読者です 読者をやめる 読者になる 読者になる

drilldripper’s blog

機械学習とソフトウェア開発を頑張ってます

AWSの無料枠とAWS Educateの登録

AWSの無料枠とAWS Educate

AWSは12ヶ月間の間、一定の範囲内で各種サービスに無料でアクセスすることができます。無料アクセスの中にはAWSの代表的なサービスであるEC2やS3、サーバレスアーキテクチャを実現するために利用するLambdaなどが含まれています。

無料枠は非常に充実しており、試しに少し遊んでみるといったような用途であれば無料枠の中で収まることが多いと思います。しかし無料枠で使用出来ないサービスもいくつか存在します。例えばAmazon Machine Learning*1などが該当します。

Amazon Machine Learning は現在 AWS 無料利用枠でご利用できないことにご注意ください。 料金 - Amazon Machine Learning | AWS

これらの機能を使うには課金が必要になりますが、学生であればAWS Educateというアカデミックライセンスを申請することで追加のクレジットを取得することができます。 追加のクレジットを使うことで、無料枠を超える機能の評価を課金なしで行えるようになります。

またその他の特典として、AWSのトレーニングコースへのアクセスなどが行えるようになります。

AWS Educate - 教育 | AWS

GitHub Education経由でAWS Educateに登録する

AWS EducateはGitHub EducationのStudent Developer Packを経由して登録を行うと、通常よりも多くクレジットを受け取ることができます。

Student Developer Pack members receive up to $110 in bonus AWS credits for a total of $75-$150 Learn How to Code Using the Student Developer Pack - GitHub Education

AWS Educateの登録条件を満たしている学生は、Github Educationの登録条件も満たしている可能性が高いと思います。またGitHub Educationに登録すると、GitHubのプライベートリポジトリを無料で作成できるようになります。

AWSGithub Educationの登録は、申請に学校のメールアドレスを使用する必要があります。注意点としてどちらのサービスもアカウントの認証に時間がかかります。サービスを使いたいと思ったら早めに登録すると良いでしょう。

AWSの初期設定

AWSの操作は基本的にIAM(Identity and Access Management)ユーザで行います。アカウントごとに細かく権限を設定することができるので、アカウントを乗っ取られた際の被害を最小限に抑えることができます。またルートアカウントでユーザの権限を剥奪することができるという利点があります。複数人でAWSを利用しない場合でも、IAMは設定しましょう。

AWSアカウント作ったらこれだけはやっとけ!IAMユーザーとAuthyを使ったMFAで2段階認証 - Qiita

また課金額の監視を行ってアラートを飛ばす設定を行っておくと、安心してサービスを利用することができるようになると思います。

AWSで無料枠を超えた時にアラートを飛ばす - Qiita

*1:Amazon Machine Learningでは機械学習のモデル構築やアルゴリズムを抽象化することによって、高度な専門知識が無くても簡単にデータの解析を行うことが出来るようになります。

OpenCVの補助として使うscikit-image ~同時生起行列特徴量~

OpenCV

画像処理の代表的なライブラリといえばOpenCVだと思います。C++Pythonなど複数の言語で使用することができるため、アプリケーションへ組み込む際に利用することも多いと思います。

OpenCVにはたくさんの関数が実装されていて非常に便利ですが、画像処理の範囲が広いため、網羅しきれていない機能が多々ありあます。私は特に画像特徴量を扱う機能が心もとないと感じています。

scikit-image

scikit-imageはPythonの画像処理ライブラリです。このライブラリはSciPyというPythonの科学ライブラリから派生したものです。同じ経緯で作られたscikit-learnは機械学習ライブラリとして広く使われています。

scikit-imageはその出自から、特徴量や認識に関するアルゴリズムに強いという特徴があります。

Python版のOpenCVとscikit-imageは画像をnumpyで取り扱っています。そのおかげでライブラリを使う際にデータ構造の変換を行う必要がありません。

つまりOpenCVにない機能をscikit-imageからつまみ食いして使うことができます。もちろんその逆も可能です。

導入方法

pipかAnacondaからインストールするのが楽だと思います。

pip install scikit-image
conda install scikit-image

