Skip to content

Latest commit

 

History

History
713 lines (409 loc) · 35.5 KB

File metadata and controls

713 lines (409 loc) · 35.5 KB

第8章: ニューラルネット

Copyright (c) 2020 NLP100-MATLAB

第6章で取り組んだニュース記事のカテゴリ分類を題材として,ニューラルネットワークでカテゴリ分類モデルを実装する.なお,この章ではPyTorch, TensorFlow, Chainerなどの機械学習プラットフォームを活用せよ.

70. 単語ベクトルの和による特徴量

問題50で構築した学習データ,検証データ,評価データを行列・ベクトルに変換したい.例えば,学習データについて,すべての事例の特徴ベクトルを並べた行列と,正解ラベルを並べた行列(ベクトル)を作成したい.

ここで, は学習データの事例数であり, はそれぞれ, 番目の事例の特徴量ベクトルと正解ラベルを表す.なお,今回は「ビジネス」「科学技術」「エンターテイメント」「健康」の4カテゴリ分類である. で4未満の自然数(0を含む)を表すことにすれば,任意の事例の正解ラベル で表現できる.以降では,ラベルの種類数を で表す(今回の分類タスクでは である).

番目の事例の特徴ベクトルは,次式で求める.

ここで, 番目の事例は 個の(記事見出しの)単語列 から構成され, は単語 に対応する単語ベクトル(次元数は )である.すなわち, 番目の事例の記事見出しを,その見出しに含まれる単語のベクトルの平均で表現したものが である.今回は単語ベクトルとして,問題60でダウンロードしたものを用いればよい. 次元の単語ベクトルを用いたので, である.

番目の事例のラベルは,次のように定義する.

  • (記事が「ビジネス」カテゴリの場合)
  • (記事が「科学技術」カテゴリの場合)
  • (記事が「エンターテイメント」カテゴリの場合)
  • (記事が「健康」カテゴリの場合)

なお,カテゴリ名とラベルの番号が一対一で対応付いていれば,上式の通りの対応付けでなくてもよい.

以上の仕様に基づき,以下の行列・ベクトルを作成し,ファイルに保存せよ.

  • 学習データの特徴量行列:
  • 学習データのラベルベクトル:
  • 検証データの特徴量行列:
  • 検証データのラベルベクトル:
  • 評価データの特徴量行列:
  • 評価データのラベルベクトル:

なお,はそれぞれ,学習データの事例数,検証データの事例数,評価データの事例数である.

問題50で保存したデータの読み込み

問題50で保存したデータを読み込みます。

opt = detectImportOptions('train.txt');
opt = setvartype(opt,["categorical","string"]);

dataTrain = readtable('train.txt',opt);
dataValid = readtable('valid.txt',opt);
dataTest  = readtable('test.txt',opt);

単語ベクトルの読み込み

単語ベクトルを作成するためのWord2Vecモデルを読み込みます(学習済みモデルのファイルは同じフォルダにあるものと仮定しています).読み込みにはバイナリ形式のword2vecファイルを読み込むための関数readW2Vbin.m を自前で作ったので、それを使っています.興味のある方はMathWorks の FileExchange に上げてありますので,そちらをどうぞ.

emb = readW2Vbin('GoogleNews-vectors-negative300.bin');

形態素解析

特徴量を計算するために、形態素解析にかけましょう。

docsTrain = tokenizedDocument(dataTrain.Title);
docsValid = tokenizedDocument(dataValid.Title);
docsTest  = tokenizedDocument(dataTest.Title);

tknTrain = tokenDetails(docsTrain);
tknValid = tokenDetails(docsValid);
tknTest  = tokenDetails(docsTest);

特徴量の計算

特徴量を計算するためには、各文を構成する単語一つ一つについて、word2vec を適用する必要があります.そこで、まずtokenDetails を使って単語の一覧テーブルを生成します.そして,列Token に対して一括でword2vec を適用し,結果を新たな列 vec に格納します.文毎に平均をとるには,列 DocumentNumber をキーにして関数 grpstats を適用します.

tknTrain.vec = word2vec(emb,tknTrain.Token);
tknValid.vec = word2vec(emb,tknValid.Token);
tknTest.vec  = word2vec(emb,tknTest.Token);

XTrainTable = grpstats(tknTrain(:,[2 end]),'DocumentNumber');
XValidTable = grpstats(tknValid(:,[2 end]),'DocumentNumber');
XTestTable  = grpstats(tknTest(:,[2 end]),'DocumentNumber');

