深層学習メモ:代表的なCNNアーキテクチャの流れ

ここでは、代表的なCNNのアーキテクチャの進化の流れについて記す。

AlexNet

2012年のILSVRCにおいて、他のアーキテクチャに圧倒的な差をつけて優勝し、深層学習モデルのブームの端緒になったモデル。
f:id:tibet:20220115215211p:plain
引用元:"ImageNet Classification with Deep Convolutional Neural Networks"
全体としては、畳み込み層を5層積み、間にmax poolingをはさみ、最後に全結合層を3層積んで出力を得る。

最適化は、weight decayとモーメンタム付きの確率的勾配降下法で行われている。エラーレートが改善しなくなった時点で、学習率は1/10にしている。

アーキテクチャの特徴としては以下である。

ReLu

勾配消失問題を回避するため、活性化関数としてReLuを使用している。

Local Response Normalization

空間的に同じ位置にある隣接したカーネルマップの出力の値から自身の出力を正規化する手法。近年ではあまり使われていない

Overlapping Pooling

AlexNetでは、Max poolingを用いているが、集約するピクセル領域をわずかにオーバーラップしたものを使用している。

DropOut

DropOutは、学習時に一定の割合でランダムに中間層を外す手法である。過学習を押さえて、より汎化したモデルを作成することが出来る。

ZFnet

2013年の"Visualizing and Understanding Convolutional Networks"で提案されたネットワーク。
CNNの可視化を行ったうえで、AlexNetの次の課題を解決するアプローチをとった。
・第一層のフィルタは極端に高い周波数と低い周波数の情報で成り立っており、その中間の周波数情報がほとんどない。
・第二層の可視化からは、第一層の畳み込みで使用されている4という大きなストライドによって、エイリアシングが発生している。

そこで、この問題を解決するために、下記のアプローチをとった。
1.第一層のフィルタサイズを11×11から7×7に変更
2.ストライドを4から2に変えた。
その結果、AlexNetを超えるパフォーマンスを示した。
アーキテクチャは下記に示すとおりである。
f:id:tibet:20220117090425p:plain

GoogLeNet

2015年のCVPRの"Going Deeper with Convolutions"で提案されたアーキテクチャで、2014年のILSVRCの優勝モデルである。
このモデルの特徴は以下である。

Inception module

Inceptionアーキテクチャの主な考え方は、畳み込みビジョンネットワークの最適な局所スパース構造を、容易に利用できる密なコンポーネントでどのように近似し、カバーできるかを見つけることに基づいている。
具体的には複数の畳み込み層やpooling層から構成されるinception moduleと呼ばれるマイクロネットワークを定義し、これを積み重ねることで一つの大きなCNNを作り上げている。
f:id:tibet:20220117091647p:plain
インセプションモジュールでは、ネットワークを分岐させてサイズの異なる畳み込みを行った後、それらの出力をつなぎ合わせるという処理を行っている。異なるサイズの畳み込みを独立して行っているため、非0のパラメータ数が大きく減ることになる。
(a)のナイーブバージョンのinception moduleはmax poolingを除くと、5×5の畳み込み一つで表現することもできるが、inception moduleを利用することで、はるかに少ないパラメータで同等の表現能力を持つCNNを構築することが出来る。
また、(b)では1×1の畳み込み層を挿入することで、次元削減を行い、さらにパラメータを削減している。

Global Average Pooling(GAP)

従来のモデルでは、畳み込み層の後に全結合層を積むことによって、出力を得る構造になっていたが、この全結合層はパラメータが多く、過学習を起こすことが問題となっていた。

GAPでは、チャネルにわたってaverage poolingをする(すなわち出力は1×1×チャネル数のテンソルとなる)。
GoogLeNetではCNNの畳み込み層の後、全結合層を利用することなくGAPを採用することで最終的な出力を得ることを提案している。
そのため、パラメータ数を大きく削減し、過学習を防ぐことが出来る。

Auxilirary Loss

GoogLeNetの全体像を下記に示す。
出典:
https://arxiv.org/pdf/1409.4842.pdf
f:id:tibet:20220117093527p:plain
途中で分岐しているが、このサブネットワークでもクラス分類をしており、Auxiliary Lossを追加することが行われている。
これにより、ネットワークの中間層に直接誤差を伝搬させることで、勾配消失を防ぐとともにネットワークの正則化を実現している。
Auxiliary Lossを導入することで計算量を抑えることは期待できないが、アンサンブル学習と同様な効果を得られるため、汎化性能の向上は期待できる。

VGGNet

2014年のILSVRCで2位の認識精度を達成したモデルである。
2015年にVery Deep Convolutional Networks for Large-Scale Image RecognitionとしてCVPRにて発表された。

この論文では、CNNの深さがどのように性能に影響するかを研究するため、下記のようなアーキテクチャの設計方針をとった。
・3×3の畳み込みのみを利用する
・同一出力チャネル数の畳み込み層をいくつか重ねた後にmax poolingによる特徴マップを半分に縮小
・max poolingの後の畳み込み層の出力チャネル数を2倍に増加。

この方針でCNNの深さを増加させていくと、コンスタントに精度が改善した。

AlexnetやZFNetで使用されていたLRNは、VGGnetのような深いネットワークではあまり効果がないため、利用されていない。

