14. AI技術: 機械学習(Deep Learning)入門 2
Photo by Claudio Schwarz on Unsplash
入門2 機械学習用フレームワークの利用
Flux パッケージ
ここまで感じただろうが,今現在の深層学習には一定の「やりかた」があり,それに沿うならばプログラムはどうしたってかなり似たようなものになる. ならば,そうした点を共通化し,かつ,計算時間の掛かりそうな箇所に高速なライブラリを組み込むような使いやすいフレームワークライブラリがあればユーザは大助かりだ.
そうした需要にこたえ,深層学習用の多くのフレームワークが存在する. Julia についてもやはり存在し,パッケージに Flux というものがあるので今回はこれを使ってみよう.
このパッケージも阪大情報教育システムやサイバーメディアセンターの JupyterHubサービス環境では既にインストール済みだ. 個人環境で未インストールの場合は下記のようにしてインストールしておこう.
|
|
ちなみに,Flux の使い方については Flux マニュアル を見るとよいだろう. なお,サンプルが Flux Model Zoo に載っているので,参考にするとよいだろう.
有名な機械学習用ライブラリ/フレームワークとして他に Caffe, Keras (高水準ライブラリ,TensorFlow などの他の低水準ライブラリを下部に使う), TensorFlow, Chainer などが知られている(主に他の言語用だが).
今回のターゲット問題
今回は典型的(かつ有名)な学習問題である「手書き数字の認識問題」をターゲットとしよう. 学習に使える実データとして有名なものに MNIST というものがある.
これは 70,000枚(学習用 60,000枚 + 学習成果検証用 10,000枚)の手書き数字画像とその「正解」情報からなるデータセットで,各画像は以下のようになっている.
- 色はモノクロ.濃淡が 0以上1以下の実数で表されている.
- サイズは 28 $\times$ 28 ドット.
- 本来のデータは 20 $\times$ 20 ドットの白黒二色のものだが,anti-aliasing アルゴリズムで 0以上1未満の濃淡に変換するなどしている. サイズが大きくなっているのは畳み込みなどの画像処理をしやすくするために各辺に「余白」をつけているのだと思われる. ただ,単に余白を足したのではなく,「大きめのキャンバスの中心に画像の中心をあわせて配置」しているので,余白は必ずしも各辺で 4ドットずつになっていないので要注意.
たとえばその 10枚目のデータを画像として見てみると

