データサイエンティスト(仮)

元素粒子論博士。今はデータサイエンティスト(仮)。

NN論文の読み会でまた発表した

前回に続き、NN論文を肴に酒を飲む会で発表しました。

tfug-tokyo.connpass.com

今回の論文のテーマは、強化学習でした。DQN周りの発展を勉強するいい機会だと思い、炎上ラーニング戦法で発表を申し込みました。私が選んだ論文は、Google DeepMindが昨年の10月に出した、DQN以降の深層強化学習のモデルをうまく融合させたモデルである「Rainbow」という論文です。

www.slideshare.net

この論文のFigureがネタ的で趣深いので、興味あるかたはぜひ一読をおすすめします。論文のリンクは以下です。
[1710.02298] Rainbow: Combining Improvements in Deep Reinforcement Learning


ネタはさておき、論文投稿時点(2017年10月)でAtari2600というレトロゲームのスコアで最先端(State of The Art:SoTA)となっており、既存手法を圧倒する性能を獲得していました。

Rainbowもすごいのですが、現時点ではより性能のよいであろうモデルが提案されています。こちらもGoogle DeepMindから論文が出ており、Ape-Xと呼ばれるモデルになります。Rainbowで使われている手法の一部と、うまく並列処理する仕組みが組み合わされたもので、既存手法より効率的に学習することができるようです。最近までICLR 2018のreview中でしたが、acceptされたようです。
Distributed Prioritized Experience Replay | OpenReview

私の不勉強のため、発表しようと思っていた時点でこちらの論文を認識しておらず、結果として読み会はApe-Xの論文紹介で締めるというオチとなりました(笑)。かなりネタ的な発表だったかもと思いますが、個人的には最近の深層強化学習の発展を追う良い機会になりました。仕事ではDeep Learning関係を使っているわけではないので、こういう勉強して議論し合える機会は貴重だなと改めて感じました。今後も発表の機会があったら積極的にしていこうと思います。

PytorchでDeep Learning : CPU onlyでインストールする際のメモ

Deep Learningフレームワークの一つにPytorchがあります。

f:id:tekenuko:20180126093727p:plain

Facebookを始めとして、様々な企業や大学が開発に携わっているようです。
f:id:tekenuko:20180126093854p:plain


PytorchはPython上でDeep Learningを行うためのフレームワークです。Preferred Networksが開発しているChainerからforkされた(らしい)ため、書き方や、ニューラルネットワークの構築をデータを流しながら行う*1といったChainerの特徴が引き継がれたものになっています。2017年の初頭に公開されたあとはどんどん人気を博していき、Tensorflow, Keras, Caffeに続くフレームワークとなっています。

Pytorchに興味をもったきっかけは、研究者の利用が多いことです。例えば、以下の柏野さんの資料などにそのような記載があります。

www.slideshare.net

研究者の利用が多いということで、研究段階でPytorchを使ったり、調査の際にPytorchを使って実験するといった機会が多くなり、結果、Pytorch実装が早めに世に出やすくなると期待されます。ビジネス側でDeep Learningのキャッチアップを行っていきたいと考えたときに、Pytorchを知っておくと情報をいち早く得られるようになるのでは、と考えています。

というわけで、Pytorchについて調べてみようと思っています。

インストール

以下の例は、MacOS SierraでPython3.6(Anaconda利用)の場合です。Linuxで最初からGPUを利用できる環境を整えている場合はまた別の挙動になるのではと思います(未検証、GPU欲しい)。

雑にやりすぎで失敗した例

Pythonフレームワークということで、pipなどでコマンドを叩けばインストールできるだろうと考え、以下のコマンドを打ってみます。

$ pip install pytorch

そうすると、私の環境では、以下のようなエラーが出たあと、Pytorchのページに飛ばされました

f:id:tekenuko:20180126100901p:plain

そして、Get Startedの項目を見ると
f:id:tekenuko:20180126101143p:plain
のように、OSやGPUあり/なしでコマンドを適宜変えないといけないらしいことがわかります。

環境に合わせてインストール

自分の環境に合わせてどうなるか見てみます。
f:id:tekenuko:20180126101321p:plain

Anaconda入っているので、condaでも良い気がしますが、ここは好みで。CUDAとはNVIDIAが開発・提供している、GPU向けの汎用並列コンピューティングプラットフォームのことで、これが適切にインストール済みでないとNVDIAのGPUを使って計算することができません。自分の環境は、GPUに対応していないので、CPU onlyで使うためにCUDAの欄はNoneにしています。
(pip3とか推奨されているけど、pipでいいんじゃん?)

