TIM Labs

fujiによるエントリー一覧

前回、28x28の手書き数字のAutoEncoderプログラムの説明をした。
そして走らせて、やっと(笑)画像が集まったので、ご覧いただこう。

10000エポックでは、この程度の画像にしか再現されない。
mnistae10000SGD.png1万エポック程度では、何が何だか分からない。
それで、一気に10万エポックまで飛ぼう。

mnistae100000SGD.png10万エポックになると、半数くらいは判別できそうだが、まだとっても苦しいところだ。

30万エポックになると、かなり数字がはっきり見えてくるようになる。
mnistae300000SGD.png30万エポックになると、かなり正確に元のイメージを再現するようになってくる。

そして、100万エポックになると、ここまで再現できるようになる。

mnistae1000000.png100万エポックになると、文句なく十分な再生が行われたと判断できるだろう。

しかし、まずい。
エポック数が100万とは、100万回も学習を繰り返さないと使えないということだ。つまり、超バカだ。

もっとエポック数が少なくて、ちゃんと再現される方法はないものだろうか。


プログラムの初期化部分に optimizerの選択があるようだ。
# Initialize model        
model = MyAE()
optimizer = optimizers.SGD()
optimizer.setup(model)
SGDというオプティマイザーが選択されているようだ。
SGDとは stochastic gradient descent(確率的勾配降下法) のことだが、面倒なので説明は省略。まあ、最初に説明されることが多い最適化方法である。

もしかして、ここを書き換えると、もっと上手くいくのだろうか?
MNISTの28x28の手書き数字は、現実の処理でも使うくらいのドット数がある。
これでAutoEndoderを作って、どのくらい再現性があるか調べてみよう。

まず、クラスMyAeのノード数の部分を書き換えよう。
中間層は、40ノードとする。つまり、748 --> 40 --> 748 と変換していこう。

class MyAE(Chain):
    def __init__(self):
        super(MyAE, self).__init__(
            l1=L.Linear(784,40),
            l2=L.Linear(40,784),
        )
変更はたったこれだけで、28x28の画像に対応できるようになる。
後は、書くだけだ。

最初に、データを読み込む。

# http://yann.lecun.com/exdb/mnist/
train, test = chainer.datasets.get_mnist()
xtrain = train._datasets[0]
ytrain = train._datasets[1]
xtest = test._datasets[0]
ytest = test._datasets[1]
今回は、学習の途中で、一定エポック毎にAutoEncoderの結果の最初の48枚の画像を1つの画像ファイルまとめて出力した。

# Learn
losslist = []
for j in range(1000000):
    x = Variable(xtrain[:10000])
    model.cleargrads()             # model.zerograds() 非推奨
    loss = model(x)
    if j%10000 == 9999:
        print( "%6d   %10.6f" % (j+1, loss.data) )
        xx = Variable(xtrain[:48], volatile='on')
        yy = model.fwd(xx)
        plotresults( yy, "mnistaeout/mnistae%d.png" % (j+1) )

    losslist.append(loss.data)     # 誤差をリストに追加
    loss.backward()
    optimizer.update()
10000エポック毎にスナップショット画像を吐き出して、全体で100万エポックまでやったのだが、このくらいやるとしっかり時間がかかり、走らせて結果は翌日確認することになった。(まだGPUは使っていない)

といことで、今回はプログラムの紹介だけで、結果は次回に示す。
CUDAをインストールしたら、GPUが動作しているかどうか、とりあえず確認しよう。
nvidia-smi というコマンドが入ったはずで、それを実行してみよう。

$ nvidia-smi
Sat Mar 18 18:46:16 2017       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 375.26                 Driver Version: 375.26                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 105...  Off  | 0000:01:00.0     Off |                  N/A |
| 35%   37C    P0    35W /  75W |    337MiB /  4038MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|    0      1129    G   /usr/lib/xorg/Xorg                             164MiB |
|    0      1919    G   compiz                                         127MiB |
|    0     11883    C   gimp-2.8                                        43MiB |
|    0     12492    G   unity-control-center                             1MiB |
+-----------------------------------------------------------------------------+

