Aidemy Tech Blog

機械学習・ディープラーニング関連技術の活用事例や実装方法をまとめる、株式会社アイデミーの技術ブログです。

不気味なロボットから考えるCNNの仕組みのおさらいとAIによる画像認識の攻防戦

こんにちは!今回ウィークリーニュースをまとめさせていただきます、たくやです!

今回はハロウィンも間近ということでこんなものを紹介したいと思います!

 

見てください、このなんともいえない不気味なロボット・・・。

f:id:meteoputi:20171019215839g:plain

夜、夢に出てきそうな不気味さがありますね・・・。

 

実はこれ、Creoqodeというところが作ったNOVAという自分で組み立てれる人工知能ロボットなんです!

 

f:id:meteoputi:20171019220135j:plain

このロボットを組み立てて、自分でプログラミングをすることにより、

コーディングや、電気やその他工学の概論を画像分析、画像処理、運動学、制御を含む様々な工学の概論を学ぶことができるそうです。

今は試作品段階のようですが、現在クラウドファンディングにて資金調達を行なっているようです。リリースされるのが楽しみですね。

f:id:meteoputi:20171019221906g:plain

f:id:meteoputi:20171019222422g:plain

 

このNOVA、車輪をつけて自分の後をついてくるようにしたり、(子供にそんなことしたら子供はギャン泣きしそうですが)、顔認識機能をつけたりと想像の及ぶ限り様々なことができるようなのですが、

この、画像認識、簡単に誤魔化すことができるのをご存知でしたか?

それが今回紹介させていただく撹乱画像(adversarial image)と呼ばれる手法です。

f:id:meteoputi:20171021061408p:plain

 http://karpathy.github.io/2015/03/30/breaking-convnets/

上の画像では57.7%の確率でパンダと判断された画像にちょっと手を加えることで、99.3%の確率でテナガザルだとAIに間違えて判断させることに成功しています。

私たち人間の目には二つの画像は同じもののように見えるのに、不思議ですね。

今回はそんな撹乱画像の仕組みをもう少し詳しく見ていきたいと思います。

■目次

CNNの仕組み                                                                                                                

AIを騙す?撹乱画像

撹乱画像の作り方とその防御

撹乱画像の脅威

 

通常、AIはディープラーニングを用いて、多くの画像からその動物の特徴を抽出し、それを元にして、画像に写っているのはその動物だ、ということを学習しています。

このとき、ディープラーニングで使われているのが

畳み込みニューラールネットワーク(CNN)と呼ばれる手法で、以前紹介させていただいたニューラルネットワークの進化版です。

ニューラルネットについて知りたい方は下記リンクをお読みください。

今話題のディープラーニングって一体なんなの? 文系から見た人工知能

 

■CNNの仕組み

CNN(Convolution Newral Network)は判断したいものの特徴に着目することで通常のニューラルネットワークと比べて重みの数が少なくてすみ、それによって計算も簡略化されるために、重宝されています。

このCNNは畳み込み層とプーリング層の二つの層をミルフィーユのように交互に重ね合わせた構造になっています。

まず、畳み込み層で、特徴を探していきます。

 

例えば "X"という文字を特定することを考えていきます。

X の特徴は

・右斜めに入った線

・二つの線がクロスしている部分

・左斜めに入った線

の三つが考えられます。

f:id:meteoputi:20171021065153p:plain

これらの特徴を探すために、今回は9×9の画像の中から3×3の一部に着目して、その特徴と合致するかを考えていきます。

左上から少しずつ3×3の枠をずらしていき、どれくらい特徴と合致していたかを数値化して再び出力していきます。

 

そうしてできた特徴マップをさらに抽象化するためにプーリングという処理を施していきます。

難しく聞こえますが、とても簡単で、今回はマックスプーリングというプーリング方法で2×2の枠の中の最大値を出力するという操作をまた、左上から枠を少しずつずらして繰り返していきます。

f:id:meteoputi:20171021070342p:plain

このようにして原画像がフィルタ処理され、プールされて、特徴がフィルタリングされた縮小画像のセットができあがります。

このセットを繰り返すことにより、さらに特徴を抽出していくことが可能となります。繰り返すごとに特徴はより大きくなり、画像はよりコンパクトになります。

もちろん、ディープラーニング(深層学習)という名前の中に”学習”という言葉が入っている通り、最初からうまく判断できるということはありません。

間違えを少しずつ修正しながらより良いモデルを生成していくのです。

では、この修正はどのように行うのでしょうか?

実はこの誤差を修正するために誤差逆伝播法と呼ばれる方法が用いられています。

先ほど説明したCNNは、入力から順番に計算していくので「順伝播」と呼ばれていて、誤差逆伝播法は誤差を出力層から逆に戻っていきます。

まず、適当な重みの値を設定して、学習データを入れて順伝播します。

そして、出力層で出た値と、本来欲しかった出力値の誤差を求めます。

誤差逆伝播法では、この誤差を最小にするため勾配降下法という手法を用います。

簡単に言うと、誤差の勾配、すなわち関数の傾きがマイナスの方向に点を動かしていくことで、極小値を出そうっていう手法です。

 

畳み込みニューラルネットワークの仕組み | コンピュータサイエンス | POSTD

CNNの詳しい説明には上記のブログを参照させていただきました。

 

少々話が逸れすぎてしましたが、誤差をEとすると勾配はそれを微分した式

Δ = ∇E

となり、それこそが撹乱画像のキーになる言葉なのです。

                                                                                                           

■AIを騙す?撹乱画像

 

例えば、バナナ画像を撮り、猫クラスのその画像の勾配に従って各ピクセルを動かすと、AIはその画像を猫だと判断してしまいます。画像は微かに変更する必要がありますが、その分、猫のスコアは増加します。しかしながら、その変化もわずかなために私たちの目には画像が他のものに写ったりすることはないのです。

要するに、誤差逆伝播法のせいで、わずかに変更した特徴が大きく拡張されてしまい、

取り込むと誤認識を起こしてしまうような致命的で局所的な欠陥も計算に取り込んでしまうのです。

このことを利用すると意図的にAIに、犬を飛行機と判断するような面白い間違えを起こさせることができます。

f:id:meteoputi:20171021081417p:plain

 https://blog.openai.com/robust-adversarial-inputs/

現に上の写真では猫をデスクトップコンピュータと勘違いさせることに成功しています。

                                                                                  

■撹乱画像の作り方とその防御                         

ここからはさらに深く、撹乱画像の作り方とその防御について学んでいきます。

撹乱画像を含むこのような手法はAdversarial exampleと呼ばれることがあります。

元の画像の入力を x 、誤認識させたい画像への摂動を η とすると入力後の画像は
x~=x+η
と表されます。

 

*摂動とは 分類器を考えた時、自然画像が高い確率で誤分類される原因となる普遍的な(画像に依存しない)ベクトルの存在。

 

ここでは小さな摂動を考えるため、小さな値
を用いて

η<ϵとします。

ηの要素のうち一番大きなものでもϵより小さいという意味です。画像のピクセルごとに摂動を与える時に、どれもϵよりも小さい摂動を与えることになります。)

重み w との内積は

wTx~=wTx+wTηと表されます。

ではどのような摂動η を与えれば影響を大きくできるでしょうか。 

sign(w)

の方向に摂動を与えれば良さそうです。

なぜなら、ベクトルの内積を最大化することを考えれば良いためです。

 

*1      

*2 signとsgnは同じです。

 

内積がベクトルであることを考えれば方向を揃えることで、その効果が倍増するのはわかると思います。

<補足>

f:id:meteoputi:20171021230432p:plain

https://juken-mikata.net/how-to/mathematics/vector-inner-product.html

cosθのθを0に近づければ近づけるほど(a,bのベクトルの方向が同じになればなるほど)aとbのベクトルの内積が大きくなるのがわかると思います。

 

それではFast Gradient Sign Method (FGSM)と呼ばれる撹乱画像の攻撃手法がどのような摂動を与えるのか式で見てみましょう。

x~=x+ϵsign(xLoss(x,y))


y
x に対応するラベルです。損失を最大化する方向に入力 x を変化させるという意味の式になっています。

もう少し詳しく解説すると、コスト関数は最もシンプルな形では

(Y1-Y2)^2 - (X1-X2)^2で表されます。

これを微分したものは一次関数であり線形であり、かつそれは傾き(勾配)を表しています。その傾きをベクトルとみなしているということです。

コスト関数を最大化するためにsignを用いてベクトルの方向を揃えています。

