失敗の本質

失敗の本質―日本軍の組織論的研究 (中公文庫)

失敗の本質―日本軍の組織論的研究 (中公文庫)

この前同僚が読んでいたので、そういえば読んでいなかったなと思って読みました。半分以上が1章の事例研究で占められていて、エビデンスを示すことに丁寧という印象を持ちました。また、2章の分析内容についても的確だと思いまし、3章の教訓についてもその通りだなと思う内容でした。組織運営みたいなものについてあまり読んだことがない人が読むにはよいのかもしれません。また、歴史書として興味がある方にもいいのかもしれないと思いました。

個人的には、何というか、分析対象が事例として指摘事項が多すぎる状態だったのであまり学びはありませんでした。どちらかというと、もうちょっとまともな状況な事例についての分析を読めるといいかなと思いました。そりゃ駄目だろみたいなツッコミどころ満載だったので。。類書か分かりませんが、戦略や兵站という意味では戦略の本質補給戦―何が勝敗を決定するのかを読んだ方がいいかと思いました。

ゼロから作るDeep Learning 6章 学習に関するテクニック

前回はゼロから作るDeep Learning 6章 学習に関するテクニック - n3104のブログです。

6章です。5章まででニューラルネットワークの学習の仕組みについては学んできたため、6章では実際に学習を行う上で選択することになる各種パラメーターについて説明しています。具体的には重みについて扱っています。まず 6.1 で重みパラメーターを探索する際の最適化手法を、6.2 では重みの初期値について、6.3 で重みの初期値の問題を学習時に解決するBatch Normalizationについて、6.4 で過学習について扱っています。

  • 6.1.3 SGD の欠点
    • 図もあるし、説明がとても分かりやすい。
    • “図 6 - 2 で表される勾配は、多くの場所で (0, 0) の方向を指さ ないということです。”
      • “図6-3 に示すようなジグザグな動きをします。"とあるように、学習できないという意味ではなくて効率が悪いという意味。
      • “つまり、SGD の欠点は、関数の形状が等方的でないと――伸びた形の関 数だと――、非効率な経路で探索することになる点にある”
        • かといって一般的に他の機械学習の手法だとSGDが利用されていると思うので、これから登場する手法もトレードオフがあるんだろうなー。
  • 6.1.4 Momentum
    • 勾配の変化も情報として扱うようにしたって感じかー。なるほど。
    • self.v[key] = np.zeros_like(val)
    • 説明だけ読むとSGDの代わりにMomentum使っておけばよさそうだけど、あまり聞かないのは何らかの制約なりトレードオフがあるんだろうなー。
  • 6.1.5 AdaGrad
    • 学習係数の減衰(learning rate decay)をパラメータ毎に実施したものということか。
    • 式自体は非常にシンプル。そのまんま。
    • 結局、勾配を利用するという部分はどの手法も同じなんだなー。
    • “最後の行で 1e-7 という小さい値を加算している点で す。これは、self.h[key] の中に 0 があった場合、0 で除算してしまうことを防ぐ ためのものです。多くのディープラーニングのフレームワークでは、この小さな値も パラメータとして設定できますが、ここでは 1e-7 として固定の値を使用しています。”
  • 6.1.6 Adam
    • MomentumとAdaGradをくっつけられないかと思ってたら、まさにそれがAdamだったw
    • “また、ハイパーパラメータの「バイアス補正(偏りの補正)」が行われてい ることも Adam の特徴です。ここでは、これ以上踏み込んで説明することは避け ます。詳細は原著論文 [8] を参照してください。”
    • “また、Python の実装については、 common/optimizer.py に Adam というクラスで実装してあるので、興味のある方 は参照してください。”
      • Adamについては理論がやや複雑というだけあって、紹介に留めてるんだなー。
      • https://arxiv.org/abs/1412.6980
        • 普通に右側のDownloadのとこからダウンロード可能。
  • 6.1.7 どの更新手法を用いるか?
    • SGD、Momentum、AdaGrad、Adam と 4 つの手法を説明してきましが、どれ を用いたらよいのでしょうか? 残念ながら、すべての問題で優れた手法というのは (今のところ)ありません。それぞれに特徴があり、得意な問題、不得意な問題があります。”
      • ですよねー。
    • “多くの研究では今でも SGD が使われています。Momentum や AdaGrad も試す価値のある手法です。最近では、多くの研究者や技術者が Adam を好んで使ってい るようです。本書では、主に SGD や Adam を使用しますが、読者の方においては、 自分の好きなようにいろいろ試してみてください。”
      • やはりSGDが使われてるんだなー。でも最近だとAdamも使われるようになってると。従来手法でもAdam使われるようになるのかなー。
  • 6.1.8 MNIST データセットによる更新手法の比較
    • “この実験の注意点として は、学習係数のハイパーパラメータや、ニューラルネットワークの構造(何層の深さ か、など)によって結果は変化するということです。ただし、一般に SGD よりも他 の 3 つの手法のほうが速く学習でき、時には最終的な認識性能も高くなります。”
      • 結局、Deep Learningになったからと言って今までよりもモデルの作成が楽になるってことはなくて、最終的な予測精度が高くなるケースがあるってぐらいなんだろうなー。。
  • 6.2.1 重みの初期値を 0 にする?
    • “なぜ重みの初期値を 0 にしてはいけない――正確には、重みを均一な値に設定して はいけない――のでしょうか? それは誤差逆伝播法において、すべての重みの値が 均一に(同じように)更新されてしまうからです。”
      • 確かに重みが 0 だと計算結果は Wij * xn = 0 でバイアス項のみが残ることになるな。でもバイアス項があるから完全には 0 にならない気もするが。。
        • 実際に4章の train_neuralnet.py で試してみた所、やはりバイアス項をランダムに初期化するだけでも学習は進んでいた。ただし、遅くなるが。そして、バイアス項も 0 にしても学習自体は進んでいた。ただし、とても遅くはなる。
          • バイアス項も 0 にしても学習が可能なのは、損失関数の結果が逆伝播するため。入力が一律 0 でも出力層で損失関数を計算するとノードによって値が異なることになるので、その差分を逆伝播する過程で少しずつ重みとバイアス項についても変化することになる。dy = (y - t) / batch_num の箇所。

デフォルトの weight_init_std=0.01 の場合 f:id:n-3104:20170716153129p:plain

weight_init_std=0 に変更した場合(biasは 0 のまま)

        weight_init_std = 0
        self.params['b1'] = np.zeros(hidden_size)
        self.params['b2'] = np.zeros(output_size)

f:id:n-3104:20170716153141p:plain

weight_init_std=0 で bias をランダムにした場合

        weight_init_std = 0
        self.params['b1'] = np.random.randn(hidden_size)
        self.params['b2'] = np.random.randn(output_size)

f:id:n-3104:20170716153152p:plain

  • 6.2.2 隠れ層のアクティベーション分布
    • 重みの初期化方法と活性化関数の組み合わせである層の出力がどのような分布となるかを確認できる。
    • 前節で 0 の場合について触れていたので実際に 0 にしてみたら全て 0.5 となった。バイアス項も無いので当然といえば当然だが。
    • “逆に、偏ったデータが流れると、勾 配消失や「表現力の制限」が問題になって、学習がうまくいかない場合があり ます。”
      • そもそもデータに偏りがあることを機械に学習させるのが機械学習だと思うのだけど、あまりに極端なケースだとDeep Learningでは扱えないってこと?それともあくまでも重みに限った話なのかなー。
    • w = np.random.randn(node_num, node_num) / np.sqrt(node_num) は割り算になっているが weight_init_activation_histogram.py は掛け算になっているので注意。
      • w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)
    • tanh 関数は、sigmoid 関数と同じ S 字カーブの関数ですが、 tanh 関数が原点 (0, 0) で対称な S 字カーブであるのに対して、sigmoid 関 数は (x, y) = (0, 0.5) において対称な S 字カーブです。なお、活性化関数に 用いる関数は、原点対称であることが望ましい性質として知られています。”
      • じゃあ、最初から tanh 関数使えばいいのでは。。

