はじめに
初めまして、Yukimuraと申します。
ネットワークエンジニア(非AI)をしていますが、AIエンジニアをやりたいなと思っている今日この頃です。
E資格やDS検定(リテラシーレベル)は取得済みのため、AIに関する基本的な知識はありますが、実践力を身に着けたく、AidemyさんのAIアプリ開発講座を受講しました。
本記事の概要
本記事は、AIアプリ開発講座の集大成として、オリジナルアプリ(画像認識)を作成したことについてです。
オリジナルアプリのネタは色々考えたところ、実家が農家のため、野菜識別器とかいいんじゃないかなと思い、それに決めました。
作成した野菜識別器は、以下になります。
実は、私よりも以前に同じ講座を受講されていた方が、野菜識別のアプリを作られていました。
なので、以下の記事はとても参考になりました。
(特にソースコードの解説が秀逸です。)
偉大な先輩、ありがとうございました。
では、どんなことをしたのかを、具体的に書いていきます。
作業環境
・Windows 11 PC
・Google Colaboratory
ターゲット選定
すぐに作業に取り掛かりたい気持ちを抑えつつ、まずはターゲットを決めなければいけません。
画像から野菜を識別するのは決まっていますが、じゃあ一体何の野菜を採用するのか。
最初は「野菜 一覧」とかでググりながら、「野菜って色々あるなぁ、これなら50種類くらいいけるかな」などと命知らずなことを考えていました。
そんな時間はないだろう、とふと冷静になり、結局30種類を選び、更に画像の集まり具合から最終的に20種類に絞りました。
最終的に採用した野菜20種
アスパラガス、枝豆、かぼちゃ、キャベツ、きゅうり、小松菜、さつまいも、ジャガイモ、春菊、セロリ、玉ネギ、大根、トマト、なす、にんじん、にんにく、長ネギ、ピーマン、ほうれん草、レタス
今回は採用を見送った野菜10種
オクラ、ごぼう、ショウガ、とうもろこし、ニラ、白菜、パセリ、ブロッコリー、もやし、れんこん
画像収集
「icrawler」というwebから画像収集をしてくれる便利なツールがあるので、それを使います。
まずはコマンドプロンプトにて、以下のコマンドでインストール
pip install icrawler
そして以下のような、簡単なスクリプトを書いて実行すると、サクサクと収集してきてくれます。
from icrawler.builtin import BingImageCrawler
crawler = BingImageCrawler(storage={"root_dir": './daikon_bing'})
crawler.crawl(keyword='大根', max_num=300)
「BingImageCrawler」の部分を「GoogleImageCrawler」に変えれば、グーグルクローラーで収集ができます。
ただ、画像収集に関しては、BingImageCrawlerの方がいいと思いました。
試しに、"大根"について、BingImageCrawlerとGoogleImageCrawlerの両者で実行してみたのですが、前者は213枚、後者は73枚と3倍くらいの差があったからです。
なので、今回の画像収集は、全てBingImageCrawlerで行いました。
尚、野菜1種につき100枚は必要らしいので、max_numを300に設定して、200枚前後を集めました。
それらを、精査によって100枚程度まで選別します。
なので、200枚 × 30種類 = 約6000枚の画像を収集しました。
ツールを使うだけなので、これは大変ではありません。
大変なのは、この後です…。
参考文献
画像の前処理
先述の画像約6000枚の精査です。
これがかなり大変でした。
お昼くらいから始めて、暗くなり始めたあたりで、ようやく終わりました…。
多分、6時間くらいはかかったと思います…。
「AI関連では、データ前処理が8割」なんて言葉を聞いたことがありますが、それを実感しました。
50種類でやらなくて、本当に良かったです!
精査方法は、目視で学習に使えそうなものを残しました。
料理の画像や、野菜の花の画像などを除外していくと、不思議と野菜1種につき200⇒100枚くらいに落ち着きました。
ここで、100枚を大幅に下回った10種類を外しました。
よって、最終的には、100枚 × 20種類 = 2000枚になります。
OpenCVによる画像の水増しは、時間の関係で今回はやりませんでした。
ここの精査がやや甘かったのと、有効な画像の水増しをしなかったのが、後々響いてきます。
画像のアップロード
今回、モデル生成はGoogle Colaboratoryで行うので、先述の2000枚の画像をアップロードします。
Googleドライブに画像をアップロードし、Google Colaboratoryでcontentフォルダ直下でマウントすればOKです。
※Google Colaboratoryへのファイルのアップロードって、意外とハマりポイントだったりします…。
参考文献
モデル生成
モデル生成のソースコードは、以下になります。
ソースコード詳細はこちらをクリック
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.layers import Dense, Dropout, Flatten, Input
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras import optimizers
#モデルの保存
import os
from google.colab import files
# ファイルパス取得
path_asupara = os.listdir('./drive//MyDrive/vegetable_images/asupara/')
path_edamame = os.listdir('./drive//MyDrive/vegetable_images/edamame/')
path_kabocha = os.listdir('./drive//MyDrive/vegetable_images/kabocha/')
path_kyabetsu = os.listdir('./drive//MyDrive/vegetable_images/kyabetsu/')
path_kyuuri = os.listdir('./drive//MyDrive/vegetable_images/kyuuri/')
path_komatsuna = os.listdir('./drive//MyDrive/vegetable_images/komatsuna/')
path_satsumaimo = os.listdir('./drive//MyDrive/vegetable_images/satsumaimo/')
path_jagaimo = os.listdir('./drive//MyDrive/vegetable_images/jagaimo/')
path_shungiku = os.listdir('./drive//MyDrive/vegetable_images/shungiku/')
path_serori = os.listdir('./drive//MyDrive/vegetable_images/serori/')
path_tamanegi = os.listdir('./drive//MyDrive/vegetable_images/tamanegi/')
path_daikon = os.listdir('./drive//MyDrive/vegetable_images/daikon/')
path_tomato = os.listdir('./drive//MyDrive/vegetable_images/tomato/')
path_nasu = os.listdir('./drive//MyDrive/vegetable_images/nasu/')
path_ninjin = os.listdir('./drive//MyDrive/vegetable_images/ninjin/')
path_ninniku = os.listdir('./drive//MyDrive/vegetable_images/ninniku/')
path_naganegi = os.listdir('./drive//MyDrive/vegetable_images/naganegi/')
path_piman = os.listdir('./drive//MyDrive/vegetable_images/piman/')
path_hourensou = os.listdir('./drive//MyDrive/vegetable_images/hourensou/')
path_retasu = os.listdir('./drive//MyDrive/vegetable_images/retasu/')
# 野菜画像格納用変数宣言
img_asupara = []
img_edamame = []
img_kabocha = []
img_kyabetsu = []
img_kyuuri = []
img_komatsuna = []
img_satsumaimo = []
img_jagaimo = []
img_shungiku = []
img_serori = []
img_tamanegi = []
img_daikon = []
img_tomato = []
img_nasu = []
img_ninjin = []
img_ninniku = []
img_naganegi = []
img_piman = []
img_hourensou = []
img_retasu = []
# 野菜画像格納
for i in range(len(path_asupara)):
img = cv2.imread('./drive//MyDrive/vegetable_images/asupara/' + path_asupara[i])
img = cv2.resize(img, (50,50))
img_asupara.append(img)
for i in range(len(path_edamame)):
img = cv2.imread('./drive//MyDrive/vegetable_images/edamame/' + path_edamame[i])
img = cv2.resize(img, (50,50))
img_edamame.append(img)
for i in range(len(path_kabocha)):
img = cv2.imread('./drive//MyDrive/vegetable_images/kabocha/' + path_kabocha[i])
img = cv2.resize(img, (50,50))
img_kabocha.append(img)
for i in range(len(path_kyabetsu)):
img = cv2.imread('./drive//MyDrive/vegetable_images/kyabetsu/' + path_kyabetsu[i])
img = cv2.resize(img, (50,50))
img_kyabetsu.append(img)
for i in range(len(path_kyuuri)):
img = cv2.imread('./drive//MyDrive/vegetable_images/kyuuri/' + path_kyuuri[i])
img = cv2.resize(img, (50,50))
img_kyuuri.append(img)
for i in range(len(path_komatsuna)):
img = cv2.imread('./drive//MyDrive/vegetable_images/komatsuna/' + path_komatsuna[i])
img = cv2.resize(img, (50,50))
img_komatsuna.append(img)
for i in range(len(path_satsumaimo)):
img = cv2.imread('./drive//MyDrive/vegetable_images/satsumaimo/' + path_satsumaimo[i])
img = cv2.resize(img, (50,50))
img_satsumaimo.append(img)
for i in range(len(path_jagaimo)):
img = cv2.imread('./drive//MyDrive/vegetable_images/jagaimo/' + path_jagaimo[i])
img = cv2.resize(img, (50,50))
img_jagaimo.append(img)
for i in range(len(path_shungiku)):
img = cv2.imread('./drive//MyDrive/vegetable_images/shungiku/' + path_shungiku[i])
img = cv2.resize(img, (50,50))
img_shungiku.append(img)
for i in range(len(path_serori)):
img = cv2.imread('./drive//MyDrive/vegetable_images/serori/' + path_serori[i])
img = cv2.resize(img, (50,50))
img_serori.append(img)
for i in range(len(path_tamanegi)):
img = cv2.imread('./drive//MyDrive/vegetable_images/tamanegi/' + path_tamanegi[i])
img = cv2.resize(img, (50,50))
img_tamanegi.append(img)
for i in range(len(path_daikon)):
img = cv2.imread('./drive//MyDrive/vegetable_images/daikon/' + path_daikon[i])
img = cv2.resize(img, (50,50))
img_daikon.append(img)
for i in range(len(path_tomato)):
img = cv2.imread('./drive//MyDrive/vegetable_images/tomato/' + path_tomato[i])
img = cv2.resize(img, (50,50))
img_tomato.append(img)
for i in range(len(path_nasu)):
img = cv2.imread('./drive//MyDrive/vegetable_images/nasu/' + path_nasu[i])
img = cv2.resize(img, (50,50))
img_nasu.append(img)
for i in range(len(path_ninjin)):
img = cv2.imread('./drive//MyDrive/vegetable_images/ninjin/' + path_ninjin[i])
img = cv2.resize(img, (50,50))
img_ninjin.append(img)
for i in range(len(path_ninniku)):
img = cv2.imread('./drive//MyDrive/vegetable_images/ninniku/' + path_ninniku[i])
img = cv2.resize(img, (50,50))
img_ninniku.append(img)
for i in range(len(path_naganegi)):
img = cv2.imread('./drive//MyDrive/vegetable_images/naganegi/' + path_naganegi[i])
img = cv2.resize(img, (50,50))
img_naganegi.append(img)
for i in range(len(path_piman)):
img = cv2.imread('./drive//MyDrive/vegetable_images/piman/' + path_piman[i])
img = cv2.resize(img, (50,50))
img_piman.append(img)
for i in range(len(path_hourensou)):
img = cv2.imread('./drive//MyDrive/vegetable_images/hourensou/' + path_hourensou[i])
img = cv2.resize(img, (50,50))
img_hourensou.append(img)
for i in range(len(path_retasu)):
img = cv2.imread('./drive//MyDrive/vegetable_images/retasu/' + path_retasu[i])
img = cv2.resize(img, (50,50))
img_retasu.append(img)
# Xに画像を入れる
X = np.array(img_asupara
+ img_edamame
+ img_kabocha
+ img_kyabetsu
+ img_kyuuri
+ img_komatsuna
+ img_satsumaimo
+ img_jagaimo
+ img_shungiku
+ img_serori
+ img_tamanegi
+ img_daikon
+ img_tomato
+ img_nasu
+ img_ninjin
+ img_ninniku
+ img_naganegi
+ img_piman
+ img_hourensou
+ img_retasu)
# yに正解ラベルを入れる
y = np.array([0]*len(img_asupara)
+ [1]*len(img_edamame)
+ [2]*len(img_kabocha)
+ [3]*len(img_kyabetsu)
+ [4]*len(img_kyuuri)
+ [5]*len(img_komatsuna)
+ [6]*len(img_satsumaimo)
+ [7]*len(img_jagaimo)
+ [8]*len(img_shungiku)
+ [9]*len(img_serori)
+ [10]*len(img_tamanegi)
+ [11]*len(img_daikon)
+ [12]*len(img_tomato)
+ [13]*len(img_nasu)
+ [14]*len(img_ninjin)
+ [15]*len(img_ninniku)
+ [16]*len(img_naganegi)
+ [17]*len(img_piman)
+ [18]*len(img_hourensou)
+ [19]*len(img_retasu))
rand_index = np.random.permutation(np.arange(len(X)))
X = X[rand_index]
y = y[rand_index]
# データの分割
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):]
# 正解ラベルをone-hotの形にする
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
# モデルにVGG16を使用
input_tensor = Input(shape=(50, 50, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)
# 特徴量抽出部分のモデル定義
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(1024, activation='relu'))
top_model.add(Dropout(0.7))
top_model.add(Dense(20, activation='softmax'))
# vggと、top_modelの連結
model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))
# vggの層の重みを変更不能にする
for layer in model.layers[:19]:
layer.trainable = False
# コンパイル
model.compile(loss='categorical_crossentropy',
optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
metrics=['accuracy'])
# 学習
history = model.fit(X_train, y_train, batch_size=64, epochs=70, validation_data=(X_test, y_test))
# 精度の評価
scores = model.evaluate(X_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])
#acc, val_accのプロット
plt.plot(history.history["accuracy"], label="accuracy", ls="-", marker="o")
plt.plot(history.history["val_accuracy"], label="val_accuracy", ls="-", marker="x")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.show()
#resultsディレクトリを作成
result_dir = 'results'
if not os.path.exists(result_dir):
os.mkdir(result_dir)
# 重みを保存
model.save(os.path.join(result_dir, 'model.h5'))
files.download( '/content/results/model.h5' )
CNN (Convolutional Neural Network) による画像認識のコードですが、正直なところ、データの読み込みあたりの書き方が、力技ごり押しのようで、イケてないですね。
リストとFor文を使えば、もっとスマートに書けそうな気がしましたが、動くものを作るのを最優先にしましたので、ああいった感じになってしまいました。
時間があるときに、リファクタリングをしたいと思っています。
処理の大まかな流れとしましては、以下の通りです。
データ読み込み
↓
データ分割
↓
モデル定義
↓
VGG16との連結
↓
コンパイル
↓
学習
↓
精度の評価
↓
評価のプロット
↓
モデルの保存
この中で、モデル定義(Denseのunits)と学習(batch_sizeとepochs)におけるパラメータを調整して、より良いモデル生成をします。
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.layers import Dense, Dropout, Flatten, Input
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras import optimizers
#モデルの保存
import os
from google.colab import files
importしたライブラリは、上記になります。
# 特徴量抽出部分のモデル定義
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(1024, activation='relu'))
top_model.add(Dropout(0.7))
top_model.add(Dense(20, activation='softmax'))
モデル定義につきましては、今回の場合、これ以上中間層を増やすと、accuracyが著しく落ちるため、これで落ち着きました。
活性化関数は、sigmoid関数よりもrelu関数の方が明らかにaccuracyが良かったため、relu関数を指定しました。
# 学習
history = model.fit(X_train, y_train, batch_size=64, epochs=70, validation_data=(X_test, y_test))
学習につきまして、batch_sizeは、64と128ではそれほど変わらず。
epochsは、50だとちょっとaccuracyが上がり切らず、100だとちょっと多すぎたので、真ん中の70くらいにしました。
様々なパラメータのパターンを何十回か測定したところ、最高でval_accuracyが55%ほどでした。
野菜識別器なので、70%くらいは欲しかったのですが、残念です。
学習結果詳細はこちらをクリック
Epoch 1/70 /usr/local/lib/python3.7/dist-packages/keras/optimizer_v2/gradient_descent.py:102: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead. super(SGD, self).__init__(name, **kwargs) 25/25 [==============================] - 2s 40ms/step - loss: 35.8766 - accuracy: 0.0748 - val_loss: 11.8645 - val_accuracy: 0.1633 Epoch 2/70 25/25 [==============================] - 1s 29ms/step - loss: 23.2324 - accuracy: 0.1706 - val_loss: 8.6124 - val_accuracy: 0.3087 Epoch 3/70 25/25 [==============================] - 1s 29ms/step - loss: 16.8910 - accuracy: 0.2383 - val_loss: 7.4802 - val_accuracy: 0.3673 Epoch 4/70 25/25 [==============================] - 1s 29ms/step - loss: 13.7042 - accuracy: 0.3086 - val_loss: 6.6567 - val_accuracy: 0.4005 Epoch 5/70 25/25 [==============================] - 1s 29ms/step - loss: 11.3202 - accuracy: 0.3380 - val_loss: 6.0878 - val_accuracy: 0.4107 Epoch 6/70 25/25 [==============================] - 1s 29ms/step - loss: 9.8168 - accuracy: 0.3649 - val_loss: 5.3787 - val_accuracy: 0.4643 Epoch 7/70 25/25 [==============================] - 1s 29ms/step - loss: 8.6416 - accuracy: 0.3879 - val_loss: 4.9492 - val_accuracy: 0.4745 Epoch 8/70 25/25 [==============================] - 1s 29ms/step - loss: 7.4661 - accuracy: 0.4147 - val_loss: 4.7056 - val_accuracy: 0.4847 Epoch 9/70 25/25 [==============================] - 1s 29ms/step - loss: 6.7184 - accuracy: 0.4294 - val_loss: 4.5220 - val_accuracy: 0.4821 Epoch 10/70 25/25 [==============================] - 1s 29ms/step - loss: 6.0693 - accuracy: 0.4383 - val_loss: 4.3961 - val_accuracy: 0.4745 Epoch 11/70 25/25 [==============================] - 1s 29ms/step - loss: 5.3708 - accuracy: 0.4601 - val_loss: 4.2232 - val_accuracy: 0.4796 Epoch 12/70 25/25 [==============================] - 1s 28ms/step - loss: 4.7698 - accuracy: 0.4805 - val_loss: 4.0237 - val_accuracy: 0.5000 Epoch 13/70 25/25 [==============================] - 1s 29ms/step - loss: 4.3647 - accuracy: 0.4978 - val_loss: 3.9316 - val_accuracy: 0.4974 Epoch 14/70 25/25 [==============================] - 1s 30ms/step - loss: 4.3106 - accuracy: 0.5137 - val_loss: 3.8294 - val_accuracy: 0.5026 Epoch 15/70 25/25 [==============================] - 1s 29ms/step - loss: 4.0096 - accuracy: 0.5304 - val_loss: 3.6782 - val_accuracy: 0.5204 Epoch 16/70 25/25 [==============================] - 1s 29ms/step - loss: 3.5132 - accuracy: 0.5438 - val_loss: 3.5664 - val_accuracy: 0.5332 Epoch 17/70 25/25 [==============================] - 1s 29ms/step - loss: 3.2925 - accuracy: 0.5412 - val_loss: 3.5016 - val_accuracy: 0.5204 Epoch 18/70 25/25 [==============================] - 1s 29ms/step - loss: 3.1134 - accuracy: 0.5482 - val_loss: 3.4349 - val_accuracy: 0.5204 Epoch 19/70 25/25 [==============================] - 1s 29ms/step - loss: 2.9102 - accuracy: 0.5636 - val_loss: 3.3871 - val_accuracy: 0.5332 Epoch 20/70 25/25 [==============================] - 1s 29ms/step - loss: 2.6021 - accuracy: 0.5930 - val_loss: 3.3226 - val_accuracy: 0.5230 Epoch 21/70 25/25 [==============================] - 1s 29ms/step - loss: 2.6292 - accuracy: 0.5968 - val_loss: 3.2329 - val_accuracy: 0.5255 Epoch 22/70 25/25 [==============================] - 1s 29ms/step - loss: 2.3134 - accuracy: 0.6064 - val_loss: 3.1330 - val_accuracy: 0.5408 Epoch 23/70 25/25 [==============================] - 1s 29ms/step - loss: 2.4324 - accuracy: 0.5968 - val_loss: 3.0966 - val_accuracy: 0.5383 Epoch 24/70 25/25 [==============================] - 1s 29ms/step - loss: 2.1360 - accuracy: 0.6102 - val_loss: 3.0476 - val_accuracy: 0.5485 Epoch 25/70 25/25 [==============================] - 1s 29ms/step - loss: 2.1603 - accuracy: 0.6115 - val_loss: 3.0394 - val_accuracy: 0.5434 Epoch 26/70 25/25 [==============================] - 1s 30ms/step - loss: 1.9442 - accuracy: 0.6345 - val_loss: 2.9954 - val_accuracy: 0.5459 Epoch 27/70 25/25 [==============================] - 1s 29ms/step - loss: 1.8971 - accuracy: 0.6313 - val_loss: 2.9952 - val_accuracy: 0.5357 Epoch 28/70 25/25 [==============================] - 1s 29ms/step - loss: 1.7660 - accuracy: 0.6530 - val_loss: 3.0271 - val_accuracy: 0.5383 Epoch 29/70 25/25 [==============================] - 1s 29ms/step - loss: 1.7536 - accuracy: 0.6486 - val_loss: 2.9993 - val_accuracy: 0.5255 Epoch 30/70 25/25 [==============================] - 1s 30ms/step - loss: 1.5592 - accuracy: 0.6792 - val_loss: 2.9644 - val_accuracy: 0.5230 Epoch 31/70 25/25 [==============================] - 1s 29ms/step - loss: 1.5253 - accuracy: 0.6850 - val_loss: 2.9286 - val_accuracy: 0.5281 Epoch 32/70 25/25 [==============================] - 1s 29ms/step - loss: 1.4880 - accuracy: 0.6843 - val_loss: 2.8948 - val_accuracy: 0.5204 Epoch 33/70 25/25 [==============================] - 1s 29ms/step - loss: 1.5461 - accuracy: 0.6607 - val_loss: 2.8717 - val_accuracy: 0.5281 Epoch 34/70 25/25 [==============================] - 1s 30ms/step - loss: 1.4480 - accuracy: 0.6901 - val_loss: 2.8396 - val_accuracy: 0.5383 Epoch 35/70 25/25 [==============================] - 1s 29ms/step - loss: 1.4154 - accuracy: 0.6920 - val_loss: 2.8299 - val_accuracy: 0.5357 Epoch 36/70 25/25 [==============================] - 1s 30ms/step - loss: 1.2613 - accuracy: 0.6882 - val_loss: 2.8418 - val_accuracy: 0.5179 Epoch 37/70 25/25 [==============================] - 1s 29ms/step - loss: 1.3110 - accuracy: 0.6978 - val_loss: 2.8377 - val_accuracy: 0.5332 Epoch 38/70 25/25 [==============================] - 1s 30ms/step - loss: 1.1576 - accuracy: 0.7157 - val_loss: 2.8055 - val_accuracy: 0.5383 Epoch 39/70 25/25 [==============================] - 1s 29ms/step - loss: 1.2242 - accuracy: 0.7227 - val_loss: 2.7894 - val_accuracy: 0.5485 Epoch 40/70 25/25 [==============================] - 1s 29ms/step - loss: 1.1093 - accuracy: 0.7323 - val_loss: 2.7611 - val_accuracy: 0.5408 Epoch 41/70 25/25 [==============================] - 1s 29ms/step - loss: 1.2431 - accuracy: 0.7450 - val_loss: 2.7135 - val_accuracy: 0.5357 Epoch 42/70 25/25 [==============================] - 1s 29ms/step - loss: 1.1525 - accuracy: 0.7348 - val_loss: 2.7137 - val_accuracy: 0.5383 Epoch 43/70 25/25 [==============================] - 1s 30ms/step - loss: 1.0988 - accuracy: 0.7361 - val_loss: 2.7160 - val_accuracy: 0.5434 Epoch 44/70 25/25 [==============================] - 1s 29ms/step - loss: 1.0774 - accuracy: 0.7470 - val_loss: 2.7199 - val_accuracy: 0.5536 Epoch 45/70 25/25 [==============================] - 1s 30ms/step - loss: 1.0711 - accuracy: 0.7342 - val_loss: 2.7277 - val_accuracy: 0.5459 Epoch 46/70 25/25 [==============================] - 1s 29ms/step - loss: 0.9480 - accuracy: 0.7597 - val_loss: 2.7460 - val_accuracy: 0.5408 Epoch 47/70 25/25 [==============================] - 1s 29ms/step - loss: 1.0666 - accuracy: 0.7514 - val_loss: 2.7426 - val_accuracy: 0.5357 Epoch 48/70 25/25 [==============================] - 1s 30ms/step - loss: 1.0483 - accuracy: 0.7406 - val_loss: 2.7388 - val_accuracy: 0.5357 Epoch 49/70 25/25 [==============================] - 1s 30ms/step - loss: 0.9762 - accuracy: 0.7489 - val_loss: 2.7414 - val_accuracy: 0.5306 Epoch 50/70 25/25 [==============================] - 1s 30ms/step - loss: 0.8907 - accuracy: 0.7655 - val_loss: 2.7499 - val_accuracy: 0.5383 Epoch 51/70 25/25 [==============================] - 1s 29ms/step - loss: 0.8555 - accuracy: 0.7764 - val_loss: 2.7408 - val_accuracy: 0.5408 Epoch 52/70 25/25 [==============================] - 1s 30ms/step - loss: 0.8303 - accuracy: 0.7885 - val_loss: 2.7335 - val_accuracy: 0.5510 Epoch 53/70 25/25 [==============================] - 1s 30ms/step - loss: 0.9116 - accuracy: 0.7585 - val_loss: 2.7355 - val_accuracy: 0.5357 Epoch 54/70 25/25 [==============================] - 1s 30ms/step - loss: 0.9020 - accuracy: 0.7655 - val_loss: 2.7264 - val_accuracy: 0.5306 Epoch 55/70 25/25 [==============================] - 1s 29ms/step - loss: 0.8301 - accuracy: 0.7783 - val_loss: 2.7055 - val_accuracy: 0.5408 Epoch 56/70 25/25 [==============================] - 1s 30ms/step - loss: 0.7630 - accuracy: 0.7968 - val_loss: 2.7262 - val_accuracy: 0.5383 Epoch 57/70 25/25 [==============================] - 1s 29ms/step - loss: 0.7734 - accuracy: 0.7879 - val_loss: 2.7178 - val_accuracy: 0.5357 Epoch 58/70 25/25 [==============================] - 1s 30ms/step - loss: 0.7316 - accuracy: 0.7949 - val_loss: 2.6867 - val_accuracy: 0.5459 Epoch 59/70 25/25 [==============================] - 1s 30ms/step - loss: 0.8420 - accuracy: 0.7955 - val_loss: 2.6716 - val_accuracy: 0.5434 Epoch 60/70 25/25 [==============================] - 1s 29ms/step - loss: 0.6739 - accuracy: 0.8026 - val_loss: 2.6827 - val_accuracy: 0.5459 Epoch 61/70 25/25 [==============================] - 1s 30ms/step - loss: 0.6887 - accuracy: 0.8102 - val_loss: 2.6769 - val_accuracy: 0.5434 Epoch 62/70 25/25 [==============================] - 1s 30ms/step - loss: 0.7280 - accuracy: 0.8032 - val_loss: 2.6510 - val_accuracy: 0.5485 Epoch 63/70 25/25 [==============================] - 1s 30ms/step - loss: 0.7558 - accuracy: 0.7923 - val_loss: 2.6609 - val_accuracy: 0.5408 Epoch 64/70 25/25 [==============================] - 1s 29ms/step - loss: 0.6398 - accuracy: 0.8224 - val_loss: 2.6271 - val_accuracy: 0.5510 Epoch 65/70 25/25 [==============================] - 1s 30ms/step - loss: 0.6769 - accuracy: 0.8051 - val_loss: 2.6236 - val_accuracy: 0.5408 Epoch 66/70 25/25 [==============================] - 1s 30ms/step - loss: 0.6145 - accuracy: 0.8217 - val_loss: 2.6321 - val_accuracy: 0.5332 Epoch 67/70 25/25 [==============================] - 1s 29ms/step - loss: 0.6556 - accuracy: 0.8141 - val_loss: 2.6275 - val_accuracy: 0.5434 Epoch 68/70 25/25 [==============================] - 1s 30ms/step - loss: 0.6336 - accuracy: 0.8198 - val_loss: 2.6252 - val_accuracy: 0.5485 Epoch 69/70 25/25 [==============================] - 1s 30ms/step - loss: 0.6218 - accuracy: 0.8288 - val_loss: 2.6194 - val_accuracy: 0.5510 Epoch 70/70 25/25 [==============================] - 1s 30ms/step - loss: 0.5855 - accuracy: 0.8371 - val_loss: 2.6253 - val_accuracy: 0.5561 13/13 [==============================] - 0s 19ms/step - loss: 2.6253 - accuracy: 0.5561 Test loss: 2.625286102294922 Test accuracy: 0.5561224222183228Downloading "model.h5":
おそらく、今の画像データでは、これ以上は出ないでしょう。
val_accuracyを上げるには、画像データの更に厳格な精査と、有効な画像の水増しが必要だと思います。
まとめ
企画からモデル生成、最終的にアプリ公開まで一通り実践できたので、とても良い経験になりました。
AIアプリ開発講座の3ヶ月コースでしたが、実質最後の3週間くらいで一気に詰め込んだ感じなので、最後は時間が足りなかったですね。
(夏休みの宿題を最終日に頑張る感じを思い出しました。)
最後に、今後の改善点をまとめました。
1.画像の精査をより厳しくする
2.有効な画像の水増しをする
3.データの読み込みのソースコードをスマートに改良する
これらを実施することで、真の野菜識別器を名乗ることができるかと思います。