├── 1-Data_pre-processing_CAN.ipynb
├── 2-CNN_Model_Development&Hyperparameter Optimization.ipynb
├── 3-Ensemble_Models-CAN.ipynb
├── CAN.png
├── LICENSE
├── Paper_2201.11812.pdf
├── README.md
├── data
├── Car_Hacking_5%.csv
└── README.md
├── framework.png
└── supplementary_code
├── CAR_IDS_SVC.ipynb
└── README.md
/1-Data_pre-processing_CAN.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# A Transfer Learning and Optimized CNN Based Intrusion Detection System for Internet of Vehicles \n",
8 | "This is the code for the paper entitled \"**A Transfer Learning and Optimized CNN Based Intrusion Detection System for Internet of Vehicles**\" accepted in IEEE International Conference on Communications (IEEE ICC). \n",
9 | "Authors: Li Yang (lyang339@uwo.ca) and Abdallah Shami (Abdallah.Shami@uwo.ca) \n",
10 | "Organization: The Optimized Computing and Communications (OC2) Lab, ECE Department, Western University\n",
11 | "\n",
12 | "**Notebook 1: Data pre-processing** \n",
13 | "Procedures: \n",
14 | " 1): Read the dataset \n",
15 | " 2): Transform the tabular data into images \n",
16 | " 3): Display the transformed images \n",
17 | " 4): Split the training and test set "
18 | ]
19 | },
20 | {
21 | "cell_type": "markdown",
22 | "metadata": {},
23 | "source": [
24 | "## Import libraries"
25 | ]
26 | },
27 | {
28 | "cell_type": "code",
29 | "execution_count": 1,
30 | "metadata": {},
31 | "outputs": [],
32 | "source": [
33 | "import numpy as np\n",
34 | "import pandas as pd\n",
35 | "import os\n",
36 | "import cv2\n",
37 | "import math\n",
38 | "import random\n",
39 | "import matplotlib.pyplot as plt\n",
40 | "import shutil\n",
41 | "from sklearn.preprocessing import QuantileTransformer\n",
42 | "from PIL import Image\n",
43 | "import warnings\n",
44 | "warnings.filterwarnings(\"ignore\")"
45 | ]
46 | },
47 | {
48 | "cell_type": "markdown",
49 | "metadata": {},
50 | "source": [
51 | "## Read the Car-Hacking/CAN-Intrusion dataset\n",
52 | "The complete Car-Hacking dataset is publicly available at: https://ocslab.hksecurity.net/Datasets/CAN-intrusion-dataset \n",
53 | "In this repository, due to the file size limit of GitHub, we use the 5% subset."
54 | ]
55 | },
56 | {
57 | "cell_type": "code",
58 | "execution_count": 3,
59 | "metadata": {
60 | "collapsed": true
61 | },
62 | "outputs": [],
63 | "source": [
64 | "#Read dataset\n",
65 | "df=pd.read_csv('data/Car_Hacking_5%.csv')"
66 | ]
67 | },
68 | {
69 | "cell_type": "code",
70 | "execution_count": 4,
71 | "metadata": {
72 | "scrolled": true
73 | },
74 | "outputs": [
75 | {
76 | "data": {
77 | "text/html": [
78 | "
\n",
79 | "\n",
92 | "
\n",
93 | " \n",
94 | " \n",
95 | " \n",
96 | " CAN ID \n",
97 | " DATA[0] \n",
98 | " DATA[1] \n",
99 | " DATA[2] \n",
100 | " DATA[3] \n",
101 | " DATA[4] \n",
102 | " DATA[5] \n",
103 | " DATA[6] \n",
104 | " DATA[7] \n",
105 | " Label \n",
106 | " \n",
107 | " \n",
108 | " \n",
109 | " \n",
110 | " 0 \n",
111 | " 1201 \n",
112 | " 41 \n",
113 | " 39 \n",
114 | " 39 \n",
115 | " 35 \n",
116 | " 0 \n",
117 | " 0 \n",
118 | " 0 \n",
119 | " 154 \n",
120 | " R \n",
121 | " \n",
122 | " \n",
123 | " 1 \n",
124 | " 809 \n",
125 | " 64 \n",
126 | " 187 \n",
127 | " 127 \n",
128 | " 20 \n",
129 | " 17 \n",
130 | " 32 \n",
131 | " 0 \n",
132 | " 20 \n",
133 | " R \n",
134 | " \n",
135 | " \n",
136 | " 2 \n",
137 | " 1349 \n",
138 | " 216 \n",
139 | " 0 \n",
140 | " 0 \n",
141 | " 136 \n",
142 | " 0 \n",
143 | " 0 \n",
144 | " 0 \n",
145 | " 0 \n",
146 | " R \n",
147 | " \n",
148 | " \n",
149 | " 3 \n",
150 | " 1201 \n",
151 | " 41 \n",
152 | " 39 \n",
153 | " 39 \n",
154 | " 35 \n",
155 | " 0 \n",
156 | " 0 \n",
157 | " 0 \n",
158 | " 154 \n",
159 | " R \n",
160 | " \n",
161 | " \n",
162 | " 4 \n",
163 | " 2 \n",
164 | " 0 \n",
165 | " 0 \n",
166 | " 0 \n",
167 | " 0 \n",
168 | " 0 \n",
169 | " 3 \n",
170 | " 2 \n",
171 | " 228 \n",
172 | " R \n",
173 | " \n",
174 | " \n",
175 | " ... \n",
176 | " ... \n",
177 | " ... \n",
178 | " ... \n",
179 | " ... \n",
180 | " ... \n",
181 | " ... \n",
182 | " ... \n",
183 | " ... \n",
184 | " ... \n",
185 | " ... \n",
186 | " \n",
187 | " \n",
188 | " 818435 \n",
189 | " 848 \n",
190 | " 5 \n",
191 | " 32 \n",
192 | " 52 \n",
193 | " 104 \n",
194 | " 117 \n",
195 | " 0 \n",
196 | " 0 \n",
197 | " 12 \n",
198 | " R \n",
199 | " \n",
200 | " \n",
201 | " 818436 \n",
202 | " 1088 \n",
203 | " 255 \n",
204 | " 0 \n",
205 | " 0 \n",
206 | " 0 \n",
207 | " 255 \n",
208 | " 134 \n",
209 | " 9 \n",
210 | " 0 \n",
211 | " R \n",
212 | " \n",
213 | " \n",
214 | " 818437 \n",
215 | " 848 \n",
216 | " 5 \n",
217 | " 32 \n",
218 | " 100 \n",
219 | " 104 \n",
220 | " 117 \n",
221 | " 0 \n",
222 | " 0 \n",
223 | " 92 \n",
224 | " R \n",
225 | " \n",
226 | " \n",
227 | " 818438 \n",
228 | " 1349 \n",
229 | " 216 \n",
230 | " 90 \n",
231 | " 0 \n",
232 | " 137 \n",
233 | " 0 \n",
234 | " 0 \n",
235 | " 0 \n",
236 | " 0 \n",
237 | " R \n",
238 | " \n",
239 | " \n",
240 | " 818439 \n",
241 | " 790 \n",
242 | " 5 \n",
243 | " 33 \n",
244 | " 48 \n",
245 | " 10 \n",
246 | " 33 \n",
247 | " 30 \n",
248 | " 0 \n",
249 | " 111 \n",
250 | " R \n",
251 | " \n",
252 | " \n",
253 | "
\n",
254 | "
818440 rows × 10 columns
\n",
255 | "
"
256 | ],
257 | "text/plain": [
258 | " CAN ID DATA[0] DATA[1] DATA[2] DATA[3] DATA[4] DATA[5] DATA[6] \\\n",
259 | "0 1201 41 39 39 35 0 0 0 \n",
260 | "1 809 64 187 127 20 17 32 0 \n",
261 | "2 1349 216 0 0 136 0 0 0 \n",
262 | "3 1201 41 39 39 35 0 0 0 \n",
263 | "4 2 0 0 0 0 0 3 2 \n",
264 | "... ... ... ... ... ... ... ... ... \n",
265 | "818435 848 5 32 52 104 117 0 0 \n",
266 | "818436 1088 255 0 0 0 255 134 9 \n",
267 | "818437 848 5 32 100 104 117 0 0 \n",
268 | "818438 1349 216 90 0 137 0 0 0 \n",
269 | "818439 790 5 33 48 10 33 30 0 \n",
270 | "\n",
271 | " DATA[7] Label \n",
272 | "0 154 R \n",
273 | "1 20 R \n",
274 | "2 0 R \n",
275 | "3 154 R \n",
276 | "4 228 R \n",
277 | "... ... ... \n",
278 | "818435 12 R \n",
279 | "818436 0 R \n",
280 | "818437 92 R \n",
281 | "818438 0 R \n",
282 | "818439 111 R \n",
283 | "\n",
284 | "[818440 rows x 10 columns]"
285 | ]
286 | },
287 | "execution_count": 4,
288 | "metadata": {},
289 | "output_type": "execute_result"
290 | }
291 | ],
292 | "source": [
293 | "df"
294 | ]
295 | },
296 | {
297 | "cell_type": "code",
298 | "execution_count": 5,
299 | "metadata": {},
300 | "outputs": [
301 | {
302 | "data": {
303 | "text/plain": [
304 | "R 701832\n",
305 | "RPM 32539\n",
306 | "gear 29944\n",
307 | "DoS 29501\n",
308 | "Fuzzy 24624\n",
309 | "Name: Label, dtype: int64"
310 | ]
311 | },
312 | "execution_count": 5,
313 | "metadata": {},
314 | "output_type": "execute_result"
315 | }
316 | ],
317 | "source": [
318 | "# The labels of the dataset. \"R\" indicates normal patterns, and there are four types of attack (DoS, fuzzy. gear spoofing, and RPM spoofing zttacks)\n",
319 | "df.Label.value_counts()"
320 | ]
321 | },
322 | {
323 | "cell_type": "markdown",
324 | "metadata": {
325 | "collapsed": true
326 | },
327 | "source": [
328 | "## Data Transformation\n",
329 | "Convert tabular data to images\n",
330 | "Procedures:\n",
331 | "1. Use quantile transform to transform the original data samples into the scale of [0,255], representing pixel values\n",
332 | "2. Generate images for each category (Normal, DoS, Fuzzy, Gear, RPM), each image consists of 27 data samples with 9 features. Thus, the size of each image is 9*9*3, length 9, width 9, and 3 color channels (RGB)."
333 | ]
334 | },
335 | {
336 | "cell_type": "code",
337 | "execution_count": 6,
338 | "metadata": {
339 | "collapsed": true
340 | },
341 | "outputs": [],
342 | "source": [
343 | "# Transform all features into the scale of [0,1]\n",
344 | "numeric_features = df.dtypes[df.dtypes != 'object'].index\n",
345 | "scaler = QuantileTransformer() \n",
346 | "df[numeric_features] = scaler.fit_transform(df[numeric_features])"
347 | ]
348 | },
349 | {
350 | "cell_type": "code",
351 | "execution_count": 7,
352 | "metadata": {},
353 | "outputs": [],
354 | "source": [
355 | "# Multiply the feature values by 255 to transform them into the scale of [0,255]\n",
356 | "df[numeric_features] = df[numeric_features].apply(\n",
357 | " lambda x: (x*255))"
358 | ]
359 | },
360 | {
361 | "cell_type": "code",
362 | "execution_count": 8,
363 | "metadata": {},
364 | "outputs": [
365 | {
366 | "data": {
367 | "text/html": [
368 | "\n",
369 | "\n",
382 | "
\n",
383 | " \n",
384 | " \n",
385 | " \n",
386 | " CAN ID \n",
387 | " DATA[0] \n",
388 | " DATA[1] \n",
389 | " DATA[2] \n",
390 | " DATA[3] \n",
391 | " DATA[4] \n",
392 | " DATA[5] \n",
393 | " DATA[6] \n",
394 | " DATA[7] \n",
395 | " \n",
396 | " \n",
397 | " \n",
398 | " \n",
399 | " count \n",
400 | " 818440.000000 \n",
401 | " 818440.000000 \n",
402 | " 818440.000000 \n",
403 | " 818440.000000 \n",
404 | " 818440.000000 \n",
405 | " 818440.000000 \n",
406 | " 818440.000000 \n",
407 | " 818440.000000 \n",
408 | " 818440.000000 \n",
409 | " \n",
410 | " \n",
411 | " mean \n",
412 | " 127.458603 \n",
413 | " 113.635407 \n",
414 | " 108.055500 \n",
415 | " 89.524039 \n",
416 | " 109.930495 \n",
417 | " 105.682464 \n",
418 | " 112.273096 \n",
419 | " 84.945440 \n",
420 | " 93.094805 \n",
421 | " \n",
422 | " \n",
423 | " std \n",
424 | " 73.780402 \n",
425 | " 89.993275 \n",
426 | " 93.448831 \n",
427 | " 100.589117 \n",
428 | " 103.632690 \n",
429 | " 95.716420 \n",
430 | " 90.993393 \n",
431 | " 101.365609 \n",
432 | " 100.186463 \n",
433 | " \n",
434 | " \n",
435 | " min \n",
436 | " 0.000000 \n",
437 | " 0.000000 \n",
438 | " 0.000000 \n",
439 | " 0.000000 \n",
440 | " 0.000000 \n",
441 | " 0.000000 \n",
442 | " 0.000000 \n",
443 | " 0.000000 \n",
444 | " 0.000000 \n",
445 | " \n",
446 | " \n",
447 | " 25% \n",
448 | " 66.876877 \n",
449 | " 0.000000 \n",
450 | " 0.000000 \n",
451 | " 0.000000 \n",
452 | " 0.000000 \n",
453 | " 0.000000 \n",
454 | " 0.000000 \n",
455 | " 0.000000 \n",
456 | " 0.000000 \n",
457 | " \n",
458 | " \n",
459 | " 50% \n",
460 | " 122.650150 \n",
461 | " 126.096096 \n",
462 | " 115.503003 \n",
463 | " 0.000000 \n",
464 | " 130.818318 \n",
465 | " 127.755255 \n",
466 | " 129.542042 \n",
467 | " 0.000000 \n",
468 | " 0.000000 \n",
469 | " \n",
470 | " \n",
471 | " 75% \n",
472 | " 190.548048 \n",
473 | " 192.462462 \n",
474 | " 193.611111 \n",
475 | " 199.099099 \n",
476 | " 190.675676 \n",
477 | " 193.355856 \n",
478 | " 190.165165 \n",
479 | " 192.207207 \n",
480 | " 190.675676 \n",
481 | " \n",
482 | " \n",
483 | " max \n",
484 | " 255.000000 \n",
485 | " 255.000000 \n",
486 | " 255.000000 \n",
487 | " 255.000000 \n",
488 | " 255.000000 \n",
489 | " 255.000000 \n",
490 | " 255.000000 \n",
491 | " 255.000000 \n",
492 | " 255.000000 \n",
493 | " \n",
494 | " \n",
495 | "
\n",
496 | "
"
497 | ],
498 | "text/plain": [
499 | " CAN ID DATA[0] DATA[1] DATA[2] \\\n",
500 | "count 818440.000000 818440.000000 818440.000000 818440.000000 \n",
501 | "mean 127.458603 113.635407 108.055500 89.524039 \n",
502 | "std 73.780402 89.993275 93.448831 100.589117 \n",
503 | "min 0.000000 0.000000 0.000000 0.000000 \n",
504 | "25% 66.876877 0.000000 0.000000 0.000000 \n",
505 | "50% 122.650150 126.096096 115.503003 0.000000 \n",
506 | "75% 190.548048 192.462462 193.611111 199.099099 \n",
507 | "max 255.000000 255.000000 255.000000 255.000000 \n",
508 | "\n",
509 | " DATA[3] DATA[4] DATA[5] DATA[6] \\\n",
510 | "count 818440.000000 818440.000000 818440.000000 818440.000000 \n",
511 | "mean 109.930495 105.682464 112.273096 84.945440 \n",
512 | "std 103.632690 95.716420 90.993393 101.365609 \n",
513 | "min 0.000000 0.000000 0.000000 0.000000 \n",
514 | "25% 0.000000 0.000000 0.000000 0.000000 \n",
515 | "50% 130.818318 127.755255 129.542042 0.000000 \n",
516 | "75% 190.675676 193.355856 190.165165 192.207207 \n",
517 | "max 255.000000 255.000000 255.000000 255.000000 \n",
518 | "\n",
519 | " DATA[7] \n",
520 | "count 818440.000000 \n",
521 | "mean 93.094805 \n",
522 | "std 100.186463 \n",
523 | "min 0.000000 \n",
524 | "25% 0.000000 \n",
525 | "50% 0.000000 \n",
526 | "75% 190.675676 \n",
527 | "max 255.000000 "
528 | ]
529 | },
530 | "execution_count": 8,
531 | "metadata": {},
532 | "output_type": "execute_result"
533 | }
534 | ],
535 | "source": [
536 | "df.describe()"
537 | ]
538 | },
539 | {
540 | "cell_type": "markdown",
541 | "metadata": {
542 | "collapsed": true
543 | },
544 | "source": [
545 | "All features are in the same scale of [0,255]"
546 | ]
547 | },
548 | {
549 | "cell_type": "markdown",
550 | "metadata": {},
551 | "source": [
552 | "### Generate images for each class"
553 | ]
554 | },
555 | {
556 | "cell_type": "code",
557 | "execution_count": 9,
558 | "metadata": {
559 | "collapsed": true
560 | },
561 | "outputs": [],
562 | "source": [
563 | "df0=df[df['Label']=='R'].drop(['Label'],axis=1)\n",
564 | "df1=df[df['Label']=='RPM'].drop(['Label'],axis=1)\n",
565 | "df2=df[df['Label']=='gear'].drop(['Label'],axis=1)\n",
566 | "df3=df[df['Label']=='DoS'].drop(['Label'],axis=1)\n",
567 | "df4=df[df['Label']=='Fuzzy'].drop(['Label'],axis=1)"
568 | ]
569 | },
570 | {
571 | "cell_type": "code",
572 | "execution_count": 30,
573 | "metadata": {},
574 | "outputs": [],
575 | "source": [
576 | "# Generate 9*9 color images for class 0 (Normal)\n",
577 | "count=0\n",
578 | "ims = []\n",
579 | "\n",
580 | "image_path = \"train/0/\"\n",
581 | "os.makedirs(image_path)\n",
582 | "\n",
583 | "for i in range(0, len(df0)): \n",
584 | " count=count+1\n",
585 | " if count<=27: \n",
586 | " im=df0.iloc[i].values\n",
587 | " ims=np.append(ims,im)\n",
588 | " else:\n",
589 | " ims=np.array(ims).reshape(9,9,3)\n",
590 | " array = np.array(ims, dtype=np.uint8)\n",
591 | " new_image = Image.fromarray(array)\n",
592 | " new_image.save(image_path+str(i)+'.png')\n",
593 | " count=0\n",
594 | " ims = []"
595 | ]
596 | },
597 | {
598 | "cell_type": "code",
599 | "execution_count": 31,
600 | "metadata": {},
601 | "outputs": [],
602 | "source": [
603 | "# Generate 9*9 color images for class 1 (RPM spoofing)\n",
604 | "count=0\n",
605 | "ims = []\n",
606 | "\n",
607 | "image_path = \"train/1/\"\n",
608 | "os.makedirs(image_path)\n",
609 | "\n",
610 | "for i in range(0, len(df1)): \n",
611 | " count=count+1\n",
612 | " if count<=27: \n",
613 | " im=df1.iloc[i].values\n",
614 | " ims=np.append(ims,im)\n",
615 | " else:\n",
616 | " ims=np.array(ims).reshape(9,9,3)\n",
617 | " array = np.array(ims, dtype=np.uint8)\n",
618 | " new_image = Image.fromarray(array)\n",
619 | " new_image.save(image_path+str(i)+'.png')\n",
620 | " count=0\n",
621 | " ims = []"
622 | ]
623 | },
624 | {
625 | "cell_type": "code",
626 | "execution_count": 33,
627 | "metadata": {},
628 | "outputs": [],
629 | "source": [
630 | "# Generate 9*9 color images for class 2 (Gear spoofing)\n",
631 | "count=0\n",
632 | "ims = []\n",
633 | "\n",
634 | "image_path = \"train/2/\"\n",
635 | "os.makedirs(image_path)\n",
636 | "\n",
637 | "for i in range(0, len(df2)): \n",
638 | " count=count+1\n",
639 | " if count<=27: \n",
640 | " im=df2.iloc[i].values\n",
641 | " ims=np.append(ims,im)\n",
642 | " else:\n",
643 | " ims=np.array(ims).reshape(9,9,3)\n",
644 | " array = np.array(ims, dtype=np.uint8)\n",
645 | " new_image = Image.fromarray(array)\n",
646 | " new_image.save(image_path+str(i)+'.png')\n",
647 | " count=0\n",
648 | " ims = []"
649 | ]
650 | },
651 | {
652 | "cell_type": "code",
653 | "execution_count": 34,
654 | "metadata": {
655 | "collapsed": true
656 | },
657 | "outputs": [],
658 | "source": [
659 | "# Generate 9*9 color images for class 3 (DoS attack)\n",
660 | "count=0\n",
661 | "ims = []\n",
662 | "\n",
663 | "image_path = \"train/3/\"\n",
664 | "os.makedirs(image_path)\n",
665 | "\n",
666 | "\n",
667 | "for i in range(0, len(df3)): \n",
668 | " count=count+1\n",
669 | " if count<=27: \n",
670 | " im=df3.iloc[i].values\n",
671 | " ims=np.append(ims,im)\n",
672 | " else:\n",
673 | " ims=np.array(ims).reshape(9,9,3)\n",
674 | " array = np.array(ims, dtype=np.uint8)\n",
675 | " new_image = Image.fromarray(array)\n",
676 | " new_image.save(image_path+str(i)+'.png')\n",
677 | " count=0\n",
678 | " ims = []"
679 | ]
680 | },
681 | {
682 | "cell_type": "code",
683 | "execution_count": 35,
684 | "metadata": {
685 | "collapsed": true
686 | },
687 | "outputs": [],
688 | "source": [
689 | "# Generate 9*9 color images for class 4 (Fuzzy attack)\n",
690 | "count=0\n",
691 | "ims = []\n",
692 | "\n",
693 | "image_path = \"train/4/\"\n",
694 | "os.makedirs(image_path)\n",
695 | "\n",
696 | "\n",
697 | "for i in range(0, len(df4)): \n",
698 | " count=count+1\n",
699 | " if count<=27: \n",
700 | " im=df4.iloc[i].values\n",
701 | " ims=np.append(ims,im)\n",
702 | " else:\n",
703 | " ims=np.array(ims).reshape(9,9,3)\n",
704 | " array = np.array(ims, dtype=np.uint8)\n",
705 | " new_image = Image.fromarray(array)\n",
706 | " new_image.save(image_path+str(i)+'.png')\n",
707 | " count=0\n",
708 | " ims = []"
709 | ]
710 | },
711 | {
712 | "cell_type": "markdown",
713 | "metadata": {},
714 | "source": [
715 | "## Split the training and test set "
716 | ]
717 | },
718 | {
719 | "cell_type": "code",
720 | "execution_count": 56,
721 | "metadata": {},
722 | "outputs": [
723 | {
724 | "name": "stdout",
725 | "output_type": "stream",
726 | "text": [
727 | "29227\n"
728 | ]
729 | }
730 | ],
731 | "source": [
732 | "# Create folders to store images\n",
733 | "Train_Dir='./train/'\n",
734 | "Val_Dir='./test/'\n",
735 | "allimgs=[]\n",
736 | "for subdir in os.listdir(Train_Dir):\n",
737 | " for filename in os.listdir(os.path.join(Train_Dir,subdir)):\n",
738 | " filepath=os.path.join(Train_Dir,subdir,filename)\n",
739 | " allimgs.append(filepath)\n",
740 | "print(len(allimgs)) # Print the total number of images"
741 | ]
742 | },
743 | {
744 | "cell_type": "code",
745 | "execution_count": 58,
746 | "metadata": {},
747 | "outputs": [],
748 | "source": [
749 | "#split a test set from the dataset, train/test size = 80%/20%\n",
750 | "Numbers=len(allimgs)//5 \t#size of test set (20%)\n",
751 | "\n",
752 | "def mymovefile(srcfile,dstfile):\n",
753 | " if not os.path.isfile(srcfile):\n",
754 | " print (\"%s not exist!\"%(srcfile))\n",
755 | " else:\n",
756 | " fpath,fname=os.path.split(dstfile) \n",
757 | " if not os.path.exists(fpath):\n",
758 | " os.makedirs(fpath) \n",
759 | " shutil.move(srcfile,dstfile) \n",
760 | " #print (\"move %s -> %s\"%(srcfile,dstfile))"
761 | ]
762 | },
763 | {
764 | "cell_type": "code",
765 | "execution_count": 59,
766 | "metadata": {
767 | "scrolled": true
768 | },
769 | "outputs": [
770 | {
771 | "data": {
772 | "text/plain": [
773 | "5845"
774 | ]
775 | },
776 | "execution_count": 59,
777 | "metadata": {},
778 | "output_type": "execute_result"
779 | }
780 | ],
781 | "source": [
782 | "# The size of test set\n",
783 | "Numbers"
784 | ]
785 | },
786 | {
787 | "cell_type": "code",
788 | "execution_count": 60,
789 | "metadata": {},
790 | "outputs": [
791 | {
792 | "name": "stdout",
793 | "output_type": "stream",
794 | "text": [
795 | "Finish creating test set\n"
796 | ]
797 | }
798 | ],
799 | "source": [
800 | "# Create the test set\n",
801 | "val_imgs=random.sample(allimgs,Numbers)\n",
802 | "for img in val_imgs:\n",
803 | " dest_path=img.replace(Train_Dir,Val_Dir)\n",
804 | " mymovefile(img,dest_path)\n",
805 | "print('Finish creating test set')"
806 | ]
807 | },
808 | {
809 | "cell_type": "code",
810 | "execution_count": 61,
811 | "metadata": {
812 | "collapsed": true
813 | },
814 | "outputs": [],
815 | "source": [
816 | "#resize the images 224*224 for better CNN training\n",
817 | "def get_224(folder,dstdir):\n",
818 | " imgfilepaths=[]\n",
819 | " for root,dirs,imgs in os.walk(folder):\n",
820 | " for thisimg in imgs:\n",
821 | " thisimg_path=os.path.join(root,thisimg)\n",
822 | " imgfilepaths.append(thisimg_path)\n",
823 | " for thisimg_path in imgfilepaths:\n",
824 | " dir_name,filename=os.path.split(thisimg_path)\n",
825 | " dir_name=dir_name.replace(folder,dstdir)\n",
826 | " new_file_path=os.path.join(dir_name,filename)\n",
827 | " if not os.path.exists(dir_name):\n",
828 | " os.makedirs(dir_name)\n",
829 | " img=cv2.imread(thisimg_path)\n",
830 | " img=cv2.resize(img,(224,224))\n",
831 | " cv2.imwrite(new_file_path,img)\n",
832 | " print('Finish resizing'.format(folder=folder))"
833 | ]
834 | },
835 | {
836 | "cell_type": "code",
837 | "execution_count": 62,
838 | "metadata": {},
839 | "outputs": [
840 | {
841 | "name": "stdout",
842 | "output_type": "stream",
843 | "text": [
844 | "Finish resizing\n"
845 | ]
846 | }
847 | ],
848 | "source": [
849 | "DATA_DIR_224='./train_224/'\n",
850 | "get_224(folder='./train/',dstdir=DATA_DIR_224)"
851 | ]
852 | },
853 | {
854 | "cell_type": "code",
855 | "execution_count": 63,
856 | "metadata": {},
857 | "outputs": [
858 | {
859 | "name": "stdout",
860 | "output_type": "stream",
861 | "text": [
862 | "Finish resizing\n"
863 | ]
864 | }
865 | ],
866 | "source": [
867 | "DATA_DIR2_224='./test_224/'\n",
868 | "get_224(folder='./test/',dstdir=DATA_DIR2_224)"
869 | ]
870 | },
871 | {
872 | "cell_type": "markdown",
873 | "metadata": {},
874 | "source": [
875 | "### Display samples for each category"
876 | ]
877 | },
878 | {
879 | "cell_type": "code",
880 | "execution_count": 2,
881 | "metadata": {},
882 | "outputs": [
883 | {
884 | "data": {
885 | "image/png": "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\n",
886 | "text/plain": [
887 | ""
888 | ]
889 | },
890 | "metadata": {
891 | "needs_background": "light"
892 | },
893 | "output_type": "display_data"
894 | }
895 | ],
896 | "source": [
897 | "# Read the images for each category, the file name may vary (27.png, 83.png...)\n",
898 | "img1 = Image.open('./train_224/0/27.png')\n",
899 | "img2 = Image.open('./train_224/1/83.png')\n",
900 | "img3 = Image.open('./train_224/2/27.png')\n",
901 | "img4 = Image.open('./train_224/3/27.png')\n",
902 | "img5 = Image.open('./train_224/4/27.png')\n",
903 | "\n",
904 | "plt.figure(figsize=(10, 10)) \n",
905 | "plt.subplot(1,5,1)\n",
906 | "plt.imshow(img1)\n",
907 | "plt.title(\"Normal\")\n",
908 | "plt.subplot(1,5,2)\n",
909 | "plt.imshow(img2)\n",
910 | "plt.title(\"RPM Spoofing\")\n",
911 | "plt.subplot(1,5,3)\n",
912 | "plt.imshow(img3)\n",
913 | "plt.title(\"Gear Spoofing\")\n",
914 | "plt.subplot(1,5,4)\n",
915 | "plt.imshow(img4)\n",
916 | "plt.title(\"DoS Attack\")\n",
917 | "plt.subplot(1,5,5)\n",
918 | "plt.imshow(img5)\n",
919 | "plt.title(\"Fuzzy Attack\")\n",
920 | "plt.show() # display it"
921 | ]
922 | },
923 | {
924 | "cell_type": "code",
925 | "execution_count": null,
926 | "metadata": {},
927 | "outputs": [],
928 | "source": []
929 | }
930 | ],
931 | "metadata": {
932 | "anaconda-cloud": {},
933 | "kernelspec": {
934 | "display_name": "Python 3",
935 | "language": "python",
936 | "name": "python3"
937 | },
938 | "language_info": {
939 | "codemirror_mode": {
940 | "name": "ipython",
941 | "version": 3
942 | },
943 | "file_extension": ".py",
944 | "mimetype": "text/x-python",
945 | "name": "python",
946 | "nbconvert_exporter": "python",
947 | "pygments_lexer": "ipython3",
948 | "version": "3.6.8"
949 | }
950 | },
951 | "nbformat": 4,
952 | "nbformat_minor": 2
953 | }
954 |
--------------------------------------------------------------------------------
/CAN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Western-OC2-Lab/Intrusion-Detection-System-Using-CNN-and-Transfer-Learning/c2aacb76cc184dc1ea29f2c6b97e5bbde8221f71/CAN.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Western OC2 Lab
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Paper_2201.11812.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Western-OC2-Lab/Intrusion-Detection-System-Using-CNN-and-Transfer-Learning/c2aacb76cc184dc1ea29f2c6b97e5bbde8221f71/Paper_2201.11812.pdf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Intrusion-Detection-System-Using-CNN-and-Transfer-Learning
2 |
3 | This is the code for the paper entitled "**[A Transfer Learning and Optimized CNN Based Intrusion Detection System for Internet of Vehicles](https://arxiv.org/pdf/2201.11812.pdf)**" published in **IEEE International Conference on Communications (IEEE ICC)**, doi: [10.1109/ICC45855.2022.9838780](https://ieeexplore.ieee.org/document/9838780).
4 | - Authors: Li Yang and Abdallah Shami
5 | - Organization: The Optimized Computing and Communications (OC2) Lab, ECE Department, Western University
6 |
7 | This repository introduces how to use **convolutional neural networks (CNNs)** and **transfer learning** techniques to develop **intrusion detection systems**. **Ensemble learning** and **hyperparameter optimization techniques** are also used to achieve optimized model performance.
8 |
9 | - Another **intrusion detection system development code** using **decision tree-based machine learning algorithms (Decision tree, random forest, XGBoost, stacking, etc.)** can be found in: [Intrusion-Detection-System-Using-Machine-Learning](https://github.com/Western-OC2-Lab/Intrusion-Detection-System-Using-Machine-Learning)
10 |
11 | - A comprehensive **hyperparameter optimization** tutorial code can be found in: [Hyperparameter-Optimization-of-Machine-Learning-Algorithms](https://github.com/LiYangHart/Hyperparameter-Optimization-of-Machine-Learning-Algorithms)
12 |
13 | ## Abstract of The Paper
14 | Modern vehicles, including autonomous vehicles and connected vehicles, are increasingly connected to the external world, which enables various functionalities and services. However, the improving connectivity also increases the attack surfaces of the Internet of Vehicles (IoV), causing its vulnerabilities to cyber-threats. Due to the lack of authentication and encryption procedures in vehicular networks, Intrusion Detection Systems (IDSs) are essential approaches to protect modern vehicle systems from network attacks. In this paper, a transfer learning and ensemble learning-based IDS is proposed for IoV systems using convolutional neural networks (CNNs) and hyper-parameter optimization techniques. In the experiments, the proposed IDS has demonstrated over 99.25% detection rates and F1-scores on two well-known public benchmark IoV security datasets: the Car-Hacking dataset and the CICIDS2017 dataset. This shows the effectiveness of the proposed IDS for cyber-attack detection in both intra-vehicle and external vehicular networks.
15 |
16 |
17 |
18 |
19 |
20 |
21 | ## Implementation
22 | ### CNN Models
23 | * VGG16
24 | * VGG19
25 | * Xception
26 | * Inception
27 | * Resnet
28 | * InceptionResnet
29 |
30 | ### Ensemble Learning Models
31 | * Bagging
32 | * Probability Averaging
33 | * Concatenation
34 |
35 | ### Hyperparameter Optimization Methods
36 | * Random Search (RS)
37 | * Bayesian Optimization - Tree Parzen Estimator(BO-TPE)
38 |
39 | ### Dataset
40 | 1. CAN-intrusion/Car-Hacking dataset, a benchmark network security dataset for intra-vehicle intrusion detection
41 | * Publicly available at: https://ocslab.hksecurity.net/Datasets/CAN-intrusion-dataset
42 | * Can be processed using the same code
43 |
44 | 2. CICIDS2017 dataset, a popular network traffic dataset for intrusion detection problems
45 | * Publicly available at: https://www.unb.ca/cic/datasets/ids-2017.html
46 |
47 | For the purpose of displaying the experimental results in Jupyter Notebook, the sampled subset of the CAN-intrusion dataset is used in the sample code. The subsets are in the "[data](https://github.com/Western-OC2-Lab/Intrusion-Detection-System-Using-CNN-and-Transfer-Learning/tree/main/data)" folder.
48 |
49 | ### Code
50 | * [1-Data_pre-processing_CAN.ipynb](https://github.com/Western-OC2-Lab/Intrusion-Detection-System-Using-CNN-and-Transfer-Learning/blob/main/1-Data_pre-processing_CAN.ipynb): code for data pre-processing and transformation (tabular data to images).
51 | * [2-CNN_Model_Development&Hyperparameter Optimization.ipynb](https://github.com/Western-OC2-Lab/Intrusion-Detection-System-Using-CNN-and-Transfer-Learning/blob/main/2-CNN_Model_Development%26Hyperparameter%20Optimization.ipynb): code for the development and CNN models and their hyperparameter optimization.
52 | * [3-Ensemble_Models-CAN.ipynb](https://github.com/Western-OC2-Lab/Intrusion-Detection-System-Using-CNN-and-Transfer-Learning/blob/main/3-Ensemble_Models-CAN.ipynb): code for the construction of three ensemble learning techniques.
53 |
54 | Libraries
55 | * Python 3.5+
56 | * [Keras 2.1.0+](hhttps://keras.io/)
57 | * [Tensorflow 1.10.0+](https://www.tensorflow.org/install/gpu)
58 | * [OpenCV-python](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html)
59 | * [hyperopt](https://github.com/hyperopt/hyperopt)
60 |
61 | ## Contact-Info
62 | Please feel free to contact us for any questions or cooperation opportunities. We will be happy to help.
63 | * Email: [liyanghart@gmail.com](mailto:liyanghart@gmail.com) or [Abdallah.Shami@uwo.ca](mailto:Abdallah.Shami@uwo.ca)
64 | * GitHub: [LiYangHart](https://github.com/LiYangHart) and [Western OC2 Lab](https://github.com/Western-OC2-Lab/)
65 | * LinkedIn: [Li Yang](https://www.linkedin.com/in/li-yang-phd-65a190176/)
66 | * Google Scholar: [Li Yang](https://scholar.google.com.eg/citations?user=XEfM7bIAAAAJ&hl=en) and [OC2 Lab](https://scholar.google.com.eg/citations?user=oiebNboAAAAJ&hl=en)
67 |
68 | ## Citation
69 | If you find this repository useful in your research, please cite this article as:
70 |
71 | L. Yang and A. Shami, "A Transfer Learning and Optimized CNN Based Intrusion Detection System for Internet of Vehicles," ICC 2022 - IEEE International Conference on Communications, 2022, pp. 2774-2779, doi: 10.1109/ICC45855.2022.9838780.
72 |
73 | ```
74 | @INPROCEEDINGS{9838780,
75 | author={Yang, Li and Shami, Abdallah},
76 | booktitle={ICC 2022 - IEEE International Conference on Communications},
77 | title={A Transfer Learning and Optimized CNN Based Intrusion Detection System for Internet of Vehicles},
78 | year={2022},
79 | pages={2774-2779},
80 | doi={10.1109/ICC45855.2022.9838780}}
81 | ```
82 |
--------------------------------------------------------------------------------
/data/README.md:
--------------------------------------------------------------------------------
1 | # The sampled datasets used for the experiments in the sample code
2 |
3 | **Car_Hacking_5%.csv**: The 5% randomly sampled subset of the [Car Hacking dataset](https://ocslab.hksecurity.net/Datasets/CAN-intrusion-dataset)
4 |
--------------------------------------------------------------------------------
/framework.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Western-OC2-Lab/Intrusion-Detection-System-Using-CNN-and-Transfer-Learning/c2aacb76cc184dc1ea29f2c6b97e5bbde8221f71/framework.png
--------------------------------------------------------------------------------
/supplementary_code/CAR_IDS_SVC.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "name": "CAR_IDS_LOGISTIC_SVM (1).ipynb",
7 | "provenance": [],
8 | "collapsed_sections": []
9 | },
10 | "kernelspec": {
11 | "name": "python3",
12 | "display_name": "Python 3"
13 | },
14 | "language_info": {
15 | "name": "python"
16 | }
17 | },
18 | "cells": [
19 | {
20 | "cell_type": "code",
21 | "execution_count": 1,
22 | "metadata": {
23 | "id": "lGiyP2dR6Jw-"
24 | },
25 | "outputs": [],
26 | "source": [
27 | "import numpy as np\n",
28 | "import pandas as pd\n",
29 | "from sklearn.linear_model import LogisticRegression\n",
30 | "from sklearn.ensemble import RandomForestClassifier"
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "source": [
36 | "def changecolumn(dataset, AttackType):\n",
37 | " df = pd.read_csv(dataset).sample(frac = 0.1, random_state = 20, replace = False).reset_index(drop=True)\n",
38 | " df.columns = [\"Timestamp\", \"CAN ID\", \"Byte\", \"DATA[0]\",\"DATA[1]\",\"DATA[2]\",\"DATA[3]\",\"DATA[4]\",\"DATA[5]\",\"DATA[6]\",\"DATA[7]\",\"AttackType\"]\n",
39 | " df['AttackType'] = np.where(df['AttackType'] == 'T',AttackType, 'Normal Message')\n",
40 | " df.dropna()\n",
41 | " return df\n",
42 | "\n",
43 | "dfDos = changecolumn('DoS_dataset.csv','DoS Attack')\n",
44 | "dfFuzzy = changecolumn('Fuzzy_dataset.csv','Fuzzy Attack')\n",
45 | "dfGear = changecolumn('gear_dataset.csv','Gear Spooing Attack')\n",
46 | "dfRPM = changecolumn('RPM_dataset.csv','RPM Spoofing Attack')\n",
47 | "frames = [dfDos, dfFuzzy, dfGear, dfRPM]\n",
48 | "df = pd.concat(frames)\n",
49 | "print(df.head(10))\n",
50 | "print(df.shape)\n"
51 | ],
52 | "metadata": {
53 | "colab": {
54 | "base_uri": "https://localhost:8080/"
55 | },
56 | "id": "K4qXdKi-756E",
57 | "outputId": "fcf9fee4-8ccb-48a1-99bc-401628828b0f"
58 | },
59 | "execution_count": 2,
60 | "outputs": [
61 | {
62 | "output_type": "stream",
63 | "name": "stdout",
64 | "text": [
65 | " Timestamp CAN ID Byte DATA[0] DATA[1] DATA[2] DATA[3] DATA[4] DATA[5] \\\n",
66 | "0 1.478200e+09 0000 8 00 00 00 00 00 00 \n",
67 | "1 1.478201e+09 0131 8 1b 80 00 00 3f 7f \n",
68 | "2 1.478199e+09 00a1 8 80 89 00 00 24 00 \n",
69 | "3 1.478200e+09 0260 8 18 21 22 30 08 8f \n",
70 | "4 1.478201e+09 02c0 8 14 00 00 00 00 00 \n",
71 | "5 1.478200e+09 0130 8 0b 80 00 ff 08 80 \n",
72 | "6 1.478200e+09 0370 8 00 20 00 00 00 00 \n",
73 | "7 1.478199e+09 04f0 8 00 00 00 80 00 69 \n",
74 | "8 1.478199e+09 0130 8 05 80 00 ff 0b 80 \n",
75 | "9 1.478198e+09 0131 8 f7 7f 00 00 4c 7f \n",
76 | "\n",
77 | " DATA[6] DATA[7] AttackType \n",
78 | "0 00 00 DoS Attack \n",
79 | "1 0e a6 Normal Message \n",
80 | "2 00 00 Normal Message \n",
81 | "3 70 05 Normal Message \n",
82 | "4 00 00 Normal Message \n",
83 | "5 04 88 Normal Message \n",
84 | "6 00 00 Normal Message \n",
85 | "7 d1 13 Normal Message \n",
86 | "8 0c ed Normal Message \n",
87 | "9 0d e7 Normal Message \n",
88 | "(1656947, 12)\n"
89 | ]
90 | }
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "source": [
96 | "print(df.dtypes)\n",
97 | "df = df.dropna()\n",
98 | "def changecolumntype(df):\n",
99 | " for column in df[['CAN ID', 'DATA[0]', 'DATA[1]', 'DATA[2]', 'DATA[3]', 'DATA[4]', 'DATA[5]', 'DATA[6]', 'DATA[7]']]:\n",
100 | " df[column] = df[column].apply(lambda x: int(str(x), base=16))\n",
101 | " return df\n",
102 | "\n",
103 | "df = changecolumntype(df)\n",
104 | "print(df.dtypes)\n",
105 | "df.head(10)"
106 | ],
107 | "metadata": {
108 | "colab": {
109 | "base_uri": "https://localhost:8080/",
110 | "height": 1000
111 | },
112 | "id": "XZTb7XOpJhQw",
113 | "outputId": "2755474a-cd7b-4575-cca0-7b7017da8297"
114 | },
115 | "execution_count": 3,
116 | "outputs": [
117 | {
118 | "output_type": "stream",
119 | "name": "stdout",
120 | "text": [
121 | "Timestamp float64\n",
122 | "CAN ID object\n",
123 | "Byte int64\n",
124 | "DATA[0] object\n",
125 | "DATA[1] object\n",
126 | "DATA[2] object\n",
127 | "DATA[3] object\n",
128 | "DATA[4] object\n",
129 | "DATA[5] object\n",
130 | "DATA[6] object\n",
131 | "DATA[7] object\n",
132 | "AttackType object\n",
133 | "dtype: object\n",
134 | "Timestamp float64\n",
135 | "CAN ID int64\n",
136 | "Byte int64\n",
137 | "DATA[0] int64\n",
138 | "DATA[1] int64\n",
139 | "DATA[2] int64\n",
140 | "DATA[3] int64\n",
141 | "DATA[4] int64\n",
142 | "DATA[5] int64\n",
143 | "DATA[6] int64\n",
144 | "DATA[7] int64\n",
145 | "AttackType object\n",
146 | "dtype: object\n"
147 | ]
148 | },
149 | {
150 | "output_type": "execute_result",
151 | "data": {
152 | "text/html": [
153 | "\n",
154 | " \n",
155 | "
\n",
156 | "
\n",
157 | "\n",
170 | "
\n",
171 | " \n",
172 | " \n",
173 | " \n",
174 | " Timestamp \n",
175 | " CAN ID \n",
176 | " Byte \n",
177 | " DATA[0] \n",
178 | " DATA[1] \n",
179 | " DATA[2] \n",
180 | " DATA[3] \n",
181 | " DATA[4] \n",
182 | " DATA[5] \n",
183 | " DATA[6] \n",
184 | " DATA[7] \n",
185 | " AttackType \n",
186 | " \n",
187 | " \n",
188 | " \n",
189 | " \n",
190 | " 0 \n",
191 | " 1.478200e+09 \n",
192 | " 0 \n",
193 | " 8 \n",
194 | " 0 \n",
195 | " 0 \n",
196 | " 0 \n",
197 | " 0 \n",
198 | " 0 \n",
199 | " 0 \n",
200 | " 0 \n",
201 | " 0 \n",
202 | " DoS Attack \n",
203 | " \n",
204 | " \n",
205 | " 1 \n",
206 | " 1.478201e+09 \n",
207 | " 305 \n",
208 | " 8 \n",
209 | " 27 \n",
210 | " 128 \n",
211 | " 0 \n",
212 | " 0 \n",
213 | " 63 \n",
214 | " 127 \n",
215 | " 14 \n",
216 | " 166 \n",
217 | " Normal Message \n",
218 | " \n",
219 | " \n",
220 | " 2 \n",
221 | " 1.478199e+09 \n",
222 | " 161 \n",
223 | " 8 \n",
224 | " 128 \n",
225 | " 137 \n",
226 | " 0 \n",
227 | " 0 \n",
228 | " 36 \n",
229 | " 0 \n",
230 | " 0 \n",
231 | " 0 \n",
232 | " Normal Message \n",
233 | " \n",
234 | " \n",
235 | " 3 \n",
236 | " 1.478200e+09 \n",
237 | " 608 \n",
238 | " 8 \n",
239 | " 24 \n",
240 | " 33 \n",
241 | " 34 \n",
242 | " 48 \n",
243 | " 8 \n",
244 | " 143 \n",
245 | " 112 \n",
246 | " 5 \n",
247 | " Normal Message \n",
248 | " \n",
249 | " \n",
250 | " 4 \n",
251 | " 1.478201e+09 \n",
252 | " 704 \n",
253 | " 8 \n",
254 | " 20 \n",
255 | " 0 \n",
256 | " 0 \n",
257 | " 0 \n",
258 | " 0 \n",
259 | " 0 \n",
260 | " 0 \n",
261 | " 0 \n",
262 | " Normal Message \n",
263 | " \n",
264 | " \n",
265 | " 5 \n",
266 | " 1.478200e+09 \n",
267 | " 304 \n",
268 | " 8 \n",
269 | " 11 \n",
270 | " 128 \n",
271 | " 0 \n",
272 | " 255 \n",
273 | " 8 \n",
274 | " 128 \n",
275 | " 4 \n",
276 | " 136 \n",
277 | " Normal Message \n",
278 | " \n",
279 | " \n",
280 | " 6 \n",
281 | " 1.478200e+09 \n",
282 | " 880 \n",
283 | " 8 \n",
284 | " 0 \n",
285 | " 32 \n",
286 | " 0 \n",
287 | " 0 \n",
288 | " 0 \n",
289 | " 0 \n",
290 | " 0 \n",
291 | " 0 \n",
292 | " Normal Message \n",
293 | " \n",
294 | " \n",
295 | " 7 \n",
296 | " 1.478199e+09 \n",
297 | " 1264 \n",
298 | " 8 \n",
299 | " 0 \n",
300 | " 0 \n",
301 | " 0 \n",
302 | " 128 \n",
303 | " 0 \n",
304 | " 105 \n",
305 | " 209 \n",
306 | " 19 \n",
307 | " Normal Message \n",
308 | " \n",
309 | " \n",
310 | " 8 \n",
311 | " 1.478199e+09 \n",
312 | " 304 \n",
313 | " 8 \n",
314 | " 5 \n",
315 | " 128 \n",
316 | " 0 \n",
317 | " 255 \n",
318 | " 11 \n",
319 | " 128 \n",
320 | " 12 \n",
321 | " 237 \n",
322 | " Normal Message \n",
323 | " \n",
324 | " \n",
325 | " 9 \n",
326 | " 1.478198e+09 \n",
327 | " 305 \n",
328 | " 8 \n",
329 | " 247 \n",
330 | " 127 \n",
331 | " 0 \n",
332 | " 0 \n",
333 | " 76 \n",
334 | " 127 \n",
335 | " 13 \n",
336 | " 231 \n",
337 | " Normal Message \n",
338 | " \n",
339 | " \n",
340 | "
\n",
341 | "
\n",
342 | "
\n",
345 | " \n",
346 | " \n",
348 | " \n",
349 | " \n",
350 | " \n",
351 | " \n",
352 | " \n",
353 | " \n",
390 | "\n",
391 | " \n",
415 | "
\n",
416 | "
\n",
417 | " "
418 | ],
419 | "text/plain": [
420 | " Timestamp CAN ID Byte DATA[0] DATA[1] DATA[2] DATA[3] DATA[4] \\\n",
421 | "0 1.478200e+09 0 8 0 0 0 0 0 \n",
422 | "1 1.478201e+09 305 8 27 128 0 0 63 \n",
423 | "2 1.478199e+09 161 8 128 137 0 0 36 \n",
424 | "3 1.478200e+09 608 8 24 33 34 48 8 \n",
425 | "4 1.478201e+09 704 8 20 0 0 0 0 \n",
426 | "5 1.478200e+09 304 8 11 128 0 255 8 \n",
427 | "6 1.478200e+09 880 8 0 32 0 0 0 \n",
428 | "7 1.478199e+09 1264 8 0 0 0 128 0 \n",
429 | "8 1.478199e+09 304 8 5 128 0 255 11 \n",
430 | "9 1.478198e+09 305 8 247 127 0 0 76 \n",
431 | "\n",
432 | " DATA[5] DATA[6] DATA[7] AttackType \n",
433 | "0 0 0 0 DoS Attack \n",
434 | "1 127 14 166 Normal Message \n",
435 | "2 0 0 0 Normal Message \n",
436 | "3 143 112 5 Normal Message \n",
437 | "4 0 0 0 Normal Message \n",
438 | "5 128 4 136 Normal Message \n",
439 | "6 0 0 0 Normal Message \n",
440 | "7 105 209 19 Normal Message \n",
441 | "8 128 12 237 Normal Message \n",
442 | "9 127 13 231 Normal Message "
443 | ]
444 | },
445 | "metadata": {},
446 | "execution_count": 3
447 | }
448 | ]
449 | },
450 | {
451 | "cell_type": "code",
452 | "source": [
453 | "df['Message'] = df.iloc[:,3:11].apply(lambda x: ''.join(x.astype(str)), axis = 1)\n",
454 | "df.head(10)"
455 | ],
456 | "metadata": {
457 | "colab": {
458 | "base_uri": "https://localhost:8080/",
459 | "height": 601
460 | },
461 | "id": "ym4-oGjemqFD",
462 | "outputId": "2dc335e5-788b-41ff-d48c-1a2602f67391"
463 | },
464 | "execution_count": 4,
465 | "outputs": [
466 | {
467 | "output_type": "execute_result",
468 | "data": {
469 | "text/html": [
470 | "\n",
471 | " \n",
472 | "
\n",
473 | "
\n",
474 | "\n",
487 | "
\n",
488 | " \n",
489 | " \n",
490 | " \n",
491 | " Timestamp \n",
492 | " CAN ID \n",
493 | " Byte \n",
494 | " DATA[0] \n",
495 | " DATA[1] \n",
496 | " DATA[2] \n",
497 | " DATA[3] \n",
498 | " DATA[4] \n",
499 | " DATA[5] \n",
500 | " DATA[6] \n",
501 | " DATA[7] \n",
502 | " AttackType \n",
503 | " Message \n",
504 | " \n",
505 | " \n",
506 | " \n",
507 | " \n",
508 | " 0 \n",
509 | " 1.478200e+09 \n",
510 | " 0 \n",
511 | " 8 \n",
512 | " 0 \n",
513 | " 0 \n",
514 | " 0 \n",
515 | " 0 \n",
516 | " 0 \n",
517 | " 0 \n",
518 | " 0 \n",
519 | " 0 \n",
520 | " DoS Attack \n",
521 | " 00000000 \n",
522 | " \n",
523 | " \n",
524 | " 1 \n",
525 | " 1.478201e+09 \n",
526 | " 305 \n",
527 | " 8 \n",
528 | " 27 \n",
529 | " 128 \n",
530 | " 0 \n",
531 | " 0 \n",
532 | " 63 \n",
533 | " 127 \n",
534 | " 14 \n",
535 | " 166 \n",
536 | " Normal Message \n",
537 | " 27128006312714166 \n",
538 | " \n",
539 | " \n",
540 | " 2 \n",
541 | " 1.478199e+09 \n",
542 | " 161 \n",
543 | " 8 \n",
544 | " 128 \n",
545 | " 137 \n",
546 | " 0 \n",
547 | " 0 \n",
548 | " 36 \n",
549 | " 0 \n",
550 | " 0 \n",
551 | " 0 \n",
552 | " Normal Message \n",
553 | " 1281370036000 \n",
554 | " \n",
555 | " \n",
556 | " 3 \n",
557 | " 1.478200e+09 \n",
558 | " 608 \n",
559 | " 8 \n",
560 | " 24 \n",
561 | " 33 \n",
562 | " 34 \n",
563 | " 48 \n",
564 | " 8 \n",
565 | " 143 \n",
566 | " 112 \n",
567 | " 5 \n",
568 | " Normal Message \n",
569 | " 2433344881431125 \n",
570 | " \n",
571 | " \n",
572 | " 4 \n",
573 | " 1.478201e+09 \n",
574 | " 704 \n",
575 | " 8 \n",
576 | " 20 \n",
577 | " 0 \n",
578 | " 0 \n",
579 | " 0 \n",
580 | " 0 \n",
581 | " 0 \n",
582 | " 0 \n",
583 | " 0 \n",
584 | " Normal Message \n",
585 | " 200000000 \n",
586 | " \n",
587 | " \n",
588 | " 5 \n",
589 | " 1.478200e+09 \n",
590 | " 304 \n",
591 | " 8 \n",
592 | " 11 \n",
593 | " 128 \n",
594 | " 0 \n",
595 | " 255 \n",
596 | " 8 \n",
597 | " 128 \n",
598 | " 4 \n",
599 | " 136 \n",
600 | " Normal Message \n",
601 | " 11128025581284136 \n",
602 | " \n",
603 | " \n",
604 | " 6 \n",
605 | " 1.478200e+09 \n",
606 | " 880 \n",
607 | " 8 \n",
608 | " 0 \n",
609 | " 32 \n",
610 | " 0 \n",
611 | " 0 \n",
612 | " 0 \n",
613 | " 0 \n",
614 | " 0 \n",
615 | " 0 \n",
616 | " Normal Message \n",
617 | " 032000000 \n",
618 | " \n",
619 | " \n",
620 | " 7 \n",
621 | " 1.478199e+09 \n",
622 | " 1264 \n",
623 | " 8 \n",
624 | " 0 \n",
625 | " 0 \n",
626 | " 0 \n",
627 | " 128 \n",
628 | " 0 \n",
629 | " 105 \n",
630 | " 209 \n",
631 | " 19 \n",
632 | " Normal Message \n",
633 | " 000128010520919 \n",
634 | " \n",
635 | " \n",
636 | " 8 \n",
637 | " 1.478199e+09 \n",
638 | " 304 \n",
639 | " 8 \n",
640 | " 5 \n",
641 | " 128 \n",
642 | " 0 \n",
643 | " 255 \n",
644 | " 11 \n",
645 | " 128 \n",
646 | " 12 \n",
647 | " 237 \n",
648 | " Normal Message \n",
649 | " 512802551112812237 \n",
650 | " \n",
651 | " \n",
652 | " 9 \n",
653 | " 1.478198e+09 \n",
654 | " 305 \n",
655 | " 8 \n",
656 | " 247 \n",
657 | " 127 \n",
658 | " 0 \n",
659 | " 0 \n",
660 | " 76 \n",
661 | " 127 \n",
662 | " 13 \n",
663 | " 231 \n",
664 | " Normal Message \n",
665 | " 247127007612713231 \n",
666 | " \n",
667 | " \n",
668 | "
\n",
669 | "
\n",
670 | "
\n",
673 | " \n",
674 | " \n",
676 | " \n",
677 | " \n",
678 | " \n",
679 | " \n",
680 | " \n",
681 | " \n",
718 | "\n",
719 | " \n",
743 | "
\n",
744 | "
\n",
745 | " "
746 | ],
747 | "text/plain": [
748 | " Timestamp CAN ID Byte DATA[0] DATA[1] DATA[2] DATA[3] DATA[4] \\\n",
749 | "0 1.478200e+09 0 8 0 0 0 0 0 \n",
750 | "1 1.478201e+09 305 8 27 128 0 0 63 \n",
751 | "2 1.478199e+09 161 8 128 137 0 0 36 \n",
752 | "3 1.478200e+09 608 8 24 33 34 48 8 \n",
753 | "4 1.478201e+09 704 8 20 0 0 0 0 \n",
754 | "5 1.478200e+09 304 8 11 128 0 255 8 \n",
755 | "6 1.478200e+09 880 8 0 32 0 0 0 \n",
756 | "7 1.478199e+09 1264 8 0 0 0 128 0 \n",
757 | "8 1.478199e+09 304 8 5 128 0 255 11 \n",
758 | "9 1.478198e+09 305 8 247 127 0 0 76 \n",
759 | "\n",
760 | " DATA[5] DATA[6] DATA[7] AttackType Message \n",
761 | "0 0 0 0 DoS Attack 00000000 \n",
762 | "1 127 14 166 Normal Message 27128006312714166 \n",
763 | "2 0 0 0 Normal Message 1281370036000 \n",
764 | "3 143 112 5 Normal Message 2433344881431125 \n",
765 | "4 0 0 0 Normal Message 200000000 \n",
766 | "5 128 4 136 Normal Message 11128025581284136 \n",
767 | "6 0 0 0 Normal Message 032000000 \n",
768 | "7 105 209 19 Normal Message 000128010520919 \n",
769 | "8 128 12 237 Normal Message 512802551112812237 \n",
770 | "9 127 13 231 Normal Message 247127007612713231 "
771 | ]
772 | },
773 | "metadata": {},
774 | "execution_count": 4
775 | }
776 | ]
777 | },
778 | {
779 | "cell_type": "code",
780 | "source": [
781 | "#df['Message'] = df['Message'].map(lambda x: int(x))\n",
782 | "df['Message'] = df['Message'].astype(float)\n",
783 | "df.head(10)"
784 | ],
785 | "metadata": {
786 | "colab": {
787 | "base_uri": "https://localhost:8080/",
788 | "height": 601
789 | },
790 | "id": "fUPHSmKPAP6_",
791 | "outputId": "4d196f89-62c4-4c91-eb66-cb3a8172af9f"
792 | },
793 | "execution_count": 5,
794 | "outputs": [
795 | {
796 | "output_type": "execute_result",
797 | "data": {
798 | "text/html": [
799 | "\n",
800 | " \n",
801 | "
\n",
802 | "
\n",
803 | "\n",
816 | "
\n",
817 | " \n",
818 | " \n",
819 | " \n",
820 | " Timestamp \n",
821 | " CAN ID \n",
822 | " Byte \n",
823 | " DATA[0] \n",
824 | " DATA[1] \n",
825 | " DATA[2] \n",
826 | " DATA[3] \n",
827 | " DATA[4] \n",
828 | " DATA[5] \n",
829 | " DATA[6] \n",
830 | " DATA[7] \n",
831 | " AttackType \n",
832 | " Message \n",
833 | " \n",
834 | " \n",
835 | " \n",
836 | " \n",
837 | " 0 \n",
838 | " 1.478200e+09 \n",
839 | " 0 \n",
840 | " 8 \n",
841 | " 0 \n",
842 | " 0 \n",
843 | " 0 \n",
844 | " 0 \n",
845 | " 0 \n",
846 | " 0 \n",
847 | " 0 \n",
848 | " 0 \n",
849 | " DoS Attack \n",
850 | " 0.000000e+00 \n",
851 | " \n",
852 | " \n",
853 | " 1 \n",
854 | " 1.478201e+09 \n",
855 | " 305 \n",
856 | " 8 \n",
857 | " 27 \n",
858 | " 128 \n",
859 | " 0 \n",
860 | " 0 \n",
861 | " 63 \n",
862 | " 127 \n",
863 | " 14 \n",
864 | " 166 \n",
865 | " Normal Message \n",
866 | " 2.712801e+16 \n",
867 | " \n",
868 | " \n",
869 | " 2 \n",
870 | " 1.478199e+09 \n",
871 | " 161 \n",
872 | " 8 \n",
873 | " 128 \n",
874 | " 137 \n",
875 | " 0 \n",
876 | " 0 \n",
877 | " 36 \n",
878 | " 0 \n",
879 | " 0 \n",
880 | " 0 \n",
881 | " Normal Message \n",
882 | " 1.281370e+12 \n",
883 | " \n",
884 | " \n",
885 | " 3 \n",
886 | " 1.478200e+09 \n",
887 | " 608 \n",
888 | " 8 \n",
889 | " 24 \n",
890 | " 33 \n",
891 | " 34 \n",
892 | " 48 \n",
893 | " 8 \n",
894 | " 143 \n",
895 | " 112 \n",
896 | " 5 \n",
897 | " Normal Message \n",
898 | " 2.433345e+15 \n",
899 | " \n",
900 | " \n",
901 | " 4 \n",
902 | " 1.478201e+09 \n",
903 | " 704 \n",
904 | " 8 \n",
905 | " 20 \n",
906 | " 0 \n",
907 | " 0 \n",
908 | " 0 \n",
909 | " 0 \n",
910 | " 0 \n",
911 | " 0 \n",
912 | " 0 \n",
913 | " Normal Message \n",
914 | " 2.000000e+08 \n",
915 | " \n",
916 | " \n",
917 | " 5 \n",
918 | " 1.478200e+09 \n",
919 | " 304 \n",
920 | " 8 \n",
921 | " 11 \n",
922 | " 128 \n",
923 | " 0 \n",
924 | " 255 \n",
925 | " 8 \n",
926 | " 128 \n",
927 | " 4 \n",
928 | " 136 \n",
929 | " Normal Message \n",
930 | " 1.112803e+16 \n",
931 | " \n",
932 | " \n",
933 | " 6 \n",
934 | " 1.478200e+09 \n",
935 | " 880 \n",
936 | " 8 \n",
937 | " 0 \n",
938 | " 32 \n",
939 | " 0 \n",
940 | " 0 \n",
941 | " 0 \n",
942 | " 0 \n",
943 | " 0 \n",
944 | " 0 \n",
945 | " Normal Message \n",
946 | " 3.200000e+07 \n",
947 | " \n",
948 | " \n",
949 | " 7 \n",
950 | " 1.478199e+09 \n",
951 | " 1264 \n",
952 | " 8 \n",
953 | " 0 \n",
954 | " 0 \n",
955 | " 0 \n",
956 | " 128 \n",
957 | " 0 \n",
958 | " 105 \n",
959 | " 209 \n",
960 | " 19 \n",
961 | " Normal Message \n",
962 | " 1.280105e+11 \n",
963 | " \n",
964 | " \n",
965 | " 8 \n",
966 | " 1.478199e+09 \n",
967 | " 304 \n",
968 | " 8 \n",
969 | " 5 \n",
970 | " 128 \n",
971 | " 0 \n",
972 | " 255 \n",
973 | " 11 \n",
974 | " 128 \n",
975 | " 12 \n",
976 | " 237 \n",
977 | " Normal Message \n",
978 | " 5.128026e+17 \n",
979 | " \n",
980 | " \n",
981 | " 9 \n",
982 | " 1.478198e+09 \n",
983 | " 305 \n",
984 | " 8 \n",
985 | " 247 \n",
986 | " 127 \n",
987 | " 0 \n",
988 | " 0 \n",
989 | " 76 \n",
990 | " 127 \n",
991 | " 13 \n",
992 | " 231 \n",
993 | " Normal Message \n",
994 | " 2.471270e+17 \n",
995 | " \n",
996 | " \n",
997 | "
\n",
998 | "
\n",
999 | "
\n",
1002 | " \n",
1003 | " \n",
1005 | " \n",
1006 | " \n",
1007 | " \n",
1008 | " \n",
1009 | " \n",
1010 | " \n",
1047 | "\n",
1048 | " \n",
1072 | "
\n",
1073 | "
\n",
1074 | " "
1075 | ],
1076 | "text/plain": [
1077 | " Timestamp CAN ID Byte DATA[0] DATA[1] DATA[2] DATA[3] DATA[4] \\\n",
1078 | "0 1.478200e+09 0 8 0 0 0 0 0 \n",
1079 | "1 1.478201e+09 305 8 27 128 0 0 63 \n",
1080 | "2 1.478199e+09 161 8 128 137 0 0 36 \n",
1081 | "3 1.478200e+09 608 8 24 33 34 48 8 \n",
1082 | "4 1.478201e+09 704 8 20 0 0 0 0 \n",
1083 | "5 1.478200e+09 304 8 11 128 0 255 8 \n",
1084 | "6 1.478200e+09 880 8 0 32 0 0 0 \n",
1085 | "7 1.478199e+09 1264 8 0 0 0 128 0 \n",
1086 | "8 1.478199e+09 304 8 5 128 0 255 11 \n",
1087 | "9 1.478198e+09 305 8 247 127 0 0 76 \n",
1088 | "\n",
1089 | " DATA[5] DATA[6] DATA[7] AttackType Message \n",
1090 | "0 0 0 0 DoS Attack 0.000000e+00 \n",
1091 | "1 127 14 166 Normal Message 2.712801e+16 \n",
1092 | "2 0 0 0 Normal Message 1.281370e+12 \n",
1093 | "3 143 112 5 Normal Message 2.433345e+15 \n",
1094 | "4 0 0 0 Normal Message 2.000000e+08 \n",
1095 | "5 128 4 136 Normal Message 1.112803e+16 \n",
1096 | "6 0 0 0 Normal Message 3.200000e+07 \n",
1097 | "7 105 209 19 Normal Message 1.280105e+11 \n",
1098 | "8 128 12 237 Normal Message 5.128026e+17 \n",
1099 | "9 127 13 231 Normal Message 2.471270e+17 "
1100 | ]
1101 | },
1102 | "metadata": {},
1103 | "execution_count": 5
1104 | }
1105 | ]
1106 | },
1107 | {
1108 | "cell_type": "code",
1109 | "source": [
1110 | "import datetime\n",
1111 | "newdf = df.copy(deep = True)\n",
1112 | "dateformat = \"%Y-%m-%d %H:%M:%S.%f\"\n",
1113 | "df['Timestamp'] = df['Timestamp'].apply(lambda x: datetime.datetime.fromtimestamp(float(x)).strftime(dateformat))\n",
1114 | "print(df.dtypes)\n",
1115 | "df.head(100)"
1116 | ],
1117 | "metadata": {
1118 | "id": "DMt9KCx9ql_W",
1119 | "colab": {
1120 | "base_uri": "https://localhost:8080/",
1121 | "height": 921
1122 | },
1123 | "outputId": "68acd251-e138-489f-8e43-ae6b6632c6af"
1124 | },
1125 | "execution_count": 6,
1126 | "outputs": [
1127 | {
1128 | "output_type": "stream",
1129 | "name": "stdout",
1130 | "text": [
1131 | "Timestamp object\n",
1132 | "CAN ID int64\n",
1133 | "Byte int64\n",
1134 | "DATA[0] int64\n",
1135 | "DATA[1] int64\n",
1136 | "DATA[2] int64\n",
1137 | "DATA[3] int64\n",
1138 | "DATA[4] int64\n",
1139 | "DATA[5] int64\n",
1140 | "DATA[6] int64\n",
1141 | "DATA[7] int64\n",
1142 | "AttackType object\n",
1143 | "Message float64\n",
1144 | "dtype: object\n"
1145 | ]
1146 | },
1147 | {
1148 | "output_type": "execute_result",
1149 | "data": {
1150 | "text/html": [
1151 | "\n",
1152 | " \n",
1153 | "
\n",
1154 | "
\n",
1155 | "\n",
1168 | "
\n",
1169 | " \n",
1170 | " \n",
1171 | " \n",
1172 | " Timestamp \n",
1173 | " CAN ID \n",
1174 | " Byte \n",
1175 | " DATA[0] \n",
1176 | " DATA[1] \n",
1177 | " DATA[2] \n",
1178 | " DATA[3] \n",
1179 | " DATA[4] \n",
1180 | " DATA[5] \n",
1181 | " DATA[6] \n",
1182 | " DATA[7] \n",
1183 | " AttackType \n",
1184 | " Message \n",
1185 | " \n",
1186 | " \n",
1187 | " \n",
1188 | " \n",
1189 | " 0 \n",
1190 | " 2016-11-03 19:08:43.044157 \n",
1191 | " 0 \n",
1192 | " 8 \n",
1193 | " 0 \n",
1194 | " 0 \n",
1195 | " 0 \n",
1196 | " 0 \n",
1197 | " 0 \n",
1198 | " 0 \n",
1199 | " 0 \n",
1200 | " 0 \n",
1201 | " DoS Attack \n",
1202 | " 0.000000e+00 \n",
1203 | " \n",
1204 | " \n",
1205 | " 1 \n",
1206 | " 2016-11-03 19:24:35.989254 \n",
1207 | " 305 \n",
1208 | " 8 \n",
1209 | " 27 \n",
1210 | " 128 \n",
1211 | " 0 \n",
1212 | " 0 \n",
1213 | " 63 \n",
1214 | " 127 \n",
1215 | " 14 \n",
1216 | " 166 \n",
1217 | " Normal Message \n",
1218 | " 2.712801e+16 \n",
1219 | " \n",
1220 | " \n",
1221 | " 2 \n",
1222 | " 2016-11-03 18:54:13.788681 \n",
1223 | " 161 \n",
1224 | " 8 \n",
1225 | " 128 \n",
1226 | " 137 \n",
1227 | " 0 \n",
1228 | " 0 \n",
1229 | " 36 \n",
1230 | " 0 \n",
1231 | " 0 \n",
1232 | " 0 \n",
1233 | " Normal Message \n",
1234 | " 1.281370e+12 \n",
1235 | " \n",
1236 | " \n",
1237 | " 3 \n",
1238 | " 2016-11-03 19:06:50.286119 \n",
1239 | " 608 \n",
1240 | " 8 \n",
1241 | " 24 \n",
1242 | " 33 \n",
1243 | " 34 \n",
1244 | " 48 \n",
1245 | " 8 \n",
1246 | " 143 \n",
1247 | " 112 \n",
1248 | " 5 \n",
1249 | " Normal Message \n",
1250 | " 2.433345e+15 \n",
1251 | " \n",
1252 | " \n",
1253 | " 4 \n",
1254 | " 2016-11-03 19:26:04.139714 \n",
1255 | " 704 \n",
1256 | " 8 \n",
1257 | " 20 \n",
1258 | " 0 \n",
1259 | " 0 \n",
1260 | " 0 \n",
1261 | " 0 \n",
1262 | " 0 \n",
1263 | " 0 \n",
1264 | " 0 \n",
1265 | " Normal Message \n",
1266 | " 2.000000e+08 \n",
1267 | " \n",
1268 | " \n",
1269 | " ... \n",
1270 | " ... \n",
1271 | " ... \n",
1272 | " ... \n",
1273 | " ... \n",
1274 | " ... \n",
1275 | " ... \n",
1276 | " ... \n",
1277 | " ... \n",
1278 | " ... \n",
1279 | " ... \n",
1280 | " ... \n",
1281 | " ... \n",
1282 | " ... \n",
1283 | " \n",
1284 | " \n",
1285 | " 95 \n",
1286 | " 2016-11-03 19:05:13.346416 \n",
1287 | " 0 \n",
1288 | " 8 \n",
1289 | " 0 \n",
1290 | " 0 \n",
1291 | " 0 \n",
1292 | " 0 \n",
1293 | " 0 \n",
1294 | " 0 \n",
1295 | " 0 \n",
1296 | " 0 \n",
1297 | " DoS Attack \n",
1298 | " 0.000000e+00 \n",
1299 | " \n",
1300 | " \n",
1301 | " 97 \n",
1302 | " 2016-11-03 19:15:01.146305 \n",
1303 | " 704 \n",
1304 | " 8 \n",
1305 | " 20 \n",
1306 | " 0 \n",
1307 | " 0 \n",
1308 | " 0 \n",
1309 | " 0 \n",
1310 | " 0 \n",
1311 | " 0 \n",
1312 | " 0 \n",
1313 | " Normal Message \n",
1314 | " 2.000000e+08 \n",
1315 | " \n",
1316 | " \n",
1317 | " 98 \n",
1318 | " 2016-11-03 18:56:54.761137 \n",
1319 | " 809 \n",
1320 | " 8 \n",
1321 | " 220 \n",
1322 | " 190 \n",
1323 | " 127 \n",
1324 | " 20 \n",
1325 | " 17 \n",
1326 | " 32 \n",
1327 | " 0 \n",
1328 | " 20 \n",
1329 | " Normal Message \n",
1330 | " 2.201901e+17 \n",
1331 | " \n",
1332 | " \n",
1333 | " 99 \n",
1334 | " 2016-11-03 18:52:14.511839 \n",
1335 | " 497 \n",
1336 | " 8 \n",
1337 | " 8 \n",
1338 | " 0 \n",
1339 | " 0 \n",
1340 | " 0 \n",
1341 | " 0 \n",
1342 | " 0 \n",
1343 | " 0 \n",
1344 | " 0 \n",
1345 | " Normal Message \n",
1346 | " 8.000000e+07 \n",
1347 | " \n",
1348 | " \n",
1349 | " 100 \n",
1350 | " 2016-11-03 19:16:38.790256 \n",
1351 | " 704 \n",
1352 | " 8 \n",
1353 | " 20 \n",
1354 | " 0 \n",
1355 | " 0 \n",
1356 | " 0 \n",
1357 | " 0 \n",
1358 | " 0 \n",
1359 | " 0 \n",
1360 | " 0 \n",
1361 | " Normal Message \n",
1362 | " 2.000000e+08 \n",
1363 | " \n",
1364 | " \n",
1365 | "
\n",
1366 | "
100 rows × 13 columns
\n",
1367 | "
\n",
1368 | "
\n",
1371 | " \n",
1372 | " \n",
1374 | " \n",
1375 | " \n",
1376 | " \n",
1377 | " \n",
1378 | " \n",
1379 | " \n",
1416 | "\n",
1417 | " \n",
1441 | "
\n",
1442 | "
\n",
1443 | " "
1444 | ],
1445 | "text/plain": [
1446 | " Timestamp CAN ID Byte DATA[0] DATA[1] DATA[2] \\\n",
1447 | "0 2016-11-03 19:08:43.044157 0 8 0 0 0 \n",
1448 | "1 2016-11-03 19:24:35.989254 305 8 27 128 0 \n",
1449 | "2 2016-11-03 18:54:13.788681 161 8 128 137 0 \n",
1450 | "3 2016-11-03 19:06:50.286119 608 8 24 33 34 \n",
1451 | "4 2016-11-03 19:26:04.139714 704 8 20 0 0 \n",
1452 | ".. ... ... ... ... ... ... \n",
1453 | "95 2016-11-03 19:05:13.346416 0 8 0 0 0 \n",
1454 | "97 2016-11-03 19:15:01.146305 704 8 20 0 0 \n",
1455 | "98 2016-11-03 18:56:54.761137 809 8 220 190 127 \n",
1456 | "99 2016-11-03 18:52:14.511839 497 8 8 0 0 \n",
1457 | "100 2016-11-03 19:16:38.790256 704 8 20 0 0 \n",
1458 | "\n",
1459 | " DATA[3] DATA[4] DATA[5] DATA[6] DATA[7] AttackType Message \n",
1460 | "0 0 0 0 0 0 DoS Attack 0.000000e+00 \n",
1461 | "1 0 63 127 14 166 Normal Message 2.712801e+16 \n",
1462 | "2 0 36 0 0 0 Normal Message 1.281370e+12 \n",
1463 | "3 48 8 143 112 5 Normal Message 2.433345e+15 \n",
1464 | "4 0 0 0 0 0 Normal Message 2.000000e+08 \n",
1465 | ".. ... ... ... ... ... ... ... \n",
1466 | "95 0 0 0 0 0 DoS Attack 0.000000e+00 \n",
1467 | "97 0 0 0 0 0 Normal Message 2.000000e+08 \n",
1468 | "98 20 17 32 0 20 Normal Message 2.201901e+17 \n",
1469 | "99 0 0 0 0 0 Normal Message 8.000000e+07 \n",
1470 | "100 0 0 0 0 0 Normal Message 2.000000e+08 \n",
1471 | "\n",
1472 | "[100 rows x 13 columns]"
1473 | ]
1474 | },
1475 | "metadata": {},
1476 | "execution_count": 6
1477 | }
1478 | ]
1479 | },
1480 | {
1481 | "cell_type": "code",
1482 | "source": [
1483 | "#df = newdf.copy(deep = True)\n",
1484 | "from sklearn import preprocessing\n",
1485 | "#print(df['AttackType'].unique())\n",
1486 | "#print(df['AttackType'].value_counts())\n",
1487 | "encoder = preprocessing.LabelEncoder()\n",
1488 | "#df1 = df[['AttackType']].copy()\n",
1489 | "df['AttackType']= encoder.fit_transform(df['AttackType'].values)\n",
1490 | "# df = df.drop(['AttackType'], axis = 1)\n",
1491 | "# df1\n",
1492 | "#df = pd.concat([df.iloc[:,0:11],df1, df.iloc[:, 11:]], axis=1)\n",
1493 | "#df = pd.get_dummies(df, columns =['AttackType'], prefix = '', prefix_sep = '')\n",
1494 | "print(df.head(10))\n",
1495 | "# print(df['AttackType Encode'])\n",
1496 | "print(df['AttackType'])\n",
1497 | "print(df.shape)\n",
1498 | "#print(df.shape)"
1499 | ],
1500 | "metadata": {
1501 | "colab": {
1502 | "base_uri": "https://localhost:8080/"
1503 | },
1504 | "id": "vEUXBabOBtpQ",
1505 | "outputId": "076885d8-3a39-446d-96b6-0816f653f48a"
1506 | },
1507 | "execution_count": 7,
1508 | "outputs": [
1509 | {
1510 | "output_type": "stream",
1511 | "name": "stdout",
1512 | "text": [
1513 | " Timestamp CAN ID Byte DATA[0] DATA[1] DATA[2] \\\n",
1514 | "0 2016-11-03 19:08:43.044157 0 8 0 0 0 \n",
1515 | "1 2016-11-03 19:24:35.989254 305 8 27 128 0 \n",
1516 | "2 2016-11-03 18:54:13.788681 161 8 128 137 0 \n",
1517 | "3 2016-11-03 19:06:50.286119 608 8 24 33 34 \n",
1518 | "4 2016-11-03 19:26:04.139714 704 8 20 0 0 \n",
1519 | "5 2016-11-03 19:03:07.624543 304 8 11 128 0 \n",
1520 | "6 2016-11-03 19:06:31.658461 880 8 0 32 0 \n",
1521 | "7 2016-11-03 18:55:47.812754 1264 8 0 0 0 \n",
1522 | "8 2016-11-03 18:46:48.226079 304 8 5 128 0 \n",
1523 | "9 2016-11-03 18:40:52.891089 305 8 247 127 0 \n",
1524 | "\n",
1525 | " DATA[3] DATA[4] DATA[5] DATA[6] DATA[7] AttackType Message \n",
1526 | "0 0 0 0 0 0 0 0.000000e+00 \n",
1527 | "1 0 63 127 14 166 3 2.712801e+16 \n",
1528 | "2 0 36 0 0 0 3 1.281370e+12 \n",
1529 | "3 48 8 143 112 5 3 2.433345e+15 \n",
1530 | "4 0 0 0 0 0 3 2.000000e+08 \n",
1531 | "5 255 8 128 4 136 3 1.112803e+16 \n",
1532 | "6 0 0 0 0 0 3 3.200000e+07 \n",
1533 | "7 128 0 105 209 19 3 1.280105e+11 \n",
1534 | "8 255 11 128 12 237 3 5.128026e+17 \n",
1535 | "9 0 76 127 13 231 3 2.471270e+17 \n",
1536 | "0 0\n",
1537 | "1 3\n",
1538 | "2 3\n",
1539 | "3 3\n",
1540 | "4 3\n",
1541 | " ..\n",
1542 | "462165 3\n",
1543 | "462166 3\n",
1544 | "462167 3\n",
1545 | "462168 3\n",
1546 | "462169 4\n",
1547 | "Name: AttackType, Length: 1636855, dtype: int64\n",
1548 | "(1636855, 13)\n"
1549 | ]
1550 | }
1551 | ]
1552 | },
1553 | {
1554 | "cell_type": "code",
1555 | "source": [
1556 | "df.columns"
1557 | ],
1558 | "metadata": {
1559 | "colab": {
1560 | "base_uri": "https://localhost:8080/"
1561 | },
1562 | "id": "jPQUX0V2PPkm",
1563 | "outputId": "45225c4a-9642-444e-bb72-1ff1c5f3e0cc"
1564 | },
1565 | "execution_count": 8,
1566 | "outputs": [
1567 | {
1568 | "output_type": "execute_result",
1569 | "data": {
1570 | "text/plain": [
1571 | "Index(['Timestamp', 'CAN ID', 'Byte', 'DATA[0]', 'DATA[1]', 'DATA[2]',\n",
1572 | " 'DATA[3]', 'DATA[4]', 'DATA[5]', 'DATA[6]', 'DATA[7]', 'AttackType',\n",
1573 | " 'Message'],\n",
1574 | " dtype='object')"
1575 | ]
1576 | },
1577 | "metadata": {},
1578 | "execution_count": 8
1579 | }
1580 | ]
1581 | },
1582 | {
1583 | "cell_type": "code",
1584 | "source": [
1585 | "X = df.iloc[:, np.r_[:,1,3:11]]\n",
1586 | "#X = df[['CAN ID', 'DATA[0]', 'DATA[1]', 'DATA[2]', 'DATA[3]', 'DATA[4]', 'DATA[5]', 'DATA[6]', 'DATA[7]']]\n",
1587 | "Y = df[['AttackType']]\n",
1588 | "X,Y"
1589 | ],
1590 | "metadata": {
1591 | "colab": {
1592 | "base_uri": "https://localhost:8080/"
1593 | },
1594 | "id": "-E_zgBKtbX4C",
1595 | "outputId": "fe0d954e-9b94-46d9-f3a1-3e8503d67c3d"
1596 | },
1597 | "execution_count": 9,
1598 | "outputs": [
1599 | {
1600 | "output_type": "execute_result",
1601 | "data": {
1602 | "text/plain": [
1603 | "( CAN ID DATA[0] DATA[1] DATA[2] DATA[3] DATA[4] DATA[5] DATA[6] \\\n",
1604 | " 0 0 0 0 0 0 0 0 0 \n",
1605 | " 1 305 27 128 0 0 63 127 14 \n",
1606 | " 2 161 128 137 0 0 36 0 0 \n",
1607 | " 3 608 24 33 34 48 8 143 112 \n",
1608 | " 4 704 20 0 0 0 0 0 0 \n",
1609 | " ... ... ... ... ... ... ... ... ... \n",
1610 | " 462165 809 220 183 126 20 17 32 0 \n",
1611 | " 462166 305 242 127 0 0 58 127 12 \n",
1612 | " 462167 305 242 127 0 0 64 127 6 \n",
1613 | " 462168 704 21 0 0 0 0 0 0 \n",
1614 | " 462169 790 69 41 36 255 41 36 0 \n",
1615 | " \n",
1616 | " DATA[7] \n",
1617 | " 0 0 \n",
1618 | " 1 166 \n",
1619 | " 2 0 \n",
1620 | " 3 5 \n",
1621 | " 4 0 \n",
1622 | " ... ... \n",
1623 | " 462165 20 \n",
1624 | " 462166 131 \n",
1625 | " 462167 22 \n",
1626 | " 462168 0 \n",
1627 | " 462169 255 \n",
1628 | " \n",
1629 | " [1636855 rows x 9 columns], AttackType\n",
1630 | " 0 0\n",
1631 | " 1 3\n",
1632 | " 2 3\n",
1633 | " 3 3\n",
1634 | " 4 3\n",
1635 | " ... ...\n",
1636 | " 462165 3\n",
1637 | " 462166 3\n",
1638 | " 462167 3\n",
1639 | " 462168 3\n",
1640 | " 462169 4\n",
1641 | " \n",
1642 | " [1636855 rows x 1 columns])"
1643 | ]
1644 | },
1645 | "metadata": {},
1646 | "execution_count": 9
1647 | }
1648 | ]
1649 | },
1650 | {
1651 | "cell_type": "code",
1652 | "source": [
1653 | "from sklearn.model_selection import train_test_split\n",
1654 | "from sklearn.svm import SVC\n",
1655 | "from sklearn.pipeline import Pipeline\n",
1656 | "X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.25, random_state = 20)"
1657 | ],
1658 | "metadata": {
1659 | "id": "7_qOxOWjbeX8"
1660 | },
1661 | "execution_count": 10,
1662 | "outputs": []
1663 | },
1664 | {
1665 | "cell_type": "code",
1666 | "source": [
1667 | "val = encoder.inverse_transform(df['AttackType'])\n",
1668 | "(unique, counts) = np.unique(val, return_counts=True)\n",
1669 | "frequencies = np.asarray((unique, counts)).T\n",
1670 | "print(frequencies)\n",
1671 | "print(df['AttackType'].value_counts())"
1672 | ],
1673 | "metadata": {
1674 | "colab": {
1675 | "base_uri": "https://localhost:8080/"
1676 | },
1677 | "id": "oUcMhUpnh4Tq",
1678 | "outputId": "6fe79717-8b48-42ea-b5a8-11fe00f2d568"
1679 | },
1680 | "execution_count": 23,
1681 | "outputs": [
1682 | {
1683 | "output_type": "stream",
1684 | "name": "stdout",
1685 | "text": [
1686 | "[['DoS Attack' 58469]\n",
1687 | " ['Fuzzy Attack' 49258]\n",
1688 | " ['Gear Spooing Attack' 60016]\n",
1689 | " ['Normal Message' 1403673]\n",
1690 | " ['RPM Spoofing Attack' 65439]]\n",
1691 | "3 1403673\n",
1692 | "4 65439\n",
1693 | "2 60016\n",
1694 | "0 58469\n",
1695 | "1 49258\n",
1696 | "Name: AttackType, dtype: int64\n"
1697 | ]
1698 | }
1699 | ]
1700 | },
1701 | {
1702 | "cell_type": "code",
1703 | "source": [
1704 | "model = Pipeline([\n",
1705 | " ('svc', SVC(random_state=20))\n",
1706 | " ])\n",
1707 | "model.fit(X_train, Y_train)\n",
1708 | "pred = model.predict(X_test)\n",
1709 | "pred"
1710 | ],
1711 | "metadata": {
1712 | "colab": {
1713 | "base_uri": "https://localhost:8080/"
1714 | },
1715 | "id": "NkzbFP_GbjSe",
1716 | "outputId": "bf0d413b-a09f-49d3-82fb-87ce53b04aae"
1717 | },
1718 | "execution_count": null,
1719 | "outputs": [
1720 | {
1721 | "output_type": "stream",
1722 | "name": "stderr",
1723 | "text": [
1724 | "/usr/local/lib/python3.7/dist-packages/sklearn/utils/validation.py:993: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().\n",
1725 | " y = column_or_1d(y, warn=True)\n"
1726 | ]
1727 | }
1728 | ]
1729 | },
1730 | {
1731 | "cell_type": "code",
1732 | "source": [
1733 | "from sklearn.metrics import confusion_matrix\n",
1734 | "from sklearn.metrics import classification_report\n",
1735 | "from sklearn.metrics import accuracy_score\n",
1736 | "accuracy = accuracy_score(Y_test, pred)\n",
1737 | "print('accuracy : \\n', accuracy)\n",
1738 | "matrix = confusion_matrix(Y_test,pred)\n",
1739 | "print('Confusion matrix : \\n',matrix)\n",
1740 | "matrix = classification_report(Y_test,pred)\n",
1741 | "print('Classification report : \\n',matrix)"
1742 | ],
1743 | "metadata": {
1744 | "id": "zCFivudpci-x"
1745 | },
1746 | "execution_count": null,
1747 | "outputs": []
1748 | },
1749 | {
1750 | "cell_type": "code",
1751 | "source": [
1752 | "dfC = df['AttackType'].value_counts()\n",
1753 | "dfcount = pd.DataFrame(dfC, index = ['Normal Message','RPM Spoofing Attack','Gear Spooing Attack','DoS Attack','Fuzzy Attack'])\n",
1754 | "#dfcount_reset = dfcount.reset_index()\n",
1755 | "dfcount.columns = ['Injected Messages']\n",
1756 | "#dfcount_reset.set_index('Attack Type')\n",
1757 | "#dfcount_reset.dropna()\n",
1758 | "print(\"\\n\",dfcount)\n",
1759 | "\n",
1760 | "#index = ['Normal Message','RPM Spoofing Attack','Gear Spooing Attack','DoS Attack','Fuzzy Attack']\n",
1761 | "# df2frame = dfDos['AttackType'].value_counts()\n",
1762 | "# df2frame_count = pd.DataFrame(df2frame)\n",
1763 | "# df2frame_count_reset = df2frame_count.reset_index()\n",
1764 | "# df2frame_count_reset.columns = ['No Of Normal Message','No Of Injected Messages']\n",
1765 | "# print(\"\\n\",df2frame_count_reset)"
1766 | ],
1767 | "metadata": {
1768 | "id": "z-aD9lxNQ2hX"
1769 | },
1770 | "execution_count": null,
1771 | "outputs": []
1772 | },
1773 | {
1774 | "cell_type": "code",
1775 | "source": [
1776 | "import matplotlib.pyplot as plt\n",
1777 | "# dffinal = pd.DataFrame({'mass': [0.330, 4.87 , 5.97],\n",
1778 | "# 'radius': [2439.7, 6051.8, 6378.1]},\n",
1779 | "# index=['Mercury', 'Venus', 'Earth'])\n",
1780 | "#plts = dfcount.plot.bar(x='Attack Type', y='Injected Messages', rot=0, figsize=(12, 8))\n",
1781 | "plot = dfcount.plot.pie(y='Injected Messages', figsize=(12, 8))"
1782 | ],
1783 | "metadata": {
1784 | "id": "7Am7edMcou4Y"
1785 | },
1786 | "execution_count": null,
1787 | "outputs": []
1788 | }
1789 | ]
1790 | }
--------------------------------------------------------------------------------
/supplementary_code/README.md:
--------------------------------------------------------------------------------
1 | # The code in this folder shows an example of the pre-processing of the Car-Hacking dataset.
2 |
--------------------------------------------------------------------------------