tahn 関数の場合の結果は以下の通り。釣鐘型と言えばそうだけど、右半分になっているような。。 f:id:n-3104:20170716153211p:plain

  • 6.2.3 ReLU の場合の重みの初期値
    • “以上のまとめとしては、活性化関数に ReLU を使う場合は「He の初期値」、sigmoid や tanh などの S 字カーブのときは「Xavier の初期値」を使う――これが現時点で のベストプラクティスということになります。”
      • ベストプラクティスというなら従えばいいとは思うが、逆に言うと従わないとまともに学習できないってことかー。。
  • 6.2.4 MNIST データセットによる重み初期値の比較
    • なるほど。今までは学習できていた std=0.01 で学習できなくなったのは隠れ層が 1 -> 4 層に増えたからかー。で、その結果ほとんどの値が 0 になり、結果として勾配消失となって学習が進まなくなったというわけか。実際、隠れ層を 1 に変えて weight_init_compare.py を実行したら今まで通り std=0.01 でも学習できた。

隠れ層 1 の場合 f:id:n-3104:20170716153234p:plain

    networks[key] = MultiLayerNet(input_size=784, hidden_size_list=[100],
                                  output_size=10, weight_init_std=weight_type)
  • 6.3 Batch Normalization
    • “そ れでは、各層で適度な広がりを持つように、“強制的”にアクティベーションの分布 を調整してみてはどうでしょうか?”
      • こういう発想いいなー。Deep Learningが実際に利用されるようになって数年だから、まだまだこういうチューニングの余地って残されてるのかなー。
  • 6.3.1 Batch Normalization のアルゴリズム
    • “そして、入力データを平均が 0 で分 散が 1 になる――適切な分布になる――ように正規化します。”
      • 結局、やってることはこれだけといえばこれだけなんだよね。
    • “さらに、Batch Norm レイヤは、この正規化されたデータに対して、固有のスケー ルとシフトで変換を行います。”
      • たんに正規化するだけじゃなくてスケール変換とシフトしてるのが肝なのでは。
      • γとβについても学習させるのかー。実際、MultiLayerNetExtend 中で勾配を求めていて、学習させていた。
        • 勾配万能だな。。もちろん、局所解に入り込むことはあるんだろうけど、それも元の数式次第だろうし、今回みたいに単純な式だと大域解に至るのかなー。
    • “初期値にそれほど依存しない(初期値に対してそこまで神経質にならなくてよい)”
      • 図 6-19 見た感じだと、初期値もやはり重要なのでは。。とはいえ、「活性化関数に ReLU を使う場合は「He の初期値」、sigmoid や tanh などの S 字カーブのときは「Xavier の初期値」を使う」を適用した上でやるならまず大丈夫って感じかなー。
    • 過学習を抑制する(Dropout などの必要性を減らす)”
      • 6.4.3 Dropout で出てくる。"Dropout は、ニューロンをランダムに消去しながら学習する手法です。訓練時に 隠れ層のニューロンをランダムに選び出し、その選び出したニューロンを消去しま す。"
  • 6.4.1 過学習
    • “また、ネットワークの複雑性を高めるために 7 層のネットワーク――各層 のニューロンの個数は 100 個、活性化関数は ReLU――を使います。”
      • なるほど。ネットワーク自体は複雑だから訓練データの認識精度を 100% にできるのか。これはこれでおもしろい。
    • 実際に試す際は overfit_weight_decay.py の # weight decay(荷重減衰)の設定 の箇所を 0.1 -> 0 に変える必要がある。weight_decay_lambda = 0.1となっているのは次節 6.4.2 Weight decay に対応したコード。
  • 6.4.2 Weight decay
    • 要は重みに対するL2正則化
    • ノルムってそう言えばどういう意味だっけ?
    • multi_layer_net.py のソースを見ていたら、正則化項は全て隠れ層の重みを足して最後に損失関数にペナルティ項として追加されていた。ニューラルネットワークの場合は層が何層もあるのでL2正則化の場合はその全ての層の重みを足し上げるんだー。まぁ、そもそも正則化項は重みを小さくするもので、その重みが各層に存在するのだから、全ての層の重みを対象とするのは同然なんだろうけど、なんとなく乱暴な感じでおもしろかったw
      • そういう意味だとニューラルネットワークの重みに対してL1正則化は利用しないんだろうなー。
      • “Weight decay は、すべての重みに対して、損失関数に 1 λW2 を加算します。” と本文に書いてはあったんだけど、そういう意味だと読んでも気づかなかった。やはりソース読むのは大切だなー。
    • “図6-21 のとおり、訓練データの認識精度とテストデータの認識精度には“隔たり” がありますが、Weight decay を用いなかった図6-20 の結果と比較すると、その隔たりは小さくなっています。これは過学習が抑制されたということです。また、訓練データの認識精度が 100%(1.0)に到達していない点も注目すべき点です。”
      • 確かにかなり改善されてる。とはいえ、元々過学習が発生するような訓練データの少ない状況が問題なので、そういうデータを集められない状況下での過学習の抑制ぐらいにしか効果はなくて、予測精度を上げたいなら訓練データを集める方が重要なんだろうなー。それとも、一般的に適用するのかなー?
  • 6.4.3 Dropout
    • “つまり、Dropout は、アンサンブル学習と同 じ効果を(擬似的に)ひとつのネットワークで実現していると考えることがで きるのです。”
      • 表現力が高いがゆえに、1つのネットワークで複数のモデルを表現できるってことかなー。でもアンサンブル学習は認識精度の向上が目的であって過学習の抑制は目的でないと思うんだけど。。
    • dropout_ratio = 0.20, 0.50 と増やすほど過学習は抑えられたが学習自体が進まなくなった。
    • multi_layer_net_extend.py のソースを見てて思うが、ほんと概念としてのレイヤと個々の計算レイヤは別物だなと思う。DropoutもDropoutを個々の隠し層に追加してDropout付きレイヤにしてて、デコレータパターン見てるような気持ちになる。
            if self.use_dropout:
                self.layers['Dropout' + str(idx)] = Dropout(dropout_ration)

dropout_ratio = 0.20 f:id:n-3104:20170716153409p:plain

