E資格学習 深層学習 Day4 ⑤Transformer
RNNとSeq2Seqのおさらい
- RNNは系列情報を内部状態ベクトルに変換する。t-1までの単語が来た時に、tにくる単語が何かを確率分布(事後確率)で出力する。始めの単語(Start of Sentence)の予測や、先頭単語を与えるこにより、文章を生成することが可能。
- Seq2Seqでは、Encoder RNNとDecoder RNNの2つが連結している。Encoder → 潜在変数(h)→ Decoderとして内部状態ベクトルhとして集約される。Decoderに正解を持たせることで教師あり学習ができる。
- 入力データ数の大小に関わらず、内部状態ベクトルは固定長という点に注意。長くなればなるほど、BLEU(評価値)が小さくなってしまう。そのため、長い系列長データの学習に弱いという特長がある。
Transformer
- 固定長ベクトル変換であるために、長い系列長の場合に精度がなかなか出ないという弱点を解決したAttention機構のみを使ったモデル。
- Encoder、Decoder双方にRNNを全く使っていない。
- 位置情報についてはPositional Encodingによって付加。
- Encoderでは、Positional Encodingによる位置情報付与→Dotprodcut Attention(Self Attention)→全結合層
- Decoderでは、Positional Encoding→未来の単語を見ないようなマスク処理(Masked Multihead Attention)をSelf Attentionで実施→ Encoderの出力とまとめてSource Target Attention →全結合層
Attention
- 足すと1になる重みを各系列データ毎に割り当てることで、入力値のどの部分に注目するのかを、注目する度合いを分散する仕組み。
- Self-Attention(自己注意)とSource Target Attentionの違い:Source Targetでは、Queryにターゲット、Key, Valueはソースデータを使う。自己注意ではQuery Key Valueは全て同じ情報が与えられる。
- Mulihead Attentionでは、Scaled Dot Attentionを8個分concatする。
Positional Encoding
def position_encoding_init(n_position, d_pos_vec): """ Positional Encodingのための行列の初期化を行う :param n_position: int, 系列長 :param d_pos_vec: int, 隠れ層の次元数 :return torch.tensor, size=(n_position, d_pos_vec) """ # PADがある単語の位置はpos=0にしておき、position_encも0にする position_enc = np.array([ [pos / np.power(10000, 2 * (j // 2) / d_pos_vec) for j in range(d_pos_vec)] if pos != 0 else np.zeros(d_pos_vec) for pos in range(n_position)]) position_enc[1:, 0::2] = np.sin(position_enc[1:, 0::2]) # dim 2i position_enc[1:, 1::2] = np.cos(position_enc[1:, 1::2]) # dim 2i+1 return torch.tensor(position_enc, dtype=torch.float)
Scaled Dot Product Attention
class ScaledDotProductAttention(nn.Module): def __init__(self, d_model, attn_dropout=0.1): """ :param d_model: int, 隠れ層の次元数 :param attn_dropout: float, ドロップアウト率 """ super(ScaledDotProductAttention, self).__init__() self.temper = np.power(d_model, 0.5) # スケーリング因子 self.dropout = nn.Dropout(attn_dropout) self.softmax = nn.Softmax(dim=-1) def forward(self, q, k, v, attn_mask): """ :param q: torch.tensor, queryベクトル, size=(n_head*batch_size, len_q, d_model/n_head) :param k: torch.tensor, key, size=(n_head*batch_size, len_k, d_model/n_head) :param v: torch.tensor, valueベクトル, size=(n_head*batch_size, len_v, d_model/n_head) :param attn_mask: torch.tensor, Attentionに適用するマスク, size=(n_head*batch_size, len_q, len_k) :return output: 出力ベクトル, size=(n_head*batch_size, len_q, d_model/n_head) :return attn: Attention size=(n_head*batch_size, len_q, len_k) """ # QとKの内積でAttentionの重みを求め、スケーリングする attn = torch.bmm(q, k.transpose(1, 2)) / self.temper # (n_head*batch_size, len_q, len_k) # Attentionをかけたくない部分がある場合は、その部分を負の無限大に飛ばしてSoftmaxの値が0になるようにする attn.data.masked_fill_(attn_mask, -float('inf')) attn = self.softmax(attn) attn = self.dropout(attn) output = torch.bmm(attn, v) return output, attn
Multihead Attention
class MultiHeadAttention(nn.Module): def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1): """ :param n_head: int, ヘッド数 :param d_model: int, 隠れ層の次元数 :param d_k: int, keyベクトルの次元数 :param d_v: int, valueベクトルの次元数 :param dropout: float, ドロップアウト率 """ super(MultiHeadAttention, self).__init__() self.n_head = n_head self.d_k = d_k self.d_v = d_v # 各ヘッドごとに異なる重みで線形変換を行うための重み # nn.Parameterを使うことで、Moduleのパラメータとして登録できる. TFでは更新が必要な変数はtf.Variableでラップするのでわかりやすい self.w_qs = nn.Parameter(torch.empty([n_head, d_model, d_k], dtype=torch.float)) self.w_ks = nn.Parameter(torch.empty([n_head, d_model, d_k], dtype=torch.float)) self.w_vs = nn.Parameter(torch.empty([n_head, d_model, d_v], dtype=torch.float)) # nn.init.xavier_normal_で重みの値を初期化 nn.init.xavier_normal_(self.w_qs) nn.init.xavier_normal_(self.w_ks) nn.init.xavier_normal_(self.w_vs) self.attention = ScaledDotProductAttention(d_model) self.layer_norm = nn.LayerNorm(d_model) # 各層においてバイアスを除く活性化関数への入力を平均0、分散1に正則化 self.proj = nn.Linear(n_head*d_v, d_model) # 複数ヘッド分のAttentionの結果を元のサイズに写像するための線形層 # nn.init.xavier_normal_で重みの値を初期化 nn.init.xavier_normal_(self.proj.weight) self.dropout = nn.Dropout(dropout) def forward(self, q, k, v, attn_mask=None): """ :param q: torch.tensor, queryベクトル, size=(batch_size, len_q, d_model) :param k: torch.tensor, key, size=(batch_size, len_k, d_model) :param v: torch.tensor, valueベクトル, size=(batch_size, len_v, d_model) :param attn_mask: torch.tensor, Attentionに適用するマスク, size=(batch_size, len_q, len_k) :return outputs: 出力ベクトル, size=(batch_size, len_q, d_model) :return attns: Attention size=(n_head*batch_size, len_q, len_k) """ d_k, d_v = self.d_k, self.d_v n_head = self.n_head # residual connectionのための入力 出力に入力をそのまま加算する residual = q batch_size, len_q, d_model = q.size() batch_size, len_k, d_model = k.size() batch_size, len_v, d_model = v.size() # 複数ヘッド化 # torch.repeat または .repeatで指定したdimに沿って同じテンソルを作成 q_s = q.repeat(n_head, 1, 1) # (n_head*batch_size, len_q, d_model) k_s = k.repeat(n_head, 1, 1) # (n_head*batch_size, len_k, d_model) v_s = v.repeat(n_head, 1, 1) # (n_head*batch_size, len_v, d_model) # ヘッドごとに並列計算させるために、n_headをdim=0に、batch_sizeをdim=1に寄せる q_s = q_s.view(n_head, -1, d_model) # (n_head, batch_size*len_q, d_model) k_s = k_s.view(n_head, -1, d_model) # (n_head, batch_size*len_k, d_model) v_s = v_s.view(n_head, -1, d_model) # (n_head, batch_size*len_v, d_model) # 各ヘッドで線形変換を並列計算(p16左側`Linear`) q_s = torch.bmm(q_s, self.w_qs) # (n_head, batch_size*len_q, d_k) k_s = torch.bmm(k_s, self.w_ks) # (n_head, batch_size*len_k, d_k) v_s = torch.bmm(v_s, self.w_vs) # (n_head, batch_size*len_v, d_v) # Attentionは各バッチ各ヘッドごとに計算させるためにbatch_sizeをdim=0に寄せる q_s = q_s.view(-1, len_q, d_k) # (n_head*batch_size, len_q, d_k) k_s = k_s.view(-1, len_k, d_k) # (n_head*batch_size, len_k, d_k) v_s = v_s.view(-1, len_v, d_v) # (n_head*batch_size, len_v, d_v) # Attentionを計算(p16.左側`Scaled Dot-Product Attention * h`) outputs, attns = self.attention(q_s, k_s, v_s, attn_mask=attn_mask.repeat(n_head, 1, 1)) # 各ヘッドの結果を連結(p16左側`Concat`) # torch.splitでbatch_sizeごとのn_head個のテンソルに分割 outputs = torch.split(outputs, batch_size, dim=0) # (batch_size, len_q, d_model) * n_head # dim=-1で連結 outputs = torch.cat(outputs, dim=-1) # (batch_size, len_q, d_model*n_head) # residual connectionのために元の大きさに写像(p16左側`Linear`) outputs = self.proj(outputs) # (batch_size, len_q, d_model) outputs = self.dropout(outputs) outputs = self.layer_norm(outputs + residual) return outputs, attns
E資格学習 深層学習 Day4 ⑥ 物体検知とセグメンテーション
物体検知
要点まとめ
評価指標について
- 一般的なクラス分類の評価指標では「混同行列」が使われる。この時、Confidence Threshold値を設定し、閾値を変えると、各データのクラス予測が0なのか1なのかが変わるが、サンプル数そのものの数量は変わらない。
- 一方、物体検知ではバウンディングボックス を予測するが、Confidence Threshold値を変えてしまうと、Confidenceの閾値以上のバウンディングボックスだけが評価対象のサンプルとして認識されるので、Thresholdが変化すると、そもそもの混同行列に入ってくる母数が変わってくる特長がある
- 物体位置の予測精度の評価には、IoUが使われる。IoU(Intersection over Union):Area of Overlap / Area of Union
- 物体検知でのPrecisionとRecallの考え方は、ConfidenceとIoUの2つに対して閾値を設ける。例えば、Conf >0.5, IoU>0.5 とすると、TP = Conf>0.5, IoU>0.5、FP = Conf>0.5, IoU<0.5 となる。(Conf<0.5のデータはそもそも選ばれない。)すでにTPと検出済みとなっているバウンディングボックスと同じ予測をしているものはFPとして分類される。
- 物体検知では、上記で記載したようにConf閾値(β)を変化することで、PrecisionとRecallが変化することがわかった。そこで、クラスを固定した上で、βを徐々に変化した時にPrecisionとRecallがどのように変化したのかを曲線で描いた下面積をAPとして評価する。複数のクラスがある場合は、クラス毎のAPを算出して平均したものをmAP(mean Average Precision)という。
- 検出精度だけでなく、検出速度(Frame Per Second)もモデルの評価に使われる。
物体検知のモデル
<2段階検出器>
- R-CNN:
- Fast R-CNN:
- Faster R-CNN:
<1段階検出器>
- SSD (Single Shot Detector):デフォルトボックスを用意し、変形しながらオブジェクトの周囲の大きさに最適化する。VGG16(conv層+全結合層で16層)の構造を継承しているが、全結合層×2がConv層に変更されている。
- YOLO(You Look Only Once)
- デフォルトボックスを修正しながら特徴マップからどのように出力される際は、クラス数分のconf値はもちろん、4つのオフセット項(Δx, Δy, Δw, Δh)が出力サイズとなる。k個のデフォルトボックスを用意するのでk(#class + 4)となる。
- バウンディングボックス を多数用意することにより生ずる弊害もある。1つのクラスに対して冗長的にBBが重なってしまう問題。→Non-Maximum Suppresiion:Conf値の一番大きいものだけを採用することで解決。
非背景と背景のBBのバランスが不均衡になる→非背景の比を1:3に制限する。
セマンテックセグメンテーション
要点まとめ
- ピクセル単位での輪郭検出をしたい。が、畳み込みネットワークではプーリング によって解像度が落ちてしまうのが難点。ではプーリング しなくてよいのでは?というとそうでもなく、特徴量の受容野の獲得のためには必要。
- そこで、畳み込みしたものをUp-Samplingによって解像度をあげる仕組みが用いられる。(3×3→5×5)逆畳み込みとも言われるが、逆演算をしているわけではない。別名:転置畳み込み(Deconvolution/ Transposed Convolution)
- Unpoolingという方法により、Pooling時の位置情報を維持する仕組みもある。
代表的なセグメンテーションのモデル
- FCN:全てConv層のネットワークで構成されている。
- U-Net:Encoder部分とDecoder部分の角層間でSkip-connectionによる接続構造が見られる。この形がU字になっているのでU-Netという名前になった。
- (その他、Segnet等)
E資格学習 深層学習 Day4 ④ その他応用モデル
MobileNet
要点まとめ
- 画像認識モデルにおいて、軽量化+高速化を実現したモデル
- 通常の畳み込み演算では、入力画像の大きさ(H×W)×カーネルサイズ(K×K)×チャネル数(C)×M(フィルタ数)の畳み込み計算が必要となる(H×W×C×K×K×M)が、この畳み込みを、Depthwise ConvolutionとPointwise Convolutionを組み合わせるという工夫により従来の畳み込み演算よりも高速化を実現した。
Depthwise Convolution
DenseNet
- 画像認識のモデル。
- Denseブロックと呼ばれる構造がある。構造としては、Batch 正規化→ReLu×畳み込み(3×3)]。
- このブロックを通過するごとに、Denseブロックを通過する前のデータのチャンネル数分追加される。(Denseブロックの中のみで実施される)4ブロックの中でこの作業が行われている。
- 類似構造として、スキップコネクションを導入しているResNetがあるが、ResNetは前層分のみであったが、DenseNetでは前方の各層が全て足される。growth rateはハイパーパラメータとして設定する。
正規化の技術(BatchNorm, Layer Norm, Instance Norm)
- 正規化=平均0、分散1にデータを加工すること。
- Batch Norm, Layer Norm, Instance Norm といった正規化の手法がある。
Batch Normalization
- ミニバッチ内のデータについて、チャネル方向で正規化する(同一チャネルが同一分布になる)
- バッチサイズが小さい条件では学習が収束しないことがある。その場合にはLayer Normが使用される。
- H×W×C ×N個のミニバッチの場合、N個のC(チャンネル数)毎にまとめて正規化を実施するイメージ
Layer Normalization
- データずつ正規化するイメージ。チャンネル方向はまとめて実施される。
- H×W×C ×N個のミニバッチの場合、H×W×C単位で正規化し、Nデータ分個々に正規化するイメージ。
Instance Normalization
- 個々のデータのチャンネル方向に対して、1データずつ個々に正規化する。
- C方向の正規化をC×N個分実施するイメージ。
WaveNet
- 時系列データを扱う音声生成モデルではあるが、RNNではなく畳み込みを適用している。
- Dilated causal Convolutionと呼ばれる。
- 層が深くなるにつれて、時間的なつながりのリンクを離すように畳み込み処理をすることで、より幅広い時間的なつながりを保った特徴量抽出が可能になった。
E資格学習 深層学習 Day4 ③ 軽量化・高速化の技術について
高速化の技術
要点まとめ
- データ量は毎年10倍ずつ増加し計算量も増えていると言われている中で、PCの性能は18ヶ月で2倍になると言われている。その中で、複数の計算資源を並列的に分散させて学習させる工夫がとられている。
- モデルが大きい場合はモデル並列化、データが大きい場合はデータ並列化をすると良い。
- 分散学習においては、最終的にデータやモデルを統合する部分における通信コストが大きくなる。
データ並列化
- ワーカー(CPU/GPU等の演算機)を複数用意し、共通のモデルを各ワーカーにコピー。データを分割して各ワーカーで学習させる。同期型と非同期型の2形態がある。
- 同期型:各ワーカーの学習が終わるのを待ち、勾配を一旦まとめて平均を算出し、それを親モデルに反映→ワーカーの子モデルに反映... を繰り返す。
- 非同期型:ワーカーは互いの計算を待たずバラバラに学習し、順次パラメータがpushされ保存されていく。ワーカーは一番新しい学習済み子モデルを使って学習を繰り返す。処理は非同期型の方が早いが、精度は同期型の方がよくなる。(PCを分けて学習させることが多い)
モデル並列化
- 巨大なニューラルネットワークのモデル(枝分かれが多用されている等)について、モデルの分割し、ワーカー毎に学習させる。(1台のPC上でGPUを分割して学習させることが多い)
- 大きなモデル(学習パラメータが膨大)であればあるほど効果は大きい。
軽量化の技術
量子化
- そもそもなぜ演算処理が増えるかというと、深層学習における大量のパラメータを保存するためのメモリ消費が要因。そこで、パラメータを64bit浮動小数点ではなく、32bitなど下位にに落とすことで省メモリ化により演算処理の削減を行える。
蒸留
- 規模の大きなニューラルネットワークの既存モデルから、知識を継承しながら軽量なモデルをつくること。
- 教師モデル(複雑なモデルor アンサンブルなモデル)と生徒モデル(シンプルかつ軽量)の2つのモデルで構成される。両方のモデルにデータを入力し学習を進めていくが、教師モデル側の重みは固定しつつ、生徒モデル側の重みのみを更新することで、生徒モデルが教師モデル並みにアップデートされていく。
プルーニング
- 巨大なニューラルネットワークにおいて寄与率が低く、影響力の小さなパラメータは不要と判断し、不要なパラメータを持つニューロンを削除する手法。(論文では、パラメータ値:0.1以下を削除)割とスカスカなニューラルネットワークになる。
- 結構なニューロンが削除されても、精度が変わらないため、高速化と省メモリが期待できる。
E資格学習 深層学習 Day4 ① 強化学習+②AlphaGo
強化学習
要点まとめ
- Q学習と関数近似法を組み合わせた手法の登場により、急速に発展してきた。
- 人間の世界で例えるとこんな感じ:ビジネスマン(エージェント)が常に変化の激しい(状態S)会社(環境)において、どうすれば物が売れるかを考えながら(方策Π)、マーケティング施策を企画・実行し(行動)、職場で成果をあげることでボーナスを得られ(正の報酬)、それによりモチベーションがさらに高まる(価値V)
- 強化学習では、初期の段階では不完全な知識をもとにランダムに行動し、徐々にベターな方向に行動指針を変えていくイメージ。ただし、過去のデータのみを頼りにしすぎると、「探索が足りない状態」となり、一方で未知の行動のみ取り続けると、「過去の成功体験を活かせない状態」となり両者はトレードオフの関係になる。
- 強化学習では、報酬をもらえるようにどのように行動すべきかを考える①方策(Π)と、エージェントにとっての②価値(V)の2つが学習ターゲットとなる。
- 方策 → 方策関数:Π(s, a)
- 価値 → 行動価値関数:Q(s, a )
方策関数
- 方策関数: その都度の行動をどうするかを決定する関数
- ある状態でどのような行動をとるのか確率を与えている。
- 価値関数と密接に関わっている。
→価値関数の価値を最大化するための方策を方策関数で決定する。
価値関数(状態価値関数 or 行動価値関数)
何がエージェントにとって良いことなのか = 価値。
どの情報を使って価値とみなすかによって、以下の2種類の考え方がある。
- 状態価値関数:環境の状態の価値にのみ注目する場合(例)どういう盤面が最も価値が高いか。
- 行動価値関数:環境の状態とエージェントの行動を組み合わせた価値に注目する場合(例)どういう盤面でどういう一手を打つのが最も価値が高いか。
- 価値関数では、ゴールまで今の施策を続けた時の報酬の予測値が得られる
(=やり続けると最終的にどうなるかがわかる)
- 昨今注目されているのは、行動価値関数
- 状態価値関数:
- 行動価値関数:
方策勾配法
- モチベーションとしては、方策関数を学習可能な関数にしたい。
- 方策関数πをθでのニューラルネットワークで考える。
- θの学習を で更新できるようにする。
- θ=重みパラメータw、J =誤差Eのようなイメージだが、強化学習の場合は、θを期待収益とすることで、最小化ではなく最大化する方向で学習を考える。
(実例1)AlphaGo Lee
- 2つの畳み込みネットワークで構成されている → 方策関数(Policy Net) と価値関数(Value Net)
- Policy Netの構造:
- 入力値は19×19×48ch(石、着手履歴、故宮展、取れる石の数 etc)の盤面情報
- Conv層(カーネル:5×5、ストライド:1、パディング:2 192ch)→ReLU
- Conv層(カーネル:3×3、ストライド:1、パディング:1 192ch)→ReLU ×11回
- Conv層(カーネル:1×1、ストライド:1)
- SoftMax
- 出力(19×19マスの着手予想確率が出力される。どこに打てばよいか)
- Value Netの構造:
(実例2)AlphaGo Zero
- AlphaGo Leeと違い、教師あり学習は一切使わない。
- 人間が目利きして入力値にしていた(ヒューリスティックな要素)盤面の特徴量を排除。→石の配置だけにした。
- PolicyNetとValueNetに分けずに一つに統合。
- Residual Network(ショートカット構造:スキップ接続)を39層つなげて導入したことで、深いネットワークでも勾配消失、勾配爆発を抑えることに成功するだけでなく、ネットワークの多くのバリエーションを生み出しアンサンブル的な効果も得た。(工夫点:WideResNet、PyramidNet)
- モンテカルロ木探索法のRoll Outシミュレーションをなくした。
- PolicyValueNetの構造:
- 入力値は19×19×17chの盤面情報
- Conv層(カーネル:3×3、ストライド:1、256ch)→ Batch Norm → ReLU
- Residual Block (Residual Network×39)
以下Policy と Valueは枝分かれして並列に処理される
↓Policy出力部分
↓Value出力部分
E資格学習 深層学習 Day3 自然言語処理(RNN, AE, Attention)
- Simple RNN
- RNNの課題感
- LSTM
- GRU
- 双方向RNN (Bi-directional RNN)
- Seq2Seq
- AE(オートエンコーダ=自己符号化器)とVAE
- Word2Vecの概要
- Attentionの概要
- その他調べたこと等
Simple RNN
要点まとめ
- 時系列データ処理に適したネットワーク。時系列データとは、時間的順序を追って一定間隔ごとに観察され、相互に統計的依存関係が認められるデータのこと(例)株価、音声データ、テキストデータ
- 時間tでの中間層での出力をt+1の中間層に反映させる。(数珠つなぎに過去の情報を遡って継承されている=再帰構造)
- RNNでは、以下3種類の重みがある。
- 入力層から中間層への重み W(in)
- 中間層から出力層への重みW(out)
- 中間層から次の中間層への重みW
yについて、を使って数式で表す。
(バイアスはb, c 活性化関数はf(x), g(x)とする。)
↓
u[:, t+1] = np.dot(X, W_in) + np.dot(z[:, t].reshape(1, -1),W) + b z[:, t+1] = sigmoid(u[:, t+1]) #f(u)がシグモイド関数の場合 v[:, t+1] = np.dot(z[:, t].reshape(1, -1), W_out) + c y[:, t+1] = sigmoid(v[:, t+1]) #g(v)がシグモイド関数の場合
構文木:再帰的に文章全体の表現ベクトルを得るプログラム
def traverse(node): if not isinstance(node, dict): v = node else: left = traverse(node["left"]) right = traverse(node["right"]) v =_activation(W.dot(np.concatenate([left, right])) #左と右とのベクトル結合 return v
BPTT(back propagation through time)
- RNNでの時間軸方向の逆伝播のこと。
- 上記の数式から、損失関数に対して重みで偏微分すると以下のようになる。
(ただし、∂E/∂u = δ^tとする)
↓
勾配計算式
- ]
- ]
- ]
def bptt(cs, ys, W ,U ,V): hiddens, outputs = rnn_net(xs, W, U, V) dW = np.zeros_like(W) dU = np.zeros_like(U) dV = np.zeros_like(V) do = _calculate_do(outputs, ys) batch_size, n_seq = ys.shape[:2] for t in reversed(range(n_seq)): dV += np.dot(do[:, t].T, hiddens[:, t]) / batch_size delta_t = do[:, t].dot(V) for bptt_step in reversed(range(t+1)): dW += np.dot(delta_t.T, xs[:, bptt_step])/ batch_size dU += np.dot(delta_t.T, hiddens[:, bptt_step-1])/ batch_size delta_t = delta_t.dot(U) return dW, dU, dV
RNNの課題感
勾配消失
勾配爆発
#勾配クリッピングの実装 def gradient_clipping(grad, threshold): norm = np.linalg.norm(grad) #与えられた勾配のノルムを算出 rate = threshold /norm #スレッショルドよりもノルムが大か小かが決まる。 if rate < 1: #もしrateが1よりも小さい=ノルムの方が大きい場合は return grad * rate #勾配にrateをかけることでthresholdまで値を小さくして出力する return grad #rateが1よりも大きい場合はそのままgradを出力する
LSTM
要点まとめ
入力ゲート
- 情報を取捨選択しながらCECに入力するゲート。どれをどのくらい使うか。
忘却ゲート
- CECから不要な記憶を忘却させるゲート。
- あってもなくても影響がないような言葉は、忘却ゲートで処理されるように学習させる。
出力ゲート
- CECからの情報のうち、どれだけ次の隠れ状態に情報を渡すか調整する
ピープホールつきLSTM(覗き穴結合)
- CECの状態を各ゲートから覗き見る機能をつける手法も編み出された。
LSTMとCECの課題
- パラメータ数が増えたことにより計算量が多くなるのが課題。
【実装】
def lstm(x, prev_h, prev_c, W, U, b): lstm_in = _activation(x.dot(W.T)+prev_h.dot(U.T)+b) a, i, f, o = np.hsplit(lstm_in, 4) a = np.tanh(a) input_gate = _sigmoid(i) forget_gate = _sigmoid(f) output_gate = _sigmoid(o) #セルの状態を更新し、中間層の出力を計算する c=input_gate * a + forget_gate *c h=output_gate * np.tanh(c) return c,h
GRU
要点まとめ
- LSTMとの違い:CECは持たない。LSTMはパラメータ多いがGRUはパラメータ少ないため計算量が少なく済むのがメリット。LSTMでは3つのゲートであったが、GRUではresetゲートとupdateゲートの2つになった。
resetゲート
- 過去の隠れ状態をどれだけ反映させるかを決めるゲート
update gateの2つのゲートに集約。
- 隠れ状態を更新するゲート
双方向RNN (Bi-directional RNN)
要点まとめ
- 過去→未来の情報だけでなく未来→過去の情報も加味することで精度を向上させる。
- 順方向へ接続するRNNと、逆方向へ接続するRNNの両方を使い結果をマージする。
- 実用例としては、機械翻訳など。
Seq2Seq
要点まとめ
- 入力データ(時系列)から異なるデータ(時系列)を生成するモデル。RNNを用いた、Encoder-Decoderモデルの一種であり、EncoderRNNとDecoderRNNの2つのニューラルネットワークを組み合わせる。機械翻訳に使われる。
- EncoderRNNによって最終的に文脈(コンテキスト)がベクトルとして表現され、その意味ベクトルから、新たな系列データをDecoderRNNが生成する。
EncoderRNNの意味集約とは
DecoderRNNの系列生成
- EncoderRNNの最終状態(意味ベクトル)をもとに系列を生成する。
- Embeddingの逆方向に変換していく。=Detokenize
HREDとVHRED
- Seq2Seqでは、1問1答しかできないが、少し前の会話の文脈に沿った文章を生成できるようになったのがHRED。
- HREDでは、結構ありがちな会話しかできなくなった。そのような当たり障りのない単語以上の出力を得られるようになったのがVHRED。
AE(オートエンコーダ=自己符号化器)とVAE
- 画像などをEncoderで潜在変数zに変換し、その後Decoderで元に戻す。
- AEのように、データを変換して結局元に戻してなんの意味があるのか?→ 入力データを情報量を落とさずに圧縮変換することに成功できれば、次元削減に使うことができる技術。
- VAEでは、上記の自動符号化器に確率分布を導入した手法。潜在変数zに、確率分布を仮定し、似たような入力データは似たようなベクトルのまま押し込めることを可能にした。Encoderの出力にノイズを与えた上で潜在変数zを出力することで、より汎用性が高くなる。
Word2Vecの概要
- 文字データを分散表現ベクトルに変換する手法。ワンホットベクトルをそのまま使うとスパースすぎるので、Embedding表現による数百ベクトル程度で表現できる。機械学習で
- 実際にはWord2VecなどでEmbedding表現したデータをSeq2Seq等の入力データとして取り扱うことが多い。
Attentionの概要
- Seq2Seqでは、中間層の出力において、固定長のベクトルに変換していた。そのため1万語の入力も10単語の入力も同様の固定長ベクトルだったため、長文の場合、うまく翻訳することが難しかった。
- Attention機構では、上記の課題を解決し固定長ベクトルではなくなった。また、入出力単語間の、相互の関連度を重みとして学習するため、長文への対応を可能にした。
その他調べたこと等
BPTTの課題の解決アプローチ(教師強制)
- BPTTでは、並列処理が不可能。(全ての中間状態を保存しておく必要があるためメモリコスト高い)そこで、前時刻の正解ラベルを予め入力として使うことで、並列処理が可能にする「教師強制」という手法がある。
- 教師強制では、「訓練時」に前時刻の正解ラベルを参照し、「テスト時」には前時刻の出力層の状態を参照する。教師強制がない場合は、いずれも前時刻のRNNユニットの状態を参照する。
BPTTの課題の解決アプローチ(Truncated BPTT)
- 全ての時刻の中間状態を保存しておく課題を解決するために、RNNユニット間の逆伝播の接続を切る方法(順伝播の接続はそのままにしておく)
Leaky接続
- Leaky接続では、前時刻からの情報の入力をα倍、入力層からの接続を1-α倍して過去の情報と現在の情報の割合を調節する手法。αが0になるほど過去の情報を破棄させる効果がある。
PyTorchでの実装例
Simple RNN
import torch import torch.nn as nn SimpleRnn = nn.RNN( input_size=3, hidden_size=2) input = torch.randn(5,1,3) h0 = torch.randn(1, 1, 2) output, hn = SimpleRnn(input, h0) print(output.shape) print(hn.shape)
LSTM
import torch import torch.nn as nn lstm= nn.LSTM( input_size=10, hidden_size=20) #入力ベクトルと隠れベクトル次元数 input = torch.randn(5,3,10) #(単語数、バッチサイズ、入力ベクトル次元数) h0 = torch.randn(1, 3, 20) #(RNNユニット数、バッチサイズ、隠れベクトル次元数) c0 = torch.randn(1, 3, 20) #(RNNユニット数、バッチサイズ、隠れベクトル次元数) output, (hn, cn) = lstm (input, (h0, c0)) print(output.shape) print(hn.shape)
双方向RNN
import torch import torch.nn as nn birnn= nn.RNN( input_size=3, hidden_size=2, bidirectional=True) input = torch.randn(5,1,3) h0 = torch.randn(2, 1, 2) #双方向のためRNNユニット数は2になっている output, hn = birnn(input, h0) print(output.shape) print(hn.shape)
E資格学習 深層学習 Day2 ② CNN概要
CNNの概要
要点まとめ
- 畳み込みニューラルネットワーク。画像処理によく使われる。
- 汎用性が高く、画像のみとは限らない。(1次元データ:音声、2次元データ:フーリエ変換した音声、3次元データ:CTスキャン、動画データ等も扱うことができる)
- 次元間でつながりのあるデータを扱うことができる(画像データの場合、隣り合うピクセル同士には繋がりが強い)
- 畳み込みで使われるフィルターが、いわゆる「重み」パラメータとして学習するものに該当する。
- 前半部分は、次元のつながりを保ったまま特徴を抽出するレイヤー構造(畳み込み+プーリング )となっており、後半は全結合層により、人間がほしい情報(確率、数値等)に変換するためのレイヤー構造となっている。
(例)LeNetの構造
- 入力層(32×32:1024個の画像データ)→畳み込み(28×28×6 :4,704個のデータ) →サブサンプリング(14×14×6:1,176個のデータ)→畳み込み(10×10×16:1,600個のデータ)→サブサンプリング(5×5×16:400個のデータ)→全結合層(120×1: 120個のデータ)→全結合層(84×1:84個のデータ)→最終出力層(10個:数値データ)
(例)AlexNetのモデルについて
- 入力層(224×224×3chの画像データ)→11×11のフィルタで畳み込み(55×55×96ch) →5×5でmaxプーリング (27×27×256ch)→3×3でmaxプーリング (13×13×384ch)→3×3で畳み込み(13×13×384ch)→13×13×256ch→Fratten(数値配列43,264個から、4,096個に変換)→全結合層(4096個のデータ)×2 →最終出力層(1000個)
畳み込みレイヤーの演算(Convolutions)
- 畳み込み演算とは、フィルターをずらしながら入力値の特徴量を抽象化するイメージ。(x方向、y方向にストライド分ずらしながら変換することで、縦・横方向の次元のつながりを保持しながら特徴抽出しているといえる。)
- 画像では、縦、横、チャンネルの3次元のデータをフィルターで読み取る。フィルターにははじめ乱数で重みの初期値が割り当てられているが、学習によって最適化されていく。フィルタを何種類も用意して、1つの画像を読み取ることもある。
- 入力値とフィルターサイズから、畳み込み後の出力サイズを算出する計算式
(カーネルサイズ=フィルタサイズ)
例)6×6の画像を2×2のフィルタで畳み込む。ストライドとパディングは1とする。
答え) 高さ:(6+2×1 - 2)/ 1 +1 =7 幅も高さと同じ計算 →よって、7×7。
- 畳み込み演算を効率化するために4次元の入力データを2次元に展開し、コンピュータ上での行列計算に適した形に変換するim2col という手法がとられる。
- 全結合層に渡すには、Frattenの他にGlobal MaxPoolingやGlobal AvgPoolingなどもある。
(各チャンネルごとの最大値or平均値のみを入力値として使う。Frattenよりも情報量はすくなくなるが、うまく学習がいくことが多い)
プーリング 層の全体像
- 畳み込み演算と組み合わせて使われるが、畳み込みとは異なり、重みを使って演算するわけではない。
- 演算処理としては、Max Pooling や Avg Poolingのように最大値、平均値を抽出するだけの処理をしている。
【実装】
#畳み込みレイヤーの実装 class Convolution: # W: フィルター, b: バイアス def __init__(self, W, b, stride=1, pad=0): self.W = W self.b = b self.stride = stride self.pad = pad # 中間データ(backward時に使用) self.x = None self.col = None self.col_W = None # フィルター・バイアスパラメータの勾配 self.dW = None self.db = None def forward(self, x): # FN: filter_number, C: channel, FH: filter_height, FW: filter_width FN, C, FH, FW = self.W.shape N, C, H, W = x.shape # 出力値のheight, width out_h = 1 + int((H + 2 * self.pad - FH) / self.stride) out_w = 1 + int((W + 2 * self.pad - FW) / self.stride) # xを行列に変換 col = im2col(x, FH, FW, self.stride, self.pad) # フィルターをxに合わせた行列に変換 col_W = self.W.reshape(FN, -1).T out = np.dot(col, col_W) + self.b # 計算のために変えた形式を戻す out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2) self.x = x self.col = col self.col_W = col_W return out
#Max プーリング 層の実装 lass Pooling: def __init__(self, pool_h, pool_w, stride=1, pad=0): self.pool_h = pool_h self.pool_w = pool_w self.stride = stride self.pad = pad self.x = None self.arg_max = None def forward(self, x): N, C, H, W = x.shape out_h = int(1 + (H - self.pool_h) / self.stride) out_w = int(1 + (W - self.pool_w) / self.stride) # xを行列に変換 col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad) # プーリングのサイズに合わせてリサイズ col = col.reshape(-1, self.pool_h*self.pool_w) # 行ごとに最大値を求める arg_max = np.argmax(col, axis=1) out = np.max(col, axis=1) # 整形 out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2) self.x = x self.arg_max = arg_max return out