上図のように、2段に別れて表示される。
現在はGPUが1個だけなので、GPU0として表示されている。たくさん同時に使うと、0,1,2,...となるはず。
FANや、温度、GPUのメモリ容量など色々表示されるので、GPUは機能しているようだ。

下段の表を見ると、すでにいくつかのソフトが勝手にGPUを使っていることが分かる。
明示的にGPUを使わなくても、様々なアプリがGPUを使うことがあるようだ。

とりあえず、動いているらしいことが分かったら、Cのプログラムをコンパイルして動作確認すべきだ。
といっても、Cのプログラムをいきなり書くのは難しい。

こういうときの場合に、CUDAのサンプルプログラムが用意されている。

普通にインストールすると、/usr/local/cuda/samples というディレクトリの中に分野別に分けてプログラムが入っているみたいだ。

fuji@fujigpu:/usr/local/cuda/samples$ ls
0_Simple     2_Graphics  4_Finance      6_Advanced       EULA.txt  bin     err
1_Utilities  3_Imaging   5_Simulations  7_CUDALibraries  Makefile  common

その中に、Makefileというのがあるので、さっそくmakeしてみよう。 すると、binの下に実行可能ファイルがいっぱいできる。
どんなプログラムかは、CUDA code sampleで概要が分かるだろう。
とにかく、適当にクリックして実行してみよう。

これは、3Dのグラフィックスで、実際には刻々と形が変化し続ける。
 
CudaMarchingCubes.png
他にもいろいろあるのだが、説明するのは面倒だし、コンパイルし実行できることが一応分かったので、CUDAのインストールの確認はこれで終わることにする。


MNISTの手書き数字の読み込みは、きわめて簡単である。
最初におまじないを並べたあと、次の1行だけで、トレーニングデータとテストデータが読み込まれ、2つのオブジェクトに入る。
train, test = chainer.datasets.get_mnist()

トレーニングデータがどのように入っているか、確認しよう。
>>> type(train)
<class 'chainer.datasets.tuple_dataset.tupledataset'>
>>> len(train)
60000
>>> train[0]
(array([ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,

.........中略..........
0. , 0.11764707, 0.14117648, 0.36862746, 0.60392159, 0.66666669, 0.99215692, 0.99215692, 0.99215692, 0.99215692, 0.99215692, 0.88235301, 0.67450982, 0.99215692, 0.94901967, 0.76470596, 0.25098041, 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.19215688, 0.9333334 , 0.99215692, 0.99215692, 0.99215692, 0.99215692, 0.99215692, .........中略.......... 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ], dtype=float32), 5) >>> train[0][0].shape (784,)
これから、trainは要素数60000個のリストで、各リストは、画像データと、数値(0から9)のタプル。
画像データは、要素数784個の1次元配列。

今回は、最初の48個の画像だけを表示するので、最初の48個だけを取り出す。
xtrain = train._datasets[0][:48]
ytrain = train._datasets[1][:48]
あとは、subplotsを使って、画面を分割して表示するだけである。
fig,ax = plt.subplots(nrows=6,ncols=8,sharex=True,sharey=True)
ax = ax.flatten()
for i in range(48):
    img = xtrain[i].reshape(28,28)
    ax[i].imshow(img,cmap='Greys',interpolation='none')
ax.flatten()により、forループ中で、表示枠を指定するのに、単にax[i]で済ますことができる。
画像データは、サイズが784の1次元配列になっているので、28x28の2次元配列に直している。
画像データは賢く表示してくれると困るので、ありのまま表示するようにinterpolation='none'を指定している。
後は、ファイルにセーブし表示している。
ax[0].set_xticks([])
ax[0].set_yticks([])
plt.tight_layout()
plt.savefig("mnistdisp48.png")
print(ytrain.reshape(6,8))
plt.show()
plt.subplots()で、分割された枠のx軸y軸の情報を共有するために、sharex、shareyをTrueにしている。そして、最後のところで、set_xticks([])、set_yticks([])により、目盛りなど目障りなものを表示しないようにしている。

ということで、最後にプログラム全体を示す。
NVIDIAのGPU、GEFORCEの装着については先日書いた。
次にすべきことは、ちゃんとGPUをプログラムでいじれるか確認することである。

