ディープラーニング 05-RNN リカレント ニューラル ネットワーク

概要

リカレント ニューラル ネットワーク (RNN) は、再帰接続を備えたニューラル ネットワーク構造であり、自然言語処理、音声認識、時系列データ分析などのタスクで広く使用されています。従来のニューラル ネットワークと比較して、RNN の主な特徴は、シーケンス データを処理し、シーケンス内のタイミング情報を取得できることです。

RNN の基本ユニットはリカレント ユニットで、前のタイム ステップからの入力と隠れ状態を受け取り、現在のタイム ステップの隠れ状態を出力します。従来の RNN では、再帰ユニットは通常、tanh や ReLU などの活性化関数を使用します。

基本的なリカレント ニューラル ネットワーク

原理

基本的なリカレント ニューラル ネットワーク構造は、入力層、隠れ層、出力層で構成されます。

ここに画像の説明を挿入します
  xxxは入力ベクトル、oooは出力ベクトル、sss は隠れ層の値を表します;UUUは入力層から隠れ層VVVは、隠れ層から出力層への重み行列です。リカレント ニューラル ネットワークの隠れ層の値 s は、現在の入力xxx 、最後の隠れ層ssの値にも依存しますs重み行列 W は、この入力の重みとしての隠れ層の最後の値です。
  
上図の基本的な RNN 構造を時間次元で展開します (RNN はチェーン構造であり、各タイム スライスは同じパラメーターを使用し、t は時間 t を表します): これで、より明確に見えます。このネットワークは、時間 t で入力 xt を受け取り
ここに画像の説明を挿入します
ます。 x_tバツその後、隠れ層の値はst s_tになります。s、出力値はot o_tですああ重要なポイントはst s_tですsx_tの値はxt だけに依存するわけではありませんバツst − 1 s_{t−1}にも依存します。st 1
公式1: st = f ( U ∗ xt + W ∗ st − 1 + B 1 ) s_t=f(U∗x_t+W∗s_{t−1}+B1)s=f ( Uバツ+Wst 1+B 1 )
式 2:ot = g ( V ∗ st + B 2 ) o_t=g(V ∗ s_t+B2)ああ=g ( Vs+B2 ) _

  • 式1はリカレント層である隠れ層の計算式である。U は入力 x の重み行列、W は最後の隠れ層の値S t − 1 S_{t−1}St 1今回の入力重み行列として、f は活性化関数です。
  • 式 2 は出力層の計算式、V は出力層の重み行列、g は活性化関数、B1 と B2 は 0 と仮定したバイアスです。

隠れ層には 2 つの入力があり、1 つ目は U とxt x_tです。バツベクトルの積、2 番目は前の隠れ層によって出力された状態st − 1 s_t−1ですs1とW直前に計算されたst − 1 s_t−1s1をキャッシュする必要があります。今回はxt x_tバツ一緒に計算し、最終的な結果を一緒に出力しますああ

式 1 を式 2 に繰り返し代入すると、次のようになります。
ここに画像の説明を挿入します
上記からわかるように、リカレント ニューラル ネットワークの出力値 ot は、前の入力値、,,,,,,, xt x_tの影響を受けます。バツxt − 1 x_{t−1}バツt 1xt − 2 x_{t−2}バツt 2xt − 3 x_{t−3}バツt 3、... 影響を受けるため、リカレント ニューラル ネットワークは任意の数の入力値を期待できるのです。これは実際には良くありません。前の値が後の値と関係がなく、リカレント ニューラル ネットワークが依然として前の値を考慮する場合、後の値の判断に影響を与えるからです。

以上が一方向単層NN全体の順伝播過程である。

input x 入力形式をより早く理解するために、nlp の Word Embedding を使用して説明しましょう。

単語の埋め込み

まず、入力テキスト x をコンピュータが読み取れる言語にエンコードする必要があります。エンコードするときは、文間の単語間に同様の行を維持することが期待されます。単語のベクトル表現は、機械学習と深層学習の基礎です。

単語埋め込みの基本的な考え方は、単語を意味空間内の点にマッピングし、単語を低次元の密な空間にマッピングすることです。このマッピングにより、意味的に類似した単語は意味空間内でより近くなります。閉じる、2 つの単語間の関係があまり近くない場合、ベクトルは意味空間内で比較的遠くにあります。

ここに画像の説明を挿入します
上図に示すように、英語とスペイン語は意味空間にマッピングされています。同じ意味を持つ数値は意味空間内で同じ分布位置を持ちます。単語の埋め込みについて簡単におさらいしてみましょう。nlp の場合、入力するのは離散記号です。ニューラルの場合
、ネットワーク、たとえば、ベクトルまたは行列を扱います。したがって、最初のステップでは、単語をベクトルにエンコードする必要があります。最も単純な方法は、ワンホット表現方法です。以下の図に示すように:
ここに画像の説明を挿入します
Python コード (ワンホット)、たとえば

import numpy as np

word_array = ['apple', 'kiwi', 'mango']
word_dict = {'apple': 0, 'banana': 1, 'orange': 2, 'grape': 3, 'melon': 4, 'peach': 5, 'pear': 6, 'kiwi': 7, 'plum': 8, 'mango': 9}

# 创建一个全为0的矩阵
one_hot_matrix = np.zeros((len(word_array), len(word_dict)))

# 对每个单词进行one-hot编码
for i, word in enumerate(word_array):
    word_index = word_dict[word]
    one_hot_matrix[i, word_index] = 1

print(one_hot_matrix)

出力:

[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]   #这就是apple的one-hot编码
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]    #这就是kiwi的one-hot编码
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]    #这就是mango的one-hot编码

行は各単語を表し、列はコーパスを表し、各列はコーパス単語 (特徴列) に対応します。

