今回はロジスティック回帰で画像とラベルの対応関係を教師付き学習させて画像分類の精度を検証して遊んでみました。
ちなみに前回は画像を教師なし学習のk-means法でカテゴリ分けしました。
データセット
17 Category Flower Datasetで公開されている花の画像を使いました。
$ wget http://www.robots.ox.ac.uk/~vgg/data/flowers/17/17flowers.tgz $ tar xvfz 17flowers.tgz
イメージファイル名は"image_連番.jpg"の形式で品種との対応は下記みたいです。
連番 | (連番 - 1) / 80 | 品種 |
---|---|---|
0001 - 0080 | 0 | Daffodil ラッパズイセン |
0081 - 0160 | 1 | Snowdrop スノードロップ |
0161 - 0240 | 2 | LilyValley スズラン |
0241 - 0320 | 3 | Bluebell ブルーベル |
0321 - 0400 | 4 | Crocus クロッカス |
0401 - 0480 | 5 | Iris アヤメ |
0481 - 0560 | 6 | Tigerlily オニユリ |
0561 - 0640 | 7 | Tulip チューリップ |
0641 - 0720 | 8 | Fritillary クロユリ |
0721 - 0800 | 9 | Sunflower ヒマワリ |
0801 - 0880 | 10 | Daisy ヒナギク |
0881 - 0960 | 11 | ColtsFoot フキタンポポ |
0961 - 1040 | 12 | Dandelion タンポポ |
1041 - 1120 | 13 | Cowslip キバナノクリンザクラ |
1121 - 1200 | 14 | Buttercup キンポウゲ |
1201 - 1280 | 15 | Windflower アネモネ |
1281 - 1360 | 16 | Pansy パンジー |
検証
特徴量にはsurfのbag-of-Visual Words, haralick, edginess_sobelを使いました。
それぞれの組み合わせで交差検証して正解率を求めて混合行列とROC曲線で可視化してみました。
# | surf | haralick | edginess_sobel | 正解率 |
---|---|---|---|---|
1 | ◯ | × | × | 59.0% |
2 | × | ◯ | × | 16.9% |
3 | × | × | ◯ | 9.2% |
4 | ◯ | ◯ | × | 60.4% |
5 | ◯ | × | ◯ | 59.0% |
6 | × | ◯ | ◯ | 18.4% |
7 | ◯ | ◯ | ◯ | 60.4% |
60.4%は低い。。。
混合行列とROC曲線1: surfのみ
混合行列とROC曲線2: haralickのみ
混合行列とROC曲線3: edginess_sobelのみ
混合行列とROC曲線4: surf/haralick
混合行列とROC曲線5: surf/edginess_sobel
混合行列とROC曲線6: haralick/edginess_sobel
混合行列とROC曲線7: surf/haralick/edginess_sobel
コード
今回のコードです。
import mahotas as mh import numpy as np from glob import glob from mahotas.features import surf from sklearn.cluster import KMeans from sklearn.cross_validation import ShuffleSplit from sklearn.metrics import confusion_matrix from sklearn.metrics import precision_recall_curve, roc_curve from sklearn.metrics import auc from collections import defaultdict from matplotlib import pylab from sklearn.linear_model import LogisticRegression def save_plot_confusion_matrix(file_name, cms, y_labels): cm_avg = np.mean(cms, axis=0) cm_norm = cm_avg / np.sum(cm_avg, axis=0) pylab.clf() fig = pylab.figure(figsize=(11,10)) ax = fig.add_subplot(111) res = ax.imshow(cm_norm, cmap='Blues', interpolation='nearest') for i, cas in enumerate(cm_avg): for j, c in enumerate(cas): n = c if n > 0: pylab.text(j-.2, i+.2, n, fontsize=12, color='red') cb = fig.colorbar(res) ax.set_xticks(range(len(y_labels))) ax.xaxis.set_ticks_position("bottom") ax.set_xticklabels(y_labels, rotation=270) ax.set_yticks(range(len(y_labels))) ax.set_yticklabels(y_labels) pylab.title('Confusion Matrix') pylab.xlabel('Predicted class') pylab.ylabel('True class') pylab.grid(False) pylab.savefig(file_name) def save_plot_roc(file_name, y_labels, roc_scores, tprs, fprs): pylab.clf() pylab.figure(figsize=(35, 35)) column = 4 row = int(len(y_labels) / column) + 1 for i, y_label in enumerate(y_labels): pylab.subplot(row, column, i+1) auc_score = np.mean(roc_scores[y_label]) label = '%s vs rest' % y_label pylab.grid(True) pylab.plot([0, 1], [0, 1], 'k--') # 混合行列に合わせて平均にしたいけどとりあえず全てplotしとく for j, tpr in enumerate(tprs[y_label]): pylab.plot(fprs[y_label][j], tpr) pylab.fill_between(fprs[y_label][j], tpr, alpha=0.5) pylab.xlim([0.0, 1.0]) pylab.ylim([0.0, 1.0]) pylab.xlabel('False Positive Rate') pylab.ylabel('True Positive Rate') pylab.title('ROC curve (AUC = %0.2f) / %s' % (auc_score, label), verticalalignment="bottom") pylab.legend(loc="lower right") pylab.savefig(file_name, bbox_inches="tight") def try_model(X, y, y_labels, confusion_matrix_filename, roc_filename): cv = ShuffleSplit(n=len(X), n_iter=10, test_size=0.3, indices=True, random_state=0) cms = [] # 混合行列 scores = [] # 正解率 roc_scores = defaultdict(list) tprs = defaultdict(list) fprs = defaultdict(list) for train, test in cv: X_train, y_train = X[train], y[train] X_test, y_test = X[test], y[test] clf = LogisticRegression() clf.fit(X_train, y_train) scores.append(clf.score(X_test, y_test)) y_pred = clf.predict(X_test) cms.append(confusion_matrix(y_test, y_pred, y_labels)) proba = clf.predict_proba(X_test) for i, label in enumerate(y_labels): y_label_test = np.asarray(y_test == label, dtype=int) proba_label = proba[:, i] fpr, tpr, roc_thresholds = roc_curve(y_label_test, proba_label) roc_scores[label].append(auc(fpr, tpr)) tprs[label].append(tpr) fprs[label].append(fpr) summary = (np.mean(scores), np.std(scores)) print "accuracy mean:%.3f\tstd:%.3f\t" % summary # 結果の生成 save_plot_confusion_matrix(confusion_matrix_filename, np.asarray(cms), y_labels) save_plot_roc(roc_filename, y_labels, roc_scores, tprs, fprs) feature_category_num = 512 images = glob('./image_*.jpg') # 画像ごとの局所特徴量を取り出す alldescriptors = [] for im in images: im = mh.imread(im, as_grey=True) im = im.astype(np.uint8) alldescriptors.append(surf.surf(im, descriptor_only=True)) # 局所特徴量からVisual Wordsを決める concatenated = np.concatenate(alldescriptors) km = KMeans(feature_category_num) km.fit(concatenated) # 各画像のbag-of-Visual Wordsを求める features = [] # 特徴量1: surf for d in alldescriptors: c = km.predict(d) features.append(np.array([np.sum(c == ci) for ci in range(feature_category_num)])) haralicks = [] # 特徴量2: haralick edginess_sobels = [] # 特徴量3: edginess_sobel for im in images: im = mh.imread(im, as_grey=True) im = im.astype(np.uint8) edges = mh.sobel(im, just_filter=True) edges = edges.ravel() edginess_sobels.append([np.sqrt(np.dot(edges, edges)),1]) # 一応1いれとく haralicks.append(mh.features.haralick(im).mean(0)) y_labels = ['Daffodil', 'Snowdrop', 'LilyValley', 'Bluebell', 'Crocus', 'Iris', 'Tigerlily', 'Tulip', 'Fritillary', 'Sunflower', 'Daisy', 'ColtsFoot', 'Dandelion', 'Cowslip', 'Buttercup', 'Windflower', 'Pansy'] y = [] for image in images: n = int(image[len('./image_'):-len('.jpg')]) n = (n - 1) / 80 y.append(y_labels[n]) y = np.array(y) # 1. surf X = np.array(features) try_model(np.array(X, dtype=int), y, y_labels, "confusion_matrix1.png", "roc1.png") # accuracy mean:0.590 std:0.013 # 2. haralick X = np.array(haralicks) try_model(np.array(X, dtype=int), y, y_labels, "confusion_matrix2.png", "roc2.png") # accuracy mean:0.169 std:0.014 # 3. edginess_sobel X = np.array(edginess_sobels) try_model(np.array(X, dtype=int), y, y_labels, "confusion_matrix3.png", "roc3.png") # accuracy mean:0.092 std:0.009 # 4. surf, haralick X = [] for i, _ in enumerate(y): X.append(np.concatenate((features[i], haralicks[i]))) try_model(np.array(X, dtype=int), y, y_labels, "confusion_matrix4.png", "roc4.png") # accuracy mean:0.604 std:0.014 # 5. surf, edginess_sobel X = [] for i, _ in enumerate(y): X.append(np.concatenate((features[i], edginess_sobels[i]))) try_model(np.array(X, dtype=int), y, y_labels, "confusion_matrix5.png", "roc5.png") # accuracy mean:0.590 std:0.016 # 6. haralick, edginess_sobel X = [] for i, _ in enumerate(y): X.append(np.concatenate((haralicks[i], edginess_sobels[i]))) try_model(np.array(X, dtype=int), y, y_labels, "confusion_matrix6.png", "roc6.png") # accuracy mean:0.184 std:0.016 # 7, surf, haralick, edginess_sobel X = [] for i, _ in enumerate(y): X.append(np.concatenate((features[i], haralicks[i], edginess_sobels[i]))) try_model(np.array(X, dtype=int), y, y_labels, "confusion_matrix7.png", "roc7.png") # accuracy mean:0.604 std:0.013
思ったこととか
もうちょい前処理の工夫や特徴量の追加やパラメータのチューニングなどをすれば精度は上げられるかもしれないです。 あとはロジスティック回帰以外の分類器についても比較したりするのも面白そうに思います。 まだ遊ぶ余地はありそうです。