GPUのソフト開発ツールを入れてあれこれ試せばよいのだが、その前にOSを入れないといけない。
AI、 Deep Learning などに使おうとしている、そしてPython, 国産Deep LearningフレームワークのChainer を使おうとしているので、こういう組み合わせの場合よく選択されるLinux/Ubuntuを入れることにした。

Download Ubuntu Desktopに行って、Ubuntu 16.04 LTSを他のマシンで落としてきてDVDに焼いて、それを使ってインストールした。
このあたりのインストール手順についてはネット上に溢れているので、省略する。

しかし、今回、Ubuntuのインストールに何故か失敗した。
もう何回インストールしたか記憶にないのだが、そのため、インストールしながら、他の雑用なども並行しておこなっていた。
そして、インストールが終わって、リブートしたら、動きがおかしい。画面がもさっとした動きしかできない。ディスプレイはGPUボードではなく、マザーボードからやっていて、マザーのディスプレイが超ダメだからこんなに遅いのかとも思ったが、インストール中の反応は悪くなかった。
UbuntuSystemSettings.png
そして、どうしようもないのが、システム設定のアイコンを押しても全く反応がないのである。
これでは、なんの設定変更も困難である。

どうも、ながらインストールしたのがいけないらしい。手抜きをし過ぎて、Ubuntuが嫌がらせをしたようであるので、心を入れ替えて、再インストールすることにした。


すると、今度は何の問題もなく、ちゃんとインストールできた。
ここまでは単なる準備である。

次に、NVIDIAのGPU、GeForceのソフトウエア開発環境をインストールをしないといけない。
AIのソフトはPythonで書くつもりなのだが、PythonからGPUを使う場合には、Cで書かれたプログラムが呼ばれる。まあ、PythonなどでGPUを動かしていたら、せっかくのGPUの速度が生かせないので、GPUを使うにはCを使うのだ。

ということで、NVIDIAが用意しているNVIDIA(自社)のGPUの開発環境であるCUDAをインストールし、GPUがちゃんと動いているか確認することにした。

まず、CUDAをダウンロードするなり、オンライン・インストールしないといけない。
なにはともあれ、CUDAのダウンロードページに行くしかないのだ。

今回は、実行環境は次のように選択した。
CudaSelectPlatform.png
全部選択し終えると、次のように3つのインストール選択肢が出てくる。 そして、サイズは1.9GBだと。巨大だ!
Installation Instructions:

 1. `sudo dpkg -i cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64.deb`
 2. `sudo apt-get update`
 3. `sudo apt-get install cuda`
どれを選んでも良いと思うが、今回は1.で、一気に1.9GBダウンロードしてからインストールしてみた。
このインストールについても、ネット上に説明が転がっているし、失敗するような面倒なことはない。ということで、インストールの詳細は省略する。

さて、インストールは済んだはずなので、動作確認をしよう。(次回につづく)

sklearnの8x8ドットの手書き文字を使うのをChainer:iris以外のデータでDeep Learning で紹介したが、ここで最初の48文字の画像を示す。
digitsdisp48.png これは、以下の数字を示しているのだが、相当無理があろう。
[[0 1 2 3 4 5 6 7]
 [8 9 0 1 2 3 4 5]
 [6 7 8 9 0 1 2 3]
 [4 5 6 7 8 9 0 9]
 [5 5 6 5 0 9 8 9]
 [8 4 1 7 7 3 5 1]]

ということで、もっと良い数字画像データセットを使うことにする。

よく使われるものに、THE MNIST DATABASE of handwritten digits がある。
The MNIST database (Mixed National Institute of Standards and Technology database) は、28x28の手書き数字のデータ・セットで、トレーニング用が6万文字、テスト用が1万文字ある大規模なものである。
全部使うとシステムが重くなったりするので、一部だけを利用することも多い。

とりあえず、トレーニングセットの最初の48文字を上と同じ形式で示す。
mnistdisp48.png
[[5 0 4 1 9 2 1 3]
 [1 4 3 5 3 6 1 7]
 [2 8 6 9 4 0 9 1]
 [1 2 4 3 2 7 3 8]
 [6 9 0 5 6 0 7 6]
 [1 8 7 9 3 9 8 5]]