次に、推奨されたコマンド(pip3はpipに変えた)を打ってみます。

$ pip install http://download.pytorch.org/whl/torch-0.3.0.post4-cp36-cp36m-macosx_10_7_x86_64.whl 
$ pip install torchvision 

結果、以下のようになり、インストールが成功します。
f:id:tekenuko:20180126102009p:plain

インストールした時期によってバージョンは多少変化すると思いますが、pip listで

torch (0.3.0.post4)
torchvision (0.2.0)

というものが見れていればPythonでPytorchが使える状況になっていると思います。実際、ipythonあたりを雑に起動してみて
f:id:tekenuko:20180126102359p:plain
という感じで、問題なくimportできています。

Next Step

今回は、Pytorchのインストールについてのメモを紹介しました。雑にpip使っていきなりサイトに飛ばされてびっくりしたのですが、普通はサイトにいってインストール方法を読んでから実行しますよね。。単に自分のアホさを露呈しただけでした。

割りと趣味的な感じなので、ちまちまとやっていく感じにはなると思いますが、Pytorchを使って色々と試していきたいと思っています。仕事でDeep Learningをやる機会があったらPytorchを使ってみたいんですけどね。そういう機会があればいいなあ。

*1:Define by runという考え方です。このような設計にすると、ミニバッチごとに異なるネットワークを準備するなどが可能になります。

Pythonでデータ分析:Auto-sklearnについてのメモ

導入

最近、Meta Learningという考えに少し興味を持ちました。もともとは認知科学発祥の考えですが、機械学習の文脈だと

ある決まったバイアス,すなわち仮説空間の中から,事例に応じて,適切な仮説を獲得する普通の学習器をベース学習器という.その上位で,学習対象のタスクやドメインに応じて,学習器のバイアスを決定するためのメタ知識を獲得するのがメタ学習 (meta learning).

メタ学習 - 機械学習の「朱鷺の杜Wiki」
という概念のようです。ざっくりいうと、学習のためのメタな学習規則を学習する(Learning to learn)という感じなのかなと思います。

このMeta Learningですが、機械学習の自動化、つまりあるデータセットを投入すると「良い」機械学習モデルを自動で作成すること、とも関係しているようです。こういった背景のもと、そういえばAuto-sklearnってMeta Learningが取り入れられていなかったっけ、とふと思い出し、中の仕組みって大まかにどうなっているんだろうというのが気になりました。そういった経緯で、Auto-sklearnのベースになった論文を調べてみたメモを、備忘録として記事に残しておくことにしました。

Auto-sklearn

Auto-sklearnは自動で機械学習のモデルを構築してくれるPythonのライブラリです。
github.com

ざっくりした概要やインストール方法は、過去に自分が紹介していたようです。
tekenuko.hatenablog.com
この記事を見ると、詳しくは以下のページといって実質元論文に投げてしまってます。過去の自分はしょうもないことやってるな。

Auto-sklearnのベース論文

Auto-sklearnの仕様やパフォーマンスが紹介されている論文は、以下のNIPS 2015での論文です。
papers.nips.cc
概要はこれを見ると載っているのですが、細かい話は「Supplementary MaterialのTableやFigにある」という記載が度々見られます。Supplement Materialってなんだろうと思ってネット検索をしてみると、以下の文献がヒットします。どうやらこれがSupplementary Materialのようです。
https://pdfs.semanticscholar.org/699c/12c4b47b7dff4c9ae9b22b9326ae9a1dacca.pdf

Auto-sklearnの概観

Auto-sklearnの全体感は以下になります。
f:id:tekenuko:20171212230243p:plain
Auto-sklearnの自動化部分は図の「AutoML system」になります。大まかな構造は以下のようになっています。

  • Meta Learning部分
  • ML framework部分
  • Build ensenble部分

ML framework部分でベイズ的最適化を用いて良いモデルを選択する、という方法は、Auto-WEKAですでに取り入れられているようです。
Auto-WEKA
ただ、Auto-WEKAだと探索するパラメータが多すぎる、アンサンブルを取ったモデルも含めて探索する、といった非効率性が欠点としてあったため、Auto-sklearnではMeta Learning部分とBuild ensanble部分を加えた3部構成にしているようです。

Meta Learning

