大規模なデータ集合での訓練
大規模なデータ集合での訓練
ニューラルネットは,メモリに適合しないほど大きなデータ集合を含め,非常に大規模なデータ集合で訓練されるのに適している.ニューラルネットを訓練するための最も人気の高い最適化アルゴリズム(NetTrainの"ADAM"や"RMSProp")は,「確率的勾配降下法」の変種である.このアプローチでは,小さなバッチのデータが完全な訓練データ集合からランダムにサンプリングされ,パラメータの交信を実行するのに使われる.したがって,ニューラルネットは「オンライン学習アルゴリズム」の例であり,訓練データ集合全体がメモリに格納されている必要はない.これは,通常訓練中にデータ集合全体がメモリに格納されていなければならないサポートベクターマシン(SVM)やランダムフォレストアルゴリズムとは対照的である.
しかし,NetTrainをメモリに収まらないデータ集合で使用する場合,訓練データ集合全体をWolfram言語セッションにロードすることができないため,特別な処理が必要になる.このような大規模なデータ集合での訓練には2つのアプローチがある. 1つ目は,評価されたときにディスクやデータベース等の外部ソースから1つのバッチのデータをロードすることができる「ジェネレータ関数」f をユーザが書くというものである.NetTrain[net,f,…]は訓練バッチの繰返しごとに f を呼び出すため,メモリには1バッチの訓練データを保存するだけで済む.これは,アウトオブコア学習を行う際の最も柔軟なアプローチである.f の評価がネットの訓練のスピードを低下させないほど速く(非常に強力なGPU上で訓練しているときには特に重要),f が完全な訓練データ集合から正確にサンプリングを行っているということを確実にするのはユーザ次第である.
2つ目のアプローチは,画像や音声ファイルからなる訓練データ集合という特殊な場合に使用できる.この場合,"Image" NetEncoder等のエンコーダがディスクに保存されている画像を効率よく読み取り,前処理を行う.
ExampleDataの中のファイルを見付ける:
f = File[FindFile["ExampleData/coneflower.jpg"]]NetEncoder[{"Image", {2, 2}}][f]- 可能な限りNetEncoderを使用する.この実装は高度に最適化されている.
- NetTrainは,前の訓練データのバッチで訓練を反復するのと平行して,次の訓練データのバッチをロードする.よって,訓練速度が訓練を1回反復するより遅い場合でも,データのロードと前処理しか訓練速度に影響しない.これはネットの複雑さと,ネットが訓練されているハードウェアに大きく依存する.
- ジェネレータ関数 f を使う場合,訓練の減速が避けられるほど速いことを確認する.このためには,dataset=f[<|"BatchSize"->1000|>]等を使ってメモリに収まる訓練データのコピーをジェネレータ関数で作成し,NetTrain[net,dataset,"MeanInputsPerSecond",TimeGoal->30]を使ってNetTrainがどのような速さでこのデータに作用するかを測定する.それから,NetTrain[net,f,"MeanInputsPerSecond",TimeGoal->30]を使って,この速度とデータのジェネレータ関数を使ったときの速度を比較する.このジェネレータ関数の方が遅ければ,f を最適化するか,アウトコア学習を行う代償として減速を容認するかする.
- ディスクからランダムなバッチのファイルを読み込む場合,通常ハードディスクドライブ(HDD)よりもソリッドステートドライブ(SSD)の方が性能がよい.これは,ランダムシークタイムがHDDよりSSDの方が1桁速いためである.
- 音声ファイルのサンプリングレートや画像ファイルの画像サイズが,ネットが訓練に使っているものとは大きく異なる場合,高価な再サンプリング操作が1度だけ行われるわけではないので,訓練が大きく減速する.最適なアプローチは,リサンプルされた画像あるいは音声ファイルの新しい集合を作成して,それで訓練することである.
- 訓練にNVIDIA GPUを使う.これにはNetTrainでTargetDeviceオプションを使う.GPU上で訓練すると,CPUだけを使った場合と比較して,訓練速度が1桁速くなる.これは,ネットの評価が,通常GPU上での方が格段に速いためである.さらに,データのロードと前処理についても,CPUはこのタスクに完全に集中するため,GPUの方が速い.
この例では,メモリではなくディスクに"JPEG"ファイルとして画像が保存されているときに,MNISTデータ集合でネットを訓練する方法を紹介する.MNISTを訓練する必要はほとんどないが,この方法を使うとImageNetデータ集合等のテラバイト規模の画像データ集合でネットを訓練することができる.
入力ポート net に"Image" NetEncoderを加えている限り,特別なシンタックスNetTrain[net,{File[…]class1,…},…]が使える.より柔軟なNetTrainジェネレータ関数のシンタックスを使ってこれを再生する方法も示す.
データの準備
まず,形式{File[…]class1,…}のMNISTのアウトオブコアバージョンを作成する必要がある.
trainingData = ResourceData["MNIST", "TrainingData"];
testData = ResourceData["MNIST", "TestData"];RandomSample[trainingData, 5]デフォルトの一時ディレクトリ$TemporaryDirectoryに画像を"JPEG"ファイルとして保存する.エキスポートされたファイルにはそれぞれ一意の名前が必要である.一意の名前を作るよい方法に,一意の画像それぞれに一意のハッシュを返すHash関数を使うというものがある.
exportImage[img_] := File@Export[FileNameJoin[{$TemporaryDirectory, Hash[img, Automatic, "HexString"] <> ".jpg"}], img]exportImage[[image]]画像で訓練する場合,通常ネットは,画像の大きさ,色空間等が単一であることを必要とする.LeNetの場合,画像はグレースケールで大きさは2828となっている.画像がディスクからロードされるたびにそれを適合させるのではなく,ディスクから読み込まれる画像がすでにネットの想定と一致していれば,訓練速度は通常向上する.画像がまだ適合していない場合は,ConformImagesを使って画像を適合させるために上記の exportImage を編集する方法が推奨される.
画像をエキスポートし,形式{File[…]->class,…}の新しい訓練集合と検証集合を作成する:
trainingDataFiles = ParallelMap[exportImage[First@#] -> Last[#]&, trainingData];testDataFiles = ParallelMap[exportImage[First@#] -> Last[#]&, testData];新しい訓練集合のRandomSampleを表示する:
RandomSample[trainingDataFiles, 3]2つの訓練集合のByteCountを取得する:
ByteCount[trainingData]ByteCount[trainingDataFiles]アウトオブコアの簡単な訓練
lenet = NetChain[{
ConvolutionLayer[20, 5], Ramp, PoolingLayer[2, 2],
ConvolutionLayer[50, 5], Ramp, PoolingLayer[2, 2],
FlattenLayer[], 500, Ramp, 10, SoftmaxLayer[]},
"Output" -> NetDecoder[{"Class", Range[0, 9]}],
"Input" -> NetEncoder[{"Image", {28, 28}, "Grayscale"}]
]testFile = trainingDataFiles[[1, 1]]ネットをNetInitializeで初期化し,それをファイルに適用する:
NetInitialize[lenet][testFile]results = NetTrain[lenet, trainingDataFiles, All, ValidationSet -> testDataFiles, MaxTrainingRounds -> 3]
trained = results["TrainedNet"]imgs = Keys@RandomSample[testData, 5];
Thread[imgs -> trained[imgs]]NetMeasurements[trained, testDataFiles, "Accuracy"]ジェネレータ関数を使った訓練
NetTrainのより一般的なジェネレータシンタックスを使うこともできる.この方法はもっと柔軟性があるため,カスタムの画像前処理やデータ拡大等が可能である.
genTrain = Function[RandomSample[trainingDataFiles, #BatchSize]]genTrain[<|"BatchSize" -> 4|>]results = NetTrain[lenet, {genTrain, "RoundLength" -> Length[trainingDataFiles]}, All, ValidationSet -> testDataFiles, MaxTrainingRounds -> 3]
trained2 = results["TrainedNet"]NetMeasurements[trained2, testDataFiles, "Accuracy"]インポートとNetEncoderの性能
データのロードにNetEncoderを使うことは,通常トップレベルのWolfram言語コードを使ってデータローダを書くよりずっと速い. 例として,簡単な画像インポータと"Image" NetEncoderを比較する.
customImageEnc[files_] := Map[Import /* ImageData /* List, files]imageEnc = NetEncoder[{"Image", 28, "ColorSpace" -> "Grayscale"}]fileList = Table[trainingDataFiles[[1, 1]], 4]timing1 = RepeatedTiming@Dimensions[customImageEnc[fileList]]timing2 = RepeatedTiming@Dimensions[imageEnc[fileList]]First[timing1] / First[timing2]ここでは,MongoDBデータベースに保存されたtoy Fisher Iris データ集合でネットを訓練する方法を示す.各訓練反復の間に,データベースから小さいバッチのデータだけがランダムにサンプリングされる.したがって,このメソッドはメモリに保存できないデータ集合にまで拡大することができる.
ここからは読者がMongoLinkとMongoDBデータベースについて知っていると仮定して話を進める.まだよく知らない場合は,まずMongoLink Introductionチュートリアルを読むことをお勧めする.
また,読者のローカルマシン上のデフォルトのホストおよびポートでMongoDBサーバが起動しているものと想定する.MongoDBサーバをローカルで実行するためのプラットフォーム別の手順は,このページをご覧いただきたい.
データの挿入
trainingData = ExampleData[{"MachineLearning", "FisherIris"}, "TrainingData"];
testData = ExampleData[{"MachineLearning", "FisherIris"}, "TestData"];RandomSample[trainingData, 5]Needs["MongoLink`"]client = MongoConnect[]MongoGetCollectionを使ってコレクションを作成する:
collection = MongoGetCollection[client, "WolframNNTutorialDatabase", "WolframNNTutorialCollection"]trainingDataDoc = <|"Input" -> First[#], "Output" -> Last[#]|>& /@ trainingData;RandomSample[trainingDataDoc, 2]MongoCollectionInsert[collection, trainingDataDoc]RandomSample[collection, 2]分類ネットの構築
MongoCollectionDistinctを使って,それぞれの例が割り当てられている一意のラベルのリストを作成する:
labels = MongoCollectionDistinct[collection, "Output"]net = NetChain[{3, SoftmaxLayer[]}, "Input" -> 4, "Output" -> NetDecoder[{"Class", labels}]]ジェネレータ関数の構築
MongoCollectionAggregateで集約演算子の"$sample"を使って,"WolframNNTutorialCollection"コレクションから2つのランダムなサンプルを取得する:
ReadList@MongoCollectionAggregate[collection, {<|"$sample" -> <|"size" -> 2|>|>}]これはコレクションにRandomSampleを使うことに等しい:
RandomSample[collection, 2]ReadList@MongoCollectionAggregate[collection, {<|"$project" -> <|"_id" -> False|>|>, <|"$sample" -> <|"size" -> 2|>|>}]NetTrainのジェネレータ関数を定義する:
gen1 = Function[ReadList@MongoCollectionAggregate[collection, {<|"$project" -> <|"_id" -> False|>|>, <|"$sample" -> <|"size" -> #BatchSize|>|>}]];gen1[<|"BatchSize" -> 2|>]ジェネレータ関数が生成することのできる有効な訓練データの形式は主に2つある.一つは上のジェネレータ関数によって生成された例の連想のリスト{<|key1val11,key2val21,…|>,<|key1val12,…|>,…}であり,もう一つは各キーが例の値のリストを持つ<|key1->{val11,val12,…},key2{val21,val22,…},…|>の単独のAssociationである.ジェネレータ関数の出力 out の形式はTranspose[out,AllowedHeads->All]を使って別の形式に変換することができる.
MongoDBは$group集約段階を使って例題のグループ化を2つ目の形式で生成する.これは多くの場合,最初の形式よりも格段に効率的である.
gen2 = Function[First@ReadList@MongoCollectionAggregate[collection, {<|"$sample" -> <|"size" -> #BatchSize|>|>, <|"$group" -> <|"_id" -> Null, "Input" -> <|"$push" -> "$Input"|>, "Output" -> <|"$push" -> "$Output"|>|>|>, <|"$project" -> <|"_id" -> False|>|>}]];gen2[<|"BatchSize" -> 2|>]gen1[<|"BatchSize" -> 64|>];//RepeatedTiminggen2[<|"BatchSize" -> 64|>];//RepeatedTiming64 / First[%]ネットの訓練
results = NetTrain[net, gen2, All, BatchSize -> 64, ValidationSet -> testData]
trained = results["TrainedNet"]このアプローチには問題がある.検証集合での性能は各バッチが訓練に使われた後計算されるが,通常の場合訓練データ集合全体を1度通した(1ラウンド)後に計算される.ジェネレータ関数を使う場合,ラウンドのサイズを明示的に指定しない限りNetTrainにはそれが分からない.
MongoCollectionCountを使ってコレクションに含まれる例の合計を得る:
roundSize = MongoCollectionCount[collection]results = NetTrain[net, {gen2, "RoundLength" -> roundSize}, All, BatchSize -> 64, ValidationSet -> testData, MaxTrainingRounds -> 2000]
trained = results["TrainedNet"]NetMeasurements[trained, testData, "Accuracy"]関連するガイド
-
▪
- 機械学習 ▪
- ニューラルネットワーク