23
22

More than 1 year has passed since last update.

AIによる野菜識別器アプリの作成

Last updated at Posted at 2022-06-03

はじめに

初めまして、Yukimuraと申します。
ネットワークエンジニア(非AI)をしていますが、AIエンジニアをやりたいなと思っている今日この頃です。

E資格やDS検定(リテラシーレベル)は取得済みのため、AIに関する基本的な知識はありますが、実践力を身に着けたく、AidemyさんのAIアプリ開発講座を受講しました。

本記事の概要

本記事は、AIアプリ開発講座の集大成として、オリジナルアプリ(画像認識)を作成したことについてです。
オリジナルアプリのネタは色々考えたところ、実家が農家のため、野菜識別器とかいいんじゃないかなと思い、それに決めました。
作成した野菜識別器は、以下になります。

野菜識別器_スクリーンショット.png

実は、私よりも以前に同じ講座を受講されていた方が、野菜識別のアプリを作られていました。
なので、以下の記事はとても参考になりました。
(特にソースコードの解説が秀逸です。)
偉大な先輩、ありがとうございました。

では、どんなことをしたのかを、具体的に書いていきます。

作業環境

・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.5561224222183228

Downloading "model.h5":

モデル精度.png

おそらく、今の画像データでは、これ以上は出ないでしょう。
val_accuracyを上げるには、画像データの更に厳格な精査と、有効な画像の水増しが必要だと思います。

まとめ

企画からモデル生成、最終的にアプリ公開まで一通り実践できたので、とても良い経験になりました。

AIアプリ開発講座の3ヶ月コースでしたが、実質最後の3週間くらいで一気に詰め込んだ感じなので、最後は時間が足りなかったですね。
(夏休みの宿題を最終日に頑張る感じを思い出しました。)

最後に、今後の改善点をまとめました。
1.画像の精査をより厳しくする
2.有効な画像の水増しをする
3.データの読み込みのソースコードをスマートに改良する

これらを実施することで、真の野菜識別器を名乗ることができるかと思います。

23
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
22