この部分は、「効率性」に着目した部位です。高精度の機械学習モデルを自動的に構築する場合、前処理、特徴量エンジニアリングの方法、機械学習アルゴリズム、ハイパーパラメータといった膨大な選択肢をしらみつぶしに探していかなければなりません。コンピュータのリソースや時間が潤沢にあればこういった探索を行ってもよいですが、実際にはリソースに限りがあることが多く、制約がある中で効率的に良いもの探していく必要があります。その際に効率的に探す指針を与えてくれるのが、このMeta Learning部分です。

大まかには、この部分では以下のような操作が行われています。

  • あらかじめ、OpenMLの140のデータセットhttps://www.openml.org/search?type=data)から、後述のMeta Feature、およびベイズ的最適化による「ML framework」(前処理、特徴量エンジニアリング、分類器選択のセット)を抽出
  • 新しいデータセット:今回モデル構築したいデータを投入した際は、データからMeta Featureを算出、Meta Featureの空間でOpenMLのデータセットとのL_1距離を計算
  • 距離が近いものから25種類のML frameworkを選択し、それらをベースに次段のベイズ的最適化へ移行

データセットから計算するMeta Featureは以下のようです。基本的には、要約統計量や次元圧縮に関連する量、エントロピーなどがMeta Featureとなっているようです。
f:id:tekenuko:20171212222410p:plain

ML framework

ここでは、前段で選択された候補に関して処理を行います。論文の図をべた張りですが、前処理や分類器の候補は以下になります。
f:id:tekenuko:20171212222927p:plain
これらと、前段で絞りこんだ情報をもとに、ベイズ的最適化によりよいものを選択します。評価に関しては、あらかじめ評価指標を設定しておきます。
f:id:tekenuko:20171212223245p:plain

Build ensenble

Auto-sklearnでは、結果をロバストにするために、ML framework部分で算出した結果のアンサンブル平均をとっています。単に足し合わせるのではなく、精度向上に効果があるものを貪欲に入れ込んでいく、としているようです。
f:id:tekenuko:20171212223424p:plain

Auto-sklearnの性能

(ここの解釈は、若干誤解しているかもしれませんので、修正が入るかもしれません。)
論文での性能比較の表は以下です。
f:id:tekenuko:20171212224314p:plain
f:id:tekenuko:20171212224345p:plain
上の表がアルゴリズム間での比較、下の表が前処理方法間での比較です。数字が低いほど良いという見方です。結果を見ると、最も良いパフォーマンスではないが、安定して良い結果になっているという印象です。なので思考停止的にデータを投入してもそこそこの結果が出て来るというものになっているようです。とっても個人的なバイアスがかかっている意見だと思いますが、DataRobotみたいだな、と思いました。
www.datarobot.com
(といいつつDataRobotは黒魔術モデルまで込みで一番いいのを頼む、という感じなので、Auto-WEKAに近しいのかな)

まとめ

今回は、Auto-sklearnについて大まかな処理内容を紹介しました。機械学習のモデルに関して詳細を知らなくてもそこそこ良い結果が出る仕組みを作ったのは素晴らしいことだと思います。さらに、論文が出た時点では分類問題のみが対象でしたが、現在は回帰についても取り扱えるようです。こういった簡単に機械学習ができる仕組みが発達していき、活用のハードルが下がった結果、機械学習の活用シーンが広がって行くことを期待します。

Pythonでデータ分析:imbalanced-learnで不均衡データのサンプリングを行う

導入

クラス分類、例えば0:負例と1:正例の二値分類を行う際に、データが不均衡である場合がたびたびあります。例えば、クレジットカードの取引データで、一つの取引に対して不正利用かどうか(不正利用なら1、それ以外は0)といった値が付与されているカラムがあるとします。通常、不正利用というのは稀に起こる事象なので、不正利用かどうかが格納されているカラムに関してはほとんどが0で、1がほとんどない、という状況になりがちです。

上記の状況で不正利用を予測するようなモデル構築をする場合、目的変数として不正利用かどうかを用いることになりますが、0と1の比率が50%から極度に乖離します(1の比率が0.X%とかになる)。こういったデータで予測モデルを構築すると、往々にして負例だけを予測する(予測値がすべて0になる)モデルになりがちです。というのは、不均衡なデータの場合はそれでも「正解率(Accuracy)」が高くなってしまうからです。例えば、目的変数の内訳が、0が99990件、1が10件の場合に、すべて0と出力するモデルができたとすると、正解率は99990 / (99990 + 10) = 99.99%となります。このモデルは正解率は高いのですが、すべての不正利用を見逃す(偽陰性:本当は不正利用(=1)だけれども不正利用でない(=0)と誤って予測する)ことになり、不正利用を検知したいという目的には全くそぐわないモデルになっています。

