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になる重みを各系列データ毎に割り当てることで、入力値のどの部分に注目するのかを、注目する度合いを分散する仕組み。
  • 辞書オブジェクトともいえる。Query, Key, Valueの3要素があり、検索したい入力クエリをもとに、キーの中からクエリに似た文章をValueから出力する仕組み。
  • query(入力)とKey を掛け合わせてSoftmaxをかませる→ Attention Weight。Attentino Weight ・ Valueから、最も類似しているValueを返す。
  • Self-Attention(自己注意)とSource Target Attentionの違い:Source Targetでは、Queryにターゲット、Key, Valueはソースデータを使う。自己注意ではQuery Key Valueは全て同じ情報が与えられる。 Attention(Q, K, V) = softmax (\frac {QK^T}{\sqrt d_k} )V
  • Mulihead Attentionでは、Scaled Dot Attentionを8個分concatする。

f:id:rakurakura:20220106154901p:plain

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 ⑥ 物体検知とセグメンテーション

物体検知

要点まとめ
  • 画像分類(Classification):写っている画像の正解ラベルを出力する
  • 物体検知(Object Detection):バウンディングボックス により、大枠の領域で何がどの辺りに写っているかを出力する。
  • セグメンテーション(意味領域と個体領域):ピクセル単位でラベル付けし、何がどこに写っているのか輪郭まで出力する。同じ種別のモノをまとめて認識する方法(Semantic Segmentation)と、同じ種別のモノでも個体単位(Instance Segmentation)で識別するかによって難易度も変わる。
代表的なデータセット

いずれもコンペで使用されている。学習したいタスクによってデータセットを選択する

評価指標について
  • 一般的なクラス分類の評価指標では「混同行列」が使われる。この時、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)という。 mAP =1/c \sum AP_i
  • Conf閾値(β)を変化させていったときのAPではなく、IoUの閾値を変化させた場合のAPを計算したものを、 mAP_{coco}と言われ、MS COCOで導入された評価軸。よりIoUの精度を厳しく見られている。
  • 検出精度だけでなく、検出速度(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時の位置情報を維持する仕組みもある。

f:id:rakurakura:20220106125647g:plain

代表的なセグメンテーションのモデル
  • 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
  • フィルタ数(カーネルのチャネル)は1chのカーネル(K×K×1)で畳み込みすることで、出力マップの計算量は、H×W×C×K×Kとなる。(空間方向の畳み込み)
Pointwise Convolution
  • カーネルサイズが1(1×1×C)フィルタ数:フィルタ数(カーネルのチャネル)=Mで畳み込みする。チャンネル方向の情報が集約される。出力マップは、H×W×Mとなる。

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 といった正規化の手法がある。

f:id:rakurakura:20220105170237p:plain

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を分割して学習させることが多い)
  • 大きなモデル(学習パラメータが膨大)であればあるほど効果は大きい。