XTrain = XTrainTable.mean_vec;
XValid = XValidTable.mean_vec;
XTest  = XTestTable.mean_vec;

ラベルベクトルの生成

MATLABでラベルデータを扱う場合は,Categorical配列として扱った方が楽な場合が多いので,作業としては読み込んだデータテーブルから該当する部分を抜きだすだけになります.

YTrain = dataTrain.Category;
YValid = dataValid.Category;
YTest  = dataTest.Category;

どうしてもベクトル化したいならば,すでに付いている文字列のラベルを数値に変換すればよいので,これも簡単です.各データに含まれているCategory列(b, t, e, m のいずれかの値をとる)をChar 型に変換して文字行列を作成し,その文字行列の各行と文字列'btem'とを比較します.比較の結果は,Category列 と同じ行数で4列の論理配列になります.これは,ラベルデータのOne-Hot表現ともよばれる表現方法ですね.

TTrain = char(dataTrain.Category)=='btem';
TValid = char(dataValid.Category)=='btem';
TTest  = char(dataTest.Category) =='btem'
TTest = 1334x4 の logical 配列    
   1   0   0   0
   1   0   0   0
   1   0   0   0
   1   0   0   0
   1   0   0   0
   1   0   0   0
   1   0   0   0
   1   0   0   0
   0   0   1   0
   0   0   1   0

この配列に対してfindを適用すると,該当するカテゴリーの線形インデックスが得られます.得られた線形インデックスを,通常の行列の添え字形式に変換してその第二成分をとることで,ラベルベクトルになります.

[~,YTrainVec] = ind2sub([height(dataTrain) 4],find(TTrain));
[~,YValidVec] = ind2sub([height(dataValid) 4],find(TValid));
[~,YTestVec ] = ind2sub([height(dataTest) 4], find(TTest))
YTestVec = 1334x1    
     1
     1
     1
     1
     1
     1
     1
     1
     1
     1

71. 単層ニューラルネットワークによる予測

問題70で保存した行列を読み込み,学習データについて以下の計算を実行せよ.

ただし, はソフトマックス関数, は特徴ベクトル を縦に並べた行列である.

行列 は単層ニューラルネットワークの重み行列で,ここではランダムな値で初期化すればよい(問題73以降で学習して求める).なお, は未学習の行列 で事例 を分類したときに,各カテゴリに属する確率を表すベクトルである. 同様に, は,学習データの事例 について,各カテゴリに属する確率を行列として表現している.

の定義

word2vec モデルからモデル次元,学習データからカテゴリー数を取得して,行列を生成します.

inputSize = emb.Dimension;
numClasses = numel( categories(dataTrain.Category));
W = rand([inputSize, numClasses]);

の計算

数式の通りに記述するだけ,と言いたいのですが一つだけ注意が必要です.一般に機械学習ではデータの行列は行が観測,列が特徴量に対応していますが,深層学習ではそれが逆になります.MATLABの関数の実装も概ねこの原理に従っていますので,softmax関数の入力はを転置したものになります.結果の確率ベクトルを行ベクトルにするためには,さらにsoftmax関数の計算結果をもう一度転置する必要があります.

