機械学習(SVM)で花とそれ以外の部分を自動分類してみた <その3>
こんにちは。らずべりーです。
前回に引き続き、機械学習による分類です。
その1とその2は以下をご参照のこと。
plant-raspberrypi3.hatenablog.com
plant-raspberrypi3.hatenablog.com
目標
紅色の花の部分画像と、それ以外(葉や枝)の部分画像を自動で区別してくれる分類器を作成する
まだ難しい方法はハードルが高いので、上記のような単純な課題設定にしてみました。 紅色の花の画像として、自分で過去に撮影していたツバキとツツジの写真を使用しました。
実行環境
手順
- 一枚の画像から部分画像を作成
- 部分画像をフォルダ分け (花の部分かそれ以外か)
- 各フォルダからランダムに同数の画像を読み込んでラベルつけ
- 個々の画像データから、基本統計量を計算
- サポートベクターマシンで学習させ、分類器を作成
- 分類器の性能をテスト
今回は4の続きから。
4. 個々の画像データの基本統計量を計算
4-4. 取り込んだ画像の基本統計量計算をバルクで実行
4-1から4-3までのコードを、バルクで処理できるようにまとめてみました。
まず、バルクで画像の読み込み&リストに格納。
import pandas as pd import numpy as np import scipy.stats as ss import os import cv2 import random #画像をバルクで読み込むための関数 def img_bulk_read_cv2(path1,format=".png"): os.chdir(path1) all_files = os.listdir(path1) file_names = [i for i in all_files if i.endswith(format)] png_files = [cv2.imread(i) for i in file_names] return file_names, png_files #BGRデータをH,S,Vに変換する関数 def bgr2h_s_v(img): return cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV)) #画像データの基本統計量を計算する関数 def img_stats(img): array1 = img.flatten() #2次元画像を1次元画像に変換 index_list = list(pd.Series(array1).describe().index) value_list = list(pd.Series(array1).describe().values) mode = float(ss.mode(array1)[0]) kurtosis = ss.kurtosis(array1) skew = ss.skew(array1) index_list += ["mode","kurtosis","skew"] value_list += [mode, kurtosis, skew] return pd.Series(value_list, index=index_list), np.array(value_list) #画像をバルクで読み込み、リストに格納 path1 = "/usr/local/working/20171105-tsubaki_tsutsuji/flower" path2 = "/usr/local/working/20171105-tsubaki_tsutsuji/others" path3 = "/usr/local/working/20171105-tsubaki_tsutsuji/partial" fn1, flower_raw = img_bulk_read_cv2(path1, format="jpg") fn2, others_raw = img_bulk_read_cv2(path2, format="jpg") fn3, partial_raw = img_bulk_read_cv2(path3, format="jpg")
flower
データの一例
others
データの一例
partial
データの一例
各グループの画像の数を最小のものに揃えます。
機械学習の時は各グループのサンプル数ができるだけ揃っていた方が良いという噂、、、
#各グループの画像の数を確認 print(len(flower_raw),",",len(others_raw),",",len(partial_raw)) #137 , 799 , 88 min_len = min(len(flower_raw),len(others_raw),len(partial_raw)) flower = random.sample(flower_raw,min_len) others = random.sample(others_raw, min_len) partial = random.sample(partial_raw, min_len)
リストの中身をランダムに抽出する方法については以下のサイトを参考にさせていただきました。
各画像をグループ別にH, S, Vに分離。
f_h, f_s, f_v = [], [], [] for i in flower: h,s,v = bgr2h_s_v(i) f_h.append(h) f_s.append(s) f_v.append(v) o_h, o_s, o_v = [], [], [] for i in others: h,s,v = bgr2h_s_v(i) o_h.append(h) o_s.append(s) o_v.append(v) p_h, p_s, p_v = [], [], [] for i in partial: h,s,v = bgr2h_s_v(i) p_h.append(h) p_s.append(s) p_v.append(v)
各画像の基本統計量を計算。
img_list = [f_h, f_s, f_v, o_h, o_s, o_v, p_h, p_s, p_v] f_ha, f_sa, f_va = [],[],[] o_ha, o_sa, o_va = [],[],[] p_ha, p_sa, p_va = [],[],[] array_list = [f_ha, f_sa, f_va, o_ha, o_sa, o_va, p_ha, p_sa, p_va] for i,j in zip(img_list,array_list): for x in i: stat_df, stat_array = img_stats(x) j.append(stat_array)
forループが二重になっているせいか、ここの処理はちょっと時間がかかりました(1分くらい?)。
各グループごとにH, S, Vの基本統計量を結合し、一つのデータにまとめます。
f_data = np.c_[np.array(f_ha), np.array(f_sa),np.array(f_va)] o_data = np.c_[np.array(o_ha), np.array(o_sa),np.array(o_va)] p_data = np.c_[np.array(p_ha), np.array(p_sa),np.array(p_va)]
numpy.array
の結合の仕方については以下のサイト参照。
np.concatenate()
やhstack()
、vstack()
といった方法もあるようです。(まだ使いこなせていない。)
ここまでで基本統計量データの準備ができました。
5. サポートベクターマシンで学習させ、分類器を作成 〜 6. 分類器の性能をテスト
4までで準備した3グループの基本統計量データを1つにまとめ、ちゃんと結合できているか調べます。
次に、データの正解ラベルを作成します。
今回、花の部分(flower
)は2、一部のみ花の部分が含まれているもの(partial
)は1、花の部分が含まれていないもの(others
)は0としました。
#3グループのデータ data = np.r_[f_data, o_data, p_data] print(pd.DataFrame(data).shape) #(264, 33) #3グループのラベル labels = np.r_[np.ones(len(f_ha))*2, np.zeros(len(o_ha)), np.ones(len(p_ha))] #flowerとothersのみのデータ data2 = np.r_[f_data, o_data] #flowerとothersのみのラベル labels2 = np.r_[np.ones(len(f_ha))*2, np.zeros(len(o_ha))]
いよいよ機械学習です!!!
分類問題では(深層学習以外で)最も評判が良さげなSVMにトライしてみました。
SVMとはなんぞや、ということについては他のサイトに説明を任せることにします(あまりちゃんと理解できていない)。
個人的にこの辺りのサイトの解説がわかりやすいと感じました。
早速、scikit-learnのsvmを使ってやってみます。
公式ドキュメントは以下。
sklearn.svm.SVC — scikit-learn 0.19.1 documentation
ですが、まだちょっと敷居が高かったので、とりあえず他の方々がやっているのを真似っこしてみるところからはじめました。
公式ドキュメントもおいおい詳しく見ていきたいです。
今回はグリッドサーチなどは行わず、クロスバリデーションと適当に調整したパラメータで学習してみました。
まずはじめに、flower
とothers
の2種類に分類してみました。
from sklearn import svm from sklearn.cross_validation import train_test_split from sklearn.metrics import classification_report, confusion_matrix # 訓練データとテストデータに分割 X_train2, X_test2, y_train2, y_test2 = train_test_split(data2, labels2, test_size=0.2, random_state=0) #svmによるモデルの作成 clf2 = svm.SVC(C=1, cache_size=200, decision_function_shape='ovr', degree=3, gamma=0.00001, kernel='rbf') #モデルを訓練データで学習 clf2.fit(X_train2, y_train2)
テストデータを用いて、学習したモデルの性能を評価します。
y_true2, y_pred2 = y_test2, clf2.predict(X_test2) print(classification_report(y_true2, y_pred2)) # precision recall f1-score support # # 0.0 1.00 1.00 1.00 18 # 2.0 1.00 1.00 1.00 18 # # avg / total 1.00 1.00 1.00 36 print(confusion_matrix(y_true2, y_pred2)) #[[18 0] #[ 0 18]]
各グループ18ずつ、合計36のデータ全てが正しく分類できているようです。
問題が簡単すぎたんでしょうか???
次に、partial
のデータも入れて、3種類に分類するのを試しました。
X_train, X_test, y_train, y_test = train_test_split(data, labels, test_size=0.2, random_state=0) clf = svm.SVC(C=1, cache_size=200, decision_function_shape='ovr', degree=3, gamma=0.00001, kernel='rbf') clf.fit(X_train, y_train) y_true, y_pred = y_test, clf.predict(X_test) print(classification_report(y_true, y_pred)) # precision recall f1-score support # # 0.0 0.77 0.89 0.83 19 # 1.0 0.86 0.67 0.75 18 # 2.0 0.94 1.00 0.97 16 # # avg / total 0.85 0.85 0.84 53 print(confusion_matrix(y_true, y_pred)) #[[17 2 0] # [ 5 12 1] # [ 0 0 16]]
partial
データの分類が難しいようですが、85%程度は予測できているみたいです。
グリッドサーチを行えば、もっと良いパラメータが見つかるかも??
confusion matrix
がそのままだと見づらかったので、pandas
で見やすく表示しなおしてみます。
cm = confusion_matrix(y_true, y_pred) pd.DataFrame(cm/cm.sum(axis=1)).round(2)
横が予測データ、縦が正解ラベルです。
2がflower
、1がpartial
、0がothers
を表します。
どうもothers
とpartial
がうまく見分けられないのが精度が上がらない原因のようです。
flower
についてはほぼ正しく分類できている様子なので、今後このモデルを使って画像認識的なことができないか試していきたいところ。
長い道のりだった、、、:(´ཀ`」 ∠):
2017/11/13 追記 scikit-learnについて、一部コードに仕様変更があるようです。
以下の記事にまとめました。