Tue, Feb 27, 2018

CourseraのDeep Learning SpecializationのSequence Modelsコースを修了した

CourseraのDeep Learning SpecializationのSequence Modelsコースを修了した

CourseraのDeep Learning SpecializationのConvolutional Neural Networksコースを修了したのに続き、Sequence Modelsコースを修了した。

このコースは、RNNの原理、代表的なアーキテクチャ、自然言語処理などについて学べる3週間のコース。 生成モデルが色々出てきて面白い。 動画は今のところ全部英語。

2018/2/6に始めて、2/27に完了。 22日間かかった。 修了したらまたCertifacateもらえた。

また、これでDeep Learning Specializationのすべてのコースを修了したので、全部まとめたCertifacateももらえた。 結局2ヶ月ほどかかり、1万円以上課金してしまった…

以下、3週分の内容をメモ程度に書いておく。

  • 1週目

    連続データを扱うシーケンス(Sequence)モデルについて学ぶ。 RNN、LSTM、GRU、BRNN。

    • 動画

      • 再帰型ニューラルネットワーク(Recurrent Neural Network)

        シーケンスモデルにはRNNなどがあって、音声認識(Speech recognition)や自然言語処理(Natural language processing)に使われる。 音楽生成(Music generation)、感情分類(Sentiment classification)、DNA解析(DNA sequence analysis)、動画行動認識(Video Activity Recognition)、固有表現抽出(Named entity recognition)なんてのも。

        入力だけが連続データだったり、出力だけが連続データだったり、両方だったり。

        自然言語処理では、ボキャブラリ(Vocabulary)を使って単語をone hotベクトルにして扱う。 ボキャブラリは普通5万次元くらいのベクトル。 ボキャブラリにない単語はそれ用(unknown)の次元に割り当てる。

        入力や出力の次元がサンプルごとに違うので、普通のNNは使えない。 また、普通のNNだと、文のある個所から学んだ特徴を他の箇所と共有しない。 また、普通のNNだと、入力サイズが大きすぎて、パラメータが多くなりすぎる。 RNNはこうした問題を持たない。

        RNNは、最初の単語xを受け取り、層で計算し、最初の出力yとアクティベーションaを出し、そのaと次のxを同じ層で受け取り、次のyとaをだす、ということを繰り返す。 xにかける重みをWax、aにかける重みをWaa、yにかける重みをWyaと呼ぶ。 あとaとyを計算するときに足すバイアスがあって、それぞれba、by。 あるxの計算をするときに、その前のxも使うので、連続データ処理に向いてる。 けど、後のxを考慮しないところが欠点。 この欠点に対処したのがBRNN(Bidirectional RNN)。

        RNNのaの活性化関数にはtanhがよく使われる。 ReLUもあり。 yには二値分類だったらシグモイドだし、そうでなければソフトマックスとか。

        損失関数は普通に交差エントロピーでいいけど、yがベクトルなので、その各要素について交差エントロピーを計算して、足し合わせたものが損失になる。 ここから逆伝播するんだけど、その際に連続データを過去にさかのぼるので、時をかける逆伝播(BPTT: Backpropagation through time)と呼ばれる。

        上で説明したRNNは、入力と出力が同じ長さだけど、そうでない問題のほうが多い。 感情分類なんかは、任意の長さの文を入力して、5段階評価とかするので、最後のxまで入力した後で一つだけyを出すようにする。 音楽生成なんかは入力が一つで出力が多いので、入力がない部分は前の出力を代わりに入力する。 翻訳みたいに入力と出力の長さが違うときは、前半入力だけして、後半出力だけする。

      • 言語モデル(Language model)

        ある文のあとに、どんな分が続くかを確率で示してくれるモデル。

        訓練データは、文をトークンに分解してone-hotベクトルにして、最後にEOSトークンを加えて作る。

        モデルは、RNNの出力をボキャブラリと同じサイズのソフトマックスにして、どの単語の確率が高いかを出力させる。 最初に0ベクトルを入力し、その出力を次の入力にして、それを繰り返す。

        単語じゃなくて文字単位でやるモデルもあるけど、あんまり使われない。

        このモデルを使うと、学習した文章に似た雰囲気の分を生成できる。 このとき、出力したベクトルから、各単語の確率にしたがって単語をサンプリングし、それを次の入力にする。

      • RNNの勾配消失

        英語の文だと、主語が単数だと動詞の形が変わるんだけど、主語と動詞がすごい離れていることがありうる。 最初のほうの単語である主語は浅い層(初期のステップ)で処理されて、一方動詞は深い層(あとのほうのステップ)で処理されることになる。 すると、勾配消失により、深い層の単語が浅い層から受ける影響が小さくなってしまって、動詞の形をいい感じに学習できない。 これがナイーブなRNNの欠点。

        勾配爆発も起こり得るけど、Gradient clipping、つまり勾配の値を計算した後に値が閾値を超えていたら修正する手法を使えば比較的簡単に回避できるので、勾配消失が深刻。

      • GRU(Gated Recurrent Unit)

        RNNにMemory cell©というアイデアを加えたもの。 cは浅い層の情報を深い層に伝える役目をして、勾配消失問題を緩和する。 cの候補は毎回、前回のcとxの線形変換をtanhに入れたものとして生成され、それを、0か1を返すゲートΓu(シグモイドな感じの関数)で実際にcとして使うかを決めて、cを更新していく。 このゲートを更新ゲート(Update gate)という。 cはソフトマックスに入れてyを出力したり、次のステップのaにする。

        実際には、もう一つ関連ゲート(Relevance gate)Γrってのがあって、cの候補を計算するときに前回のcに掛ける。

      • LSTM(Long short term memory)

        RNNの勾配消失に対処するまた別のアイデア。 GRUよりパワフル。 けどGRUより古くからあるもので、GRUがそれのシンプル版という関係。

        LSTMの論文はかなりむずい。

        GRUと比べると、まずΓrはない。

        で、GRUはΓuが1だったらcを更新して、0だったら前のを保持するという感じだったけど、LSTMでは忘却ゲート(Forget gate)Γfに前回のcをかけて、捨てるかどうかを決める。

        また、出力ゲート(Output gate)Γoが追加されて、単にcを次のaにするんじゃなくて、Γo*cをaにする。

        各ゲートは前のaと今回のxの線型結合にバイアスを加えたものをシグモイドして計算する。 ゲートの計算にcもいれることがあって、のぞき穴接続 (Peephole connection)と呼ばれる。

        基本的にはLSTM使えばいいけど、GRUのほうが計算コストが少なくて大きなネットワーク作りやすいから、GRUのほうがいいこともある。

      • BRNN(Bidirectional RNN)

        普通にRNNやった後、後ろの入力から順番に逆向きにRNNする。 yは、順向きのaと逆向きのaとバイアスを線形計算して非線形変換したものになる。

      • Deep RNN

        単にyを出力するんじゃなくて、そのyを入力とする別のRNNを積み上げていくとdeepになる。 RNNは時間軸の方向にすでに深いので、出力方向には普通は2、3個だけ積み上げる。

        出力方向にRNNを積み上げる代わりに、出力を普通のNNにいれるってのもある。

    • プログラミング課題

      3つもある…

      • ベーシックRNNとLSTMの順伝播をNumPyで実装

      • 恐竜の名前を生成する言語モデルをNumPyで実装

        Gradient clippingとサンプリングを実装して、モデルを訓練しながら、恐竜の名前をいい感じに生成できるようになっていく様を観察する。

      • LSTMの音楽生成モデルをKerasで実装

        Jazzの曲の断片を学習させて、それっぽい曲を生成してみる。

  • 2週目

    自然言語処理。

    • 動画

      • 単語埋め込み(Word embedding)

        1週目でやったように、単語をone-hotベクトルで表すと、単語同士の積が0になって、単語間の関係(距離)が表せられない。 代わりに、単語を特徴のベクトルにして、各次元に特徴量をもたせる、特徴付き表現(Featurized representation)がある。

        単語の特徴は数百とかにするけど、可視化するために2Dにすることがある。 このための代表的なアルゴリズムがt-SNE。 t-SNEは複雑で非線形な処理をするので、後述の類推には使えないけど、似たような単語のクラスタを観察できる。

        特徴の分布を表すN次元の空間に単語を埋め込むため、単語埋め込みという。 このN次元ベクトルを単語の数だけ結合したものをEで表す。

        この表現形式にすると、大量の適当なテキストデータで学習させたり、学習済みのモデルをダウンロードしたりしたあと、特定のタスクのために転移学習させることができる。

        また、類推(Analogical reasoning)が可能になる。 単語のペアが二つあって、ペア内の単語ベクトル間の差を計算して、ペア間でその値が近ければ、それらのペアは同じような関係の組み合わせだと言える。 例えば、男 - 女王 - 女王に近くなる。 類似度の計算にはコサイン類似度(Cosine similarity)がよく使われる。 ユークリッド距離(Euclidean distance)でもいいけど、コサイン類似度のほうが一般的。

      • 単語埋め込みの学習

        Eにone-hotベクトルをかけると、そのone-hotベクトルが表す単語の特徴ベクトルが得られる。

        数単語の後に続く単語を予測するニューラル言語モデルを考えると、与えられたそれぞれの単語を表すone-hotベクトルを入力して、Eをかける層があって、その結果を全結合層にいれて、その結果をボキャブラリサイズのベクトルを出力するソフトマックス層にいれる。 ソフトマックス層の出力で、一番大きい値の単語が予測する単語になる。 Eをパラメータにしておくと、このモデルを訓練するとEが学習される。 予測精度自体はそんなによくならなくても問題ない。

        予測する単語の前だけじゃなくて、前後の単語を使って学習させたりも。 1単語だけで予測しても結構いい結果になる。

        上記のモデルはSkip-Gramモデルと呼ばれる。 実際には、文の中からターゲット単語を選び、前後ウィンドウサイズ(5とか)以内のコンテクスト単語を一つ選び、それらをペアにして一つの訓練データを作る。 コンテクスト単語は完全にランダムに選ぶと、aとかtheとかofとかが多くなっちゃうので、ヒューリスティックにバランスよく選ぶ必要がある。

        ソフトマックス層は、ボキャブラリサイズのベクトルを出力するため、計算コストがでかい。 その対策として、階層的ソフトマックス(Hierarchical softmax)ってのがあって、これは木構造で分類を表す手法。 もう一つ負例サンプリング(Negative sampling)という手法があって、こっちのほうがシンプルで効果的。

        Skip-Gramモデルのほか、CBOW(Continuous Bag Of Words)というモデルもある。 これはターゲット単語とその前後数単語を訓練データにするもの。 これらのモデルをWord2Vecモデルといったり、それを使ってEを学習する手法をWord2Vecアルゴリズムといったりする。

        負例サンプリングではまず、コンテクスト単語に対して別の単語を与え、ターゲット単語なら1、違うなら0というラベル付き訓練データを作る。 ターゲット単語とコンテクスト単語の組はSkip-Gramと同様に選んで、0になる単語はランダムに選ぶ。 コンテクスト単語一つに対し、yが1になるデータを一つ、0になるデータをk個(2~20くらい)作る。 kはデータセットが大きいほど少なくする。 で、Skip-Gramモデルのソフトマックス層を、ボキャブラリの数だけの二値分類ノードに変える。 これらのノードは毎回全部計算するんじゃなくて、k+1個だけを計算するので計算コストが小さい。

        単語埋め込みの学習アルゴリズムとして、Word2Vecじゃないものだと、GloVe(Global Vectors)アルゴリズムってのがある。 GloVeでは、単語iと単語jが近くに現れることが何回あるかをxijで表す。 xijをボキャブラリのすべての単語の組み合わせで数えて、それらと何かの二乗誤差にヒューリスティックな重み付けをしたコスト関数を作って最適化して単語埋め込みを学習させる。 細かいことはよくわからなかった…

      • 感情分類

        Yelpみたいなサービスで、コメントから星の数を推定する。

        感情分類は、教師データがあまり得られないことが多いのが課題だけど、よく訓練したEがあれば上手く分類できる。

        コメントを単語に分解したら、それぞれのone-hotベクトルをEとかけて単語ベクトルを作って、単語間の平均を計算して、ソフトマックス層に入れて、出力を星の数だけ作る。 というのがシンプルなモデル。 これだと語順を考慮しないので、RNNに単語ごとに入力して、最後の出力をソフトマックスするといい感じになる。

      • 単語埋め込みからのバイアス除去(Debias)

        単語埋め込みに性別とか人種のバイアスがかかってると、いいモデルができない。 例えば、プログラマ - 男 + 女 = 主婦みたいになってるとまずい。 こういうバイアスは、学習データのテキストのバイアス、つまりそれを書いた人のバイアスからくる。

        単語空間のバイアスの方向を調べるには、例えば性別のバイアスなら、he - sheとかmale - femaleとかの平均をとる。 するとある1次元のベクトルが得られて、これがバイアスの方向になる。 この上にある値を他の次元の射影に変換することでバイアスを削減(Neutralize)できる。

        実際には、特異値分解(SVD: Singular Value Decomposition)でもっとシステマチックにバイアスを計算する。 バイアスも数次元のベクトルになりうる。

        Neutralizeしたらさらに、バイアスをなくす方向に単語の組の位置をずらす(Equalize pairs)。 例えば、男と女の組を、ベビーシッターからの距離に差がなくなるようにずらす。

        Neutralizeすべき単語は、線形分類で決めることができる(?)。 Equalizeすべき単語は手で選ぶ。

    • プログラミング課題

      • 訓練済みのGloVeの単語埋め込みをロードして、コサイン類似度計算を実装して、類推を実行。

      • NeutralizationとEqualizationを実装。

      • 感情分類(文に自動で絵文字を付ける)の、シンプルな実装と2層LSTMでの実装。

  • 3週目

    連続データを入力して連続データを出力するRNNアーキテクチャを学ぶ。

    • 動画

      • Sequence to sequence(Seq2Seq)モデル

        翻訳などに使うモデル。 エンコーダ(encoder)ネットワークで元の単語を読み込み、デコーダ(decoder)ネットワークが翻訳を吐き出す。

        画像を読んでその説明文(caption)を吐くのにもつかわれる。 この場合、CNNで画像を読んで、最後の全結合層の出力をRNNに入れる。

        小説を生成するような言語モデルと異なるのは、ランダムに生成してほしいのではなくて、ベストな結果を生成してほしいところ。 デコーダネットワークの部分は言語モデルと一緒。 入力がエンコーダネットワークの出力か0ベクトルかの違いだけ。 このようなのを条件付き言語モデル(Conditional language model)と呼ぶ。

        言語モデルだと、次に出力する単語をランダムで選んでたけど、翻訳ではそうはいかない。 貪欲法(Greedy algorithm)みたいに、毎回一番確率が高いものを選んでもうまくいかない。 最終的に出力する全単語の確率の掛け合わせが最大になるやつを選びたい。

        ビームサーチ(Beam search)を代わりに使う。 ビーム幅(Beamwidth)Bを決めて、最初にBの数だけ単語の候補を確率の高い順に選ぶ。 で、それらを次の入力にしてB個の出力ベクトルを得たら、最初の単語の確立をそれらのベクトルの各要素にかけて、大きい順にまたB個単語を選ぶ。 あとはこれの繰り返し。

        ビームサーチをもう少し改良できる。 その一つが長さ正規化(Length normalization)。 条件付確率を計算するときに、単語が増えてくると1未満の数を何回もかけることになり、アンダーフローや丸め誤差が発生してしまう。 ので、各単語の確率をかけ合わせる代わりに、各確率のlogを足し合わせる。 これだとまだ、確率が1未満なのでlogは常に負の値になり、単語の数が少ない程総和は大きくなるので、翻訳結果に短い文が選ばれがちになっちゃう。 ので単語数で割る。 または単語数をα(0~1)乗したもので割る。 この式(目的関数)をNormalized log probability objectiveとかNormalized log likelihood objectiveとか呼ぶ。

        Bはどう選ぶか。 Bが小さいと計算コストが低く、最適解を見つける可能性が低い。 Bが大きいとその逆。 プロダクション環境では、10~100くらいが一般的。 研究では数千とかも。 試行錯誤していい値を見つけるしかない。

        深さ優先探索(DFS: Depth First Search)、幅優先探索(BFS: Breadth First Search)に比べて、ビームサーチは最適解を見つけられないかもしれないけど、リーズナブル。

        ビームサーチはヒューリスティックなアルゴリズムなので、いつもいい結果を出すとは限らない。 だめだったときはエラー分析をする。 ダメな翻訳が出力されたとき、RNNの訓練が足らないのか、ビームサーチのBが小さすぎるのかを切り分けたい。

        まずは、翻訳中のある単語について、期待する出力とモデルの出力の確率をそれぞれ見てみる。 前者が大きければ、モデルは正しい出力してるけど、ビームサーチが間違ったものを選択している。 逆ならモデルに問題がある。 (長さ正規化してたらその目的関数を比べる。) これを色んなサンプルで試して、より多くのサンプルでダメだったほうの改善に努めるべし。

        いい感じの訳が複数あったらどうする? Bleu(BiLingual Evaluation Understudy)スコアで評価する。 Bleuスコアは、生成した訳が期待する訳(リファレンス)のいずれかに近ければ高くなる。

        precisionは、生成した訳の単語のいくつが、リファレンスにも出現するかを示す。 これだと、例えばthe the the is the theみたいな訳で高い値(6/6)をとれちゃう。 のでmodified precisionを代わりに使う。 modified precisionでは、それぞれの単語について、一つのリファレンスに最大何回出現するかをcreditと定義する。 で、theのcreditが2、isのcreditが1なら、上記訳のmodified precisionは3/6になる。

        Bleuスコアは、Nグラム(N-Gram)についてmodified precisionを計算する。 あるN-Gramについてのmodified precisionをPn、N-Gramの数をNとすると、Bleuスコアはexp((ΣPn)/N) * BP。 BPはbrevity penaltyで、短い訳で高いスコアを簡単に取れないようにするためのもの。 出力長がリファレンス長より長ければ1で、そうでなければexp(1 - 出力長 / リファレンス長)

      • Attentionモデル

        Seq2Seqを改良したもの。

        長い文の場合でも、Seq2Seqは原文を全部読んで、それをactivationに記憶して、そこから翻訳を出力する。 けど人が翻訳するときは、短い文や節に区切って出力していく。 実際、長い原文を記憶するのは難しく、原文の単語数が増えていくにしたがってSeq2Seqは性能が落ちる。 Attentionモデルは人と同様な翻訳の仕方をするので、原文の単語数が増えても性能を保てる。

        Attentionモデルでは、エンコーダはBRNN。 で、エンコーダはデコーダに対し、t番目の出力に際してどの入力単語に注目すべきかという重みづけαから生成するコンテクストcを入力する。 エンコーダの順方向と逆方向のアクティベーションを結合したものをaとすると、c = Σαa。 デコーダはそのcと、1ステップ前のアクティベーションsを使って一単語を出力する。 1ステップ前のsとaを小さいシンプルなNNにいれて入力単語ごとに計算したeを、入力単語に渡って足すと1になるようにスケールしたものがα。

        Attentionモデルは、画像を読んで説明文を出力するときにも使われる。 説明文は画像の特定の箇所に注目した説明の集まりなので。

      • 音声認識(Speech recognition)

        Seq2Seqモデルで、音声データを読んで字幕を出力する。 従来、音声を音素(Phoneme)に分解して処理していたが、深層学習ではその必要が無い。 但し300~100000時間くらいの音声データが要る。

        普通に音声データをエンコーダに時系列に従って入力してデコーダに文字を出力させてもいいけど、それだと、入力データのステップ数は出力ステップ数よりはるかに大きくなっちゃう。 ので、CTC(Connectionist temporal classification)モデルは、RNNに入力と同じだけ出力をさせて、その出力を圧縮して最終的な字幕を生成する。 例えばtheについて、ttt_h_eee___みたいな出力をさせる。 _はブランクという特殊な出力で、単語の切れ目のスペースとはまた別のもの。

        「OK Google」みたいなトリガーワードを識別するシステムに使うアルゴリズムはまだ発展途上で、これといったものはない。 例えば、トリガーワードを含む音声を入力して、トリガーワードの終わりの部分の出力を1、それ以外を0として学習させる方法がある。 終わりの瞬間だけ1にすると0ばっかりになっちゃうので、終わりから一定時間1にする。 トリガーワードを聞いた瞬間に検知するようにしたいので、BRNNじゃなくて単方向のRNNを使う。

    • プログラミング課題

      • Kerasで日付を特定のフォーマットに変換するAttentionモデルを作る。

      • トリガーワード検知システムを作る。

        トリガーワードとそれ以外の言葉とノイズを別々に録音して、合成してラベルを付けて訓練データを作る。 で、Kerasで1D畳み込み層がひとつ、GRUが2層のDeep RNNモデルを作って学習させる。