この記事は2866文字約8分で読めます

機械学習で彼女を作ろうとかふまじめなことはやめよう!

どうも。最近暑くてどうにもやる気がでませんが、そんなとき、めんどくさいことを機械がやってくれればなと思うわけです。

最近(とは言ってもだいぶ成熟した分野ですが)流行の機械学習を勉強することによって、明るい未来を作ろうということを考えるわけです。

前回はおしゃべり彼女を作りましたので、今回はまじめにセキュリティ系のネタをやっていこうかと思います。

Table of Contents

データセットを用意しよう

データセットは以下のものを使います。

KDD Cup 1999 Data

国際会議SIGKDDの1999年のデータマイニングコンペデータだそうで、そのときの侵入検知データはセキュリティ界隈ではかなり有名だそうです。私は最近まで知りませんでした。恥ずかしい。

このデータはかなり膨大なので、今回はフルデータを10%抽出したkddcup.data_10_percentをさらに学習データと評価データにわけて実験します。

今回は通常の通信と、いくつかの攻撃手法とを分類します。

たまにはR言語を使ってみよう

R言語と聞くとじんま疹が出る生物系の学生も多いと思いますが、きちんとPythonとかでプログラミングするよりは簡単だと思います。無料だし。まぁSPSSとかの方がずっと簡単ですが。

分類にはサポートベクターマシン(SVM)

機械学習のパターン認識(分類)にはサポートベクターマシン(SVM)がいいと言われています。SVM自体は比較的古い手法ですが、汎化性能を高める工夫がしっかりしている点で非常に未学習データの認識が優れていると言われています。

R言語ではKernlabというパッケージにSVMがあります。

実際にやってみた(C-SVM)

データを読み込む

CSV形式のデータですのでReadCSVで読み込み、データ型を再定義した後、攻撃手法をざっくりとした分類に分けます。

最後のカラムのLabelが攻撃手法を定義したものですが、量が多いので、ここを参考に4つの攻撃手法に分けておきます。5クラス分類問題となります。

  • DOS
  • R2L(リモートからの不正ログイン試み)
  • U2R(Root権限奪取)
  • Probe(調査)
# CSVとして読み込み

> kddcup <- read_csv("~/Downloads/kdd.ics.uci.edu/databases/kddcup99/kddcup.data_10_percent", header=F)

# ヘッダーをつける

> colnames(kddcup)<-c("duration","protocol_type","service","flag","src_bytes","dst_bytes","land","wrong_fragment","urgent","hot","num_failed_logins","logged_in","num_compromised","root_shell","su_attempted","num_root","num_file_creations","num_shells","num_access_files","num_outbound_cmds","is_host_login","is_guest_login","count","srv_count","serror_rate","srv_serror_rate","rerror_rate","srv_rerror_rate","same_srv_rate","diff_srv_rate","srv_diff_host_rate","dst_host_count","dst_host_srv_count","dst_host_same_srv_rate","dst_host_diff_srv_rate","dst_host_same_src_port_rate","dst_host_srv_diff_host_rate","dst_host_serror_rate","dst_host_srv_serror_rate","dst_host_rerror_rate","dst_host_srv_rerror_rate","label")

# データを整える