dropout_ratio = 0.50 f:id:n-3104:20170716153419p:plain

  • 6.5 ハイパーパラメータの検証
    • “ハイパーパラメータの決定には一般に多くの試行錯誤が伴い ます。ここでは、できるだけ効率的にハイパーパラメータの値を探索する方法につい て説明します。”
      • 結局、これが解決しないと従来手法と大差ない気がする。。まぁ、予測精度は従来手法より上げられるようなので、そこはブレイクスルーなんだろうけど。
  • 6.5.1 検証データ
    • “それは、テストデータを使ってハイパーパラメータを調整するとすれば、 ハイパーパラメータの値はテストデータに対して過学習を起こすことになるからで す。”
    • “そのため、ハイパーパラメータを調整する際には、ハイパーパラメータ専用の確認 データが必要になります。ハイパーパラメータの調整用のデータは、一般に検証デー タ(validation data)と呼びます。この検証データを使って、ハイパーパラメータの 良さを評価します。”
      • なるほど。別に用意するんだ。
    • “訓練データは、パラメータ(重みやバイアス)の学習に利用します。検証デー タは、ハイパーパラメータの性能を評価するために利用します。テストデータ は汎化性能をチェックするために、最後に(理想的には一度だけ)利用します。”
      • ニューロンの数まで変えたら、それは訓練からやり直しになるんでは。。
    • “ shuffle_dataset という関数は、np.random.shuffle を利用したもので、common/util.py に、その実装があります。”
  • 6.5.2 ハイパーパラメータの最適化
    • 結局探索するだけw
    • ニューラルネットワークのハイパーパラメータの最適化では、グリッドサーチ などの規則的な探索よりも、ランダムにサンプリングして探索するほうが良い 結果になることが報告されています [15] 。これは、複数あるハイパーパラメー タのうち、最終的な認識精度に与える影響度合いがハイパーパラメータごとに 異なるからです。”
      • “設定されたハイパーパラメータの範囲から、ランダムにサンプリングする。”
      • ほんとランダムなんだなーw
    • “ハイパーパラメー タの最適化において、より洗練された手法を求めるとすれば、ベイズ最適化(Bayesian optimization)が挙げられるでしょう。ベイズ最適化は、ベイズ の定理を中心とした数学(理論)を駆使して、より厳密に効率良く最適化を行 います。詳しくは、論文「Practical Bayesian Optimization of Machine Learning Algorithms」[16] などを参照してください。”
  • 6.5.3 ハイパーパラメータ最適化の実装
    • “この結果を見ると、うまく学習が進んでいるのは、学習係数が 0.001 から 0.01、 Weight decay 係数が 10−8 から 10−6 ぐらいということが分かります。このように、 うまくいきそうなハイパーパラメータの範囲を観察し、値の範囲を小さくしていき ます。そして、その縮小した範囲で同じ作業を繰り返していくのです。そのようにし て、適切なハイパーパラメータの存在範囲を狭め、ある段階で、最終的なハイパラ メータの値をひとつピックアップします。”
      • こういう作業なら機械にやらせるでいい気がする。。問題はネットワークのノード数とか隠れ層の数とかそっちだと思う。
      • 時間がネックになるという話で、結局ハードの進化に依存してる話だなと改めて思うなー。

従来手法と比べてDeep Learningニューラルネットワークを多層にすることで表現力を増やすことが出来る反面、学習に時間がかかったり勾配損失のような問題が発生するため、その問題を解決するための手法が色々考えられているんだと改めて実感できる章だったと思います。従来手法であれば入力データの特徴抽出に悩むわけですが、Deep Learningはそこは悩まないで良い分、ネットワーク設計と学習自体をどう進めるかについて悩むことになるんだなぁーと。

次回はゼロから作るDeep Learning 7章 畳み込みニューラルネットワーク - n3104のブログです。

Developers.IO 2017に参加しました

先週*1Developers.IO 2017に行ってきました。

Developers.IO 2017|EventRegist(イベントレジスト)

自社イベントなんですが、今回は登壇者ではなかったので普通に一参加者として参加しました*2。残念ながら私用で午前の基調講演は聞けなかったのですが、参加できたセッションはどのセッションも普通におもしろく、改めていいイベントだと思いましたw

ということで、参加したセッションの感想を残しておきたいと思います。

L-1】 ランチセッション

クラスメソッドの代表である横田の会社紹介でした。相変わらず売上とか従業員数とかの数字を赤裸々に話していて、横田さんらしいなーと思いながら聞いてました。最近いらすとやにハマっているのもネタにしていて、一緒にお弁当を食べていた同僚と「またいらすとやだw」と話してました。なお、お弁当はほんと美味しかったです(^q^)

【H-1】 今からはじめるボードゲーム解析

Developers.IO 2017セッション「今からはじめるボードゲーム解析」で話しました #cmdevio2017 | Developers.IO

解析とタイトルにあるようにボードゲームを解析的に解こうとするとこういう感じになるというのが分かって面白かったです。ゲーム木の辺りまで行くと探索の枝刈りみたいな感じかなと思ったり、局面の値は機械にやらせるには数値に落とし込んで比較できるようにする必要があるので、機械学習の損失関数みたいだなーと思ったりしてました。あとは局面の直和の帰結類に未確定のものがあるのは、要はゲーム開始時点で先手と後手のどちらが勝つか確定していないゲームであることを意味するのかなと思ったりしました。

AWSにはまったく関係ない内容でしたが非常におもしろかったので、平田さんには次回もぜひAWSと関係ないテーマで話してもらいたいと思いましたw

【A-2】 基礎からの OAuth 2.0 〜 認証と認可の概念、認可コードとアクセストークンの意味 〜

Developers.IO 2017セッション「基礎からの OAuth 2.0」でお話してきました #cmdevio2017 | Developers.IO

いやー、ほんと分かりやすかったです。認証と認可については都元さんと休憩室で雑談していた内容を思い出したりしつつ、OAuth 2.0自体は特に勉強していなかったので一気に概要把握できたと思います。もともと6年前ぐらいにGoogleTwitterAPIをOAuth 1.0で操作してたりしたことがあるので、その頃の記憶を思い出しながらなるほどなーと思ったりしてました。当時このスライドがあればよかったのにと思いました。OAuthについて学ぶ際は最初にこのスライドを見るといいと思います。

それにしてもOAuth 2.0にはフローが4つもあるのは知らず驚きました。4. Authorization code grantぐらいしか認識してなかったので。3. Implicit grantはまだスマフォアプリがあるので納得ですが、残りの1. Client credentials grantと2. Resource owner password credentials grantを利用するケースがあるのかちょっとイメージがわかなかったです。

【A-3】 開発環境でのDocker活用事例と本番運用に向けて考えたこと

Developers.IO 2017セッション「開発環境でのDocker活用事例と本番運用に向けて考えたこと」で話しました #cmdevio2017 | Developers.IO

Dockerについて詳しくないので聞きました。そういう意味でDockerの基本から説明してくれたのでちょうどよい内容でした。あとは実際に試してみてなので。。

【C-4】 Voice User Interface の歴史と未来

Voice User Interface の歴史と未来 – 人類は VUI にたどり着き、どこへ向かうのか #cmdevio2017 | Developers.IO

VUI(Voice User Interface)が今後どのように発展していくのか知れたらいいなと思って聞きました。タイトル通り歴史ということでCUIGUI、NUIの歴史について説明した上でのVUIの歴史と未来についての話でした。今まで聞いたことがないような話が多く、とても面白かったです。

なんというか、今まで参加した勉強会では聞けないような内容で、こういうセッションが成立するのはDevelopers.IOならではな気がしてとてもいいと思いました。

最後に

企画運営をしてくれたクラスメソッドマーケティング部のみなさま、登壇者のみなさま、同僚のスタッフ各位に感謝です。会場提供してくれたSAPジャパン様にも感謝です。そして、何よりもご来場いただいたみなさまに感謝です。参加者がいなければ企画自体が成立しませんのでm(__)m

次回も一参加者として参加して楽しめればと思ってますw

