5. Pythonによる機械学習
Pythonへのデータ読み込みと学習
今回の学習はランダムフォレスト(のみ)でやることにします。Python上での基本的な文法は一緒なので、ほかにためしたい学習器があれば、各自でお試しください(元ネタの方ではいろんな学習器を比較もしているので、興味があればそちらへ。xgboostとかも良さそうですね)。
はい、ランダムフォレスト完了です。いい時代ですね。いちおう解説しておきますと、
1行目 基本ライブラリ(numpy, matplotlib, pandas)のインポート
2~4行目 機械学習関連のライブラリのインポート
5行目 データcsv読み込み
6行目 データを学習用7割、テスト用3割にランダム分割(xは特徴ベクトル、yは出力カテゴリ)。csvの1~3列目は緯度経度標高情報なので除外。
7行目 ランダムフォレストのモデルを作成。木の数は500本(てきとう)、木の特徴量の数は与える特徴ベクトルの次元dに対し√d。
8行目 学習実行
完了です、とさらっと言いましたが、本来であれば木の数やノードの切り方など、ハイパーパラメータのチューニングもやるのが望ましくはあります。まあ今回は紹介なのでいいでしょう。
精度評価もしておきましょう。多クラス分類ではエラーマトリクス[1]を作るのが通例なのでそうしてみます。
エラーマトリクスの出力は以下のようになりました。
[[ 64 8 0 0 5 3 1 0 0 0]
[ 3 114 1 0 1 1 0 1 0 1]
[ 0 4 17 0 0 0 1 0 0 0]
[ 0 0 0 7 2 0 0 2 6 0]
[ 3 2 0 0 253 3 0 42 7 0]
[ 10 2 1 0 39 62 1 11 2 2]
[ 0 0 0 0 5 0 9 0 0 0]
[ 1 1 1 0 24 3 0 299 1 0]
[ 0 0 0 0 7 0 0 7 89 0]
[ 0 0 0 0 6 0 0 0 2 14]]
その他スコアも含め、表の見かたを間違えないようにきれいにまとめ直すと、以下のようになります[2](自動化してもいいですが、とりあえずコピペ&手打ちしました)。よこ(列)がランダムフォレストにより予測(作成)されたデータ集計で、たて(行)がリファレンスデータの集計です。
croplandとgrassland, barrenとurban, waterとcroplandあたりを間違えがちなようです。このあたりについては、時系列データを追加したりすれば改善しそうな気がしますね。
精度指標の定義まとめ
ここで、精度指標については紛らわしいので定義を確認しておきましょう。
まず全データ数Nのうち、予測データとリファレンスデータのカテゴリが一致したもの(エラーマトリクスの斜めマスの合計)のしめる割合を「全体精度(Overall accuracy; OA)」といいます。「正解率」とか単に「精度(accuracy)」といわれることもあります。データ間の単純な一致率をみるもっとも基本的な精度指標です。
カテゴリごとに予測がうまくいっているのかどうかをより詳細に見るためには、以下のようにします。
注目カテゴリに分類されたデータを正例とか陽性(positive case)、それ以外に分類されたデータを負例とか陰性(negative case)と呼び、さらに
正例のリファレンスについて、ただしく正例と予測できたデータ数を真陽性(True positive; TP)
正例のリファレンスだが、負例と間違って予測されたデータ数を偽陰性(False negative; FN)
負例のリファレンスについて、ただしく負例と予測できたデータ数を真陰性(True negative; TN)
負例のリファレンスだが、正例と間違って予測されたデータ数を偽陽性(False positive; FP)
とかき分けることにします。このとき、
正例と予測されたデータのうち、確かに正例だった割合 TP/(TP+FP) を、適合率(precision)といいます。「適合度」と呼ばれることもありますし、よりによって「精度」と呼ばれることもあります。Overall accuracyと紛らわしいのでやめてほしいですね。また、リモセン業界の慣例かわかりませんが、使用者精度(User's accuracy; UA)という言い方もよく聞きます。
正例と予測されたデータのうち、本当は負例だった割合 FP/(TP+FP) を、コミッションエラー(commission error)といいます。個人的にはこの用語が一番わかりやすく、AIの「やらかし・やりすぎ」エラー、みたいなイメージですね。1−UA=commission errorの関係にあります。
リファレンスで正例であるもののうち、ただしく正例と予測できた割合 TP/(TP+FN) を、再現率(recall)といいます。「感度」と呼ばれることもあります。作成者精度(Producer's accuracy; PA)という言い方もされます。
リファレンスで正例であったが、負例と間違えた割合 FN/(TP+FN) を、オミッションエラー(omission error)といいます。こちらは「みのがし」エラー、みたいなイメージです。1−PA=omission errorの関係にあります。
用語が多くて「もう全体精度だけみればよくない?」と思った人もいるかもしれません。そういう人は、以下の事例を考えてみてください。
正例と負例だけの単純な2カテゴリをもつ、100個のデータがあるとします。ただし正例の出現率はめっぽう低く、100個のデータ中、1個だけが正例、残り99個は負例だったとします。このとき私は、全体精度99%を達成するさいきょーのAIを1秒で作ることができます。以下のようなものです。
さいきょーAI:入力された特徴ベクトルxがなんであろうと、常に「負例!」と元気よく出力する。
このAIで100個の負例が出力され、うち99個のリファレンスデータは負例で一致しますので、全体精度は99%です。強いですね。
…そういうわけで、全体精度はカテゴリ間のサンプルサイズの偏りに弱いので[3]、別途カテゴリごとに精度はどうか、とみていく必要があるわけですね。当該さいきょーAIのrecallは0%、見逃しエラー100%です。正例の抽出に使いたいのであれば、まったく虚無のAIということになります。
もう一つだけコメントしておくと、precisionとrecallは一般にトレードオフの関係にあります。極端な例ですが、上記で、正例を抽出したいからと常に「正例」と出力することにすれば、見逃しはなくなります(recall=100%)が、precisionは1%で驚異的なオオカミ少年になります。
このあたりを考慮し、2/(1/precision+1/recall) と調和平均をとったF1値もよく使われます。precisionとrecallがともに高いモデルはいいモデルと言えます。
GRASSとの連携
学習済みのAIをつかって、マップ全体を分類することにしましょう。grassのターミナル上でpythonを立ち上げていれば、grass.scriptというライブラリがpythonにインストールでき、GRASS上のラスタをpythonのnumpy配列として読むことができるようになります。以下のような具合です。
1行目: GRASSとの連携用のライブラリです。
2行目: 読み込んだマップを特徴ベクトルにするための入れ物を用意しています。範囲(region)は、行・列ともに1800 pixel、特徴ベクトルの次元(マップ数)は18です。
5行目: GRASSのラスタをnumpy配列に変える部分です。
8行目: 学習済みのAIに読んだマップを与えて、土地被覆カテゴリを予測させています。
9行目: 得られた土地被覆マップを、ふたたびGRASSのラスタに戻しています。ここまで終わったら、quit()でpythonを終了しましょう。
あとは、GRASS側で適当に可視化してやります。
得られたマップは右のとおりです。念のため、カテゴリの対応は
1:DF(落葉林) 2:EF(常緑林) 3:bamboo(竹林) 4:barren(裸地)
5:croplands(耕作地) 6:grasslands(草地) 7:orchard(果樹園)
8:rice(水田) 9:urban(都市) 10:water(水域)
筑波山の尾根付近に落葉林、その周りは常緑林、霞ヶ浦はもちろん水域、そこから桜川沿いに上ると水田、つくば市街、という感じで雰囲気はわかります。詳細は実際に散歩して検証してみたり、Google EarthやGoogleストリートビューで引きこもり散歩して検証してみるとよいでしょう(耕作地がやたら多いような気もします)。
[1] エラーマトリクス、混同行列、混合行列、対応行列、 error matrix、confusion matrix、contingency matrixなどなどいろんな呼び方があります。
[2] ヒートマップ的に自動で可視化してくれるConfusionMatrixDisplayというのもあります。https://scikit-learn.org/stable/modules/generated/sklearn.metrics.ConfusionMatrixDisplay.html#sklearn.metrics.ConfusionMatrixDisplay
[3] このあたりの "対策" として、以前は全体精度から「偶然の一致率」がおよぼす影響をのぞいた「κ係数」というのもよく使われていました。しかしよくよく考えると、いうほどカテゴリ間インバランスの影響を軽減できる指標でもないし、意味ないのでは?という話(Pontius & Millones, 2011)もでてきており、最近ではあまり見かけることはなくなったように思います。