しかしながら、これは正しいクラスに対する損失を増加させているだけなので、どのクラスに誤分類されるかは分かりません。

しかし、その方向性をあらかじめ計算し、その重みを元画像に掛け合わせることで、

事前に意図したものにAIが画像を誤認識するように仕向けられるのです。

感覚的にわかってもらうために下のような図を用意しました。

f:id:meteoputi:20171022050036p:plain

図の青や赤で書いてある線は分類器が猫か、それ以外かを判断するための境界線だと思ってください。ただこれは平坦な紙の上に書かれたものではありません。

実はこの表面はボコボコしているのです。この傾斜は画像の特徴で構成されており、ボールを転がしてみて、最終的にボールが止まったところがどこの領域かで、AIはそれが何かを判断します。

今、何もしていない状態の猫の画像を優秀なAIに読み込ませたとします。ボールは猫の領域内で止まり、もちろんAIは正しく猫を猫だと判断することができるでしょう。

しかし、ピクセルをちょこっといじった撹乱画像を読み込ませてみましょう。

もちろん、ちょっとした細工が施してあるため、AIは猫とは全然関係ない特徴を拾い上げてしまい、コロコロとボールは坂を赤や青の線の外へと転がっていきます。そしてとうとう、ボールは境界線をこえ、AIはとうとう画像を猫だと判断できなくなります。

今、AIに猫の画像を読み込ませ、犬と判断させたいとします。

簡単です。犬の特徴量をピクセルに加えて傾斜を犬の方向へ調節してあげれば良いのです。こうして、ボールは犬の領域まで転がっていき,AIは猫の画像を犬と判断してしまいます。

 

このような手法で元画像を変化させていくと、どんな風に画像が変化していくでしょうか。例えば車の画像であれば、ダチョウクラスに向かって変化させていくと車の画像が徐々にダチョウっぽく変化していくと予想する人が多いのではないでしょうか。

実際にはそうはなりません。人間には分からないレベルで僅かに変化するだけです。人間には元画像と全く同じように見えますが、モデルは高い確信度でダチョウだと分類するようになってしまいます。

なぜ、人間の目には車に見えるのに、機械の目にはダチョウに映るのでしょうか。

それは、何度かでてきた”ベクトル”という言葉にヒントがあります。

ベクトルの方向を全て揃えてあげることで、一つ一つは小さな値でも、チリも積もればの原理でその力は大きなものになるのです。

具体的に例をみてみましょう。

[-0.4, 0.5, -0.2, 0.4, -0,3, 0.6, -0.4, 0.1, -0.9, 0.7]

[-1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0]

という数列があったとします。

両者の内積を普通に取ってしまうと答えは0.3となってしまいますが

signを使って符号を揃えてあげることで答えは4.5となります。

その差はなんと 4.2 です!違いは一目瞭然ですね。

画像の認識では数列はさらに大きく、複雑なものになります。

signを使って符号を揃えるのと揃えないのでは全く違った結果になってくるのです。

 

 

実はこの撹乱画像、ディープラーニングに特有の問題だと誤解されている場合がありますが、そうではありません。線形性に原因があることを考えるとロジスティック回帰のような単純なモデルでも問題になります。

Adversarial exampleに対する防御方法の一つとして、下記のようにadversarial exampleもtraining setに含めて学習するという方法が考えられます。

