大規模なデータ集合での訓練

ニューラルネットは,メモリに適合しないほど大きなデータ集合を含め,非常に大規模なデータ集合で訓練されるのに適している.ニューラルネットを訓練するための最も人気の高い最適化アルゴリズム(NetTrain"ADAM""RMSProp")は,「確率的勾配降下法」の変種である.このアプローチでは,小さなバッチのデータが完全な訓練データ集合からランダムにサンプリングされ,パラメータの交信を実行するのに使われる.したがって,ニューラルネットは「オンライン学習アルゴリズム」の例であり,訓練データ集合全体がメモリに格納されている必要はない.これは,通常訓練中にデータ集合全体がメモリに格納されていなければならないサポートベクターマシン(SVM)やランダムフォレストアルゴリズムとは対照的である.
しかし,NetTrainをメモリに収まらないデータ集合で使用する場合,訓練データ集合全体をWolfram言語セッションにロードすることができないため,特別な処理が必要になる.このような大規模なデータ集合での訓練には2つのアプローチがある. 1つ目は,評価されたときにディスクやデータベース等の外部ソースから1つのバッチのデータをロードすることができる「ジェネレータ関数」f をユーザが書くというものである.NetTrain[net,f,]は訓練バッチの繰返しごとに f を呼び出すため,メモリには1バッチの訓練データを保存するだけで済む.これは,アウトオブコア学習を行う際の最も柔軟なアプローチである.f の評価がネットの訓練のスピードを低下させないほど速く(非常に強力なGPU上で訓練しているときには特に重要),f が完全な訓練データ集合から正確にサンプリングを行っているということを確実にするのはユーザ次第である.
2つ目のアプローチは,画像や音声ファイルからなる訓練データ集合という特殊な場合に使用できる.この場合,"Image" NetEncoder等のエンコーダがディスクに保存されている画像を効率よく読み取り,前処理を行う.
ExampleDataの中のファイルを見付ける:
ファイルにエンコーダを適用する:
NetTrain[net,{File[]->,},]はデータ集合に対するアウトオブコア学習を自動的に行う.ここでFile[]はアウトオブコアの画像あるいは音声ファイルを表す.
次はアウトオブコア学習を行う際の,性能に関するヒントである.
MNISTでのアウトオブコア訓練
この例では,メモリではなくディスクに"JPEG"ファイルとして画像が保存されているときに,MNISTデータ集合でネットを訓練する方法を紹介する.MNISTを訓練する必要はほとんどないが,この方法を使うとImageNetデータ集合等のテラバイト規模の画像データ集合でネットを訓練することができる.
入力ポート net"Image" NetEncoderを加えている限り,特別なシンタックスNetTrain[net,{File[]class1,},]が使える.より柔軟なNetTrainジェネレータ関数のシンタックスを使ってこれを再生する方法も示す.

データの準備

まず,形式{File[]class1,}のMNISTのアウトオブコアバージョンを作成する必要がある.
MNIST訓練集合と検証集合を得る:
デフォルトの一時ディレクトリ$TemporaryDirectoryに画像を"JPEG"ファイルとして保存する.エキスポートされたファイルにはそれぞれ一意の名前が必要である.一意の名前を作るよい方法に,一意の画像それぞれに一意のハッシュを返すHash関数を使うというものがある.
$TemporaryDirectory"JPEG"ファイルに画像をエキスポートする関数を定義する:
画像をエキスポートする:
画像で訓練する場合,通常ネットは,画像の大きさ,色空間等が単一であることを必要とする.LeNetの場合,画像はグレースケールで大きさは2828となっている.画像がディスクからロードされるたびにそれを適合させるのではなく,ディスクから読み込まれる画像がすでにネットの想定と一致していれば,訓練速度は通常向上する.画像がまだ適合していない場合は,ConformImagesを使って画像を適合させるために上記の exportImage を編集する方法が推奨される.
ここで訓練集合と検証集合全体にエキスポート関数をマップすることができる.MapではなくParallelMapを使って並列のエキスポートを実行するための最適化を行う.
画像をエキスポートし,形式{File[]->class,}の新しい訓練集合と検証集合を作成する:
新しい訓練集合のRandomSampleを表示する:
画像ファイルへの参照だけがメモリに保存されていればよくなった.これだと画像自体をメモリに保存するよりかなり小さくてすむ.
2つの訓練集合のByteCountを取得する:

アウトオブコアの簡単な訓練

入力ポートに"Image" NetEncoderが加わったたたみ込みニューラルネットワークを定義する:
このネットは,入力ポートに"Image" NetEncoder が加わっているので,入力としてFileオブジェクトで表される画像を取ることができる.
訓練集合からファイルを得る:
ネットをNetInitializeで初期化し,それをファイルに適用する:
{File[]->class1,}の形式のデータでネットを訓練することは,形式{image1->class1,}のデータで訓練することと同じである.
ネットを訓練ラウンド3回訓練する:
検証集合からランダムに抽出された画像で,訓練されたネットワークを直接評価する:
訓練されたネットの検証集合における正確さを得る:

ジェネレータ関数を使った訓練

NetTrainのより一般的なジェネレータシンタックスを使うこともできる.この方法はもっと柔軟性があるため,カスタムの画像前処理やデータ拡大等が可能である.
訓練データのバッチを返すジェネレータ関数を定義する:
4つの訓練の例からなる1つのバッチを生成する:
ジェネレータ関数を使ってネットを訓練する:
訓練されたネットの検証集合での正確さを得る:

