Loading web-font TeX/Math/Italic

2014年1月25日土曜日

std::unique_ptr<T>はTのfull definitionを必要とするか?

以下のコードなら foo.h に #include "hoge.h" は必要ない。 以下の場合、foo.h に #include "hoge.h" は必要である。 後者の場合、foo の destructor は inline で定義される。これは、hoge の full definition を要求してしまう。

2014年1月16日木曜日

Neural Networkにおける誤差逆伝播法


はじめに


Neural Network(NN)で使われる誤差逆伝播法の計算をまとめます。

NNの基本構造


いま、NNの中間層 k-1,k,k+1 を考える。k-1 層内の j 番目のユニットと k 層内の i 番目のユニット間の重みを \omega_{i,j}^{k} とする。
k-1 層内の全てのユニットから k 層内の i 番目のユニットへの入力値 x_{i}^{k} は以下のように書ける(本解説ではバイアス項を無視する)。 \begin{equation} x_{i}^{k} = \sum_{j}\;\omega_{i,j}^{k}\;h_{j}^{k-1} \label{first} \end{equation}
ここで、h_{j}^{k-1}k-1 層内の j 番目のユニットからの出力値である。k 層内の i 番目のユニットからの出力値 h_{i}^{k} は、活性化関数 f を用いて \begin{equation} h_{i}^{k} = f(x_{i}^{k}) \end{equation}
と書くことができる。
NNに解かせたい問題が多クラス問題の場合、最上層(a 層とする)のユニットの数をクラスと同数とする。そして、各ユニットからの出力値を次式で定義する。 \begin{equation} h_{i}^{a} = \frac{ \exp{ \left(x_{i}^{a}\right) } }{ \sum_{j}\exp{ \left(x_{j}^{a}\right) } } \equiv p_{i} \end{equation}
p_{i} は正規化された量である。 \begin{equation} \sum_{i}\;p_{i}=1 \end{equation}
最下層に与えられるデータ X のクラスのラベルが i であるなら、最上層からの出力値は p_{i}=1,p_{j\neq i}=0 となるのが理想である。

目的関数


NNにおける学習の目的は、パラメータ w_{i,j}^{k} を全て求めることである。その際、次の目的関数を用いる。 \begin{equation} C = -\sum_{i}\;d_{i}\;\log{p_{i}} \label{obj} \end{equation}
最下層に与えられるデータ X のクラスのラベルが i なら、\{d_{i}\}d_{i}=1,d_{j\neq i}=0 である。ここで、0\le p_{i}\le 1 であるから \log{p_{i}}\le 0 が成り立つ。もし、p_{i}=1,p_{j\neq i}=0 が成り立つなら、このとき C は最小値 0 となる。 従って、理想に近い p_{i} を実現するには、C を最小化すればよい。

勾配降下法(Gradient Descent Method)


C は重み w_{i,j}^{k} の関数である。 \begin{equation} C = C(w_{1,1}^{1},w_{1,2}^{1},\cdots,w_{i,j}^{k},\cdots) \end{equation}
この全ての重みを求めることが目的である。 勾配降下法の手順は以下の通りである。
  1. 適当な初期値 \{\omega_{i,j}^{k}\} を設定する。
  2. C を計算する。C の変化量がしきい値以下なら終了。
  3. 次式を計算する。 \begin{equation} \delta w_{i,j}^{k} = -\epsilon \frac{\partial C}{\partial w_{i,j}^{k}} \end{equation}
    ここで、\epsilon は正のパラメータである。
  4. w_{i,j}^{k} を更新する(w_{i,j}^{k}\leftarrow w_{i,j}^{k}+\delta w_{i,j}^{k})。
  5. 2へ
これを実行するには、 \begin{equation} \frac{\partial C}{\partial w_{i,j}^{k}} \end{equation}
を計算しなければならない。 C\{x_{i}^{k}\} の関数である。 \begin{equation} C = C(x_{1}^{k},x_{2}^{k},\cdots) \end{equation}
また、x_{i}^{k}\{\omega_{i,j}^{k}\} の関数である。 \begin{equation} x_{i}^{k} = x_{i}^{k}(w_{i,1}^{k},w_{i,2}^{k},\cdots) \end{equation}
従って、 \begin{eqnarray} \frac{\partial C}{\partial \omega_{i,j}^{k}}&=&\frac{\partial C}{\partial x_{i}^{k}}\frac{\partial x_{i}^{k}}{\partial \omega_{i,j}^{k}}\nonumber \\ &=&\frac{\partial C}{\partial x_{i}^{k}}h_{j}^{k-1} \label{point} \end{eqnarray}
を得る。ただし、最後の式で式(\ref{first})を用いた。結局、\frac{\partial C}{\partial \omega_{i,j}^{k}} を得るには、\frac{\partial C}{\partial x_{i}^{k}} を求めなければならないことが分かる。 これを計算する手法が誤差逆伝播法である。

