【ラビットチャレンジ】 stage4 - 深層学習後編 (day3)

#ラビットチャレンジ

ラビットチャレンジはE資格の受験に必要な協会認定講座のプログラムです。
ラビットチャレンジでは認定プログラムを修了してE資格の受験資格を習得するために、指定のレポートとテストで合格点を取得しなければなりません。

https://ai999.careers/rabbit/

今回はStage4深層学習後編(day3)についてまとめます。

- [ ] Stage1 応用数学
- [ ] Stage2 機械学習
- [ ] stage3 深層学習前編(day1, day2)
- [x] stage4 深層学習後編(day3, day4)

---

#Sectoin1 再帰型ニューラルネットワークについて
時系列データに対応可能な、ニューラルネットワークをRNN(Recurrent Neural Network)という。
時系列データとは、時間的順序を追って一定間隔ごとに観察され、しかも相互に統計的依存関係が認められる。
具体的には音声データやテキストデータなどが当てはまる。

RNNの特徴として時系列モデルを扱う。
時系列モデルを扱うには、初期の状態と過去の時間$t-1$の状態を保持し、そこから次の時間での$t$を再帰的に求める再帰構造が必要になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/d7ab0728-b768-9f0c-2057-e401637c7d0c.png)


RNNではパラメータ調整法の一種でBPTT(Back Propagation Through Time)を使用する。

#Section2 LSTM (Long Short Term Memory)
RNNでは時系列を遡れば遡るほど、勾配が消失していく。
つまり長い時系列の学習が困難になる。
そこでLSTMでは構造自体を変更して解決している。

LSTMの全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/da6087ac-c400-0e24-a6d3-6f71e8763310.png)


###CEC (constant error carousel)
今までの値を記憶するのみで学習特性はない。
CECだけでは記憶しかできないが、周りに学習機能を持たせた取り巻きを増やすことで、何を覚えるのか？どう使うのか？を制御してもらう。
課題としては、入力データについて時間依存度に関係なく重みが一律になってしまう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/1240766b-91cc-0c38-5dc4-26821e7ec02e.png)



###入力ゲートと出力ゲート
入力ゲートは、今回のデータをどのような感じで覚えればよいのか指示する。
どのような感じか良いのかは入力ゲートが学習する。

出力ゲートはCECの情報をどう使うかを指示する。
どう使うかは出力ゲートが学習する。

入力ゲートと出力ゲートを追加することで、それぞれのゲートへの入力値の重みを重み行列$W$,$U$で下辺可能とする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/3cee6a2b-8fb9-c34e-6094-15f2a30d604c.png)

###忘却ゲート
CECには過去の情報が全て保管されいる。
CEC内に残っている情報は不要であっても使われてしまうが、削除することはできない。
そこで忘却ゲートがどの程度思えるのかを指示し情報の削除(忘却)を可能にした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/b691d365-5ed9-f5b7-6c4e-b11da5569456.png)


###のぞき穴結合
CEC自身の値も学習に使用したい。
そのためCEC自身の値に、重み行列を介して伝播可能にした構造

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/214d4452-7f71-c79b-41bc-c0334c428be4.png)


#Section3 GRU (Gated Recurrent Unit) 
LSTMではパラメータ数が多く計算負荷が高くなる問題があった。
しかしGRUではそのパラメータを大幅に削減し、精度は同等以上が望めるようにすることができた。
また、計算負荷も低い。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/37228001-2d99-110d-6d0a-c68f4dc236c9.png)


#Section4 双方向RNN
過去の情報だけでなく、未来の情報を加味することで精度を向上させるためのモデル。
文章の推敲や、機械翻訳などに使用される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/d97ae225-df0f-c250-c17d-db7f03c26db3.png)

#Section5 Seq2Seq
Encoder-Decoderモデルの一種を指す。
器械対話(チャットボット)や、機械翻訳などに使用されている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/6192ef6d-47ed-5d00-970a-070996b80979.png)


###Encoder RNN
ユーザーがインプットしたテキストデータを単語などのトークンに区切って渡す構造。

Talking:文章を単語などのトークン毎に分割しトークン毎のIDに分割する。
Enbedding:IDから、そのトークンを表す分散表現ベクトルに変換する。
Encoder RNN:ベクトルを順番にRNNに入れていく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/c53eb520-487b-0879-2f8e-b8f18d57ee83.png)


※単語の意味が似ているとembeddingの値は近くなる