ワンホット エンコーディングはシンプルで効果的な特徴表現方法ですが、いくつかの欠点もあります。

  1. 高次元表現: ワンホット エンコーディングを使用する場合、各特徴は、その特徴の一意の値の数に等しい次元を持つ大きなスパース ベクトルを作成する必要があります。その結果、入力データが高次元になり、計算とストレージのオーバーヘッドが増加します。特に、多数の離散特徴を伴う問題を扱う場合、その結果、非常に大きな特徴空間が生じます。

  2. 次元の独立性: ワンホット エンコーディングは、特徴間の相関関係や意味関係を考慮せずに、各特徴を独立したバイナリ特徴として表します。これにより、モデルが特徴間の相互作用や相関関係を捉えることが困難になり、モデルのパフォーマンスに影響を与える可能性があります。

  3. 未知の特徴を処理できません: ワンホット エンコーディングでは、特徴の一意の値がトレーニング セットに表示される必要があります。そうでない場合、問題が発生します。トレーニング セットに現れない特徴値がテスト セットや実際のアプリケーションで見つかった場合、ワンホット エンコーディングを実行できず、モデルがこれらの未知の特徴を処理できなくなる可能性があります。

  4. 特徴の疎性: ワンホット エンコーディングの特徴ベクトルは疎であるため、ほとんどの要素は 0 であり、これによりデータの疎性が増加し、一部のアルゴリズム (線形モデルなど) で問題が発生する可能性があります。

要約すると、ワンホット コーディングは場合によってはシンプルで効果的な特徴表現方法ですが、特に高次元の離散特徴を扱う場合、特徴間の関係を考慮する場合、未知の特徴値を扱う場合にはいくつかの欠点もあります。問題が発生する可能性があります。

ワンホット エンコーディングの代わりに nn.Embedding を使用する主な理由は 2 つあります。

  1. 次元の柔軟性: ワンホット エンコーディングを使用する場合、各特徴は、その特徴の一意の値の数に等しい次元を持つ大きなスパース ベクトルを作成する必要があります。その結果、高次元の入力が発生し、計算とストレージのオーバーヘッドが増加します。埋め込みを使用すると、離散的な特徴を低次元の連続ベクトル表現にマッピングできるため、ストレージとコンピューティングのコストが削減されます。

  2. 意味的な関係と類似性: ベクトルを埋め込むことで、特徴間の意味的な関係と類似性をキャプチャできます。たとえば、自然言語処理タスクでは、埋め込みベクトルを使用すると、単語を連続ベクトル表現にマッピングできるため、同様の意味を持つ単語が埋め込み空間内でより近くに配置されます。このような機能は、モデルが機能間の関係をよりよく理解して学習し、モデルのパフォーマンスを向上させるのに役立ちます。

したがって、ワンホット エンコーディングの代わりに nn.Embedding を使用すると、特に高次元の離散特徴を扱う場合に、モデルの効率とパフォーマンスを向上させることができます。

さて、nn.embedding の 2 つのパラメーターの効果をプッシュする簡単な例を見てみましょう。

文分類タスクがあるとします。入力は文であり、各単語は特徴です。[「I」、「love」、「deep」、「learn」、「!」]の5つの単語があります。

nn.embedding を使用して、これらの単語を埋め込みベクトル (単語を指す座標系内の位置) にマッピングできます。各単語を 3 次元ベクトルとして埋め込むとします。ここで、num_embeddings は 5、つまり 5 つの異なる単語があることを意味し、embedding_dim は 3、つまり各単語が 3 次元ベクトルとして埋め込まれていることを意味します。

次の表を使用して、各単語の埋め込みベクトルを表すことができます。

言葉 埋め込みベクトル
"私" [0.1、0.2、0.3]
"愛" [0.4、0.5、0.6]
"深い" [0.7、0.8、0.9]
"学ぶ" [0.2、0.3、0.4]
「!」 [0.5、0.6、0.7]

nn.embedding を使用すると、文内の各単語を対応する埋め込みベクトルに変換できます。たとえば、「深層学習が大好きです!」という文は、次の一連の埋め込みベクトルに変換できます。

[[0.1, 0.2, 0.3]、[0.4、0.5、0.6]、[0.7、0.8、0.9]、[0.2、0.3、0.4]、[0.5、0.6、0.7]]

このようにして、離散的な単語の特徴を、深層学習モデルで使用するための連続的な埋め込みベクトルに変換できます。

以下は pytorch の使用法です ( Python 入門)

 # 创建词汇表
        vocab = {"I": 0, "love": 1, "deep": 2, "learning": 3, "!": 4}
        strings=["I", "love", "deep", "learning", "!" ]
        # 将字符串序列转换为整数索引序列
        input = t.LongTensor([vocab[word] for word in strings])
        #注意第一个参数是词汇表的个数,并不是输入单词的长度,你在这里就算填100也不影响最终的输出维度,这个输入值影响的是算出来的行向量值
        #nn.Embedding模块会随机初始化嵌入矩阵。在深度学习中,模型参数通常会使用随机初始化的方法来开始训练,以便模型能够在训练过程中学习到合适的参数值。
        #在nn.Embedding中,嵌入矩阵的每个元素都会被随机初始化为一个小的随机值,这些值将作为模型在训练过程中学习的可训练参数,可以使用manual_seed固定。
        t.manual_seed(1234)
        embedding=nn.Embedding(len(vocab),3)
        print(embedding(input))

出力結果は次のとおりです:
tensor([[-0.1117, -0.4966, 0.1631],
[-0.8817, 0.0539, 0.6684], [
-0.0597, -0.4675, -0.2153],
[ 0.8840, -0.7584, -0.3689],
[- 0.3424、-1.4020、0.3206]]、grad_fn=)

Embedding の最初のパラメータは入力文字の長さではなく、語彙の長さであることに注意してください。たとえば、語彙
{"I": 0, "love": 1, "deep": 2, " があります。 learning": 3, " !": 4}、入力は次のようになります。i love。この時点では、最後の隠れ層の予測には完全な接続が必要であるため、2 ではなく 5 を渡す必要があります。語彙全体のすべての単語に対する現在の入力単語の確率。