全然画像の細かさが違うのが分かるだろう。

さて、これをどうやって読み込み、並べて表示したプログラムについては、次回に説明しよう。
AIをいじっていると、どうしても計算時間で苦しめられるようになる。ちょっと勉強するにしても、何とか計算時間を短縮できないものかと思うようになる。
AIでは、大量の計算をCPUではなく、GPU(グラフィックプロセッサ)で行うことが普通だ。
また、大量のGPUが使えるクラウドサービスも色々ある。
東京工業大学のTSUBAMEのように、スパコンを細かく分けて使えるようにしているサービスもある。詳しくは、TSUBAME計算サービスを参照のこと。運用状況なども公開されている。

しかし、もうちょっと身近に、かつ勝手に使ってみないと、細かいことを実感できないなと思っていると、こんなものが先日机の上に置かれていた。

GPUといえば、今は圧倒的にNVIDIAが有名だ。多くのスパコンでも使われていて、Deep Learning は、NVIDIAのGPUの上で動いていると言ってもだいたい当たっている。
....などと考えていると、

GTX0150Ti-1.jpg
マザーボードと、NVIDIAのGPUである GEFORCCE GTX 1050Ti と、真っ赤なメモリが机の上に置かれていた。

CPUは、そのへんのをつければ動く。GPUでも超省エネの場合は普通のミニタワー型パソコンでも大丈夫だが、これは2スロット分の幅があり、電気もそれなりに食うので、電源がしっかりしているボックスでないとダメだ。
とりあえず、750ワットのケースの中身を入れ替えて、そこいらのi5を挿してみた。

GTX1050Ti-3.jpgGTX1050Ti-2.jpg
GTX1050Ti-4.jpgGTX1050Ti-5.jpg

電源を入れると、暗いケースの中で、GPUボードのLEDが怪しく光るようになった。ちゃんと動いているということだろうか。

ちゃんと動作しているかは、起動時にDELキーを押してBIOS設定画面に入ると分かりやすい。
どんどんグラフィカルな制御盤みたいになっていくな。

GTX1050-Ti-6.jpgということで、インストールについては、次回(GPU編)、書こう。

AutoEncoder1回分の処理を行う関数は前回説明した。

今回は、そのexeconce()を呼び出して脳内情報を得て、matplotlibを利用して図示しよう。

9回呼び出して、3x3の形に並べてみよう。
まず、図のサイズを決める。デフォルトでは小さいので、サイズ指定した。

plt.figure(figsize=(16,12))
figure()により、図に関するさまざまな事を指定できる。
figsizeは、全体のサイズをインチ単位で指定する。
dpiもあり、デフォルトはdpi=100になっている。
なので、上記は、100dpiで、16インチx12インチになる。
つまり、ドットで言えば、1600ドットx1200ドットとなる。

準備ができたら、9回ループして、実行し、結果を毎回画面の指定区分の中に表示するだけ。

for idx in range(9):
    print("exec ",idx)
    ans = execonce()

    ansx1 = ans[0:50,0]
    ansy1 = ans[0:50,1]
    ansx2 = ans[50:100,0]
    ansy2 = ans[50:100,1]
    ansx3 = ans[100:150,0]
    ansy3 = ans[100:150,1]

    plt.subplot(3,3,idx+1)
    plt.scatter(ansx1,ansy1,c="r",alpha=0.5,linewidth=0)
    plt.scatter(ansx2,ansy2,c="g",alpha=0.5,linewidth=0)
    plt.scatter(ansx3,ansy3,c="b",alpha=0.5,linewidth=0)
ansx1,ansy1,...の部分は、3種類のIrisに合わせて、3つのx座標、y座標に分けているだけ。

plt.subplot()で、画面分割している。
行分割数、列分割数の順に指定し、最初から何番目枠に表示するかを指定する。枠番号は、1から始まる。

plt.scatter()で散布図の形で示した。
最初の2つが、x座標値、y座標値んびなる。
c=色、alpha=透過率、linewith=0 で表示している。
マークを使ったり、色を細かく指定したり、いろいろできるが説明省略。