誤差逆伝播法


Ck+1 層への入力値 \{x_{i}^{k+1}\} の関数とみなす。 \begin{equation} C = C(x_{1}^{k+1},x_{2}^{k+1},\cdots) \end{equation}
また、k+1 層への入力値 x_{i}^{k+1} は、k 層への入力値 \{x_{i}^{k}\} の関数である。 \begin{equation} x_{i}^{k+1} = x_{i}^{k+1}(x_{1}^{k},x_{2}^{k},\cdots) \end{equation}
従って、次式が成り立つ。 \begin{equation} \frac{\partial C}{\partial x_{i}^{k}}=\sum_{j}\;\delta _{j}^{k+1}\frac{\partial x_{j}^{k+1}}{\partial x_{i}^{k}} \label{aa} \end{equation}
ここで、 \begin{equation} \delta_{j}^{k+1} \equiv \frac{\partial C}{\partial x_{j}^{k+1}} \end{equation}
とした。ところで、 \begin{equation} x_{j}^{k+1} = \sum_{l}\;\omega_{j,l}^{k+1}\;f(x_{l}^{k}) \end{equation}
であるから、 \begin{equation} \frac{\partial x_{j}^{k+1}}{\partial x_{i}^{k}}=\omega_{j,i}^{k+1}\;f^{\;\prime}(x_{i}^{k}) \end{equation}
が成り立つ。従って、式(\ref{aa})は \begin{equation} \delta_{i}^{k}=f^{\;\prime}(x_{i}^{k})\;\sum_{j}\;\delta_{j}^{k+1}\;\omega_{j,i}^{k+1} \label{ite} \end{equation}
と変形される。ただし、 \begin{equation} \delta_{i}^{k} \equiv \frac{\partial C}{\partial x_{i}^{k}} \end{equation}
とした。式(\ref{ite})から、\{\delta_{i}^{k+1}\} が決まれば、\{\delta_{i}^{k}\} が求まることが分る。すなわち、最上層の値 \begin{equation} \delta_{i}^{a} \equiv \frac{\partial C}{\partial x_{i}^{a}} \end{equation}
が決まれば、式(\ref{ite})を順に適用することにより、全ての \{\delta_{i}^{k}\} を求めることができる。
ここで、最上層の \delta_{i}^{a} を計算するため、C を変形する。 \begin{eqnarray} C &=& -\sum_{l}\;d_{l}\;\log{p_{l}}\nonumber\\ &=& -\sum_{l}\;d_{l}\;\log{h_{l}^{a}} \nonumber\\ &=& -\sum_{l}\;d_{l}\;\log{\frac{ \exp{ \left(x_{l}^{a}\right) } }{ \sum_{j}\exp{ \left(x_{j}^{a}\right) } } } \nonumber\\ &=& -\sum_{l}\;d_{l}\;x_{l}^{a} + \left( \sum_{l}\;d_{l} \right)\;\log{ \left(\sum_{j}\exp{ \left( x_{j}^{a} \right) }\right) } \nonumber\\ &=& -\sum_{l}\;d_{l}\;x_{l}^{a} + \log{ \left(\sum_{j}\exp{ \left( x_{j}^{a} \right) }\right) } \end{eqnarray}
最後の式で \begin{equation} \sum_{l}\;d_{l}=1 \end{equation}
を用いた。従って \begin{equation} \frac{\partial C}{\partial x_{i}^{a}}=p_{i}-d_{i}=\delta_{i}^{a} \end{equation}
を得る。 p_{i}\{\omega_{i,j}^{k}\} が決まれば計算できる量であり、\{d_{i}\} は最初に当たられる量である。この量は、実際の出力(p_{i})と理想の出力(d_{i})とのずれ(誤差)を表している。ここまでの議論で、最上層から最下層まで全ての \{\delta_{i}^{k}\} を計算できることが分かる。そして、式(\ref{point})を用いれば次式のように \frac{\partial C}{\partial \omega_{i,j}^{k}} を全て求めることができる。 \begin{equation} \frac{\partial C}{\partial \omega_{i,j}^{k}}=\delta_{i}^{k}\;h_{j}^{k-1} \end{equation}
最上層の誤差を最下層へ向かって伝播していく手法なので、誤差逆伝播法と呼ぶ。

