macに機械学習の環境をつくり、画像分類を学習させたモデルの作成とVison+CoreMLで画像を分類をするまでを試したのでそのメモ。
この記事で参考になるかもしれない情報
この記事は初心者が初めて機械学習をやってみた、という内容なので参考になるところはほとんどないと思いますが!
- tensorflow-macos を使えばintel+eGPU環境でもGPUを使ってトレーニング時間は短縮できるっぽい。
eGPU (Radeon RX 580) 接続 |
mlcompute .set_mlc_device(device_name="gpu") |
トレーニング 時間 |
---|---|---|
✔️ | 41.2s | |
231.5s | ||
✔️ | 606.9s |
- coremltools 4.0 は tensorflow-macos 環境(pre-release tensorflow_macos 0.1alpha1)ではエラーになる。coremltoolsのバグらしい。修正はされているがリリースはされていない。いたしかたなく tensorflow-macos を使わない環境を作って回避した。
ERROR:root:Constant Propagation pass failed: invalid version number '2.4.0-rc0'
動作確認環境
- macOS Big Sur 11.1
- Mac mini(2018) 3.2GHz 6コア Intel Core i7 メモリ 16GB
- 内蔵GPU Intel UHD Graphics 630
- eGPU Radeon RX 580 8GB (+ Sonnet eGFX Breakaway Box 550)
動作確認の流れ
-
- トレーニング環境構築
-
- 学習用画像の準備
-
- 学習用と検証用のデータの作成
-
- トレーニング
-
- mlmodel に変換
-
- Vison+CoreMLで推論
1. トレーニング環境構築
1-1) tensorflow-macos インストール
手順は参考にさせていただいたこちらの記事 【Apple Silicon M1 でtensorflow-macosを実行したらめちゃくちゃ速かった。】 と同じ。
Keras@mini2018 ~ % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/apple/tensorflow_macos/master/scripts/download_and_install.sh)"
Installation script for pre-release tensorflow_macos 0.1alpha1. Please visit https://github.com/apple/tensorflow_macos
for instructions and license information.
This script will download tensorflow_macos 0.1alpha1 and needed binary dependencies, then install them into a new
or existing Python 3.8 virtual enviornoment.
Continue [y/N]? y
(略)
./install_venv.sh will perform the following operations:
-> Create new python 3.8 virtual environment at /Users/Keras/tensorflow_macos_venv.
-> Install tensorflow_macos 0.1a1 into created virtual environment /Users/Keras/tensorflow_macos_venv.
-> Install bundled binary wheels for numpy-1.18.5, grpcio-1.33.2, h5py-2.10.0, scipy-1.5.4, tensorflow_addons-0.11.2+mlcompute into /Users/Keras/tensorflow_macos_venv.
Confirm [y/N]? y
(略)
TensorFlow and TensorFlow Addons with ML Compute for macOS 11.0 successfully installed.
To begin, activate the virtual environment:
. "/Users/Keras/tensorflow_macos_venv/bin/activate"
~
1-2) tensorflow-macosの仮想環境をアクティベート
Keras@mini2018 ~ % source /Users/Keras/tensorflow_macos_venv/bin/activate
(tensorflow_macos_venv) Keras@mini2018 ~ %
1-3) pillow のインストール
引き続きコマンドラインから python で画像を扱うライブラリ pillow
をインストールする。
(tensorflow_macos_venv) Keras@mini2018 ~ % pip install pillow
1-4) scikit-learn のインストール
Pythonの機械学習ライブラリの scikit-learn
をインストールする。
(tensorflow_macos_venv) Keras@mini2018 ~ % pip install scikit-learn
1-5) keras のインストール
TensorFlowのラッパーライブラリ keras
をインストールする。
(tensorflow_macos_venv) Keras@mini2018 ~ % pip install keras
1-6) flickrapi のインストール
機械学習用の画像を入手するため、写真投稿コミュニティのflickr を利用する。Pythonから flickr API を利用するためのライブラリがあるので、これをインストールする。提供元はこちらのサイト。
(tensorflow_macos_venv) Keras@mini2018 ~ % pip install flickrapi
2. 学習用画像の準備
機械学習に使う画像を 写真投稿コミュニティのflickr から入手する。
画像取得までの流れ
- flickr API key と secret を入手
- Python を使って画像を入手
2-1) flickr にアクセスして Sign Up する
2-2) App Garden にアクセスする
The App Gardenにアクセスすると、Flickrが提供するAPIの一覧を確認できる。
ここから Flickr APIを利用するための Key と Secret を入手する。
2-3) App Garden のページの左上の「Create an App」をクリック
2-4) Get your API Key をクリック
2-5) 非商用か商用かを選択する。今回は非商用を選択
2-6) アプリ名とアプリの目的を入力し「SUBMIT」をクリック。
2-7) KeyとSecretが表示されるのでメモる
2-8) 欲しい画像のキーワードを引数にとって画像をダウンロードするプログラムを準備
import urllib.request
import flickrapi
import time, os, sys
# ダウンロードしたい画像のキーワード
keyword = sys.argv[1]
# flickr アクセス用
api_key = "★★★入手した key をここに指定★★★"
api_secret = "★★★入手した secret をここに指定★★★"
# ダウンロードした画像の格納フォルダを作成
folder = keyword
if not os.path.exists(folder):
os.mkdir(folder)
# Flickr API ライブラリ インスタンス化
flickr = flickrapi.FlickrAPI(api_key, api_secret, format='parsed-json')
# flickrからキーワードで写真一覧を取得
response = flickr.photos.search(
text = keyword, # 取得する写真のキーワード
media = 'photos', # 写真のみ
safe_search = 1, # 不適切なコンテンツを除くための指定。1が一番安全。
per_page = 500, # 欲しい画像数を指定(1ページあたり最大500枚まで)
extras = 'url_q' # 150x150の画像ファイルURLをresponseに含める指定。
)
photos = response['photos']
print('response pages:' + str(photos['pages']) + ' perpage:' + str(photos['perpage']))
for photo in photos['photo']:
image_url = photo['url_q']
print(image_url)
filename = image_url.rsplit('/', 1)[1]
filepath = folder + '/' + filename
# URLからファイルをダウンロード
urllib.request.urlretrieve(image_url, filepath)
# スパム判定されないようにウェイトを入れておく
time.sleep(0.5)
参考:
2-9) 画像をダウンロードする
入手したい画像のキーワードを指定して画像をダウンロードする。
(tensorflow_macos_venv) Keras@mini2018 BotanicalPB % python downloader.py lilium
response pages:117 perpage:500
https://live.staticflickr.com/65535/50863926903_730d70a26d_q.jpg
https://live.staticflickr.com/1914/45868122311_b85a29090c_q.jpg
...
2-10) 不適切な画像を削除する。
ダウンロードした画像ファイルの中には、想定した画像とは異なるものが含まれるので、これを手作業で削除する。
3. 学習用と検証用のデータの作成
入手した画像を学習用と検証用のデータに加工する。
3-1) Numpy形式に加工するプログラムを準備
画像を読み込み、学習用と検証用のデータに分類し、それぞれnumpy形式に変換後、ファイルに保存するプログラムを準備。
※この記事ではマーガレット、スターチス、ゆりの花の画像の分類を行うため、それぞれの画像を240枚準備済みの前提。
from PIL import Image
from sklearn import model_selection
import os
import glob
import numpy as np
# マーガレット、スターチス、ゆり の画像分類
classes = ["marguerite", "statice", "lilium"]
# 全画像ファイル数
file_count = 240
# 検証用ファイル数
test_file_count = 40
# 50x50pxの画像で学習させる
image_size = 50
# 学習用のデータと検証用のデータの器
X_train = [] # 学習用画像
X_test = [] # 学習用のラベル
Y_train = [] # 検証用画像
Y_test = [] # 検証用のラベル
for class_index, class_label in enumerate(classes):
# 分類ごとの画像ファイルを取得
files = glob.glob("./" + class_label + "/*.jpg")
files = files[:file_count]
print("Label: " + class_label + " File Count: " + str(len(files)))
for file_index, file in enumerate(files):
# ファイル読み込み
image = Image.open(file)
# RGB(8bit x 3)に変換
image = image.convert("RGB")
# リサイズ
image = image.resize((image_size, image_size))
# 配列として扱えるようにする
data = np.asarray(image)
# 学習用と検証用に画像を振り分ける
if file_index < test_file_count:
X_test.append(data)
Y_test.append(class_index)
else:
X_train.append(data)
Y_train.append(class_index)
# 画像データを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("./botanical.npy", xy)
3-2) Numpy形式に加工する
プログラムを実行する。
(tensorflow_macos_venv) Keras@mini2018 BotanicalPB % python data_generator.py
Label: marguerite File Count: 240
Label: statice File Count: 240
Label: lilium File Count: 240
4. トレーニング
4-1) トレーニングプログラムの準備
画像分類の学習済みモデルを作成するため、KerasのCIFAR-10のコードを流用したプログラムを準備する。
※この記事を書いている途中(2021/1月中旬くらい)にKerasのGithubから CIFAR-10のコードが削除(移動?)されてしまった模様。
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.utils import np_utils
import keras, time
import numpy as np
classes = ["marguerite", "statice", "lilium"]
def main():
x_train, x_test, y_train, y_test = np.load("./botanical.npy", allow_pickle=True)
# データの正規化。0〜255 -> 0〜1に変換
x_train = x_train.astype("float") / 256
x_test = x_test.astype("float") / 256
# one-hotベクトルを作る
y_train = np_utils.to_categorical(y_train, len(classes))
y_test = np_utils.to_categorical(y_test, len(classes))
# モデルを作る
model = generate_model()
model.summary()
# トレーニング
start = time.perf_counter()
model.fit(x_train, y_train, batch_size=32, epochs=100)
end = time.perf_counter()
print("Elapsed time : {0} s.".format(end-start))
# モデルの精度を測る
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=1)
print('test_loss: ', test_loss)
print('test_acc: ', test_acc)
# モデルの保存
model.save('./botanical_cnn.h5')
# モデルを作る(CIFAR-10)
def generate_model():
model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same', input_shape=(50,50,3)))
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))
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))
# 出力は"marguerite", "statice", "lilium" の3つ
model.add(Dense(3))
model.add(Activation('softmax'))
opt = keras.optimizers.RMSprop(lr=0.0001, decay=1e-6)
model.compile(loss='categorical_crossentropy',
optimizer=opt,
metrics=['accuracy'])
return model
if __name__ == '__main__':
main()
4-2) トレーニング実行
(tensorflow_macos_venv) Keras@mini2018 BotanicalPB % python botanical_cnn.py
- CPUでのトレーニング結果
Elapsed time : 231.572187988 s.
4/4 [==============================] - 0s 12ms/step - loss: 2.6364 - accuracy: 0.7833
test_loss: 2.6363558769226074
test_acc: 0.7833333611488342
- eGPU接続状態でのトレーニング結果
Elapsed time : 41.286664788 s.
4/4 [==============================] - 0s 11ms/step - loss: 0.7714 - accuracy: 0.8750
test_loss: 0.7714245915412903
test_acc: 0.875
下図はトレーニング時のGPU負荷の状況。eGPUに負荷がかかっていることがわかる。
- eGPU未接続・GPUトレーニング有効化
参考にさせていただいたこちらの記事 【Apple Silicon M1 でtensorflow-macosを実行したらめちゃくちゃ速かった。】 にあるようにtensorflowを動作させるデバイスを次のようにすることで選択できるので、gpu
を指定して内蔵GPUでトレーニングさせる。
from tensorflow.python.compiler.mlcompute import mlcompute
mlcompute.set_mlc_device(device_name="gpu")
結果はやたら遅い。
Elapsed time : 606.924531516 s.
4/4 [==============================] - 1s 113ms/step - loss: 0.7534 - accuracy: 0.8500
test_loss: 0.7534401416778564
test_acc: 0.8500000238418579
5. mlmodel に変換
学習済みのモデルを Vison+CoreML から利用できるように mlmodel 形式に変換する。
変換はAppleが提供している coremltools を利用する。
で、tensorflow-macos(0.1alpha1) + coremltools4.0 環境では変換実行時にエラーになる。
(tensorflow_macos_venv) Keras@mini2018 BotanicalPB % pip list | grep coremltools
coremltools 4.0
(tensorflow_macos_venv) Keras@mini2018 BotanicalPB % python convert_h5.py
WARNING:root:scikit-learn version 0.24.1 is not supported. Minimum required version: 0.17. Maximum required version: 0.19.2. Disabling scikit-learn conversion API.
WARNING:root:TensorFlow version 2.4.0-rc0 detected. Last version known to be fully compatible is 2.3.1 .
WARNING:root:Keras version 2.4.3 detected. Last version known to be fully compatible of Keras is 2.2.4 .
Running TensorFlow Graph Passes: 0%| | 0/5 [00:00<?, ? passes/s]ERROR:root:Constant Propagation pass failed: invalid version number '2.4.0-rc0'
Traceback (most recent call last):
File "/Users/Keras/tensorflow_macos_venv/lib/python3.8/site-packages/coremltools/converters/mil/frontend/tensorflow/tf_graph_pass/constant_propagation.py", line 79, in _constant_propagation
if tf.__version__ < _StrictVersion("1.13.1"):
File "/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/distutils/version.py", line 64, in __gt__
c = self._cmp(other)
File "/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/distutils/version.py", line 168, in _cmp
other = StrictVersion(other)
File "/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/distutils/version.py", line 40, in __init__
self.parse(vstring)
File "/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/distutils/version.py", line 137, in parse
raise ValueError("invalid version number '%s'" % vstring)
ValueError: invalid version number '2.4.0-rc0'
この問題はすでにcoremltools側で修正されているようだが 、リリースされていないので、ここでは、tensorflow-macos を使わない環境を別に用意する。
5-1) coremltools利用環境を作る
新たにPythonの仮想環境を作る。
Keras@mini2018 BotanicalPB % python3 -m venv env
Keras@mini2018 BotanicalPB % source env/bin/activate
(env) Keras@mini2018 BotanicalPB % pip install tensorflow
(env) Keras@mini2018 BotanicalPB % pip install keras
(env) Keras@mini2018 BotanicalPB % pip install coremltools
(env) Keras@mini2018 BotanicalPB % pip install pillow
5-2) 変換プログラムの準備
import coremltools as ct
from PIL import Image
# ラベル定義
class_labels = ['marguerite', 'statice', 'lilium']
classifier_config = ct.ClassifierConfig(class_labels)
# mlmodelに変換
coreml_model = ct.converters.convert('botanical_cnn.h5',
inputs=[ct.ImageType()],
classifier_config=classifier_config,
source='TensorFlow',)
# mlmodelを保存
coreml_model.save('./botanical_cnn.mlmodel')
# 推論して mlmodel に正しく変換できたか確認
# https://coremltools.readme.io/docs/model-prediction
test_image = Image.open("lily2.jpg").resize((50, 50))
data = {'conv2d_input': test_image}
out_dict = coreml_model.predict(data)
print(out_dict['classLabel'])
classifier_config=classifier_config
でラベルの配列を指定しないと、Vision+CoreML の実行時にエラーになるので重要。この部分、coremltools4.0での解決方法が見つからなくてcoremltoolsのドキュメント を眺めていて解決。
もう一点、推論に与える画像の指定で'conv2d_input'
はmlmodel変換後にXcodeで確認できる(Xcodeで確認するなら、Preview タブで推論の動作確認できちゃうのでmlmodel変換時に推論確認する意義は小さいかも)。
5-3) 変換の実施
(env) Keras@mini2018 BotanicalPB % python convert_h5.py
WARNING:root:TensorFlow version 2.4.1 detected. Last version known to be fully compatible is 2.3.1 .
WARNING:root:Keras version 2.4.3 detected. Last version known to be fully compatible of Keras is 2.2.4 .
Running TensorFlow Graph Passes: 100%|███████| 5/5 [00:00<00:00, 16.11 passes/s]
Converting Frontend ==> MIL Ops: 100%|███████| 36/36 [00:00<00:00, 566.06 ops/s]
Running MIL optimization passes: 100%|████| 17/17 [00:00<00:00, 404.85 passes/s]
Translating MIL ==> MLModel Ops: 100%|███████| 53/53 [00:00<00:00, 113.73 ops/s]
marguerite
ワーニングが出まくっているが、一応変換はできている。
mlmodel変換後の推論もできている(マーガレットの画像を与えて分類できている)。
6. Vison+CoreMLで推論
変換したmlmodelを使って、任意の画像の推論をする。
Playgroundを使って試してみた結果は次の通り。
- mlmodelを読み込んで推論するコード
import UIKit
import CoreML
import Vision
guard let model = try? VNCoreMLModel(for: botanical_cnn(configuration: MLModelConfiguration()).model) else {
fatalError()
}
let request = VNCoreMLRequest(model: model) { request, error in
guard let results = request.results as? [VNClassificationObservation],
let result = results.first else {
fatalError()
}
print("Confidence: \(result.confidence * 100) % Label: \(result.identifier)")
}
guard let image = UIImage(named: "lily.jpg"),
let cgimage = image.cgImage else { fatalError() }
let handler = VNImageRequestHandler(cgImage: cgimage, options: [:])
try? handler.perform([request])
- Playground の設定
変換した mlmodel と分類したい画像(ここでは「ゆり」)を組み込んでおく。
- 実行結果
画像分類ができた。