個人的にはAnacondaを利用することをおすすめします。Anacondaのパッケージマネージャであるcondaは、画像処理や機械学習などの科学系ライブラリをバイナリで配布してくれているため、簡単に導入することができます。

pipを使ったインストールでは手元の環境でコンパイルする必要があります。このコンパイルは特にWindowsだとよくコケます。

またAnacondaは強力なバージョン管理機能と仮想環境管理機能を提供してくれます。これは共存できないライブラリをインストールする際や開発を環境を分ける際に役に立ちます。

その他詳しい説明は以下のページを参考にすると良いと思います。

データサイエンティストを目指す人のpython環境構築 2016 - Qiita

同時生起行列特徴量

OpenCVに実装されていて、scikit-imageに実装されていない機能の例として同時生起行列(GLCM:Gray-Level-Co-Occurrence Matrix)を使ったテクスチャ特徴量を取り上げます。

まず同時生起行列を求めます。同時正規行列は任意の角度の2つの離れた画素の変位からを計算し、変位の出現頻度を計算したものです。以下のスライドの20~22ページあたりを読むと概要がわかると思います。

画像解析論(6)東京工業大学

scikit-imageではgreycomatrixとして実装されています。

Module: feature — skimage v0.13dev docs greycomatrix

次に同時生起行列からテクスチャ特徴量を求めます。テクスチャ特徴量は同時生起行列の統計量として表されます。定義はgreycopropsのドキュメントに示されているので、そちらを確認してください。

Module: feature — skimage v0.13dev docs greycoprops

例えば計算できる特徴量のひとつに相違度(dissimilarity)があります。相違度は同時生起行列内の値の差が大きくなるほど大きな値になります。

ソースコード

画像サイズと同じ大きさのテクスチャ特徴量配列(テクスチャ特徴量)を作成するソースコードです。

# -*- coding: utf-8 -*-
import numpy as np
import cv2
from skimage.feature import greycomatrix, greycoprops

def make_glcm_features(input_image, feature):
    glcm_feature = np.zeros((input_image.shape[0], input_image.shape[1]))

    for i in range(input_image.shape[0] ):
        print(i)
        for j in range(input_image.shape[1] ):
            # 境界値処理
            if i <3 or j <3 or i > input_image.shape[0] - 4 or j > input_image.shape[1] - 4:
                glcm_feature[i,j]= 0
                continue
            # 7x7のウィンドウで画像を切り取る
            glcmWindow = input_image[i-3: i+4, j-3 : j+4]
            # 0度と90度の同時生起行列を計算する
            glcm_x = greycomatrix(glcmWindow, [1], [0])
            glcm_y = greycomatrix(glcmWindow, [1], [90])
            # テクスチャ特徴量を計算
            texture_feature_x = greycoprops(glcm_x, feature)
            texture_feature_y = greycoprops(glcm_y, feature)
            glcm_feature[i,j] = (texture_feature_x + texture_feature_y)/2
    return glcm_feature

if __name__ == "__main__":
    file_name = "hoge.png"
    # 画像読み込み(OpenCVを使用)
    image = cv2.imread(file_name)
    # グレースケールに変換(OpenCVを使用)
    image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    # 計算する二次特徴量を選ぶ
    feature_names = ['contrast', 'dissimilarity', 'homogeneity', 'ASM', 'correlation']
    for feature_name in feature_names:
        print (feature_name)
        glcm_feature = make_glcm_features(image, feature_name)
        # 特徴量配列を保存
        np.save(file_name + feature_name + ".npy", glcm_feature)

参考

ディジタル画像処理[改訂新版]

ディジタル画像処理[改訂新版]

ランダムフォレストの理論と重要な特徴量の選定

ランダムフォレストと決定木学習

ランダムフォレストを理解するためには、決定木学習の手法について理解する必要があります。まず最初に決定木学習の理論について説明します。

決定木学習

決定木は親から順に条件分岐を辿っていくことで、結果を得る手法です。下は決定木のイメージです。

決定木学習とはデータの応じて上の図のような決定木を構成し、分類を行う機械学習の手法のことを指します。 f:id:drilldripper:20161005074636p:plain

決定木学習は、データの種類に応じて決定木を成長させていきます。 決定木の分類条件は、データを分類したときの情報利得IG(Infomation Gain)が最大になるようにすることです。情報利得は式(1)で表されます。