vec1をRNNに入力し、hidden stateを出力。
このhidden stateと次の入力vec2をまたRNNに入力してきたhidden stateを出力。
これを繰り返す。

最後のvecを入れたときのhidden stateをfinal stateとして取っておく。
このfinal stateがthought vectorと呼ばれ、入力した文の意味を表すベクトルとなる。

###Decoder RNN
システムがアウトプットデータを単語などのトークン毎に生成する構造。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/dd489f90-91da-3ab0-b1b3-13ff32ba27dd.png)


Decoder RNN:Encoder RNNのfinal state(thought vector)から、各トークンの生成確率を出力する。
final stateをDecoder RNNのinitial stateとして設定し、Embeddingを入力する。

2. Sampling:生成確率に基づいてトークンをランダムに選択。
3. Embedding: 2で選ばれたトークンをEnbeddingしてDecoder RNNへの次への入力とする。
4. Detokenize:1~3を繰り返し、2で得られたトークンを文字列に直す。

###HRED
Seq2Seqでは一問一答しかできない。
つまり、問に対して文脈もなく、ただ応答が行われるだけになる。
HREDでは過去$n-1$個の発話から次の発話を生成する。
Seq2Seqでは、会話文の文脈無視で応答なされていたが、HREDでは前の単語の流れに即して応答されるため、より人間らしい文章が生成される。
HREDとはSeq2Seq+Context RNNでできている

Context RNN:Encoderのまとめた各文章の系列をまとめて、これまでの会話コンテキスト全体を表すベクトルに変換する構造。
つまり、過去の発話の履歴を加味した返答ができる。

しかしHREDは確率的な多様性が字面にしかなく、会話の流れのような多様性がない。
同じコンテキスト(発話リスト)を与えられても答えの内容が毎回会話の流れとしては同じものしか出せない。
結果として短く情報量に乏しい答えをしがちになる。
ex)「うん」「そうだね」「・・・」など

###VHRED
VHREDとはHREDにVAEの潜在変数の概念を追加したもの。
VAEの潜在変数の概念を追加することでHREDの課題を解決した。

###オートエンコーダー
オートエンコーダーとは教師なし学習の一つ。
オートエンコーダーは入力データから潜在変数Zに変換するニューラルネットワークをEncoder、逆に潜在変数Zをインプットとして元画像を復元するニューラルネットワークをDecoderと呼ぶ。
このときzの次元数が入力データより小さい場合に次元削減を行うことができる。

入力 ⇒ Encoder ⇒ 潜在変数z ⇒ Decoder ⇒ 出力

###VAE
通常のオートエンコーダーでは潜在変数zがどのような構造なのかはわからない。
VAEではこの潜在変数に確率分布z~N(0,1)を仮定しデータを確率分布という構造にすることを可能にしている
学習時にはzの前後にノイズをいれて汎化させる。

#Section6 Word2Vec
RNNでは単語のような可変長の文字列をNNに与えることはできないため、固定長形式で単語を表す必要がある。
word2vecではボキャブラリx任意の単語ベクトル次元で重み行列を作る。

#Section7 AttentionMechanism
seq2seqでは長い文章への対応が難しい。
2単語であっても。100単であっても固定次元ベクトルの中に入力しなければならない。
Attention mechanismでは「入力と出力のどこ単語が関連しているのか」の関連度を学習する仕組みで、文章が長くなるほどそのシーケンスの内部表現の次元を大きくすることができる。




#確認テスト
###畳み込み後のサイズ (11P)
問：サイズ5x5の入力画像を、サイズ3x3のフィルタで畳み込んだ時の出力サイズを答えよ。
なおストライドは2、パディングは1とする

公式

```math
\frac{画像の高さ+2\times パディング高-フィルタ高}{ストライド}+1
```

当てはめると

```math
\frac{5+2\times 1-3}{2}+1 = 3
```

###RNNの重み (23P)
問：RNNのネットワークには大きく分けて3つの重みがある。
1つは入力から現在の中間層を定義する際にかけられる重み。
1つは中間層から出力を定義する際にかけられる重みである。
残り一つについて説明せよ。

解答：前回の中間層から、現在の中間層にかけられる重み

###連鎖率 (36P)
連鎖率の原理を使い、dz/dxを求めよ。

```math
z=t^2\\
t=x+y
```