パイトーチrnn

以下は、pytorch rnn に慣れるために、pytorch で rnn を使用する最も簡単な例です。pytorch
の rnn は、隠れ層から出力層へのロジックを処理しないことに注意してください。隠れ層の出力結果のみに焦点を当てています。非表示レイヤーを結果出力に変換する必要がありますが、完全に接続されたレイヤーを追加できますが、ここではこの部分には焦点を当てません。

#%%

import torch
import torch.nn as nn

# 定义输入数据
input_size = 10   # 输入特征的维度
sequence_length = 5   # 时间步个数
batch_size = 3   # 批次大小

# 创建随机输入数据
#输入数据的维度为(sequence_length, batch_size, input_size),表示有sequence_length个时间步,
#每个时间步有batch_size个样本,每个样本的特征维度为input_size。
input_data = torch.randn(sequence_length, batch_size, input_size)
print("输入数据",input_data)
# 定义RNN模型
# 定义RNN模型时,我们指定了输入特征的维度input_size、隐藏层的维度hidden_size、隐藏层的层数num_layers等参数。
# batch_first=False表示输入数据的维度中批次大小是否在第一个维度,我们在第二个维度上。
rnn = nn.RNN(input_size, hidden_size=20, num_layers=1, batch_first=False)
"""
在前向传播过程中,我们将输入数据传递给RNN模型,并得到输出张量output和最后一个时间步的隐藏状态hidden。
输出张量的大小为(sequence_length, batch_size, hidden_size),表示每个时间步的隐藏层输出。
最后一个时间步的隐藏状态的大小为(num_layers, batch_size, hidden_size)。
"""
# 前向传播,第二个参数h0未传递,默认为0
output, hidden = rnn(input_data)
print("最后一个隐藏层",hidden.shape)
print("输出所有隐藏层",output.shape)

# 打印每个隐藏层的权重和偏置项
# weight_ih表示输入到隐藏层的权重,weight_hh表示隐藏层到隐藏层的权重,注意这里使出是转置的结果。
# bias_ih表示输入到隐藏层的偏置,bias_hh表示隐藏层到隐藏层的偏置。
for name, param in rnn.named_parameters():
    if 'weight' in name or 'bias' in name:
        print(name, param.data)

出力

最后一个隐藏层 torch.Size([1, 3, 20])
输出所有隐藏层 torch.Size([5, 3, 20])

ここに画像の説明を挿入します

なぜ重みが 10 行 20 列なのか?パラメーター畳み込みニューラル ネットワークの原理
データの最も外側の行の長さによって順伝播時系列の数が決まります。
この input_size は入力データの次元です。たとえば、単語がワンホットに変換された後の列は、辞書の特徴長です。この hidden_​​size は、隠れ層
ニューロンの数であり、入力された特徴の数です。最後の隠れ層へ。
num_layer には、多層の隠れ層が積み重ねられています。

一般的な構造

RNN (リカレント ニューラル ネットワーク) の一般的に使用される結果タイプには、単一入力単一出力、単一入力複数出力、複数入力複数出力、および複数入力単一出力が含まれます。以下では、各結果の種類とその使用方法について詳しく説明します。

  1. 単一入力単一出力 (SISO): これは最も一般的な RNN 結果タイプであり、入力はシーケンスであり、出力は単一の予測値です。たとえば、テキストが与えられた場合は次の単語を予測し、シーケンス データの期間が与えられた場合は次のタイム ステップの値を予測します。このタイプの結果は、言語モデル、時系列予測など、多くのシーケンス予測タスクに適しています。
    たとえば、家の価格を予測したい場合、家の面積、寝室の数、バスルームの数などの複数の特徴を使用できます。このようにして、これらの特徴をモデルの入力として特徴ベクトルに結合することができ、モデルの出力は予測された住宅価格になります。したがって、線形回帰を使用して、単一の出力に対する複数の特徴の問題を解決できるため、単一入力単一出力モデルと呼ばれます。

  2. 単一入力複数出力 (SIMO): この結果タイプでは、入力はシーケンスですが、出力は複数の予測値です。たとえば、テキストが与えられると、次の単語とその単語の品詞タグが同時に予測され、オーディオ信号が与えられると、発話の感情と話者のアイデンティティが同時に予測されます。この結果タイプは、複数の関連タスクを同時に予測する必要がある状況に適しています。

  3. 複数入力複数出力 (MIMO): この結果タイプには、複数の入力シーケンスと複数の出力シーケンスがあります。たとえば、機械翻訳タスクでは、入力はソース言語の一連の文、出力はターゲット言語の一連の文です。対話システムでは、入力はユーザーからの一連の質問です。出力はシステムからの一連の応答です。この結果タイプは、複数の入力および出力シーケンスを処理する必要があるタスクに適しています。Mimo には、入力および出力数が等しい場合と異なる場合の 2 つのタイプがあります。

  4. 複数入力単一出力 (MISO): この結果タイプでは、複数の入力シーケンスがありますが、出力は 1 つだけです。たとえば、画像記述生成タスクでは、入力は画像シーケンス、出力は画像の記述ですが、自動運転では、入力は複数のセンサーからのデータ シーケンス、出力は車両制御コマンドです。この結果タイプは、複数の入力シーケンスを 1 つの出力シーケンスにマッピングする必要があるタスクに適しています。

線形回帰は、入力に複数の特徴を指定できる単純な機械学習モデルですが、出力は 1 つだけです。ここでの「単一入力単一出力」とは、モデルの入力がベクトル (複数の特徴の組み合わせ) であり、出力がスカラー (予測値) であることを意味します。線形回帰では、入力特徴を線形に組み合わせて予測値を取得します。したがって、入力は複数の要素にすることができますが、出力は 1 つだけです。

ここに画像の説明を挿入します

双方向リカレントニューラルネットワーク