*1:あれ、まだ1週間しか経ってないんでしたっけ。もうだいぶ前の印象があるんですが。。

*2:一応スタッフ業務もやってましたがちょっとお手伝いする程度で無いに等しいぐらいでした。

ハーバード・ビジネススクールが教える 顧客サービス戦略

ハーバード・ビジネススクールが教える 顧客サービス戦略

ハーバード・ビジネススクールが教える 顧客サービス戦略

CS(Customer Satisfaction)を組織として提供するにはどうすればいいか具体的な方法を知りたいと考えて読みました。主張自体も分かりやすく、個々の主張の説明に実際の企業を事例を多数使って説明しており、読みやすかったです。良書だと思いました。

成功する顧客サービスを構成するものとして以下の4つを原則として挙げており、各章でその詳細と実践方法について説明する構成となっています。その際、複数の企業を例に紹介しているため内容も具体的かつ分かりやすかったです。

  • 原則1 「 すべてが最高」には無理がある
  • 原則2 誰かがコストを負担しなくてはならない
  • 原則3 悪いのはスタッフではない
  • 原則4 顧客をマネジメントせよ

前述の原則は普段の仕事でも意識していたことなので、非常に共感したというか、自分自身の考えはあっていたのかなという点で自信をもらえました。私自身は精神論みたいなものは持続性がないので、個人に依存せず、組織として再現性のある仕組みを作る必要があるという考え方です。また資源は有限なので、取捨選択(特にやらないことを決めるというか増やすというか)が重要という考え方です。その辺りの考え方が近い内容だったので特に違和感なく読めました。

ゼロから作るDeep Learning 5章 誤差逆伝播法

前回はゼロから作るDeep Learning 4章 ニューラルネットワークの学習 - n3104のブログです。

5章です。Deep Learningで有名な(?)誤差逆伝播法(Backpropagation)について学びます。4章で数値微分による勾配降下法を利用してニューラルネットワークで学習する方法について学びました。ただ、数値微分は計算コストが大きいのでより効率的に勾配を算出できる誤差逆伝播法を利用するそうで、この章ではその誤差逆伝播法の仕組みと実装方法について説明しています。

  • まえがき
    • “ひとつは「数式」によって、もうひとつは「計算グラフ(computational graph)」によって理解するというものです。前者のほうが一般的な方法で、特に、機 械学習に関する書籍の多くでは、数式を中心に話を展開していきます。確かに、数式 による説明は、厳密で簡潔になるのでもっともな方法なのですが、いきなり数式を中 心に考えようとしたら、本質的なことを見逃してしまったり、数式の羅列にとまどっ たりすることがあります。そこで本章では、計算グラフによって“視覚的”に誤差逆 伝播法を理解してもらおうと思います。実際にコードに書くことでさらに理解が深ま り「なるほど!」と納得できると思います。”
      • 計算グラフによる理解方法もあるんだー。
  • 5.2.3 連鎖律と計算グラフ
    • zの部分の偏微分を求めているのがちょっとピンとこない。。後の項で出てくるのかなー。 ← 5.3.1 を見た感じ、右端も常に微分する必要はあって、今回は終端だったというだけかな。
  • 5.4 単純なレイヤの実装
  • 5.4.1 乗算レイヤの実装
    • 微分(dout)
      • これ、多分 delta out なのかなー。
        • “また、backward() の引数は、「順伝播の際の出力変数に対する微分」を入力する”
        • あってるっぽい。
  • 5.4.2 加算レイヤの実装
    • こんなに簡単に実装できるんだなー。。
  • 5.5 活性化関数レイヤの実装
    • 読めば分かるし、式変換もぎり出来た。。
    • (5.12) 式は単純に第二項は y そのもので、第三項は (1 - y) に変換できると言うだけ。
  • 5.6.1 Affine レイヤ
    • ニューラルネットワークの順伝播で行う行列の内積は、幾何学の分野では「アフィン変換」と呼ばれます。そのため、ここでは、アフィン変換を行う処理を 「Affine レイヤ」という名前で実装していきます。”
      • なぜAffineレイヤと呼ばれるかというと行列の内積の計算がアフィン変換に含まれるからということらしい。
    • “ただし X、W、B は行列(多次元配列) であるということに注意しましょう。これまで見てきた計算グラフは「スカラ値」が ノード間を流れましたが、この例では「行列」がノード間を伝播します。”
      • そっか。ここからはノード単位からレイヤ単位に拡張されてるのか。 ← 違ってた。ニューラルネットワークの場合は前のレイヤーの全ノードの出力が単一のノードの入力になるので行列に拡張されているだけだった。図3-17 入力層から第 1 層目への信号の伝達みたいな感じ。なお、行列に拡張と言ってもこの時点では W のみで、X と B はベクトルにすぎない。X が行列に拡張されるのは次節の 5.6.2 バッチ版 Affine レイヤで、そこでも B についてはベクトルのまま。
    • “(式 (5.13) が導かれる過程はここでは省略します)”
      • さすがにそろそろ導出過程は省略するようになってきたか。。
      • 導出過程はないけど、要は掛け算なので 5.4.1 乗算レイヤの実装 と同じ考え方でそれぞれ掛けるものが入れ替わり、かつ、行列なので掛ける順序が式の通りになってるって感じかなー。個々の要素を展開して数式眺めれば納得するのかなー。。
      • 式展開してたら X が横ベクトルじゃないと計算できないはずなのに W は行列で shape の値が表示されてて混乱してきた???
        • いろいろ調べた結果、nparray は1次元の場合はベクトルに縦とか横とかの概念がないそうで、np.transpose しても縦/横は変換されず、np.dot の場合は片方が二次元の行列でもう片方が一次元のベクトルなら縦横関係なく計算てくれることが判明した。これは補足に書いてくれてもいい気もするんだけど、3.3 多次元配列の計算 では説明してなかった。。
        • numpyの1d-arrayを2d-arrayに変換 - keisukeのブログ
          • http://kaisk.hatenadiary.com/entry/2014/09/20/185553
          • “numpyはベクトルと行列を分けているので*1,ベクトルの転置が取れなくて困る.
          • n次元ベクトルxは,numpyでは行ベクトルでも列ベクトルでもない.単にn次元ベクトル."
        • numpy.dot — NumPy v1.12 Manual
>>> X = np.random.rand(2)
>>> X
array([ 0.19710846,  0.80155544])
>>> W = np.random.rand(2,3)
>>> W
array([[ 0.82160224,  0.64742996,  0.16274919],
       [ 0.82007676,  0.46175103,  0.96207937]])
>>> np.dot(X, W)
array([ 0.81928174,  0.49773297,  0.8032392 ])
>>> np.dot(W, X)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: shapes (2,3) and (2,) not aligned: 3 (dim 1) != 2 (dim 0)
>>> np.dot(np.transpose(W), X)
array([ 0.81928174,  0.49773297,  0.8032392 ])

# X への np.transpose は意味がない
>>> np.dot(np.transpose(X), W)
array([ 0.81928174,  0.49773297,  0.8032392 ])
>>> np.dot(np.transpose(W), np.transpose(X))
array([ 0.81928174,  0.49773297,  0.8032392 ])