ResNet

Resiual Networks(ResNet)は、2015年のILSVRCの優勝モデルである。その後、2015年のCVPRでDeep Residual Learning for Image Recognitionとして発表された。
ResNetでは、処理ブロックをショートカットして入力を次段に渡すResidualモジュールを採用したことが特徴である。これによって、誤差逆伝搬時にも勾配が直接下層に伝わっていくことになり、非常に深いネットワークにおいても効率的に学習できるようになった。

Residual module

下記がResidual Moduleのビルディングブロックである。
出典:
https://arxiv.org/pdf/1512.03385.pdf
f:id:tibet:20220117133914p:plain
入力を処理ブロックF(x)への入力経路とスキップして次段に直接入力する経路に分けるため、次段への入力は下記のようになる。
 H(x)=F(x)+x
このような構造をIdentity Mappingとも呼ぶ。
f:id:tibet:20220117134928p:plain
上記の左側は、実際に使われているresidual moduleの構造で、出力チャネル64の3×3の畳み込み層が2つ配置されている。
正確にはBatch nomalizationとReLuが配置されている。
右側はbottleneckと呼ばれるもので、1×1の畳み込みで次元削減を行った後に3×3の畳み込みを行い、その後さらに1×1の畳み込みで次元を復元するという形をとることで左側と同等の計算量を保ちながら、より深いモデルを構築することが出来る。

Batch Nomalization

深いネットワークでは、ある層のパラメータ更新によって、その次の層の入力の分布が
バッチごとに大きく変化してい住まう内部共変量シフトが発生し、学習が進まない問題があった。Batch nomalizationでは、この内部共変量シフトを正規化して各レイヤが独立して学習が行えるようにすることで、学習を安定化・高速化する。

Heの初期化

重みの初期化について、ReLuを活性化関数として利用する場合の適切なスケーリングを理論的に導出した。

SENet

Squeeze-and-Excitation Networks(SENet)は2017年のILSVRCの優勝モデルである。
2018年のCVPRにてSqueeze-and-Excitation Networksとして発表された。
特徴は、特徴マップをチャネルごとに適応的に重みづけをするAttentionの機構を導入したことである。これは、SE(Squeeze-Exitation) Blockで実現されている。

SE Block

出典:
https://arxiv.org/pdf/1709.01507.pdf
f:id:tibet:20220117142400p:plain
上記がSE Blockの概念図である。
SE BlockはSqueezeステップとExcitationステップの2段階が行われる。
Squeezeステップではチャネル依存性に取り組むために、グローバルな空間情報をチャネル記述子に”絞り込む"(squeeze)。
具体的には、H×W×Cの特徴マップに対してGlobal Average pooling1を適用する。
次にexitationステップでは、チャネルごとの依存関係を抽出するために、1×1の畳み込みを適用し、ReLuと再度出力チャネル数Cの1×1の畳み込みを経て最後にシグモイド関数を適用し、チャネルごとの重みを出力する。










\*yu4u氏の畳み込みニューラルネットワークの最新研究動向 (〜2017)を参考に自身の勉強のために書いています。

深層学習メモ 強化学習

ベルマン方程式

動的計画法として知られる数的最適化において、最適化の条件を示す式。
状態 x を外部からの入力 u で制御できると考えているときに、ある種の評価 J の下で u を色々変えてみて、いざ評価 J を最も良くするような u が見つかったときに成り立っているべき方程式である。
強化学習については、下記のようにかける。
 V_{\pi}(s)=\displaystyle\sum_{a}\pi (a|s)\displaystyle\sum_{s'}T(s'|s,a)(R(s|s')+ \gamma V_{\pi}(s'))