インポートとNetEncoderの性能

データのロードにNetEncoderを使うことは,通常トップレベルのWolfram言語コードを使ってデータローダを書くよりずっと速い. 例として,簡単な画像インポータと"Image" NetEncoderを比較する.
カスタムの画像データローダを定義する:
"Image" NetEncoderを定義する:
これらの符号化のそれぞれが入力ファイルのリストに適用されると,階数4の数値配列が生成される.
4ファイルからなる1つのバッチを生成する:
両方の画像エンコーダをファイルのリストに適用して,出力の次元と評価にかかった時間を得る:
"Image" NetEncoderとカスタムの画像エンコーダを比較した場合の速度の向上を求める:
ご覧の通り,"Image" NetEncoderの方がカスタムの画像エンコーダよりも400倍速い.バッチサイズが大きくなればなるほど,その差も大きくなる.
MongoDBデータベースを使う
MongoDBデータベースは,大規模なデータ集合を保存する解決策の一つである.Wolfram言語には,MongoDBデータベースとインタラクトするためのMongoLinkパッケージを備えている.
ここでは,MongoDBデータベースに保存されたtoy Fisher Iris データ集合でネットを訓練する方法を示す.各訓練反復の間に,データベースから小さいバッチのデータだけがランダムにサンプリングされる.したがって,このメソッドはメモリに保存できないデータ集合にまで拡大することができる.
ここからは読者がMongoLinkとMongoDBデータベースについて知っていると仮定して話を進める.まだよく知らない場合は,まずMongoLink Introductionチュートリアルを読むことをお勧めする.
また,読者のローカルマシン上のデフォルトのホストおよびポートでMongoDBサーバが起動しているものと想定する.MongoDBサーバをローカルで実行するためのプラットフォーム別の手順は,このページをご覧いただきたい.

データの挿入

使用しているデータ集合はFisherの Iris データ集合である.ここでのタスクは,花を数的特徴に基づいて4つのクラスに分類するというものである.
Fisherの Iris データ集合を取得する:
訓練データをMongoDBデータベースに挿入する.
MongoLinkをロードする:
デフォルトのホスト "localhost" およびポート27017(これがローカルマシンでMongoDBサーバを実行するときのデフォルトのホスト名とポート)を使って,クライアント接続を構築する:
データベース"WolframNNTutorialDatabase""WolframNNTutorialCollection"という名前のMongoDBコレクションを作成する.
MongoGetCollectionを使ってコレクションを作成する:
コレクションとデータベースが存在しない場合は,最初にデータを挿入したときに生成される.
訓練データをMongoDBドキュメントのリストに変換する:
これで訓練データがコレクションに挿入する:
データが挿入されたことを確認するために,コレクションからランダムなサンプルを取る:

分類ネットの構築

データ集合の分類を実行することができるネットを構築する.まず,可能なクラスすべてのリストが必要となる.これはデータベースクエリによって生成される.
MongoCollectionDistinctを使って,それぞれの例が割り当てられている一意のラベルのリストを作成する:
ネットの出力を各クラスの確率として解釈するために"Class"デコーダを使って,回帰を行うNetChainを作成する:

ジェネレータ関数の構築

NetTrainで使用するためのジェネレータ関数を定義する必要がある.この関数は"WolframNNTutorialCollection"からランダムにドキュメントをサンプリングしなければならない.
MongoCollectionAggregateで集約演算子の"$sample"を使って,"WolframNNTutorialCollection"コレクションから2つのランダムなサンプルを取得する:
これはコレクションにRandomSampleを使うことに等しい:
しかし"_id"フィールドは必要ない.Wolfram言語の出力を修正することで削除する.もっと効率がよいのは,集約パイプラインに"$project"演算子を加えて削除する方法である.
"_id"フィールドを削除したコレクションから読み込む:
NetTrainのジェネレータ関数を定義する:
このジェネレータ関数は,バッチサイズが指定されたときに,ランダムにサンプルされた例のリストを正しく返す:
ジェネレータ関数が生成することのできる有効な訓練データの形式は主に2つある.一つは上のジェネレータ関数によって生成された例の連想のリスト{<|key1val11,key2val21,|>,<|key1val12,|>,}であり,もう一つは各キーが例の値のリストを持つ<|key1->{val11,val12,},key2{val21,val22,},|>の単独のAssociationである.ジェネレータ関数の出力 out の形式はTranspose[out,AllowedHeads->All]を使って別の形式に変換することができる.
MongoDBは$group集約段階を使って例題のグループ化を2つ目の形式で生成する.これは多くの場合,最初の形式よりも格段に効率的である.
例のバッチをグループにまとめて返すジェネレータ関数を定義する:
2つ目のジェネレータ関数を使っ2つの例のバッチを作成する:
サイズ64のバッチを作成する場合,グループ化されたジェネレータ関数の方が速い:
2つ目のジェネレータ関数により毎秒生成される例を得る:

ネットの訓練

ジェネレータ関数を使ってネットを訓練する:
このアプローチには問題がある.検証集合での性能は各バッチが訓練に使われた後計算されるが,通常の場合訓練データ集合全体を1度通した(1ラウンド)後に計算される.ジェネレータ関数を使う場合,ラウンドのサイズを明示的に指定しない限りNetTrainにはそれが分からない.
MongoCollectionCountを使ってコレクションに含まれる例の合計を得る:
2000ラウンドを指定してネットを訓練する:
訓練集合でのネットの正確さを検証する: