アウトプットブログ

主にプログラミングで学んだことをアウトプットします。

畳み込みニューラルネットワークを用いた画像分類

fuhito615.hatenablog.com
以前、ニューラルネットワークを自前で実装し、ぷよぷよの画像を使用して画像分類を行いましたが、画像の平均色のみをインプットとして分類を行ったため、一部上手く分類できていないものもありました。


今回はPythonを使用し、畳み込みニューラルネットワークを用いて画像分類を行います。

畳み込みニューラルネットワーク

「畳み込み層」と「プーリング層」とよばれる層をもつニューラルネットワークです。
「畳み込み層」では直線や曲線、円や四角などの形を取り出す「空間フィルター」を用いて、画像の特徴を捉えます。
「プーリング層」では、小さい範囲での最大値(または平均値)を抜き出して処理することで、位置ずれに対する頑強さを持たせます。
それぞれの仕組みの詳細は割愛しますが、これらの技術によって画像認識が成り立っています。

実装

今回は流石に自前で実装できそうになかったので、Pythonを用いました。

①テストデータの作成

(1)ぷよぷよのゲーム画面を切り抜き、120px×240pxにサイズ変更
(2)6×12の72分割して1マス分の画像を作成
(3)ぷよぷよの種類ごとにフォルダ分け
上記をひたすら繰り返しました。
最終的にテストデータの画像は3,168枚(44画面分)となりました。
f:id:fuhito615:20220114231355p:plain


集めた画像から、データセットを作成。

# 定数
LABELS = ["none", "green", "red", "blue", "yellow", "purple", "ojama"]

# データセットの作成
datasets = []

cwd = os.getcwd()

for label_num, label in enumerate(LABELS) :
    path = os.path.join(cwd, "datasets", label)
    
    for image_name in os.listdir(path) :
        image_array = cv2.imread(os.path.join(path, image_name))
        datasets.append([image_array, label_num])

random.shuffle(datasets)


データセットを確認(先頭の50枚)。

# データセットの確認
for i in range(50) :
    plt.subplot(5, 10, i+1)
    plt.axis('off')
    plt.imshow(cv2.cvtColor(datasets[i][0], cv2.COLOR_BGR2RGB))
    plt.title(label = datasets[i][1])

f:id:fuhito615:20220114232252p:plain


訓練データとテストデータに分割。

# 訓練データ、テストデータの作成
X = []
Y = []

for image_array, label_num in datasets :
    X.append(image_array)
    Y.append(label_num)

X = np.array(X)
Y = np.array(Y)

X = X.astype("float32")/255.0  # 0~255 → 0.0~1.0
Y = to_categorical(Y, 7)       # one-hot

X_train, X_test, Y_train, Y_test = train_test_split(X, Y)

②畳み込みニューラルネットワークの作成

技術書などを参考にしていると、
畳み込み層→畳み込み層→プーリング層→畳み込み層→畳み込み層→プーリング層・・・
と、かなり層を重ねて実装しているものが多くありましたが、どのように層を積み重ねていくのがベストなのかがまだよく分からず、テストデータの画像も殆ど同じ画像で複雑さも無さそうなので、シンプルな実装で試しました。


1層目 入力層(20×20×3)
2層目 畳み込み層(3×3のカーネル16枚)
3層目 プーリング層(2×2の最大プーリング、ドロップアウト0.25)
4層目 全結合(512、ドロップアウト0.5)
5層目 出力層、全結合(7)

# モデルの作成
model = Sequential()

model.add(Conv2D(16, (3, 3), activation='relu', padding='same', input_shape=(20, 20, 3)))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(7, activation='softmax'))

# コンパイル
model.compile(loss='categorical_crossentropy', optimizer=Adam(learning_rate=0.001), metrics=['acc'])

③学習

# 学習
history = model.fit(X_train, Y_train, batch_size=128, epochs=20, validation_split=0.1)

# 学習結果、グラフ表示
plt.plot(history.history['acc'], label='training acc')
plt.plot(history.history['val_acc'], label='validation acc')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(loc='best')
plt.show()

f:id:fuhito615:20220114233644p:plain


すぐに正答率が100%近くになりました。
やはり画像が単純なので、すぐに学習完了するんでしょうかね。

④評価

テストデータで評価します。

# 評価
test_loss, test_acc = model.evaluate(X_test, Y_test)

出力

25/25 [==============================] - 0s 5ms/step - loss: 0.0111 - acc: 0.9987


テストデータで不正解だったデータを確認。

# 不正解データを表示する
ng = []
predicts = model.predict(X_test)

for i, predict in enumerate(predicts) :
    if predict.argmax() != Y_test[i].argmax() : ng.append(i)

for i in range(len(ng)) :
    plt.subplot(1, len(ng), i+1)
    plt.axis('off')
    plt.imshow(cv2.cvtColor(X_test[ng[i]], cv2.COLOR_BGR2RGB))
    title = "pre:" + str(predicts[ng[i]].argmax()) + ", ans:" + str(Y_test[ng[i]].argmax())
    plt.title(label = title)

f:id:fuhito615:20220114234135p:plain
テストデータの1つが不正解でした。
AIの答えは"0"(ぷよなし)、正解は"1"(みどりぷよ)。
エフェクトが被ったマスっぽいので仕方がないといえば仕方がないのかもしれないですね。

おわりに

畳み込みニューラルネットワークの仕組みを、ある程度知ることができました。
凄く暇があれば、畳み込みニューラルネットワークも自前で実装してみるのも面白そうですね。
(今のところは全くやる気はないですが)


もう少し複雑な画像分類なども試してみて、どのようにネットワークを構築するのがいいのか経験していくのも必要だと感じました。


今回は以上です。

参考にさせていただいた資料

例によって、ヨビノリさんの動画で学習しました。
www.youtube.com


また、以下の技術書も参考にしました。
伊藤真Pythonで動かして学ぶ!あたらしい機械学習の教科書」,翔泳社
布留川英一「AlphaZero 深層学習・強化学習・探索 人工知能プログラミング実践入門」,株式会社ボーンデジタル