通常の RNN は、前の瞬間のタイミング情報に基づいて次の瞬間の出力を予測することしかできませんが、問題によっては、現在の瞬間の出力が前の状態だけでなく将来の状態にも関係することがあります。

たとえば、文中の欠落している単語を予測する場合、文脈に基づいて真に判断するには、前のテキストに基づいて判断するだけでなく、その背後にある内容も考慮する必要があります。

BRNN は、上下に重ねられた 2 つの RNN で構成され、出力は 2 つの RNN の状態によって決まります。
ここに画像の説明を挿入します
まず、必要なときに簡単に参照できるように、図内の記号と数式に注目してみましょう。

  • ht1h_t^1ht1時刻 t に Cell1 内で左から右に取得されたメモリ (情報) を表します。
  • W 1 、U 1 W^1、U^1W1U1は図のCell1の学習可能なパラメータを表し、Wは隠れ層のパラメータ、Uは入力層のパラメータです。
  • f1f_1f1Cell1 の活性化関数を表します。
  • ht2h_t^2ht2時刻 t に Cell2 内で右から左に取得されたメモリを表します。
  • W2W^2W2U 2 U^2U2 は図の Cell2 の学習可能なパラメータを表します。
  • f 2 f_2f2Cell2 の活性化関数を表します。
  • VVVは出力層のパラメータであり、MLP として理解できます。
  • f 3 f_3f3は出力層の活性化関数です。
  • yt y_ty時間 t における出力値です。

図 1-1 では、時刻 t における入力xt x_tに対して、バツ、メモリht − 1 1 h^1_{t-1}と左から右に組み合わせることができます。ht 11、現時点のメモリht 1 h^1_tを取得します。ht1:同様に、メモリht − 1 2 h^2_{t−1} も
ここに画像の説明を挿入します
右から左に組み合わせることができます。ht 12、現時点でのメモリht 2 h^2_tを取得します。ht2:
ここに画像の説明を挿入します
ではht 1 h^1_tht1そしてht 2 h^2_tht2ヘッドとテールは出力層ネットワークVVを介してカスケード接続されます。V は出力yt y_ty:
ここに画像の説明を挿入します
このようにして、あらゆる瞬間 t において、さまざまな方向から取得されたメモリを確認できるため、モデルの最適化が容易になり、モデルの収束速度が向上します。

パイトーチrnn

以下は、PyTorch の nn.RNN モジュールを使用して双方向 RNN を実装する最も簡単な例です。

import torch
import torch.nn as nn

# 定义输入数据
input_size = 10   # 输入特征的维度
sequence_length = 5   # 时间步个数
batch_size = 3   # 批次大小

# 创建随机输入数据
input_data = torch.randn(sequence_length, batch_size, input_size)

# 定义双向RNN模型
rnn = nn.RNN(input_size, hidden_size=20, num_layers=1, batch_first=False, bidirectional=True)

# 前向传播
output, hidden = rnn(input_data)

# 输出结果
print("输出张量大小:", output.size())
print("最后一个时间步的隐藏状态大小:", hidden.size())

出力

输出张量大小: torch.Size([5, 3, 40])
最后一个时间步的隐藏状态大小: torch.Size([2, 3, 20])

この例では、入力データの次元は前の例と同じです。

双方向 RNN モデルを定義するときは、RNN モデルのパラメーターに bidirection=True を設定し、双方向 RNN モデルを構築することを示します。

順伝播中に、入力データを双方向 RNN モデルに渡し、出力テンソル出力と最後のタイム ステップの隠れ状態を取得します。出力テンソルのサイズは (sequence_length、batch_size、hidden_​​size num_directions) です。ここで、num_directions は 2 で、順方向と逆方向の両方を示します。最後のタイム ステップでの隠れ状態のサイズは、(num_layers num_directions、batch_size、hidden_​​size) です。

双方向 RNN は過去と未来の両方の情報を利用でき、時系列データの特徴をより適切に捕捉できます。入力データのサイズやRNNモデルのパラメータなどを必要に応じて調整して実験を行うことができます。

双方向 RNN の出力は通常、前方隠れ状態と後方隠れ状態の組み合わせであり、配列に格納されます。具体的には、PyTorch の nn.RNN モジュールを使用して双方向 RNN を実装する場合、出力テンソルの形状は (sequence_length、batch_size、hidden_​​size * 2) になります。ここで、hidden_​​size * 2 は、順方向とバッチのサイズの合計を表します。非表示状態を反転します。この出力テンソルには、各タイム ステップにおける前方および後方の隠れ状態に関する情報が含まれており、後続のタスクで使用できます。
双方向rnnの最終的な隠れ層サイズは(2,batch_size,hidden_​​size)です。

ディープ RNN (多層 RNN)

先ほど紹介した RNN は、時間次元でのデータの変換です。時間次元がどれほど長くても、RNN モジュールは 1 つだけ存在します。つまり、単層 RNN に属する学習対象のパラメーターのセット (W、U) は 1 つだけです。Deep RNN は多層 RNN とも呼ばれ、その名前が示すように、入力データを空間次元で変換する複数の RNN カスケードで構成されています。図に示すように、これは L 層 RNN アーキテクチャです。各層は個別の RNN であり、合計 L 個の RNN があります。
ここに画像の説明を挿入します

各層の水平方向には、学習可能なパラメータのセットが 1 つだけありますレイヤーのパラメータl W l U l W^lU^lWlU _l水平方向は時間次元に沿ってデータを変換することであり、その変換メカニズムは単一の RNN のメカニズムと一致しています (詳細は前回の記事を参照してください)。各時間 t における垂直方向には、学習可能なパラメータのセットが L 個あります (W i , U i W^i,U^iWUi ) i = 1、2、…、L。ll層lの時間 t における Cell の入力データは2 つの方向から来ます。1 つは前の層からの出力htl − 1 h^{l−1}_thtl 1: 1 つはll
ここに画像の説明を挿入します
からのものですl層、t − 1 t − 1t1瞬間ht − 1 lh^l_{t−1}ht 1:つまり、Cell htlh^l_t
ここに画像の説明を挿入します
の出力ht:
ここに画像の説明を挿入します
基本的に、Deep RNN は、単一の RNN に基づいて、現時点の入力を上位層の出力に変更します。このようにして、RNN は空間データ変換を完了します。追加の言及: DeepRNN の各層は双方向 RNN にすることもできます。

