Mathematicaで深層学習 deep learning の基礎を学ぶことができます。ここでは Wolfram Languageだけを使い、実装してみます。
深層学習には学習データが必要です。ここでは手書き数字データMNISTを例として使いますが、Mathematicaが計算可能データとして予め用意しているものをResourceDataから取得します。なお、TrainingDataは学習用データ、TestDataは学習結果の精度検証validationに使用します。
trainingData = ResourceData["MNIST", "TrainingData"];
testData = ResourceData["MNIST", "TestData"];
trainingData // Length
testData // Length
Mathematicaでは学習データは以下の例でも分かるように、画像とラベルの連想形式(Rule関数使用)を用いています。逆にいえば、自前でデータを用意するときは、連想形式で用意する形になります。
SeedRandom[1234]; data =RandomSample[trainingData, 20]
SeedRandom[1234]; testdata =RandomSample[testData, 20]
データを1つ取り出し、内容を確認してみます。学習データの最初([[1]])の連想部分を取り出し、ついでその前半([[1]][[1]]、つまり画像)をimg9として定義します。
img9 = data[[1]][[1]]
testimg5 = testdata[[1]][[1]]
画像 img9 は 0. 以上、1.未満の値を要素に持つ 28x28 サイズの配列からなります。
Information[img9]
ImageData[img9]
ArrayPlot[ImageData[img9]]
学習対象データがなんであれ、ニューロン層に入力する際は、数値列に変換する必要があります。データの数値列への変換をencodingと呼びます。
enc = NetEncoder[{"Image", {28, 28}, "Grayscale"}]
Wolfram Language の出力は、たとえば、このJupyter Notebookの場合、画像として出力されます。したがってMathematicaのノートブックでは可能はインタラクティブな操作、たとえば+ボタンを押し、中身を見るようなことはできません。幸い、表示は画像でも実態はオブジェクトとして情報操作対象となります。ここではencがオブジェクト情報を持っています。中身を開く一番簡単な方法は Normal 関数を使うことです。
ただし、理由は不明ですが、Wolfram Languageでは表示情報はここまでで、一方、同様の操作をMathematicaで行うと、以下のようにもっと詳細な情報が得られます。
<|"ColorChannels" -> 1, "ColorSpace" -> "Grayscale",
"DataTransposed" -> False,
"ImageSize" -> NeuralNetworks`ValidatedParameter[{28, 28}],
"Interleaving" -> False, "MeanImage" -> None,
"Output" -> {1, 28, 28}, "Type" -> "Image", "VarianceImage" -> None|>
enc // Normal
入力用encoderとしてencを指定し、出力ベクトル次数を3とした全結合層の重みweightsとバイアスbiasesを初期化したものを linear と定義します。
linear = NetInitialize@LinearLayer[3, "Input" -> enc]
初期化された重みとバイアスをそれぞれ weights、biases に取り出します。 Normal はMathemtica固有のオブジェクトを一般の数値データとして扱えるように変換するために利用しています。この際、weights は3x784(=28x28)の2次元配列となります。
weights = NetExtract[linear, "Weights"]
weights = weights//Normal;
biases = NetExtract[linear, "Biases"]
biases = biases//Normal;
サンプル画像 img9 をこの全結合層に入力したとき、エンコードされた出力として以下の3要素からなるベクトルが返ってきます。そして、これは img9 の内部データを一次元化したものに重み weights を掛け(内積)、結果に biases を足したものに等しいことがわかります。
linear[img9]
Dot[weights, Flatten[ImageData[img9]]] + biases
MNIST手書き数字分類器として、2つの全結合層 LnearLayer からなる多層パーセプトロンを考えます。2層間に活性化関数として Ramp 関数を置き、分類関数としてSofrmaxLayerを使います。1段目のLinearLayer内のニューロン数は100としていますが、2段目のニューロン数は空白となっています。この場合、文脈(学習データが持つクラス数)から自動的に10と決まります。
mlp = NetChain[{
LinearLayer[100],
Ramp,
LinearLayer[],
SoftmaxLayer[]}]
学習時間はMacBook Air2019年モデル上のAnaconda Jupyter上で約145秒掛かっています。
Timing[trained =
NetTrain[mlp, trainingData, MaxTrainingRounds -> 10]]
ネットモデルmlpにおいて、明示的にInputおよびOutput層は指定されていません。当然、encoderおよびdecoderも省略されています。しかし、学習結果 trained と見ると、encoderは学習データを3x28x28の数値データに変換し、decoderは長さ10の確率ベクトルに変換していることがわかります。
NetExtract[trained, "Input"]
NetExtract[trained, 1]
NetExtract[trained, 3]
NetExtract[trained, "Output"]
学習済み分類器trained を使い、画像testimg5を確率込みで分類してみます。ラベルとしては5がほぼ一択0.99748で選ばれています。
trained[testimg5, "Probabilities"]
trained[testimg5]
今度は同じモデルを使い、ValidationSetとしてテストデータを与え、再度学習させてみます。
Timing[trained = NetTrain[
mlp, trainingData, ValidationSet -> testData, MaxTrainingRounds -> 3]]
分類測度を計算するClassifierMeasurementを用い、精度(確度)を計ってみます。
Timing[
cm = ClassifierMeasurements[trained, testData]]
cm["Accuracy"]
精度比較を視覚的に見るConfusionMatrixを出力します。たとえば、数字3データは1010個のうち、869個が正解しましたが、たとえば88個も数字5と勘違いしました[偽陰性]。一方、本来、5でない数字283個(=1108-825)が5と勘違いされました[偽陽性]。
cm["ConfusionMatrixPlot"]
一方、2層のConvolution層と2層の全結合層からなるLeNetを用いると、学習精度は98.98%にも上がることがわかります。
lenetModel = NetModel["LeNet Trained on MNIST Data"]
NetExtract[lenetModel, All]
Timing[result =
NetTrain[lenetModel, trainingData, All, ValidationSet -> testData]
]
trained = result["TrainedNet"];
cm = ClassifierMeasurements[trained, testData];
cm["Accuracy"]
trained[testimg5]
NetInformation[trained]
NetInformation[trained, "SummaryGraphic"]
result["EvolutionPlots"]
NetTrain[
lenetModel, trainingData, "RMSWeightsEvolutionPlot",
ValidationSet -> testData]
Mathematicaは各種ネットモデルを予め提供しています。
NetModel[]
それらネットモデルを使った具体例が以下のサイトで紹介されています。なお、これらのモデルをそのままWolfram Engineで動作するかの確認は行っていません。
Mathematicaの場合、学習結果はMXNet形式で保存されます。ただし、上記サイトで紹介されている方法では、うまく行かず、ここでは以下のページの情報を参考に変更しています。
<< GeneralUtilities`
jsonPath = GeneralUtilities`MLExport[
FileNameJoin[{$TemporaryDirectory, "net.json"}],
result["TrainedNet"]]
paramPath =
FileNameJoin[{DirectoryName[jsonPath], "net.params"}]
FileByteCount[paramPath]
imported = GeneralUtilities`MLImport[jsonPath];
imported[testimg5]