# reshape して行列に変換すれば np.transpose できる
>>> X.shape
(2,)
>>> np.transpose(X).shape
(2,)
>>> X.reshape(1,2).shape
(1, 2)
>>> np.transpose(X.reshape(1,2)).shape
(2, 1)
>>> np.dot(X.reshape(1,2), W)
array([[ 0.81928174,  0.49773297,  0.8032392 ]])
>>> np.dot(np.transpose(X.reshape(1,2)), W)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: shapes (2,1) and (2,3) not aligned: 1 (dim 1) != 2 (dim 0)
  • 5.6.2 バッチ版 Affine レイヤ
    • “これまで説明してきた Affine レイヤは、入力である X はひとつのデータを対象と したものでした。ここでは N 個のデータをまとめて順伝播する場合、つまり、バッ チ版の Affine レイヤを考えます”
      • ??? N 個のデータをまとめてというのは 5.6.1 Affine レイヤ まではレイヤではなくてやはりノードの話で、バッチ版でレイヤにまで拡張されてる? ← これは4.2.3 ミニバッチ学習のバッチという意味だった。
  • 5.6.3 Softmax-with-Loss レイヤ
    • 図5-28 を見るとAffineもReLUもSoftmaxも全てレイヤになっている。。もうちょっと先まで読み進まないと理解できないのかなー。
    • 書いてあることは分かるんだけど、こう、なんというか納得感がないというか、結局どう使うのか分からないというか。。
    • “伝播する値をバッチの個数(batch_size)で割ることで、データ 1 個あたりの 誤差が前レイヤへ伝播する点に注意しましょう。”
      • バッチ処理なので一度に複数のデータを処理するので1回のバッチの変化量のオーダーを揃えようとしてるでいいんだっけ?
      • 4.2.3 ミニバッチ学習 に書かれているとおりで、"ただし、最後に N で割って正規化しています。こ の N で割ることによって、1 個あたりの「平均の損失関数」を求めることになりま す。そのように平均化すれば、訓練データの数に関係なく、いつでも統一した指標が 得られます。たとえば、訓練データが 1,000 個や 10,000 個の場合であっても、1 個 あたりの平均の損失関数を求められます。"だかららしい。ここは式 (4.3) だけなら納得なんだけど、SoftmaxWithLoss の実装では損失を合計してないんだよなー。。
      • 4.5 学習アルゴリズムの実装 に書かれているように1回の学習で更新するパラメータのオーダーをバッチサイズに関係なく一定にするってことかなー。。
      • 合計のオーダーを揃えようとしてるでいいみたい。合計する箇所はAffineレイヤだった。5.6.2 バッチ版 Affine レイヤ の 図5-27 バッチ版 Affine レイヤの計算グラフ を見ると分かるが、W については内積なのでバッチの個数分、つまり N 個が足される。なので、あらかじめ batch_size つまり N で割っておくとオーダーが揃うことになる。実装は 5.7 誤差逆伝播法の実装 の train_neuralnet.py で確認できる。ここで batch_size を減らすと N が変わることを layers.py でデバッグプリント入れると確認できる。

train_neuralnet.py

# batch_size = 100
batch_size = 3

layers.py

    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        # 以下、追加したデバッグプリント
        print(self.x.T.shape)
        print(dout.shape)
        print(self.dW.shape)
        self.db = np.sum(dout, axis=0)

出力

(50, 3)
(3, 10)
(50, 10)
(784, 3)
(3, 50)
(784, 50)
  • 5.7 誤差逆伝播法の実装
    • この節で今まで出てきたものの全体における位置づけが分かるっぽい。
  • 5.7.1 ニューラルネットワークの学習の全体図
    • “これまで説明した誤差逆伝播法が登場するのは、ステップ 2 の「勾配の算出」で す。前章では、この勾配を求めるために数値微分を利用しましたが、数値微分は簡単 に実装できる反面、計算に多くの時間がかかりました。誤差逆伝播法を用いれば、時 間を要する数値微分とは違い、高速に効率良く勾配を求めることができます。”
      • なるほど。結局勾配を求める際の計算の効率化が目的なのか。
  • 5.7.2 誤差逆伝播法に対応したニューラルネットワークの 実装
    • self.params[‘b1’] = np.zeros(hidden_size)
      • バイアス項は初期値 0 で初期化するのかー。
    • self.layers = OrderedDict()
      • 本文にも開設のあるとおり順番付きディクショナリらしい。
      • Python本体に含まれている模様。
      • Javaで言うとこの LinkedHashMap みたいなものか。
    • self.layers[‘Relu1’] = Relu()
      • Relu は 5.5.1 ReLU レイヤ で実装していて、ノードかレイヤか分からなかったが、よくよく見直したらレイヤとして実装されてた。なるほど。そして、Relu レイヤは活性化関数のレイヤなのでプログラミングとしてはニューラルネットワークの1つの層を2つのレイヤ(AffineとRelu)で実装してるってことか。
      • 5.4 単純なレイヤの実装 についても改めて見てみたが、こちらはノードを実現するためのレイヤで合ってた。
        • “計算グラフの乗算ノードを「乗算レイヤ(MulLayer)」、加算ノー ドを「加算レイヤ(AddLayer)」という名前で実装することにします。”
      • レイヤという用語は単なる層ではあるが、ニューラルネットワークとしてのレイヤと計算を行うレイヤ(単ノード/複数ノード)があるので、何というか用語の使い分けなりがあっても良かった気はする。 ← 改めて読み直したらいちおう説明はしていた。うーん。。
        • 5.4 単純なレイヤの実装 の最初に断り書きはあった。
          • “ここで言う「レイヤ」とは、ニューラルネットワー クにおける機能の単位です。たとえば、シグモイド関数のための Sigmoid や、 行列の内積のための Affine など、レイヤ単位で実装を行います。そのため、 ここでも「レイヤ」という単位で、乗算ノードと加算ノードを実装します。”
        • 5.5 活性化関数レイヤの実装 の最初でも以下のように層としてのレイヤと記述してはいた。
          • “ここでは、ニューラルネットワークを構成する「層(レイヤ)」をひとつのクラ スとして実装することにします。まずは、活性化関数である ReLU と Sigmoid レイ ヤを実装していきます。”
    • self.lastLayer = SoftmaxWithLoss()
      • predict メソッドでは利用しない。これは 5.6.3 Softmax-with-Loss レイヤ に書いてある。
        • ニューラルネットワークで行う処理には、推論(inference)と学習の 2 つの フェーズがあります。ニューラルネットワークの推論では、通常、Softmax レ イヤは使用しません。たとえば、図5-28 のネットワークで推論を行う場合、 最後の Affine レイヤの出力を認識結果として用います。なお、ニューラルネットワークの正規化しない出力結果(図5-28 では Softmax の前層の Affine レ イヤの出力)は、「スコア」と呼ぶことがあります。つまり、ニューラルネット ワークの推論で答えをひとつだけ出す場合は、スコアの最大値だけに興味があ るため、Softmax レイヤは必要ない、ということです。一方、ニューラルネッ トワークの学習時には、Softmax レイヤが必要になります。”
    • 実際に動かして動作を確認するには 5.7.4 誤差逆伝播法を使った学習 で登場する train_neuralnet.py を利用する。
  • 5.7.3 誤差逆伝播法の勾配確認
    • “さて、数値微分は計算に時間がかかります。そして、誤差逆伝播法の(正しい)実 装があれば、数値微分の実装は必要ありません。そうであれば、数値微分は何の役に 立つのでしょうか? 実は、数値微分が実践的に必要とされるのは、誤差逆伝播法の 実装の正しさを確認する場面なのです。数値微分の利点は、実装が簡単であるということです。そのため、数値微分の実装 はミスが起きにくく、一方、誤差逆伝播法の実装は複雑になるためミスが起きやすいの が一般的です。そこで、数値微分の結果と誤差逆伝播法の結果を比較して、誤差逆伝 播法の実装の正しさを確認することがよく行われます。”
      • なるほどなー。
    • 数値微分は計算に時間が掛かるということだったけど、確かに数値微分はパラメータ毎の勾配を求めるためにはネットワーク全体を2回順伝播することになるのに対して、誤差逆伝播法の場合は順伝播と逆伝播の合計 2 回だけで演算終わるもんなー。なので、数値微分自体の計算が重いというよりは、数値微分の場合はレイヤ数に比例して計算量が増えるので重いと言った方が適切なんだろうな。
    • x_batch = x_train[:3]
>>> x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> x[:3]
array([0, 1, 2])
>>> y = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> y[:3]
[0, 1, 2]

5章については最初はレイヤの定義が曖昧に見えて悩みました。結局最後まで読み進めた上でソースにデバッグプリント入れながら動きを確認することで、最終的には理解できてとてもスッキリしました。また4章までの部分も何度か読み直したので、5章は4章までを理解していないと読んでいてスッキリしないかもしれません。

次回はゼロから作るDeep Learning 6章 学習に関するテクニック - n3104のブログです。

プラットフォーム ブランディング

プラットフォーム ブランディング

プラットフォーム ブランディング

IoTの成功事例、コマツの「KOMTRAX」は何がすごいのか : 市況かぶ全力2階建を偶然読んでいた際に紹介されていたので読んでみました。ブランド戦略について体系立てて説明しており、実践における留意点も紹介されていて良書だと思いました。

プラットフォームというと仕事柄身近なのはアマゾン(AWS)やアップル(iOS)だったりするんですが、コマツのKOMTRAXも確かにプラットフォームだと思ったので、そういうプラットフォームの作り方とか仕組みが書いてあるかなと期待して読んでみました。実際にはブランド戦略が主題で、プラットフォームについてはあくまでもブランド戦略の要素として紹介していました。なお、コマツのKOMTRAXについては10行程度しか書かれてませんでしたw

あとがきにも書かれている通りもともとブランディングに関わっている現場担当者向けではなく、(ブランド戦略に理解の浅い)経営者向けに書かれているということもあり、ブランド戦略に登場する概念について基本的な定義から個々の事例の分析、実践方法から歴史まで紹介してくれていて読みやすかったです。また、それぞれのテーマについて深掘りするための書籍も紹介しており、まずはこの本を読んで全体像を掴み、その上で個別のテーマを学ぶのが効率がよさそうに思いました。

本書でも触れられていることですが、色々なものがコモディティ化する中で、ブランド戦略による差別化は必須の要素になってるんだなーと実感しました。今後、仕事において色々考える際に役に立ちそうな印象を持ちました。

ゼロから作るDeep Learning 4章 ニューラルネットワークの学習

前回はゼロから作るDeep Learning 3章 ニューラルネットワーク - n3104のブログです。

4章です。3章では学習済みのモデルを使いましたが、4章ではそのモデルの学習方法について学びます。といっても、実際にニューラルネットワークで学習する際は4章で学ぶ数値微分ではなく5章で学ぶ誤差逆伝播法を利用します。なので、モデルの学習方法というよりはモデルを学習する仕組みについて学ぶと言ったほうが適切かもです。

  • 4.1.1 データ駆動
    • 機械学習はデータが命です。”
      • ほんとその通り。
    • もちろん特徴量の抽出は考えなくて良くなるという建前だけど、元のデータが役に立つ情報を含んでいなければ当然いけないし、ネットワークの設計は残ると思うのだけど。。
      • 画像の識別なら同じネットワークが使えるのかなー。でも、画層の種類は問題によって異なるだろうし、画像の大きさとか階調でも違う気がするんだけど。。
  • 4.2 損失関数
    • 4.1.2 訓練データとテストデータでの汎化能力の説明にあるように1節で1つの概念を説明するようにする構成はとてもいいと思うんだけど、「幸せ指標」はメタファとしてはイケてない気が。。テストの点数ぐらいで良かった気がするんだけどw
  • 4.2.2 交差エントロピー誤差
    • 2 乗和誤差と比べて交差エントロピー誤差はクラスラベルの次元のみ利用するから、分類問題にしか使えないんだろうなー。その分、はっきりと結果が出るんだろうけど。
    • y = logxのグラフで 0 - 1 の区間は -inf - 0 に対応してるから -log すると 0 - inf になるって言うわけかー。
    • “中身の実装では、np.log の 計算時に、微小な値である delta を足して計算しています。これは、np.log(0) の ような計算が発生した場合、np.log(0) はマイナスの無限大を表す-inf となり、そ うなってしまうと、それ以上計算を進めることができなくなります。その防止策とし て、微小な値を追加して、マイナス無限大を発生させないようにしています。”
      • なるほどー。プログラムとして実装する上でのノウハウだなー。
      • delta = 1e-7 となってるけど e-7 が妥当というのはどこから来てるんだろう?
  • 4.2.3 ミニバッチ学習
    • “ただし、最後に N で割って正規化しています。こ の N で割ることによって、1 個あたりの「平均の損失関数」を求めることになりま す。そのように平均化すれば、訓練データの数に関係なく、いつでも統一した指標が 得られます。たとえば、訓練データが 1,000 個や 10,000 個の場合であっても、1 個 あたりの平均の損失関数を求められます。”
      • なるほど。
  • 4.2.4 [バッチ対応版]交差エントロピー誤差の実装
    • cross_entropy_error 関数は分かりやすくするためだろうが delta によるエラー回避のコードが省略されているので、実際に実行するとエラーになったw
    • ndim は配列の次元数だった。
    • reshape を使うことで配列の次元数を変換することも可能。なのでここでは入力が一次元配列の場合に二次元配列に変換している。
    • y.shape[0] はレコード数になる。
    • np.log( y[np.arange(batch_size), t] ) の所は正解ラベルが配列のインデックスと一致するラベル名だから動作するわけであって、他のラベルだと動作しないのだから紹介するのはどうなんだろう。。 ← [2017-04-20 追記] 一般的にエンコードするから問題なかった。

ndimについて

>>> np.array([1, 2]).ndim
1
>>> np.array([1, 2]).shape
(2,)
>>> np.array([[1, 2],[3,4]]).ndim
2
>>> np.array([[1, 2],[3,4]]).shape
(2, 2)
>>> np.array([[1, 2],[3,4]]).reshape(4,1)
array([[1],
       [2],
       [3],
       [4]])
>>> np.array([[1, 2],[3,4]]).reshape(1,4)
array([[1, 2, 3, 4]])
>>> np.array([[1, 2],[3,4]]).reshape(1,4).ndim
2
>>> np.array([[1, 2],[3,4]]).reshape(4,1).ndim
2
>>> np.array([[1, 2],[3,4]]).reshape(4)
array([1, 2, 3, 4])

y[np.arange(batch_size), t] について

>>> # 3行分のダミーデータを作る。
... y = softmax(np.random.rand(3, 10))
>>> y
array([[ 0.07759622,  0.08309147,  0.09904634,  0.07516991,  0.06775452,
         0.13294043,  0.11345575,  0.07123801,  0.150684  ,  0.12902336],
       [ 0.10928588,  0.09100464,  0.09566815,  0.05923705,  0.0791873 ,
         0.08125782,  0.13062538,  0.08983766,  0.14875257,  0.11514354],
       [ 0.10586304,  0.13518227,  0.1316193 ,  0.12738668,  0.06843716,
         0.05849982,  0.13193427,  0.11540083,  0.05508124,  0.07059538]])
>>>
>>> t = np.array([2, 5, 7])
>>> batch_size = y.shape[0]
>>> # [0, 2], [1, 5], [2, 7] のデータが抽出されることが分かる。
... y[np.arange(batch_size), t]
array([ 0.09904634,  0.08125782,  0.11540083])
>>>
>>> y[0, 2]
0.099046337771526632
>>> y[1, 5]
0.081257822450627418
>>> y[2, 7]
0.11540082864090417
  • 4.2.5 なぜ損失関数を設定するのか?
    • “「認識精度」を指標にすべきではないか”
      • 実際に認識精度を指標としてパラメータのチューニングってできるんだっけ?
    • ニューラルネットワークの学習の際に、認識精度を“指標”にしてはいけない。 その理由は、認識精度を指標にすると、パラメータの微分がほとんどの場所で 0 になってしまうからである。”
      • “つまり、パラメータの少しの調整だけでは、認識 精度は改善されず一定のままなのです。もし認識精度が改善されたとしても、その値 は 32.0123…% のような連続的な変化ではなく、33% や 34% のように、不連続のと びとびの値へと変わってしまいます。一方、損失関数を指標とした場合、現在の損失 関数の値は 0.92543…のような値によって表されます。そして、パラメータの値を少 し変化させると、それに反応して損失関数も 0.93432…のように連続的に変化するの です。 ”
      • 書いてあることはその通りなんだろうけど、いまいち腑に落ちないなー。
    • “活性化関数の「ステップ関 数」にも同じ話が当てはまります。”
      • “ステップ関数は「ししおどし」のように、ある瞬間だけ変化を起こす関数でした が、一方、シグモイド関数の微分(接線)は、図4-4 に示すように、出力(縦軸の値) が連続的に変化し、さらに、曲線の傾きも連続的に変化します。つまり、シグモイド 関数の微分はどの場所であっても 0 にはならないのです。これは、ニューラルネット ワークの「学習」において重要な性質になります。この性質――傾きが 0 にはならな い――によって、ニューラルネットワークは正しい学習が行えるようになります。”
      • ここはわりと腑に落ちた。要は極端になるってことなのかなー。。
  • 4.3.1 微分
    • “ここで行っているように、微小な差分によって微分を求めることを数値微分 (numerical differentiation)と言います。一方、数式の展開によって微分を求めることは、解析的(analytic)という言葉を用いて、たとえば、「解析的に 解く」とか「解析的に微分を求める」などと言います。たとえば、y = x2 の微 分は、解析的には、dy = 2x として解くことができます。”
    • 数値微分は中心差分を使うだけでいいんだ。なんて単純なんだ。。!
    • 実際に前方差分のみと中心差分の場合とで比べてみたら、前方差分のみは 0 になってしまったw
def numerical_diff1(f, x):
    h = 10e-50
    return (f(x+h) - f(x)) / h


def numerical_diff2(f, x):
    h = 1e-4 # 0.0001
    return (f(x+h) - f(x-h)) / (2*h)


def function_1(x):
    return 0.01*x**2 + 0.1*x


print(numerical_diff1(function_1, 5))
print(numerical_diff1(function_1, 10))
print(numerical_diff2(function_1, 5))
print(numerical_diff2(function_1, 10))

出力

0.0
0.0
0.1999999999990898
0.2999999999986347
  • 4.3.2 数値微分の例
    • 4.3.1 微分 を確認する際に少し読んでいたので特に気になる点はなかった。
  • 4.3.3 偏微分
    • 説明は分かるんだけど、初めて偏微分見た人は理解が追いつくのかな。。
    • 特に説明してないけど、numerical_diff を使って偏微分を実装している。これで偏微分が出来るのは偏微分する変数以外は定数になってるからだよなー。結果的に偏微分の対象となる変数の微分、つまり微小な変化の極限が出てる。
  • 4.4 勾配
    • “すべての変数の偏微分をベクトルとしてまとめた ものを勾配(gradient)と言います。”
      • そうだったのか!
    • “ひとつ補足として述べるとすれ ば、np.zeros_like(x) は、x と同じ形状の配列で、その要素がすべて 0 の配列を 生成するということです。”
      • いや、そこよりも色々補足必要な気がするw
      • numerical_gradient で勾配が出せるのは、次元毎に中心差分を取ってるから。要は numerical_diff のロジックを再実装している。対象となる次元以外の次元は同じ値になるので結果的に相殺され、対象となる次元の微分、つまり偏微分を出せる。
      • 引数となる x に再代入しているけれど、最後に tmp_val を代入しているのでもとに戻る。とはいえ、処理中に例外発生したら呼び出し元の配列の中身が書き換わるから、この実装方法は結構微妙な気がするんだけど。。機械学習の場合は巨大な行列を扱うこともあるからメモリ節約の観点でわざわざコピーせずにやるのかなー?
    • “この勾配は何を意味しているのでしょうか? そ れを理解するために、f (x0 , x1 ) = x20 + x21 の勾配を図で表してみることにしましょ う。ただし、ここでは勾配の結果にマイナスを付けたベクトルを描画します”
      • マイナスつけてるなら納得。
    • “勾配が示す方向は、各場所において関数の値を最も減らす方向”
      • マイナス付けた場合という但し書きはあってもいい気もするが。要は傾きなので単純な勾配は減らす方向にならない。
  • 4.4.1 勾配法
    • “また、関数が複雑で歪な形をしていると、(ほとんど)平らな土地に入 り込み、「プラトー」と呼ばれる学習が進まない停滞期に陥ることがあります。”
      • なるほどー。確かにこういうケースもありそう。
    • “勾配法は、目的が最小値を探すことか、それとも最大値を探すことかによって 呼び名が変わります。正確には、最小値を探す場合を勾配降下法(gradient descent method)、最大値を探す場合を勾配上昇法(gradient ascent method)と言います。ただし、損失関数の符号を反転させれば、最小値を探す 問題と最大値を探す問題は同じことになるので、「降下」か「上昇」かの違いは 本質的には重要ではありません。一般的に、ニューラルネットワーク(ディー プラーニング)の分野では、勾配法は「勾配降下法」として登場することが多 くあります。 ”
      • 勾配上昇法というものあるんだねー。でも、上昇法だと無限を目指す必要があるから降下法を使うのかなー。 ← [2017-05-19 追記] そういう意味ではなくて上に凸なら勾配上昇法で下に凸なら勾配降下法というだけだった。そもそも、下に凸でもそこが 0 とは限らず、どこまで深いかは関数次第。
    • ニューラルネットワークの学習においては、学習率の値を変更し ながら、正しく学習できているかどうか、確認作業を行うのが一般的です。”
      • 学習率というハイパーパラメーターも残ってたか。。
      • “学習率のようなパラメータはハイパーパラメータと言います。これは、ニュー ラルネットワークのパラメータ――重みやバイアス――とは性質の異なるパラ メータです。なぜなら、ニューラルネットワークの重みパラメータは訓練デー タと学習アルゴリズムによって“自動”で獲得されるパラメータであるのに対し て、学習率のようなハイパーパラメータは人の手によって設定されるパラメー タだからです。一般的には、このハイパーパラメータをいろいろな値で試しな がら、うまく学習できるケースを探すという作業が必要になります。 ”
    • gradient_method.py はカレントディレクトリが ch04 であることを前提としていた。これをIntelliJでやる手順は以下の通り。
      • File -> Project Structure… (Cmd+;)
      • Modules -> Sources タブで ch01…ch08 を選択状態にして Sources ボタンで追加
    • “この実験の結果が示すように、学習率が大きすぎると、大きな値へと発散してしま います。逆に、学習率が小さすぎると、ほとんど更新されずに終わってしまいます。”
      • gradient_descent 関数は init_x の中身を書き換える実装になってた。なので、実際は書き換えない実装にしないと駄目な気もするけど、機械学習の界隈ではそういう割り切りなのかなー。
      • 実際にいくつか試したが、今回だと 1.0 を超えると大きすぎて発散していく。