> kddcup$duration = as.numeric(as.character(kddcup$duration))
> kddcup$protocol_type = factor(kddcup$protocol_type)
> kddcup$service = factor(kddcup$service)
> kddcup$flag = factor(kddcup$flag)
> kddcup$src_bytes = as.numeric(as.character(kddcup$src_bytes))
> kddcup$dst_bytes = as.numeric(as.character(kddcup$dst_bytes))
> kddcup$land = factor(kddcup$land)
> kddcup$wrong_fragment = as.numeric(as.character(kddcup$wrong_fragment))
> kddcup$urgent = as.numeric(as.character(kddcup$urgent))
> kddcup$hot = as.numeric(as.character(kddcup$hot))
> kddcup$num_failed_logins = as.numeric(as.character(kddcup$num_failed_logins))
> kddcup$logged_in = factor(kddcup$logged_in)
> kddcup$num_compromised = as.numeric(as.character(kddcup$num_compromised))
> kddcup$root_shell = factor(kddcup$root_shell)
> kddcup$su_attempted = factor(kddcup$su_attempted)
> kddcup$num_root = as.numeric(as.character(kddcup$num_root))
> kddcup$num_file_creations = as.numeric(as.character(kddcup$num_file_creations))
> kddcup$num_shells = as.numeric(as.character(kddcup$num_shells))
> kddcup$num_access_files = as.numeric(as.character(kddcup$num_access_files))
> kddcup$is_guest_login = factor(kddcup$is_guest_login)
> kddcup$count = as.numeric(as.character(kddcup$count))
> kddcup$srv_count = as.numeric(as.character(kddcup$srv_count))
> kddcup$serror_rate = as.numeric(as.character(kddcup$serror_rate))
> kddcup$srv_serror_rate = as.numeric(as.character(kddcup$srv_serror_rate))
> kddcup$rerror_rate = as.numeric(as.character(kddcup$rerror_rate))
> kddcup$srv_rerror_rate = as.numeric(as.character(kddcup$srv_rerror_rate))
> kddcup$same_srv_rate = as.numeric(as.character(kddcup$same_srv_rate))
> kddcup$diff_srv_rate = as.numeric(as.character(kddcup$diff_srv_rate))
> kddcup$srv_diff_host_rate = as.numeric(as.character(kddcup$srv_diff_host_rate))
> kddcup$dst_host_count = as.numeric(as.character(kddcup$dst_host_count))
> kddcup$dst_host_srv_count = as.numeric(as.character(kddcup$dst_host_srv_count))
> kddcup$dst_host_same_srv_rate = as.numeric(as.character(kddcup$dst_host_same_srv_rate))
> kddcup$dst_host_diff_srv_rate = as.numeric(as.character(kddcup$dst_host_diff_srv_rate))
> kddcup$dst_host_same_src_port_rate = as.numeric(as.character(kddcup$dst_host_same_src_port_rate))
> kddcup$dst_host_srv_diff_host_rate = as.numeric(as.character(kddcup$dst_host_srv_diff_host_rate))
> kddcup$dst_host_serror_rate = as.numeric(as.character(kddcup$dst_host_serror_rate))
> kddcup$dst_host_srv_serror_rate = as.numeric(as.character(kddcup$dst_host_srv_serror_rate))
> kddcup$dst_host_rerror_rate = as.numeric(as.character(kddcup$dst_host_rerror_rate))
> kddcup$dst_host_srv_rerror_rate = as.numeric(as.character(kddcup$dst_host_srv_rerror_rate))
> kddcup$label = as.character(kddcup$label)

# 攻撃手法をまとめる

> kddcup$label[kddcup$label == "ipsweep."] = "probe"
> kddcup$label[kddcup$label == "portsweep."] = "probe"
> kddcup$label[kddcup$label == "nmap."] = "probe"
> kddcup$label[kddcup$label == "satan."] = "probe"
> kddcup$label[kddcup$label == "buffer_overflow."] = "u2r"
> kddcup$label[kddcup$label == "loadmodule."] = "u2r"
> kddcup$label[kddcup$label == "perl."] = "u2r"
> kddcup$label[kddcup$label == "rootkit."] = "u2r"
> kddcup$label[kddcup$label == "back."] = "dos"
> kddcup$label[kddcup$label == "land."] = "dos"
> kddcup$label[kddcup$label == "neptune."] = "dos"
> kddcup$label[kddcup$label == "pod."] = "dos"
> kddcup$label[kddcup$label == "smurf."] = "dos"
> kddcup$label[kddcup$label == "teardrop."] = "dos"
> kddcup$label[kddcup$label == "ftp_write."] = "r2l"
> kddcup$label[kddcup$label == "guess_passwd."] = "r2l"
> kddcup$label[kddcup$label == "imap."] = "r2l"
> kddcup$label[kddcup$label == "multihop."] = "r2l"
> kddcup$label[kddcup$label == "phf."] = "r2l"
> kddcup$label[kddcup$label == "spy."] = "r2l"
> kddcup$label[kddcup$label == "warezclient."] = "r2l"
> kddcup$label[kddcup$label == "warezmaster."] = "r2l"
> kddcup$label[kddcup$label == "normal."] = "normal"
> kddcup$label = as.factor(kddcup$label)

# 学習データと評価データを分ける(7:3)

> rowdata<-nrow(kddcup)
> random_ids<-sample(rowdata,rowdata*0.7)
> kddcup_train<-kddcup[random_ids, ]
> kddcup_pre<-kddcup[-random_ids, ]

SVMで学習させる

# 学習

> kddcup_svm<-ksvm(label ~., data=kddcup_train)

> kddcup_svm

# 結果

Support Vector Machine object of class "ksvm"

SV type: C-svc (classification)
parameter : cost C = 1

Gaussian Radial Basis kernel function.
Hyperparameter : sigma = 0.000103739702263078

