皆様新年あけましておめでとうございます。冬場は毎日在宅勤務したいh_matsumotoです。最近無料でディープラーニングを学べるという「Aidemy」をやってみました。とても勉強熱心なので、12月24日と25日を丸々費やしてみました。決して予定が空いていた訳ではないと思います。
Aidemyではディープラーニングを利用して、0~9までの数字を分類する方法を学ぶ事が出来ます。
今回はそれを応用して「ひらがな」を分類してみたいと思います。
【目次】
1 ひらがなデータの入手
「ひらがな 画像 データセット」等でググって探してみると、こちらのサイトが見つかりましたので利用させて頂きました。
NDL Lab
文字画像データセット(平仮名73文字版)を試験公開しました | NDLラボ
画像数は数がバラバラですが、大体一つのひらがなにつき1000枚以上画像があります。
1枚の画像サイズは48 x 48 [pixels]です。
2 画像データを配列にする
Aidemyの数字判別用のデータは最初から配列に変換されたデータですが、こちらは画像データなので1枚ずつ画像データを配列に変換する必要があります。Aidemyではその方法もレクチャーとしてありました。
https://aidemy.net/courses/4050
使用言語
python3.5
使用ライブラリ
import numpy as np import pandas as pd %matplotlib inline #jupyter note bookで実行する場合 import matplotlib.pyplot as plt from keras.layers import Activation, Dense, Dropout from keras.models import Sequential from keras import optimizers from keras.utils.np_utils import to_categorical from keras.callbacks import EarlyStopping import requests import zipfile import cv2 import os from sklearn.model_selection import train_test_split
zipファイルをダウンロードし展開。
画像ファイルを読み込み、特徴量とラベルデータを作る。
#フォルダ名:{ひらがな:ラベル(番号)} aiueo = {"U3042":{"あ":1}, "U3044":{"い":2}, "U3046":{"う":3}, "U3048":{"え":4}, "U304A":{"お":5}, "U304B":{"か":6}, "U304C":{"が":7}, "U304D":{"き":8}, "U304E":{"ぎ":9}, "U304F":{"く":10}, "U3050":{"ぐ":11}, "U3051":{"け":12}, "U3052":{"げ":13}, "U3053":{"こ":14}, "U3054":{"ご":15}, "U3055":{"さ":16}, "U3056":{"ざ":17}, "U3057":{"し":18}, "U3058":{"じ":19}, "U3059":{"す":20}, "U305A":{"ず":21}, "U305B":{"せ":22}, "U305C":{"ぜ":23}, "U305D":{"そ":24}, "U305E":{"ぞ":25}, "U305F":{"た":26}, "U3060":{"だ":27}, "U3061":{"ち":28}, "U3062":{"ぢ":29}, "U3064":{"つ":30}, "U3065":{"づ":31}, "U3066":{"て":32}, "U3067":{"で":33}, "U3068":{"と":34}, "U3069":{"ど":35}, "U306A":{"な":36}, "U306B":{"に":37}, "U306C":{"ぬ":38}, "U306D":{"ね":39}, "U306E":{"の":40}, "U306F":{"は":41}, "U3070":{"ば":42}, "U3071":{"ぱ":43}, "U3072":{"ひ":44}, "U3073":{"び":45}, "U3074":{"ぴ":46}, "U3075":{"ふ":47}, "U3076":{"ぶ":48}, "U3077":{"ぷ":49}, "U3078":{"へ":50}, "U3079":{"べ":51}, "U307A":{"ぺ":52}, "U307B":{"ほ":53}, "U307C":{"ぼ":54}, "U307D":{"ぽ":55}, "U307E":{"ま":56}, "U307F":{"み":57}, "U3080":{"む":58}, "U3081":{"め":59}, "U3082":{"も":60}, "U3084":{"や":61}, "U3086":{"ゆ":62}, "U3088":{"よ":63}, "U3089":{"ら":64}, "U308A":{"り":65}, "U308B":{"る":66}, "U308C":{"れ":67}, "U308D":{"ろ":68}, "U308F":{"わ":69}, "U3090":{"ゐ":70}, "U3091":{"ゑ":71}, "U3092":{"を":72}, "U3093":{"ん":73}} def download_file(url): filename = url.split('/')[-1] r = requests.get(url, stream=True) with open(filename, 'wb') as f: for chunk in r.iter_content(chunk_size=1024): if chunk: f.write(chunk) f.flush() return filename return False def zip_extract(filename): zfile = zipfile.ZipFile(filename) zfile.extractall('.') return zfile file_name = download_file('http://lab.ndl.go.jp/dataset/hiragana73.zip') zfile = zip_extract(file_name) d1 = zfile.filename.replace('.zip','') #特徴量 hiragana_array = [] #ラベル hiragana_label = [] for d2 in os.listdir('./' + d1): for picture in os.listdir('./' + d1 + '/' + d2): #ここで画像を読み込んで配列(48,48,3)を取得する img = cv2.imread('./' + d1 + '/' + d2 + '/' + picture) #画像をモノクロ画像にする(48,48,3)→ (48,48)になって計算が早くなる img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) #閾値処理(二値化)する retval, my_img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) #画像の配列を1次元にする(48×48)⇒ 2304 hiragana_array.append(my_img.reshape(-1)) hiragana_label.append(list(aiueo[d2].values())[0])
閾値処理(二値化)について
retval, my_img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
上記の処理ですが、大津の二値化というアルゴリズムを利用して画像毎に閾値を自動で検出させています。
algorithm.joho.info
3 分類してみる
訓練データと検証データの分類には「sklearnのtrain_test_split」を使いました。
多分kerasにも、同じようなメソッドがあると思います。
#訓練データと検証データに分ける x_train,x_test,y_train,y_test = train_test_split(np.array(hiragana_array),hiragana_label, test_size=0.2) #ひらがなに振った番号を0,1のarray形式にする y_train = to_categorical(y_train) y_test = to_categorical(y_test) #データの最小値が0、最大値が1になるように正規化する x_train = x_train/255 x_test = x_test/255 #ここからいよいよディープラーニングの設定 model = Sequential() model.add(Dense(256, input_dim=2304)) model.add(Activation("sigmoid")) model.add(Dropout(p=0.1)) model.add(Dense(74)) model.add(Activation("softmax")) sgd = optimizers.SGD(lr=0.1) model.compile(optimizer=sgd, loss="categorical_crossentropy", metrics=["accuracy"]) #Aidemyには無かった「EarlyStopping」を追加 es_cb = EarlyStopping(monitor='val_loss', patience=0, verbose=0, mode='auto') history = model.fit(np.array(x_train), np.array(y_train), batch_size = 500, nb_epoch = 100, verbose = 1, validation_data = (x_test, y_test), callbacks = [es_cb])
基本的にはAidemyの数字分類のコードと一緒ですが、過学習を防ぐために「EarlyStopping」を追加してあります。
参考にさせて頂いたサイト
blog.shoby.jp
ハイパーパラメーターについては正直私も勉強不足でよくわかりませんが、何度か数字を変えて試したところデータ数が少ないせいか上記の設定が一番精度が良かったです。
実行すると、以下のように徐々に精度が向上していく過程が見れます。
Train on 64000 samples, validate on 16000 samples Epoch 1/100 64000/64000 [==============================] - 13s - loss: 3.3197 - acc: 0.4270 - val_loss: 2.3735 - val_acc: 0.7947 Epoch 2/100 64000/64000 [==============================] - 13s - loss: 1.8139 - acc: 0.8273 - val_loss: 1.3459 - val_acc: 0.8924 Epoch 3/100 64000/64000 [==============================] - 12s - loss: 1.1035 - acc: 0.8992 - val_loss: 0.8846 - val_acc: 0.9276 Epoch 4/100 64000/64000 [==============================] - 12s - loss: 0.7821 - acc: 0.9212 - val_loss: 0.6636 - val_acc: 0.9342 Epoch 5/100 64000/64000 [==============================] - 13s - loss: 0.6126 - acc: 0.9309 - val_loss: 0.5355 - val_acc: 0.9402 Epoch 6/100 64000/64000 [==============================] - 12s - loss: 0.5089 - acc: 0.9379 - val_loss: 0.4552 - val_acc: 0.9459 Epoch 7/100 64000/64000 [==============================] - 12s - loss: 0.4396 - acc: 0.9422 - val_loss: 0.3987 - val_acc: 0.9477 Epoch 8/100 64000/64000 [==============================] - 14s - loss: 0.1414 - acc: 0.9701 - val_loss: 0.1438 - val_acc: 0.9686 Epoch 38/100 64000/64000 [==============================] - 15s - loss: 0.1389 - acc: 0.9703 - val_loss: 0.1423 - val_acc: 0.9683 Epoch 39/100 64000/64000 [==============================] - 14s - loss: 0.1374 - acc: 0.9700 - val_loss: 0.1402 - val_acc: 0.9701 Epoch 40/100 64000/64000 [==============================] - 13s - loss: 0.1355 - acc: 0.9705 - val_loss: 0.1389 - val_acc: 0.9695 Epoch 41/100 64000/64000 [==============================] - 14s - loss: 0.1339 - acc: 0.9711 - val_loss: 0.1379 - val_acc: 0.9698 Epoch 42/100 64000/64000 [==============================] - 14s - loss: 0.1319 - acc: 0.9713 - val_loss: 0.1368 - val_acc: 0.9696 Epoch 43/100 64000/64000 [==============================] - 12s - loss: 0.1303 - acc: 0.9714 - val_loss: 0.1354 - val_acc: 0.9692 Epoch 44/100 64000/64000 [==============================] - 13s - loss: 0.1290 - acc: 0.9715 - val_loss: 0.1345 - val_acc: 0.9693 Epoch 45/100 64000/64000 [==============================] - 13s - loss: 0.1269 - acc: 0.9721 - val_loss: 0.1328 - val_acc: 0.9696 Epoch 46/100 64000/64000 [==============================] - 12s - loss: 0.1259 - acc: 0.9722 - val_loss: 0.1319 - val_acc: 0.9701 Epoch 47/100 64000/64000 [==============================] - 14s - loss: 0.1249 - acc: 0.9719 - val_loss: 0.1307 - val_acc: 0.9703 Epoch 48/100 64000/64000 [==============================] - 14s - loss: 0.1234 - acc: 0.9730 - val_loss: 0.1305 - val_acc: 0.9703 Epoch 49/100 64000/64000 [==============================] - 13s - loss: 0.1224 - acc: 0.9725 - val_loss: 0.1280 - val_acc: 0.9712 Epoch 50/100 64000/64000 [==============================] - 15s - loss: 0.1209 - acc: 0.9729 - val_loss: 0.1268 - val_acc: 0.9706 Epoch 51/100 64000/64000 [==============================] - 15s - loss: 0.1193 - acc: 0.9732 - val_loss: 0.1279 - val_acc: 0.9704
4 結果の確認
こんな簡単にやってみたのですが、分類精度は97%と非常に高いです。
検証データの説明変数を出来たモデルに入れて予測したもの
np.argmax(model.predict(x_test[0:10]), axis=1) >array([18, 63, 39, 37, 60, 12, 6, 34, 50, 24])
検証データのラベル
#0,1のarrayにしているため1がいる位置を取得する print([list(y_test[i]).index(1) for i in range(10)]) >[18, 63, 39, 37, 60, 12, 6, 34, 50, 24]
始めの10件だけですが見事に一致しています。
ラベルだと何のひらがなか分からないので、配列から画像を復元してみましょう。
for i in range(5): plt.imshow(np.reshape(x_test[0 + i:1 + i]*255,(48,48,3)), cmap = 'gray', interpolation = 'bicubic') plt.xticks([]), plt.yticks([]) plt.show()
"U3057":{"し":18},
"U3088":{"よ":63},
"U306D":{"ね":39},
"U306B":{"に":37},
"U3082":{"も":60},
先ほど定義した辞書のラベルとも一致していますね!
いくつか他のkerasを利用した画像分類の事例を見てみましたが、今回精度が高かった要因は画像データが大体同じような形
(極端なイタリックやボールド、拡大縮小されていない)のため精度が高かったと考えられます。
最後に
ファンコミュニケーションズでは機械学習エンジニアを募集しています。
主な開発言語はScala, PHPですが、分析作業においてはPythonやRも多用しています。
また分析環境としてTreasure Dataを導入しているため、SQLによって学習データの作成・機械学習をシームレスに行うことができ、データクレンジングなどの雑務に追われることなく非常に大きなデータを扱える環境が整っています。
興味がある方は以下のページをご覧ください。(Webアプリケーションエンジニアと書いてありますが、機械学習エンジニアも含んでいますので安心してください。)