画像データからカタカナの判定
前回は手書き数字の判定をしてみましたが、今度は手書きのカタカナを判定してみましょう。数字に比べて文字種類が増えますが、大丈夫でしょうか。また、今回は最初に大量の PNG画像を用意して、画像ファイルからディープラーニングを実践する方法も解説します。前回の記事をご覧になっていない方はこちらをまずは一読ください。
機械学習の入力カと出力の説明
本記事で作成するプログラムでは、手書きのカタカナ画像の一覧を読み込みます。カタカナを学習し、どれくらいの精度で手書きカタカナを判定できるかを出力します。
ETL文字データベースを利用しよう
さて、ここではカタカナの判定に挑戦しますが、そのために、産業技術総合研究所が公開している「ETL文字データベース」を利用してみましょう。ETL文字データベースは、手書きまたは印刷の英数字、記号、ひらがな、カタカナ、教育漢字、JIS 第1水準漢字など、約 120万の文字画像データを収集したデータベースです。以下の Web サイトで公開されています。
ETL文字データベース
[URL]http://etlcdb.db.aist.go.jp/?lang-ja
なお、このデータベースは完全無料というわけではなく、研究用途に限り無料で使用できるというものなので、商用利用が必要な場合は別途問い合わせが必要となります。
ETL文字データベースのダウンロード
データをダウンロードするには、フォームへの入力が必要です。画面上部にある「Download」のリンクをクリックします。表示されたフォームに、使用目的やメールアドレスを入力して、「送信」ボタンをクリックします。すると、入力したメールアドレス宛てに、ダウンロードのURLとパスワードが送られてきます。その指示に従って Web ブラウザーでURL を開くと、以下のようなダウンロードページが表示されます。ETLの文字種類に応じて、データベースをダウンロードできます。ここでは、ETL-1の手書き文字データベースを利用します。これは、99個の文字(数字:10, 英大文字:26, 特殊文字:12, カタカナ:51)を含む手書き文字のデータセットです。ダウンロードページから「ETL-1」のリンクを選んでダウンロードしてください。ダウンロードしたZIP ファイルを解凍すると、ETL1 というディレクトリーに 14個ファイルができます。このETL1というディレクトリー(とそれ以下にあるファイル)をJupyter Notebook の実行ディレクトリーにコピーしましょう。
データベースを画像に変換しよう
前回の記事に掲載したMNIST のデータセットは、すでにライブラリーの Keras により、Pythonからわかりやすい形式で利用できるように加工されたものでした。しかし、これから機械学習を実践するとき、Python から扱いやすい形式でデータセットが用意されていることはまれでしょう。そこで今回は、最初にETLの独自形式のデータベースを読み込んで、画像として出力してみましょう。その後で、画像を読み込んで機械学習を実践します。それでは、ETL1のデータベース (ETL1ディレクトリー)を、Jupyter Notebook のカレントディレクトリーに配置した上で、以下のプログラムを実行してみましょう。
# ETL1のファアイルを読み込む
import struct
from PIL import Image, ImageEnhance
import glob, os
# 出力ディレクトリー
outdir =”png-etl1/”
if not os.path.exists(outdir): os.mkdir(outdir)
# ETL1 ディレクトリー以下のファイルを処理する(*1)
files=glob.glob(“ETL1/*”)
for fname in files:
if fname==”ETL1/ETL1INFO”: continue # 情報ファイルは飛ばす
print (fname)
# ETL1のデータファイルを開く(*2)
f = open (fname, ‘rb’)
f.seek (0)
while True:
# メタデータ+画像データの組を1つずつ読む(*3)
s=f.read(2052)
if not s: break
# バイナリデータなのでPython が理解できるように抽出(*4)
r = struct.unpack(‘>H2sH6BI4H4B4x2016s4x’, s)
code_ascii=r[1]
code_jis = r[3]
# 画像データとして取り出す(*5)
iF = Image.frombytes(‘F’, (64, 63), r[18], ‘bit’, 4)
iP = iF.convert(‘L’)
# 画像を鮮明にして保存
dir = outdir + “/” + str(code_jis)
if not os. path.exists(dir): os.mkdir(dir)
fn =”{0:02x}-{1:02x}{2:04x}.png”.format(code_jis, r[0], r[2])
fullpath=dir + “/” + fn
if os.path.exists(fullpath): continue
enhancer =ImageEnhance.Brightness(iP)
iE=enhancer.enhance (16)
iE.save (fullpath, ‘PNG’)
print (“ok”)
すると、以下のように手書き文字のIS コードごとに大量の画像が生成されます。作成されたディレクトリーを見てみると、177 から221のディレクトリーに「ア」から「ン」が、166に「ヲ」の画像データが収録されているのを見ることができます。各ディレクトリーには、1411個ずつの画像が収録されています。これらの画像を用いて機械学習でカタカナの判定を行いましょう。一応、プログラムを確認してみましょう。プログラムの(※1)では、ETL1 というディレクトリー以下に配置されたファイルをすべて処理します。このように glob モジュールを使うと、ファイルの一覧を手軽に取得できます。(※2)の部分では、ETL1 のデータファイルを開いて、1つずつデータを読んでいきます。(※3)の部分を見るとわかる通り、各データは 2052 バイトの固定長となっています。また、1つのデータは、画像データだけでなく、メタデータ(画像の説明)と画像データの組となっています。また、(※4)の部分では、読み出したバイナリデータをPython が理解できるように、struct モジュールの unpack0 メソッドを利用して、データを意味のある単位ごとに抽出します。その後、(※5)の部分で、画像部分を取り出したら、画像を鮮明にして PNG 画像としてファイルに保存します。
画像を学習させよう-画像リサイズ
画像を学習させるにあたって、まずは画像データを必要最低限のサイズに縮小してから学習することにしましょう。以下のプログラムをJupyter Notebookで実行してみましょう。すると、全画像を25× 25に縮小し、リサイズ後のバイナリデータを1つのファイルにまとめて「png-et1/katakana.pickle」に保存します。
import glob
import numpy as np
import cv2
import matplotlib.pyplot as plt
import pickle
# 保存先や画像サイズの指定(*1)
out_dir = “./png-et11” # 画像データがあるディレクトリー
im_size= 25 # 画像サイズ
save_file = out_dir + “/katakana.pickle” # 保存先
PIt.figure(figsize=(9, 17))# 出力画像を大きくする
#カタカナの画像が入っているディレクトリーから画像を取得(*2)
kanadir == list(range (177,220+1))
kanadir.append (166) #ヲ
kanadir.append (221) # ン
result =[]
for i, code in enumerate (kanadir):
img_dir = out_dir + “/” + str(code)
fs =glob.glob(img_dir + .”/*”)
print (“dir=”, img_dir)
# 画像を読み込んでグレースケールに変換しリサイズする(*3)
for j, f in enumerate(fs):
img=cv2. imread (f)
img_gray=cv2.cvtColor (img, cv2.COLOR_BGR2GRAY)
img=cv2.resize(img_gray, (im_size, im_size))
result.append ([i, img])
# Jupyter Notebook で画像を出力
if j==3:
plt.subplot(11, 5, i + 1)
plt.axis (“off”)
plt.title(str(i))
plt.imshow (img, cmap=’gray’)
# ラベルと画像のデータを保存(*4)
pickle.dump (result, open(save_file, “wb”))
plt.show()
print(“ok”)
また、プログラムを実行したとき、どのような画像があるのかを表示するようにしてみました。プログラムを実行すると、次のように「ア」から「ン」までの画像が表示されます。
プログラムを確認してみましょう。プログラムの(※1)の部分では、保存先や画像サイズを指定します。また、plt.figure() を指定して、Jupyter Notebook で実行したとき、出力画像が大きめに表示されるように指定します。次に(※2)の部分では、カタカナの画像が入っているディレクトリー(文字コードがディレクトリー名になっている)を指定して、カタカナ画像を読み込みます。(※3)の部分では、カタカナ画像を読み込んだら、グレースケールに変換し、画像をリサイスして画像データを読み込みます。そして、最後(※4) の部分では、読み込んだ画像データおよびラベル情報をファイルへ保存します。
なお、今回 Pythonのデータ構造をファイルへ保存するpickle モジュールを利用して、読み込んだ画像データをファイルへ保存します。つまり、Python から手軽に利用できるカタカナ画像のデータセットを作成したことになります。画像を縮小していますが、画像数が多いので、48MB ものファイルが生成されます。
データを学習しよう
先ほど作成した、Python 用のカタカナ画像データセットを読み込んだら、さっそく簡単なニューラルネットワークのモデルを使ってテストしてみましょう。どれくらいの精度が出るでしょうか。
import numpy as np
import cv2, pickle
from sklearn.model_selection import train_test_split
import keras
# データファイルと画像サイズの指定(*1)
data_file = “./png-etl1/katakana.pickle”
im_size = 25
in_size= im_size * im_size
out_size = 46 # ア-ンまでの文字の数
# 保存した画像データー覧を読み込む(*2)
data=pickle.load (open (data_file, “rb”))
# 画像データを0-1の範囲に直す(*3)
y = []
x=[]
for d in data:
(num, img)=d
img=img.reshape (-1).astype (‘float’) / 255
y.append (keras.utils.to_categorical (num, out_size))
- append (img)
x=np. array (x)
y = np.array (y)
# 学習用とテスト用に分離する(*4)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.2, train_size = 0.8, shuffle = True)
# モデル構造を定義(*5)
Dense = keras.layers.Dense
model = keras.models.Sequential()
model.add (Dense (512, activation=’relu’, input_shape%3D(in_size,)))
model.add (Dense (out_size, activation=’softmax’))
# モデルを構築して学習を実行(*6)
model.compile(
loss=’categorical_crossentropy’,
optimizer=’adam’,
metrics=[‘accuracy’])
model.fit(x_train, y_train,
batch_size=20, epochs=50, verbose=1,
validation_data=(x_test, y_test))
# モデルを評価
score = model.evaluate(x_test, y_test, verbose=1)
print (‘ 正解率=’, score [1], ‘loss=’, score[0])
プログラムを実行すると、以下のように表示されます。正解率は、0.9(90%) になりました。何もチューニングしていない割には、なかなかの数値が出たように思います。
正解率= 0.9020827306913509 loss= 0.5594334717070005
プログラムを確認してみましょう。プログラムの(※1)の部分では、データファイルと画像サイズなどの情報を指定します。(※2)の部分では、pickle モジュールを利用して、前回のプログラムで作成したカタカナ画像のデータセットを読み込みます。プログラムの(※3)の部分では、1.0 までの実数に変換します。そして、(※4)の部分では、学習用データとテスト用データに分割します。これで、機械学習を行うための準備が整いました。(※5)の部分で、ニューラルネットワークのモデル構造を定義します。このモデルは、入力層から512個のユニットを経由して出力層に出力するだけの簡単なモデルです。(※6)の部分では、モデルを構築して学習を行います。読み込んだ画像データを1つずつ一次元の配列に展開し、0.0から
CNN で学習しよう
次に、判定精度を向上させるために、畳み込みニューラルネットワーク (CNN)を利用して機械学習を実践してみましょう。CNNについては、すでに何度か紹介しているので、ここでは精度が向上することを確認しましょう。また、CNN を行うために画像データをどのようにモデルの構造に合わせるのかも確認しましょう。
import numpy as np
import cv2, pickle
n sklearn.model_selection import train_test_split
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.optimizers import RMSprop
from keras.datasets import mnist
import matplotlib.pyplot as plt
# データファイルと画像サイズの指定
data_file = “./png-etl1/katakana.pickle”
im_size= 25
out_size = 46 # ア-ンまでの文字の数
im_color=1# 画像の色空間/ グレースケール
in_shape=(im_size, im_size, im_color)
# カタカナ画像のデータセットを読み込む(*1)
data=pickle.load(open (data_file, “rb”))
# 画像データを変形して0-1の範囲に直す(*2)
x=[]
y=[]
for d in data:
(num, img)=d
img=img.astype(‘float’).reshape(
im_size, im_size, im_color) / 255
y.append (keras.utils.to_categorical(num, out_size))
х.append (img)
x = np. array(x)
y = np.array(y)
# 学習用とテスト用に分離する
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.2, train_size = 0.8, shuffle = True)
# CNN モデル構造を定義(*3)
model=Sequential ()
model.add(Conv2D(32,
kernel size3D(3, 3),
activation=’relu’,
input_shape=in_shape))
model.add (Conv2D (64, (3, 3), activation=’relu’))
model.add (MaxPooling2D (pool_size=(2, 2)))
model.add (Dropout (0.25))
model.add (Flatten())
model.add (Dense (128, activation=’relu’))
model.add (Dropout (0.5)).
model.add (Dense (out_size, activation=’softmax’))
model.compile(
loss=’categorical_crossentropy’,
optimizer=RMSprop(),
metrics=[‘accuracy’])
# 学習を実行して評価(*4)
hist = model.fit(x_train, y_train,
batch_size=128,
epochs=12,
verbose=1,
validation_data=(x_test, y_test))
# モデルを評価
score = model.evaluate (x_test, y_test, verbose=1)
print (‘正解率 =’, score[1], ‘loss=’, score [0])
# 学習の様子をグラフへ描画(*5)
# 正解率の推移をプロット
plt.plot (hist.history[‘accuracy’])
plt.plot (hist.history[‘val_accuracy’])
plt.title(‘Accuracy’)
plt.legend ([‘train’, ‘test’], loc=’upper left’)
plt.show()
# ロスの推移をプロット
plt.plot (hist.history[‘loss’])
plt.plot (hist.history [‘val_loss’])
plt.title(‘Loss’)
plt.legend([‘train’, ‘test’], loc=’upper left’)
plt.show ()
プログラムを実行してみましょう。以下のように表示されます。
正解率=0.9608041654613827 loss= 0.1766713881873592
CNNを使ったところ、正解率は 0.96(96%) に向上しました。先ほどの0.9(90%) と比べると大幅な精度向上に成功しました。続いてプログラムを確認してみましょう。プログラムの(※1)の部分でカタカナ画像データセットを読み込みます。今回のポイントとなるのが、(※2)の部分です。ここでは、画像データを CNNのモデルで読み込めるように次元変換を行います。1つ前のプログラムでは、1つの画像を一次元配列に変換していましたなお、どのように次元数を変換したのかを確認するには、NumPy の配列オブジェクトの shape プロパティを確認します。たとえば、Jupyter Notebook で今回のプログラムを実行した後で、以下のようにが、ここでは(幅,高さ,色空間数)となるように次元数を変換します。実行してみましょう。
x_train.shape
すると、(69137, 25, 25, 1)のように表示されることでしょう。つまり、(画像数,画像幅,画像高さ,色数)の次元を持つ配記列になっていることを表しています。CNN を実践する際には、このように次元が復雑になりがちなので、注意する必要があります。プログラムの続く(※3)の部分では、CNN モデル構造を定義して、(※4)で学習を実行して結果を表示します。また、(※5)の部分では、学習の様子をグラフにプロットして表示します。
CNNの威力を実感
さて、ここでは畳み込みニューラルネットワーク (CNN)を利用した機械学習を実践しました。やはり、画像の判定を行うプログラムでは、CNNを使うと高い精度を出すことができます。今回も、簡単なニューラルネットワークでは精度が 0.9程度でしたが、CNNを使うことで0.96 の精度を出すことができました。
改良のヒント
以上、ETLの手書きカタカナ画像データセットを利用して、画像判定を実践してみました。今回は、カタカナ画像だけでしたが、ETLでは、英数字や漢字など、いろいろな文字種類のデータを提供しています。それらを利用して、より自然な画像認識ができるように挑戦するのも良いでしょう。また、今回はETLが用意している 25 × 25 ピクセルの画像のみ利用しましたが、画像の水増しテクニックを使ったり、画像のノイズを除去してから機械学習を行うなど、データを工夫することで、さらなる精度向上が期待できるでしょう。
この節のまとめ
・ETL文字データベースは日本語の手書きデータを数多く収録している
・カタカナのように文字種類が多くても、画像データの種類が多ければ高い精度で文字認識を行うことができる
・CNN を使うと学習に時間がかかるが判定精度は高い