パイトーチrnn

以下は、nn.RNN モジュールを使用して多層 RNN を実装する最も簡単な例です。

import torch
import torch.nn as nn

# 定义输入数据和参数
input_size = 5
hidden_size = 10
num_layers = 2
batch_size = 3
sequence_length = 4

# 创建输入张量
input_tensor = torch.randn(sequence_length, batch_size, input_size)

# 创建多层RNN模型
rnn = nn.RNN(input_size, hidden_size, num_layers)

# 前向传播
output, hidden = rnn(input_tensor)

# 打印输出张量和隐藏状态的大小
print("Output shape:", output.shape)
print("Hidden state shape:", hidden.shape)

上記の例では、最初に入力データの次元、RNN モデルのパラメーター (入力サイズ、隠れ状態のサイズ、層の数)、さらにバッチ サイズとシーケンス長を定義しました。次に、形状 (sequence_length、batch_size、input_size) を使用して入力テンソルを作成します。次に、nn.RNN モジュールを使用して、2 層の多層 RNN モデルを作成します。最後に、入力テンソルを RNN モデルの forward メソッドに渡して順伝播を実行し、出力テンソルのサイズと隠れ状態を出力します。

出力テンソルには形状 (sequence_length、batch_size、hidden_​​size) があることに注意してください。ここで、sequence_length とbatch_size は一定のままで、hidden_​​size は隠れ状態のサイズです。隠れ状態の形状は (num_layers、batch_size、hidden_​​size) です。ここで、num_layers は RNN モデルの層の数です。

RNNの欠点

勾配の爆発および消滅の問題

実際には、前に紹介したいくつかの RNN は長いシーケンスをうまく処理できません。RNN はトレーニング中に勾配の爆発や勾配の消失を起こしやすく、その結果勾配が長いシーケンスに渡されず、RNN が勾配を捕捉できなくなります。効果。

一般に、勾配爆発は対処が容易です。なぜなら、勾配が爆発すると、プログラムは NaN エラーを受け取ることになるからです。勾配のしきい値を設定することもでき、勾配がこのしきい値を超えると、勾配を直接遮断できます。

グラデーションの消失は検出が難しく、対処も少し難しくなります。一般に、勾配消失問題に対処するには 3 つの方法があります。

1. 適切な初期化重み値。勾配が消失する領域を避けるために、各ニューロンができるだけ最大値または最小値を取らないように重みを初期化します。

2. 活性化関数として sigmoid と Tanh の代わりに relu を使用します。

3. 最も一般的なアプローチである Long Short-term Memory Network (LTSM) や Gated Recurrent Unit (GRU) など、他の構造の RNN を使用します。

短期記憶

ユーザーが話す意図を判断する必要があり (天気について尋ねる、時間を尋ねる、目覚まし時計をセットするなど)、ユーザーが「今何時ですか?」と言う場合、最初に文を分割する必要があります。 RNN を順番に入力すると、まず
ここに画像の説明を挿入します
「何を」を RNN の入力として分割し、出力「01」が得られます。
ここに画像の説明を挿入します
次に、「時間」を RNN ネットワークに順番に入力し、出力「02」が得られます。得られた。

このプロセスでは、「時間」が入力されると、前の「何を」の出力も影響することがわかります(隠れ層の半分は黒です)。
ここに画像の説明を挿入します
同様に、以前のすべての入力が将来の出力に影響を与えるため、円形の非表示レイヤーには以前のすべての色が含まれていることがわかります。下図に示すように、
ここに画像の説明を挿入します
意図を判断する際に必要なのは、下図に示すように、最終層の出力「05」だけです:
ここに画像の説明を挿入します
RNN の欠点も明らかです
ここに画像の説明を挿入します
上記の例を通じて、次のことがわかりました。短期記憶の影響は大きくなりますが (オレンジ色の領域など)、長期記憶の影響は非常に小さいです (黒と緑の領域など)。これが RNN の短期記憶の問題です。

  1. RNN には短期的なメモリの問題があり、非常に長い入力シーケンスを処理できません
  2. RNNのトレーニングには莫大なコストがかかる

RNNの最適化アルゴリズム

LSTM – 長短期記憶ネットワーク

RNN は厳格なロジックであり、入力が遅いほど影響が大きく、入力が早いほど影響が小さくなり、このロジックは変更できません。
LSTM による最大の変更は、この厳格なロジックを打ち破り、重要な情報のみを保持する柔軟なロジックを使用することです。
簡単に言えば、重要なポイントに集中してください。
ここに画像の説明を挿入します
たとえば、次の段落をざっと読みましょう: ざっと
ここに画像の説明を挿入します
読み終えた後は、次の重要な点だけを覚えているかもしれません:
ここに画像の説明を挿入します
LSTM は上記の強調表示された点と似ています。「重要な情報」をより長いシーケンス データに保持できます。重要でない情報を無視すること。これにより、RNN の短期記憶の問題が解決されます。

原理

元の RNN の隠れ層には状態 h が 1 つだけあり、これは短期間の入力に非常に敏感です。次に、特徴の循環と損失を制御する別のゲート機構 (c) を追加し、長期状態を保存させると、これが長短期記憶ネットワーク (Long Short Term Memory、LSTM) になります。
ここに画像の説明を挿入します
新たに追加された状態cをユニット状態と呼びます。時間次元に従って LSTM を拡張します。
ここで、画像上のロゴはσ \sigmaσフラグは、sigmod を [0-1]、tanh ⁡ \tanhTanh は[-1,1] に有効になります
。 ⨀ は要素ごとの積またはアダマール積を表す数学記号です。同じ次元の 2 つの行列、ベクトル、またはテンソルが要素ごとに乗算される場合、これを示すために ⨀ 記号を使用できます。

