├── 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 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \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 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | "
CAN IDDATA[0]DATA[1]DATA[2]DATA[3]DATA[4]DATA[5]DATA[6]DATA[7]Label
0120141393935000154R
180964187127201732020R
21349216001360000R
3120141393935000154R
420000032228R
.................................
818435848532521041170012R
818436108825500025513490R
8184378485321001041170092R
81843813492169001370000R
818439790533481033300111R
\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 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | " \n", 484 | " \n", 485 | " \n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | "
CAN IDDATA[0]DATA[1]DATA[2]DATA[3]DATA[4]DATA[5]DATA[6]DATA[7]
count818440.000000818440.000000818440.000000818440.000000818440.000000818440.000000818440.000000818440.000000818440.000000
mean127.458603113.635407108.05550089.524039109.930495105.682464112.27309684.94544093.094805
std73.78040289.99327593.448831100.589117103.63269095.71642090.993393101.365609100.186463
min0.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.000000
25%66.8768770.0000000.0000000.0000000.0000000.0000000.0000000.0000000.000000
50%122.650150126.096096115.5030030.000000130.818318127.755255129.5420420.0000000.000000
75%190.548048192.462462193.611111199.099099190.675676193.355856190.165165192.207207190.675676
max255.000000255.000000255.000000255.000000255.000000255.000000255.000000255.000000255.000000
\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 | " \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 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | "
TimestampCAN IDByteDATA[0]DATA[1]DATA[2]DATA[3]DATA[4]DATA[5]DATA[6]DATA[7]AttackType
01.478200e+090800000000DoS Attack
11.478201e+09305827128006312714166Normal Message
21.478199e+0916181281370036000Normal Message
31.478200e+0960882433344881431125Normal Message
41.478201e+097048200000000Normal Message
51.478200e+09304811128025581284136Normal Message
61.478200e+098808032000000Normal Message
71.478199e+0912648000128010520919Normal Message
81.478199e+093048512802551112812237Normal Message
91.478198e+093058247127007612713231Normal Message
\n", 341 | "
\n", 342 | " \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 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | " \n", 581 | " \n", 582 | " \n", 583 | " \n", 584 | " \n", 585 | " \n", 586 | " \n", 587 | " \n", 588 | " \n", 589 | " \n", 590 | " \n", 591 | " \n", 592 | " \n", 593 | " \n", 594 | " \n", 595 | " \n", 596 | " \n", 597 | " \n", 598 | " \n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 607 | " \n", 608 | " \n", 609 | " \n", 610 | " \n", 611 | " \n", 612 | " \n", 613 | " \n", 614 | " \n", 615 | " \n", 616 | " \n", 617 | " \n", 618 | " \n", 619 | " \n", 620 | " \n", 621 | " \n", 622 | " \n", 623 | " \n", 624 | " \n", 625 | " \n", 626 | " \n", 627 | " \n", 628 | " \n", 629 | " \n", 630 | " \n", 631 | " \n", 632 | " \n", 633 | " \n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | "
TimestampCAN IDByteDATA[0]DATA[1]DATA[2]DATA[3]DATA[4]DATA[5]DATA[6]DATA[7]AttackTypeMessage
01.478200e+090800000000DoS Attack00000000
11.478201e+09305827128006312714166Normal Message27128006312714166
21.478199e+0916181281370036000Normal Message1281370036000
31.478200e+0960882433344881431125Normal Message2433344881431125
41.478201e+097048200000000Normal Message200000000
51.478200e+09304811128025581284136Normal Message11128025581284136
61.478200e+098808032000000Normal Message032000000
71.478199e+0912648000128010520919Normal Message000128010520919
81.478199e+093048512802551112812237Normal Message512802551112812237
91.478198e+093058247127007612713231Normal Message247127007612713231
\n", 669 | "
\n", 670 | " \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 | " \n", 821 | " \n", 822 | " \n", 823 | " \n", 824 | " \n", 825 | " \n", 826 | " \n", 827 | " \n", 828 | " \n", 829 | " \n", 830 | " \n", 831 | " \n", 832 | " \n", 833 | " \n", 834 | " \n", 835 | " \n", 836 | " \n", 837 | " \n", 838 | " \n", 839 | " \n", 840 | " \n", 841 | " \n", 842 | " \n", 843 | " \n", 844 | " \n", 845 | " \n", 846 | " \n", 847 | " \n", 848 | " \n", 849 | " \n", 850 | " \n", 851 | " \n", 852 | " \n", 853 | " \n", 854 | " \n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 859 | " \n", 860 | " \n", 861 | " \n", 862 | " \n", 863 | " \n", 864 | " \n", 865 | " \n", 866 | " \n", 867 | " \n", 868 | " \n", 869 | " \n", 870 | " \n", 871 | " \n", 872 | " \n", 873 | " \n", 874 | " \n", 875 | " \n", 876 | " \n", 877 | " \n", 878 | " \n", 879 | " \n", 880 | " \n", 881 | " \n", 882 | " \n", 883 | " \n", 884 | " \n", 885 | " \n", 886 | " \n", 887 | " \n", 888 | " \n", 889 | " \n", 890 | " \n", 891 | " \n", 892 | " \n", 893 | " \n", 894 | " \n", 895 | " \n", 896 | " \n", 897 | " \n", 898 | " \n", 899 | " \n", 900 | " \n", 901 | " \n", 902 | " \n", 903 | " \n", 904 | " \n", 905 | " \n", 906 | " \n", 907 | " \n", 908 | " \n", 909 | " \n", 910 | " \n", 911 | " \n", 912 | " \n", 913 | " \n", 914 | " \n", 915 | " \n", 916 | " \n", 917 | " \n", 918 | " \n", 919 | " \n", 920 | " \n", 921 | " \n", 922 | " \n", 923 | " \n", 924 | " \n", 925 | " \n", 926 | " \n", 927 | " \n", 928 | " \n", 929 | " \n", 930 | " \n", 931 | " \n", 932 | " \n", 933 | " \n", 934 | " \n", 935 | " \n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | " \n", 940 | " \n", 941 | " \n", 942 | " \n", 943 | " \n", 944 | " \n", 945 | " \n", 946 | " \n", 947 | " \n", 948 | " \n", 949 | " \n", 950 | " \n", 951 | " \n", 952 | " \n", 953 | " \n", 954 | " \n", 955 | " \n", 956 | " \n", 957 | " \n", 958 | " \n", 959 | " \n", 960 | " \n", 961 | " \n", 962 | " \n", 963 | " \n", 964 | " \n", 965 | " \n", 966 | " \n", 967 | " \n", 968 | " \n", 969 | " \n", 970 | " \n", 971 | " \n", 972 | " \n", 973 | " \n", 974 | " \n", 975 | " \n", 976 | " \n", 977 | " \n", 978 | " \n", 979 | " \n", 980 | " \n", 981 | " \n", 982 | " \n", 983 | " \n", 984 | " \n", 985 | " \n", 986 | " \n", 987 | " \n", 988 | " \n", 989 | " \n", 990 | " \n", 991 | " \n", 992 | " \n", 993 | " \n", 994 | " \n", 995 | " \n", 996 | " \n", 997 | "
TimestampCAN IDByteDATA[0]DATA[1]DATA[2]DATA[3]DATA[4]DATA[5]DATA[6]DATA[7]AttackTypeMessage
01.478200e+090800000000DoS Attack0.000000e+00
11.478201e+09305827128006312714166Normal Message2.712801e+16
21.478199e+0916181281370036000Normal Message1.281370e+12
31.478200e+0960882433344881431125Normal Message2.433345e+15
41.478201e+097048200000000Normal Message2.000000e+08
51.478200e+09304811128025581284136Normal Message1.112803e+16
61.478200e+098808032000000Normal Message3.200000e+07
71.478199e+0912648000128010520919Normal Message1.280105e+11
81.478199e+093048512802551112812237Normal Message5.128026e+17
91.478198e+093058247127007612713231Normal Message2.471270e+17
\n", 998 | "
\n", 999 | " \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 | " \n", 1173 | " \n", 1174 | " \n", 1175 | " \n", 1176 | " \n", 1177 | " \n", 1178 | " \n", 1179 | " \n", 1180 | " \n", 1181 | " \n", 1182 | " \n", 1183 | " \n", 1184 | " \n", 1185 | " \n", 1186 | " \n", 1187 | " \n", 1188 | " \n", 1189 | " \n", 1190 | " \n", 1191 | " \n", 1192 | " \n", 1193 | " \n", 1194 | " \n", 1195 | " \n", 1196 | " \n", 1197 | " \n", 1198 | " \n", 1199 | " \n", 1200 | " \n", 1201 | " \n", 1202 | " \n", 1203 | " \n", 1204 | " \n", 1205 | " \n", 1206 | " \n", 1207 | " \n", 1208 | " \n", 1209 | " \n", 1210 | " \n", 1211 | " \n", 1212 | " \n", 1213 | " \n", 1214 | " \n", 1215 | " \n", 1216 | " \n", 1217 | " \n", 1218 | " \n", 1219 | " \n", 1220 | " \n", 1221 | " \n", 1222 | " \n", 1223 | " \n", 1224 | " \n", 1225 | " \n", 1226 | " \n", 1227 | " \n", 1228 | " \n", 1229 | " \n", 1230 | " \n", 1231 | " \n", 1232 | " \n", 1233 | " \n", 1234 | " \n", 1235 | " \n", 1236 | " \n", 1237 | " \n", 1238 | " \n", 1239 | " \n", 1240 | " \n", 1241 | " \n", 1242 | " \n", 1243 | " \n", 1244 | " \n", 1245 | " \n", 1246 | " \n", 1247 | " \n", 1248 | " \n", 1249 | " \n", 1250 | " \n", 1251 | " \n", 1252 | " \n", 1253 | " \n", 1254 | " \n", 1255 | " \n", 1256 | " \n", 1257 | " \n", 1258 | " \n", 1259 | " \n", 1260 | " \n", 1261 | " \n", 1262 | " \n", 1263 | " \n", 1264 | " \n", 1265 | " \n", 1266 | " \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 | " \n", 1286 | " \n", 1287 | " \n", 1288 | " \n", 1289 | " \n", 1290 | " \n", 1291 | " \n", 1292 | " \n", 1293 | " \n", 1294 | " \n", 1295 | " \n", 1296 | " \n", 1297 | " \n", 1298 | " \n", 1299 | " \n", 1300 | " \n", 1301 | " \n", 1302 | " \n", 1303 | " \n", 1304 | " \n", 1305 | " \n", 1306 | " \n", 1307 | " \n", 1308 | " \n", 1309 | " \n", 1310 | " \n", 1311 | " \n", 1312 | " \n", 1313 | " \n", 1314 | " \n", 1315 | " \n", 1316 | " \n", 1317 | " \n", 1318 | " \n", 1319 | " \n", 1320 | " \n", 1321 | " \n", 1322 | " \n", 1323 | " \n", 1324 | " \n", 1325 | " \n", 1326 | " \n", 1327 | " \n", 1328 | " \n", 1329 | " \n", 1330 | " \n", 1331 | " \n", 1332 | " \n", 1333 | " \n", 1334 | " \n", 1335 | " \n", 1336 | " \n", 1337 | " \n", 1338 | " \n", 1339 | " \n", 1340 | " \n", 1341 | " \n", 1342 | " \n", 1343 | " \n", 1344 | " \n", 1345 | " \n", 1346 | " \n", 1347 | " \n", 1348 | " \n", 1349 | " \n", 1350 | " \n", 1351 | " \n", 1352 | " \n", 1353 | " \n", 1354 | " \n", 1355 | " \n", 1356 | " \n", 1357 | " \n", 1358 | " \n", 1359 | " \n", 1360 | " \n", 1361 | " \n", 1362 | " \n", 1363 | " \n", 1364 | " \n", 1365 | "
TimestampCAN IDByteDATA[0]DATA[1]DATA[2]DATA[3]DATA[4]DATA[5]DATA[6]DATA[7]AttackTypeMessage
02016-11-03 19:08:43.0441570800000000DoS Attack0.000000e+00
12016-11-03 19:24:35.989254305827128006312714166Normal Message2.712801e+16
22016-11-03 18:54:13.78868116181281370036000Normal Message1.281370e+12
32016-11-03 19:06:50.28611960882433344881431125Normal Message2.433345e+15
42016-11-03 19:26:04.1397147048200000000Normal Message2.000000e+08
..........................................
952016-11-03 19:05:13.3464160800000000DoS Attack0.000000e+00
972016-11-03 19:15:01.1463057048200000000Normal Message2.000000e+08
982016-11-03 18:56:54.7611378098220190127201732020Normal Message2.201901e+17
992016-11-03 18:52:14.511839497880000000Normal Message8.000000e+07
1002016-11-03 19:16:38.7902567048200000000Normal Message2.000000e+08
\n", 1366 | "

100 rows × 13 columns

\n", 1367 | "
\n", 1368 | " \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 | --------------------------------------------------------------------------------