{IG(D_p, f) = I(D_p) - \sum_{j=1}^{m}\frac{N_j}{N_p}I(D_j)}\tag{1}

{D_p}は親のデータ、{N}はノード、{j}は注目しているデータを表します。 {m}は木を分割するノード数です。一般的に決定木は二分木として実装されるので、ほとんどの場合は{m=2}となります。

{I}は不純度という指標で、含まれるデータに偏りがあるほど大きな値になります。不純度の計算にはエントロピー、ジニ不純度、分類誤差などが用いられます。今回はエントロピーを使って説明をします。式(2)にエントロピーの式を示します。

{I_H(t) =  -\sum_{i=1}^{c}p(i|t)\log_2 p(i|t)}\tag{2}

{p(i|t)}はデータ数tの中に含まれているデータ数iの割合を表します。

エントロピーはデータのばらつきが大きいほど大きな値となります。例えばサンプル数100個のデータを分類したとき、左の木に100個、右の木に0個に分類すると、エントロピーは最も大きな値である1になります。逆にエントロピーが最も小さくなるのは、左右の木に50個ずつ分類したときで、エントロピーは0になります。

式(1)の{I}エントロピー{I_h}を代入することで情報利得の計算を行います。

ランダムフォレスト

ランダムフォレストは決定木を複数組み合わせて、各決定木の予測結果を多数決することによって結果を得ます。*1

以下にアルゴリズムを示します。

  1. ランダムにデータを抽出する*2

  2. 決定木を成長させる

  3. 1,2ステップを指定回繰り返す

  4. 予測結果を多数決することによって分類閾値(ラベル)を決定する。

ランダムフォレストはパラメータが非常に簡単になるという利点があります。主要なパラメータはサンプリング数と決定木を成長させる際に使用する特徴量の数だけです。決定木の特徴量数は一般的にサンプル数をnとしたときに{\sqrt n} にすると良いと言われています。*3

ランダムフォレストのメリットとデメリット

メリット

  • 特徴量のスケーリングが必要ない(SVMなどの分類手法では必要になる)
  • 考慮するパラメータが少ない
  • 並列化が容易
  • どの特徴量が重要かを知ることができる

デメリット

  • 複雑なデータではSVMなどの分類手法に比べて汎化性能が下がる

Scikit-learnを使って特徴量の重要度にアクセスする

Scikit-learnにはランダムフォレストの実装されています。今回はこの実装を使ってデータを分類行って、特徴量の重要度を調べてみます。 使用する学習データはアヤメの計測データであるIrisデータセットです。以下にソースコードと結果を示します。

from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
import numpy as np
iris = datasets.load_iris()
feature_names = iris.feature_names

forest = RandomForestClassifier(n_estimators=10000, random_state=0, n_jobs=-1)
forest.fit(iris.data,iris.target)
importances = forest.feature_importances_
print("{0:<20}".format ("feature names"), "importances")

for (feature_name, importance) in zip(feature_names, importances):
    print("{0:<20}".format (feature_name), importance)
feature names        importances
sepal length (cm)    0.0980144976191
sepal width (cm)     0.0232983974998
petal length (cm)    0.439861958314
petal width (cm)     0.438825146567

結果をみるとpetal length(花弁の長さ)が最も重要な指標であり、sepal width( がく片の幅)が最も重要度の低い指標であることがわかります。 この結果は、SVMなどの機械学習アルゴリズムの特徴量選択の参考にすることができます。

参考文献

*1:弱学習器を組み合わせてロバストな学習器を作ることをアンサンブル学習と呼びます

*2:重複を許さないブートストラップ標本の抽出を行う

*3:ただしデータに依存するので、必ずしもこの値が良いというわけではありません

ETロボコンの走行を支える技術

What is ETロボコン

ETロボコンは統一規格のロボット(Mindsorms EV3)を使い、指定されたコースを走行する精度とタイムを競う競技です。UML等で書かれた仕様とアピールポイントをまとめたモデルシートを作成する必要があり、モデルシートも評価の対象となります。

ETロボコン2016公式サイト