```math
\frac{\partial z}{\partial x} = \frac{\partial z}{\partial t}\frac{\partial t}{\partial x}\\
\frac{\partial z}{\partial t} = 2t\\
\frac{\partial t}{\partial x} = 1\\
\frac{\partial z}{\partial x} = \frac{\partial z}{\partial t}\frac{\partial t}{\partial x} = 2t \times 1 = 2(x+y)
```

###RNN (45P)
問：下図の$y_1$を$x z_0 z_1 w_{in} w w_{out}$を用いて数式で表せ。
※バイアスは任意の文字で定義せよ
※また、中間層の出力にシグモイド関数g(x)を作用させよ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/1ab564f0-3287-d3ce-471d-b8e0bf98096d.png)

```math
u^t  = w_{in}x^t+wz^{t-1}+b\\
z^t = f(u^t)\\
v^t=w_{out}z^t+c\\
y^t=g(v^t)=g(w_{out}z^t+c)\\
y_1 = g(w_{out}z_1+w_{in})
```


###シグモイド関数 (62P)
問：シグモイド関数を微分した時、入力値が0の時に最大値をとる。
その時の値として正しいものを選べ。
解答：0.25

公式

```math
f(x) = \frac{1}{1+e^{-x}}\\
f(x)'=(1-f(x))f(x)
```

x = 0を当てはめると

```math
f(0) = \frac{1}{1+e^{-0}}\\
f(0)'=(1-f(0))f(0)=(1-0.5)(0.5) = 0.25

```

###LSTM (78P)
問：以下の文章をLSTMに入力し空欄にあてはまる単語を予測したいとする。
文中の「とても」という言葉は空欄の予測において無くなっても影響を及ぼさないと考えられる。
このような場合、どのゲートが作用すると考えられるか。
「映画おもしろかったね。ところで、とてもお腹が空いたから何か____。」
解答：忘却ゲート
忘却ゲートが「ところで」に対する重要度をなくすことで予測への影響をなくしている。

###LSTMとCEC (88P)
問：LSTMとCECが抱える問題について、それぞれ簡潔に述べよ
解答：LSTMはパラメータ数が多く、計算負荷が高い
CECは不要になった情報を破棄することができず、不要になっても使われてしまう。

###LSTMとGRU (92P)
問：LSTMとGRUの違いを簡潔に述べよ
解答：LSTMではパラメータ数が多かったが、GRUではゲートを減らすことでパラメータ数を大幅に減らして計算負荷を少なくしている。

###seq22seq (109P)
問：下記の選択肢からseq2seqについて説明しているものを選べ。
1) 時刻に関して順方向と逆方向のRNNを構成し、それら2つの中間層表現を特徴量として利用するものである。
2) RNNを用いたEncoder-Decoderモデルの一種であり、機械翻訳などのモデルに使われる。
3) 構文木などの木構造に対して、隣接単語から表現ベクトル(フレーズ)を作るという演算を再帰的に行い(重みは共通)、文全体の表現ベクトルを得るニューラルネットワークである。
4) RNNの一種であり、単純なRNNにおいて問題となる勾配消失問題をCECゲートの概念を導入することで解決したものである。

解答：2

###Seq2Seq,HRED,VHRED
問：seq2seqとHRED、HREDとVHREDの違いを簡潔に述べよ
解答：seq2seqは一問一答でHREDは文脈の意味を加味している
HREDが短い返事しかできない問題に対して改良したのがVHRED

###VAE
問：VAEに関する下記の説明文中の空欄にあてはまる言葉を答えよ。
自己符号化器の潜在変数に_____を導入したもの。
解答：確率分布

###RNN, wor2vec, seq2seq, attention
問：RNNとwor2vec, seq2seqとattentionの違いを簡潔に述べよ
解答：
・RNNは時系列データを処理するのに適したNN
・word2vecは単語の分散表現ベクトルを得る手法
・seq2seqは一つの時系列データから別の時系列データを得る
・Attentionは時系列データの中身に対して関連性を得る

#実装演習
###RNN (3_1_simple_RNN.py)

横軸が学習回数、縦軸が損失を表す
![3_1_result01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/28ec0579-20cc-b9be-3117-2eb0b155c1b2.png)

実験
weight_init_stdやlearning_rate, hidden_layer_sizeを変更してどうなるのか確認する