不正利用を予測したい、つまり誤検出が多少増えてもから不正利用を検出したいという状況では、サンプリングによって正例と負例の割合を変える、といった方法が採られます。つまり、学習に使われる正例の割合を増やすことで偽陰性を減らし、多少の偽陽性(本当は不正利用していない(=0)けれども不正利用(=1)と誤って予測する)は出しつつも不正利用も検出できるようにします。割合を変化させるにあたって、大きく以下の3パターンがあります。

  • Under Sampling:負例を減らす
  • Over Sampling:正例を増やす
  • 上記の両方を行う

これら割合の変化は、Pythonではimbalanced-learnというライブラリを用いると簡単に行えます。今回は、このimbalanced-learnを用いてUnder/Over Samplingをどう行うかを簡単に紹介します。

ライブラリのインストール

pipが利用できるなら、以下のように簡単にインストールできます。

$ pip install -U imbalanced-learn 

開発版を使用したい場合は、githubからインストールします。

$ git clone https://github.com/scikit-learn-contrib/imbalanced-learn.git
$ cd imbalanced-learn
$ python setup.py install

サンプルデータ

不均衡データを人工的に生成します。こういった人工データは、sklearn.datasets.make_classificationを用いると簡単に作成できます。今回、10万件のデータで、正例が10件のデータを以下のようにして作成しました。

from sklearn.datasets import make_classification
df = make_classification(
    n_samples = 100000, n_features = 10, n_informative = 2, n_redundant = 0, 
    n_repeated = 0, n_classes = 2, n_clusters_per_class = 2, weights = [0.9999, 0.0001], 
    flip_y = 0, class_sep = 1.0, hypercube = True, shift = 0.0, 
    scale = 1.0, shuffle = True, random_state = 71)

パラメータの意味は以下です。今回大事なのは、n_sample, n_classes, weightsの3つで、あとはえいやっと決めています。

パラメータ名 説明
n_samples 生成するサンプルの数
n_features 生成する特徴量の数
n_informative 目的変数のラベルと相関が強い特徴量(Informative fearture)の数
n_redundant Informative featureの線形結合から作られる特徴量(Redundant fearture)の数
n_repeated Infomative、Redundant featureのコピーからなる特徴量の数(Repeated feature)
n_classes 分類するクラス数
n_clusters_per_class 1クラスあたりのクラスタ
weights クラスの比率で、例えば、2値分類問題の場合、Noneとすると0と1が50%ずつだが、[0.9, 0.1] と与えると0が90%、1が10%になる
flip_y クラスのフリップ率で、例えば0.01とすると各クラスの1%の符号がランダムに変更される
class_sep 生成アルゴリズムに関係するパラメータ(細かい話はドキュメント参照)
hypercube 生成アルゴリズムに関係するパラメータ(細かい話はドキュメント参照)
shift 全ての特徴量にshiftを加算する。Noneが指定された場合、[-class_sep, class_sep]の一様乱数を加算する
scale 全ての特徴量にscaleを乗算、Noneが指定された場合、 [1, 100]の一様乱数を乗算する
shuffle Trueにすると行と列をシャッフルする
random_state 乱数を制御するパラメータで、Noneにすると毎回違うデータが生成されが、整数をシードとして渡すと毎回同じデータが生成される

sklearn.datasets.make_classification — scikit-learn 0.19.1 documentation

あとは申し訳程度にDataFrameに変換してカラム名などをつけておきます。ここは、私がcsvにいったん保存したりする関係で行った操作なので、必須ではないです。

import numpy as np
import pandas as pd
from pandas import DataFrame, Series
df_raw = DataFrame(df[0], columns = ['var1', 'var2', 'var3', 'var4', 'var5', 'var6', 'var7', 'var8', 'var9', 'var10'])
df_raw['Class'] = df[1]

クラスの割合は、以下のようになっています。

df_raw['Class'].value_counts()

# 出力
0    99990
1       10
Name: Class, dtype: int64

圧倒的に0が多くなっています。

プロトタイプモデル作成

不均衡データをサンプリングしないまま、分類のためのロジスティック回帰モデルを作成してみます。

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix

# 学習用と検証用に分割
X = df_raw.iloc[:, 0:10]
y = df_raw['Class']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 71)

# モデル構築
mod = LogisticRegression()
mod.fit(X_train, y_train)

# 予測値算出
y_pred = mod.predict(X_test)

正解率(Accuracy)は、以下になります。

print('Accuracy(test) : %.5f' %accuracy_score(y_test, y_pred))

# 出力
Accuracy(test) : 0.99990

このように、正解率99.99%という、一見精度が良さそうなモデルができています。

しかし、混同行列を出力してみると

tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
(tn, fp, fn, tp)

# 出力
(29997, 0, 3, 0)

のように、TN(正でない(=0)ものを正でない(=0)と予測する)とFN(本当は正(=1)だが正でない(=0)と誤って予測する)のみに値があり、FP(本当は正でない(=0)ものを正である(=1)と誤って予測する)とTP(正である(=1)ものを正である(=1)と予測する)が0となっています。つまり、単にすべて0と予測するモデルになっています*1

Precision(正と予測したデータのうち,実際に正であるものの割合:TP / (TP + FP))とRecall(実際に正であるもののうち,正であると予測されたものの割合:TP / (TP + FN))を評価してみます。

print('precision : %.4f'%(tp / (tp + fp)))
print('recall : %.4f'%(tp / (tp + fn)))

# 出力
precision : nan
recall : 0.0000

計算が不能になっているか、0になっているという、ひどい結果です。まあ実質意味のある予測ができるモデルではありませんからね…。

Under Sampling

ここでは、負例を減らして結果がどう変わるかを見てみます。imbalanced-learnで提供されているRandomUnderSamplerで、負例サンプルをランダムに減らし、正例サンプルの割合を10%まで上げます。

# ライブラリ
from imblearn.under_sampling import RandomUnderSampler

# 正例の数を保存
positive_count_train = y_train.sum()
# print('positive count:{}'.format(positive_count_train))とすると7件

# 正例が10%になるまで負例をダウンサンプリング
rus = RandomUnderSampler(ratio={0:positive_count_train*9, 1:positive_count_train}, random_state=71)

# 学習用データに反映
X_train_resampled, y_train_resampled = rus.fit_sample(X_train, y_train)

あとはプロトタイプモデル作成の際と同様、ロジスティック回帰モデルを構築し、性能を見てみます。

# モデル作成
mod = LogisticRegression()
mod.fit(X_train_resampled, y_train_resampled)

# 予測値算出
y_pred = mod.predict(X_test)

# Accuracyと混同行列
print('Confusion matrix(test):\n{}'.format(confusion_matrix(y_test, y_pred)))
print('Accuracy(test) : %.5f' %accuracy_score(y_test, y_pred))

# 出力
Accuracy(test) : 0.96907
Confusion matrix(test):
[[29070   927]
 [    1     2]]

# PrecisionとRecall
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print('precision : %.4f'%(tp / (tp + fp)))
print('recall : %.4f'%(tp / (tp + fn)))

# 出力
precision : 0.0022
recall : 0.6667

正解率は落ちたものの、PrecisionとRecallが0でない値になりました。混同行列を見ても、TPが0でなくなっており、FNが小さくなっていることがわかります。しかし、その代償としてFPが927件と大きくなってしまい、それが小さいPrecisionとして跳ね返っています。

Over Sampling

今度は逆に正例を水増しして正例サンプルの割合を10%まで上げます。imbalanced-learnで提供されているRandomOverSamplerで行います。

# ライブラリ
from imblearn.under_sampling import RandomOverSampler