たとえば、2 つのベクトル [a1, a2, a3] ⨀ [b1, b2, b3]=[a1 b1 , a2 b2, a3*b3] の場合、それらの要素ごとの積を表すことができます
ここに画像の説明を挿入します
。て、

LSTM には 3 つの入力があります: 現時点でのネットワークの出力値xt x_tバツ、直前の瞬間の LSTM の出力値ht − 1 h_{t−1}ht 1、および前の瞬間のメモリ単位ベクトルct − 1 c_{t−1}ct 1

LSTM には 2 つの出力があります: 現時点の LSTM 出力値ht h_th、現時点での隠れ状態ベクトルht h_th、および現時点でのメモリユニット状態ベクトルct c_tc

注: メモリ ユニット c は LSTM 層内で作業を終了し、他の層に出力しません。LSTM の出力は、隠れ状態ベクトル h のみです。

LSTM の鍵となるのはセルの状態です。セルの状態は、グラフの上部を横切る水平線で、ベルトコンベアに似ています。この部分は一般にセル状態と呼ばれ、LSTM のチェーン システム全体に最初から最後まで存在します。
ここに画像の説明を挿入します

忘却の扉

フィートフィートfこれは忘却ゲートと呼ばれ、C t − 1 C_{t−1}を意味します。Ct 1C t C_t の計算に使用される特徴はどれですかCフィートf_tfはベクトルであり、ベクトルの各要素は (0 ~ 1) の範囲内にあります。通常、活性化関数としてシグモイドを使用します。シグモイドの出力は (0~1) の間の値ですが、訓練された LSTM を観察すると、ほとんどのゲート値が 0 に非常に近いことがわかります。または 1、その他の値はほとんどありません。
ここに画像の説明を挿入します

入力ゲート

CtC_tC入力データxt x_tによって決まるユニットステータス更新値を表します。バツおよび隠れノードht − 1 h_{t−1}ht 1ニューラル ネットワーク層を通じて取得されるユニット状態更新値の活性化関数には、通常、tanh が使用されます。それはft f_tと同じ、入力ゲートと呼ばれます。fこれは、要素が (0 ~ 1) の範囲内にあるベクトルでもあり、xt x_tで表されます。バツそしてht − 1 h_{t−1}ht 1シグモイド活性化関数を通じて計算
ここに画像の説明を挿入します

出力ゲート

最後に、予測値yty^tを計算するためにytを実行し、次のタイム スライスの完全な入力を生成するには、隠しノードht h_tの出力を計算する必要があります。h
ここに画像の説明を挿入します

lstmは詩を書きます


まず、pytorch でのlstmの使用法を勉強しましょう。

sequence_length =3
batch_size =2
input_size =4
#这里如果是输入比如[张三,李四,王五],一般实际使用需要通过embedding后生成一个[时间步是3,批量1(这里是1,但是如果是真实数据集可能有分批处理,就是实际的批次值),3(三个值的坐标表示一个张三或者李四)]
input=t.randn(sequence_length,batch_size,input_size)
lstmModel=nn.LSTM(input_size,3,1)
#其中,output是RNN每个时间步的输出,hidden是最后一个时间步的隐藏状态。
output, (h, c) =lstmModel(input)
#因为是3个时间步,每个时间步都有一个隐藏层,每个隐藏层都有2条数据,隐藏层的维度是3,最终(3,2,3)
print("LSTM隐藏层输出的维度",output.shape)
#
print("LSTM隐藏层最后一个时间步输出的维度",h.shape)
print("LSTM隐藏层最后一个时间步细胞状态",c.shape)

出力

LSTM隐藏层输出的维度 torch.Size([3, 2, 3])
LSTM隐藏层最后一个时间步输出的维度 torch.Size([1, 2, 3])
LSTM隐藏层最后一个时间步细胞状态 torch.Size([1, 2, 3])

二重層 lstm

sequence_length =3
batch_size =2
input_size =4
input=t.randn(sequence_length,batch_size,input_size)
lstmModel=nn.LSTM(input_size,3,num_layers=2)
#其中,output是RNN每个时间步的输出,hidden是最后一个时间步的隐藏状态。
output, (h, c) =lstmModel(input)
print("2层LSTM隐藏层输出的维度",output.shape)
print("2层LSTM隐藏层最后一个时间步输出的维度",h.shape)
print("2层LSTM隐藏层最后一个时间步细胞状态",c.shape)

出力:
2 層 LSTM torch の隠れ層の出力の次元。Size([3, 2, 3])
2 層 LSTM torch の隠れ層の最後のタイム ステップの出力の次元。Size([ 2, 2, 3])
2 層 LSTM の Hidden 層トーチの最後のタイム ステップのセル状態。Size([2, 2, 3])

2 層の場合、出力は最後の隠れ層の出力です。h と c は、1 つのタイム ステップにおける 2 つの隠れ層とメモリ セルの層です。

詩の開始例
これはプロジェクトのディレクトリ構造です
ここに画像の説明を挿入します

データのダウンロード

実験データは、中国の愛好家によって Github 上で収集された 50,000 以上の唐詩からのものです。著者はこれに基づいていくつかのデータ処理を実行しました。データ処理には時間がかかり、pytorch 学習の焦点では​​ないため、ここでは省略します。著者はnumpy圧縮パッケージtang.npzを提供しています。ダウンロードアドレスは
データの特定の構造を参照できます。以下のコードの主要部分です。

from torch.utils.data import  Dataset,DataLoader
import numpy as np
class PoetryDataset(Dataset):
    def __init__(self,root):
        self.data=np.load(root, allow_pickle=True)
    def __len__(self):
        return len(self.data["data"])
    def __getitem__(self, index):
        return self.data["data"][index]
    def getData(self):
        return self.data["data"],self.data["ix2word"].item(),self.data["word2ix"].item()
