機械学習入門: 自前データでニューラルネット
テキストのニューラルネットのプログラム(ch05/train_neuralnet.py)に, 生物資源学類生の作った手書き文字データを入れて走らせてみよう。そのためには, まず, 資源生データを, ch05/train_neuralnet.pyの入力データ(訓練データ・テストデータ)とできるだけ同じ形に加工しよう(テキストP72~74も参考に!)。
入力データ形式の調査
課題1-01: ch05/train_neuralnet.pyは, 入力データ(訓練データ・テストデータ)をどのような名前のオブジェクト(インスタンス)に入れているか?
課題1-02: それらのオブジェクト(インスタンス)は, どの行でどのようなコマンドで読み込んでいるか?
課題1-03: そのコマンドのソースコードを探しだせ。それを含むファイルのパスと, そのファイルの中の何行目にあるかを述べよ。
課題1-04: そのソースコードを読み, 入力データはどのようなファイルに入っているかを調べ, そのファイルを実際にlsコマンド(Linux)で探し出せ。
以後, datasetディレクトリに入ってpythonシェルを立ち上げて, その中で作業せよ。
課題1-05: pythonシェルで, そのファイルをdatasetという名前で読み込め。読み込み方は, 君が既に調べた上記のソースコードに書いてあるだろう。
課題1-06: そのdatasetというオブジェクトは, どのようなクラスのインスタンスか, 調べよ。ヒント: helpコマンド
課題1-07: そのdatasetというオブジェクトは, どのような構造をしているか, 調べよ。
課題1-08: そのdatasetというオブジェクトには, 訓練データ, そのラベル, テストデータ, そのラベルがそれぞれどのように入っているか, 調べよ。
課題1-09: そのdatasetというオブジェクトの訓練データの最初の手書き数字を数値として表示してみよ。
課題1-10: そのdatasetというオブジェクトの訓練データの最初の手書き数字を画像として表示してみよ。
課題1-11: それが訓練データの最初のラベルに正しく対応しているかどうか調べよ。
課題1-12: そのdatasetというオブジェクトのテストデータの最初の手書き数字を画像として表示してみよ。
課題1-13: それがテストデータの最初のラベルに正しく対応しているかどうか調べよ。
その結果, 入力データは,
- 'test_img', 'test_label', 'train_img', 'train_label'という4つのキーを持つディクショナリクラスのインスタンスが,
- mnist.pklというファイル名でファイルになったものであり,
- 'test_label', 'train_label'は0~9の手書き数字にそれぞれ0~9のラベルがつけられたもの(one_hot表現ではない)であり,
- 'test_img', 'train_img'は手書き数字の画像群を表現した(データ数 x 784)のnumpy.ndarrayクラスのインスタンスであり,
- 個々の手書き数字の画像は784(=28 x 28)個の数値で表され, その数値は0以上255以下の整数型(uint8)である。
ということがわかった。資源生データもそのようにしよう。
オリジナルデータの読み込み・加工
いったんLinuxシェルに戻って, 以下を行え。
課題2-01: 資源生の手書き文字データ(shigen_tegaki.tar.gz)を, manabaからダウンロードし, datasetディレクトリに入れよ。不具合データを取り除いた改良版。
課題2-02: それを解凍せよ(datasetディレクトリに行ってから, tar zxvf shigen_tegaki.tar.gz)。
課題2-03: 資源生の手書き数字を切り出し, 28x28サイズのjpegファイルに変換せよ。cd shigen_tegaki; ./README.shでOK。resize/ディレクトリに5800枚のJPEG画像ができているはず。
では, 再びpythonシェルに戻って, 以下をやってみよう。pythonシェルはdatasetディレクトリから起動すること。
課題2-04: 資源生データ(手書き数字ファイル)から任意の1つを選んで, それらのファイル名(パス)をjpgfilenameという文字列型変数に格納せよ。もしもshigen_tegaki/resize/2_4hn1-08.jpgを選んだなら,
In[]: jpgfilename='shigen_tegaki/resize/2_4hn1-08.jpg'
課題2-05: そのファイルをimという名前のオブジェクトとして読み込んで, 表示してみよ。
from PIL import Image import matplotlib.pyplot as plt im=Image.open(jpgfilename) plt.imshow(im) plt.show()
課題2-06: それを, 784個の要素からなる数ベクトル(np.ndarrayクラスのインスタンス)に変換せよ。
import numpy as np im=np.array(im).flatten print(im)
課題2-07: そのラベルは, ファイル名の先頭文字で紐付けられている。それを数値として取り出せ:
import os label=int(os.path.basename(jpgfilename)[0]) print(label)
課題2-08: 同様のことを, 3つのファイルについてやってみよう。資源生データ(手書き数字ファイル)から任意の3つを選んで, それらのファイル名(パス)をjpgfilename_listというリストにせよ。ヒント: jpgfilename_list=['shigen_tegaki/resize/2_4hn1-08.jpg', 'shigen_tegaki/resize/4_1hn1-29.jpg', 'shigen_tegaki/resize/7_0hn2-30.jpg']みたいにするだけ。
課題2-09: for ループをjpgfilename_listに使って, その3つのファイルを順次読み込んで, 画像を(3 x 784)のimagesというnumpy.ndarrayインスタンスに, ラベルをlabelsというnumpy.ndarrayインスタンスに格納せよ(うまくできたか確認せよ)。
images=[] labels=[] for jpgfilename in jpgfilename_list: print(jpgfilename) im=np.array(Image.open(jpgfilename)).flatten() lb=int(os.path.basename(jpgfilename)[0]) images += [im] labels += [lb] images = np.array(images) labels = np.array(labels)
課題2-10: それを参考にして, 資源生手書き数字を全て読み込み, 画像を(5800, 784)のimagesというnumpy.ndarrayインスタンスに, ラベルを要素数784のlabelsというnumpy.ndarrayインスタンスに格納するpythonプログラムを, "shigen_tegaki_create.py"という名前で作れ(うまくできたか確認せよ)。ヒント: glob.glob
課題2-11: "shigen_tegaki_create.py"を改良し, 以下ができるようにせよ:
- 全データの1/6をテストデータに, 5/6を訓練データになるように分割する。
- その分割は, 乱数を使って行う。ヒント: np.random.choice(, , replace=False)
- 上で調べた, ch05/train_neuralnet.pyの入力データと同じ構造にする(ディクショナリを使って)。
- それを, "shigen_tegaki.pkl"というファイルに書き出す。ヒント: pickle.dumpコマンド。
# 例 import os import numpy as np import sys import pickle import glob from PIL import Image picklefilename="shigen_tegaki.pkl" jpgfilename_list=glob.glob("shigen_tegaki/resize/?_*.jpg") jpgfilename_list.sort() images=[] labels=[] for jpgfilename in jpgfilename_list: print(jpgfilename) im=np.array(Image.open(jpgfilename)).flatten() lb=int(os.path.basename(jpgfilename)[0]) images += [im] labels += [lb] images = np.array(images) labels = np.array(labels) numdata=labels.shape[0] numtest=int(numdata/6) index_test=np.random.choice(numdata, numtest, replace=False) index_test.sort() test_img=images[index_test] test_label=labels[index_test] labels[index_test]=-1 train_img=images[labels>-1] train_label=labels[labels>-1] dataset = {'train_img':train_img, 'train_label':train_label, 'test_img':test_img, 'test_label':test_label} with open(picklefilename, 'wb') as f: pickle.dump(dataset, f, -1)
ニューラルネットのソースコード変更と実行
課題3-01: dataset/mnist.pyを改造して, その中にload_shigen()という関数を作れ。その関数は, load_mnist()とほぼ同じ動作をするが, 読み込むのは先ほど作ったshigen_tegaki.pklである。
例: def load_mnist(...)のあたりをコピーして, 関数名を変えて以下のようにする:
def load_shigen(normalize=True, flatten=True, one_hot_label=False): dataset_dir = os.path.dirname(os.path.abspath(__file__)) shigen_file = dataset_dir + "/shigen_tegaki.pkl" (以下略)
課題3-02: ch05/train_neuralnet.pyを改造して, mnist.pklでなくshigen_tegaki.pklによってニューラルネットを訓練できるようにせよ。ヒント: まず, ch05/train_neuralnet.pyの冒頭部で, load_shigen()関数を使えるようにしておく。次に, 「データの読み込み」の部分を変更して, load_mnistのかわりにload_shigenを使う。また, 現状ではiter_per_epochが整数値にならない場合があるので, 整数値になるようにキャストする。
課題3-03: 結果はどうか? いろいろうまくいかないだろう。最初は精度もぜんぜん上がらないだろう(10~20 %くらい)。それらをひとつずつ潰して, うまく走るようにしてみよ。最終的に精度は90%程度になるはず。ヒント: 資源生の画像をMNISTの画像と比べてみよ。様子がだいぶ違うだろう。それをMNISTの画像に寄せていくのだ。いろんなやり方があるが, dataset/mnist.pyのload_shigen()関数の中で, normalizeのあたりで処理をするというのもひとつの手だろう。ちなみに, dataset/mnist.pyの変更を反映してch05/train_neuralnet.pyを走らせるには, いちどpythonシェルを抜けてから入り直す。
課題3-04: shigen_tegaki.pklで訓練したニューラルネットを, mnist.pklのテストデータで評価してみよ。
課題3-05: mnist.pklで訓練したニューラルネットを, shigen_tegaki.pklのテストデータで評価してみよ。
訓練データ・テストデータのサンプリングが精度に及ぼす影響
課題4-01: 上の課題3-04, 課題3-05で, 精度が上がらなかった理由は何か? 精度が10 %くらいということは, ぜんぜん当たっていないということ(10個の数字の判別なので、でたらめな予想でも1/10=10%の精度は出る)。これはひどすぎるだろう。何かがおかしいはずだ。ヒント: shigen_tegakiのデータと, mnistのデータをもういちどひとつずつ画像に表示して, 様子を比べてみよ。
課題4-02: その理由をもとに, 課題3-04で精度がせめて30%を超えるように工夫せよ。
ところで, 課題3-03では, shigen_tegakiのデータは, 資源生116人が書いた5800個の数字で訓練およびテストを行った。その際, 訓練データとテストデータは重複しないように気をつけた。しかし, それでも同一人物が書いた文字が, 訓練データとテストデータにまたがって存在しているだろう。これでは真の意味での「テスト」になっていない。
課題4-03: 訓練データを書いた人と, テストデータを書いた人が重ならないように, 訓練データとテストデータを作りなおせ。その際, 訓練データとテストデータのバランスが概ね5:1になるように工夫せよ。
課題4-04: そのデータを用いて, ニューラルネットを訓練し, 精度を評価してみよ。課題3-03の結果と比べて, 考察せよ。
畳み込みニューラルネットワーク
課題5-01: shigen_tegakiのデータを使って, 7章の畳み込みニューラルネットワークを訓練し, 精度を評価せよ。(ch07/train_convnet.pyをいじればよい)。
ライブラリを使う
課題6-01: scikit-learnライブラリを使って, shigen_tegakiの手書き数字データでサポートベクトルマシーンを訓練し、精度を評価せよ。ヒント: 「RaspberryPiではじめる機械学習」第6章。ポイントは,
- ml-06-06-svc-traintest.pyをベースに書き換える。
- from dataset.mnist import load_shigenを入れて, load_shigen()を使えるようにする。
- その際, パスに注意。必要に応じて, import sys, os; sys.path.append(os.pardir)などを入れよう(意味を理解してね!!)
- digits=からclf=の直前までの行は不要。そのかわりに, (X_train, y_train), (X_test, y_test) = load_shigen(normalize=True, one_hot_label=False) を入れればよい。これまでxだったのが大文字Xになり, tだったのがyになったことに注意。one_hot_label=Falseにすることも大事。このへんはもとのコードを走らせながら自分で気づくべし。
- だいたい89 %くらいの精度になるでしょう!
課題6-02: scikit-learnライブラリを使って, shigen_tegakiの手書き数字データでニューラルネットワークを訓練し、精度を評価せよ。ヒント: 「RaspberryPiではじめる機械学習」第6章。ポイントは,
- ml-06-07-nn-traintest.pyを参考にする。
- 課題6-01で作ったプログラムをベースに書き換える。
- といっても, すべきことは分類アルゴリズムの変更だけ。以下の行を適当にどこかの行と入れ替える(自分で考えよう): clf = MLPClassifier(hidden_layer_sizes=(100, ), max_iter=1000, tol=0.0001, random_state=None)
- ただし, それだけではNameError: name 'MLPClassifier' is not definedみたいなエラーが出るかも。何がおかしいか, 自分で考えよう!!
- だいたい76 %くらいの精度になるでしょう!
課題6-03(オプション): scikit-learnライブラリを使って, shigen_tegakiの手書き数字データでディープラーニングを訓練し、精度を評価せよ。ヒント: 「RaspberryPiではじめる機械学習」第10章。この課題は, 学情端末ではうまくいかないだろう。ライブラリが足りないからである。自分のパソコン等を使ってがんばろう!!
Keyword(s):
References:[機械学習入門]