となっている(この画像では 0 $\cong$ 黒, 1 $\cong$ 白として表示されている).ちなみにこれは数字の「4」だそうな.
MNIST のデータは本家の web からダウンロードしてもよいが,それだとちょいとした前処理が必要となる. しかし実は,Flux パッケージは綺麗に処理された MNIST データをダウンロードする機能をもっているので,その機能を使おう(どうやるかはプログラムを見ればわかる).
今回用意する neural netowrk
少し要素数などが先週より大きいので,丁寧に準備しよう. ただし,以下では意外に小さい NN を用意するので驚くかもね.
さて,今回は,
- 入力は要素数 784 ($= 28\times 28$) の実数ベクトル
- 出力は数字 0 ~ 9 に対する「確率」を表すベクトル. つまり,要素数 10 の実数ベクトル $\boldsymbol{p}$で,$0 \leq p_i \leq 1$ かつ $\sum_i p_i = 1$.
- 通常の密結合 NN を用いる.中間(network)層は 2つ.
- 1つ目の中間層の出力サイズは 32 で,活性関数は ReLU.
- 2つ目の中間層の出力サイズは 10 で,活性関数の代わりに softmax 関数で正規化して出力が確率分布として成り立つようにする.(注: 2つ目の中間層の出力が最終出力)
という NN を Flux で用意して,この中に含まれるパラメータを(学習によって)修正することにしよう.
なお,12. AI技術: 機械学習(Deep Learning)入門のプログラム中にも登場したが, softmax 関数というのは実数の列(負の実数もOK)を確率分布として解釈できる数列に(かつ,各要素は単調に)変換する関数の一つで, ベクトル $\boldsymbol{a} = \{ a_i \}$ に対して
$\displaystyle\mbox{ SoftMax }(\boldsymbol{a})_i = \frac{\exp(a_i)}{\sum_i \exp(a_i)}$
と定義できる. $\exp$ 関数で強引に正の値に変換してから合計値で正規化しているという,シンプルな変換だ. 単調性と確率分布の性質を満たそうとするとたぶんこれが最初の候補だろう. ちなみに,すべての $a_i$ が正ならば, $\exp$ での変換が不要で,合計値で割って単純に正規化したほうが楽だろうな.
あと,これまた同じ回のプログラム中に登場したが,出力の「誤差」を測る方法として出力ベクトル $\boldsymbol{y}$ と真値ベクトル $\boldsymbol{z}$ に対する Cross Entropy
$\displaystyle\mbox{ CrossEntropy }(\boldsymbol{y}, \boldsymbol{z}) = - \sum_i \, z_i \, \exp( y_i )$
を使う.
入力独立変数に対して非対称な関数なので入力の順序に注意.目くじら立てるほどの本質的な違いはないと思ってもまあいいが.順序は定義によるので,プログラムの source を確認しておく必要がある.Flux パッケージの crossentropy 関数は上の順序になっている.
実際にやってみる
あとは少しずつプログラムを作っていくだけだ.
まずはいつものように Flux などのパッケージの使用宣言だ.
|
|
次に、MLDatasets
package の機能で MNIST データを使わせていただこう.
それには関数 MNIST
を呼び出せばよい.
ただし,この関数を初めて呼び出したときにデータが(一回だけ)ダウンロードされるので解説しよう.
実際は,下記のように,このデータについてごく簡単な説明があり,それを理解した上でダウンロードするのか,y/n で尋ねられる.
そこで stdin>
に "y" (と Enter)を入力しよう.
するとダウンロードが始まる.少しだけ待とう.
|
|
stdin> ■■■■■■
This program has requested access to the data dependency MNIST.
which is not currently installed. It can be installed automatically, and you will not see this message again.
Dataset: THE MNIST DATABASE of handwritten digits
Authors: Yann LeCun, Corinna Cortes, Christopher J.C. Burges
Website: http://yann.lecun.com/exdb/mnist/
[LeCun et al., 1998a]
Y. LeCun, L. Bottou, Y. Bengio, and P. Haffner.
"Gradient-based learning applied to document recognition."
Proceedings of the IEEE, 86(11):2278-2324, November 1998
The files are available for download at the offical
website linked above. Note that using the data
responsibly and respecting copyright remains your
responsibility. The authors of MNIST aren't really
explicit about any terms of use, so please read the
website to make sure you want to download the
dataset.
Do you want to download the dataset from
["https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz",
"https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz",
"https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz",
"https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz"]
to "c:\julia\PKG\v1.8\datadeps\MNIST"? [y/n]
すると次のような出力が出て,どんな形式でダウンロードされたかと,この呼出し方だと学習用データ(60,000個)が得られるということがわかる.
dataset MNIST:
metadata => Dict{String, Any} with 3 entries
split => :train
features => 28×28×60000 Array{Float32, 3}
targets => 60000-element Vector{Int64}
次に,これを変数に入れよう.
|
|
raw_imgs
変数に画像データが, labels
変数にその画像がどの数字を描いたものか,が入る.
ただ,raw_imgs
変数は画像が 90度回転した格好で格納されているので,次のようにして(人間に)見やすいものにしておこう.
|
|
さて,念のためにもダウンロードしたデータをちょっと見ておこう.
もとは画像データなのだから,当然,画像で見てみるのが良いだろう. 次のようにすると,行列データを(以下の場合はグレイ表現の)画像で見ることができる.
|
|

ちなみにこれは 0から9 のどの数字かというと,
|
|
5
ということで,正解は 5 だそうだ.
次に,これらを,学習プログラムに渡せるよう,固まりのデータに変換する.
|
|
変換した結果を見ておこう.
特に,Y
が何を意味するのか,よくみるとわかるだろう.
|
|
784×60000 Matrix{Float32}:
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 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 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 0.0 0.0 0.0 0.0 0.0 0.0
…略…
|
|
10×60000 OneHotMatrix(::Vector{UInt32}) with eltype Bool:
⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ … ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ 1 ⋅ ⋅ 1 ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ 1 ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅
⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ … ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ 1 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ 1
⋅ ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 ⋅ 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
では,NN を作ろう. Flux では大変簡単に,以下のように NN を構成できる.
|
|
意味は,上の注釈でわかるだろう.
さて,この学習「していない」 NN での出力を念の為に確認しておこう. 1つ目のデータを入れるとどういう答えが返ってくるか…
|
|
10-element Array{Float32,1}:
0.09637651
0.12438649
0.08440215
0.09990795
0.0857012
0.11381065
0.11615292
0.11861699
0.09201259
0.06863261
これが NN の出力で,この画像が「 0 ~ 9 である確率 」を並べたもの,と解釈することになる. わかりやすいようにグラフで見ておこう.
|
|
これだと,この画像は "2" である確率が一番高いことになり,正解の "5" を当てられていないことがわかる.
同様にもう二つほど見ておこう. データの 2つ目は
|
|