これだけで、Deep Learning の脳内がちょっと表示できたと言えるかな。

Irisデータで実験したAutoEncoderの、中間層(ノード数2)の中身は次のようになっていた。
各グラフが1回の実験を示し、9回実験したものを並べてみた。
もちろん同じデータで同じ条件で実験したのだが、こんなに違う。
目盛りも図毎にかなり異なる。
つまり、

同じデータを与え、ほとんど似た結果が出ても、中間層の状態は同じにはならない。

中間層は2ノードの層が1つあるだけ。この2つのノードのそれぞれをx軸、y軸とし、Irisの種類にによって、RGBの3色で中間層のノードの値をプロットしてみた。
Irisの種類によって、偏りがあるのがわかる。
とくに、R(赤)は、G、Bとははっきり分かれている。
irisautoencode.pngでは、この図をどうやって作ったか、プログラムの説明をしよう。

今までは、学習し、評価し、それで終わりのプログラムであったが、今回は全データを学習に用い、中間層の中身を取り出して、その中間層の中身を返す関数にオートエンコーダの1回分の処理をまとめ、execonce() とした。

def execonce():
    # Initialize model        
    model = MyAE()
    optimizer = optimizers.SGD()
    optimizer.setup(model)

    # Learn
    n = 150
    for j in range(3000):
        x = Variable(xtrain)
        model.cleargrads()    # model.zerograds() は古い
        loss = model(x)
        loss.backward()
        optimizer.update()
                                                                                                                        
    # get middle layer data
    x = Variable(xtrain, volatile='on')
    yt = F.sigmoid(model.l1(x))
    ans = yt.data

    return ans
学習が終わったところで、今度はmodelではなく、その中の中間層の値が決まる所まで実行し、中間層のノードが入っているdataだけを取り出して返す。 これで、execonce()を呼ぶと、各入力データに対する中間層の2ノードの値がまとめて帰ってくる。

では、このexeconce() を直接実行してみよう。
>>> ans = execonce()
>>> ans.shape
(150, 2)
>>> ans[:5]
array([[ 0.99554908,  0.00865224],
       [ 0.99200833,  0.01623198],
       [ 0.99302506,  0.01248077],
       [ 0.992621  ,  0.02188915],
       [ 0.99591887,  0.00837815]], dtype=float32)
>>> 
ちゃんとデータ数150個に対し、2つの値が求まっている。
これを、Irisの種類に応じて色分けして表示すると、何か分かるかも知れない。

ということで、最初に示した図が得られるのだが、そのあたりは次回説明しよう。
前回MyAEというクラスを説明した。
このクラスからオブジェクトを作ると、用意ができる。

# Set data
from sklearn import datasets
iris = datasets.load_iris()
xtrain = iris.data.astype(np.float32)

# Initialize model        
model = MyAE()
optimizer = optimizers.SGD()
optimizer.setup(model)
ここまで用意ができたら、ループを回して延々と学習するだけであり、その処理は今までとほとんど同じである。

# Learn
losslist = []
for j in range(5000):
    x = Variable(xtrain)
    model.cleargrads()             # model.zerograds() 非推奨
    loss = model(x)
    if j%1000 == 999:
        print( "%6d   %10.6f" % (j, loss.data) )
    losslist.append(loss.data)     # 誤差をリストに追加
    loss.backward()
    optimizer.update()
今回は、誤差が徐々に縮まっていくところをグラフ化してみよう。
そのため、modelに学習データを与えて実行(学習)させると、誤差(loss)が帰ってきて、それをリストlossに加えていく。
正確には、loss.dataが誤差である。

これで、誤差がlosslistに溜まっているので、表示し、画像ファイルとしてセーブした。

# 誤差グラフの表示
plt.plot(losslist)
plt.savefig("irisautoencodeloss.png")
plt.show()

irisautoencodeloss.png誤差がどんどん小さくなっているいることが分かる。
つまり、学習が進むと、入力と出力がどんどん一致していっていることが分かる。

さて、次回は、もっと突っ込んで、ネットワークの内部のデータを調べてみよう。


このアーカイブについて

このページには、fujiが最近書いたブログ記事が含まれています。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。