Appleに毒された人の何気ない日常

写真撮るの好き 旅行好き Apple好き

Python と TensorFlow で作る画像判定 AI

こんにちは(^^)
Apple に毒された人こと、ゆうです
この前も iPhone 12 Pro と Apple Watch Series 6, Home Pod mini を買っちゃいました!
え、お金...?知らん...

最近、Udemy というオンライン学習サービスを利用して Deep Learnig と画像判定 AI の作成について勉強したので、復習も兼ねて書き連ねたいと思います
ご興味ある方は是非読んでいただけると嬉しいです!

初めにですが、めちゃめちゃ初学者がコーティングしてるので、なんだこれ?!ってのがあるかもですがあしからず。。。(むしろ何か気付いたらコメントとかで教えて欲しい)
普通に日頃から開発してる人は、自分にとって強すぎマンなので優しい目で軽く読んでいってくれると嬉しいです!

つくりたいもの

今回作っていくものは、動物の写真を与えたら
「これは猫ちゃんだ!!」「これは犬くんだ!!」
って教えてくれる単純な AI です
ここでは Animal Classifier AI と名付けることにします

開発中に猫の写真いっぱい集めてたんですけど、かわいくて癒されました〜

実行環境

  • Python 3.8.5 (Anaconda 3)
  • macOS Catalina (ver 10.15.7)
  • その他は画像の通り

f:id:yukun0519:20201123151755p:plain
00_MyPC_Specification

Python は 3 系であればとりあえず大丈夫だと思います(3.6, 3.7, 3.8とか)
無難に Anaconda 3 を使って環境を構築をしました(楽だから 笑)
インストール方法の参考リンクも貼っておきます

qiita.com

ただし、Anaconda はめちゃめちゃ容量(6~7GB)を食うので余裕がない人は自前で構築した方がいいかも
元から持ってる Python 環境があればそれで十分
ちなみに、個人的には Pipenv を用いた環境構築がオススメ qiita.com

開発フロー 