αLoss(W,x,l)+(1α)Loss(W,x+ϵsign(∇xLoss(W,x,y))
事前に行われるであろう攻撃をあらかじめ、学習に組み込んで、その上で正しい答えを出せるようにトレーニングをさせるため、エラー値が下がるのは当然と言えます。

現に普通に学習するとadversarial exampleに対しエラー率が89.4%だったモデルで、上記のようにしてadversarial trainingを行うとエラー率が17.9%に下がったという報告もあるそうです。だいぶエラー率が下がりましたが、まだ2割近くもエラーがあります。また、FGSMによる攻撃はある程度防御できても、他の手法による攻撃には脆弱であると考えられます。このように様々な攻撃に対して有効な防御方法は少なくとも現時点では存在せず、更なる研究が求められています。

 

はじめてのAdversarial Example

この撹乱画像の防御では上記のサイトを大いに参照させていただきました。

Adversarial Example をさらに詳しく知りたい方はこちらを参照してみてください。

                                                                                  

■撹乱画像の脅威

では、この画像の誤認識がなぜ問題となってくるのでしょうか?

今まで私たちは画像認識精度を高めることに注力しすぎて、画像認識への攻撃からの防御の術を開発してきませんでした。

また、たちが悪いことに攻撃の方が防御よりもよっぽど簡単なのです。

将来、自動運転が実現され、車が標識を読み込む際に、その看板に細工がしてある、なんてことを考えるとこの問題はやはり、簡単に見過ごすことができる問題とは言えそうにありません。

 

参照

http://postd.cc/how-do-convolutional-neural-networks-work/

http://palloc.hateblo.jp/entry/2016/08/02/213847

https://www.kickstarter.com/projects/creoqode/nova-diy-artificial-intelligence-robot?lang=ja

https://www.digitaltrends.com/cool-tech/creepy-robot-mask-coding/#/4

http://karpathy.github.io/2015/03/30/breaking-convnets/

https://elix-tech.github.io/ja/2017/10/15/adversarial.html

 

主成分分析と固有値問題

主成分分析がいかにして固有値問題へと帰着されるのか、詳しめに解説してみる。
少し数式が多め。

目次

主成分分析とは

主成分分析とは、"与えられたデータ情報の次元を圧縮する手法"の一つである。
データの次元を圧縮するとはどういうことだろうか?実は日常生活でもデータを低次元化するということはよく行われている。例えば、肥満度を測るためのBMIという指標が存在する。BMIは体重÷(身長)^2という計算式で導出されるものであるが、よく考えてみると元のデータは(身長,体重)という2成分を持つデータであったのに対し、BMIはただ1成分の数値となっている。これが次元の圧縮であり、情報を削ぎ落としたにも関わらず、肥満度という特徴を表すのに十分な情報を持っている。このように、データに適切な処理を行えば、情報量の削減と特徴の抽出を同時に行う事ができる。また、人がイメージできないような高次元データも、3次元以下に圧縮すればグラフとして描画できるようになる、といったメリットもある。


しかし、現実問題ではBMIの場合のように特徴量があらかじめわかっている場合だけではない。そこで、与えられたデータの傾向から自動的に特徴量を見つけ出し、その特徴を良く表す低次元データへと次元圧縮を行うのが「主成分分析」である(これはある種の機械学習であり、特に自動的に特徴量を見出すという点において、「教師なし学習」と分類されるものである)。


f:id:ut25252:20171019212457p:plain:w500
具体的にどのように次元圧縮を行えばいいのだろうか?簡単に、二次元のデータを一次元へと圧縮する場合を考えてみよう。二成分の数値を一成分へと変換する計算式など無数に存在しそうだが、ここでは図のように、データを二次元平面にプロットした時に何らかの直線に向かって全データを射影するという方法を考えることにする。射影した結果、直線の垂直方向の情報が完全に失われ、平行方向の情報のみの一次元データが残る。この残された情報が出来るだけ元データの特徴を良く表していて欲しい。この時、残された一次元データがなるべくバラついている、つまり分散の値が大きいほどよいのである。なぜなら、バラついているということは、各データ点1つ1つの違いをより多く情報として保っていることになるからである。逆に言えば、バラツキの少ない方向というのは、各データが共通して持っている自明な情報なので削除してしまっても問題ないのである。


まとめると、主成分分析では、データの次元圧縮を行う。その際、圧縮後のデータの分散が大きくなるような射影をすることで、特徴量を自動的に抽出するのである。

共分散行列?固有値問題?

統計や数学の勉強を始めると、何に使うのかよくわからない概念が数多く出て来る。
例えば、統計学の教科書や授業などでは「分散」や「共分散」という概念を習う。
「分散」とは、ある一次元のデータ$(x_1, x_2, x_3)$に対して、平均値$\overline{x}$からの差の二乗和の平均である。式で書けば次のようになる。
\begin{eqnarray}
\sigma_{xx} = \frac{ (x_1-\overline{x})^2+(x_2-\overline{x})^2+(x_3-\overline{x})^2 }{3}
\end{eqnarray}「共分散」とは、ある多次元のデータ$( (x_1,y_1), (x_2,y_2), (x_3,y_3) )$に対して、平均$\overline{x}$または$\overline{y}$からの差、の積平均にあたる。式で書けば次のようになる。
\begin{eqnarray}
\sigma_{xy} = \frac{ (x_1-\overline{x})(y_1-\overline{y})+(x_2-\overline{x})(y_2-\overline{y})+(x_3-\overline{x})(y_3-\overline{y}) }{3}
\end{eqnarray}分散は式や定義から、データのバラつきを表していることが直感的にわかる。また、共分散はすぐにはわかりづらいが、xデータとyデータの相関度合いを示すものである。続いて、これらを要素として並べた「共分散行列」なるものが存在する。
\begin{eqnarray}
\Sigma &=& \left( \begin{array}{cc} \sigma_{xx} & \sigma_{xy} \\ \sigma_{xy} & \sigma_{yy} \end{array} \right) \\
&=&{\scriptsize \frac{1}{3} \left( \begin{array}{cc} (x_1-\overline{x})^2+(x_2-\overline{x})^2+(x_3-\overline{x})^2 & (x_1-\overline{x})(y_1-\overline{y})+(x_2-\overline{x})(y_2-\overline{y})+(x_3-\overline{x})(y_3-\overline{y}) \\ (x_1-\overline{x})(y_1-\overline{y})+(x_2-\overline{x})(y_2-\overline{y})+(x_3-\overline{x})(y_3-\overline{y}) & (y_1-\overline{y})^2+(y_2-\overline{y})^2+(y_3-\overline{y})^2 \end{array} \right) }
\end{eqnarray}といった感じである。行列の形に並べて何の意味があるのか、筆者自身も最近までその使い道を知らなかった。


他には、数学の線型代数において、何らかの行列を与えられた時に固有値と固有ベクトルを求めるという「固有値問題」がよく登場する。
突然なんらかの行列、例えば$\left( \begin{array}{cc} 1 &2 \\ 3 & 4 \end{array} \right)$が与えられた時、次の固有方程式
\begin{eqnarray}
\left( \begin{array}{cc} 1 &2 \\ 3 & 4 \end{array} \right) \left( \begin{array}{c} x \\ y \end{array} \right) = \lambda \left( \begin{array}{c} x \\ y \end{array} \right)
\end{eqnarray}を満たす固有値$\lambda$と固有ベクトル$\left( \begin{array}{c} x \\ y \end{array} \right)$を求めよ、といった具合である。しかし、数学として固有方程式を解く方法だけ学んだとしても、結局何のためにあるのかわからないまま終わってしまう。


これら二つの謎概念(共分散行列、固有値問題)を応用する例の1つが「主成分分析」なのである。

共分散行列についての固有方程式

問題設定

ここからは具体的なデータ群に対して主成分分析を行ってみよう。
少し数学的にはなるが、流れはいたって単純で、

  1. ある方向に射影した時のデータの分散を計算する
  2. 分散が最小になるような方向を見つける

を行うだけである。結論としては、1を行うと共分散行列が出現し、2を行うとその共分散行列に対する固有方程式が得られる。

f:id:ut25252:20171019201356p:plain:w250
シンプルな例として、3つの二次元データ
\begin{eqnarray}
\vec{a}_1 = \left( \begin{array}{c} a_{1x} \\ a_{1y} \end{array} \right), \vec{a}_2 = \left( \begin{array}{c} a_{2x} \\ a_{2y} \end{array} \right), \vec{a}_3 = \left( \begin{array}{c} a_{3x} \\ a_{3y} \end{array} \right)
\end{eqnarray}についての主成分分析を考えてみよう。簡単のため、重心が原点(0,0)となるようなデータであるとしている。(このように仮定しても問題ないことは最後まで読めばわかるはず)

準備

今後のために、次のようにデータ成分を並べた行列を定義しておく。
\begin{eqnarray}
X &\equiv& \left( \begin{array}{c} \vec{a}_1^\mathrm{T} \\ \vec{a}_2^\mathrm{T} \\ \vec{a}_3^\mathrm{T} \\ \end{array} \right) \\
&=& \left( \begin{array}{cc} a_{1x} & a_{1y} \\ a_{2x} & a_{2y} \\ a_{3x} & a_{3y} \\ \end{array} \right)
\end{eqnarray}転置行列$X^{\mathrm{T}}$との積$X^{\mathrm{T}} X$を計算してみると、
\begin{eqnarray}
X^{\mathrm{T}}X
&=&
\left( \begin{array}{ccc} a_{1x} & a_{2x} & a_{3x} \\ a_{1y} & a_{2y} &a_{3y} \\ \end{array} \right)
\left( \begin{array}{ccc} a_{1x} & a_{1y} \\ a_{2x} & a_{2y} \\ a_{3x} & a_{3y} \\ \end{array} \right) \\
&=&
\left( \begin{array}{ccc} a_{1x}^2+a_{2x}^2+a_{3x}^2 & a_{1x}a_{1y}+a_{2x}a_{2y}+a_{3x}a_{3y} \\
a_{1x}a_{1y}+a_{2x}a_{2y}+a_{3x}a_{3y} & a_{1y}^2+a_{2y}^2+a_{3y}^2 \\ \end{array} \right)
\end{eqnarray}最終的な表式が、上で見た共分散行列の形になっていることがわかる("1/データ数"の因子を除いて)。共分散行列は後で出てくるので、次のように$\Sigma$として定義しておこう。
\begin{eqnarray}
\Sigma \equiv \frac{1}{3} X^{\mathrm{T}}X
\end{eqnarray}

1. 単位ベクトルにデータを射影した時の分散を計算

f:id:ut25252:20171019201416p:plain
ある方向の単位ベクトルを$\vec{e} = \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right)$と表す。データを$\vec{e}$の方向に射影して、1次元データとなった後の分散値の表式を求める。例えば、$\vec{a}_1$を$\vec{e}$に射影した値は$\vec{a}_1\cdot \vec{e}$のようにベクトルの内積を取ることで簡単に計算でき、これを用いて分散(var)を定義通りに計算すると、
\begin{eqnarray}
\mathrm{var} = \frac{ (\vec{a}_1\cdot \vec{e})^2 + (\vec{a}_2\cdot \vec{e})^2 + (\vec{a}_3\cdot \vec{e})^2}{3}
\end{eqnarray}
となる(平均値は0となっていることに注意)。これを行列の積の形に分解して表現してみると、次に示す式の一行目のようになる。実は、この分解した行列がそれぞれ($X \vec{e}$)とその転置行列$(X \vec{e})^\mathrm{T}$になっている(実際に計算してみれば確認できる)。二行目から三行目では行列の性質を用いている。こうすると$X^\mathrm{T} X$という並びが出現する。これは上で計算した共分散行列$\Sigma$である。
\begin{eqnarray}
\mathrm{var} &=& \frac{1}{3}
\left( \begin{array}{ccc} \vec{a}_1\cdot \vec{e} & \vec{a}_2\cdot \vec{e} & \vec{a}_3\cdot \vec{e} \end{array} \right)
\left( \begin{array}{c} \vec{a}_1\cdot \vec{e} \\ \vec{a}_2\cdot \vec{e} \\ \vec{a}_3\cdot \vec{e} \end{array} \right) \\
&=& \frac{1}{3} (X \vec{e})^\mathrm{T} (X \vec{e}) \\
&=& \frac{1}{3} \vec{e}^\mathrm{T} X^\mathrm{T} X \vec{e} \\
&=& \vec{e}^\mathrm{T} \Sigma \vec{e}
\end{eqnarray}
このvarの表式はデータが多成分(つまり高次元)、多データ化しても一般的に成り立つはずである。なぜなら、行列Xやベクトル$\vec{e}$の縦成分と横成分数をデータ次元数とデータ数に併せて拡張すれば同じような計算が行えるからである。

2. varが最小になる方向を求める

さて、主成分分析ではこのvarを最小化するような$\vec{e}$を求めることが目的であった。また、$\vec{e}$には単位ベクトル、つまりノルムが1であるという制約条件も付いている。このように、ある制約条件のもとで、関数の最大(最小)を決定する時には、「ラグランジュの未定乗数法」というものがよく用いられる。

 ラグランジュの未定乗数法:
  変数x,yについて、$g(x,y)=0$の制約条件下で$f(x,y)$という関数を最小化するのは、
$L(x,y,\lambda) \equiv f(x,y) - \lambda g(x,y)$
  という関数を定義した時に、
$\frac{\partial L}{\partial x} = 0, \frac{\partial L}{\partial y} = 0,\frac{\partial L}{\partial \lambda} = 0$
  を満たすような解$(x,y)$である。