統一規格のロボットを使うというルールから分かる通り、ソフトウェアデザインとアルゴリズムに重きが置かれたロボコンと言えます。*1珍しい特徴として、ETロボコンは学生チームと企業チームが同じ土俵で戦います。*2車両開発系や組込系のIT企業も参加するとても熱い競技です。

今回はロボットの走行を支える一般的な技術について書きたいと思います。UMLなどのソフトウェア設計技術も順位決定の重要な要素ですが、今回は触れません。

ライントレース

ロボットは基本的にコースに引かれた黒い線を追いかけて走行します。原始的な手法として、白を検知したら右に旋回し、黒を検知したら左に旋回するという手法があります。下の図ようなイメージです。 f:id:drilldripper:20160925192150p:plain

これは一般にON/OFF制御として知られている手法です。この手法では右と左にしか動作することができないので、ロボットがジグザグな動きとなります。当然安定性が低く速度も遅くなります。これを改善するためにPID制御が用いられます。

PID制御

PID制御(Proportional-Integral-Differential Controller)は古くから使われるフィードバックループの制御手法の一つです。名前の通りフィードバッグの要素として比例値P、微分値I、積分値Dを使います。PID制御は以下の式で表されます。

{  K_pe(t) + K_i \int_0^t e(\tau) \: d\tau + K_d\frac{de(t)}{dt} }  (tを時間、eを入力と目標値の誤差とする)

各項の意味を簡単に説明します。(そのため正確性は少し欠いているかもしれません)

  • Kp値(比例制御):

    • 入力値と目標値(出力したい値)の偏差を計算し、偏差の大きさによって操作量(出力に近づけるための値)を変化させます。
  • Ki値(積分制御):

    • 入力値の目標値の偏差を積分をします。偏差がある状態が長く続くほどこの項は大きくなり、目標値に近づいていきます。
  • Kd値(微分制御)

    • 積分制御の項が大きくなりすぎると、目標値を超えて制御不能になる場合や、目標値の前後を振動してしまう場合があります。そこで微分制御では偏差の微分値を計算することで、急激な変化を捉えて動作を押さえ込みます。

PIDの各パラメータを適切に設定できれば、ロボットを滑らかに走行させることができます。

探索アルゴリズム

走行エリアには難所と呼ばれるアトラクションがあり、クリアするとボーナスポイントを取得できます。難所の一部は探索問題に帰着させることができる場合があります。問題をよく考察してグラフなどのデータ構造に落とし込めば、以下のアルゴリズムが使用できます。*3

計算量や実装難度と相談して、攻略に最適なアルゴリズムを選びましょう。

データ分類(機械学習)

ロボットからは様々なセンサ情報を取得することが出来ます。EV3であればジャイロセンサ、超音波センサ、光センサなどから情報が取得できます。取得したセンサ情報から状況を把握することで、ロボットの次の動作を決定できます。

人間が手動で分類の閾値を決定することもできますが、うまく教師データを作ることができればRandom ForestやSupport Vector Machineなどの機械学習アルゴリズムを活用できる可能性があります。

デッドレコニング(自己位置推定)

競技ではロボットの位置によって動作を変更しなければならないので、ロボットの自己位置を推定する必要があります。センサ情報とロボットの仕様から自己位置を推定する手法を、一般的にデッドレコニングと呼びます。

デッドレコニングは走行距離が長くなると、蓄積した誤差によって自己位置の推定に失敗してしまいます。そのため何らかの工夫で誤差の蓄積をリセットする必要があります。

タスクの制御

ロボットの上で複数の処理を同時に動作させるさせることで、柔軟な処理が可能になります。リアルタイムOSでは処理をタスクと呼ばれる単位に分割して、優先度を決めて動作させるさせることができます。

一般的な並列プログラミングと同様に、排他制御に気を使う必要があります。

おわりに

いろいろ技術を学ぶことができる熱い競技なので、課題*4をクリアできるのであれば参加してみると楽しいと思います。

*1:ETロボコンの正式名称はEmbedded Technology Software Design Robot Contestです。名前からもソフトウェアに重きを置いていることがわかります。

*2:ISUCONなどと同じですね

*3:一瞬「競技プログラミングは役に立たない」の反例かと思ったが、競技の攻略に競技が役立ったと主張するのはだめな気がします

*4:時間、予算、練習場の確保、チームメンバ集めなど