3層の場合


 いま、3層の場合を具体的に考えてみる。各層において次式が成り立つ。 \begin{eqnarray} x_{i}^{1}&=&\sum_{j=1}^{D}\;\omega_{i,j}^{1}\;h_{j}^{0},\;\;\;i=1,\cdots,M \\ h_{i}^{1}&=&f(x_{i}^{1}),\;\;\; i=1,\cdots,M \\ x_{i}^{2}&=&\sum_{j=1}^{M}\;\omega_{i,j}^{2}\;h_{j}^{1},\;\;\; i=1,\cdots,K \\ h_{i}^{2}&=&\frac{\exp{x_i^2}}{\sum_{j=1}^{K}\;\exp{x_j^2}}=p_i,\;\;\; i=1,\cdots,K \end{eqnarray}
入力層( k=0 )のユニット数を D、隠れ層( k=1 )のユニット数を M、出力層( k=2 )のユニット数を K とした。 活性化関数 f として \begin{equation} f(x) = \tanh{x} \end{equation}
を考える。このとき、次式が成り立つ。 \begin{equation} f^{\prime}(x) = 1-\left(f(x)\right)^2 \end{equation}
最初に \delta_{i}^{2} を求める。 \begin{equation} \delta_{i}^{2}=p_{i}-d_{i},\;\;\; i=1,\cdots,K \end{equation}
この結果を用いて、隠れ層の \delta_{i}^{1} を計算する。 \begin{equation} \delta_{i}^{1}=\left(1-f(x_{i}^{1})^2\right)\;\sum_{j=1}^K\;\delta_{j}^{2}\;\omega_{j,i}^{2},\;\;\; i=1,\cdots,M \end{equation}
ここまでで \{\delta_{i}^{1}\}\{\delta_{i}^{2}\} を求めることができた。これらを用いて \begin{eqnarray} \frac{\partial C}{\partial \omega_{i,j}^{1}}=\delta_{i}^{1}\;h_{j}^{0},\;\;\;i=1,\cdots,M,\;\;\;j=1,\cdots,D\\ \frac{\partial C}{\partial \omega_{i,j}^{2}}=\delta_{i}^{2}\;h_{j}^{1},\;\;\;i=1,\cdots,K,\;\;\; j=1,\cdots,M \end{eqnarray}
を計算することができる。また、入力値 h_j^0 の微小変化に対する出力値 p_k の変化分を表す量 J_{k i} \begin{equation} J_{k i} = \frac{\partial p_k}{\partial h_i^0} \end{equation}
は以下のように計算される。 \begin{eqnarray} J_{k i} &=& \frac{\partial p_k}{\partial h_i^0} \nonumber \\ &=& \sum\; \frac{\partial p_k}{\partial x_\eta^2} \frac{\partial x_\eta^2}{\partial h_\alpha^1} \frac{\partial h_\alpha^1}{\partial x_\beta^1} \frac{\partial x_\beta^1}{\partial h_i^0} \nonumber \\ &=& \sum_{\eta,\alpha,\beta}\; \frac{\partial p_k}{\partial x_\eta^2} \omega^2_{\eta,\alpha} \frac{\partial h_\alpha^1}{\partial x_\beta^1} \omega^1_{\beta,i} \nonumber \\ &=& \sum_{\eta,\alpha}\; \left(\delta_{\eta,k}\;p_k - p_k\;p_\eta\right) \omega^2_{\eta,\alpha}\; f^{\prime}(x_\alpha^1)\; \omega^1_{\alpha,i} \end{eqnarray}

2014年1月13日月曜日

moveの実験コード

私的な覚え書き。

2014年3月13日追記:std::forwardはテンプレート引数に対して使うものなので、ここで示した使い方は流儀に反するのかもしれません。

クラスfooのコンストラクタにクラスbarのオブジェクトを渡す。 このとき、std::moveで渡す場合(check_move_0)と、値で渡す場合(check_move_1)を比較する。 出力: 19行目のstd::forwardのおかげで、必要最低限のコピーで済むことがわかる。 std::forwardを使わず、bar_(b)と書くと以下のように不必要なコピーが行われる。 参照サイト

2014年1月5日日曜日

boost::logでlink error

BOOST_LOG_DYN_LINKあるいはBOOST_ALL_DYN_LINKを定義しておけば良い。