# coding: utf-8
import numpy as np
import matplotlib.pylab as plt
from gradient_2d import numerical_gradient


def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    x_history = []

    for i in range(step_num):
        x_history.append( x.copy() )

        grad = numerical_gradient(f, x)
        x -= lr * grad

    return x, np.array(x_history)


def function_2(x):
    return x[0]**2 + x[1]**2

init_x = np.array([-3.0, 4.0])    

lr = 0.1
step_num = 20
x, x_history = gradient_descent(function_2, init_x, lr=lr, step_num=step_num)

plt.plot( [-5, 5], [0,0], '--b')
plt.plot( [0,0], [-5, 5], '--b')
plt.plot(x_history[:,0], x_history[:,1], 'o')
print(init_x)

init_x = np.array([3.0, 4.0])
lr = 0.95
x, x_history = gradient_descent(function_2, init_x, lr=lr, step_num=step_num)
plt.plot(x_history[:,0], x_history[:,1], 'o')
print(init_x)

init_x = np.array([3.0, -4.0])
lr = 0.01
x, x_history = gradient_descent(function_2, init_x, lr=lr, step_num=step_num)
plt.plot(x_history[:,0], x_history[:,1], 'o')
print(init_x)

plt.xlim(-3.5, 3.5)
plt.ylim(-4.5, 4.5)
plt.xlim(-5.5, 5.5)
plt.ylim(-5.5, 5.5)
plt.xlabel("X0")
plt.ylabel("X1")
plt.show()

f:id:n-3104:20170320165206p:plain

  • 4.4.2 ニューラルネットワークに対する勾配
    • 項のタイトル通り、本当に勾配を1回出すだけで、学習しない。1項1テーマに絞ってるな、ほんと。
    • ネットワークの形は図示したほうがいい気がするけど、それは次節以降で表記してるのかなー。結局、何を求めたかイメージわかない人もいるのでは。
      • 最後まで確認したが、4章の範囲ではネットワークの形は図示しないんだなー。
    • W は np.random.randn で生成しているので、当然毎回結果が異なるwとはいえ、x と t は固定だからある程度傾向は似るんだねー。
    • 一通りコードを追いかけ直したけど、ここのサンプルソースはすごい分かりにくいのでは。。普通に f の引数 W を利用する損失関数にした方が直感出来だった気がする。
      • W に関する勾配を出すわけだから損失関数は W に関する関数である必要がある。で、loss 関数は内部で predict 関数を実行するので、結果的に loss 関数は W に関する関数であると言える。なので、勾配自体は出せる。
        • numerical_gradient 関数で勾配が出せるのは、第2引数が net.W だから。そのため、predict 関数で利用する W の参照が得られるので、numerical_gradient 関数内で前方差分と後方差分を出す際に W の参照を経由して微小量 h が増減する。。
        • “(ここで定義した f(W) という関数の引数 W は、ダミーとし て設けたものです。これは、numerical_gradient(f, x) が内部で f(x) を実行す るため、それと整合性がとれるように f(W) を定義しました)”
          • そうではなくて、ここで net.W を渡さないとそもそも W を変化させて損失関数 f を実行できないから勾配が取得できない。ここは、途中で筆者が混乱したのかなー。simpleNet のコンストラクタで W を指定するのではなくて、predict と loss 関数の引数に W があってもいい気がするんだけど。まぁ、そのなるとクラスにする意味もなくなるけどねー。。
            • ここについては、ほんとにダミーでもよかった。4.5.1 で self.params でアクセスしてた。。。
  • 4.5 学習アルゴリズムの実装
    • 学習について分かりやすくまとまっている。ただ、機械学習について初めてこの本で学ぶ人はどこまで理解できるんだろう。。
  • 4.5.1 2 層ニューラルネットワークのクラス
    • numerical_gradient を見てて思うが、こういう感じで勾配というか W と b の偏微分出せちゃうんだねー。。実際、loss 関数の中で predict 関数を呼び出していて、その中で y を出すために W と b を使って何度か計算してるけど、これを1つの数式にできちゃうもんなー。で、それぞれの偏微分ができちゃうと考えると、確かに数学そこまで分からなくてもコードレベルで理解できるというか、こう書けばいいというのはわかるなー。。
    • 今後 gradient 関数を使うし、numerical_gradient だと計算が遅いからといって、若干天下り的な感じもするねw
    • t = np.random.rand(100, 10) # ダミーの正解ラベル(100 枚分)
      • これ、正解ラベル自体が確率分布っぽくなっちゃうけど、それは問題ないんだっけ?
        • MNISTのデータは1つだけ 1 で他は 0 だった。だよねー。。
>>> np.random.rand(3, 10)
array([[ 0.65365022,  0.63623433,  0.70117989,  0.89724433,  0.82817134,
         0.31230608,  0.34446204,  0.23931909,  0.13342318,  0.67355462],
       [ 0.85156144,  0.10901498,  0.02327781,  0.69283457,  0.13562442,
         0.09658677,  0.32591785,  0.00592776,  0.38647803,  0.91804246],
       [ 0.86498541,  0.03354259,  0.69663751,  0.01011192,  0.1069335 ,
         0.30273103,  0.8269463 ,  0.4885381 ,  0.20540422,  0.9368264 ]])
  • 4.5.2 ミニバッチ学習の実装
    • train_neuralnet.py のソースが描画するグラフは loss ではなくて訓練データとテストデータの正解率の推移だったw
      • loss を表示するようにコードを書いてみたけど、最初から loss の値は 2 ぐらいからスタートしてた。書籍中のグラフはどこから持ってきたw?
  • 4.5.3 テストデータで評価
    • “エポック(epoch)とは単位を表します。1 エポックとは学習において訓練 データをすべて使い切ったときの回数に対応します。たとえば、10,000 個の 訓練データに対して 100 個のミニバッチで学習する場合、確率的勾配降下法を 100 回繰り返したら、すべての訓練データを“見た”ことになります。この場 合、100 回= 1 エポックとなります。”
      • 分かりやすい。要は訓練データを一通り学習し終えたら1エポック。で、イテレーション数自体はエポックというか訓練データのサイズは考慮しないでバッチサイズと一緒に指定するっぽい。言われてみると、いままでもそんな感じだったかも。
    • train_neuralnet.py は 4.5.3 のソースということか。

個人的には数値微分の実装で興奮しました。こんな簡単に実装できるんですね!

次回はゼロから作るDeep Learning 5章 誤差逆伝播法 - n3104のブログです。