ラグランジュの未定乗数法を用いると、
\begin{eqnarray}
L(e_{x} ,e_{y} , \lambda )
&\equiv& \mathrm{var} - \lambda (e_x^2 + e_y^2 -1 ) \\
&=& \left( \begin{array}{cc} e_{x} & e_{y} \end{array} \right) \Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right) - \lambda (e_x^2 + e_y^2 -1 )
\end{eqnarray} と定義した関数$L(e_{x} ,e_{y} , \lambda )$に対して、
\begin{eqnarray}
\frac{\partial L(e_{x} ,e_{y} , \lambda )}{\partial e_x} = 0 \\
\frac{\partial L(e_{x} ,e_{y} , \lambda )}{\partial e_y} = 0
\end{eqnarray} が成立するような$e_{x} ,e_{y} $を見つければよい($\lambda$についての偏微分は一旦無視する)。
$\frac{\partial L(e_{x} ,e_{y} , \lambda )}{\partial e_x}$,$\frac{\partial L(e_{x} ,e_{y} , \lambda )}{\partial e_y}$を計算すると、
\begin{eqnarray}
\frac{\partial L(e_{x} ,e_{y} , \lambda )}{\partial e_x}
&=& \left( \begin{array}{cc} 1 &0\end{array} \right) \Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right) + \left( \begin{array}{cc} e_{x} & e_{y} \end{array} \right) \Sigma \left( \begin{array}{c} 1 \\ 0 \end{array} \right) - 2 \lambda e_x \\
&=& \left( \begin{array}{cc} 1 &0\end{array} \right) \Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right) + \left[ \left( \begin{array}{cc} e_{x} & e_{y} \end{array} \right) \Sigma \left( \begin{array}{c} 1 \\ 0 \end{array} \right) \right] ^{\mathrm{T}} - 2 \lambda e_x \\
&=& \left( \begin{array}{cc} 1 &0\end{array} \right) \Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right) + \left( \begin{array}{cc} 1 &0\end{array} \right) \Sigma^\mathrm{T} \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right) - 2 \lambda e_x \\
&=& 2 \left( \begin{array}{cc} 1 &0\end{array} \right) \Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right) - 2 \lambda e_x \\ \frac{\partial L(e_{x} ,e_{y} , \lambda )}{\partial e_y}
&=& 2 \left( \begin{array}{cc} 0 &1 \end{array} \right) \Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right) - 2 \lambda e_y
\end{eqnarray} であり、これらが0になるので
\begin{eqnarray}
\left( \begin{array}{cc} 1 &0\end{array} \right) \Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right)
&=& \lambda e_x \\
\left( \begin{array}{cc} 0 &1\end{array} \right) \Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right)
&=& \lambda e_y
\end{eqnarray} と式変形出来る。2つの方程式をまとめると、
\begin{eqnarray}
\left( \begin{array}{cc} 1 &0 \\ 0 & 1\end{array} \right) \Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right)
&=& \lambda \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right)
\end{eqnarray} 一番左の行列はただの単位行列なので省略して、
\begin{eqnarray}
\Sigma \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right)
&=& \lambda \left( \begin{array}{c} e_{x} \\ e_{y} \end{array} \right)
\end{eqnarray} これは共分散行列$\Sigma$に対する固有方程式となっている!!
(上で無視していた$\frac{\partial L(e_{x} ,e_{y} , \lambda )}{\partial \lambda} = 0$、つまり$e_x^2 + e_y^2 = 1$というベクトルの正規化条件は、固有値問題を解いた後の固有ベクトルを拡大縮小することで調節出来る。)
実際にデータを主成分分析する際には、データから共分散行列を生成し、固有ベクトルを計算、全データを射影する、といった作業を行えば良い。プログラミング言語によってはこれらの作業を簡略化出来るパッケージが存在したりするので、とても簡単に処理出来る。

参考リンク

主成分分析に関しては、以下の解説およびブログを参照した。
http://manabukano.brilliant-future.net/document/text-PCA.pdf
yusuke-ujitoko.hatenablog.com



「俺」は背中で語る ~一人称から見るTwitter分析~

f:id:iTeresa:20171017182425p:plain

 こんにちは、てれにゃんです。幼少期は自分のことを「てれちゃん」と呼び、思春期から恥ずかしくなって「ウチ」になり、それも恥ずかしくなって「わたし」への矯正に成功してはや10年。

 自分のことを何と呼ぶかで、与える印象は大きく変わります。男性の場合で考えると、「おれ」だとより男性的で力強いイメージ「ぼく」だと従順で腰が低いイメージかなと思います。では、実際に使う一人称によって、発言自体に違いが現れてくるのでしょうか?

 今回はTwitterのツイート文字数に違いがあるかを調べました。日本の男らしい男性は、あまり多くを語らないとよく言われます。「俺」「僕」はいずれも一般的に男性が使う一人称ですが、「俺」の方がより男性性の強いイメージがあります。このことから、「俺」を使う人の方がより多くを語らないのではないかと考えられます。そこで、以下の仮説を立てました。

〜仮説〜

「俺」を使う人は「僕」を使う人より1ツイートにおける文字数が少ない?

 ちなみに、俺や僕を使う女性もたまにいますが、そのへんは無視して考えたいと思います。 では早速調べてみましょう。

0.環境&準備

  • Python

  • jupyter notebook

  • anaconda

  • Twitterアカウント

1.「俺」「おれ」「僕」「ぼく」を含むツイートを取得

 まずはサーチをしながら手っ取り早くたくさんのツイートを取得したいと思います。Twitterを使って分析したい場合はとにかくAPIを取得する必要がありますので、API取得のためのキーを持っていない方はまずこちらを参照してください。

qiita.com

 以下のリンクのコードをコピペすると、API切れなども考慮された挙動をするため大変便利です。約10000件まではAPI切れせず素早く取得できました。それ以上は15分ほど待てばさらに取得可能です。

ailaby.com

#キーワードで取得
getter = TweetsGetter.bySearch(u'俺 OR おれ OR 僕 OR ぼく')

 サーチ単語をこのように書き換えてしまえばOKです。カタカナも含めようか考えましたが、「カフェオレ」が一緒に拾われそうなのでやめました。

2.俺/僕判定

 「俺」「おれ」「僕」「ぼく」のいずれかが含まれるツイートを取得しましたが、1ツイートの中で一人称が複数含まれる場合が考えられます。俺が2回や俺とおれの両方が含まれている場合は「俺」、俺と僕の両方が含まれている場合は「どちらでもない」という判断をする必要があります。

 以下では「俺」「おれ」が含まれていたらOREリストに1、「僕」「ぼく」が含まれていたらBOKリストに1が追加されるようになっています。ただし、俺と僕、俺とぼく、おれと僕、おれとぼくのような組み合わせの場合は、いずれのリストも0となります。

        counter = Counter(tweet["text"])
        ORE_c=counter["俺" or "おれ"] 
        BOK_c=counter["僕" or "ぼく"]
        ore=0
        bok=0

         if ORE_c + BOK_c >= 1:
        #俺か僕のフラグをたてる
            if ORE_c > BOK_c :
                ore=1
                fp=1
            elif BOK_c> ORE_c:
                bok=1
                fp=2
            else:
                fp=0
        else:
            fp=0

 fpは、俺が1、僕が2、その他(俺と僕が同数など)が0となるようにしています。

3.データの記録

 下の for では、ツイートに含まれる複数情報の取得を20000回繰り返しています。取得したらただちに append を使ってリストに追加していきます。

cnt = 0
TweetList = []
ORE = []
BOK = []
FP = []
LEN = []

for tweet in getter.collect(total = 20000):
        cnt += 1
        tweet["text"] = tweet["text"].replace('\n','')
        TweetList.append(tweet["text"])
        ORE.append(ore)
        BOK.append(bok)
        FP.append(fp)
        LEN.append(len(tweet["text"]))

 ここでは文字数が重要になってきますので、空白や改行が邪魔になります。そのため

tweet["text"] = tweet["text"].replace('\n','')

で空白・改行を消しました。

 また、len(tweet["text"]) で取得したテキストデータから文字数をカウントしています。

import pandas as pd

#データフレームに変換
df = pd.DataFrame([TweetList,
                   ORE,
                   BOK,
                   LEN]).T
df.columns = ["tweet","ore","bok","len"]

#CSVに保存
df.to_csv('TwitterData.csv',encoding="utf-16")

utf-8だと文字化けしてcsvでテキストを読むことができなかったため、utf-16にしています。 リストをデータフレームにまとめたことで、dfはこのような状態になっています。 f:id:iTeresa:20171019005658p:plain

