日本の古文書で機械学習を試す(2)　作成したモデルを使って文字を認識させる

前回、[日本の古文書で機械学習を試す(1)](https://qiita.com/ariera/items/0e06538872caf7262648)で、『日本古典籍字形データセット』のサンプルプログラムを用いて作ったモデルを使って、自分で手書きした文字を認識させてみる。

### 学習用データ、テスト用データの中身を見る

手書きした文字を認識させるには、手書きデータを学習用データと同じ形に変換する必要がある。そのため、学習用データがどうなっているのか確認してみた。

サンプルプログラム中の train_test_file_list.h5 が学習用データとテスト用データがセットになったデータセットだ。HDF5というファイル形式のようなので、中身を見るために[こちら](https://confluence.hdfgroup.org/display/support/HDFView+2.14)から、HDFViewというツールをダウンロードして中身を確認した。(HDFViewのインストールで結構ハマったのだが、本筋とは関係ないので、この投稿の最後に追記する。)

HDFViewでtrain_test_file_list.h5を開くと、中身はこうなっている。画面上に表示されるのは3514x28のデータだが、下の方にデータ型が3514x28x28と書いてあるので、3次元のデータらしい。最後の次元の28個は、画面の上の方の黄色い矢印で順番に表示させることができる。
test_xは、28ピクセル x 28ピクセルの画像が3514個入っている。各セルの数字は0～255の画素値っぽい。データの性格からすると、画面上に28x28を表示して、黄色い矢印で3514個を送れるようにしてくれたほうが分かりやすいのだが、汎用ツールなので仕方がない。
![FDFView_test_x.jpg](https://qiita-image-store.s3.amazonaws.com/0/306431/fa366d94-bb9b-7aa6-5afa-ae4784f6bf36.jpeg)

test_yのほうはこんな感じ。3514個の画像がどの文字を表しているかが、0～9の数字で入っている。
![FDFView_test_y.jpg](https://qiita-image-store.s3.amazonaws.com/0/306431/384ab544-586e-e532-16cc-faf85b68bab7.jpeg)

サンプルプログラム中のconfig.pyにUTF-8の文字コードが設定されていて、各数字と文字の対応は以下の通り。

0  U+3057 = し
1  U+306B = に
2  U+306E = の
3  U+3066 = て
4  U+308A = り
5  U+3092 = を
6  U+304B = か
7  U+304F = く
8  U+304D = き
9  U+3082 = も

train_x, train_yは画像の数が19909個に増えているだけで、データ形式はtest_x, test_yと同じだった。

### 学習用データはどんな画像なのかチェック

画素値だけでは、どんな画像なのかよくわからないので、読み込んだデータを画像として表示させてみた。ついでなので、勉強のため、サンプルプログラム中に入っていたライブラリconfig.py load.pyを使わずに、データファイルを読み込んでみた。

```python:学習用データ表示
# 学習用＆テスト用データファイル読み込み
import numpy as np
import h5py
train_test_file_path = 'train_test_file_list.h5'
with  h5py.File(train_test_file_path,'r') as hf:
    train_x = np.array(hf.get('train_x'))
    train_y = np.array(hf.get('train_y'))
    test_x = np.array(hf.get('test_x'))
    test_y = np.array(hf.get('test_y'))

# 学習用データを画像として見てみる
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize=(8,5)) # 全体の表示領域のサイズ(横, 縦)
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.5, wspace=0.05)
# ↑サブ画像余白(左と下は0、右と上は1で余白なし) サブ画像間隔 縦,横
for i in range(40):
    ax = fig.add_subplot(5, 8, i + 1, xticks=[], yticks=[]) # 縦分割数、横分割数、何番目か、目盛り表示なし
    ax.title.set_text("[" + str(i) + "]") # グラフタイトルとして番号を表示
    ax.imshow(train_x[i].reshape((28, 28)), cmap='gray')
```

19909個の学習用データ中の最初の40個を表示させてみた結果。まだまだ古文書初心者なので読めない字も多い(汗)
![学習用画像.png](https://qiita-image-store.s3.amazonaws.com/0/306431/527b5416-f622-d9a5-7c19-30888622ae2c.png)

### 自分で書いた文字を認識させる

ここからやっと本題。Windows標準のペイントで文字を書いて、前回作ったモデルで認識させてみる。

まずはメモ帳で、ひらがなを書いてJPEGで保存する。28x28ピクセルだと手書き文字を書くのが難しいので、280x280で作成して、あとでサイズ変換する。
![paint_mo.jpg](https://qiita-image-store.s3.amazonaws.com/0/306431/e30b5cc3-7467-7f7c-0c54-bc0499c91ced.jpeg)

サンプルプログラム中のload.py中に画像ファイルを読み込んでHDF5ファイルを作る関数があったので、それを参考に、CV2を使って作成したJPEGを読み込み、28x28ピクセルにサイズ変換する。

```python:画像データ読み込み
import cv2
x = np.zeros((28,28))
img =  cv2.imread('paint_mo.jpg', 1) # ペイントで作成した画像ファイル名。画像はpythonと同じフォルダに置く
grey_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
x = cv2.resize(grey_image, (28,28), interpolation = cv2.INTER_LINEAR )
```
サンプルプログラム本体run.pyを見ると、学習用データとテストデータのxデータ(画像データ)は、[データの個数] x 28 x 28 x 1　の行列なので、作成したデータも (28,28)から(1,28,28,1)の行列に変換する。さらに、学習用データ、テストデータに合わせて、0～255のデータを、255で割って0～1の少数データにする。

```python:画像データ変換
x = np.expand_dims(x, axis=0)
x = np.expand_dims(x, axis=3)
x = x.astype('float32')
x /= 255
```
これでやっと準備が完了したので、前回作成したモデルを読み込んで、認識させてみる。

```python:モデルを使って認識させる
# モデル読み込み
from keras.models import load_model
model = load_model('model_komonjo_mnist.h5')

# どの文字かを判別する
preds = model.predict_classes(x)
pred = preds.tolist()
print("予測結果：" + str(pred))

# 各文字である確率
prob = model.predict_proba(x)
prlist = prob.tolist()
print(prlist)
```

```python:結果
予測結果：[9]
[[0.29332467913627625, 0.012350857257843018, 2.0295312424423173e-05, 0.0023382618092000484, 0.003347291611135006, 0.001386790070682764, 0.0007454972364939749, 0.002765553304925561, 1.101136376746581e-06, 0.6837196946144104]]
```
予測結果はこうなった。番号9は「も」なので、ちゃんと正解できている！
各文字の確率は、9(も)が一番高くて0.68、次が0(し)で0.29、その次になると1(に)で0.012とかなり確率が低くなる。直感的にも「も」に一番似ているのは「し」かなぁと思うので、まぁ妥当な結果だ。学習させたのは古文書のくずし字なので、現代人が書くひらがなとはちょっと違うのかもしれないが…

調子乗って下の「く」と「の」でも試してみたが、これもちゃんと認識できた。「く」は何と確率99%以上で「く」と判定された。「の」は約70%だった。
![paint_ku_small.jpg](https://qiita-image-store.s3.amazonaws.com/0/306431/ec030397-bd5c-3d8f-2ad3-06825a794705.jpeg)　　![paint_no_small.jpg](https://qiita-image-store.s3.amazonaws.com/0/306431/dc46895b-af5a-224c-1554-7c78c5e65c5e.jpeg)

### おまけ、その１　Kerasのload_img

CV2ではなく、Kerasのload_imgを使った画像読み込みコードも書いてみた。

```python:Kerasのload_img版
from keras.preprocessing.image import load_img, img_to_array
my_img = load_img('paint_no.jpg', True, target_size=(28,28)) # 2つめの引数はグレースケール
x = my_img.convert('L') # ローパスフィルタ=薄いグレーを白にする？
x = img_to_array(x)
x = np.expand_dims(x, axis=0) # (28,28,1)の行列から(1,28,28,1)の行列に変換
x = x.astype('float32')
x /= 255
```

load_imgを呼び出そうとすると

```
ImportError: Could not import PIL.Image. The use of `array_to_img` requires PIL.
```
というエラーが出た。調べてみるとPillowが必要らしいので、プロンプトからインストールした。

```
> conda install Pillow
```
このとき、Jupyter Notebookを起ち上げっぱなしにしていたら、Pillowをインストールしてもエラーが消えなかったが、一度Jupyter Notebookを終了して再起動したらちゃんと動いた。ライブラリをインストールした後は、Jupyter Notebookの再起動が必要らしい。

## おまけ、その２　HDFViewでハマった話

[こちら](https://confluence.hdfgroup.org/display/support/HDFView+2.14)から、HDFViewをダウンロードしてインストールした後、train_test_file_list.h5を開こうと思ったら、HDFViewは起動するものの、

```
Failed to open file c:\Users ......
java.io.IOException: Unsupported fileformat -C:\Users ...
```
というメッセージが出てファイルが開けない。ネットで調べたら64bit版と32bit版の違いとか、色々書いてあったので、最初にインストールした64bit版をアンインストールして32bit版をインストールしてもダメ、バージョン2.14から、最新の3.0にアップしてもダメ。(ちなみに、2.14はすぐにダウンロードできたが、3.0のコンパイル済みバイナリをダウンロードするには[こちら](https://www.hdfgroup.org/register/)から会員登録する必要があった)

面倒だけどソースからコンパイルするしかないか～と思ったが、ふと気づいて、ファイルのパスに日本語を含まない場所(C:\直下とか)に置いたら開けた！！ 日本語のフォルダ名をつけていたのがエラーの原因だったらしい。(ちなみに、動いたのはバージョン2.14のWindows 32bit版。他のバージョンでも動くのかもしれないが、試す気力がなかった。)