GPU

  • GPGPU(グラフィック以外の用途で使用されるGPUの総称:General-purpose on GPU
  • GPGPUの環境:CUDA or OpenCL。CUDAはNVIDIAGPUのみで使用可。OpenCLAMDやARM等のGPUでも使用可。主流はCUDA。
  • CPU:高性能なコアが少数。複雑で連続的な処理が得意
  • GPU:低性能なコアが多数。簡単な並列的な処理が得意(行列演算など)

軽量化の技術

要点まとめ

  • GPUサーバのような高度な演算機でなくても、スマホのような端末でも機械学習の恩恵を受けるようにするにはどうすればよいか、というモチベーション。

量子化

  • そもそもなぜ演算処理が増えるかというと、深層学習における大量のパラメータを保存するためのメモリ消費が要因。そこで、パラメータを64bit浮動小数点ではなく、32bitなど下位にに落とすことで省メモリ化により演算処理の削減を行える。

蒸留

  • 規模の大きなニューラルネットワークの既存モデルから、知識を継承しながら軽量なモデルをつくること。
  • 教師モデル(複雑なモデルor アンサンブルなモデル)と生徒モデル(シンプルかつ軽量)の2つのモデルで構成される。両方のモデルにデータを入力し学習を進めていくが、教師モデル側の重みは固定しつつ、生徒モデル側の重みのみを更新することで、生徒モデルが教師モデル並みにアップデートされていく。

プルーニング

E資格学習 深層学習 Day4 ① 強化学習+②AlphaGo

強化学習

要点まとめ
  • 強化学習は、教師あり学習教師なし学習とは少し毛色が違う。データのパターンを見つけ出し、予測するわけではない。報酬というモチベーションをもとに、優れた方策(行動を決定する指針)を見つけることが目標。
  • Q学習と関数近似法を組み合わせた手法の登場により、急速に発展してきた。
  • 人間の世界で例えるとこんな感じ:ビジネスマン(エージェント)が常に変化の激しい(状態S)会社(環境)において、どうすれば物が売れるかを考えながら(方策Π)、マーケティング施策を企画・実行し(行動)、職場で成果をあげることでボーナスを得られ(正の報酬)、それによりモチベーションがさらに高まる(価値V)
  • 強化学習では、初期の段階では不完全な知識をもとにランダムに行動し、徐々にベターな方向に行動指針を変えていくイメージ。ただし、過去のデータのみを頼りにしすぎると、「探索が足りない状態」となり、一方で未知の行動のみ取り続けると、「過去の成功体験を活かせない状態」となり両者はトレードオフの関係になる。
  • 強化学習では、報酬をもらえるようにどのように行動すべきかを考える①方策(Π)と、エージェントにとっての②価値(V)の2つが学習ターゲットとなる。
  1. 方策 → 方策関数:Π(s, a) 
  2. 価値 → 行動価値関数:Q(s, a )
方策関数
  • 方策関数: π(s) = a その都度の行動をどうするかを決定する関数
  • ある状態でどのような行動をとるのか確率を与えている。
  • 価値関数と密接に関わっている。

 →価値関数の価値を最大化するための方策を方策関数で決定する。

価値関数(状態価値関数 or 行動価値関数)

何がエージェントにとって良いことなのか = 価値。
どの情報を使って価値とみなすかによって、以下の2種類の考え方がある。

  1. 状態価値関数:環境の状態の価値にのみ注目する場合(例)どういう盤面が最も価値が高いか。
  2. 行動価値関数:環境の状態とエージェントの行動を組み合わせた価値に注目する場合(例)どういう盤面でどういう一手を打つのが最も価値が高いか。
  • 価値関数では、ゴールまで今の施策を続けた時の報酬の予測値が得られる

 (=やり続けると最終的にどうなるかがわかる)

  • 昨今注目されているのは、行動価値関数
  1. 状態価値関数: V^{n}(s)
  2. 行動価値関数: Q^{n}(s, a)
方策勾配法
  • モチベーションとしては、方策関数を学習可能な関数にしたい。
  • 方策関数πをθでのニューラルネットワークで考える。
  • θの学習を  θ^{t+1} = θ^{t}+ε∇J(θ) で更新できるようにする。
  • θ=重みパラメータw、J =誤差Eのようなイメージだが、強化学習の場合は、θを期待収益とすることで、最小化ではなく最大化する方向で学習を考える。
(実例1)AlphaGo Lee
  • 2つの畳み込みネットワークで構成されている → 方策関数(Policy Net) と価値関数(Value Net)
  • Policy Netの構造:
  1. 入力値は19×19×48ch(石、着手履歴、故宮展、取れる石の数 etc)の盤面情報
  2. Conv層(カーネル:5×5、ストライド:1、パディング:2 192ch)→ReLU
  3. Conv層(カーネル:3×3、ストライド:1、パディング:1 192ch)→ReLU ×11回
  4. Conv層(カーネル:1×1、ストライド:1)
  5. SoftMax
  6. 出力(19×19マスの着手予想確率が出力される。どこに打てばよいか)

  1. 入力値は19×19×49ch(Policy Netの入力に加え、「手番」が追加)の盤面情報
  2. Conv層(カーネル:5×5、ストライド:1、パディング:2 192ch)→ReLU
  3. Conv層(カーネル:3×3、ストライド:1、パディング:1 192ch)→ReLU ×11回
  4. Conv層(カーネル:1×1、ストライド:1)
  5. Fratten →全結合層 256
  6. 全結合層 1
  7. TanH関数(-∞〜∞ を-1 ~ 1に変換)
  8. 出力(-1 ~ 1の勝つか、負けるかの勝率を出力とする)
  • どうやって学習させるのか?
  • 強化学習をいきなり実施するのは計算量が非常に多くコストが高い。そこで、方策関数については、人間と人間の対局データ(3000万局面分の教師データ)を使い、手番を予測する教師あり学習を先に施行している。また、RollOutPolicyという工夫により、手番予測について3ミリ秒→3マイクロ秒にスピード化実現。
  • 教師あり学習で、RollOutPolicy(手番出力の高速版)とPolicyNetを学習 → 強化学習でPolicy Net →強化学習Value Netの順番で学習させる。
  • 価値関数の更新時にモンテカルロ木探索」が使われている。
(実例2)AlphaGo Zero
  • AlphaGo Leeと違い、教師あり学習は一切使わない。
  • 人間が目利きして入力値にしていた(ヒューリスティックな要素)盤面の特徴量を排除。→石の配置だけにした。
  • PolicyNetとValueNetに分けずに一つに統合。
  • Residual Network(ショートカット構造:スキップ接続)を39層つなげて導入したことで、深いネットワークでも勾配消失、勾配爆発を抑えることに成功するだけでなく、ネットワークの多くのバリエーションを生み出しアンサンブル的な効果も得た。(工夫点:WideResNet、PyramidNet)
  • モンテカルロ木探索法のRoll Outシミュレーションをなくした。
  • PolicyValueNetの構造:
  1. 入力値は19×19×17chの盤面情報
  2. Conv層(カーネル:3×3、ストライド:1、256ch)→ Batch Norm → ReLU
  3. Residual Block (Residual Network×39)

以下Policy と Valueは枝分かれして並列に処理される
↓Policy出力部分

  1. Conv層(カーネル:1×1、ストライド:1、2ch)→Batch Norm →ReLU
  2. Fratten →全結合層 362
  3. SoftMax
  4. Policy出力

Value出力部分

  1. Conv層(カーネル:1×1、ストライド:1)→Batch Norm →ReLU
  2. Fratten →全結合層 256
  3. 全結合層 1
  4. TanH関数(-∞〜∞ を-1 ~ 1に変換)
  5. 出力(-1 ~ 1の勝つか、負けるかの勝率を出力とする)

E資格学習 深層学習 Day3 自然言語処理(RNN, AE, Attention)

Simple RNN

要点まとめ
  • 時系列データ処理に適したネットワーク。時系列データとは、時間的順序を追って一定間隔ごとに観察され、相互に統計的依存関係が認められるデータのこと(例)株価、音声データ、テキストデータ
  • 時間tでの中間層での出力をt+1の中間層に反映させる。(数珠つなぎに過去の情報を遡って継承されている=再帰構造)
  • RNNでは、以下3種類の重みがある。
  1. 入力層から中間層への重み W(in)
  2. 中間層から出力層への重みW(out)
  3. 中間層から次の中間層への重みW

f:id:rakurakura:20220104142935p:plain

yについて、 z^{t-1}, z^{t+1}, w_{in}, w, w_{out}を使って数式で表す。
(バイアスはb, c 活性化関数はf(x), g(x)とする。)

  •  u^t = W_{in}x^t + Wz^{t-1} + b
  •  z^t = f(W_{in}x^t + Wz^{t-1} + b) = f(u^t )
  •  v^t = W_{out}z^t + c
  •  y^t = g(W_{out}z^t + c) = g(v^t )
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とする)