ここで、
π:ある時点の方策
s:ある時点の状態
s':次の時点の状態
a:ある時点のアクション
γ:時間による割引率
 V_{\pi}(s):ある状態である方策を選んだ時の価値関数
 \pi (a|s):ある状態から方策πに従ってとる行動確率
 T(s'|s,a):ある状態から次の状態への状態遷移確率
 R(s|s'):ある状態から次の状態に移る時の報酬
 V_{\pi}(s'):次の状態での価値関数
つまり、価値関数は、直近の報酬に1ステップ先の価値関数を足したものである。ただし、方策および遷移確率で未来のとりうる値は枝分かれするので、その期待値をとる。

方策勾配法

 目的関数J(θ)を最大化する方策パラメータベクトルθを勾配から探索する手法。学習率をαとすると、更新式は下記のようにあらわされる。
 \theta_{t+1}=\theta_t+\alpha ∇_θJ(\theta_t)
方策を学習する目的は、エージェントの行動を最適化して機体リターンを最大化することである。そこで、学習開始時の期待リターンとして、その時点での方策  \pi (a|s_0, \theta ) のもとで計算された価値関数Vなので、これを目的関数J(θ)とする。
 J(\theta )=V^{\pi}(s_0)=E\left[ S_0=s_0\right]
この定義をθで微分することで、以下の方策勾配定理を得る。

方策勾配定理

勾配方策定理とは、Q値を用いて、累積報酬を増加させる方策の勾配を求めるための定理である。

 ∇_θJ(θ)\propto E^{\pi}\left[ ∇_{\theta}log\pi (a|s,\theta ))Q_{\pi}(s,a)\right]
J(θ)が平均エピソード長に比例するため、等式ではなく比例関係で結ばれている。

モンテカルロ法

動的行動計画法では、環境のダイナミクスが既知であることが前提となっている。ところが、あらゆる行動に対して起こりうる変化をすべて把握して、それらをすべてモデリング出来るケースは現実には非常に少ない。
そこで、環境のダイナミクスが既知でなくても価値の推定・方策改善を行う手法がモンテカルロ法になる。

強化学習におけるモンテカルロ法では、エピソードに従ってエージェントは行動をとり報酬を得る。各状態で実際に得られた報酬の平均をとることで期待値を計算する。
モンテカルロ法では現在時刻tからエピソード終了までの時間Tまでの報酬の現在価値を求めるので、学習率α、割引率γとして行動状態価値関数は下記のようになる。
 G(S_t,a_t)=\displaystyle \sum_{i=t}^T(\gamma^{i-t}r^i)
 Q(S_t,a_t)←Q(S_t,a_t)+α\left[ G(S_t,a_t)-Q(S_t,a_t)\right]
状態価値関数は、下記のようにあらわせる。
 V(S_t)=max{Q(S_t,a_t)}

TD法

TD法では、予測で修正を行う。そのために正確性はモンテカルロ法より劣るが、修正速度は1行動ごとになるので早い。
そのため、tとt+1の差異を求める。
 Q(s_t,a_t)←Q(s_t, a_t)+α(r_{t+1}+γQ(s_{t+1},a_{a+1})-Q(s_t,a_t))

深層学習の学習:指標など

BLEU

機械翻訳の評価指標の一つでBLEUがある。
この評価方法の前提は、「プロの翻訳者の訳と近ければ近いほど、その機械翻訳の精度は高い」という考え方である。
BLEUは0~1の間で表現され、それを100倍したものをBLEUスコアとして用いる。
目安としては40以上なら高品質と言える。

BLUEスコアは、下記のような式で計算される。
 BLEU=BP\times exp\left(\displaystyle\sum_{n=1}^Nw_nlogp_n\right)
ここで、
 p_n=\dfrac{\sim_i翻訳文iと参照文iで一致したn-gram数}{\sum_i2翻訳文i中の全n-gram数}
 w_n=\dfrac{1}{N}

BLEUでは、機械翻訳が参照翻訳より短い場合は、ペナルティを与え、機械翻訳が参照翻訳より長い場合はペナルティを与えないBP(Brevity penalty)がある。
cを機械翻訳の長さとして、下記のようにあらわされる。
 BP=   \left\{
    \begin{array}{l}
      1 \ \   c\geq r\\
      exp(1-r/c) \ \ c < r
    \end{array}
  \right.

soft plus関数およびsigmoid関数との関係

soft plus関数は、下記のように定義する。
 f(x)=log(1+exp(x))
グラフにすると、下記のような形で、ReLu関数に近いが、連続的につながっていて、全域で微分可能であることが特徴である。
f:id:tibet:20220109161929p:plain

sigmoidとsoft plus関数の関係

シグモイドは下記の関数である。
 σ(x)=\dfrac{1}{1+exp(-x)}
 \dfrac{d}{dx}f(x)=σ(x)
 f(x)=\int_{-∞}^xσ(y)dAy

soft plus関数の実装

簡易的には、pythonで下記の実装もありえる。

import numpy as np
def softplus(x):
 return np.log(1.0+np.exp(x))

ただ、x=1000を超えるような数をいれるとオーバーフローをする場合がある。
そこで、下記のような実装の工夫があり得る。
 f(x)=log(1+exp(x))=log(1+exp(x))-log(exp(x))+x
 =log \dfrac{1+exp(x)}{exp(x)}+x=log\left( 1+\dfrac{1}{exp(x)}\right) + x=log(1+exp(-x))+x
この変形を用いて、

import numpu as np
return maximum(x,0)+log(1+np.exp(-np.abs(x)))

KLダイバージェンスとJSダイバージェンス

KLダイバージェンスは、正解分布p(x)に対して、推定分布q(x)がどの程度近似しているかを評価する指標である。
 DLK(P||Q)=\int_{-∞}^{∞}p(x)log(\dfrac{p(x)}{q(x)}dx

KLダイバージェンスは、p(x)とq(x)が非対称なため、使いにくい場合もある。そこで、等価な形に改めたのがJSダイバージェンスである。
 D_{DS}(P||Q)=\dfrac{1}{2}(D_{KL}(P||Q)+D_{KL}(Q||P))

情報量とエントロピー

情報量

情報量は、下記の2つの条件を満たすものとして定義する。
1. 発生する確率が低いことが分かった時のほうが情報量が多い
2. 情報量は足し算で増えていく
この条件を満たす情報量を以下のように定義できる。あることが分かった時の「そのことの情報量」を自己情報量と呼ぶ.
 i(x)=-log_2P(x)

平均情報量とエントロピー

ある物事について、どれほど知らないことがあるかという事を表すのが平均情報量である。
 H(X)=\displaystyle \sum_{i=1}^n-P(x_i)\times log_2P(x_i)
この「分からなさ」あるいは「不確実性」を情報エントロピーとも呼ぶ。
平均情報量=情報エントロピー
として定義される。

相対エントロピー

相対エントロピーは、カルバックライブラー(Kullback-Lebler)の情報量とも呼ばれる。
これは確率分布の差異を表す指標で、分布間擬距離とも呼ばれる。
あらかじめわかっている確率(リファレンスの確率)をP、評価したい確率をQとすると、相対エントロピーは下記のように定義できる。
 相対エントロピー=\displaystyle\sum_{i=1}^nQ_ilog_2\dfrac{Q_i}{P_i}
P=Qの場合は、相対エントロピーは0となります。

結合エントロピー

複数の事象を同時に知った時に得られる平均情報量を結合エントロピーという。
 H(X,Y)=H(X)+H(Y|X)=H(Y)+H(X|Y)=-\displaystyle\sum_{i=1}^{X_x}\displaystyle\sum_{j=1}^{M_Y}P(x_i,y_j)logP(x_i,y_j)

相互情報量

二つの確率変数の相互依存の尺度を表す指標である。
 I(X;Y)=\displaystyle\sum_{y\in Y}\displaystyle\sum_{x\in X}p(x,y)log\dfrac{p(x,y)}{p(x)p(y)}
連続変数なら、相和の代わりに定積分を用いる。
I(X;Y)を情報エントロピーで表すと以下になる。
 I(X;Y)=H(X)-H(X|Y)=H(X)+H(Y)-H(X,Y)

条件付きエントロピー

f:id:tibet:20220106224429p:plain
上記の図が参考になる。
条件付きエントロピーは、次のように表せる。
 H(X|Y)=H(X)-I(X,Y)=H(X,Y)-H(Y)

確率pで表現すると次のようになる。
 H(X|Y)=-\displaystyle\sum_y\left( \displaystyle\sum_xp(x,y)logp(x,y)-p(y)log(y)\right)
もしくは、
 H(X|Y)=-\sum_{x\in 0,1}\sum_{y\in 0,1}P(X=x|Y=y)P(Y=y)log(P(X=x\Y=y)

深層学習の学習 R-CNN

R-CNN(Region-CNN)とは

2014年に発表された一般物体検出の代表的なネットワーク。
元論文は、 Rich feature hierarchies for accurate object detection and semantic segmentation
ディープラーニングの一般物体検出手法の発展をまとめた図が下記のようにGitHubで公開されているが、R-CNNが出発点になっていることが分かる。

https://github.com/hoya012/deep_learning_object_detection/blob/master/assets/deep_learning_object_detection_history.PNG?raw=true

R-CNNのプロセス

R-CNNのプロセスは大きく下記のように示せる。
(参考はこちら
1.対象画像の入力
2.入力画像に対して、物体が映っている領域の候補(region Proposal)をSelective Searchで約2000個抽出し、CNNのインプットの大きさに合うようにそれぞれの領域中の画像をリサイズする。
3.それぞれの領域に対してCNN(Alex Net)で特徴量を計算
4.それぞれの領域に何が映っているのかSVMで分類する

Region Proposal

入力画像からSelective Searchで物体が写っている領域の候補(Region Proposal)矩形を2000こ抽出し、CNNの入力画像とする。Selective Searchによってあらかじめ候補領域を絞り込むことで、高速化を図っている。
CNNの入力サイズは固定のため、Selective Searchで抽出した領域はCNNの入力サイズに合わせてリサイズする。

Selective Search

ピクセルレベルで類似する領域を階層的に結合して一つの物体領域を出力し、そのBBを出力する。

Compute CNN Feature

物体認識として使うCNNは、ImageNetデータセットで学習した一般的認識のネットワークを流用し、別のデータセットで転移学習を行う。

Classify Regions

SVMによって領域を分類する。

R-CNNの課題

R-CNNは深層学習以前の一般物体検出手法に比べて認識精度を大きく向上させたが、時間が非常にかかる。
また、CNN、SVM、BBの回帰とプロセスごとに別々に学習する必要がある。

深層学習の学習:BERT

BERT

BERTとは、2018年にGoogleが発表した自然言語処理用の深層学習モデルである。

  • 論文タイトル

BERT:Pre-training of Deep Bidirectional Transformers for Language Understanding

  • 投稿日

2018/10/11

  • 著者

Jacob Devlin, Ming-Wei Chang, Kenton Lee, Kristina Toutanova

  • 概要
    • Bidirectional Transformerをユニットにフルモデルで構成したモデル
    • 事前学習タスクとして、マスク言語予測タスク、隣接文判定タスクを与える
    • BERTからTransfer Lerningを行った結果、8つのタスクでSOTA
    • Googleが事前学習済みモデルを公開済み

背景

 様々な言語処理タスクにおいて事前学習が有効である。

  • 文レベルのタスク:文同士の関係性が重要
    • 文章類似度
    • 言い換え
  • トークンレベル:モデルはトークンレベルでよい出力が求められる

事前学習には二種類のアプローチがある。

    • Feature-Based
    • Fine-tuning

Feature-Based アプローチ

 事前学習したモデルでターゲットの文章を読み込ませ、隠れ層の値(特徴量)を抽出して、ターゲットのタスクモデルに渡すアプローチ。
 様々なNLPタスクの素性として利用される。
 最近では、ElMoが話題になった。

Fine-tuningアプローチ

 言語モデルで事前学習し、それをタスクのモデルで転移学習するアプローチ。
 事前学習は、パラメタの初期値として使用される。
 Open AI GPT、BERTで使用されており、最近はこちらが注目されている。

BERTの概要

Fine-tuningアプローチの事前学習に工夫を加えている。
具体的には、双方向トランスフォーマーは、よい学習効果をもたらすが、言語モデルの学習においては、従来のモデルでは未来情報のリークを防ぐためのマスクが無いため、採用が難しい。
そのため、事前学習タスクにおいて工夫をする必要があった。
f:id:tibet:20211228170042p:plain

入力表現

3種類のEmbeddingのSumを入力とする。

  • Token Embedding: WordPieceでTokenizationしたものをEmbedding
  • 単語位置埋め込み:系列長1-512の表現
  • 文区別埋め込み:1文目、2分目の区別
事前学習

1. 空欄語予測(Masked Language Modeling (MLM))

  • 文章中の単語のうち、15%をMASK対象に選ぶ。選ばれた15%の単語の位置にはフラグを立てておく。
  • 選んだ単語のうち、80%を[MASK]に置き換え、10%を他の単語に置き換え、残り10%は置き換えない。

文章を入力としてフラグが点いている位置のオリジナルの入力単語が何であるかを出力する。

  • 背景

双方向モデルは、left-to-rightモデルや双方向にconcatしたものより一般に強力である。しかし、双方向だと複数層の文脈から自分を見てしまうため、学習出来ない。
そこで発想を転換し、次の単語ではなく、ランダムに抜かれた単語を予測するモデルにする
一方でこの方法を採用すると、単語全体の15%しか学習に使えないため、学習に時間がかかる。

2.隣接文予測(Next Sentence Prediction(NSP))
2つの文章の関係性理解が重要だが、言語モデルのタスクのみではとらえられないという問題点があった。
そこで、2つの連なる文章のペアに対して、隣接文を50%の確率でシャッフルし、隣接文であるかのT/Fを出力させた。

BERTの具体的な事前学習方法
  • データセット:BooksCorpus+English Wikipedia
  • 事前処理
    • 入力文章の合計系列長が512以下になるように2つの文章をサンプリング
    • Next Sentence Predictionのため、文章1と文章2の組み合わせは50%の確率で変わる
    • MLMのため、Wordpieceトークンに分けられた後マスクされる
  • バッチサイズ:256(256×512=128,000単語/バッチ)
  • 1,000,000ステップ=33億個のたんごを40エポック学習
  • 活性化関数:GeLu

有効性

8個のNLPベンチマークタスクで、タスクspecificなアーキテクチャを組むことなく一気にSOTA達成








 

LSTM/RNNの実装

通常のニューラルネットワーク

一般のニューラルネットワークにおける順伝搬は下記のように表せる。
 h^l=f^l(W^lh^{l-1}+b^l)
下図のニューラルネットワークでは、下記の式になる。
 h=f(W^{(1)}x+b{(1)})
 y=g(W^{(2)}h+b{(2)})
f:id:tibet:20211226102409p:plain
 u=W^{(1)}x+b^{(1)}
 v=W~{(2)}h+b~{(2)}
と置けば、下記のように書ける。
 h=f(u)...(1)
 y=g(v)...(2)

誤差逆伝搬法

損失関数をLとすると、誤差の勾配は(1), (2)式を用いて下記のように書ける。
 δ^{(v)}=\dfrac{\partial L}{\partial v}=\dfrac{\partial L}{\partial y}\dfrac{\partial y}{\partial v}=\dfrac{\partial L}{\partial y}g'(v)
 δ^{(u)}=\dfrac{\partial L}{\partial u}=\dfrac{\partial L}{\partial v}\dfrac{\partial v}{\partial h}\dfrac{\partial h}{\partial u}=(W^{(2)})^Tδ^{(2)}f'(u)

各パラメータの勾配は下記のようになる。
 \dfrac{\partial L}{\partial W^{(2)}}=\dfrac{\partial L}{\partial v}\dfrac{\partial v}{\partial W^{(2)}}=δ^{(v)}\bigotimes h
 \dfrac{\partial L}{\partial b^{(2)}}=\dfrac{\partial L}{\partial v}\dfrac{\partial v}{\partial b^{(2)}}=δ^{(v)}
 \dfrac{\partial L}{\partial W^{(1)}}=\dfrac{\partial L}{\partial u}\dfrac{\partial u}{\partial W^{(1)}}=δ^{(u)}\bigotimes x
 \dfrac{\partial L}{\partial b^{(1)}}=\dfrac{\partial L}{\partial v}\dfrac{\partial v}{\partial b^{(1)}}=δ^{(u)}

 \bigotimesテンソル積。

RNN

  • 過去の隠れ層の状態も入力につかう
  • 系列データを扱うのに適したニューラルネットワークである。
    • 過去の情報を保持する
    • 可変長入力

f:id:tibet:20211125172417p:plain

RNNの順伝搬

 h_t=f(W^{(1)}x_t+Uh_{t-1}+b^{(1)})
 y_t=g(W^{(2)}h_t+b^{(2)})

RNNの誤差逆伝搬

  • BPTT

時間展開したうえでの誤差逆でパン

  • Truncated BPTT

遡るステップ数を限定したBPTT

RNNの問題

  • 勾配消失の問題があり、長期的な依存関係を学習できないことである。

 δ^{(u)}=\dfrac{\partial L_t}{\partial u_{t-1}}=\dfrac{\partial L_t}{\partial u_t}\dfrac{\partial u_t}{\partial h_{t-1}}\dfrac{\partial h_{t-1}}{\partial u_{t-1}}=U_Tδ_t^{(u)}f'(h_{t-1})
上記のように勾配計算に活性化関数の微分値が残る。

LSTM

RNNの問題を解決し、長期的な依存関係を学習できるようにしたものがLSTMである。
セルとゲートを導入した。
f:id:tibet:20211129195005p:plain

LSTMの誤差逆伝搬

  • RNNと同じくBPTT
  • RNNに比べてパラメータが多い
  • 勾配爆発を避けるために勾配クリッピングがある
  • 大きい勾配に対してノルムを閾値以下に正規化

LSTMが勾配消失を避けられる理由

cについての勾配は、1ステップ前においても活性化関数の微分を通さず、忘却ゲートの値倍となる。
忘却ゲートの値が1に近ければ、誤差は消失しない。
 δ_t^{(c)}=\dfrac{\partial L_t}{\partial c_t}
 δ_{t-1}^{(c)}=\dfrac{\partial L_t}{\partial c_{(t-1)}=δ_t^{(c)}f_t]

主要フレームワークAPI

RNNのAPI(TensorFlow)

  • tf.contrib.rnn.BasicRNNcell(num_units, activation=tanh)

RNNセルを生成

  • tf.contrib.rnn.BasicLSTMCell(num_units,forget_bias=1.0,activation=tanh)

LSTMセルを生成

  • tf.contrib.rnn.static_rnn(cell, inputs,initial_state=None)

時間方向に展開。出力と最後の内部状態を返す。

LSTMのAPI(Chainer)

  • chainer.links.LSTM(in_size,out,forget_bias_init=1.0)

LSTM層を生成
__call__(x)
内部情報を更新
 LSTMの出力を返す

実装演習

TensorFlowによるRNN・LSTMの実装

環境準備

%tensorflow_version 1.x
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
% matplotlib inline
import seaborn as sns
sns.set()

学習用データを設定する

# sin曲線+ノイズ
ts = np.linspace(0, 10 * np.pi, 500)
ys = np.sin(ts) + np.random.normal(scale=0.05, size=len(ts))
plt.plot(ts, ys)

f:id:tibet:20211226120327p:plain
学習データの設定(n_stepsごとに分割した訓練・テストデータの準備)

# 学習設定
batch_size = 32    # ミニバッチサイズ
n_steps = 50       # 入力系列の長さ
input_size = 1     # 入力の次元
hidden_size = 50   #  中間層の次元
output_size = 1    # 出力層の次元

lr = 0.005         # 学習率(SGD)
n_iter = 500       # イテレーション回数
# 訓練データとテストデータに分割
train_ratio = 0.8
data = []
for i in range(len(ys) - n_steps - 1):
    data.append(ys[i: i+n_steps+1])
data = np.array(data, dtype=np.float32)
n_train = int(train_ratio * len(data))
x_train, y_train = np.split(data[:n_train], [-1], axis=1)
x_test, y_test = np.split(data[n_train:], [-1], axis=1)

x_train = np.reshape(x_train, [-1, n_steps, input_size])
x_test = np.reshape(x_test, [-1, n_steps, input_size])

モデル構築

def inference(x):
    """
    推論(順伝播)
    x: (batch_size, n_steps, input_size)
    """
    # static_rnn関数の引数に適切な形に変形
    x = tf.transpose(x, [1, 0, 2])  # (n_steps, batch_size, input_size)
    x = tf.reshape(x, [-1, input_size])  # (n_steps*batch_size, input_size)
    x = tf.split(x, n_steps, axis=0)  # [(batch_size, input_size)] * n_steps
    
    # RNN(LSTM)セルを定義
    rnn_cell = tf.contrib.rnn.BasicRNNCell(hidden_size)
#     rnn_cell = tf.contrib.rnn.BasicLSTMCell(hidden_size, forget_bias=1.0)
    
    # RNNセルを時間方向に伝播
    hs, _ = tf.contrib.rnn.static_rnn(rnn_cell, x, dtype=tf.float32)
    
    # 出力層の定義
    W_out = tf.Variable(tf.random_normal([hidden_size, output_size]))
    b_out = tf.Variable(tf.random_normal([output_size]))
    return tf.matmul(hs[-1], W_out) + b_out
tf.reset_default_graph()

# 入出力プレースホルダーの定義
x = tf.placeholder("float", [None, n_steps, input_size])
y = tf.placeholder("float", [None, output_size])

# オペレーションの定義
pred = inference(x)
loss = tf.losses.mean_squared_error(y, pred)
# train_step = tf.train.GradientDescentOptimizer(learning_rate=lr).minimize(loss)
train_step = tf.train.AdamOptimizer().minimize(loss)

学習

init = tf.global_variables_initializer()
# セッション
sess = tf.Session()
# 変数初期化
sess.run(init)

# 訓練データのインデックスをランダムに
perm = np.random.permutation(len(x_train))

for i in range(n_iter):
    idx = (i * batch_size) % len(x_train)
    batch_x, batch_y = x_train[perm[idx: idx+batch_size]], y_train[perm[idx: idx+batch_size]]
    
    feed_dict = {x: batch_x, y: batch_y}
    sess.run(train_step, feed_dict=feed_dict)
    if i % 50 == 0:
        l = sess.run(loss, feed_dict=feed_dict)
        print("step: {}, loss {:.5f}".format(i, l))

結果

step: 0, loss 2.82473
step: 50, loss 0.01717
step: 100, loss 0.02267
step: 150, loss 0.01304
step: 200, loss 0.01130
step: 250, loss 0.01892
step: 300, loss 0.00806
step: 350, loss 0.00597
step: 400, loss 0.00722
step: 450, loss 0.00508

テストデータに対する予測

# テストデータに対する予測
prediction = sess.run(pred, feed_dict={x: x_test})

# 1次元配列に
prediction = prediction.reshape(-1)
true_y = y_test.reshape(-1)
# テストデータに対する予測を可視化
xx = np.arange(len(prediction))
plt.plot(xx, true_y, label='true')
plt.plot(xx, prediction, label='prediction')
plt.legend()

f:id:tibet:20211226121522p:plainf:id:tibet:20211226121522p:plain

テストデータの最初の値からスタートして、モデルの予測結果を利用して再帰的に予測をする。
モデルのトレーニング回数は2000回で行っている。

# テストデータの最初のデータからスタートし、
# モデルの予測を利用し再帰的に予測
curr_x = x_test[0]
predicted = []
# 予測するステップ数
N = 200
for i in range(N):
    # 予測
    predicted.append(model.predict(curr_x[None]))
    # 入力を更新
    curr_x = np.insert(curr_x, n_steps, predicted[-1], axis=0)[1:]

# 1次元配列に
predicted = np.array(predicted).reshape(-1)

#再帰的な予測を可視化
plt.plot(xx, true_y, label='true')
plt.plot(np.arange(N), predicted, label='prediction')
plt.legend()

f:id:tibet:20211226124725p:plain

為替(USD/JPY)のLSTMによる予測

準備

import pandas as pd
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
% matplotlib inline
import seaborn as sns
sns.set()

データ読み込み

!mkdir data
!wget https://dl.dropbox.com/s/rzzcc89lokvzu3r/usd_jpy.csv -O data/usd_jpy.csv

データ構造は以下。

日付 始値 高値 安値 終値
0 2007/04/02 117.84 118.08 117.46 117.84
1 2007/04/03 117.84 118.98 117.72 118.96
2 2007/04/04 118.92 119.08 118.56 118.72
3 2007/04/05 118.72 118.99 118.44 118.72
4 2007/04/06 118.72 119.39 118.67 119.27

終値を図示してみる。

plt.figure(figsize = (18,9))
plt.plot(np.arange(df.shape[0]), df['終値'].values)
plt.xticks(np.arange(0, df.shape[0], 260), df['日付'].loc[::260], rotation=45)

f:id:tibet:20211226125457p:plain
終値をターゲットに、予測モデルを作成する。
データを作成する。

close_prices = df['終値'].values

train_ratio = 0.8
n_train = int(len(close_prices) * train_ratio)
train, test = close_prices[:n_train], close_prices[n_train:]
train = train.reshape(-1, 1)
test = test.reshape(-1, 1)

MinMaxScalerを使ってデータを0-1に正規化する。

from sklearn import preprocessing
scaler = preprocessing.MinMaxScaler()
train = scaler.fit_transform(train)
test = scaler.transform(test)

レーニングデータ数は2340である。

データジェネレータのクラスを用意する。

import math

class DataGenerator(tf.keras.utils.Sequence):
    def __init__(self, data, batch_size, n_steps, input_size, output_size):
        self.data = data
        self.batch_size = batch_size
        self.n_steps = n_steps
        self.input_size = input_size
        self.output_size = output_size

    def __len__(self):
        return math.floor((len(self.data) - self.n_steps) / self.batch_size)
    
    def __getitem__(self, idx):
        xs = np.zeros((self.batch_size, self.n_steps, self.input_size))
        ys = np.zeros((self.batch_size, self.output_size))

        for i in range(self.batch_size):
            step_begin = (idx * self.batch_size) + i
            step_end = (idx * self.batch_size) + i + self.n_steps
            x = np.zeros((self.n_steps, self.input_size))
            y = np.zeros((self.output_size))

            if step_end >= len(self.data):
                break
            else:
                x = self.data[step_begin: step_end]
                y = self.data[step_end]

            xs[i] = x
            ys[i] = y

        return xs, ys

学習の設定をする。

# 学習設定
batch_size = 32    # ミニバッチサイズ
n_steps = 50       # 入力系列の長さ
input_size = 1     # 入力の次元
hidden_size = 50   #  中間層の次元
output_size = 1    # 出力層の次元

n_epochs = 30       # エポック数

train_gen = DataGenerator(train, batch_size, n_steps, input_size, output_size)

モデル構築を行う

def create_model():
    inputs = tf.keras.layers.Input(shape=(n_steps, input_size))
    lstm = tf.keras.layers.LSTM(hidden_size)(inputs)
    output = tf.keras.layers.Dense(output_size)(lstm)

    model = tf.keras.Model(inputs, output)
    model.compile(optimizer='adam', loss='mse', metrics='accuracy')

    return model

model = create_model()

model.summary()
Model: "model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_1 (InputLayer)        [(None, 50, 1)]           0         
                                                                 
 lstm (LSTM)                 (None, 50)                10400     
                                                                 
 dense (Dense)               (None, 1)                 51        
                                                                 
=================================================================
Total params: 10,451
Trainable params: 10,451
Non-trainable params: 0

上記のようにパラメータ数10,451のLSTMを作成した。
学習を行う

model.fit_generator(train_gen, epochs=30, shuffle=True)
Epoch 3/30
71/71 [==============================] - 1s 13ms/step - loss: 0.0018 - accuracy: 8.8028e-04
Epoch 4/30
71/71 [==============================] - 1s 13ms/step - loss: 0.0013 - accuracy: 8.8028e-04
Epoch 5/30
71/71 [==============================] - 1s 14ms/step - loss: 0.0014 - accuracy: 8.8028e-04
Epoch 6/30
71/71 [==============================] - 1s 13ms/step - loss: 0.0014 - accuracy: 8.8028e-04
Epoch 7/30
71/71 [==============================] - 1s 14ms/step - loss: 0.0010 - accuracy: 8.8028e-04
Epoch 8/30
71/71 [==============================] - 1s 13ms/step - loss: 0.0011 - accuracy: 8.8028e-04
Epoch 9/30
71/71 [==============================] - 1s 13ms/step - loss: 0.0012 - accuracy: 8.8028e-04
Epoch 10/30
71/71 [==============================] - 1s 14ms/step - loss: 9.7010e-04 - accuracy: 8.8028e-04
Epoch 11/30
71/71 [==============================] - 1s 14ms/step - loss: 9.1534e-04 - accuracy: 8.8028e-04
Epoch 12/30
71/71 [==============================] - 1s 14ms/step - loss: 0.0011 - accuracy: 8.8028e-04
Epoch 13/30
71/71 [==============================] - 1s 14ms/step - loss: 9.7462e-04 - accuracy: 8.8028e-04
Epoch 14/30
71/71 [==============================] - 1s 14ms/step - loss: 8.6392e-04 - accuracy: 8.8028e-04
Epoch 15/30
71/71 [==============================] - 1s 14ms/step - loss: 8.6688e-04 - accuracy: 8.8028e-04
Epoch 16/30
71/71 [==============================] - 1s 14ms/step - loss: 0.0010 - accuracy: 8.8028e-04
Epoch 17/30
71/71 [==============================] - 1s 14ms/step - loss: 0.0011 - accuracy: 8.8028e-04
Epoch 18/30
71/71 [==============================] - 1s 14ms/step - loss: 8.6142e-04 - accuracy: 8.8028e-04
Epoch 19/30
71/71 [==============================] - 1s 14ms/step - loss: 7.6914e-04 - accuracy: 8.8028e-04
Epoch 20/30
71/71 [==============================] - 1s 14ms/step - loss: 8.8667e-04 - accuracy: 8.8028e-04
Epoch 21/30
71/71 [==============================] - 1s 14ms/step - loss: 6.5316e-04 - accuracy: 8.8028e-04
Epoch 22/30
71/71 [==============================] - 1s 14ms/step - loss: 6.6321e-04 - accuracy: 8.8028e-04
Epoch 23/30
71/71 [==============================] - 1s 14ms/step - loss: 6.9309e-04 - accuracy: 8.8028e-04
Epoch 24/30
71/71 [==============================] - 1s 14ms/step - loss: 6.6656e-04 - accuracy: 8.8028e-04
Epoch 25/30
71/71 [==============================] - 1s 13ms/step - loss: 7.9177e-04 - accuracy: 8.8028e-04
Epoch 26/30
71/71 [==============================] - 1s 13ms/step - loss: 7.3700e-04 - accuracy: 8.8028e-04
Epoch 27/30
71/71 [==============================] - 1s 13ms/step - loss: 7.8668e-04 - accuracy: 8.8028e-04
Epoch 28/30
71/71 [==============================] - 1s 13ms/step - loss: 5.7617e-04 - accuracy: 8.8028e-04
Epoch 29/30
71/71 [==============================] - 1s 13ms/step - loss: 5.7106e-04 - accuracy: 8.8028e-04
Epoch 30/30
71/71 [==============================] - 1s 13ms/step - loss: 6.8287e-04 - accuracy: 8.8028e-04
<keras.callbacks.History at 0x7f655020a450>

テスト及び予測結果の可視化を行う

test_gen = DataGenerator(test, batch_size, n_steps, input_size, output_size)
predicts = model.predict_generator(test_gen)
fixed_predicts = scaler.inverse_transform(predicts)
fixed_test = scaler.inverse_transform(test)
# テストデータに対する予測を可視化
mod = (len(fixed_test) - n_steps) % batch_size
xx = np.arange(len(fixed_predicts))
plt.figure(figsize=(18, 9))
plt.plot(xx, fixed_test[n_steps:-mod], label='true')
plt.plot(xx, fixed_predicts, label='prediction')
plt.legend()

f:id:tibet:20211226132152p:plain