4.ヒストグラム

 まずはどれくらいの文字数でのツイートが多いかを可視化するために、一人称ごとでヒストグラムを描きます。ここでは3つのヒストグラムが重なって表示されるようにしていますが、fp=0 は「俺」でも「僕」でもない邪魔者なので消えてほしいです。そのため、とりあえず fp=0 のときだけ透明にして見えないようにしておきました。透明度は plt.hist() の中の alpha で調節できます。

import matplotlib.pyplot as plt

col = ["b","g","r"]
alp = [0, 0.3, 0.3]

for key, grp in df.head(total).reset_index().groupby('fp'):
    if len(grp['len']) != 1:
        plt.hist(grp['len'], bins=20, alpha=alp[key], histtype='stepfilled',color=col[key])
plt.ylabel("Tweet (frequency)")
plt.xlabel("word count")
plt.show()

f:id:iTeresa:20171018212448p:plain

 いずれの一人称も30文字あたりがピークとなっていますが、緑(俺)の方がかなり多くなっています。それに対して、ピークを過ぎてからは俺と僕で差が小さくなっています。

 ぱっと見では緑(俺)の方が文字数が少ないように見えますが、そもそも緑(俺)の方がツイートが多いため、このヒストグラムではよくわかりません。そこで、正規化してもう一度ヒストグラムを出してみます。

plt.hist(grp['len'],normed = True, bins=20, alpha=0.3, histtype='stepfilled',color=col[key])

f:id:iTeresa:20171018212446p:plain

このように、上のコードに normed = True をつけたすだけで正規化できました。

 縦軸は割合となり、緑と赤の合計が同じとなりました。さきほどとは異なり、60文字以上で赤(僕)が緑(俺)を上回っているのが目立ちますね。

5.棒グラフ

 まず記述統計をしたいと思いますが、dfにはツイートの文字が含まれているため、数字を数字として扱うことができません。そのため、以下のようにnew_dfという数字だけのデータフレームを新たに作りました。

df.ore = pd.to_numeric(df.ore)
df.len = pd.to_numeric(df.len)
df.fp = pd.to_numeric(df.fp)

new_df = pd.DataFrame()
new_df["ore"] = df.ore
new_df["len"] = df.len
new_df["fp"] = df.fp

 平均値および中央値は以下のように簡単に出せます。

#平均値
len_mean = new_df.groupby("ore")["len"].mean()
print("mean:"+str(len_mean))

#中央値
len_median = new_df.groupby("ore")["len"].median()
print("median:"+str(len_median))
#一人称ごとのグラフ
X = [1,2]
Y = [len_median[0],len_median[1]]
plt.bar(X,Y, align="center")
plt.xticks(X, ["BOKU",'ORE'])
plt.xlabel("First-Person")
plt.ylabel("word count (median)")

#標準誤差バー
SD = new_df.groupby("ore")["len"].std()
ORE_SE = SD/math.sqrt(df["ore"].sum())
BOK_SE = SD/math.sqrt(df["bok"].sum())
err = [BOK_SE[0],ORE_SE[1]]
plt.errorbar(X,Y,yerr=err,fmt='k ',ecolor='k')
plt.show()

f:id:iTeresa:20171018223636p:plain

 文字数について、「俺」の平均値が54.3文字、「僕」の平均値が61.6文字でした。中央値も「俺」で42文字、「僕」で52文字となり、いずれの値においても、「俺」の方が1ツイートにおける文字数が少ないという結果になりました。なお、標準誤差エラーバーもがんばって出してみましたが、小さすぎてゴミのようになってしまいました。

6.検定

 こちらの方法で統計的検定ができます。

from scipy import stats
group_ore = new_df[new_df['fp'] == 1]['len']
group_bok = new_df[new_df['fp'] == 2]['len']

#t検定
print(stats.ttest_ind(group_ore, group_bok))

#U検定
print(stats.mannwhitneyu(group_ore, group_bok))

 ヒストグラムを見てわかるように、文字数は正規分布していないので、今回はt検定ではなくU検定をします。 俺群と僕群の間で文字数についてU検定をおこなった結果、U値が30960093.5、p < 0.05(p = 4.15e-42)で両者の差は有意となりました。つまり、「俺」は「僕」より1ツイートにおける文字数が少ないという仮説が支持されました。2万件近くもツイートがあると、p値が5%切るのは余裕ですね。検定するまでもなく明らかです。たくさんデータをとるとp値は小さくなりますが、p値は2群間の差の大きさを表すものではないので注意してください。

7.まとめ

 ヒストグラムと棒グラフで視覚的に確認しても、検定を行って数値で確認しても、「俺」を使ったツイートの方が「僕」を使ったツイートよりも文字数が少ないことがわかりました。「俺」を使う男らしい(印象を与える)人は口数が少なく、背中で語るような人なのかもしれませんね。

 今回紹介した統計的分析手法は心理学でもよく使われる基礎的なもので、わざわざPythonを使わなくてもできそうなものです。しかし、Pythonを使うことで、Twitterで取得した大量のデータを使って気軽に遊ぶことができます。Twitter上に埋まった大量の宝をぜひPythonで掘り出してみてくださいね!

f:id:iTeresa:20171017180016p:plain

顔認証の基本である顔認識をOpenCVを使って簡単に実装!

f:id:hiroki-ddd:20171011230425j:plain

こんにちは!

研修中のヒロです!

機械学習という分野を未経験から学んでいる最中なのですが

最近よく耳にする顔認証も機械学習による技術になります!

 

「iPhone X」採用の顔認証「FaceID」

Appleが発表した新型iphone Xも従来の指紋認証システム

「Touch ID」に代わり、新たな顔認証システム「Face ID」を発表しましたよね!Appleによれば、顔認識は、指紋認証に比べて誤認識をなんと20分の1に減らすことができると言われています。この「Face ID」も機械学習を活用されております。

AIが人間の顔を認識する精度はここ最近、かなり向上していて、顔を認識するAIとディープラーニングは、コンピュータの視覚システムの心臓部であり、AIは人間よりも正確に顔を認識することができるくらいに成長しています!

 

広がる顔認証システム

これからはiphoneのようにスマホのカメラに顔を向けるだけで簡単にユーザー認証とロック解除ができてしまうことがだんだん当たり前になりつつあります。指紋認証のような無駄な手間が省かれるのは便利ですよね。

顔で同一人物か判断するこの顔認証は、スマホ以外にも様々なシーンでも利用されています。例えばUSJの年パスを購入した本人かどうか顔認証システムで確認したり、オフィスへの入退室のチェックも顔認証している企業もあり、入国審査で顔認証を利用している国もあったりします、そして日本も羽田空港で導入するようです。

羽田の帰国手続きに「顔認証」 10月から認印不要に :日本経済新聞

 

利用が進んでいる顔認証の技術ですが、いったいどんな仕組みになっているのか?

 

顔認証の仕組み

コンピュータは顔検出をして他の物の写真の中からどれが人間の顔かを最初にみつけます!このとき、コンピュータはまず明暗に注目します。下の中央の写真のように、人間の顔をモザイク状にしてみると、目のあたりは暗く、その周囲は明るい。あるいは、鼻筋は明るいがその両側は暗いというような、大ざっぱな特徴があることわかります。

f:id:hiroki-ddd:20171012000921j:plain

そうして見つけたいくつかのパターンを、コンピュータはあらかじめ学習したデータと比較し人間の顔のパターンのデータと一致する場所が写真の中にあれば顔として認識するわけですね。これが顔検出の最初のプロセスです!

顔の場所がわかったら次は「顔認識」、つまり誰の顔かを判断するプロセスへと進む。顔認識では、コンピュータは検出された顔の目や眉毛、鼻、口などの部分を見つけて、そこに印をつけていく。たとえば、唇のまん中と端っこといった具合です。

実は、このプロセスを実地に試してみることができるサイトがあります。「detectFace();」というサイトで、ここの「簡易サンプル」というページに画像をアップすると、顔認識処理をしてくれます。実際に処理してみたのが下の写真です。

f:id:hiroki-ddd:20171012002146j:plain

赤い点で表示されているのが、特徴点と呼ばれるポイントで、この特徴点のすべてに、ポイントID、つまり名前がついています。この各特徴点の位置や特徴間の距離などが、この人の特徴を数値で表す顔認証データとなるわけです。

このデータをたとえばA子さんという名前で登録をしておくとほかの画像データで「顔検出→顔認識」のプロセスを繰り返し、顔認証データがA子さんと同じものが見つかれば、それもA子さんの写真ということになります。