勾配計算式

  •  ∂E/ ∂W_{in} = ∂E/ ∂u^t \cdot  ∂u^t/ ∂W_{in}  = δ^t [x^t]
  •  ∂E/ ∂W_{out} =∂E/ ∂v^t \cdot ∂u^t/ ∂W_{out} = δ^{out, t} [z^t ]
  •  ∂E/ ∂W =  ∂E/ ∂u^t \cdot ∂u^t/ ∂W = δ^{t} [z^{t--1} ]
  •  ∂E/ ∂b  =  δ^t × 1 = δ^t
  •  ∂E/ ∂c  =  δ^{out, 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の課題感

勾配消失
  • RNNの課題は、時系列を遡れば勾配が消失していくことがあり、長い系列の学習が困難となる。勾配消失は、0~1の間が微分値が複数掛け合わされることで起こる。シグモイド関数の場合微分値は最大でも0.25。)
勾配爆発
  • 微分値が1よりも大きくなる場合、勾配消失とは逆に、勾配が大きくなりすぎる問題もある。これが勾配爆発と呼ぶ。
  • これを防ぐ方法として、勾配クリッピングという手法がある。
#勾配クリッピングの実装
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

要点まとめ
  • 勾配消失を解決したのがLSTM。CECという機構により、メモのように情報を記憶しておく機能を導入した!
  • 特長:3つのゲート(忘却ゲート、入力ゲート、出力ゲート)とCECを持つ。
  • 学習と記憶の機能を分離させるイメージ。(CEC自体には学習機能がないため、ゲートによって重みを学習させる。)
入力ゲート
  • 情報を取捨選択しながらCECに入力するゲート。どれをどのくらい使うか。
  •  g = tanh (x_t W_x^g + h_{prev}W_h^g + b^g)
  •  i = σ (x_t W_x^i + h_{prev}W_h^i + b^i)
  •  g \odot i
忘却ゲート
  • CECから不要な記憶を忘却させるゲート。
  • あってもなくても影響がないような言葉は、忘却ゲートで処理されるように学習させる。
  •  f= σ (x_t W_x^f + h_{prev}W_h^f + b^f
出力ゲート
  • CECからの情報のうち、どれだけ次の隠れ状態に情報を渡すか調整する
  •  o= σ (x_t W_x^o + h_{prev}W_h^o + b^o
ピープホールつき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の意味集約とは
  • 自然言語処理では、単語をベクトルに変換して入力値とする。単語ごとに数字を割り振るとone-hotベクトルとして表現することができるが、これではあまりにもスパースで無駄が多いので、Embeddingにより数万→数百程度までベクトルの大きさが小さくできる。
  • このEmbedding処理には機械学習が用いられ、うまくできると意味の近さを抽出することができる。
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つの画像を読み取ることもある。
  • 入力値とフィルターサイズから、畳み込み後の出力サイズを算出する計算式

 \boldsymbol {出力サイズ = \frac {入力値+2×パディングサイズ-カーネルサイズ}{ストライド} +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