1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

CNNで花を分類するアプリの作成

Last updated at Posted at 2023-12-27

はじめに

道端や観光スポットで花をよくみかけるが、何の花なのかわからないことが多く、これをAIで解決することができないかと考えモデルを作成することにしました。
元々、AIには興味があったため、勉強を兼ねて実施しています。
今回は5種類の花の分類を行っています。

#目次
1.開発環境
2.データセットの作成
3.シンプルなCNNでの検証
3-1.ライブラリの読み込み
3-2.学習データの読み込み
3-3.モデルの構築
3-4.モデルのコンパイル、学習
3-5.制度の評価
3-6.層を増減させて確認
4.転移学習の導入
4-1.コードの変更
4-2.結果
4-3.画像サイズ変更
5.Flask形式に置き換え、Renderへアプリをデプロイ
6.完成品
7.おわりに

開発環境

Windows 10
Visual Studio Code
Anaconda

データセットの作成

データセットの作成にあたり、Kaggleから5 Flower Types Classification Datasetという5種類の花の画像がセットになったデータセットをダウンロードします。
https://www.kaggle.com/datasets/kausthubkannan/5-flower-types-classification-dataset
5 Flower Types Classification Datasetは、

  • リリー
  • ロータス
  • ひまわり
  • チューリップ
    がそれぞれ1000画像あるデータセットです。

シンプルなCNNでの検証

まずは基本的なCNNモデルを構築しました。
CNNとは、画像認識に広く使われるディープニューラルネットワークを用いた深層学習手法です。
このCNNモデルに5つに分類した花画像を学習させ、的確に花の種類を識別させるのが目標です。

ライブラリの読み込み

5つの分類データの保存パスから画像データを読み込み、学習データ、検証データに分離しました。

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,Conv2D,MaxPooling2D,Activation, BatchNormalization
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras import optimizers

学習データの読み込み

path_lilly = os.listdir('flower_images/Lilly/')
path_lotus = os.listdir('flower_images/Lotus/')
path_orchid = os.listdir('flower_images/Orchid/')
path_sunflower = os.listdir('flower_images/Sunflower/')
path_tulip = os.listdir('flower_images/Tulip/')

img_lilly = []
img_lotus = []
img_orchid = []
img_sunflower = []
img_tulip = []

for i in range(len(path_lilly)):
    img = cv2.imread('flower_images/Lilly/' + path_lilly[i])
    b,g,r = cv2.split(img)
    img = cv2.merge([r,g,b])
    img = cv2.resize(img, (100,100))
    img_lilly.append(img)

for i in range(len(path_lotus)):
    img = cv2.imread('flower_images/Lotus/' + path_lotus[i])
    b,g,r = cv2.split(img)
    img = cv2.merge([r,g,b])
    img = cv2.resize(img, (100,100))
    img_lotus.append(img)

for i in range(len(path_orchid)):
    img = cv2.imread('flower_images/Orchid/' + path_orchid[i])
    b,g,r = cv2.split(img)
    img = cv2.merge([r,g,b])
    img = cv2.resize(img, (100,100))
    img_orchid.append(img)

for i in range(len(path_sunflower)):
    img = cv2.imread('flower_images/Sunflower/' + path_sunflower[i])
    b,g,r = cv2.split(img)
    img = cv2.merge([r,g,b])
    img = cv2.resize(img, (100,100))
    img_sunflower.append(img)

for i in range(len(path_tulip)):
    img = cv2.imread('flower_images/Tulip/' + path_tulip[i])
    b,g,r = cv2.split(img)
    img = cv2.merge([r,g,b])
    img = cv2.resize(img, (100,100))
    img_tulip.append(img)


X = np.array(img_lilly + img_lotus + img_orchid + img_sunflower + img_tulip)
y =  np.array([0]*len(img_lilly) + [1]*len(img_lotus) + [2]*len(img_orchid) + [3]*len(img_sunflower) + [4]*len(img_tulip))

rand_index = np.random.permutation(np.arange(len(X)))
X = X[rand_index]
y = y[rand_index]

y = to_categorical(y)

# データの分割
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層と全結合層を3層に通すシンプルなモデルを作成しました。
後に全結合層の層の数を増減させて検証するために、A,B,C層と振っておきます。

model = Sequential()
model.add(Conv2D(input_shape=(100,100,3),filters=32,kernel_size=(5,5),strides=(1,1),padding="same"))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(BatchNormalization())
model.add(Conv2D(filters=32,kernel_size=(3,3),strides=(1,1),padding="same"))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(BatchNormalization())
model.add(Conv2D(filters=32,kernel_size=(3,3),strides=(1,1),padding="same"))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(BatchNormalization())
model.add(Flatten())
# A層
model.add(Dense(256))
model.add(BatchNormalization())
model.add(Activation('relu'))
# B層
model.add(Dense(25))
model.add(BatchNormalization())
model.add(Activation('relu'))
# C層
model.add(Dropout(0.5))
model.add(Dense(5, activation='softmax'))

モデルのコンパイル、学習

model.compile(loss="categorical_crossentropy",optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),metrics=["accuracy"])
model.summary() #モデルの表示
history = model.fit(X_train, y_train, batch_size=400, epochs=100, validation_data=(X_test, y_test))

制度の評価