SVMの基本原理と不均衡データに対するパラメータ調整

前回の記事では不均衡データをサンプリングすることで、学習の精度を上げる方法を書きました。今回はSVMのパラメータを調整することで、不均衡データの学習の精度を上げる方法について書こうと思います。
そのためにSVMの基本を理解しておいたほうがよいと思うで、簡単にまとめてみたいと思います。

SVMの基本原理

SVMのイメージは以下のページを見てもらうとわかりやすいと思います。

SVMを使うとなにが嬉しいの?

簡単に言うとSVMの目的は、データを2つのクラスに分離する線を引こうとしたときに、2つのクラスとのユークリッド距離が最も大きくなるようにする(マージンを最大化する)ことです。これだけではわからないと思うので、数式で原理を追ってみます。

正方向の傾きをx_p、負方向の傾きをx_n w をクラスを2つに分離する直線したとき、正の超平面は式(1)、負の超平面は式(2)のように表せます。
w_0 + w^T x_p = 1 \tag{1}
w_0 + w^T x_n = -1\tag{2}
式(1) - 式(2)から式(3)を得ます。

w^T(x_p - x_n) = 2\tag{3}

これをベクトルの長さとして標準化すると式(4)が得られます。

|| w || = \sqrt{\sum_{j=1}^{m} w^2 _ j}\tag{4}

超平面について表した式(3)とベクトルの長さ式(4)から、式(5)が得られます。
\frac{ w^T(x_p - x_n)}{||w||} = \frac{2}{||w||}\tag{5}

左辺は超平面の距離(マージン)なので、右辺を最大化することで最大マージンを得ることができます。
最大マージンを得るためには、式(5)について、式(6)、式(7)の制約を満たすような問題を解く必要があります。

w_0 + w^T x^i \geq 1 \tag{6}
w_0 + w^T x^i < -1 \tag{7}
(iはすべてのデータです)

ただし式(5)の右辺は逆数をとって2乗した\frac{1}{2} ||w||^2を解く方が簡単になることが知られている*1ので、この式を使うことにします。

ここまでがSVMの基本的な原理です。さらにこれを改良して非線形なデータに対しても分類が行えるようにしたいと思います。
制約を緩和するための変数を\zeta*2を式(6)、式(7)に導入して式(8)、式(9)を得ます。

w_0 + w^T x^i \geq 1 - \zeta \tag{8}
w_0 + w^T x^i < -1 + \zeta\tag{9}

これを\frac{1}{2} ||w||^2に当てはめることで式(10)を得ます。
\frac{1}{2} ||w||^2 + C\sum_{i}^{}\zeta^i \tag{10}

これで変数Cの値を変えることで、データの分類に対するペナルティを設定できるようになりました。Cの値を大きくすると誤分類にペナルティを与え、Cの値が小さいときには誤分類を許容するようになります。

不均衡データへの適応

不均衡データの学習がうまくいかない理由は、殆どのデータを偏った大きなクラスに分類することで「正答率」が上がってしまうためです。

例えば天気予報を行うことを考えてみます。天気は一年を通して多くの日が晴れとなるので、仮にすべての日を晴れと予報しても「正答率」は高くなります。しかし当然のことながら、私たちはいつ雨が降るかということが知りたいです。つまり雨の日を晴れの日とご分類するケースをできるだけ少なくしたいということです。これは式(10) においてCの値を大きくするということに値します。

Scikit-learnのSVMではこのCのパラメータを簡単に変更することができます。
以下のように、分類器をインスタンス化するときに引数に設定するだけです。詳しくはドキュメントを読んでみてください。

clf = SVC(kernel='rbf', C=1000)

sklearn.svm.SVC — scikit-learn 0.18.1 documentation

また不均衡データについても公式ドキュメントで触れられているので、目を通しておくといいと思います。
1.4. Support Vector Machines — scikit-learn 0.18.1 documentation

参考文献

*1:二次計画法を使うことで解くことができる

*2:スラック変数といいます

pandasを使った不均衡データの整形

不均衡データ

機械学習を行う上で正例と負例が偏っている学習データを使うと、学習がうまく行きません。サンプル数が多いクラスに引っ張られてしまいます。そのため事前にデータの加工を行うと、結果が良くなることが知られています。