# 正例を10%まであげる
ros = RandomOverSampler(ratio = {0:X_train.shape[0], 1:X_train.shape[0]//9}, random_state = 71)

# 学習用データに反映
X_train_resampled, y_train_resampled = ros.fit_sample(X_train, y_train)

Under Samplingの場合と同様、モデルを作成して性能を見てみます。

# モデル作成
mod = LogisticRegression()
mod.fit(X_train_resampled, y_train_resampled)

# 予測値算出
y_pred = mod.predict(X_test)

# Accuracyと混同行列
print('Confusion matrix(test):\n{}'.format(confusion_matrix(y_test, y_pred)))
print('Accuracy(test) : %.5f' %accuracy_score(y_test, y_pred))

# 出力
Accuracy(test) : 0.98983
Confusion matrix(test):
[[29693   304]
 [    1     2]]

# PrecisionとRecall
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print('precision : %.4f'%(tp / (tp + fp)))
print('recall : %.4f'%(tp / (tp + fn)))

# 出力
precision : 0.0065
recall : 0.6667

Under Samplingの場合と比較して、FPの数が若干抑えられており(304件)、Precisionが若干良くなっています。

SMOTE

上記のOver Samplingでは、正例を単に水増ししていたのですが、負例を減らし、正例を増やす、といった考えもあります。こういった方法の一つに、SMOTE(Synthetic Minority Over-sampling Technique)というアルゴリズムがあります。imbalanced-learnでは、このSMOTEも提供されているので、ここでも試してみます。

# ライブラリ
from imblearn.over_sampling import SMOTE

# SMOTE
smote = SMOTE(ratio={0:X_train.shape[0], 1:X_train.shape[0]//9}, random_state=71)
X_train_resampled, y_train_resampled = smote.fit_sample(X_train, y_train)

# モデル作成
mod = LogisticRegression()
mod.fit(X_train_resampled, y_train_resampled)

# 予測値算出
y_pred = mod.predict(X_test)

# Accuracyと混同行列
print('Confusion matrix(test):\n{}'.format(confusion_matrix(y_test, y_pred)))
print('Accuracy(test) : %.5f' %accuracy_score(y_test, y_pred))

# 出力
Accuracy(test) : 0.98923
Confusion matrix(test):
[[29675   322]
 [    1     2]]

# PrecisionとRecall
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print('precision : %.4f'%(tp / (tp + fp)))
print('recall : %.4f'%(tp / (tp + fn)))

# 出力
precision : 0.0062
recall : 0.6667

Under SamplingとOver Samplingの間くらいの性能になりました。Under/Over Samplingを両方合わせ技でやっているので、直感的にはそうなるんですかね。

まとめ

今回は、Pythonのライブラリで不均衡データの取扱いについて紹介しました。今回はOver Samplingが一番有効でありましたが、データが与えられたときに有力な手法はそのデータの性質に依存する部分も大きいです。なのでどういったサンプリングがよいかは、都度色々試してみて決める必要があります。

また、今回紹介したimbalanced-learnには、上記の3つ以外にもサンプリングの方法が実装されています。今回はそのすべてを紹介できませんでしたが、どういったものがあるかは、以下のページを参照していただければと思います。
imbalanced-learn API — imbalanced-learn 0.3.0 documentation

*1:この結果を出すためにseedを調整していたり…。

Dynamic Routing Between Capsulesを読む

前提

この記事はDeepLearning論文紹介 Advent Calendar 2017 - Adventarの12月10日の記事です。Advent Calendarで記事を書くのは初めてですが、頑張ります。理解が足りなくて非常にわかりづらい記事になっていると思いますので、今後もちょくちょく修正が入ると思います。

読んだ論文

Hintonさんが著者に入っている、以下の論文です。
[1710.09829] Dynamic Routing Between Capsules
Deep Learning界の重鎮のアイデアということもあり、興味を持ったため、こちらの論文を読むことにしました。

概要

正直、Introductionは門外漢すぎてあまり腹落ちしていないのですが、より人間の脳に近しいと思われるネットワーク構造を考え、CNNよりも人間の認知に近いような分類器を作ろうという試みのようです。そのための一歩として、内積スカラー値の塊であった層をベクトルの塊であるcapsuleというものに置き換え、capsuleを用いたネットワークであるCapsNetを提唱している論文になります。

現在、物体認識ではCNNを用いることがスタンダードに見えますが、以下の記事のHintonの講義にあるように、欠点もあるようです。
Dynamic Routing Between Capsules | moskomule log
大きくは

  • poolingで微小変換に対する不変性を獲得する代わりに、顔→目, 鼻といった位置情報、階層構造が失われる
  • 回転などの差異を識別するために別途学習データが必要

といったものです。本論文で提唱している、capsuleを組み込んだCapsNetでは、位置や姿勢などの情報をcapsuleというベクトルの形で保持し,capsule同士が階層的な「構文木」をなすように結合の仕方(routing)を学習することで、CNNの欠点を解消できる可能性があります。

以下、CapsNetの構造、および検証結果について紹介していきます。

Capsuleの計算の入出力

capsuleのimplementationの方法にはいくつか方法があるようですが、論文ではCapsNetのアイデアのキモになる部分、capsuleの入出力とcapsule間の関係を決める方法にフォーカスして紹介しています。

squashing:capsuleの入出力

capsuleでは、capsuleの出力ベクトルの長さが対応するものの存在確率を表現するようにしています。通常のニューラルネットワークでは、活性化関数でそういった効果を表現しますが、capsuleでは以下のような「squashing」という変換でベクトルの長さを|0, 1)にして表現しているようです。
$$
\vec{v}_l = \frac{ \| \vec{s}_j \|^2}{1 + \| \vec{s}_j \|^2} \frac{\vec{s}_j}{\| \vec{s}_j \|}
$$
ここで、\vec{v}_j capsule jの出力ベクトルで、\vec{s}_j はそのcapsuleに対する入力です。

Routing:capsule間の関係性

\vec{s}_j は、前層のcapsuleとの結びつきによって決まります。前層のi番目*1capsuleの出力を\vec{u}_i, 次の層のj番目のcapsuleとの「重み行列」をW_{ij}とします*2。これらを掛け合わせたものを「prediction vector\hat{\vec{u}}_{j|i}とし、各iに対して重みつけ和をとったものが\vec{s}_j となります。
$$
\vec{s}_j = \sum _i c_{ij} \hat{\vec{u}}_{j|i}
$$
係数c_{ij}は、c_{ij}は、以下のようなsoftmaxの形をしています。
$$
c_{ij} = \frac{\text{exp}(b_{ij})}{\sum_k \text{exp}(b_{ik})}
$$
ロジットb_{ik}は以下のroutingアルゴリズムによって動的に更新されます。

f:id:tekenuko:20171210152049p:plain

「Agreement」\hat{\vec{u}}_{j|i} \cdot \vec{s}_j capsule間の類似度的な量になっていると考えられます。この量を用いてロジットを更新していくため、結びつきが強そうなcapsule間の係数c_{ij}がどんどん1に、そうでない係数は0に近づいていくような動きをするのでしょう。

ここの解釈に関して、以下のブログでこのような例えがありました。
f:id:tekenuko:20171210160729p:plain
Dynamic Routing Between Capsules | moskomule log

CapsNetのアーキテクチャ

CapsNetのアーキテクチャーは以下になります。
f:id:tekenuko:20171208113058p:plain:w800
中間層は全3層で、2層が畳み込み層、1層が全結合層です。2層目と3層目がCapsuleになっています。

  • 1層目:畳み込み層
    • channel(フィルタの数):256
    • kernel(フィルタのサイズ):9×9
    • stride(畳み込みの際にスライドさせるピクセルの間隔):1
    • 活性化関数:ReLU
    • 出力:20×20×256
  • 2層目:畳み込みCapsule(PrimaryCapsules)
    • Capsuleの数:32
    • channel(Capsuleのベクトルの成分数):8
    • kernel:9×9
    • stride:2
    • squashing
  • 3層目:全結合Capsule(DigitCaps)
    • Capsuleの数:10(クラス数10に対応)
    • Capsuleのベクトルの成分数:16

Loss関数

Margin Lossを採用。各クラスに対応するcapsuleに対し
$$
L_k = T_k \ \text{max}\left( 0, m^+ \|\vec{v}_k \| \right)^2 + \lambda \left( 1 - T_k \right) \ \text{max}\left( 0, \|\vec{v}_k \| - m^- \right)^2
$$
としています。T_k はクラスkが存在する場合は1をとり、m^+ = 0.9, \ m^- = 0.1, \lambda = 0.5ととっています。全体のLossはすべてのcapsuleのLossの和をとったものを使用します。

routingの実装について

Tensorflowで実装し、OptimizerはAdamを使用しているとのことです。パラメータはTensorflowのデフォルトを使用、学習率は指数的に落ちていくようになっているようです。

正則化による再構成

本論文では、Margin Lossに加えて、以下の構造を使った入力画像と再構成した出力による二乗誤差を新たに追加しているようです。
f:id:tekenuko:20171210162726p:plain:w500
最終層の次元が784なのは、MNISTの入力の28×28 = 784に対応しています。加える際には、0.0005を掛けてから追加しているようです。再構成した画像が入力に近くない場合は、大きな二乗誤差が出ることになるため、ネットワークが入力画像に近い画像を出せるような重みになることを期待し、またそのような重みに学習が進むことをエンカレッジしているような考えなんだと思います。

再構成できているかの図が一つ紹介されていますが、5と3が間違えやすいということはあれど、概ねよく再現されているのでしょう。
f:id:tekenuko:20171210172856p:plain:w500

実験結果

CapsNetの性能に関しては、主にMNISTとMultiMNIST(MNISTの数字を重ねたもの)を用いて伝統的なCNNとの比較を行っています。

結果のまとめは以下となります。
f:id:tekenuko:20171210164000p:plain:w500

Baselineは、畳み込み層の数は3つで、channel数がそれぞれ256, 256, 128, kernelとstrideは5×5と1に統一しています。畳み込み層のあとは328次元、192次元の全結合層で、最後にdropoutを利用した出力層、という構造になっており、Loss関数はクロスエントロピーを使用しています。OptimizerはAdamを使用しているようですが、細かいパラメータの話は記載がありません。routingの実装の際と同様、デフォルトのパラメータを使用しているのだと思います。

MNIST

CapsNetはデータ拡張やデータの回転、スケーリング、アンサンブルを用いずにテストエラー0.25%を達成しています。先行研究(アンサンブルなどを使用)の0.21%と近しい精度となっており、routingが精度向上に寄与しているのではないかと考えられます。また、CapsNetは従来のCNNと比較してパラメータ数が少なく(Baselineは35.4M個、CapsNetは再構成を除くと6.8M、含めても8.2M)、効率的な学習ができていると見ることもできそうです。

また、学習で獲得したDigitCapに関して、16次元のベクトルの要素を少しずつ動かした結果が以下になります。
f:id:tekenuko:20171210173131p:plain:w500
太さやスケールの変換を性質として持つような成分が獲得されているような振る舞いになっています。これはとても面白い性質だと思います。

Affine変換に対するロバスト

また、CapsNetが画像の変換に対してロバストかを検証するために、MNISTをアフィン変換(伸縮・回転・平行移動)をしたデータセット(affNIST)に関してテストを実施しています。以下は、affNISTの画像の例です。
f:id:tekenuko:20171210170936p:plain:w300
あらかじめCapsNetとCNN(畳み込み層+プーリング層とDropout層)でMNISTによってそれぞれ訓練データの精度を99.2~3%となるまで学習します。これらをaffNISTでテストした結果、それぞれ精度が79%, 66%となっており、CapNetのほうがよい性能を出しています。よって、従来のCNNよりもCapsNetが変換に対してロバストであると期待されます。

MultiMNIST

本論文では、MNISTの画像を重ねたもの(MultiMNIST)に関しても検証を行っています。以下は重ねたものに関して分離できているかを表す図です。
f:id:tekenuko:20171210171923p:plain:w500
R(,) が再構築をする際に使ったラベル、L(,) が正解のラベルを表しています。 白黒の画像はMultiMNISTの画像で、数字が2つ重なっています、カラフルな画像は下は再構築した画像で、R(,) のラベルを1つずつ使って再構築した画像を色違いで重ねています。重なっている場合でも概ねうまくラベル付けできるようです。

他のデータセット

CIFAR 10やsmallNORB、SVHMといったデータセットでも検証を行っています。ただし、例えばCIFAR 10では7モデルのアンサンブルによってテストエラー10.6%を達成しているなど、アンサンブルを行っています。そういった操作を行わない場合の性能が気になります。

Discussionと所感

CapsNetは、2000年代はじめの音声認識によるRNNと似たような研究段階にあるとの主張をしています。結構アナロジーがあるようです。音声認識では隠れマルコフがかつては有用な方法だったが、スケールすると非効率な点があり、再帰型のネットワークの研究が登場し、とって変わられたという歴史があるようですが*3、物体認識に関するCNNとcapsuleの関係も似たものである可能性があります。CNNは回転や平行移動はデータを水増しして学習する必要があるので、リッチな認識性能を獲得するためにはどんどん学習データを増やさなくてはいけません。一方で、capsuleは変換や重なりといった構造を少ないコストで獲得することが期待されるもので、研究が進めば物体認識でCNNにとって変わる存在になるかもしれません。正直、capsuleそのものや論文に関してきちんと理解ができていない部分があったり疑問点もありますが、将来非常に有用なものになる可能性を感じました。今後の研究の進展に期待したいと思います。

実装に関しては、早速何人かによって実装例が出ているようです。自分でも使ってみて、より理解を深めていきたいと思います。
github.com
github.com
github.com

*1:この言い方が適切かは検討が必要

*2:この重み行列に関しては、どう学習するかの記載がないのですが、おそらく誤差逆伝搬で学習するのだと思います。

*3:適当なこと言っているかもしれません。