Number of Support Vectors : 8239

Objective Function Value : -329.9296 -738.1392 -65.7569 -28.2657 -670.0012 -175.8475 -32.3014 -70.8226 -27.5727 -27.7096
Training error : 0.001939

Training errorを確認すると、まずまず学習できているみたいです。さすが。

評価する

> result_predict<-predict(kddcup_svm, kddcup_pre)
> table(result_predict,kddcup_pre$label)

result_predict dos normal probe r2l u2r
dos 195344 22 160 0 0
normal 108 48781 141 40 28
probe 96 40 1724 0 0
r2l 0 13 0 511 0
u2r 0 0 0 0 3

表でまとめます

result_predictiondosnormalprober2lu2r適合率
dos195344221600099.9%
normal10848781141402899.3%
probe964017240092.7%
r2l0130511097.5%
u2r00003100%
再現率99.9%99.8%85.1%92.7%9.7%99.7%

縦が予測結果で、横が実際のラベルです。総じて上手くいきました。

ただ、U2Rの精度が低いことが気になります。そもそもデータが少なく学習があまり上手くいっていないのでしょうか。

実際にやってみた(C-SVMでクロスバリデーション)

クロスバリデーション(交差検証)は学習データをいくつかのデータに分け、1つの塊を評価用にそれ以外を学習用としすべての場合を尽くすように学習を進め、汎化性能を上げる手法です。例えば、

  1. 全データをA・B・Cの3つに分けます。
  2. A・Bを学習データとして学習し、Cを用いて評価します。
  3. 次にB・Cを学習データとし、Aを用いて評価します。
  4. 次にC・Aを学習データとし、Bを用いて評価します。
  5. それぞれの結果からもっともよい結果をモデルとして採用します。

こうすることで少ないデータでも汎化性能を上げることができるというものです。当然、分割したデータでそれぞれ学習させるので時間はかかります。

今回はデータ量も多いので3つに分割して実施します。(K-folds法) 通常は10分割にすることが一般的で、データ量が少ない場合はleave-one-out交差検証とかも使われます。

学習させる(C-SVM closs=3)

# closs=3で3つに分割して学習

> kddcup_svm<-ksvm(label ~., data=kddcup_train, cross=3)

> kddcup_svm

Support Vector Machine object of class "ksvm"

SV type: C-svc (classification)
parameter : cost C = 1

Gaussian Radial Basis kernel function.
Hyperparameter : sigma = 9.3825289054228e-05

Number of Support Vectors : 9664

Objective Function Value : -435.7988 -1029.419 -65.9252 -38.3732 -854.7929 -212.3627 -49.4105 -74.7773 -37.6926 -34.5452
Training error : 0.001888
Cross validation error : 0.002689

Cross validation errorは上がってしまいました。ただし、クロスバリデーションを用いないときと比べ学習に用いるデータが少ないため、学習の収束は低くでてしまうのは仕方ありません。

むしろ、収束しきらないところでも高精度を叩きだすことが目的でもありますし。

評価する

> result_predict<-predict(kddcup_svm, kddcup_pre)
> table(result_predict,kddcup_pre$label)

result_predict dos normal probe r2l u2r
dos 117643 7 85 0 0
normal 58 28821 95 23 12
probe 53 19 1058 0 0
r2l 0 6 0 324 0
u2r 0 0 0 0 3

# 誤差率を計算してみる

> 1-sum(diag(kddcup_table))/sum(kddcup_table)
[1] 0.00241554
result_predictiondosnormalprober2lu2r適合率
dos1176437850099.9%
normal582882195231299.4%
probe531910580093.6%
r2l060324098.2%
u2r00003100%
再現率99.9%99.9%85.5%93.4%20%99.8%

ほんの少しですが精度が上がった気がします。

ニューラルネットでもやってみます(nnet)

比較のためにニューラルネットでもやってみます。R言語ではnnetというパッケージでニューラルネットが使えます。

隠れ層のユニット数は出力結果のラベル数を考慮し、5としてます。(3で実施したら、ラベルが全部でませんでした。)

学習させる(nnnet)

# パッケージインストール&適応

> install.packages( "nnet" )

> library( nnet )

# 隠れ層のユニット数5、ランダム値範囲-0.1~0.1、減衰5e-4で実施

