4 手書き文字の認識
4.1 データの読み込み
画像を機械学習し、その画像が何であるかを判定することもできます。例えば、工場で生産品の画像が正常か異常かを判定する、などです。
ここでは手書きで描いた数字の画像データを読み込んでそれが何という数字を書いたかを認識(予測)してみます。
機械学習の練習で使う有名なデータセットがいろいろありますが、scikit-learn本体にそのようなデータセットがいくつか内蔵されています。そのうちの一つがこの数字の手書き画像です。
さっそく、読み込んでみましょう。今までと異なり、csvファイルからではなく、scikit-learnのdatasetsから読み込むことが出来ます。 ファイル digits.ipynb に以下を記述します。
from sklearn import datasets
digits = datasets.load_digits() # 手書き数字データ読み込み
読み込んだ変数 digitsの images にデータが格納されています。 いくつあるかを表示してみましょう。
# データ数
len(digits.images)
最初のデータを表示してみます。
# 0番目のデータ
digits.images[0]
すると二次元配列になっており、0~15までの数が入っています。これが手書き文字の画像です。データは縦8ドット×横8ドットで、数字が大きいほど色が濃いドットを表しています。0以外のところを見るとなんとなく数字の0を表しているように見えます。
この数字が何を表しているかは、digits.target に答えが入っています。
# 0番目の答え
digits.target[0]
4.2 画像の表示
しかし、数字ではわかりにくいので画像で表示してみましょう。 matplotlibでimshow関数にこのデータを渡すと画像として表示されます。
import matplotlib.pyplot as plt
# 画像の表示
plt.imshow(digits.images[0], cmap="gray")
引数にcmap="gray"を付けて灰色にして表示しています。
for文を使って最初の15個を表示してみましょう。 matplotlibのsubplot機能を使って3行×5列の複数の画像を表示してみます。
# 最初の15個を表示
for i in range(15):
plt.subplot(3, 5, i+1) # 表示場所
plt.axis("off")
plt.title( str(digits.target[i]) ) # タイトルに答えを表示
plt.imshow( digits.images[i], cmap="gray" )
4.3 モデルの構築と学習
では、これを教師データとして、特徴量 x と正解ラベル y に入れましょう。
x = digits.images
y = digits.target
ただし、特徴量 x は一次元リストのリストである必要があります。今の段階では8×8の二次元データのリストになっていますので、これを64個の一次元のリストに変換します。
# 二次元のリストを一次元のリストに変換
x = x.reshape(-1, 64)
次に学習用データとテスト用データに分割します。
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0)
学習を行います。今回も決定木を使用してみましょう。
# 学習
from sklearn import tree
model = tree.DecisionTreeClassifier(random_state=0)
model.fit(x_train, y_train)
4.4 評価の表示
では、どれぐらい正解率があるかを出してみましょう
model.score(x_test,y_test)
80%台は出たのでは無いかと思います。まずまずの結果です。
どの文字が間違いが多かったでしょうか。予測した結果と正解とを比較してみます。
# 予測した結果を predicted に入れる
predicted = model.predict(x_test)
# y_test と比較した結果の表示
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
cm = confusion_matrix(y_test, predicted)
disp = ConfusionMatrixDisplay(confusion_matrix=cm)
disp.plot()
結果は一番上の行が「0が何と予測されたか」の件数が入っています。左から0と予測されたもの、1と予測されたもの、2と予測されたものです。
実際にどの画像が何と予測されたか、画像と照らしあわせて最初の15個を表示してみます。
for i in range(15):
# テストデータ(8x8の2次元に戻す)
image = x_test[i].reshape(8,8)
seikai = y_test[i]
yosoku = predicted[i]
plt.subplot(3, 5, i + 1)
plt.axis('off')
plt.title(f'{seikai}:{yosoku}')
plt.imshow(image, cmap="gray")
4.5 過学習
決定木の階層を表示してみましょう。
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(font=["Meiryo"])
tree.plot_tree(model, filled=True)
plt.show()
このとき、木の階層が下に何階層も伸びていることが分かります。 この階層の最大値は、モデル作成時にmax_depthで指定することが出来ます。
# 3階層に制限した決定木
model = tree.DecisionTreeClassifier(max_depth=3)
max_depthを大きくすると階層が深くなり、それだけたくさんの判断を重ねることができます。しかし、深すぎても良い結果は得られなくなります。 なぜなら過学習を起こすからです、
過学習とはあまりにも学習データに最適化しすぎて未知のデータの判断を誤ってしまうことをいいます。学習しすぎてもいけないということです。
それを確認してみましょう。max_depthを1から初めて14まで増やしてみます。 結果をDataFrameに格納し表示してみます。
import pandas as pd
dfk = pd.DataFrame(columns=['学習用', 'テスト用'])
for n in range(1,15):
model = tree.DecisionTreeClassifier(max_depth=n, random_state=0)
model.fit(x_train, y_train)
# 学習データを予想した結果
score_train = model.score(x_train,y_train)
# テストデータを予想した結果
score_test = model.score(x_test,y_test)
dfk.loc[n] = [score_train, score_test]
dfk
max_depthを増やしていくと学習用もテスト用も順調に正解率が伸びることが分かります。しかし、max_depthが12ぐらいになると学習用データについては正解率は伸びますが、テスト用データでは逆に下がっています。
モデルが学習用データに最適化しすぎて、テスト用データで正しく判断できなくなっていることが分かります。
過学習を起こさないようにすることが汎用的に正解率を上げるポイントです。
4.6 ランダムフォレスト
決定木では80%台の正解率でした。これをもっと上げたいところです。 そこで、より強力なアルゴリズムを使用します。それがランダムフォレストです。ランダムフォレストは決定木を複数生成します。そして、その決定木で出た結果で最も多いものを予測結果とします。
初期設定では決定木を100個作成します。もちろん、同じ判断基準では同じ結果になってしまいますので、違う判断が出来るようにランダムで判定基準を作成します。
では、使用してみましょう。
# ランダムフォレスト
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(random_state=0)
model.fit(x_train, y_train)
model.score(x_test,y_test)
ランダムフォレストでは、90%以上の結果が出るはずです。 なお、ランダムフォレストでも、max_depthを指定できます。また、n_estimatorsで決定木の数を指定できます。
それぞれの決定木を可視化したい場合、model.estimators_ に決定木のリストが入っています。
plt.subplots(figsize=(16, 12))
# 最初の決定木
tree.plot_tree(model.estimators_[0], filled=True)
plt.show()