if __name__=="__main__":
    datas=PoetryDataset("./tang.npz").data
    # data是一个57580 * 125的numpy数组,即总共有57580首诗歌,每首诗歌长度为125个字符(不足125补空格,超过125的丢弃)
    print(datas["data"].shape)
    #这里都字符已经转换成了索引
    print(datas["data"][0])
    # 使用item将numpy转换为字典类型,ix2word存储这下标对应的字,比如{0: '憁', 1: '耀'}
    ix2word = datas['ix2word'].item()
    print(ix2word)
    # word2ix存储这字对应的小标,比如{'憁': 0, '耀': 1}
    word2ix = datas['word2ix'].item()
    print(word2ix)
    # 将某一首古诗转换为索引表示,转换后:[5272, 4236, 3286, 6933, 6010, 7066, 774, 4167, 2018, 70, 3951]
    str="床前明月光,疑是地上霜"
    print([word2ix[i] for i in str])
    #将第一首古诗打印出来
    print([ix2word[i] for i in datas["data"][0]])

モデルの定義

import torch.nn as nn
class Net(nn.Module):
    """
        :param vocab_size 表示输入单词的格式
        :param embedding_dim 表示将一个单词映射到embedding_dim维度空间
        :param hidden_dim 表示lstm输出隐藏层的维度
    """
    def __init__(self, vocab_size, embedding_dim, hidden_dim):
        super(Net, self).__init__()
        self.hidden_dim = hidden_dim
        #Embedding层,将单词映射成vocab_size行embedding_dim列的矩阵,一行的坐标代表第一行的词
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        #两层lstm,输入词向量的维度和隐藏层维度
        self.lstm = nn.LSTM(embedding_dim, self.hidden_dim, num_layers=2, batch_first=False)
        #最后将隐藏层的维度转换为词汇表的维度
        self.linear1 = nn.Linear(self.hidden_dim, vocab_size)

    def forward(self, input, hidden=None):
        #获取输入的数据的时间步和批次数
        seq_len, batch_size = input.size()
        #如果没有传入上一个时间的隐藏值,初始一个,注意是2层
        if hidden is None:
            h_0 = input.data.new(2, batch_size, self.hidden_dim).fill_(0).float()
            c_0 = input.data.new(2, batch_size, self.hidden_dim).fill_(0).float()
        else:
            h_0, c_0 = hidden
        #将输入的数据embeddings为(input行数,embedding_dim)
        embeds = self.embeddings(input)     # (seq_len, batch_size, embedding_dim), (1,1,128)
        output, hidden = self.lstm(embeds, (h_0, c_0))      #(seq_len, batch_size, hidden_dim), (1,1,256)
        output = self.linear1(output.view(seq_len*batch_size, -1))      # ((seq_len * batch_size),hidden_dim), (1,256) → (1,8293)
        return output, hidden

電車

次のコード: input, target = (data[:-1, :]), (data[1:, :])説明:

LSTM を単語予測に使用する場合、入力シーケンスとターゲット シーケンスを揃えるように入力とラベルが設定されます。

言語モデルでは、前の単語に基づいて次の単語を予測する必要があります。したがって、入力シーケンスは前の単語であり、ターゲット シーケンスは次の単語です。

次の例を考えてみましょ
う: 「私はディープ ラーニングが大好きです。」という文があるとします。
これを次の形式の入力シーケンスとターゲット シーケンスに分割できます:
入力シーケンス: ["I", "love", "deep"]
ターゲット シーケンス: [「愛」、「深い」、「学び」]

この例では、入力シーケンスは前の単語 ["I"、"love"、"deep"] であり、ターゲット シーケンスは対応する次の単語 ["love"、"deep"、"learning"] です。

コードでは、データはすべての単語を含むデータセットであり、各行が単語を表します。データを入力とターゲットにスライスするときは、入力シーケンスとして data[:-1, :] を使用します (つまり、最後の単語を除きます)。そして、data[1:, :]が2ワード目以降のターゲットシーケンスとなります。

このように入力シーケンスとターゲット シーケンスを設定する目的は、モデルが前の単語に基づいて次の単語を予測できるように、入力とラベルを調整することです。

import fire
import torch.nn as nn
import torch as t
from data.dataset import PoetryDataset
from models.model import Net
num_epochs=5
data_root="./data/tang.npz"
batch_size=10
def train(**kwargs):
    datasets=PoetryDataset(data_root)
    data,ix2word,word2ix=datasets.getData()
    lenData=len(data)
    data = t.from_numpy(data)
    dataloader = t.utils.data.DataLoader(data, batch_size=batch_size, shuffle=True, num_workers=1)
    #总共有8293的词。模型定义:vocab_size, embedding_dim, hidden_dim = 8293 * 128 * 256
    model=Net(len(word2ix),128,256)
    #定义损失函数
    criterion = nn.CrossEntropyLoss()
    model=model.cuda()
    optimizer = t.optim.Adam(model.parameters(), lr=1e-3)
    iteri=0
    filename = "example.txt"
    totalIter=lenData*num_epochs/batch_size
    for epoch in range(num_epochs):  # 最大迭代次数为8
        for i, data in enumerate(dataloader):  # 一批次数据 128*125
            data = data.long().transpose(0,1).contiguous() .cuda()
            optimizer.zero_grad()
            input, target = (data[:-1, :]), (data[1:, :])
            output, _ = model(input)
            loss = criterion(output, target.view(-1))  # torch.Size([15872, 8293]), torch.Size([15872])
            loss.backward()
            optimizer.step()
            iteri+=1
            if(iteri%500==0):
                print(str(iteri+1)+"/"+str(totalIter)+"epoch")
            if (1 + i) % 1000 == 0:  # 每575个batch可视化一次
                with open(filename, "a") as file:
                    file.write(str(i) + ':' + generate(model, '床前明月光', ix2word, word2ix)+"\n")
    t.save(model.state_dict(), './checkpoints/model_poet_2.pth')
