線形代数のまとめ その3(行列式について)
その2の続き
その2で写像って何だ?というとこから基底や逆行列についてメモした
最後で独立の意味って何だろう?とか任意のある行列に逆行列が存在するかの判定や任意でとった基底の組が基底としての条件を満たしているかの判断はどうすればいいんだろうという疑問が浮かんで終わった。続きをメモする。
ところで線形代数の授業を受けた人なら行列の他にも行列式というのを見たことがあるというか解かされたことがあるんじゃないだろうか
$$ \begin{bmatrix} 1 & 9 & 3 \\ 7 & 5 & 3 \\ 3 & 1 & 5 \end{bmatrix} $$
こんな形のやつ。自分は思考を停止して余因子展開したり2次の行列式なんかに関してはもうして終わりッッって感じに解いていた。
前回で行列は写像、基底の移り先を教えてくれるものという見方をした。2次元空間において基底の組 (単位ベクトル )をとったとき
正方行列AとB
$$ A = \begin{pmatrix} 1.5 & 0 \\ 0 & 0.5 \end{pmatrix} B = \begin{pmatrix} 1 & -0.7 \\ -0.3 & 0.6 \end{pmatrix} $$
この2つの行列による写像はそれぞれ以下のようになる。
元の基底ベクトルたちがそれぞれビヨーンと伸びたり縮んだり、方向自体変わったりした。
ここで線形空間ではベクトルの長さという概念はないが、この写像される前と後の基底ベクトルから成る四角形の面積を求めてみる。
ちなみに長さや角度の概念がある線形空間は内積空間と呼ぶそうだ
上の画像のように、と、を求めたい。
を単位ベクトルからできる四角形の面積、横=1、縦=1としてとする。
はが1.5倍、が0.5倍拡大されたものだから、
となる。0.75倍ということは写像前より小さくなった。
は本には0.39倍と書いてあったがどうやって求めたんだ・・・・と悩んだが調べたら外積で求めることができるそうだ。電磁気学の授業でよく教授が右ねじ〜^^と言いながら親指をグルグルさせていたのをよく覚えている。
とが成す外積は
は 0.39倍とこっちも小さくなった。
ここで、
自分は思考を停止して余因子展開したり2次の行列式なんかに関してはもうして終わりッッって感じに解いていた。
このを見て欲しい。行列AとBが都合よく2次行列だしせっかくなので行列式とやらを求めてみる。2次行列の要素を
$$ \begin{pmatrix} a & b \\ c & d \end{pmatrix}$$
とする。このときAとBの行列式は
$$ detA = \begin{bmatrix} 1.5 & 0 \\ 0 & 0.5 \end{bmatrix} = 1.5 \ast 0.5 - 0 \ast 0 = 0.75 $$
$$ detB = \begin{bmatrix} 1 & -0.7 \\ -0.3 & 0.6 \end{bmatrix} = 1 \ast 0.6 - (-0.7) \ast (-0.3) = 0.39 $$
!?
各行列式の値 detA、detBと面積の値とが一致している・・・しかもBの行列式は心なしか外積と計算式が似ている・・・・・・・・
というわけでこの本では上で求めた面積の拡大率のことを行列式とよぶ。(3次なら体積拡大率とも言える)
授業や試験に出た2次の行列式の問題はつまり四角形の面積の拡大率を計算させられていたということか・・・
イケてる小学生は行列式で平行四辺形の面積を求めるのかもしれん
ちなみに行列式は正方行列に対してだけ定義されるものなため正方行列じゃないものに関しては行列式は考えないそうだ
行列式は行列の要素によっては計算結果から負の値が出ることも考えられる。負の面積拡大率って何だろう?という問いに関しては面積を求めたい図形が裏返しになる、イメージだそうだ。外積では「向き」という考えがあったしそんな感じかな。
写像する前の画像(平面)は写像した後だと反転している(ステッキとか)。「面積自体は拡大したけど写像していた向きと同じ向きに拡大されたわけではない」ということを示したい時とかだと負の値を見れば一発でわかるね便利だね
行列式には以下のような性質がある
*
*
一つ目は単位行列が「写像しても何も変化しない」行列なので、つまり元の面積を1倍すると考えると納得できる。
二つ目は行列Bで拡大して次に行列Aで拡大した拡大率は倍して倍したと同じということ。
行列式が面積拡大率ということなら行列式が0になるというのは何を意味するだろう?面積が0倍・・・?
上にある単位ベクトルが基底のときの行列式()は1だった。これを何かしらの行列で写像して、その結果の拡大率が0ということは元々あった面積がぺちゃんこになってしまったということ
こんな感じだ。元々の面積というよりは基底ベクトルからできていた平面がほぼ点にしか見えないような平面へと写像されるイメージ
またはこんな風に写像されてしまったかもしれない
面積が0ということは平面が作れないということ。写像した後の基底ベクトルが同じ向きを向いてしまったら、平面は作ることができない。
そんなこんなで(2次元空間において)行列式が二つの基底ベクトルが成す面積拡大率を表すもので、行列式の計算結果が0になってしまうことはその時の基底ベクトルが同じ向きを向いているか、1本しかない、などの状況が考えられる。
こんな状況になる基底は基底としての条件を満たさないということだった。
上の画像のようになってしまう基底ベクトルでは基底は作れなかった。つまり
行列式を求めることで移された基底の組が基底の条件を満たすかどうかの判定ができる!!!!!
行列式スゲー
次にの性質を使ってがどうなるか考える。
逆行列の定義からとなるので、左辺はとなるはず。
そうなると右辺も1にならないといけない。がの値の逆数であれば両辺は成立する。
という関係式が得られる。
このとき面積拡大率が0、が0の時この関係式が成り立たないことがわかる。
成り立たないってことは が存在しないって言ってもおかしくない。
ん?
ちょっと待てよ・・・ということは
行列式を求めることでその行列に逆行列が存在するかどうかも判定できる!!!
行列式スゲー!!!!!!!!!!
また逆行列が存在するかを行列式から求めることができる→行列式は正方行列にしか定義されない→逆行列は正方行列でなければ存在しないという前回の単射・全射あたりがより納得できる
まとめると、n次正方行列Aがあってのとき * そのときのn本の基底ベクトルから成る基底の組は、いずれかのベクトルが他のベクトルで表現できるような独立でない、またはn本より少ない状態にある。 * 逆行列が存在しない
ということがわかった。
逆にじゃないなら、そのn次正方行列Aは正則行列ということが言えるのがわかった。
n次のときで証明してない?買おう
長くなったので余因子展開とか別の行列式の性質はまた今度
線形代数のまとめ その2(写像について)
その1の続き
その1では線形代数の授業で雰囲気で覚えていた線形空間や基底についてメモった
小学校や中学校で方眼紙を使ったことがあると思うが、一つの四角のマスが長さ 1cm を表しているとして
この方眼紙においては、一つの四角のマスの横()と縦()があるベクトル に行くための基準となってくれる。
上の画像にあるベクトル への行き先は基準を使って
と書き表せる
基準となる1組のベクトルのことが基底だった。ここでは方眼紙の縦と横を表せればいいので基底の組は(,)であり、組の要素であるとかが基底ベクトルだった。
のようなもベクトルの足し合わせでできたベクトルなので、もちろん足し算と定数倍はできる。
今回は授業でよく聞いたけどイメージが湧かない写像をメモする
ある行列 A があるとする
$$ A = \begin{pmatrix}
2 & 1 \\
1 & 3
\end{pmatrix} $$
写像というのはのようにベクトルを行列Aで別のベクトルに移すこと
(m,n)行列Aのとき、その行列は n次元空間のものを m次元空間に移す写像を表す
試しにベクトルを上の(2,2)行列Aで写像してみることにする
の部分がに変わるので計算式は以下のようになる
$$ \boldsymbol{y} = \begin{pmatrix} 2 & 1 \\ 1 & 3 \end{pmatrix} \begin{pmatrix} 3 \\ 2 \end{pmatrix} $$
はこのとき になる。
途中式を書いて基底がどうなったかを確認する。そもそもはを表したものだから
$$ \begin{pmatrix} 3 \\ 2 \end{pmatrix} = 3\begin{pmatrix} 1 \\ 0 \end{pmatrix} + 2\begin{pmatrix} 0 \\ 1 \end{pmatrix}$$
を変形すると
$$ \boldsymbol{y} = \begin{pmatrix} 2 & 1 \\ 1 & 3 \end{pmatrix} \begin{pmatrix} 3\begin{pmatrix} 1 \\ 0 \end{pmatrix} + 2\begin{pmatrix} 0 \\ 1 \end{pmatrix} \end{pmatrix} $$
分配法則(定数は前に持ってくる)から
$$ \boldsymbol{y} = 3 \begin{pmatrix} 2 & 1 \\ 1 & 3 \end{pmatrix} \begin{pmatrix} 1 \\ 0 \end{pmatrix} + 2 \begin{pmatrix} 2 & 1 \\ 1 & 3 \end{pmatrix} \begin{pmatrix} 0 \\ 1 \end{pmatrix} $$
$$ \boldsymbol{y} = 3 \begin{pmatrix} 2 \\ 1 \end{pmatrix} + 2 \begin{pmatrix} 1 \\ 3 \end{pmatrix} $$ $$ = \begin{pmatrix} 6 \\ 3 \end{pmatrix} + \begin{pmatrix} 2 \\ 6 \end{pmatrix} = \begin{pmatrix} 8 \\ 9 \end{pmatrix} $$
分配法則で計算したけど最終的な答えはと一致したから問題なし。大事なのは基底を表している部分
$$ \boldsymbol{y} = 3 \begin{pmatrix} 2 \\ 1 \end{pmatrix} + 2 \begin{pmatrix} 1 \\ 3 \end{pmatrix} $$
最初の基底と変わっている。同じじゃない
定数は無視して、がに、がになってる
元の基底をプロットすると
こんな感じ
をした後の基底をプロットすると
元の行き先はかなり遠くに移された。
上のことから行列Aは元の基底の移り先を表しているとも言える。(Aの第一列が、第二列目がに対応している)
人間、一度は故郷を離れることがあると思う。
たまに故郷に戻りたくなったりしないだろうか
つまりとして移ったからに戻りたいということ。これも同じように何かしらの行列Pで写像して基底の移り先を教えてあげればいいはず
よく聞いた逆行列というのがこれで、ある正方行列((n,n)行列ということ)Aに対してその逆写像に対応する行列をAの逆行列という
その1のときに基底の表し方が1通りでいけない理由はこの逆行列のことを考えると、2通りあったらどっちへ戻ればいいかわかんなくなって困るというのがわかる
じゃあ正方行列でないといけない理由は?
(2,3)行列Aでの写像を考える。これは3次元のものを2次元空間に写像する。
3次元という立体のような空間で表されるものを2次元という平面の空間に移したら立体らしさの情報が欠落してしまう。
上の画像でいうなら黄緑の面とオレンジの面に存在するベクトルをそれぞれとまた()とする。画像の写像先を見ると同じ x = p という同じ移り先にいることになっている。これは「2通りあったらどっちへ戻ればいいかわかんなくなって困る」という状態になってしまった。
基底という言葉を使えば立体らしさを表してくれる基底の組の中にある基底ベクトルの情報が写像によって失われてしまう感じかな
(3,2)行列のように2次元のものを3次元の空間に写像したらどうなるか
前者の場合と違って次元が増えるので情報が欠落することはない
でも移り先の3次元空間に存在する全てのベクトルをカバーできていないのがわかる。赤の平面以外の場所に存在するベクトルの戻り先が存在しないのでこれはこれで困ってしまった。
図で(2,3)行列のような場合はこんな感じになる
移り先 f(x) の 3 から x へ戻るとき、この 3 は 2 か 4 どちらから移ってきたかわからず困る。こういう状況を全射
(3,2)行列の場合は
移り先から戻り先は1対1対応だけど、 f(x) の中にある i や 10 に対応するものが戻り先にはないケース。こういう状況を単射
逆行列で嬉しい場合は1対1対応で、移り先のベクトルに対して唯一の戻り先があること
単射であり、全射。二つ合わせて全単射という
雑だけど(m,n)行列において m < n でも m > n の場合でも困ることがわかった。そのため(n,n)という正方行列のがいいことになる。
正方行列でもその1でメモした基底の条件を満たすような正方行列じゃないといけないことに注意する
正方行列で写像した基底の移り先が実はどの基底ベクトルも同じ方向を向いていたとかだったらどっかしら情報が欠落して元に戻れなくて困る。すなわち逆行列が存在しない。正方行列で逆行列が存在する行列を正則行列と呼ぶ。
まとめるとして
* (m,n)行列による写像は移すベクトルの基底の移り先を示す、または n次元空間のものを m次元空間へと写像する。
* 写像した先から元に戻るためには写像した行列が正則行列であれば元に戻れる。
逆行列が存在するかどうかにも基底は関わっているし、基底がめちゃめちゃ重要なのがわかる。
でも逆行列が存在するとか、任意でとった基底の組が基底としての条件を満たしているとかはどうやって判断するのだろうか。
謎は深まるばかり
線形代数のまとめ その1(線形空間、基底とか)
プログラミングのための線形代数という本で線形代数の復習をしている。4章あたりまで読んだので振り返りとしてメモを残すことにする。
よく変数で配列だったりタプルだったりを使うことがある。これは複数の数値や文字列の集まりを一つのものとして見る。物理や線形代数だったりの勉強をしているとそれらはベクトルと呼ばれてる。
こんなやつ。大学だと矢印じゃなくて太字で書くものしか見ない...
ベクトルでは同じ次元(上で言うとnの値)のもの同士なら足し算(引き算)と定数倍ができる。
例えばこんなベクトルがあるとして
3人の現在の身長 = (1.5m,1.8m,1.7m)
3人の2年前の身長 = (1.4m,1.6m,1.5m)
3人の2年前から伸びた分の身長
を求めたいとする。これは現在の身長から2年前の身長を引いて残ったものが伸びた分になるから
3人の2年前から伸びた分の身長 = 3人の現在の身長 - 3人の2年前の身長 = (1.5m - 1.4m , 1.8m - 1.6m , 1.7m - 1.5m) = (0.1m , 0.2m ,0.2m)
3人の2年前から伸びた分の身長= (0.1m , 0.2m ,0.2m)
となる。
定数倍は
3人の現在の身長を3倍したもの = 3*(3人の現在の身長)
と表せることができて
3人の現在の身長を3倍したもの = 3*(3人の現在の身長) = 3*(1.5m,1.8m,1.7m) = (3*1.5m,3*1.8m,3*1.7m) = (4.5m,5.4m,5.1m)
ベクトルを構成している各要素を c倍 すればいい
掛け算や割り算もできそうじゃんって思うけど、できない
3人の現在の身長(cm表記) = (150cm,180cm,170cm)
3人の2年前の身長(cm表記) = (140cm,160cm,150cm)
先ほどのベクトルをcm
表記で表したものを用意する。
この2つから3人の2年前から伸びた分の身長(cm表記)
と3人の現在の身長を3倍したもの(cm表記)
を求めると
3人の2年前から伸びた分の身長(cm表記) = 3人の現在の身長(cm表記) - 3人の2年前の身長(cm表記) = (150m - 140cm , 180cm - 160cm , 170m - 150cm) = (10cm , 20cm , 20cm)
3人の現在の身長を3倍したもの(cm表記) = 3*(3人の現在の身長(cm表記)) = 3*(150cm,180cm,170cm) = (3*150cm,3*180cm,3*170cm) = (450cm,540cm,510cm)
表記は違うけど、求めたいものの意味合い(数値)は同じ
(3人の現在の身長)x(3人の2年前の身長)
と(3人の現在の身長(cm表記))x(3人の2年前の身長(cm表記))
は同じになるか考える
(3人の現在の身長)x(3人の2年前の身長) =(1.5*1.4, 1.8*1.6, 1.7*1.5) =(2.1m, 2.88m, 2.55m)
(3人の現在の身長(cm表記))x(3人の2年前の身長(cm表記)) =(150*140, 180*160, 170*150) =(21000, 28800, 25500) //m表記に直すと =(21m, 28.8m, 25.5m)
足し算や定数倍は同じものだったのに掛け算は全く別物になってしまった。
こんな風に掛け算と割り算はcmをmに直すといった座標系と呼ばれるものを変更するとxy = z
がx'y' ≠ z'
という感じになってしまうからできない。というよりは成立するかわからないからできない。
元に戻って「足し算(引き算)」と「定数倍」が定義された世界のことを線形空間またはベクトル空間と呼ぶ。
ベクトルというのは以下のように矢印のようなもので表すことができる。(x軸とy軸で構成する2次元空間とする)
これは という場所にはx座標に3歩進んで、y座標に2歩進むことで到着できる
を表す
ちょっと待ってほしい。この方眼紙のような目盛りって誰が用意したのだ
もしこの目盛りがなくなったとして、こんな状況を考える
誰かに の地点までお使いして欲しいとき、行き方はどうやって教えればいいだろう
目盛りがないと3コマ目のようにどの方向にどんな歩幅で3歩、2歩進めばいいか悩んでしまう。
目盛りがないなら、自分で作ればいいじゃん!ということで基準(目盛り)となるベクトル を定める。
基準を決めてしまえば、それを使って への行き先を「の方向に3歩、の方向に2歩進む」と伝えることができる。
ベクトルは足し算、定数倍できることはわかったからと書ける。
つまりはとという基準の組み合わせで表現できるということ。
「基準となる1組のベクトル」を基底、「各基準で何歩進むか」を座標と呼ぶ。
上の例だと「基底に関して、ベクトルの座標は」
ここでは基底はの組であり、各要素、のことを基底ベクトルという。
基底という目盛りを自分で作れることがわかったけど、基底を作るにも条件がある。
(今考えている線形空間内の)どのベクトルでも という形で表せること(x1,...,xnは任意の数)
その表し方は一通りであること
一つ目は特定のベクトルだけ表せる基底なんて基底と言えない。二つ目は表し方が一通りでないと、異なる座標とが与えられたときそれが違うもの同士か同じものが2通りの書き方をされているか困ってしまうことがあるから
2次元空間において以下のようなものが基底として取れる
2本の基底ベクトルが「独立した方向」を向いていること
この独立という単語がいまいち難しいかもしれないけど、後々大事になってくる
以下のようなものは基底として取れない悪い例
左は1本の基底ベクトルでは無理ということ
2次元空間なのに1本の基底ベクトルでは(x軸、y軸としたら)片方の軸しか表すことが出来ないから。これは基底の条件である
どのベクトルでも という形で表せることという条件を破ってしまう真ん中は2次元空間において3本(以上)の基底ベクトルは余分ということ
という基底ベクトルははで表すことができるから必要ない。(基底の組として要素にする必要がない)右は2本の基底ベクトルが同じ方向ではダメということ
を c倍 すればになるからは余分
結局1本の基底ベクトルだけということになる。これは基底の条件の一つ目を破ってしまうのでダメ
数学的な表現では
与えられたベクトルに対して、任意の数を用意してできるベクトル
をの線型結合をよぶ
複数のベクトルを n倍 だったり m倍 して足したり引いたりしたものをその複数のベクトルの線型結合というんだな
これらの条件から基底は「の線型結合で任意のベクトルが表現できて、その表し方が一通り(唯一)であるとき、を基底と呼ぶ」
おまけで次元数は基底ベクトルの本数から定義されるので
次元 = 基底ベクトルの本数 = 座標の成分数
と定義される。
長くなったのでここまで
チコノフ(Tikhonov)正則化について
お久しぶりです。
最近プログラミングのための線形代数という本で線形代数のやり直しをしている。基底のイメージが掴みやすくて読んでいて楽しい
第二章の最後あたりにチコノフ正則化というものについて書いてあったので試してみることにした
コードはいつも通りここ(リンクを修正)
線形代数をやったことある人は以下の数式をよく見かけるはず
(y,xはベクトル、Aは行列)
何をしているかというと(m,n)行列Aでn次元のベクトルxをm次元のベクトルyへと写像している
写像という言葉がイメージを掴みにくいけれど、m=3,n=2(m>n) に設定するとして
2次元空間で表されるベクトルxという平面を3次元空間に移す(写像する)とき、その空間でベクトルxはどのように表現されるかということ、であってるはず
図で表すとこうなる。y=Axから元の2次元平面にある点xは写像した結果3次元空間ではyというベクトルで表される。図から写像した赤い平面以外の場所にある点(zとする)へと移れるxは存在しないことがわかる。こういう場合は単射というそうだ。
もちろん逆の場合m=2,n=3(m<n)も考えられる。3次元ベクトルで表されるxが2次元空間へ写像したときどう表現されるかということ。3次元情報を2次元情報で表すのは無理があるのでいくつか情報は削られてしまう。マリオがペーパーマリオになってしまう感じだ。
なので写像するときにはn次元からn次元への写像が基本的に嬉しい。n次元ベクトルxを回転したり引き延ばしたりする写像Aで写した時のn次元ベクトルyとか考えやすい。
n次元からn次元への写像において y = Ax とすると、yからxに戻すときは逆行列 inv_A を用いれば元に戻れる。
このことから写像する行列Aには逆行列が存在しないといけないこともわかる。(行列Aは正則行列でなくてはならないということ)
逆行列が存在しないということはn次元空間を作るためのn本の基底が存在しない(n-1本だったりとか)ということで上の3次元情報を2次元情報で表現するといった状況になっていることになる。
世の中はひねくれているので正則行列ではあるけどほぼ逆行列に近い行列Aというのも存在する。
立方体をほぼ平面にするくらいぺちゃんこにしてしまう行列Aとかそんな感じ。このとき逆行列は元の立方体に戻す行列だからぺちゃんこな立方体を物凄く拡大して戻す逆行列 inv_A となる。
ここで元の立方体にノイズ(立方体の箱に黒い点のような落書きをしておくイメージ)を乗せて上のようなほぼ逆行列に近い行列Aでぺちゃんこにして、 inv_A で元に戻した時、ノイズも物凄く拡大されてしまう(黒い点のような落書きがかなり引き伸ばされたりしているイメージ)問題が発生すると本に書いてあった。
試してみることに
元の画像は以下のカニさん
画像を多少ぼかす行列Aを用意する
単位行列の対角成分周りをぼやぼやした感じ(0.13とか0.6とか値を小さく)調整した。
y = Ax としたときのyの結果はこうなった
全体的に暗くなった感じもあるけどちょっとぼやけてるのがわかる。
このyにノイズを乗せる(y+noise)
Aの逆行列をかけて元に戻す( x = inv_A(y+noise) )
かなりザリザリした結果になった。ノイズが引き伸ばされたのはわかる。
ちなみに逆行列自体はこんな感じだった
なんかロマンチックだ
このノイズが引き伸ばされるのをなんとかしようというのがチコノフ正則化で、ざっくりいうと Ax と y+noise の距離(|| Ax - y ||)とか正則項を適用したりして、いい感じになるxを決めるぽかった。 数式で表すとこうなる
右辺のyを除いた行列の結果は以下のようになった(α=0.03)
逆行列 inv_A を表したものを太くした感じに見える
これをノイズありのyにかけるとこうなる
ちょっとノイズが減ったのがわかる。でも完璧にとはいかなかった。αを0.5とかに変えたりすると元の画像xではなくどちらかというとぼかし行列を加えたyに近くなった。
この原因はなんだろう。逆問題あたりをもっと調べるとわかってくるのだろうか。
Dropoutのハイパーパラメータの決定について
ここ1ヶ月ほど、会社のアルバイトで6クラスの画像分類モデルをTensorflowを使って作ることを任された
色々詰まった、考えさせられたことがあったのでメモ
画像分類だったらCNNで、ネットワークはVGG16だったりAlexNetで作りましたべろ〜んwって感じでいいと思うが、そのモデルをProtocolBufferファイルにしてラズパイなどに乗せて動かすということなのでネットワークの層が深すぎるとProtocolBufferファイルが400~700MBになって重すぎて敵わん...となってしまうので6層ほどで適当に作ることにした
全然6層じゃなかった
畳み込みのkernel_sizeは3~5で、Dropoutは0.5で調整した
いざ学習させたらなぜか学習が進まなかった。損失は減っているが精度が0.23~0.25間で向上しなかった。
別の2層の畳み込み層と2層の全結合層のモデルは学習が進んでいたことから、自分のモデルのパラメータの設定がおかしいのだろうかと悩んだ
畳み込み層、学習係数のハイパーパラメータは同じだったので、全結合層のパラメータやLocal response Normalization
層が原因なのかと考えたりした
試行錯誤した結果、上記のせいで学習が滞っていたのではなく、Dropoutのパラメータが原因だったということがわかった
上のネットワーク図からわかるように2つの隠れ層である全結合層にはDropoutを適用している。どちらもkeep_prob
は0.5に設定した
このkeep_prob
をどちらも0.8に変更した結果、学習が進んだ
Dropoutについては青いイルカさんの本とかでは仕組みや嬉しいことの説明はあったが最適なパラメータの設定値の説明はなかった。なのでいつも0.5で設定していた
調べたところ最初に来る全結合層に対してDropoutを適用するとき、そのパラメータを0.5にするのはあまり良くないそうだ
というわけで学習が進まない問題は回避できてなんとかなった
その後、全結合層のノード数とDropoutのkeep_probパラメータの間に何か関係があるのか気になったので調べることにした
使うデータはTensorflowのチュートリアルで有名なMNISTで全結合層のみのネットワークを構築した
mport tensorflow as tf import numpy as np from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets('MNIST_data', one_hot=True) t = tf.placeholder(tf.float32, shape=(None,10)) X = tf.placeholder(tf.float32, shape=(None,28*28),name="input") keep_prob_1 = tf.placeholder(tf.float32) # ドロップアウトする割合 keep_prob_2 = tf.placeholder(tf.float32) # ドロップアウトする割合 stddev = np.sqrt(2.0 / 28*28) h_W1 = tf.Variable(tf.truncated_normal([28*28,256], stddev=stddev)) h_b1 = tf.Variable(tf.constant(0.1, shape=[256])) h_fc1 = tf.nn.relu(tf.matmul(X,h_W1) + h_b1) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob_1) stddev = np.sqrt(2.0 / 256) h_W_fc2 = tf.Variable(tf.truncated_normal([256,512], stddev=stddev)) h_b_fc2 = tf.Variable(tf.constant(0.1, shape=[512])) h_fc2 = tf.nn.relu(tf.matmul(h_fc1_drop, h_W_fc2) + h_b_fc2) h_fc2_drop = tf.nn.dropout(h_fc2, keep_prob_2) stddev = np.sqrt(2.0 / 512) W_fc3 = tf.Variable(tf.truncated_normal([512,10], stddev=stddev)) b_fc3 = tf.Variable(tf.constant(0.1, shape=[10])) fc3 = tf.matmul(h_fc2_drop, W_fc3) + b_fc3 p = tf.nn.softmax(fc3,name="output") loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=fc3, labels=t)) optimizer = tf.train.AdamOptimizer(1e-4) train_step = optimizer.minimize(loss) correct_prediction = tf.equal(tf.argmax(fc3,1), tf.argmax(t,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) saver = tf.train.Saver() ### 学習の実行 sess = tf.Session() sess.run(tf.global_variables_initializer()) i = 0 for i in range(0,10000): batch = mnist.train.next_batch(30) sess.run(train_step, feed_dict={X:batch[0],t:batch[1],keep_prob_1:0.5,keep_prob_2:0.5}) if i % 1000 == 0: train_acc, train_loss = sess.run([accuracy,loss], feed_dict={X:batch[0],t:batch[1],keep_prob_1:1.0,keep_prob_2:1.0}) print("[Train] step: %d, loss: %f, acc: %f" % (i, train_loss, train_acc)) # テストデータによるモデルの評価 test_acc, test_loss = sess.run([accuracy,loss], feed_dict={X:mnist.test.images,t:mnist.test.labels,keep_prob_1:1.0,keep_prob_2:1.0}) print("[Test] loss: %f, acc: %f" % (test_loss, test_acc)) saver.save(sess, "./ckpt/mnist") sess.close()
h_fc1
とh_fc2
にdropoutを適用したりしなかったり、keep_prob_1
とkeep_prob_2
の値を変えて訓練結果を確認した
まずh_fc1
とh_fc2
のノード数が、48,96でdropoutを適用しなかった時の結果(多いので2000step毎だけ)
[Train] step: 0, loss: 15.007373, acc: 0.066667 [Test] loss: 12.547914, acc: 0.097100 [Train] step: 2000, loss: 0.960308, acc: 0.666667 [Test] loss: 0.894258, acc: 0.735600 [Train] step: 4000, loss: 0.381689, acc: 0.800000 [Test] loss: 0.603150, acc: 0.818700 [Train] step: 6000, loss: 0.460051, acc: 0.866667 [Test] loss: 0.484288, acc: 0.855700 [Train] step: 8000, loss: 0.595815, acc: 0.866667 [Test] loss: 0.419885, acc: 0.873900
精度はいまいち...次にノード数はそのままでdropoutを適用し、keep_prob_1
、keep_prob_2
が共に0.5に設定したとき
[Train] step: 0, loss: 18.346718, acc: 0.033333 [Test] loss: 12.756997, acc: 0.122000 [Train] step: 2000, loss: 1.504397, acc: 0.500000 [Test] loss: 1.692765, acc: 0.451800 [Train] step: 4000, loss: 1.380499, acc: 0.566667 [Test] loss: 1.422444, acc: 0.552400 [Train] step: 6000, loss: 1.460326, acc: 0.600000 [Test] loss: 1.400168, acc: 0.609900 [Train] step: 8000, loss: 1.548799, acc: 0.533333 [Test] loss: 1.289341, acc: 0.667200
適用なしの時より悪化しているのがわかる
色々試した結果、keep_prob_1
は1.0(dropoutを適用しない)keep_prob_2
を0.8に設定した結果がdropout適用しないときより良かった
mnist-dropout-48-96-10-08-ckpt [Train] step: 0, loss: 17.872959, acc: 0.066667 [Test] loss: 16.914766, acc: 0.103000 [Train] step: 2000, loss: 1.499189, acc: 0.666667 [Test] loss: 1.236634, acc: 0.663800 [Train] step: 4000, loss: 0.986408, acc: 0.666667 [Test] loss: 0.778916, acc: 0.762500 [Train] step: 6000, loss: 0.436568, acc: 0.866667 [Test] loss: 0.605089, acc: 0.813300 [Train] step: 8000, loss: 0.290827, acc: 0.933333 [Test] loss: 0.504056, acc: 0.846000
次に上記の結果はノード数が少なすぎるからkeep_prob_1
、keep_prob_2
が共に0.5の結果は悪かったのでは?と疑問に思い、各ノード数を256,512にした時でも同様に結果を観察することにした
まずはdropout適用しないときの結果
[Train] step: 0, loss: 9.332975, acc: 0.166667 [Test] loss: 10.855021, acc: 0.127700 [Train] step: 2000, loss: 0.380751, acc: 0.966667 [Test] loss: 0.349467, acc: 0.905500 [Train] step: 4000, loss: 0.222563, acc: 0.966667 [Test] loss: 0.241931, acc: 0.931800 [Train] step: 6000, loss: 0.108663, acc: 0.966667 [Test] loss: 0.199128, acc: 0.943000 [Train] step: 8000, loss: 0.077383, acc: 0.966667 [Test] loss: 0.172284, acc: 0.948200
元々の表現力が上がったせいか48,96のときより精度が良くなっている
次にkeep_prob_1
、keep_prob_2
が共に0.5のときの結果
[Train] step: 0, loss: 7.612897, acc: 0.200000 [Test] loss: 9.190090, acc: 0.129300 [Train] step: 2000, loss: 0.934076, acc: 0.733333 [Test] loss: 0.541602, acc: 0.850500 [Train] step: 4000, loss: 0.303545, acc: 0.966667 [Test] loss: 0.453306, acc: 0.869100 [Train] step: 6000, loss: 0.434249, acc: 0.900000 [Test] loss: 0.529966, acc: 0.884700 [Train] step: 8000, loss: 0.707538, acc: 0.866667 [Test] loss: 0.521837, acc: 0.899100
精度自体はdropoutなしの方より悪い気するけど48,96でkeep_prob_1
、keep_prob_2
が共に0.5のときの結果と比べて、学習が停滞しているようには感じない
やはりノード数が多いからだろうか?
最後にkeep_prob_1
は1.0(dropoutを適用しない)keep_prob_2
を0.8に設定した結果
[Train] step: 0, loss: 15.822262, acc: 0.000000 [Test] loss: 15.912350, acc: 0.069200 [Train] step: 2000, loss: 0.096453, acc: 0.966667 [Test] loss: 0.338137, acc: 0.914300 [Train] step: 4000, loss: 0.138627, acc: 0.966667 [Test] loss: 0.221914, acc: 0.936700 [Train] step: 6000, loss: 0.005314, acc: 1.000000 [Test] loss: 0.170819, acc: 0.948000 [Train] step: 8000, loss: 0.288768, acc: 0.866667 [Test] loss: 0.142067, acc: 0.958000
dropoutなしよりとても良くなった
Dropoutのパラメータの設定でここまで結果が変わるとは思っていなかったので調べてて面白かった
ベイズの定理とソフトマックス関数について
授業が始まって中々勉強に時間が取れないがちょっとだけPRML上巻を読んだ。
ベイズの定理について自分なりの解釈としてのメモをかくことに
中学で確率における同時確率(または結合確率)というのを習った。これは事象XとYがあるとして、XとYが同時に起こる確率を示したもの。
数式で表すとこうなる
重要なのはこの等式は事象XとYが独立のとき成立するということ。
XとYが独立であるとは、片方の事象が起こる可能性がわかっていても、もう片方の可能性に何も影響しないということを意味している。
独立の場合は上の等式のようにそれぞれの事象の確率の積で同時確率を計算できる。
中学の問題で言えば裏表のコインやサイコロを振るとかがあった。
実際の状況、問題においては独立でない場合の事象を考えることが多いからこの等式では同時確率を求めるのは難しい。 困った。
そこで、事象の依存性を考慮した同時確率を考える。これは以下のようになる。
確率の乗法定理と呼ばれる。この式が意味することは「事象XとYが同時に起こる確率」は「Xが起こる確率」と「Xが起きた時にYが起こる確率」の積で求められるということ。
右辺にあるP(Y|X)
は「Xが起きた時にYが起こる確率」からYの事前確率だったりYの条件付き確率だったり呼ばれる。らしい。。
またこの乗法定理において事象XとYが独立とわかっているなら、P(Y|X)
はP(Y)
と書き換えることができる。
最初この乗法定理を見た時、Yの事前確率にP(X)
というXが起こる確率で重み付けしたものが、同時確率P(X,Y)
になると考えてしまった。
機械学習の勉強をしているとどっかしらの項を重みと見てしまう悪い発作だ。。。
同時確率P(X,Y)
はP(Y,X)
と書き換えることもできる。そしてこんな感じになる。
つまり以下の関係が得られる。
2枚目の方は有名なベイズの定理。何度も見て何度も忘れた。
式だけを見れば、「Yの事後確率」は「Yの起こる確率」と「Xの事後確率」を掛け合わせたものを「Xの起こる確率」で割ったもので求めることができる、という意味になる。
正直こんな感じだ。でも嬉しいことはおそらくP(Y|X)
を求めるには他の3つの確率を調べれば良いということなんだろう。
こういう時は例で考える。事象Xを年間の行事と考える。ハロウィンだったりプレミアムフライデーだったり色々だ。そして事象Yを美味しい料理と考える。これもケーキだったり厚焼き玉子だったり寿司だったり色々ある。
ある人がクリスマスでうきうきしていて、ケンタッキーのフライドチキンを食べたいとする。その人は「そもそもクリスマスのとき、ケンタッキーのフライドチキンがでる確率っていくらぐらいなんだ・・・?」と疑問に思ったので調べたいとする。
つまりP(Y=ケンタッキー|X=クリスマス)
を求めたい。
求めるにはクリスマスに出てくる全ての料理を挙げて、そのうちケンタッキーがどれくらいの割合を占めるかを考えないと求めることはできない。
ベイズの定理から、他の3つの確率で求めてみる。
P(Y)
はケンタッキーが出てくる確率。年間の売り上げ数から求められるはず。P(X|Y)
はケンタッキーが出てきたとき、その日の行事がクリスマスである確率となる。これもケンタッキーの年間売り上げからクリスマスの日がどれだけの割合を占めているかで求められそうだ。P(X)
はクリスマスが起こる確率。クリスマスは年に一度のため悩む必要はない。
P(Y=ケンタッキー|X=クリスマス)
で求めるか、他の3つの確率で求めるか、どちらのが簡単だろうか。
前者はクリスマスに出てくる全ての料理を挙げ切るのはとても厳しい気がする。後者も大変かもしれないがケンタッキーの年間売り上げあたりを調べればいいだけだ。
P(Y|X)
の測定が難しいなら、他の容易な3つの確率から求めることができる。すごいぞベイズの定理。
もう一つ疑問に思ったことがある。ベイズの定理の右辺にある分母のP(X)
は何をしているのだろうか。
純粋に式だけを見ればP(Y)P(X|Y)
を割っているだけだが、なぜ割ればP(Y|X)
が求まるのだろう。。そういう定理だからで片付くが今いちしっくりこない。
右辺の分母について考える。P(X)
は「Xが起こる確率」だが「事象Yの値に関係なく事象Xが起こる確率」とも言える。これは式で表すと
Nが表すのは上の例(事象Yを美味しい料理)で言うならば y1 = ケーキ、 y2 = 玉子焼き、... といった事象Yが取りうる数。
P(X=クリスマス,Y=ケーキ) + P(X=クリスマス,Y=卵焼き) + ... P(X=クリスマス,Y=yj)
まで事象Xがクリスマスの時に起こりうる同時確率の総和がP(X)
だ。
P(X)
をこの数列で表した時のベイズの定理はこうなる
分子は事象X=クリスマス、事象Y=ケンタッキーの同時確率なのでこう書き換えられる
こうみるとどうだろう。分子は分母の数列の中にある一部の同時確率である。
これは(全体の一部、あるいは比べる量) ÷ (元、全体の量)
といった割合として見ることができるじゃないか。
百分率ではないので右辺の値は必ず [0,1] 範囲に収まる。確率としての値が出てくる。
分母のP(X)
は右辺が [0,1] 範囲に収まるようにある意味での正規化としての役割を担ってくれていたのだ・・・
そしてこの時何かに似ていると気づいた。ニューラルネットワークの活性化関数の一つであるソフトマックス関数のことである。
分類問題における出力が [o1,o2,...,on]
となっているとき、ソフトマックス関数を適用すると各出力の総和が 1 になるように o1,o2,..,on
を変換してくれる。
ベイズの定理にある分母のP(X)
と同じように正規化をしてくれる。
青いイルカの深層学習本ではベイズの定理の説明がなかったので自分がベイズの定理についてあやふやだったソフトマックス関数のとこは「ほーん」って感じに読んでいたがベイズの定理がベースになってるじゃーんと今さらだが興奮した。
KaggleのTitanicで上位10%に入った手法のまとめ
初心者向けですが深層学習の講師を最近やりました。
講義の中で実際にKaggleのコンペで腕試しをするということをしたかったので講義をやる前にチャレンジした。
今回腕試しをするコンペはkaggleのチュートリアルで有名なtitanicを選んだ。
Titanic: Machine Learning from Disaster | Kaggle
どういった内容かというと、タイタニック号の乗客のデータからある乗客は生存したか否かを判定し、その正解率を競う感じ。
試行錯誤して自分の最高スコアは 0.81339 になった。上位10%だしそこそこ頑張れたはず。
もう少し上を目指したいとこだが授業も始まったので一旦打ち切り
コードはここ
データ分析にはpandas
を用いた。最初はdescribe()
やdf.isnull().sum()
とかで統計や欠損値の確認をする。
訓練データのAge、Cabinなどに欠損値があるのがわかる。
次に仮説をいくつか立ててその条件でデータを操作し、その時の生存者の割合を確認していった。仮説は主に
子供や赤ん坊など年齢が若い人の方が優先して救助されるのではないか
女性のが優先して救助されるのではないか
Pclass1(だいたいお金持ちの乗客)の人たちは優先して救助されるのではないか
などなど
年齢はやっぱりというか子供のが生存率が高いのは確認できた
生存率に関しては性別の違いが一番別れていた。
後は名前の属性を何かしら使いたい(文字列の操作を練習したかった)ので名前からMr
やMiss
などを抽出してそれらの生存率を確認したりした。
生存の判別モデル構築の問題として年齢を特徴として扱いたいけど欠損している乗客がいる。
この問題に対して訓練データの年齢における欠損値を除いた年齢の中央値で穴埋めか年齢自体を予測するモデルを作り、そのモデルの予測結果で穴埋めするといった2つの手法をとった。
良い精度が出たのは後者の方だった。年齢予測モデルはXGBoostで構築した。
最終的に使う特徴はこれらに絞った
one_hot
系は文字列カテゴリを数値に変換したもの
SibParch
はSibSp
とParch
を合わせたもの
Fare_rounddown_split
はFare
の小数点を打ち切り、10ドルごとにカテゴリ分けしたもの
By_Age_class
はAge
を10歳ごとにカテゴリ分けしたもの
生存者の判別モデルとしてTensorflowでロジスティック回帰モデルとXGBoostモデルの2つを構築し、比較した。
Tensorflowのロジスティック回帰モデルは以下のように構築した
# 入力層 X = tf.placeholder(tf.float32, shape=[None, 7], name="input") t = tf.placeholder(tf.float32, shape=[None, 1]) # パラメータ1 stddev = np.sqrt(2.0 / 7) W1 = tf.Variable(tf.truncated_normal([7,24], stddev=stddev)) b1 = tf.Variable(tf.constant(0.1, shape=[24])) # パラメータ2 stddev = np.sqrt(2.0 / 24) W2 = tf.Variable(tf.truncated_normal([24,48], stddev=stddev)) b2 = tf.Variable(tf.constant(0.1, shape=[48])) keep_prob = tf.placeholder(tf.float32) # ドロップアウトする割合 # パラメータ3 stddev = np.sqrt(2.0 / 48) W3 = tf.Variable(tf.truncated_normal([48,1], stddev=stddev)) b3 = tf.Variable(tf.constant(0.1, shape=[1])) layer1 = tf.nn.relu(tf.matmul(X,W1) + b1) layer2 = tf.nn.relu(tf.matmul(layer1,W2) + b2) layer2_drop = tf.nn.dropout(layer2, keep_prob) layer3 = tf.matmul(layer2_drop,W3)+b3 p = tf.nn.sigmoid(layer3,name="output") # 荷重減衰 norm_term = tf.nn.l2_loss(layer1) + tf.nn.l2_loss(layer2_drop) # 正則項 lambda_ = 0.001 # 損失関数 loss = tf.reduce_mean(tf.square(p - t))+ lambda_*norm_term # 学習アルゴリズム optimizer = tf.train.AdamOptimizer() train_step = optimizer.minimize(loss) # 精度 correct_prediction = tf.equal(tf.sign(p-0.5), tf.sign(t-0.5)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
XGboostとlog_lossで誤差の値を比べたがXGboostの勝利だった
NNと決定木における表現力の問題だろうか・・・
ハイパーパラメータをいじって提出していたら 0.81339 という結果が出た。
終わった後に他の人と話してタイタニックは実際にあった話、つまり背景があるから背景から得られる情報を使えばよかったとか元のタイタニック号の船構造を見つけて救命艇がどこにあるかや部屋番号の割り当て(Cabin)を確認するのもアリだったよねーとか灯台下暗しって感じに知見を得た。
他にもstackingや交差検証など学習方法の見直しも大事