多い方のクラスを少なくする手法をアンダーサンプリング、小さい方のクラスを多くする手法(人工的にデータを作り出す手法)オーバーサンプリングといいます。今回はpandasを使ってアンダーサンプリングを行ってみます。

データのアンダーサンプリング

gista519abe99ebbe9a687b3a1288dfb8995

小さいクラスのデータ数を取得し、大きいクラスからランダムに小さいクラスの個数分データを抽出します。その後データをマージしています。

この方法ではアンダーサンプリングした結果に偏りが生じる可能性があります。学習の精度を向上させたいときはk-meansでデータをクラスタリングを行い、偏りがないようにデータを抽出する必要があります。

スクリーンショット自動化ツール「BindScreen」を作りました

スクリーンショット自動化ツール「BindScreen」

スクリーンショットを自動でとって加工する「BindScreen」を作りました。 ツールとソースコードGitHubからダウンロードすることができます。

BindScreen - Github drilldripper/BindScreen

使用例

次のスライドをこのツールで画像化してみたいと思います。*1

C++の歴史 江添亮 (このスライドはGFDL1.3で配布されています)

今回の設定は以下のようにしてみました。

f:id:drilldripper:20160811220020p:plain

キャプチャ間隔はスクリーンキャプチャを行う間隔です。またキー入力もこの間隔で行われます。

初期化時間はプログラムが実行が始まるまでの時間です。設定した時間の間にキャプチャする画面を前面に表示しておきます。今回はブラウザの要素をキャプチャしますが、多くのブラウザではF11を押すと全画面表示になるので活用するといいと思います。

今回は画像のトリミングを行わずに圧縮を行いたいので、「画像のトリミングを行う」のチェックを外し、「圧縮フォルダを出力する」にチェックを入れます。見開き方向の選択はトリミングを行わない場合はどちらを選んでも出力結果に影響はありません。またこのスライドは右キーで次ページに移動するので、"右キー"を選択して出力先フォルダを設定します。

設定が完了したら実行をクリックしましょう。スライドが終了すると自動的にスクリーンショットの撮影が止まります。

指定したフォルダを確認すると「spread_image」と「spread_complete.zip」が作成されています。試しにspread_imageを開くと以下のような画像が生成されています。

f:id:drilldripper:20160811220042p:plain

f:id:drilldripper:20160811220203p:plain

うまくいきました。

他にもソフトの表示結果のログを画像として記録する用途などで使えると思います。

技術的な話

画像処理

このツールはPythonを使って開発を行いました。画面のキャプチャにはPythonの標準的なツールであるPIL(Python Imaging Library)のforkであるPillowを使っています。PILは開発が停滞していてPython2.7までしか対応していませんが、Pillowは開発が継続的に行われていてPython3以降に対応しています。 また画像の分割や余白の除去もPillowで行っています。

画面キャプチャは変化が無くなった時点で自動的に終了しますが、これは前後の画像のMD5を計算してメッセージダイジェストが同じであれば終了する仕組みになっています。この方法では数ピクセルでも画像が異なっていれば違う画像として判定されます。もし画像の僅かな違いを許容したいのであれば、画像のヒストグラムを比較して閾値で判定を行う方法をとると良いかもしれません。

キーボード入力

キーボード入力はwin32APIであるSendKeysを使ってキーストロークを送信しています。GUIでは上下左右の方向キーのみ対応していますが、CUIAPIを引数に与えれば任意の入力を行うことが出来ます。

SendKeys メソッド - MSDN

このツールがWindows専用ツールになったのはSendKeysAPIを使用しているからです。 xautomationなどを使えばLinuxMacに対応することができますが、依存ライブラリが増えてしまうので今回は使用しませんでした。

GUI

GUIの作成にはPyQt4を使いました。今回初めて使いましたが、さくっとGUIが出来てとても便利でした。PyQtはモジュールが疎結合に作られているので、ボトムアップ的で面白かったです。

このツールはもともとCUIとして作成したので、GUIは引数とオプションを決めてスクリプトに渡す設計にしています。このような作り方にするとCUIが独立して使えるので、バッチ処理ができるのでいい感じですね。

*1:テキストのスライドを画像にしたら逆に使いにくいと思いますが、一例ということで…