Yhat = softmax((XTrain(1:4,:)*W)')'
Yhat = 4x4 の single 行列    
    0.2863    0.2195    0.2776    0.2165
    0.4017    0.1729    0.1647    0.2607
    0.2441    0.1912    0.2021    0.3626
    0.3929    0.2097    0.2409    0.1566

の計算

の計算ができていれば,単一の事例に対する計算結果は対応する行を抜き出すだけです.

yhat_1 = Yhat(1,:)
yhat_1 = 1x4 の single 行ベクトル    
    0.2863    0.2195    0.2776    0.2165

72. 損失と勾配の計算

学習データの事例 と事例集合 に対して,クロスエントロピー損失と,行列 に対する勾配を計算せよ.なお,ある事例 に対して損失は次式で計算される.

( は事例 に分類される確率)

ただし,事例集合に対するクロスエントロピー損失は,その集合に含まれる各事例の損失の平均とする.

ちょっとした手計算

クロスエントロピー損失と勾配については,ラベルデータをOne-Hot表現で表現すると扱いが楽になります.事例集合全体の損失関数をとすると,

となります.また,行列に対する勾配は次のようにかけます.

これを実装すると次のようになります.あってるかな?

Yhat = softmax((XTrain*W)')';

L = -sum(sum(TTrain.*log(Yhat)))
L = 1.5060e+04
dLdW = -XTrain'*(TTrain-Yhat)
dLdW = 300x4 の single 行列    
   23.0598   -3.0855 -118.0084   98.0341
  -39.3303   13.8832  -53.9515   79.3985
   21.0242  -38.0043  128.0468 -111.0667
 -110.2795   38.3179   -3.8562   75.8178
   60.7615   17.1151  -72.9147   -4.9619
   56.3110  -36.3838  -21.5720    1.6448
   15.8674  -34.8244   60.4932  -41.5362
   22.8266  -77.9163  189.9676 -134.8779
 -208.1470   53.7098  -51.3764  205.8136
 -118.0010   18.3425  -85.6335  185.2921

73. 確率的勾配降下法による学習

確率的勾配降下法(SGD: Stochastic Gradient Descent)を用いて,行列を学習せよ.なお,学習は適当な基準で終了させればよい(例えば「100エポックで終了」など).

ニューラルネットの定義

問題73以降では手を抜きます.というのも,MATLABのDeepLearning Toolbox では重み行列・損失関数・勾配は内部で自動的に計算してくれるので直接扱うことはほとんどないからです.ここでは,今考えている問題をネットワークオブジェクトとして定義して学習を行います.ここまでの内容から,ネットワークは入力層,全結合層(重み行列に相当),ソフトマックス層,分類出力層(交差エントロピー損失の計算に相当)の4層で構成されることになります.

Deep Learning Toolbox では,画像・シーケンスデータ両方を扱うことができますが,この例題の場合には,サイズが300×1のグレースケール画像(チャンネル数1)として扱うことにします.

layers = [ ...
  imageInputLayer([300 1 1],"Name","Input","Normalization","none"); % 入力層
  fullyConnectedLayer(numClasses,"Name","FC"); ...  % 全結合層
  softmaxLayer; ...       % ソフトマックス層
  classificationLayer ... % 分類出力層
  ];

また,学習・検証・評価データについて,各事例のサイズを300×1×1に変換します.

nTrain = size(XTrain,1);
nValid = size(XValid,1);
nTest  = size(XTest, 1);

XpTrain = reshape(XTrain',[inputSize 1 1 nTrain]);
XpValid = reshape(XValid',[inputSize 1 1 nValid]);
XpTest  = reshape(XTest', [inputSize 1 1 nTest ]);

ネットワークの学習については,学習アルゴリズムとしてSGDを,最大エポック数を100として,あとはデフォルト値を使ってみます.それから,後のほうで「GPUを使うようにコードを改変せよ」とのお題があるので,あえてCPUで計算させるオプションを設定します.また,ここではミニバッチは使用しないので,MiniBatchSize オプションを全事例数とします.

options = trainingOptions('sgdm','MaxEpochs',100,"ExecutionEnvironment","cpu","MiniBatchSize",nTrain);

netInit = trainNetwork(XpTrain,YTrain,layers,options);
|=================================================================|
|  エポック  |  反復  |     経過時間     |  ミニバッチの精度  |  ミニバッチ損失  |  基本学習率  |
|        |      |  (hh:mm:ss)  |            |           |         |
|=================================================================|
|      1 |    1 |     00:00:00 |     21.24% |    1.3853 |  0.0100 |
|     50 |   50 |     00:00:01 |     76.19% |    1.0490 |  0.0100 |
|    100 |  100 |     00:00:01 |     77.39% |    0.9311 |  0.0100 |
|=================================================================|

74. 正解率の計測

問題73で求めた行列を用いて学習データおよび評価データの事例を分類したとき,その正解率をそれぞれ求めよ.

これは簡単.

predResub = classify(netInit,XpTrain);
okRatioResub = nnz(predResub == YTrain)/numel(predResub)
okRatioResub = 0.7739
predTest  = classify(netInit,XpTest);
okRatioTest = nnz(predTest == YTest)/numel(predTest)
okRatioTest = 0.7744

75. 損失と正解率のプロット

問題73のコードを改変し,各エポックのパラメータ更新が完了するたびに,訓練データでの損失,正解率,検証データでの損失,正解率をグラフにプロットし,学習の進捗状況を確認できるようにせよ.

学習パラメータの設定

これらのことは学習オプションのパラメータ値を調整することで実現できます.まず,学習オプションの"Plot"プロパティを "training-progress" にすれば,学習中の状況を表示してくれます.また,プロパティ"ValidationData"に検証用データと検証用ラベルデータをセル配列として与えることで検証データの設定ができます.また,"ValidationFrequency" で検証頻度が設定できます.

options = trainingOptions('sgdm','MaxEpochs',100,'MiniBatchSize',nTrain, ...
  "ExecutionEnvironment","cpu", ...
  "Plots","training-progress", ... 
  "ValidationData",{XpValid,YValid}, ...
  "ValidationFrequency",10);

net0 = trainNetwork(XpTrain,YTrain,layers, options); 
|===================================================================================|
|  エポック  |  反復  |     経過時間     |  ミニバッチの精度  |  検証精度  |  ミニバッチ損失  |  検証損失  |  基本学習率  |
|        |      |  (hh:mm:ss)  |            |        |           |        |         |
|===================================================================================|
|      1 |    1 |     00:00:00 |     20.31% | 21.29% |    1.4049 | 1.3974 |  0.0100 |
|     10 |   10 |     00:00:01 |     32.43% | 36.88% |    1.3401 | 1.3204 |  0.0100 |
|     20 |   20 |     00:00:01 |     52.48% | 56.82% |    1.2331 | 1.2092 |  0.0100 |
|     30 |   30 |     00:00:01 |     66.43% | 68.82% |    1.1530 | 1.1276 |  0.0100 |
|     40 |   40 |     00:00:01 |     71.98% | 72.79% |    1.0996 | 1.0732 |  0.0100 |
|     50 |   50 |     00:00:01 |     74.53% | 75.19% |    1.0610 | 1.0343 |  0.0100 |
|     60 |   60 |     00:00:02 |     75.82% | 76.46% |    1.0302 | 1.0035 |  0.0100 |
|     70 |   70 |     00:00:02 |     76.40% | 77.06% |    1.0037 | 0.9776 |  0.0100 |
|     80 |   80 |     00:00:02 |     76.72% | 77.29% |    0.9802 | 0.9548 |  0.0100 |
|     90 |   90 |     00:00:02 |     76.86% | 77.21% |    0.9590 | 0.9344 |  0.0100 |
|    100 |  100 |     00:00:03 |     76.91% | 77.36% |    0.9396 | 0.9158 |  0.0100 |
|===================================================================================|

figure_0.png

76. チェックポイント

問題75のコードを改変し,各エポックのパラメータ更新が完了するたびに,チェックポイント(学習途中のパラメータ(重み行列など)の値や最適化アルゴリズムの内部状態)をファイルに書き出せ.

学習パラメータの設定

これも簡単で,学習オプションの CheckpointPath プロパティにチェックぽいのとファイルを保存するフォルダ名をセットすれば,各エポック毎にチェックポイントを.mat ファイルに保存してくれます.

options = trainingOptions('sgdm','MaxEpochs',100,'MiniBatchSize',nTrain, ...
  "ExecutionEnvironment","cpu", ...
  "Plots","training-progress", ... 
  "ValidationData",{XpValid,YValid}, ...
  "ValidationFrequency",10, ...
  "CheckpointPath",pwd);
net1 = trainNetwork(XpTrain,YTrain,layers,options);
|===================================================================================|
|  エポック  |  反復  |     経過時間     |  ミニバッチの精度  |  検証精度  |  ミニバッチ損失  |  検証損失  |  基本学習率  |
|        |      |  (hh:mm:ss)  |            |        |           |        |         |
|===================================================================================|
|      1 |    1 |     00:00:00 |     23.66% | 25.64% |    1.4035 | 1.4007 |  0.0100 |
|     10 |   10 |     00:00:01 |     44.42% | 46.70% |    1.3350 | 1.3197 |  0.0100 |
|     20 |   20 |     00:00:01 |     57.16% | 58.17% |    1.2234 | 1.2044 |  0.0100 |
|     30 |   30 |     00:00:01 |     65.17% | 65.14% |    1.1419 | 1.1217 |  0.0100 |
|     40 |   40 |     00:00:02 |     70.10% | 70.61% |    1.0889 | 1.0680 |  0.0100 |
|     50 |   50 |     00:00:02 |     73.09% | 72.64% |    1.0517 | 1.0304 |  0.0100 |
|     60 |   60 |     00:00:03 |     74.57% | 74.36% |    1.0224 | 1.0010 |  0.0100 |
|     70 |   70 |     00:00:03 |     75.48% | 75.86% |    0.9974 | 0.9763 |  0.0100 |
|     80 |   80 |     00:00:04 |     75.92% | 76.54% |    0.9752 | 0.9546 |  0.0100 |
|     90 |   90 |     00:00:04 |     76.26% | 76.76% |    0.9551 | 0.9351 |  0.0100 |
|    100 |  100 |     00:00:05 |     76.54% | 77.29% |    0.9367 | 0.9174 |  0.0100 |
|===================================================================================|

figure_1.png

77. ミニバッチ化

問題76のコードを改変し,事例ごとに損失・勾配を計算し,行列の値を更新せよ(ミニバッチ化).の値をと変化させながら,1エポックの学習に要する時間を比較せよ.

ミニバッチのサイズを振ってループを回せば良いのですね.

miniBatchSize = [64 128 256 512];
elapsedTime   = zeros(size(miniBatchSize));
net2 = cell(size(miniBatchSize));

options = trainingOptions('sgdm','MaxEpochs',100, ...
  "ExecutionEnvironment","cpu", ...
  "Plots","training-progress", ... 
  "ValidationData",{XpValid,YValid}, ...
  "Verbose",false);

for kk = 1:length(miniBatchSize)
  options.MiniBatchSize = miniBatchSize(kk);
  options.ValidationFrequency = floor(nTrain/miniBatchSize(kk));
  tic
  net2{kk} = trainNetwork(XpTrain,YTrain,layers,options);
  elapsedTime(kk) = toc;
  
end

figure_2.png

figure_3.png

figure_4.png

figure_5.png

78. GPU上での学習

問題77のコードを改変し,GPU上で学習を実行せよ.

学習パラメータの設定

これも学習オプションのプロパティ値を変更すれば良いだけです."ExecutionEnvironment"の値を"gpu"に変えるだけ.

options = trainingOptions('sgdm','MaxEpochs',100, ...
  "MiniBatchSize",256, ...
  "ExecutionEnvironment","gpu", ...
  "Plots","training-progress", ... 
  "ValidationData",{XpValid,YValid}, ...
  "ValidationFrequency",20, ...
  "Verbose",false);

net3 = trainNetwork(XpTrain,YTrain,layers,options); 

figure_6.png

79. 多層ニューラルネットワーク

問題78のコードを改変し,バイアス項の導入や多層化など,ニューラルネットワークの形状を変更しながら,高性能なカテゴリ分類器を構築せよ.

間に適当に2層入れて順々に絞ってゆくような作りにしてみます(単なる思いつき).

layers = [ ...
  imageInputLayer([inputSize 1 1],"Name","Input"); ... % 入力層
  fullyConnectedLayer(128,"Name","FC1"); ...
  tanhLayer; ...
  fullyConnectedLayer(32,"Name","FC2"); ...
  reluLayer; ...
  fullyConnectedLayer(8,"Name","FC3"); ...
  reluLayer; ...
  fullyConnectedLayer(numClasses,"Name","FC4"); ...
  softmaxLayer; ...       % ソフトマックス層
  classificationLayer ... % 分類出力層
  ];

MiniBatchSize = 500;
MaxEpochs = floor(MiniBatchSize/4);

options = trainingOptions('sgdm','MaxEpochs',MaxEpochs, ...
  "MiniBatchSize", MiniBatchSize, ...
  "ExecutionEnvironment","gpu", ...
  "Plots","training-progress", ... 
  "ValidationData",{XpValid,YValid}, ...
  "ValidationFrequency",floor(nTrain/MiniBatchSize), ...
  "Verbose",false, ...
  "Shuffle","every-epoch", ...
  "LearnRateSchedule","piecewise", ...
  "LearnRateDropPeriod",10, ...
  "LearnRateDropFactor",0.8, ...
  "InitialLearnRate",0.02, ...
  "L2Regularization",0.005, ...
  "Momentum",0.95);

net4 = trainNetwork(XpTrain,YTrain,layers,options); 

figure_7.png

predTest  = classify(net4,XpTest);
okRatioTestNew = nnz(predTest == YTest)/numel(predTest)
okRatioTestNew = 0.8951

他にもいろいろやってみましたが,検証精度で90%程度が限界っぽいです.