> kddcup_nnet <- nnet(label~., size=5, data=kddcup_train, rang = .1, decay = 5e-4, maxit = 3000)
# weights: 615
initial value 575217.442435
iter 10 value 74350.753004
iter 20 value 39017.507761
iter 30 value 33989.403391
iter 40 value 30099.250316
iter 50 value 27641.918258
iter 60 value 24688.366217
iter 70 value 23909.225741
iter 80 value 22006.056620
iter 90 value 21385.374653
iter 100 value 20640.355978
iter 110 value 19443.652176
iter 120 value 17966.682333
iter 130 value 17561.847075
iter 140 value 17298.101203
iter 150 value 16746.291929
iter 160 value 16219.714301
iter 170 value 15885.755704
iter 180 value 14769.847387
iter 190 value 14277.103879
iter 200 value 13651.371989
iter 210 value 12878.412456
iter 220 value 12637.064141
iter 230 value 11873.795106
iter 240 value 10919.945647
iter 250 value 9574.787432
iter 260 value 8064.989190
iter 270 value 7261.795869
iter 280 value 5941.270340
iter 290 value 4788.910803
iter 300 value 4263.836429
iter 310 value 4077.903774
iter 320 value 4035.943916
iter 330 value 3953.874037
iter 340 value 3927.603041
iter 350 value 3850.554389
iter 360 value 3690.449366
iter 370 value 3679.445537
iter 380 value 3660.072147
iter 390 value 3654.366314
iter 400 value 3649.497562
iter 410 value 3613.612850
iter 420 value 3546.881873
iter 430 value 3466.460679
iter 440 value 3332.472239
iter 450 value 3202.713314
iter 460 value 3192.588930
iter 470 value 3169.223153
iter 480 value 3097.055361
iter 490 value 2897.312752
iter 500 value 2595.342208
iter 510 value 2323.495624
iter 520 value 2160.486970
iter 530 value 2068.124473
iter 540 value 1986.553216
iter 550 value 1917.448970
iter 560 value 1848.341606
iter 570 value 1826.934175
iter 580 value 1817.437531
iter 590 value 1783.570324
iter 600 value 1749.888183
iter 610 value 1727.915109
iter 620 value 1724.945680
iter 630 value 1712.222157
iter 640 value 1709.191769
iter 650 value 1702.575583
iter 660 value 1689.403803
iter 670 value 1687.694131
iter 680 value 1685.270318
iter 690 value 1684.377098
iter 700 value 1684.111195
iter 710 value 1682.717637
iter 720 value 1682.526618
iter 720 value 1682.526604
iter 730 value 1682.082587
iter 730 value 1682.082587
final value 1682.081493
converged

評価する

# TypeはClassで

> kddcup_nnet_pre <- predict(kddcup_nnet, kddcup_pre, type="class")
> table(kddcup_nnet_pre,kddcup_pre$label)

kddcup_nnet_pre dos normal probe r2l u2r
dos 117326 7 3 4 3
normal 3 29315 11 57 7
probe 2 0 1164 0 0
r2l 1 11 0 289 1
u2r 0 1 0 0 2

# 誤差率を評価する
> kddcup_table_pre <- table(kddcup_nnet_pre,kddcup_pre$label)
> 1-sum(diag(kddcup_table_pre))/sum(kddcup_table_pre)
[1] 0.0007489525

表でまとめます

result_predictiondosnormalprober2lu2r適合率
dos117326734399.99...%
normal3293151157799.7%
probe2011640099.8%
r2l1110289195.7%
u2r0100266.7%
再現率99.99...%99.9%98.8%82.6%15.4%99.9%

精度はクロスバリデーションを用いたC-SVMより上がりました?

過学習っぽい。

おまけ それぞれのノードの重みを確認する

生態学のデータ解析 - ニューラルネット を参考にニューラルネットのノードを可視化してみます。

source("http://hosho.ees.hokudai.ac.jp/~kubo/log/2007/img07/plot.nn.txt")
> plot.nn(kddcup_nnet)

:

おお~。可視化するとすごいっすね。

結論

パターン認識にはやはり古典的ではありますが、SVMは単純なパラメータでもかなりの精度を叩きだしました。さらに学習データが十分だと思ってもクロスバリデーションでほんの少し精度を向上できました。

それ以上に精度を出せたのはニューラルネットとなりました。(過学習気味ですが)ある程度データ量があれば、強力な分類精度を叩きだしますね。

参考文献

SVMで天気予報

【caret】R で SVM を学ぶ【Grid-Search】

Rでnnetを試してみる

RとWEKAによるニューラルネットワーク

生態学のデータ解析 - ニューラルネット

tubone24にラーメンを食べさせよう!

ぽちっとな↓

Buy me a ramen

 Related Posts

hatena bookmark