non-techがCNNで画像認識にチャレンジした
0.はじめに
これは機械学習初心者の私が、AIに特化したプログラミングスクール "Aidemy" で3ヶ月間学習した成果として、CNN(Convolutional Neural Network)で画像分類AIモデルを作成した結果をまとめています。
かなり初心者向けの内容となってはいますが、『non-techな人が3ヶ月でどの程度までAIモデルを扱えるようになるのか?』という観点で一つの目安にしていただけるかと思いますので、興味ある方は最後まで読んでいただけるかと幸いです。
参考までに学習開始時点の私の経験値は下記のような感じです。
・理系大学院卒(非情報系、研究のツールとしてプログラミング経験あり)
・新卒外資IT企業でコンサルタントからのAIベンチャーに転職(non-tech)
・AIの知識なし
・初Python
1. 題材
今回は画像認識ジャンルの比較的難易度が低いテーマとして、SIGNATEから下記データセットを選択しました。
2. 実行環境
以下の実装や推論の実行は、すべてブラウザ上から実行可能で無料でGPUが利用できるPython開発環境である「Google Colaboratory」上で実施しました。
3. 実装内容
では実際にどのような流れでAIモデル完成に至ったかを順を追ってご説明します。
3.1 画像データ
まずは上記SIGNATEサイトから下記ファイルをダウンロードしました。
学習用画像データ(train_data)内の画像ファイルは名前を確認すると、"ok"が入っているものは正常品、"def"が入っているものは欠陥品となっていますので、下記のような形でフォルダ分けして、評価用画像データ(test_data)とともに作業領域に配置しました。
3.2 AIモデル構築方針
AIモデルを作成するにあたって、画像認識分野で多く用いられているCNNを利用することにしましたが、用意されている学習用画像データが合計250枚と比較的少ないため、何らかの対策が必要であると考えました。
学習用データが少ない場合のよくある対応策は下記2点です。
・データを水増し
・転移学習←今回はこっち
※転移学習とはすでに学習済みのAIモデルを利用して、少ない学習データでも短い時間で高い精度を出すことができる手法です。
今回のモデルでは世界的に利用実績がとても多く、金属部品の画像認識においても利用実績がある、VGG16を利用することとしました。
VGG16とは
VGG16というのは,「ImageNet」と呼ばれる大規模画像データセットで学習された16層からなるCNNモデルです。
引用元: VGG16モデルを使用してオリジナル写真の画像認識を行ってみる
3.3 データ準備
まずは3.1で画像データを配置したディレクトリを指定し、画像ファイルの名前をリストとして取得。
# 学習用データを配置したディレクトリを指定しファイル名リストを取得
path_def = os.listdir('/content/drive/MyDrive/鋳造製品の欠陥検出/train_data/def')
path_ok = os.listdir('/content/drive/MyDrive/鋳造製品の欠陥検出/train_data/ok')
画像を格納するための空のリストを、欠陥品と正常品それぞれで作成し、上の名前リストの順に空のリストに追加。
# 欠陥品、正常品それぞれの画像を格納するためのリストを作成
img_def = []
img_ok = []
# リストに各画像を格納
for path in path_def:
img = cv2.imread('/content/drive/MyDrive/鋳造製品の欠陥検出/train_data/def/' + path)
img_def.append(img)
for path in path_ok:
img = cv2.imread('/content/drive/MyDrive/鋳造製品の欠陥検出/train_data/ok/' + path)
img_ok.append(img)
欠陥品と正常品の画像リストを結合し、画像リストXを作成。Xに対応する形でリストyに欠陥品には0、正常品には1のラベルを付与。
# 画像リストを結合
X = np.array(img_def + img_ok)
# 欠陥品は0、正常品は1のラベルを付与
y = np.array([0]*len(img_def) + [1]*len(img_ok))
学習用データとして、Xとyのリスト内は欠陥品と正常品がランダムに並んでいることが好ましいため、インデックスを操作してランダムな順番に変更。
# ランダムなインデックスを振り順番を変更
rand_index = np.random.permutation(np.arange(len(X)))
X = X[rand_index]
y = y[rand_index]
学習用データと検証用データを8:2に分けデータの準備は完了。
# データの分割
X_train = X[:int(len(X)*0.8)]
y_train = y[:int(len(y)*0.8)]
X_test = X[int(len(X)*0.8):]
y_test = y[int(len(y)*0.8):]
3.4 モデル構築
モデルは前述の通りVGG16を利用し、全結合層のみFine Tuningする形としました。
今回の画像データはすべて300*300pixで、カラーはGBRで定義(3チャンネル)。また、全結合層はFine Tuningするため'include_top=False'と定義。
# vgg16のインスタンスの生成
input_tensor = Input(shape=(300, 300, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)
全結合層は下記のとおり。ドロップアウトは50%、かつ2クラス分類のため出力は1つで定義。
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(rate=0.5))
top_model.add(Dense(1, activation='sigmoid'))
VGG16に作成した全結合層を連結し、全結合層以外の学習の重みが更新されないよう設定。損失関数は2クラス分類のため、バイナリ交差エントロピーを選択、最適化はAdamを選択し、学習率を0.0001に設定。
# モデルの連結
model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))
# vgg16の重みの固定
for layer in model.layers[:19]:
layer.trainable = False
# モデルをコンパイル
model.compile(loss='binary_crossentropy',
optimizer=optimizers.Adam(learning_rate=0.0001),
metrics=['acc'])
モデルのサマリを表示した結果は下記のとおり。
model.summary()
3.5 学習
モデルの定義は完了しましたので、実際に学習データを読み込ませて見ましょう。
下記のような形で学習を実行しました。
result = model.fit(X_train, y_train, batch_size=16, epochs=10, validation_data=(X_test, y_test))
学習の推移は下記のような形となりました。
エポック数ごとのaccuracy(上)とloss(下)の推移を示しています。学習回数を重ねるごとに、学習用データと検証用データどちらでもaccuracyが高くなり、lossも安定して低く推移していることから、効率よく学習ができていると見られます。
3.6 推論
良さげなAIモデルができましたので、早速評価用画像データで推論を実行してみましょう!
サンプル提出ファイルを読み取り、0列の画像ファイル名を取得します。画像ファイルを一つずつ推論し、結果が0.5以上であれば1、0.5未満であれば0に変換してリストに格納。
# サンプル提出ファイルの読み取り
submission = pd.read_csv("/content/drive/MyDrive/鋳造製品の欠陥検出/sample_submission.csv", header=None)
# 画像ファイル名を取得
images = submission[0]
r = []
# 評価用画像を推論し、結果から0,1に2値化
for image in images:
img = cv2.imread('/content/drive/MyDrive/鋳造製品の欠陥検出/test_data/' + image)
pred = np.amax(model.predict(img.reshape(1,300,300,3)))
if pred >= 0.5:
r.append(1)
else:
r.append(0)
提出用サンプルファイルに推論結果リストを追記し提出ファイルが完成。
# 提出ファイルを出力
submission["result"] = r
submission = submission.drop(1, axis=1)
submission.to_csv("first_submit.csv", index=False, header=None)
SIGNATEのページで作成した提出ファイルを提出した結果は下記のようになりました。
かなり良い結果!と言いたいところですが、97%以上の精度の方が80名程度いらっしゃいました。まだまだ改善の余地がありそうですね。
4. 結果
結果として十分な精度が期待できるAIモデルを作成することができました。ただし、さらに高い精度を出しているケースも多くありましたので、まだ改善できる余地はあるものと考えられます。ハイパーパラメータの調整や、画像のグレースケール化など、今後試していきたいと思います。
5. まとめ
10年以上前にFortranなんて化石言語を触っていた人間からしてみると、Pythonのライブラリの充実ぶりや書きやすさは同じプログラミング言語とは思えないほどでした。
今後もコンペに参加しながら自らのスキルを磨いていきたいと考えていますので、同じような考えの方がいらっしゃったらコメントください!
以上最後までご覧いただきましてありがとうございました!
この記事が気に入ったらサポートをしてみませんか?