となっていて,明らかに正解は "0" だ.まあ確認しておくか.
|
|
0
ふむ.ではこの画像データを NN に入れると…
|
|
これも当然うまくいってない.
データの3つ目も確認しておこう. (画像をみるのはもう省略して)真値は
|
|
4
となるので "4" が真値で,NN が出す確率は
|
|
となっていてこれももちろんうまくいってない.
さて,では学習に必要な関数の準備をしよう.
|
|
NN の正解率を測ることができるようになったので,学習していない現状の NN での正解率をみておこう.
|
|
0.048916666666666664
ふ~む.ランダムな状態の NN での正解率が 4.9% ということで,まあこんなものかな(ランダムに数字を一つだせば,正解率は平均で 10% になるはずなので).
ではデータセットの設定と,パラメータをどうやって修正していくかの方法の指定,そして画面表示設定等をしてしまおう.
|
|
これで準備ができたので,早速実行しよう. ただし,一回小さなデータで関数をコンパイルしておく.
|
|
loss(X, Y) = 2.0165222f0
では,大きめのデータで学習させてみよう.
|
|
loss(X, Y) = 1.6647032f0
loss(X, Y) = 0.22654326f0
loss(X, Y) = 0.15942372f0
loss(X, Y) = 0.11917354f0
loss(X, Y) = 0.0933378f0
loss(X, Y) = 0.07503337f0
loss(X, Y) = 0.061352428f0
loss(X, Y) = 0.050547197f0
loss(X, Y) = 0.0419089f0
loss(X, Y) = 0.0345261f0
99.593398 seconds (62.96 k allocations: 116.684 GiB, 8.53% gc time)
計算時間は CPU 等によってひどく異なるので,私有環境で上の 10倍以上の時間がかかっても不思議ではない.
さて,学習が終了したようなので,結果をチェックしてみよう.
|
|
0.9925833333333334
ふむ.正解率は 99.25% か.学習前は 4.9% だったことを考えると,2分弱の学習としてはたいへん上出来だ.
最初の 3つのデータについて,個別に学習結果をチェックしてみよう. まずは 1つ目のデータに対する NN の回答は,
|
|
となる.これだと "5" である確率がほぼ 100%で,正解をきちんと当てていると言えるな.
次に 2つ目のデータだ.
|
|
"0" である確率がほぼ 100% だ.これも正解を完全に当てている.
3つ目も見てみよう.
|
|
これも "4" である確率が約 90% で,正解をきちんと当てている.
次に,学習に使っていない,テスト用データを対象としてこの NN の性能を見よう. まず,テスト用データのダウンロードと整形だ.
|
|
この NN を適用した場合の精度を見よう.
|
|
0.9623
ふむ,未知のデータに対しても約 96% の確率で正解を出せるということだな. どうやらこの NN の学習はうまくいった,と言ってよいだろう.
おまけ: GPU を使った場合
上記の計算は小さめとはいえ約 5分かかっている. これに対し,特殊なハードウェアの力を借りて計算したらどうなるか,という例を示しておこう. 今回は nVIDIA 社の GPU (まあ,要はグラフィックカードだ) を使ってみる,という例になる.
残念ながら下記の code は nVIDIA 社の GPU がインストールされていない環境では動かない. nVIDIA 社のグラフィックカードが刺さっている環境で試そう.
準備
nVIDIA 社の GPU を Flux から使うには,事前に
- グラフィックカードのデバイスドライバ: まあこれは Flux に関係なくインストールしているだろう.
- CUDA Toolkit : CUDA という専用言語で GPU を扱うためのライブラリ.nVIDIA製. Windows だと,ダウンロードしてダブルクリックするだけでインストールできる. ただし,ファイルサイズが 2GB を超えるのでダウンロードの際は覚悟しよう.
- cuDNN : 深層 NN 用ライブラリ.nVIDIA製.cuDNN のインストール方法 を読むと(デバイスドライバと CUDA Toolkit も含めて)インストール方法がわかる.まあ,解凍してファイルをいくつかコピーするだけだ.
のインストールを上の順序でしておく必要がある…のだが,実は,下記のCUDA パッケージ1をインストールすると自動でやってくれる.楽だ.
というわけで,NVidia 社の GPUボードが入っている PC を使っている環境であるならば,CUDA パッケージを使えるように下記のようにしてインストールしよう.
|
|
ちなみに,阪大情報教育システムだと CUDA パッケージはインストールされているが,肝心のハードウェアが無いので,実際に CUDA の機能を使おうとしたときに
Info: The GPU function is being called but the GPU is not accessible.
というメッセージが出て使えないことがわかるぞ.
そして,サイバーメディアセンターの JupyterHubサービス環境(β)では,この CUDA がインストールされているうえ,起動時にGPU付き環境を選択すれば実際に使えるぞ.ここで試してみるのも良いだろう.
あとは次のように,必要なデータや計算ルーチンを GPU に渡すようにしてプログラムを書けば良い.
|
|
GPU に渡すデータのサイズをむやみに大きくしないようにしよう.
上の例で言えば,large_dataset
をあまり大きくすると,GPU の挙動が変になったりして,OS までトラブったりするぞ.
これで準備ができた. 念の為に初期の精度を測っておく.
|
|
0.12608333333333333
ふむ.平均正解率 12.6% というところで,まあ妥当だな.
では学習させてみよう. ただし,一回小さなデータで関数をコンパイルしておく.
|
|
では,大きめのデータで学習させてみよう. このデータ(ただしサイズ 500)を CPU で学習したときは 100秒近くかかったが…
|
|
loss(X, Y) = 2.2181697f0
3.170826 seconds (594.81 k allocations: 39.628 MiB, 2.81% gc time, 0.78% compilation time)
おお! さすが. データサイズ 500 で CPU だと 100秒かかったこの学習(データサイズを 300 にそろえて考えると 60秒)が,データサイズ 300 で この環境だとたった 3秒で学習が終わる. おおよそ 20倍は速くなった,ということだな.
ちなみに,サイバーの JupyterHubサービスの GPU付き環境だと(データサイズはすべて 500で揃えて) CPU のみで 109秒,GPU で 2.54 sec. なので,40倍ぐらい高速化している. 高速化の度合いはハードウェアの問題なので,より新しく,より良い GPUボードを積んでいればそれだけ高速化できるだろう,ということだね.
さて,学習後の NN の精度も見てみよう.
|
|
0.9519166666666666
正解率が 95% ということで,確かによく学習できているようだ.
未知のデータについてもこの NN の能力を確かめておこう.
|
|
0.9467
未知のデータに対しても 95% の正答率だ.たった数秒の学習なのに,大変よく出来たと言えよう.
ということで,GPU などの特殊なハードウェアの力を借りることができれば,こうした学習も大変速く行えることがわかる.デスクトップ PC 用のグラフィックカードであればそう高いものでもないので,機械学習を少し勉強して見るならばこうしたハードウェアの調達も検討しておくと良いだろう.
レポート
下記要領でレポートを出してみよう.
- e-mail にて,
- 題名を 2022-numerical-analysis-report-14 として,
- 教官宛(アドレスは web の "TOP" を見よう)に,
- 自分の学籍番号と名前を必ず書き込んで,
- 内容はテキストでも良いし,pdf などの電子ファイルを添付しても良いので,
下記の内容を実行して,結果や解析,感想等をレポートとして提出しよう.
- 上の例で NN の中間層を増やしてみよう.ただし,計算時間は増大するので,バランスが厳しいかも.
- nVIDIA のグラフィックカードが刺さっている PC環境を探すなどして,可能ならば GPU での計算例も試してみよう.
- Flux Model Zoo を見て,他の例を試してみよう.ただし,知らないことばかりの場合は無理しなくて良い.
-
少し前までは Julia では NVidia 社の GPU を使うには
CuArrays
package を使うもの,だったが,今はCUDA
package を使え,ということになっている. ↩︎