おおまかに

  • データ収集・生成
  • データの前処理
  • モデルの定義
  • 教師あり学習・テスト・評価
  • データ収集・生成
    Flickr の利用と API Key の取得

    さて、今回の Animal Classifier AI を作るには大量の教師データ(動物の画像)が必要です
    でも、動物の画像ってどうやって沢山集めればいいん??ってなると思います
    Google 先生に尋ねて毎回ダウンロード...はしたくないです
    そこで Flickr というとても便利なサイトを使ってプログラムから画像をダウンロードしていきます! まずは、ここに会員登録(Sign up)をしてログイン(Sign in)します

    www.flickr.com

    Sign in できたら画像のダウンロード用に API key を取得していきたいと思います
    まずはサイトのアドレスバーに以下のように Services と追記して専用の "The App Garden" ページに遷移します

    https://www.flickr.com/services/

    "The App Garden" のページに来たら、以下のように "Create an App" をクリックします

    f:id:yukun0519:20201024155010p:plain
    03_Create_an_App
      次に "① Get your API Key" の "Request an API Key" をクリックします

    f:id:yukun0519:20201024155724p:plain
    04_Request_an_API_Key

    商用利用かどうか聞かれるので、"NON-COMMERCIAL" を選択します

    f:id:yukun0519:20201024160512p:plain
    05_NON-COMMERCIAL

    どんなアプリケーションを作るために利用するのか?と聞かれるので、適切に記入します
    今回はアプリ名を "AnimalClassifier" 、内容を "To create animal classifier with deep learning" としました
    利用規約同意のためチェックボックスにチェックを入れ、"SUBMIT" を押します

    f:id:yukun0519:20201024161412p:plain
    06_Tell_us_about_your_app

    すると以下のように、"Key" と "Secret" が表示されるので、両方メモしておきます
    後でこの Key をプログラムを書く際に利用します
    ひとまずここまでお疲れ様でした!!👍  

    f:id:yukun0519:20201024162332p:plain
    07_APIKey

     

    データ収集プログラムの作成

    動物の画像をダウンロードしていきたいと思います!
    やっと楽しくなってきました 笑
    まずはデスクトップなどに画像とプログラムを入れるフォルダを作成します
    僕は "AnimalAI" フォルダの配下に "cat" などの画像格納フォルを作りました
    動物のフォルダ名はプログラムの都合上、小文字の英語の動物名を指定してください!

    f:id:yukun0519:20201024170915p:plain
    08_Folder

    ターミナル(Windows はコマンドプロンプト)を開き、Flickr API を使うためのライブラリ "flickrapi" を "pip" でインストールします

    $ pip install flickrapi
    

    では早速、画像をダウンロードと保存するプログラム "download.py" を書いていきます!
    はいっ!!どん!

    # cording: utf-8
    # download.py
    from flickrapi import FlickrAPI
    from urllib.request import urlretrieve
    from pprint import pprint  # lsit や dict を綺麗に整形して print
    import os, time, sys
    
    # API key
    key = "****"  # メモしておいた Key を入力
    secret = "****"  # メモしておいた Secret を入力
    # request interval
    wait_time = 1
    
    # saving directry
    animalname = sys.argv[1]
    savedir = "./" + animalname
    
    # json 形式で結果を取得
    flickr = FlickrAPI(key, secret, format="parsed-json")
    result = flickr.photos.search(
        text = animalname,
        per_page = 400,  # 400 枚
        media = "photos",
        sort = "relevance",  # 関連順にソート
        safe_search = 1,  # UIコンテンツを表示しない
        extras = "url_q, licence"  # 取得する情報 画像のurlとライセン情報
    )
    
    # result
    photos = result["photos"]
    # pprint(photos)  # 確認用
    
    for i, photo in enumerate(photos["photo"]):
        url_q = photo["url_q"]
        filepath = savedir + "/" + photo["id"] + ".jpg"
        if os.path.exists(filepath): continue  # 重複画像の保存を避ける
        urlretrieve(url_q, filepath)  # 画像の保存
        time.sleep(wait_time)  # サーバへのアクセス連発を避ける
    

    実行する場合は第一引数で動物の名前を指定します
    すると、先ほど作成したフォルダに該当する動物の画像が 400 枚保存されます!
    (画像選別後に学習用に 300 枚以上欲しいので 400 にしてます)
    念のため、for ループの部分を書く前に "pprint(photos)" で出力を確認してください
    どうゆう形でデータが取得できているか分かると思います
    問題なければ最後の for ループの箇所も書いて以下のように実行します

    $ python download.py cat

    すると、たくさんの猫ちゃんがっ!!かわいい!!
    同様に、他の動物ももう 2, 3 種類くらいダウンロードしましょう

    f:id:yukun0519:20201024175159p:plain
    09_cat

    データの前処理

    画像の選定

    画像がダウンロードできたら不要なデータを削除します
    どうやるかと言うと...力技です
    地道に全ての画像を確認し、学習に不適切なものを削除します
    ここでいう不適切な画像は、例えば猫の場合、人間と一緒に写っていたり、体の一部しか写ってなかったり、画像の大部分が背景だったり、そもそも猫ではなかったり、というものです
    これらは学習に向いてないので削除して、しっかり顔や体が写っているものだけ残します

    僕の場合、

    • 猫 399 -> 371
    • 犬 400 -> 379
    • 猿 400 -> 387

    のように、それぞれ約 10 ~ 30 枚くらい削除しました

    画像データを NumPy 配列へ変換

    次に、選定した画像データを NumPy 配列に変換し、プログラムで扱いやすくします

    ターミナルを開き、 Python で画像を扱うためのライブラリ "pillow" と機械学習のためのライブラリ "sklearn" をインストールします

    $ pip install pillow
    $ pip install sklearn

    インストールできたら、早速コードを書いていきましょう
    今回は Numpy 配列のデータを生成すりプログラムなので、ファイル名を "gen_data.py" としました

    # cording: utf-8
    # gen_data.py
    from PIL import Image
    import os, glob
    import numpy as np
    from sklearn import model_selection
    
    classes = ["cat", "dog", "monkey"]
    num_classes = len(classes)
    image_size = 50  # 画像を 50 pixel * 50 pixel にサイズ縮小
    
    
    X = []  # 画像データ用リスト
    Y = []  # データのラベル用リスト
    
    # 画像の読み込み
    for index, classlabel in enumerate(classes):
        photos_dir = "./" + classlabel
        files = glob.glob(photos_dir + "/*.jpg")  # glob で画像を探しに行く
        for i, file in enumerate(files):
            if i >= 300: break  # 300 枚で終了する
            image = Image.open(file)
            image = image.convert("RGB")  # RGB の 3 色に変換(0 ~ 255 で表現)
            image = image.resize((image_size, image_size))  #  画像のサイズ変更
            data = np.asarray(image)  # 数字の配列にする
            X.append(data)
            Y.append(index)
    
    # TensorFlow で扱い易いように NumPy に変換する
    X = np.array(X)
    Y = np.array(Y)
    
    # 学習用データとテストデータに分類
    X_train, X_test, Y_train, Y_test = model_selection.train_test_split(X, Y)  # 3:1 で分割
    xy = (X_train, X_test, Y_train, Y_test)
    np.save("./animal.npy", xy)  # 保存
    

    書けたら実行しましょう!

    $ python gen_data.py

    実行ファイルと同じディレクトリに "animai.npy" という名前でデータが保存されていればOKです
    もし中身が気になるなら、コードの最後に "print(xy)" と追記して確認してみてください

    モデルの定義

    トレーニングを実行するコードの作成

    ここまでの作業で学習データの準備ができたので、次にこのデータを学習させるコードを書いていきます
    では始めに、ディープラーニングのためのライブラリ "keras" をターミナルからインストールします
    また、学習済みモデルを保存するために "h5py" もインストールします f:id:yukun0519:20210117193952p:plain

    $ pip install keras
    $ pip install h5py

    名前は、畳み込みのニューラルネットワークということで "animal_cnn.py" としました
    Keras の公式サイトでは機械学習のためのサンプルコードがたくさん紹介されています!
    今回の学習用のコードは以下を参考にしました

    github.com

    作成したコードはこんな感じになりました!
    (コメントアウト部分はほぼ自分用ですがよければ参考に)
    簡単に説明すると、構成は以下のように 3 つから成り立ちます

    • main() : メイン関数
    • model_train() : モデルを定義して学習させる関数
    • model_eval() : モデルを評価する関数

    今回は 150 回 (epochs = 150) 学習させるようにしました

    # cording: utf-8
    # animal_cnn.py
    from keras.models import Sequential  # ニューラルネットワークのモデルの定義に使う
    from keras.layers import Conv2D, MaxPooling2D  # 2次元の畳み込みとプーリングの処理
    # 順に、活性化関数、ドロップアウト処理を行う関数、データを一次元化、全結合層を扱う関数
    from keras.layers import Activation, Dropout, Flatten, Dense
    from keras.utils import np_utils
    import keras
    import numpy as np
    
    classes = ["cat", "dog", "monkey"]
    num_classes = len(classes)
    image_size = 50  # 画像を 50 pixel * 50 pixel にサイズ縮小
    
    # main 関数
    def main():
        # データの読み込み
        X_train, X_test, Y_train, Y_test = np.load("./animal.npy", allow_pickle=True)
        # データの正規化、画像の色データ 0~255 の整数値を 0~1 小数値にする
        X_train = X_train.astype("float") / 256
        X_test = X_test.astype("float") / 256
        # one-hot-vector 正解値は 1 他は 0 になるよう変換する
        Y_train = np_utils.to_categorical(Y_train, num_classes)
        Y_test = np_utils.to_categorical(Y_test, num_classes)
        
        # model 生成
        model = model_train(X_train, Y_train)
        # model 評価
        model_eval(model, X_test, Y_test)
    
    # モデル生成
    def model_train(X, Y):
        # モデルの作成
        model = Sequential()
        # モデルの層を足す 
        # 32個のフィルターでサイズ3*3 畳み込み結果が同じサイズになるようピクセルを左右に足す 入力データ(画像)の形状は X_train (個数を除く)
        model.add(Conv2D(32,(3,3), padding="same", input_shape=X.shape[1:]  )) 
        model.add(Activation("relu"))  # 正の値だけ通し、負の値は通さない 
        model.add(Conv2D(32,(3,3)))
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2,2)))  # 一番大きい値を取り出す
        model.add(Dropout(0.25))  # 25パーセントを減らす
    
        model.add(Conv2D(64, (3,3), padding="same"))
        model.add(Activation('relu'))
        model.add(Conv2D(64, (3,3)))
        model.add(Activation('relu'))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        model.add(Dropout(0.25))
    
        model.add(Flatten())  #  データを一列に並べる
        model.add(Dense(512))  # 全結合層
        model.add(Activation("relu"))
        model.add(Dropout(0.5))  # 半分残す
        model.add(Dense(3))  #  最後の出力層のノードは3つ
        model.add(Activation("softmax"))  # それぞれの画像と一致している確率を足すと1になよう結果を変換
        
        # 最適化処理
        #  トレーニング時の更新アルゴリズム lr:learning rate, decay:学習レートを下げる
        opt = keras.optimizers.RMSprop(lr=0.0001, decay=1e-6)
        # loss:損失関数 正解と推定との誤差, metrics:評価の値
        model.compile(loss="categorical_crossentropy", optimizer=opt, metrics=["accuracy"])
        model.fit(X, Y, batch_size=32, epochs=150)  # epochs:トレーニングの回数
        
        # モデルの保存
        model.save("./animal_cnn.h5")
        
        return model
    
    # モデル評価
    def model_eval(model, X, Y):
        scores = model.evaluate(X, Y, verbose=1) # verbose:途中で結果を表示する
        print("Test Loss : ", scores[0])
        print("Test Acurracy : ", scores[1])
    
    if __name__ == "__main__":
        main()
    

    教師あり学習・テスト・評価

    さて、頑張ってコードを書いたのであとは実行してモデル作成をしましょう!
    そのままテスト画像を使った評価までしてしまいます
    ちなみに CPU にやらせるとめちゃめちゃ時間がかかると思うので、もし可能なら GPU 環境での実行をお勧めします
    環境によって GPU での実行方法が異なるのでここでは記載しませんが、気になったら「DeepLearing GPU」とか「TesorFlow GPU」とかで Google 先生に聞いてください 笑
    自分は Mac なので参考にした Mac 用の記事を載せておきます(Windows の人ごめん)

    qiita.com

    実行するぞ、えいや

    $ python animal_cnn.py

    僕の場合、出力結果は以下のようになりました

    f:id:yukun0519:20201123221308p:plain
    10_Result1

    学習に使った画像での正当性 (accuracy) は 99 % に収束していてなかなかいい感じです!
    ですが、テスト画像での正当性は 53.3 % しかない。。。むむむ

    精度向上

    精度向上の考え方としては主に 3 つあるそうです

    • データ量を増やす(学習画像の、枚数増量、反転、回転、平行移動、拡大 etc.)
    • ハイパーパラメーター・アルゴリズの改良
    • モデルを見直す(層やセルの数)

    今回は 1 番簡単で効果的なデータ増量の手法を試します
    具体的にはこんな感じ!

    • -20° から 20° まで 5° づつ回転(データ量 9 倍)
    • 反転(データ量 2 倍)

    同じ画像でも工夫することで 9 × 2 = 18 枚の別の画像として学習できます!
    (下の画像は例として 10° づつ回転)
    あ〜猫ちゃんがたくさんだぁ〜!!

    f:id:yukun0519:20201122142957p:plain
    11_Rotated_Reversed_Cat

    先ほど作成した、"gen_data.py" を "gen_data_augmented.py" として改良していきます
    基本的には画像データを回転させる処理と、テストデータの分類、保存するファイル名を変えただけです

    # cording: utf-8
    # gen_data_augmented.py
    from PIL import Image
    import os, glob
    import numpy as np
    from sklearn import model_selection
    
    classes = ["cat", "dog", "monkey"]
    num_classes = len(classes)
    image_size = 50  # 画像を 50 pixel * 50 pixel にサイズ縮小
    num_testdata = 100
    
    X_train = []  # 画像データ用リスト
    X_test = []
    Y_train = []  # データのラベル用リスト
    Y_test = []
    
    # 画像の読み込み
    for index, classlabel in enumerate(classes):
        photos_dir = "./" + classlabel
        files = glob.glob(photos_dir + "/*.jpg")  # glob で画像を探しに行く
        for i, file in enumerate(files):
            if i >= 300: break  # 300 枚で終了する
            image = Image.open(file)
            image = image.convert("RGB")  # RGB の 3 色に変換(0 ~ 255 で表現)
            image = image.resize((image_size, image_size))  #  画像のサイズ変更
            data = np.asarray(image)  # 数字の配列にする
            
            if i < num_testdata:
                X_test.append(data)
                Y_test.append(index)
            else:
                # 角度ごとに回転させた画像データと反転させた画像データを追加していく
                for angle in range(-20,20,5):
                    # 回転
                    img_r = image.rotate(angle)
                    data = np.asarray(img_r)
                    X_train.append(data)
                    Y_train.append(index)
                    # 反転
                    img_trans = image.transpose(Image.FLIP_LEFT_RIGHT)
                    data = np.asarray(img_trans)
                    X_train.append(data)
                    Y_train.append(index)
    
    # TensorFlow で扱い易いように NumPy に変換する
    X_train = np.array(X_train)
    X_test = np.array(X_test)
    Y_train = np.array(Y_train)
    Y_test = np.array(Y_test)
    
    xy = (X_train, X_test, Y_train, Y_test)
    np.save("./animal_aug.npy", xy)  # 保存
    

    出力ファイル名は animal_aug.npy としたので、"animal_cnn.py" で読みこむファイル名をこれに変更して実行します
    そして、その結果がこれ!

    f:id:yukun0519:20210117165701p:plain
    12_Result2

    テスト画像での正当性が 67 % と前回の 53.3 % より格段と良くなりました!
    いい感じです👍
    (でも、なんとか 70 % は超えたかった、、、!)

    まとめ

    今回は Python で動物の画像判定 AI を作成したきました!
    結果としてはもう少し改善が必要なものとなりましたが、まぁ初めてだしこんなもんでしょ?って正当化しておく
    正直、学習アルゴリズムの層とかセル数をどう工夫したらいいか、あんまり分かっていないので頑張って勉強しようと思います!

    この記事が誰かの参考になれば幸いです
    それでは〜👋