weight_init_stdを変えてみる(デフォルト1

|1|2|4|8|
|:-:|:-:|:-:|:-:|
|![3_1_result01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/7cb30594-feab-ce44-c66c-ee835cda5f6b.png)|![3_1_result02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/52f1b03b-79e4-ff9b-8dea-e7c400e39386.png)|![3_1_result04_0.4_64.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/3b4bd769-25bd-c9be-61ec-2a622a06a6fd.png)|![3_1_result08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/5e33dc42-78d1-8443-f974-84b619d123f9.png)|

learning_rateを変えてみる(デフォルト0.1

|0.1|0.2|0.4|0.8|
|:-:|:-:|:-:|:-:|
|![3_1_result01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/48e21162-97b7-a244-7495-8b52fb8d6052.png)|![3_1_result01_0.2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/5360fb09-429f-5364-f29b-f58c303e89fe.png)|![3_1_result01_0.4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/1809dcdb-c9e2-a93b-ca57-8ff118fce48c.png)|![3_1_result01_0.8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/20c17a95-b834-e393-dda4-22bd7b53c321.png)|

これは効果が高い

hidden_layer_sizeを変えてみる(デフォルト16

|16|32|64|96|
|:-:|:-:|:-:|:-:|
|![3_1_result01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/f29a8336-e04d-5105-ba4e-2a555aaeaeda.png)|![3_1_result01__32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/9eee450c-8d3a-120e-6426-1d82e3936b53.png)|![3_1_result01__64.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/c3902b9a-904a-84dd-2645-5f58df33a7f9.png)|![3_1_result01__96.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/97180fbf-d683-e052-be9e-3ae9a23e240e.png)|

いろいろ変更
・weight_init_std：1⇒1
・learning_rate：0.1⇒0.8
・hidden_layer_size：16⇒32

![3_1_result01_0.8_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/f79586e9-4e1f-9a33-948b-17de7e498740.png)

1,000エポックあたりですでに安定している

次に重みの初期化方法を変更する

|無し|Xavier|He|
|:-:|:-:|:-:|
|![3_1_result01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/2da069c7-1205-453c-000a-ee7eb1747328.png)|![3_1_result01_Xavier.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/0b3c9a29-5d8d-a3b5-415f-2905b971cccc.png)|![3_1_result01_He.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/7640c9e5-499c-426f-19dc-ce80ad43e903.png)|

出だしが安定している


最後に中間層の活性化関数を変更する

|sigmoid|ReLU|tanh|
|:-:|:-:|:-:|
|![3_1_result01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/497cc15d-fc07-9302-58e5-b0136c3dfffe.png)|![3_1_result01_LeRU.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/b8bf1e52-8a08-c78d-9b40-83158bd70326.png)|![3_1_result01_tanh.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/85ba3f27-d7d4-f537-19b8-c34d8691ffc1.png)|

ReLUの場合に誤差が1.0で固まってしまっている

#演習チャレンジ

### 表現ベクトル(26P)
以下は再起型ニューラルネットワークにおいて構文木を入力として再帰的に文全体の表現ベクトルを得るプログラムである。ただしニューラルネットワークの重みパラメータはグローバル変数として定義しており、activation関数はなんらかの活性化関数であるとする。機構増は再帰的な辞書で定義してあり、rootが最も外側の辞書であると仮定する。
(く)にあてはまるのはどれか？

```Python
def traverse(node):
    if not isinstance(node, dict):
        v = node
    else:
        left = traverse(node['left'])
        right = traverse(node['right'])
        v = _activation('[く]')
    return v
```

解答

```Python
        v = _activation(W.dot(np.concatenate([eft, right]))
```

表現ベクトルから表現ベクトルを作る作業は隣接している表現Left,rightを合わせたものを特徴量としてそこに重みを掛けることで実現する。

### BPTT (53P)
BPTTを行うプリグラムで簡単化のため活性化関数は恒等関数であるとする。また、calculate_dout関数は損失関数を出力に関して偏微分した値を返す関数であるとする。(お)にあてはまるはどれか？

```python
def bptt(xs, 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 reserved(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 = '(お)'
    return dW, dU, dV
```

解答

```Python
            delta_t = delta_t.dot(U)
```

RNNでは中間層出力が過去の中間層出力に依存する。
RNNにおいて損失関数を重みWやUに関して偏微分するときは、それを考慮する必要があり、過去に差上るたびにUが掛けられる

### 勾配のクリッピング (64P)
RNNや深いモデルでは勾配の消失または爆発が起こる傾向がある。勾配爆発を防ぐために勾配のクリッピングを行うという手法がある。具体的には勾配のノルムがしきい値を超えたら、勾配のノルムをしきい値に正規化するというものである。以下は勾配のクリッピングを行う関数である。
(さ)にあてはまるのはどれか。

```Python
def gradient_clipping(grad, threshold):
    norm = np.linalg.norm(grad)
    rate = threshold / norm
    if rate < 1:
        return '(さ)'
    return grad
```

解答

```Python
        return gradient*rate
```

勾配のノルムがしきい値より大きい時は勾配のノルムをしきい値に正規化するのでクリッピングした勾配は勾配x(しきい値/勾配のノルム)となる

### LSTM (79P)
以下はLSTMの順伝播を行うプログラムである。ただし_sigmoid関数は要素ごとにシグモイド関数を作用させる関数である。
(け)にあてはまるのはどれか

```ptyhon
def lstm(x, prev_h, prevc, 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 = '(け)'
    h = output_gate * np.tanh(c)
    return c, h
```

解答

```ptyhon
    c = input_gate * a + forget_gate * c
```

新しいセルの状態は計算されたセルへの入力と1ステップ前のセルの状態に入力ゲート、忘却ゲートを掛けて足し合わせたものと表現される

### GRU(90P)
GRUもLSTMと同様にRNNの一種であり、単純なRNNにおいて問題となる勾配消失問題を解決し、長期的な依存関係を学習することができる。LSTMに比べ変数の数やゲートの数が少なく、より単純なモデルであるが、タスクによってはLSTMより良い性能を発揮する。以下のプログラムはGRUの順伝播を行うプログラム(こ)にあてはまるのはどれか

```Python
def gru(x, h, W_r, U_r, W_z, U_z, W, U):
    r = _sigmoid(x.dot(W_r.T) + h.dot(U_r.T))
    z = _sigmoid(x.dot(W_z.T) + h.dot(U_z.T))
    h_bar = np.tanh(x.dot(W.T) + (r * h).dot(U.T))
    h_new = '(こ)'
    return h_new
```

解答

```Python
    h_new = (1 - z) * h + z * h_bar
```

新しい中間状態は、1ステップ前の中間表現と計算された中間表現の線形和で表現される


### 双方向RNN (95P)
以下は双方向RNNの順伝播を行うプログラムである。順方向については、入力層への重みW_f、1ステップ前の中間層出力から中間層への重みをU_f、逆方向に関しては同様にパラメータW_b, U_bを持ち、両者の中間層表現を合わせた特徴から出力層への重みはVである。_rnn関数はRNNの順伝播を表し中間層の系列を返す関数であるとする。(か)にあてはまるのではどれか。

```Python
def bidirectional_rnn_net(xs, W_f, U_f, W_b, U_b, V):
    xs_f = np.zeros_like(xs)
    xs_b = np.zeros_like(xs)
    for i, x in enumrate(xs):
        xs_f[i] = x
        xs_b[i] = x[::-1]
    hs_f = _rnn(xs_f, W_f, U_f)
    hs_b = _rnn(xs_b, W_b, U_b)
    hs = ['か' for h_f, h_b in zip(hs_f, hs_b)]
    ys = hs.dot(V.T)
    return ys
```

解答

```Python
    hs = [np.concatenate([h_f, h_b[::-1]], axis = 1) for h_f, h_b in zip(hs_f, hs_b)]
```

双方向RNNでは順方向と逆方向に伝播したときの中間層表現を合わせたものが特徴量となる


###Encoder (110P)
機械翻訳タスクにおいて、入力は複数の単語からなる文章であり、それぞれの単語はone-hotベクトルで表現されている。Encoderにおいて、それらの単語は単語埋め込みにより特徴量に変換され、そこからRNNによって時系列の情報を持つ特徴へとエンコードされる。以下は入力である文章を時系列の情報をもつ特徴量へとエンコードする関数である。ただし、_activation関数は何らかの活性化関数を表すとする。
(き)にあてはまるのはどれか。


```Python
def encode(words, E, W, U, b):
    hidden_size = W.shape[0]
    h = np.zeros(hidden_size)
    for w in words:
        e = '(き)'
        h = _activation(W.dot(e) + U.dot(h) + b)
    return h
```

解答

```Python
        e = E.dot(w)
```

tangowはone-hotベクトルであり、それを単語埋め込みにより別の特徴量に変換する。これは埋め込み行列Eを用いてE.dot(w)と書ける