plt.figure(figsize=(10,5))
plt.subplot(1,2,1)
plt.plot(history.history["accuracy"], label="acc", ls="-", marker="o")
plt.plot(history.history["val_accuracy"], label="val_acc", ls="-", marker="x")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.grid(True)
plt.subplot(1,2,2)
plt.plot(history.history["loss"], label="acc", ls="-", marker="o")
plt.plot(history.history["val_loss"], label="val_acc", ls="-", marker="x")
plt.ylabel("loss")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.grid(True)
plt.show()

シンプルなCNN_3層.png

結果としては56%程度であり、高い精度が出ていないようです。

層を増減させて確認

B層を削除して2層にして確認
シンプルなCNN_2層.png
結果は60%程度になりました。

A層とB層の間に下記層を挿入して4層にして確認

model.add(Dense(125))
model.add(BatchNormalization())
model.add(Activation('relu'))

シンプルなCNN_4層.png
結果としては60%程度でした。

結論としては全結合層を多少増減させた程度では精度の向上は見れなさそうということになります。
今回は全結合層の1層の増減だけにしましたが、大きく増加させた場合や畳み込み層を変更した際の制度も今後検証していきたいと思います。

転移学習の導入

精度の向上が見込めなかったため、転移学習を導入してみることにしました。
転移学習とは、すでに大量のデータを使ってある程度学習しているモデルをベースに、自分で層を追加して学習を行う方法です。
今回はVGG16モデルを通した後、3つの全結合層を追加するモデルを構築しました。

コードの変更

新たにライブラリの読み込みを追加し、先ほどのモデルの構築部分をVGGを使用するように差し替えます。

from tensorflow.keras.applications.vgg16 import VGG16
# vgg16のインスタンス作成 
input_tensor=Input(shape=(100,100,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(256))
top_model.add(BatchNormalization())
top_model.add(Activation('relu'))
top_model.add(Dense(25))
top_model.add(BatchNormalization())
top_model.add(Activation('relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(5, activation='softmax'))

# モデルの連結
model = Model(inputs=vgg16.input,outputs=top_model(vgg16.output))

# vgg16の重みの固定
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"])

結果

結果は66%程度と多少精度は上がっていましたが、劇的な効果は見られませんでした。
vgg16.png

画像サイズ変更

画像サイズを100100から200200にして精度を検証してみました。
※batch_size=400だと「ResourceExhaustedError」がでたため、batch_size=200に変更して実施しています
vgg16_200_200.png

結果は89%と制度が向上していることがわかります。
画像サイズはある程度大きくないと特徴を抽出しきれないのかもしれません。
今後は、画像サイズを変えたことによる特徴量の抽出の変化を可視化し、何が変わったのか検証していきたいと思います。

Flask形式に置き換え、Renderへアプリをデプロイ

Flask形式に置き換え、WEBアプリとしてRenderで公開することにしました。
事前準備として先ほど作成したプログラムに、以下の工程を追加して識別モデルをmodel.h5として書き出しておきます。

#resultsディレクトリを作成
result_dir = 'results'
if not os.path.exists(result_dir):
    os.mkdir(result_dir)
# 重みを保存
model.save(os.path.join(result_dir, 'model.h5'))

他にはpythonのバージョンを記したruntime.txt、読み込みライブラリを記したrequirements.txt、アプリ表面上のレイアウトとなるindex.html、デザイン要素をまとめたstylesheet.cssと画像を全て1つのフォルダ内に用意しました。
folder_image.png

そしてアプリとしてメインで動くmain.pyを以下のように作成しました。
Aidemyアプリ開発講座、Flask入門のコードを流用して作成しています。

import os
from flask import Flask, request, redirect, render_template, flash
from werkzeug.utils import secure_filename
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.preprocessing import image

import numpy as np


classes = ["リリー","ロータス","","ひまわり","チューリップ"]
image_size = 200

UPLOAD_FOLDER = "uploads"   
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])

app = Flask(__name__)

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

path = os.getcwd()
print(path)
model = load_model('./model.h5', compile=False)
#学習済みモデルをロード


@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        if 'file' not in request.files:
            flash('ファイルがありません')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('ファイルがありません')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(UPLOAD_FOLDER, filename))
            filepath = os.path.join(UPLOAD_FOLDER, filename)

            #受け取った画像を読み込み、np形式に変換
            img = image.load_img(filepath, grayscale=False, target_size=(image_size,image_size))
            img = image.img_to_array(img)
            data = np.array([img])
            #変換したデータをモデルに渡して予測する
            result = model.predict(data)[0]
            predicted = result.argmax()
            pred_answer = "これは " + classes[predicted] + " です"

            return render_template("index.html",answer=pred_answer)

    return render_template("index.html",answer="")


if __name__ == "__main__":
    port = int(os.environ.get('PORT', 8080))
    app.run(host ='0.0.0.0',port = port)

Renderにデプロイして完成です。

完成品

出来上がったアプリは下記です。
https://flask-flower-app.onrender.com/

おわりに

VGGを適切に使用することによって、初心者でもある程度の画像認識の精度を出せることができました。
しかし、パラメータの設定や、画像サイズによって制度は大きく変わってくることがわかりました。
ただ、全結合層を1層増減させただけでは、学習結果に大きな影響がなかったため、大きく増加させた場合や畳み込み層を変更した際の制度も今後検証していきたいと思います。
また、画像サイズを変えたことによる特徴量の抽出の変化を可視化し、何が変わったのか検証していきたいと思います。
今回の検証において、学習データが大きいと、自身のPCではエラーが発生したため、大量のデータを使用する場合は環境構築が大変だと感じました。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?