不確定な世界

科学の話題を中心に、勉強したことや考えたことを残していきたいと思います

「ゼロから作るDeep Learning」を読んだ(前編)

斎藤康毅著「ゼロから作るDeep Learning」を読んだので、その読書メモ。
人工知能ディープラーニングには前々から興味を持っていてブルーバックスレベルの本を数冊読んでいたが、この分野の本格的な専門書を読むのは初めてだ。この本は有名なので、既に無数の紹介記事や書評記事が存在する。なので、この記事では書籍紹介や内容要約の体裁は取らずに、読んだときの思考をそのまま書き散らすことにする。なお、一周ざっと読んでから二週目で考えをまとめるために書いているため、思考の時系列が飛んでいるところがあるのであしからず。

1章 Python入門

私は元々物理の計算のためにPythonを使用していた経験があるので、ここは軽く読み飛ばした。Numpyで配列とスカラー値を掛け算すると配列の全要素に一気に演算が行われる仕様のことを「ブロードキャスト」と呼ぶらしい。今まで何気なく使っていたが、初めて知った。

2章 パーセプトロン

パーセプトロンというニューロンを模したモデルにより、論理回路を構成できる。ただし、単層パーセプトロンではXORは作れない

パーセプトロンを多層化することで、XORを構成できる。そもそも単層でNANDが構成できる時点で計算万能なのだから、適当に組み合わせればどんな回路でも作れるのは当たり前といえば当たり前だが。

20170628追記:
多層パーセプトロンの計算万能性について、自分なりにもう少し理解が深まったので追記する。
ここでは、パーセプトロンの重み(+バイアス)を適当に調節して適当に組み合わせるとNAND回路が構成できることを学んだ。NAND回路を組み合わせることにより、理論上可能な限りどんな複雑な処理でも行うことができる。
ところで、人工知能による分類問題や判断問題は、要するに膨大なif分岐だといえる。もしこういう状況ならこうしろ、もしこういう特徴ならこうだろう、といった感じだ。しかし、これを直接プログラムに書き下すのは大変だ。必要な知識をごく一部の特定の分野に絞ったものがいわゆるエキスパートシステムであったわけだが、汎用性を求めれば人間にはとても認識できないような複雑な分岐なるだろうし、そもそも明確な条件分として定義できない曖昧な判断基準もあるだろう。いかに有効な条件文を作成してそれをどう組み合わせるかが、人工知能エンジニアのノウハウだったはずだ。
しかし、どんなに複雑な処理であろうと、NAND回路を適切に組み合わせれば実現できるのだった。そして、NAND回路を組み合せるというのは、多層パーセプトロンニューラルネットワーク)の言葉で言えば、重みとバイアスを調節することだった(全結合だとすると、重みを0にするとノードの接続を切れるので、ノードの繋ぎ方も重みで調節できる)。どんな複雑で曖昧な判断処理の構築も、パーセプトロンの重みを調節することに帰着するのだ。
要するに、判断や分類に必要な条件文の抽出という行為が、重みパラメータという(コンピュータで扱いやすい)数値データの調節に置き換わったわけだ。
「そもそもニューラルネットワークが学習するとはどういうことなのか」「ニューラルネットワークが”何でもできる”のはなぜか」という素朴な疑問を自分の言葉で説明するとこうなる。特に、知識の獲得を数値の調整で実現する、という表現にたどり着いたのが個人的にはとても面白い。これが正解かどうかは分からないが、それなりに納得できたように思う。

3章 ニューラルネットワーク

パーセプトロンが信号を出力(生物学用語では興奮)するかどうかを判定する関数を「活性化関数」と呼ぶ。単純パーセプトロンではステップ関数を用いるが、ニューラルネットワークでは微分ができるように滑らかな「シグモイド関数」を用いる。また、最近ではReLU(ランプ)関数なるものが多く使われるらしい。

→よく使われるということは、性能が良いのだろう。なぜReLU関数の方が性能がいいのかは、今の段階ではよくわからない。
→ReLU関数のメリットの一つは計算量が少ないこと。確かに、expを使うシグモイドに比べたら楽そうだ。だけど本当にそれだけなのだろうか?この分野では現実的な問題として計算量を減らす工夫が大事なのだから別にいいのだけど、もう少し本質的な理論上の根拠があったほうが個人的には面白い。

→いずれにしても大事なのは、活性化関数が非線形であるということ。非線形性が面白い性質を生み出すという、カオス理論や複雑系科学と同じ理屈だ。

・「行列の内積」と言われて、少し目が止まってしまった。紹介されているのは明らかに普通の「行列の積」だ。おそらく、Numpyのブロードキャストのせいで「行列の積」がdot関数に追いやられているので、勘違いしてしまったのだろう。このことはGitHub上でも訂正されている。
ちなみに、「行列の内積」というものが存在すること、それ自体は事実である。私も数学は専門ではないので墓穴を掘りたくはないが、少なくとも量子力学では以下のように習った(A、Bは複素成分で同じ大きさの行列とする)。

行列の内積の定義:
\displaystyle
(A,B) \equiv \mathrm{Tr}(A^{\dagger}B)

例えば

\displaystyle
A = \left(
    \begin{array}{ccc}
      a_{11} & a_{12}\\
      a_{21} & a_{22}\\
    \end{array}
  \right)
\

,B = \left(
    \begin{array}{ccc}
      b_{11} & b_{12}\\
      b_{21} & b_{22}\\
    \end{array}
  \right)
\

とすると、

\displaystyle
A^{\dagger}B = \left(
               \begin{array}{ccc}
                {a^{*}}_{11}b_{11}+{a^{*}}_{21}b_{21} & {a^{*}}_{11}b_{12}+{a^{*}}_{21}b_{22}\\
                {a^{*}}_{12}b_{11}+{a^{*}}_{22}b_{21} & {a^{*}}_{12}b_{12}+{a^{*}}_{22}b_{22}\\
              \end{array}
            \right)
          \

となるので、

\displaystyle
\mathrm{Tr}(A^{\dagger}B) = {a^{*}}_{11}b_{11}+{a^{*}}_{21}b_{21}+{a^{*}}_{12}b_{12}+{a^{*}}_{22}b_{22}

である。すなわち「対応する成分を掛け算して全部足す」という「ベクトルの内積」を、そっくりそのまま行列に拡張した形になる。当然、「行列の内積」でも出力は一つのスカラー値だ。

話がだいぶそれてしまった。数式を書く練習にちょうどよかった。

・重み行列と入力データを表すベクトルをかけることで、次のノードへの入力が計算できる。量子力学では普通縦ベクトルを使って行列×ベクトルと書くので、横ベクトルを使って行列が右に来る書き方はちょっと違和感がある。

・出力層では中間層とは異なる活性化関数が用いられる。回帰問題では恒等関数を、分類問題ではソフトマックス関数が用いられる。

・ソフトマックス関数の出力は「確率」と解釈できる。なぜわざわざexpを使うかだが、おそらく損失関数として使うlogと逆関数の関係になっていることが、誤差逆伝播の計算に重要だからだろう。

・ソフトマックス関数を実装する際、式変形することでオーバーフローを防ぐ。これは目からウロコだった。実装の問題は実装で逃げるしかないと思っていたが、純粋に数学的な式変形だけで根本的に解決できるのか。

・MNISTデータセットは、手書き数字の画像データの集まり。MNISTは画像認識分野のHello Worldに相当するらしい。

数値計算のライブラリは巨大な行列の計算を効率よく行うことに最適化されているので、入力データ(ベクトル)を束ねて行列として一気に処理してしまうと、計算が大幅に速くなる。

後編に続く