def generate(model, start_words, ix2word, word2ix):     # 给定几个词,根据这几个词生成一首完整的诗歌
    txt = []
    for word in start_words:
        txt.append(word)
    input = t.Tensor([word2ix['<START>']]).view(1,1).long()      # tensor([8291.]) → tensor([[8291.]]) → tensor([[8291]])
    input = input.cuda()
    hidden = None
    num = len(txt)
    for i in range(48):      # 最大生成长度
        output, hidden = model(input, hidden)
        if i < num:
            w = txt[i]
            input = (input.data.new([word2ix[w]])).view(1, 1)
        else:
            top_index = output.data[0].topk(1)[1][0]
            w = ix2word[top_index.item()]
            txt.append(w)
            input = (input.data.new([top_index])).view(1, 1)
        if w == '<EOP>':
            break
    return ''.join(txt)
if __name__=="__main__":
    fire.Fire()

5epoch、10batch、一般的な PC、GTX1050、2GB ビデオメモリ、トレーニング時間は 30 分です。
50エポック、128バッチ、colabフリーGPU、16GBメモリ、トレーニング時間1時間

テスト

def test():
    datasets = PoetryDataset(data_root)
    data, ix2word, word2ix = datasets.getData()
    modle = Net(len(word2ix), 128, 256)  # 模型定义:vocab_size, embedding_dim, hidden_dim —— 8293 * 128 * 256
    if t.cuda.is_available() == True:
        modle.cuda()
        modle.load_state_dict(t.load('./checkpoints/model_poet_2.pth'))
        modle.eval()
        name = input("请输入您的开头:")
        txt = generate(modle, name, ix2word, word2ix)
        print(txt)

5 エポックしか訓練されていないため、効果はあまり良くありません。損失を視覚化した後、複数のエポックで効果を確認できます。別の問題があります。入力が変更されない場合、生成される結果は同じになります。そのため、ノイズ干渉が必要になる場合があります。
5epochバージョンの効果

(env380) D:\code\deeplearn\learn_rnn\pytorch\4.nn模块\案例\生成古诗\tang>python main.py test
请输入您的开头:唧唧复唧唧
唧唧复唧唧,不知何所如?君不见此地,不如此中生。一朝一杯酒,一日相追寻。一朝一杯酒,一醉一相逢。

(env380) D:\code\deeplearn\learn_rnn\pytorch\4.nn模块\案例\生成古诗\tang>python main.py test
请输入您的开头:我儿小谦谦
我儿小谦谦,不是天地间。有时有所用,不是无为名。有时有所用,不是无生源。有时有所用,不是无生源。

50エポックバージョンの効果

(env380) D:\code\deeplearn\learn_rnn\pytorch\4.nn模块\案例\生成古诗\tang>python main.py test
请输入您的开头:我家小谦谦
我家小谦谦,今古何为郎。我生不相识,我心不可忘。我来不我见,我亦不得尝。君今不我见,我亦不足伤。

(env380) D:\code\deeplearn\learn_rnn\pytorch\4.nn模块\案例\生成古诗\tang>python main.py test
请输入您的开头:床前明月光
床前明月光,上客不可见。玉楼金阁深,玉瑟风光紧。玉指滴芭蕉,飘飘出罗幕。玉堂无尘埃,玉节凌风雷。
(env380) D:\code\deeplearn\learn_rnn\pytorch\4.nn模块\案例\生成古诗\tang>python main.py test
请输入您的开头:唧唧复唧唧
唧唧复唧唧,胡儿女卿侯。妾本邯郸道,相逢两不游。妾心不可再,妾意不能休。妾本不相见,妾心如有钩。

GRU

Gated Recurrent Unit – GRU は LSTM の亜種です。重要でない情報を強調表示したり無視したりするという LSTM の特性が維持されており、長期的な伝播中に失われることはありません。

LSTM にはパラメータが多すぎるため、計算に時間がかかります。そこで、業界では最近、GRU(Gated Recurrent Unit)を提案しています。GRU は LSTM でゲートを使用するという概念を保持していますが、パラメータを減らして計算時間を短縮します。

隠し状態とメモリ ユニットの 2 つの行を使用する LSTM と比較して、GRU は隠し状態のみを使用します。
ここに画像の説明を挿入します
GRU 計算グラフGRU
ここに画像の説明を挿入します
計算グラフ、σ ノードと Tanh ノードには専用の重みがあり、ノード内でアフィン変換が実行されます (「1−」ノードは x を入力し、1 − x を出力します)。 GRU で実行される処理は上記 4 つで構成されており、これを
ここに画像の説明を挿入します
式で表すと(ここでは xt と ht−1 はいずれも行ベクトルです)、図に示すように、GRU にはメモリ単位はなく、時間方向に伝播する隠れ状態 h のみが存在します。ここでは r と z の 2 つのゲートが使用されます (LSTM では 3 つのゲートが使用されます)。r はリセット ゲートと呼ばれ、z はアップデート ゲートと呼ばれます。

r (リセット ゲート) は、過去の隠れた状態をどの程度まで「無視」するかを決定します。式 2.3 によると、r が 0 の場合、新しい隠れ状態 h~ は入力xt x_tにのみ依存します。バツつまり、この時点での過去の隠れた状態は完全に無視されます。

z (update Gate)** は隠れ状態を更新するゲートであり、LSTM の忘却ゲートと入力ゲートの 2 つの役割を果たします。式 2.4 の(1−z)⊙ ht − 1 h_{t−1}ht 1部分的に忘却ゲートとして機能し、忘れるべき情報を過去の隠された状態から削除します。z⊙hh ^~h この部分は、新しく追加された情報を重み付けするための入力ゲートとして機能します。

おすすめ

転載: blog.csdn.net/liaomin416100569/article/details/131380370