こうしてたくさんの画像データのなかから、A子さんの顔認証データの数値と同じもの(あるいは似ているもの)を検索して見つけるというわけです。顔認証の仕組みを簡単に説明すると、以上のようになります。

では次は顔認証の技術の初歩的な顔認識を実践してみましょう!

 

 

OpenCVを使って簡単に顔認識をしてみよう!

OpenCVとは?

オープンソースのコンピューター・ビジョン・ライブラリです。コンピュータで画像や動画を処理するのに必要な、さまざま機能が実装されています。

www.buildinsider.net

OpenCVのHaar-like特徴分類器を使って、顔認識をしてみます。

Haar-like特徴を用いたブースティングされた分類器のカスケードはこちらからダウンロードしてみてください。https://github.com/opencv/opencv/tree/master/data/haarcascades

  • 環境について
  • jupyter notebook
  • 使用言語
  • python3系
  • 使用したパッケージ
  • OpenCV:特徴点を抽出する
  • matplotlib(plt):グラフの作成

 では実際にやってみましょう!

  1.  cv2.CascadeClassifierでHaar-like特徴分類器を作成し
  2. Haar-like特徴分類器に対して画像を読み込む
  3. 画像に対して検出した領域を矩形で囲む

画像はこちらを使用します!

f:id:hiroki-ddd:20171012151914j:plain

import cv2 
import matplotlib.pyplot as plt 
%matplotlib inline
#顔
face_cascade_path = "haarcascade_frontalface_alt.xml"
# カスケード分類器を作成
face_cascade = cv2.CascadeClassifier(face_cascade_path) 

# 画像を読み込む
img = cv2.imread('face.jpg') 
# グレースケール化
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 


# 出力結果用にコピー & RGB化
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 
#囲む色
color = (0, 255, 0) 
#顔を検知
faces = face_cascade.detectMultiScale(img_gray) 
for (x,y,w,h) in faces:
    # 検知した顔を矩形で囲む
    cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
    roi_color = img[y:y+h, x:x+w]
    
#画像表示
plt.imshow(img)

短いコードで済みました!

 実行結果

f:id:hiroki-ddd:20171012152735p:plain

上手いこと全員の顔を認識できました!

ただしこの画像は全員正面を向いているのでかなり認識しやすいものです。

OpenCVの便利さを感じて頂けたでしょうか?
今回、使用したのは顔だけでしたが他にも笑顔や目も認識できますので!

簡単な物ならこのように簡単に実装することができます!

今回は以上となります。機械学習やディープラーニングについてさらに技術的な面からも

学びたい人は弊社Aidemyのプログラミング講座もぜひご検討ください。

無料カウンセリングは下のリンクから

aidemy.net

最後までご覧頂き,ありがとうございました!

 

 

 

今話題のディープラーニングって一体なんなの? 文系から見た人工知能

こんにちは!ゴリゴリの文系学生のたくやです! 

実は僕、今Aidemyで研修中なのですが、

課題. MNISTデータセットを多層パーセプトロン(MLP)で学習せよ

うーん、なんだかかっこいいけど何を言ってるのか分からん!笑みたいな状況です笑

 

 今回はそんな僕がAI(人工知能)について学んできて分かってきた内容をみなさんにシェアしたいと考えています!

今回は他のブログにあるような難しいコードは一切ないので箸休め的な感じで気楽に読んでもらえたらと思います!

■目次

AI、機械学習、ディープラーニングの違い  

ディープラーニングの仕組み  

今日からディープラーニングを使う  

ディープラーニングについてもっと知ろう!

 

AI、機械学習、ディープラーニングの違い

まず、AI、機械学習、ディープラーニングなどの言葉を最近よく耳にするようになりましたが、皆さんはこの3つの違いを明確に説明することはできますか?

 

 

 

 

 

 

 

答え合わせです!

         http://deliways.com/wp/wp-content/uploads/2016/09/da9c8fe340571bb3c3001d536a63234d.png

 http://deliways.com/blog/marketingautomation/172

実は上記のような関係性になっています。

最近話題のディープラーニングは人工知能の中の機械学習という分野の話だったんですね!

 

 

また、別の角度から三者の違いを説明する為に"AIの4つの知能レベル"という概念を取り入れて見ます。具体的なシチュエーションとしてたくさんの荷物が積まれた流通倉庫を考えてみましょう!

 

<レベル1>

荷物を決められた規定で仕分けする。

これは元来、制御工学やシステム工学と呼ばれる分野であって近年のAIブームに乗っかってAIと呼ばれだした分野です。ちょっとその道の人たちに対しては失礼な気もしますね。

 

<レベル2>

荷物を決められた規定で仕分けしつつ、丁寧に扱うなどのタグがついていたらそれに従う。

これは入力されたら決められた値を出力するという古典的なAIです。(AIのブームは1956年頃から実はもう起こっていました。)

 

<レベル3>

荷物のサンプルからルールを自分で学習して仕分けする。

これは入力と出力を関連づける方法をデータを基に学習する段階ですが、これが機械学習と呼ばれています。

 

<レベル4>

このタイプの荷物はサイズで見れば大だけど形状が違うから別扱いにしようなど自分でルールを作って一番効率的な仕分けを学習する。

この段階まできたものディープラーニングと呼んでいます。

 

ディープラーニングの仕組み

では次に今話題のディープラーニングはどのような仕組みで動いているのか、簡単ではありますが説明したいと思います。

 

ディープラーニング(深層学習)はその名前の通り層を深くすることで個々の事象を抽象化していく方法です。

          https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRE_oU7aRorcmDK5e0h5hSqCyipVcT7kpfpan8wyCuuz6Q3u3-M

http://qiita.com/rkosaka/items/60105faf86be1fa1d750

上の図では入力された情報X1とX2が細いところを通って真ん中の隠れ層と呼ばれるところに出力されます。この通路は細いため、すべての情報がそのまま伝達されるわけではありません。

中間層の値をもう一度、細いところに通し今度は一番右の出力層と呼ばれるところに出力します。このとき出てきた値がy1、y2という本来なって欲しかった値と等しくなるように細い経路を通る時にどこの情報を重視するかという「重み」が修正されます。(専門用語では活性化関数という数値が値に対して掛け算されます。)

その後、出力層とX1、X2は重ねてしまい、

今度は隠れ層だったところを入力層として再び同じ操作を繰り返していきます。

これを繰り返すことで、どこの部分に着目すれば本来あるべき値に近づけることができるのかということが洗練されます。それによって抽象度が上がり、事象の本質をコンピュータが学んでいきます。

      https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSW-rY_O5zG0HNxYA3QctDYrOR_WHnpH_HQ-cDW5VCT_QV0zo9e

https://deepage.net/deep_learning/2017/05/23/recurrent-neural-networks.html

上図ですと隠れ層は2つになっていますが、複雑なものになりますと隠れ層がもっと多くなることもあります。

 

少々、難しかったしょうか?

今日からディープラーニングを使う

こういったディープラーニングを本格的に導入するとなると大変な研究開発コストと深いコンピュータの知識が知識が必要になりそうです。ディープラーニングの技術を適用したい中小企業にとっては遠い未来のお話に思えるかもしれません。

しかし、今、だれもがディープラーニングの成果をビジネスに生かせるサービスが既にグーグルから提供されています。

 グーグルから提供されるサービスは大きく分けて2種類

・機械学習の訓練済みモデルをAPI(アプリケーション・プログラミング・インターフェイス)として提供する形

・機械学習ライブラリのTensorFlowの提供

まずは前者について詳しく解説します。

グーグルでは四つのAPIを公開してます。

・Google Cloud Vision API

画像認識、画像分析の機能を提供するAPIです。画像に含まれているものによって素早く複数のカテゴリーに分類してくれます。(例えば、ヨット、ライオン、りんご、など。)また、画像の中に複数の物や人物が写っている場合は個別に検出することができたりテキスト部分を検出して読み取ることも可能です。さらに、人の感情を読み取って感情を評価するマーケティングなどに生かすことも可能です。

・Google Cloud Speech API

話された音声を認識してテキストに変換する機能を提供します。80以上の言語と方言を認識してグローバルな活用ができる他、結果をリアルタイムで返すことが可能です。また雑音の多い音声データに対しても正確な認識ができます。

・Google Natural Language API

