「あなたのデータサイエンスの力で企業の課題を解決する」
~Nishika Web サイトから~
こんにちは、井上
@michio_MWJ です。1222 年以来 800 年ぶりの「スーパー猫の日」はいかがでしたか? datetime を使って 22:22:22 の
記念ツイートをされた方もいらっしゃいましたね(笑)私は既に寝落ちていました・・。
さて、今回は Nishika さんとの共同企画「
ソフトウェアの異常検知」のご紹介とベンチマークコードの解説です。今日(2/25)からはじまるこのコンペ。ソフトウェアの安定的な稼働の為に異常を早く確実に検知すべく、ログデータをもとにしたシステムの健全性状態予測に挑戦します。
~2022/3/22 追記~
MATLAB ライセンス提供中!
ライセンスに申し込む際のステップは
申し込みガイド にまめました。実際に利用いただけるまで申請から少し時間がかかりますのでまずは申請を済ませておくことをおススメいたします。
コンテスト概要
- 分析課題:ソフトウェアの異常検知
- 参加締切:2022年 4 月 26 日
- 賞金総額:25万円
MATLAB をメインに使って参加頂いた方には順位に応じて MATLAB 特別賞をお贈りします。
データは OS や WebLogic Server より観測される CPU やメモリの利用率など。それらを説明変数にしてソフトウェア(MATLAB じゃないよ)の異常を予測するというシンプルな設定。与えられた多くの時系列データをどう料理するかが問われます。
ベンチマーク解析
ここでは学習データの一部を使ってデータの読み込みから予測モデルの学習までを実施してみます。スクリプト自体はページ下部のボタンから DL できますし、使用するデータと一緒に
MATLAB Drive でも公開しています。MathWorks アカウントと関連ライセンスがあれば MATLAB Online で実行してみることもできます。
今回、学習用データセットとして与えられている train.csv は 1.8 GB と多少大きいので、このコードでは一部を取り出した trainIphost17.csv を使用します。変数 host が “lphost17” のデータだけを抽出しています。読み込むデータを train.csv に置き換えてもそのまま機能するはずです。
使用する Toolbox
データの読み込み
datadir = “datafiles”; % ファイルの保存場所
filepath = fullfile(datadir,“trainlphost17.csv”);
170 変数が認識されていますね。問題なく読み込めそうです。
preview で冒頭の 8 行だけ確認してみます。
preview(ds)
ans = 8×170 table
|
timestamp |
host |
Anomaly |
process |
ActiveConnections__MXBean_com_bea_Name_source01_Type_JDBCConnec |
ActiveConnections__MXBean_com_bea_Name_source01_Type_JDBCDataSo |
ActiveConnections__MXBean_com_bea_Name_source02_Type_JDBCConnec |
ActiveConnections__MXBean_com_bea_Name_source02_Type_JDBCDataSo |
ActiveConnections__MXBean_com_bea_Name_source03_Type_JDBCConnec |
ActiveConnections__MXBean_com_bea_Name_source03_Type_JDBCDataSo |
ActiveConnections__MXBean_com_bea_Name_source04_Type_JDBCConnec |
ActiveConnections__MXBean_com_bea_Name_source04_Type_JDBCDataSo |
ActiveConnections__MXBean_com_bea_Name_source05_Type_JDBCConnec |
ActiveConnections__MXBean_com_bea_Name_source05_Type_JDBCDataSo |
ActiveConnections__MXBean_com_bea_Name_source06_Type_JDBCConnec |
ActiveConnections__MXBean_com_bea_Name_source06_Type_JDBCDataSo |
ActiveConnections__MXBean_com_bea_Name_source08_Type_JDBCConnec |
ActiveConnections__MXBean_com_bea_Name_source08_Type_JDBCDataSo |
ActiveConnections__MXBean_com_bea_Name_source09_Type_JDBCConnec |
ActiveConnections__MXBean_com_bea_Name_source09_Type_JDBCDataSo |
ActiveConnections__MXBean_com_bea_Name_source10_Type_JDBCConnec |
ActiveConnections__MXBean_com_bea_Name_source10_Type_JDBCDataSo |
ActiveTransactions__MXBean_com_bea_Name_JTARuntime_Type_JTARunt |
AvailableDbConnectionActivity__d_dx_MXBean_com_bea_Name_source0 |
AvailableDbConnectionActivity__d_dx_MXBean_com_bea_Name_sour_1 |
AvailableDbConnectionActivity__d_dx_MXBean_com_bea_Name_sour_2 |
AvailableDbConnectionActivity__d_dx_MXBean_com_bea_Name_sour_3 |
AvailableDbConnectionActivity__d_dx_MXBean_com_bea_Name_sour_4 |
AvailableDbConnectionActivity__d_dx_MXBean_com_bea_Name_sour_5 |
AvailableDbConnectionActivity__d_dx_MXBean_com_bea_Name_sour_6 |
1 |
2015-02-06 04:44:00 |
‘lphost17’ |
0 |
‘wls1’ |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
2 |
2015-02-06 04:45:00 |
‘lphost17’ |
0 |
‘wls1’ |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
3 |
2015-02-06 04:46:00 |
‘lphost17’ |
0 |
‘wls1’ |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
4 |
2015-02-06 04:47:00 |
‘lphost17’ |
0 |
‘wls1’ |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
5 |
2015-02-06 04:48:00 |
‘lphost17’ |
0 |
‘wls1’ |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
6 |
2015-02-06 04:49:00 |
‘lphost17’ |
0 |
‘wls1’ |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
7 |
2015-02-06 04:50:00 |
‘lphost17’ |
0 |
‘wls1’ |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
8 |
2015-02-06 04:51:00 |
‘lphost17’ |
0 |
‘wls1’ |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
timestamp, host, Anomaly, process など170 個の変数が確認できますね。
readall を使って一気に読み込んでみます。
matfilepath = fullfile(datadir,“trainData_lphost17.mat”);
if exist(matfilepath,“file”)
load(matfilepath,“trainData”);
% mat ファイルからの読み込みの方が速いので保存しておく
save(matfilepath,“trainData”);
host と process がデータ元のラベルの様なのでこれらの種類を確認します。
unique(trainData.process)
このデータでは host は lphost17 のみ、そして process は wls1 と wls2 です。正常か異常のラベルが Anomaly という変数で、今回の目的変数ですね。ラベルの数を
tabulate 関数で見ておきます。
tabulate(trainData.Anomaly)
Value Count Percent
0 198122 99.66%
1 676 0.34%
異常データ(Anomaly = 1)がかなり少ないことが確認できます。データ元は host/process で特定できますが、2変数だと不便なのでデータ元を表す変数 origin を作っておきます。それぞれカテゴリ変数に変換しておきます。
trainData.origin = categorical(string(trainData.host) + string(trainData.process));
trainData.host = categorical(trainData.host);
trainData.process = categorical(trainData.process);
時系列の確認
ここでは host/process 毎にデータ、特に時刻データをチェックします。パッと見たところ時刻データはきれいに並んでいるようですが、timestamp の最大、最小、時間間隔を念のため確認します。
Gstats = groupsummary(trainData,[“host”,“process”],{“max”,“min”,…
@(x) {unique(diff(x))}},“timestamp”)
Gstats = 2×6 table
|
host |
process |
GroupCount |
max_timestamp |
min_timestamp |
fun1_timestamp |
1 |
lphost17 |
wls1 |
99396 |
2015-04-16 10:46:00 |
2015-02-06 04:44:00 |
10×1 duration |
2 |
lphost17 |
wls2 |
99402 |
2015-04-16 10:46:00 |
2015-02-06 04:44:00 |
9×1 duration |
データ数は wls1/wls2 ともに大体同じ。計測開始・終了時刻も同じとなっています。時間ステップ幅を確認するために関数ハンドルを使って @(x) {unique(diff(x))} という処理を加えています。process = wls1 のデータを見ると
Gstats.fun1_timestamp{1}
00:01:00
00:02:00
00:03:00
00:06:00
00:07:00
00:11:00
00:12:00
00:21:00
01:01:00
03:33:00
異常の発生状況プロット
学習データ内の異常発生状況を確認します。
uniqueOri = unique(trainData.origin);
for ii=1:length(uniqueOri)
idx = trainData.origin == uniqueOri(ii);
plot(tmp.timestamp,tmp.Anomaly,LineWidth=2)
difftimestep = [0; diff(tmp.timestamp)];
plot(tmp.timestamp, minutes(difftimestep),LineWidth=2)
ylabel(“Interval (minutes)”)
wls1 では 5 回、wls2 では 2 回異常が発生しています。時間ステップの揺らぎ合わせてプロットしていますが、異常発生と同時に起こっている場合もあれば関係なく発生している箇所もあります。
説明変数の絞り込み
説明変数が約 170 個と多いので、サクッと計算させるために使用する変数を絞り込みます。絞り込む方法は多数考えられますがこちらのヘルプページ(
特徴選択の紹介)も参考になるかもしれません。ここでは Anomaly と相関が高いもの、そして相互に相関が高いものは片方だけを残すことにします。timestamp, host, process 以外の数値データを対象とします。
features = trainData(:,S); % 数値データだけ取り出す
corrcoef 関数で相互相関を計算して、Anomaly との相関係数の絶対値(1行目)を昇順に並べます。
R = corrcoef(features.Variables);
[Rsort,idx] = sort(abs(R(1,:)),‘descend’,‘MissingPlacement’,‘last’);
title(“Anomaly との相関係数絶対値”)
次に Anomaly との相関係数(絶対値)が上位 30 の変数に絞って、相互相関を見てみます。
features30 = features(:,idx(1:30));
R = corrcoef(features30.Variables);
説明変数間でも相関係数が高いものがあります。特に高い(0.9 以上)ものは同じ情報を持っているとみなして片方だけ使用することで更に変数を絞り込みます。
tmp = triu(absR>0.9,1); % 行列の上三角部分
[row,col] = ind2sub(size(absR),find(tmp)); % 0.9 以上の位置
col = col(~idxDiag); % 0.9 未満のものだけ残す
features2use = features30;
features2use(:,col) = [];
R = corrcoef(features2use.Variables);
Anomaly 含めて 23 個残りました。変数の名前(Anomaly との相関(絶対値)が高い順)を確認しておきます。
varnames = features2use.Properties.VariableNames’
‘Anomaly’
‘GCTime__incld_dx_MXBean_java_lang_name_PSMarkSweep_type_Garbage’
‘GCActivity__incld_dx_MXBean_java_lang_name_PSMarkSweep_type_Gar’
‘ActiveConnections__MXBean_com_bea_Name_source09_Type_JDBCConnec’
‘NoData’
‘ProcessCPU__MXBean_java_lang_type_OperatingSystem__ProcessCpuLo’
‘LastGCDuration__MXBean_java_lang_name_PSMarkSweep_type_GarbageC’
‘SystemCPU__MXBean_java_lang_type_OperatingSystem__SystemCpuLoad’
‘LastGCDuration__MXBean_java_lang_name_PSScavenge_type_GarbageCo’
‘ProcessCPU___Process_java__CPU_’
予測モデルの学習
次はこの変数で予測モデルを作ってみます。
学習に使用するデータ区間の切り出し
上で見た通り正常(Anomaly = 0)のデータが多いため、正常データのアンダーサンプリングの為以下の操作を行います。
- 異常発生箇所を確認し異常区間の長さ(データ点数)を確認
- 異常中(Anomaly = 1) の前後に異常区間の長さ分のデータを加えた区間を抽出
dataAll = []; % データ確保用変数(単純に繋げたもの)
uniqueOri = unique(trainData.origin)
lphost17wls1
lphost17wls2
% 注意:スタートとエンドは正常であることが前提(lphost7 のデータはOK)
for ii=1:length(uniqueOri)
idx = trainData.origin == uniqueOri(ii);
tmp = trainData(idx,varnames);
% 0から1に変化する箇所: anomaly starts
% 1から0に変化する箇所: anomaly ends
isAnomaly1 = tmp.Anomaly;
isAnomaly2 = [0; isAnomaly1(1:end-1)];
isChange = xor(isAnomaly1, isAnomaly2); % 変化点検知
posFalls = find(isChange & isAnomaly2); % 1 -> 0 (end)
posRises = find(isChange & ~isAnomaly2); % 0 -> 1 (start)
for jj=1:length(posRises)
duration = 1*(posFalls(jj) – posRises(jj));
if posRises(jj) – duration > 0
idxStart = posRises(jj) – duration;
if posFalls(jj) + duration < height(tmp)
idxEnd = posFalls(jj) + duration;
dataSeg = [dataSeg; {tmp(idxStart:idxEnd,:)}];
dataAll = [dataAll; tmp(idxStart:idxEnd,:)];
dataOri = [dataOri; uniqueOri(ii)];
tabulate(dataAll.Anomaly)
Value Count Percent
0 1318 64.70%
1 719 35.30%
正常 (0) と異常 (1) のデータ比が 2:1 程度です。
切り出し部分のプロット
まず単純に繋げてプロット。
異常箇所毎に分けてプロット。
area(tmp.Anomaly,‘LineWidth’,2)
予測モデル学習 LSTM
データの分割
上で作った各異常区間のデータ(dataSeg)を使用しますが、まずは学習用とテスト用に分割します。ここではデータ元(lphost17wls1 or lphost17wls2)で層化させて、学習データと検証データを分割します。
rng(0) % 同じ結果が再現するように乱数シートを固定
cv = cvpartition(dataOri,‘Holdout’,0.4);
学習データ(6セット)
trainSeg = dataSeg(cv.training)
trainSeg = 6×1 cell
|
1 |
1 |
391×23 table |
2 |
70×23 table |
3 |
115×23 table |
4 |
436×23 table |
5 |
79×23 table |
6 |
388×23 table |
検証データ(3セット)
testSeg = dataSeg(cv.test)
testSeg = 3×1 cell
|
1 |
1 |
82×23 table |
2 |
457×23 table |
3 |
19×23 table |
LSTM の学習データとして使用するために、各時系列セグメントとラベルデータをセル配列として成形します。generateDataset4LSTM 関数はスクリプト下部にローカル関数として定義しています。
[xTrain,yTrain] = generateDataset4LSTM(trainSeg); % 学習データ
[xTest,yTest] = generateDataset4LSTM(testSeg); % テストデータ
こんな形です。セルの中身は 22 個の特徴量が 22 x N の形で並んでいることに注意。
xTrain
xTrain = 6×1 cell
|
1 |
1 |
22×391 double |
2 |
22×70 double |
3 |
22×115 double |
4 |
22×436 double |
5 |
22×79 double |
6 |
22×388 double |
ネットワーク構築
試しに隠れユニット数 100 の LSTM 層を 2 層としてみます。この辺は最適化する余地はたくさんありそうです。
実験マネージャーアプリが役に立つかもしれません。
numFeatures = 22; % 特徴量変数の数
numHiddenUnits = 100; % 隠れユニット数
numClasses = 2; % 予測クラス数(異常・正常)
sequenceInputLayer(numFeatures)
lstmLayer(numHiddenUnits,‘OutputMode’,‘sequence’)
lstmLayer(numHiddenUnits,‘OutputMode’,‘sequence’)
fullyConnectedLayer(numClasses)
classificationLayer(‘Classes’,{‘0’,‘1’},‘ClassWeights’,[1,2])]
layers =
次の層をもつ 6×1 の Layer 配列:
1 ” シーケンス入力 22 次元のシーケンス入力
2 ” LSTM 100 隠れユニットのある LSTM
3 ” LSTM 100 隠れユニットのある LSTM
4 ” 全結合 2 全結合層
5 ” ソフトマックス ソフトマックス
6 ” 分類出力 クラス ‘0’ および ‘1’ によるクラス加重 crossentropyex
分類層の設定で重み付きクロスエントロピー損失関数を使用しています。詳細は
こちらの classificationLayer のヘルプページ(英語)を確認してください。
学習オプション設定
options = trainingOptions(‘adam’, …
‘ValidationData’, {xTest,yTest},…
‘Plots’,‘training-progress’);
学習
rng(0) % 同じ結果が再現するように乱数シートを固定
net = trainNetwork(xTrain,yTrain,layers,options);
単一の CPU で学習中。
|===================================================================================|
| エポック | 反復 | 経過時間 | ミニバッチの精度 | 検証精度 | ミニバッチ損失 | 検証損失 | 基本学習率 |
| | | (hh:mm:ss) | | | | | |
|===================================================================================|
| 1 | 1 | 00:00:03 | 24.05% | 45.68% | 1.0928 | 0.9173 | 0.0010 |
| 9 | 50 | 00:00:14 | 93.04% | 72.80% | 0.1581 | 0.8408 | 0.0010 |
| 10 | 60 | 00:00:17 | 98.62% | 72.91% | 0.0531 | 0.8381 | 0.0010 |
|===================================================================================|
学習終了: 最大数のエポックが完了しました。
保存しておきます。
save(‘lstmnet_lphost17_LSTM.mat’,‘net’,‘varnames’);
検証データへの予測精度を再確認
[yPred, scores] = classify(net,xTest);
混同行列を描きます(異常区間ごとにセル配列になっているのですべて結合しています)
confusionchart([yTest{:}], [yPred{:}],…
‘ColumnSummary’,‘column-normalized’,…
‘RowSummary’,‘row-normalized’);
まあまぁです。
評価指標 PRC (Prediction-Recall-Curve)
今回の評価指標である PRC (Prediction-Recall-Curve) を描いてみます。
labelScores = [scores{:}];
anomalyScores = labelScores(2,:); % labels(2) = 1 (異常) の score
thresholds = linspace(0,1,N);
labels = net.Layers(end).Classes;
% 閾値を変えて recall/recision を計算
[recalls(ii),precisions(ii)] = getRecallPrecisionCurve(th,anomalyScores,trueLabel,labels);
plot(recalls, precisions)
提出用の予測作成
datadir フォルダ内に test.csv がある想定です。
load(‘lstmnet_lphost17_LSTM.mat’,‘net’,‘varnames’); % モデル読み込み
テストデータ読み込み
test.csv ファイルは コンペページ(
ソフトウェアの異常検知)から参加登録後に入手して datadir に保存していることが前提です。
matfilepath = fullfile(datadir,“test.mat”);
if exist(matfilepath,“file”)
load(matfilepath,“testData”);
filepath = fullfile(datadir,‘test.csv’);
testData = readtable(filepath);
% mat ファイルからの読み込みの方が速いので保存しておく
save(matfilepath,“testData”);
学習に使用した変数 varnames と id/host/process のみを取り出します。
testData = testData(:,[{‘id’;‘host’;‘process’};varnames(2:end)]);% varname(1) は Anomaly
head(testData)
ans = 8×25 table
|
id |
host |
process |
GCTime__incld_dx_MXBean_java_lang_name_PSMarkSweep_type_Garbage |
GCActivity__incld_dx_MXBean_java_lang_name_PSMarkSweep_type_Gar |
ActiveConnections__MXBean_com_bea_Name_source09_Type_JDBCConnec |
NoData |
ProcessCPU__MXBean_java_lang_type_OperatingSystem__ProcessCpuLo |
LastGCDuration__MXBean_java_lang_name_PSMarkSweep_type_GarbageC |
SystemCPU__MXBean_java_lang_type_OperatingSystem__SystemCpuLoad |
LastGCDuration__MXBean_java_lang_name_PSScavenge_type_GarbageCo |
ProcessCPU___Process_java__CPU_ |
Rel_HeapUsage___MXBean_java_lang_type_Memory__HeapMemoryUsage_u |
Rel_OpenFileDescriptors___MXBean_java_lang_type_OperatingSystem |
StuckThreads__MXBean_com_bea_ApplicationRuntime_sys01_Name_defa |
Rel_SwapUsage____Swap_used__Swap_total__ |
MemorySpaceUsage___MXBean_java_lang_name_PSEdenSpace_type_Mem_1 |
MemorySpaceUsage___MXBean_java_lang_name_PSEdenSpace_type_Memor |
Rel_HeapCommitted___MXBean_java_lang_type_Memory__HeapMemoryUsa |
Rel_UnavailableConnections___MXBean_com_bea_Name_source09_Type_ |
GCTime__incld_dx_MXBean_java_lang_name_PSScavenge_type_GarbageC |
MemorySpaceUsage___MXBean_java_lang_name_PSOldGen_type_MemoryPo |
DaemonThreadCount__MXBean_java_lang_type_Threading__DaemonThrea |
ConnectionDelay__MXBean_com_bea_Name_source08_Type_JDBCConnecti |
DBConnectionStarted__incld_dx_MXBean_com_bea_Name_source09_Type |
1 |
0 |
lphost06 |
wls1 |
0 |
0 |
2 |
0 |
0.0020 |
2008 |
0.0050 |
28 |
0 |
0.2048 |
0.1484 |
0 |
0.4239 |
0.0367 |
0.0488 |
0.3722 |
1 |
0 |
0.4692 |
66 |
0 |
0 |
2 |
1 |
lphost06 |
wls1 |
0 |
0 |
2 |
0 |
0.0020 |
2008 |
0.0270 |
25 |
0 |
0.1929 |
0.1484 |
0 |
0.4238 |
0.0046 |
0.0484 |
0.3702 |
1 |
25 |
0.4692 |
66 |
0 |
0 |
3 |
2 |
lphost06 |
wls1 |
0 |
0 |
2 |
0 |
0.0010 |
2008 |
0.0060 |
25 |
0 |
0.1985 |
0.1484 |
0 |
0.4238 |
0.0195 |
0.0484 |
0.3702 |
1 |
0 |
0.4692 |
66 |
0 |
0 |
4 |
3 |
lphost06 |
wls1 |
0 |
0 |
2 |
0 |
0.0010 |
2008 |
0.0160 |
25 |
0 |
0.2040 |
0.1484 |
0 |
0.4236 |
0.0344 |
0.0484 |
0.3702 |
1 |
0 |
0.4692 |
66 |
0 |
0 |
5 |
4 |
lphost06 |
wls1 |
0 |
0 |
2 |
0 |
0.0020 |
2008 |
0.0060 |
25 |
0 |
0.1929 |
0.1484 |
0 |
0.4235 |
0.0045 |
0.0485 |
0.3718 |
1 |
26 |
0.4692 |
66 |
0 |
0 |
6 |
5 |
lphost06 |
wls1 |
0 |
0 |
2 |
0 |
0.0020 |
2008 |
0.0060 |
25 |
0 |
0.1991 |
0.1484 |
0 |
0.4232 |
0.0213 |
0.0485 |
0.3718 |
1 |
0 |
0.4692 |
66 |
0 |
0 |
7 |
6 |
lphost06 |
wls1 |
0 |
0 |
2 |
0 |
0.0010 |
2008 |
0.0040 |
25 |
0 |
0.2047 |
0.1484 |
0 |
0.4233 |
0.0363 |
0.0485 |
0.3718 |
1 |
0 |
0.4692 |
66 |
0 |
0 |
8 |
7 |
lphost06 |
wls1 |
0 |
0 |
2 |
0 |
0.0020 |
2008 |
0.0080 |
33 |
0 |
0.1924 |
0.1484 |
0 |
0.4231 |
0.0031 |
0.0481 |
0.3701 |
1 |
33 |
0.4692 |
66 |
0 |
0 |
ここは思い切って、host/process もまとまったうえで、時系列順に id が並んでいるという想定で進めます・・。host/process 別に識別できるようデータ元(origin)変数を作っておきます。
testData.origin = categorical(string(testData.host) + string(testData.process));
予測
host/process 毎に切り分けて予測処理を実行します。
testOri = unique(testData.origin);
testData.Anomaly = zeros(height(testData),1);
idx = testData.origin == testOri(ii);
tmpData = testData{idx,varnames(2:end)};
tmpData = zscore(tmpData); % 変数を正規化(方法は検討の余地あり)
[~,tmpScores] = classify(net,tmpData’);
testData.Anomaly(idx,:) = tmpScores(2,:)’; % labels(2) の score
提出ファイル作成
submission.csv に出力する値は Anomaly = 1 であるスコアなので、上の scores の2列目です。
output = testData(:,[“id”, “Anomaly”]);
head(output)
ans = 8×2 table
|
id |
Anomaly |
1 |
0 |
0.4541 |
2 |
1 |
0.3938 |
3 |
2 |
0.3247 |
4 |
3 |
0.2618 |
5 |
4 |
0.2107 |
6 |
5 |
0.1712 |
7 |
6 |
0.1418 |
8 |
7 |
0.1215 |
writetable(output,“submission.csv”);
まとめ
無事に最後の提出用ファイル作成までたどり着きました。今回はサクサク進めるように一部のデータだけを使ってみましたが、ぜひ本番の train.csv でも試してみてください。
もう少しデータを詳しく調査してもっとよい変数を探してみたり、新たな特徴量を作ってみたり、モデルに凝ってみたり、といろいろ試したいことが出てきますが、ここから先はコンペに参加する方のお楽しみということで私からの紹介はここまでとします。
いずれにしても一部の変数(or 一部のデータ)から始めてみて、まずデータの把握と課題の理解が第一歩かと思います。応募締め切りは 4 月 26 日。入賞目指して頑張ってください!
MATLAB ライセンス
関連リンク集
ヘルパー関数
閾値を変えて recall と precision を計算する関数
function [recall,precision] = getRecallPrecisionCurve(threshold,anomalyScores,trueLabel,labels)
prediction = repelem(labels(1), length(trueLabel),1);
prediction(anomalyScores > threshold,:) = labels(2);
results = confusionmat(trueLabel,prediction);
LSTM を学習する為にデータを成形する関数
function [x,y] = generateDataset4LSTM(dataSeg)
% Anomaly 以外の変数を正規化(方法は検討の余地あり)
tmpT{:,2:end} = zscore(tmpT{:,2:end});
tmpY = tmpT.Anomaly(:,:)’;
y = [y; {categorical(tmpY)}];
Comments
To leave a comment, please click here to sign in to your MathWorks Account or create a new one.