自然言語処理(人間が日常的に使っている自然言語をコンピュータに処理させる一連の技術)を行うAPIです。テキストの構造や意味を認識し、文章が何を言おうとしているのかの判断に役立てることができます。また、テキストの内容から肯定・否定といった感情を検知することもできます。用途としては膨大な記事をトピックごとに整理したり不適切なコンテンツをフィルタリングしたりすることが考えられます。

・Google Cloud Translate API

ある言語で記述された文字列を他の言語の文字列へと翻訳する機能です。webサイトやアプリケーションでこれを利用すれば言語の壁を超えたサービスを手軽に提供することができます。翻訳元の言語が不明でも自動検出することもできるので特定の言語に限らない翻訳サービスを自社のサイトやサービスに組み込むことができます。

 

次に後者のTensorFlowについて説明します。

・TensorFlow

これは提供されたAPIが医療や産業などの特定の分野にフィットしない場合に利用します。TensorFlowの大きな魅力はpythonという言語で簡単なコードを書くだけでディープラーニングを利用できることにあります。様々な複雑な物事の調整は自動的に行ってくれます。例えばAPIが「既製品の服」だとすればTensorFlowは「イージーオーダーの服」を作るためのミシンのような役割をしてくれます。服を一から作る技術がなくても、それを作るための手助けをしてくれます。

 

 

ディープラーニングについてもっと知ろう!

これからの時代、人工知能の知識が問われるのはエンジニアだけではありません。

ビジネスサイドの人も知識を持っていることで、どこで、どのように人工知能を活用すればビジネスチャンスを広げられるのかがわかり、大きな利益を生むことができる場面が出てくることでしょう。

あなたも目の前のビジネスチャンスを拾い上げるための力を身につけませんか? 

ディープラーニングやプログラミングについてさらに技術的な面からも学びたい人は弊社Aidemyのプログラミング講座もぜひご検討ください。

 

無料カウンセリングは下のリンクから

aidemy.net

 

参考文献

グーグルに学ぶディープラーニング 日経ビックデータ[編]

https://www.amazon.co.jp/グーグルに学ぶディープラーニング-日経ビッグデータ/dp/4822236862

人工知能は人間を超えるのか、ディープラーニングの先にあるもの 松尾豊 著

https://www.amazon.co.jp/人工知能は人間を超えるか-ディープラーニングの先にあるもの-角川EPUB選書-松尾-豊/dp/4040800206

データを水増しする際の注意点!

 

機械学習がしたい...でもデータがない!

機械学習の勉強をするうえでほしいデータは、web上で機械学習用のデータとして見つけることができます。ただもし自分で実装するときは独自でデータセットの収集を行う必要になりそうです。自分でデータセット見つけるのは大変そう、めんどくさい。そこで今回は少ないデータセットでもよりいい精度を出すためにデータの水増しを実装してみました!データセットにはcifar-10のデータ数を少なくして使います。そこでデータの水増しの概要と気を付ける点についてまとめてみました!

データの水増しとは

データの水増しとはデータに様々な線形変換を加えることによってデータの数を増やすことを言います。線形変換とは、左右反転したり、コントラストを変えたり、ズームしたり、ずらしてみたりといったようなことです。今回は左右反転、ずらし、ノイズ付加の3つを実装してみました。ほかにも様々な変換がありますので詳しくは次のサイトを参考にしてみてください。

qiita.com

このように少ないデータを水増しする技術をデータオーギュメンテーションというそうです。





cifar-10とは?

cifar-10というデータセットを知っていますか?cifar-10とはairplane, automobile, bird, cat, deer, dog, frog, horse, ship, truckの10クラスの写真が格納されたデータベースです。合計60000枚あり、サイズは32×32ピクセルでRBGの3チャネルがあります。80 million tiny images というもののサブセットで物体が何かを認識する一般物体認識という分野で有名なデータセットです!ちなみにデータはnumpy.ndarrayの形で入っているのですぐにpythonで扱うことができます。

 cifar-10の詳細に関してはこちら

aidiary.hatenablog.com

では、このcifar-10をCNNで学習しましょう!

 




水増しcifar-10をCNNで推定してみる。

まずkarasから出しているdatasetsを利用してcifar-10を入れる

random_state = 0
(cifar_X_1, cifar_y_1), (cifar_X_2, cifar_y_2) = cifar10.load_data()
cifar_X = np.concatenate((cifar_X_1,cifar_X_2),axis = 0)
cifar_y = np.concatenate((cifar_y_1,cifar_y_2),axis = 0)
#cifar_Xを0~1に、cifar_yをone_hot形式にする
cifar_X = cifar_X / 255.
train_X, test_X, train_y, test_y = train_test_split(cifar_X, cifar_y, test_size=10000, 
random_state=random_state)

 

これでそれぞれにデータが入りました。
表示してみると

fig = plt.figure(figsize=(9, 15))
fig.subplots_adjust(left=0, right=1, bottom=0, top=0.5, hspace=0.05,
                    wspace=0.05)

for i in range(36):
    ax = fig.add_subplot(6,6, i + 1, xticks=[], yticks=[])
    ax.imshow(train_X[i])

f:id:bkenken1234:20170920002615p:plain
cifer-10.plot

このように写真を見ることができます。
写真粗い 笑



次にCNNの実装をしたいところなのですが、その前にデータの数をそれぞれ100個づつに減らしていきます。

y_num = np.argmax(train_y,1)
train_X_10 = np.zeros([100,32,32,3])
for i in range(10):
    train_X_10 = np.concatenate((train_X_10,train_X[np.where(y_num == i)][:100]),axis = 0)
train_y_100 = np.repeat(range(10),100)
train_X_100 = train_X_10[100:]

 

これで各クラス10個の計1000のデータとそのラベルが得られました!

次にこのデータに3つの加工を加えて4倍に数を増やします。

#processing
train_X_pro = train_X_100
train_y_pro = train_y_100
#flapping 左右反転
train_X_flip = train_X_100[:, :, ::-1, : ]
#cropped 移動
padded = np.pad(train_X_100, ((0, 0), (4, 4), (4, 4), (0, 0)), mode='constant')
crops = rng.randint(8, size=(len(train_X_100), 2))
cropped_train_X = [padded[i, c[0]:(c[0]+32), c[1]:(c[1]+32), :] for i, c in enumerate(crops)]
train_X_cropped = np.array(cropped_train_X)

#gain noise ノイズ付加
train_X_noise = train_X_100 * rng.binomial(size=(train_X_100.shape), n=1, p=0.8)


train_X_processing = [train_X_flip,train_X_cropped,train_X_noise]
for train_processing in train_X_processing:
    train_X_pro = np.concatenate((train_X_pro,train_processing),axis = 0)
    train_y_pro = np.concatenate((train_y_pro,train_y_100),axis = 0)



ここで一回整理
train_X_100 , train_y_100 が加工する前のデータで、
train_X_pro , train_y_pro が加工後のデータとなっています。




この後CNNを実装します。CNNの実装に関しては次のページを参照してみてください。
CNNの構造に関しては今回のメインではないので割愛させていただきます。詳しい実装は以下のサイトなど参考にしてみてください。

qiita.com



それではCNNを使ってそれぞれepoch数を5にしてF値を計算してみました。



水増しcifar-10結果

EPOCHは試行回数
cost は誤差関数の値、今回はクロスエントロピーというものを使用。
test F1はCNNより得られたF値です。testには画像1万枚を使用しました。

水増し無し
EPOCH:: 1, cost: 2.457, test F1: 0.102
EPOCH:: 2, cost: 2.431, test F1: 0.116
EPOCH:: 3, cost: 2.418, test F1: 0.124
EPOCH:: 4, cost: 2.414, test F1: 0.131 
EPOCH:: 5, cost: 2.417, test F1: 0.138

水増しあり
EPOCH:: 1, cost: 2.395, test F1: 0.112
EPOCH:: 2, cost: 2.379, test F1: 0.118
EPOCH:: 3, cost: 2.370, test F1: 0.121
EPOCH:: 4, cost: 2.362, test F1: 0.123
EPOCH:: 5, cost: 2.354, test F1: 0.127

...?むしろさがってる!?
最初は水増しデータのほうが成績が良かったんですがすぐに逆転されてしまいました。
どうしてF値が下がってしまったのでしょうか




考えられる理由

  • 少ない画像を加工して何回も使うのでオーバーフィッティングした

オーバーフィッティングとは
datahotel.io


今回の場合、同じようなデータを入れすぎたため汎用性がなくなってしまったのかなと思います。


  • 与えたデータが難しすぎた

たとえばノイズなど実際のテストデータには存在しない水増しデータも追加していたのでその影響で目的としてないノイズが入った画像でも分類できるモデル!を作ろうとしてしまい目的であった分類が難しくなってしまった。与える水増しデータは実際に想定されるものでないとおかしな汎用性を生んでしまいF値が低下してしまう。


例として
たとえばデータセットをMNISTにして、水増しデータとして左右逆転のものを入れると


#水増し無し
EPOCH:: 1, cost: 2.265, test F1: 0.183
EPOCH:: 2, cost: 2.240, test F1: 0.247
EPOCH:: 3, cost: 2.213, test F1: 0.316
EPOCH:: 4, cost: 2.182, test F1: 0.401
EPOCH:: 5, cost: 2.146, test F1: 0.484
EPOCH:: 6, cost: 2.102, test F1: 0.539
EPOCH:: 7, cost: 2.049, test F1: 0.585
EPOCH:: 8, cost: 1.983, test F1: 0.612
EPOCH:: 9, cost: 1.902, test F1: 0.627
EPOCH:: 10, cost: 1.806, test F1: 0.636

#水増しあり
EPOCH:: 1, cost: 2.270, test F1: 0.168
EPOCH:: 2, cost: 2.251, test F1: 0.188
EPOCH:: 3, cost: 2.232, test F1: 0.195
EPOCH:: 4, cost: 2.211, test F1: 0.193
EPOCH:: 5, cost: 2.187, test F1: 0.187
EPOCH:: 6, cost: 2.159, test F1: 0.188
EPOCH:: 7, cost: 2.127, test F1: 0.192
EPOCH:: 8, cost: 2.087, test F1: 0.209
EPOCH:: 9, cost: 2.042, test F1: 0.241
EPOCH:: 10, cost: 1.988, test F1: 0.285

結果はかなり悪くなりました。ほんとうに悪い
データは加工無しが100枚でCNNの構造はcifar-10と同じです。

MNISTに関してはtestデータもだいたい中心にそろっているわけだから移動させたデータもよくなかったのかもしれません。

この話はデータセットの話だけでなく実際にもあって
ほかにも顔検出の場合にたは、顔の部分の隠れが多すぎる画像を加えると、顔検出の性能を悪くすることが起こったりするらしいです。


改善案

  • データの数を多くしてオーバーフィッティングを防ぐ。
  • クロスバリデーションなどオーバーフィッティングの対策アルゴリズムを実装する。
  • 線形変換によりありえそうなものを考えて実装する。


今回はデータの少なすぎることが原因でオーバーフィッティングしてそうなので
実際にデータ数を各クラス1000にしてオーバーフィッティングを防いでみました。

#水増し無し
EPOCH:: 1, cost: 2.004, test F1: 0.267
EPOCH:: 2, cost: 1.829, test F1: 0.339
EPOCH:: 3, cost: 1.712, test F1: 0.385
EPOCH:: 4, cost: 1.634, test F1: 0.420
EPOCH:: 5, cost: 1.585, test F1: 0.439

#水増しあり
EPOCH:: 1, cost: 1.685, test F1: 0.384
EPOCH:: 2, cost: 1.454, test F1: 0.489
EPOCH:: 3, cost: 1.341, test F1: 0.532
EPOCH:: 4, cost: 1.266, test F1: 0.563
EPOCH:: 5, cost: 1.211, test F1: 0.583

計算するのになかなか時間がかかりました。
しかしちゃんと水増しの効果が得られたことがみえます!
やっぱりデータ数が各クラス100ではデータが少なすぎてオーバーフィッティングしてたっぽいですね!

総括

今回はデータの水増しについて勉強しました。はじめ思いついたときにデータ少なくてもできるじゃん!!と思ったんですが簡単にはうまくいかないものですね。水増しについて調べながらオーバーフィッティングや画像変換の方法など学ぶことができました。参考になったサイトを下に貼っておきます。


blog.takuya-andou.com

qiita.com

 

 

GPyOptをインストールする際の落とし穴

追記(2017/10/3):
このエラーが発生していたのは前提パッケージとなるGPyなんですが、修正版が早速公開されたようです。
以下の記述は筆者がタイミング悪く修正される前のパッケージをインストールしたときのものです。
なので似たような事例があった場合の解決方法として残しておきます。

GPyOptをインストールする際は以下のコマンドを入力してください。

pip install GPy
pip install GPyOpt

余談ですがすでにparamzはバージョン0.8.5になっていて更新が早い……。


みなさん、機械学習楽しんでますか?
そろそろ機械学習にも慣れてきてハイパーパラメーターチューニングに手を出そうと考えてる人も多いのでは?

さてそんな人なら耳には入っているであろうパラメーターをベイズ推定によりサーチするパッケージの一つGPyOptについて。

まずはGPyをインストール

GPyOptはとても便利なパッケージなためインストールも簡単……ではありません。
まずはGPyというパッケージをインストールしなければなりません。
これは次のコマンドをコマンドプロンプトに打ち込みましょう。

pip install GPy

管理者権限が必要な場合はsudoもつけてくださいね?

GPyOptをインストール……できない!

問題はここから。
次に以下のコマンドを実行します。

pip install GPyOpt

すると次のエラーが。

Collecting GPyOpt
  Using cached GPyOpt-1.0.3.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/62/7q4x8lld00d5mb54jglx5nnw0000gn/T/pip-build-5e3ix6yi/GPyOpt/setup.py", line 6, in <module>
        from GPyOpt.__version__ import __version__
      File "/private/var/folders/62/7q4x8lld00d5mb54jglx5nnw0000gn/T/pip-build-5e3ix6yi/GPyOpt/GPyOpt/__init__.py", line 7, in <module>
        from GPyOpt.core.task.space import Design_space
      File "/private/var/folders/62/7q4x8lld00d5mb54jglx5nnw0000gn/T/pip-build-5e3ix6yi/GPyOpt/GPyOpt/core/__init__.py", line 4, in <module>
        from .bo import BO
      File "/private/var/folders/62/7q4x8lld00d5mb54jglx5nnw0000gn/T/pip-build-5e3ix6yi/GPyOpt/GPyOpt/core/bo.py", line 8, in <module>
        from ..core.task.cost import CostModel
      File "/private/var/folders/62/7q4x8lld00d5mb54jglx5nnw0000gn/T/pip-build-5e3ix6yi/GPyOpt/GPyOpt/core/task/__init__.py", line 4, in <module>
        from .objective import SingleObjective
      File "/private/var/folders/62/7q4x8lld00d5mb54jglx5nnw0000gn/T/pip-build-5e3ix6yi/GPyOpt/GPyOpt/core/task/objective.py", line 8, in <module>
        import GPy
      File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/GPy/__init__.py", line 6, in <module>
        from . import core
      File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/GPy/core/__init__.py", line 4, in <module>
        from GPy.core.model import Model
      File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/GPy/core/model.py", line 3, in <module>
        from .parameterization.priorizable import Priorizable
      File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/GPy/core/parameterization/__init__.py", line 9, in <module>
        from paramz import ties_and_remappings, ObsAr
    ImportError: cannot import name 'ties_and_remappings'
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/62/7q4x8lld00d5mb54jglx5nnw0000gn/T/pip-build-5e3ix6yi/GPyOpt/

paramzの中にties_and_remappingsがないよというメッセージが。
あわててparamzの公式ドキュメントをみてみました。

ドキュメントの中にはこの関数はあるようです。

モジュールのバージョンが違った

ふとここでモジュールのバージョン違いでは?と考えバージョンを確認しました。

pip show paramz
Name: paramz
Version: 0.8.3
Summary: The Parameterization Framework
Home-page: https://github.com/sods/paramz
Author: Max Zwiessele
Author-email: ibinbei@gmail.com
License: BSD 3-clause
Location: /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages
Requires: scipy, six, decorator, numpy

公式ドキュメントは0.7.4のものでした。
どうやらバージョンアップの際になくなったらしいです。

なのでいったんバージョンを下げて再インストール。

pip install paramz==0.7.4

今度はGPyOptインストール成功

もう一度GPyOptをインストール。

pip install GPyOpt

今度はうまくいきました!
ちなみにアップグレードオプションをつけるとparamzが0.8.3に戻りますが0.7.4のままにするほうが無難だと思います。

ところで、なぜGPyOptはrequirement.txtをつけていないのだろうか?
それがあればさきにpipでGPyを入れなければいけないという面倒なことをしなくて済むと思うのですが。

ちなみにparamz 0.8.3の公開日は2017/09/30だそう。
どおりで色々検索しても出ないわけだ……。