[[201448]] 私は、TensorFlow リカレント ニューラル ネットワークのチュートリアルを読み、PTB での実装を調べるのにほぼ午後を費やしましたが、わかりにくくて理解しにくいと感じました。そこで、コードの一部を参考にし、Keras の LSTM テキスト生成のアイデアを使用して、言語モデルの簡略版を自分で作成しました。 コードアドレス: Github 転載の際は出典を明記してください: Gaussic 言語モデル 言語モデルの主な考え方は、単語の前の部分に基づいて次に最も可能性の高い単語を推測することです。たとえば、「太った猫は毛布の上に座った」ということがわかっているので、猫は帽子よりも毛布の上に座る可能性が高いため、次の単語は帽子よりもマットである可能性が高いと考えます。 これは常識のように思えるかもしれませんが、自然言語処理では、このタスクは確率統計モデルを使用して記述できます。たとえば、太った猫がマットの上に座っていたとします。最初の単語 The が出現する確率は p(The)p(The) と計算でき、The の後に fat が続く条件付き確率は p(fat|The)p(fat|The) と計算でき、The と fat が同時に出現する結合確率は次のように計算できます。 - p(The,fat)=p(The)⋅p(fat|The)p(The,fat)=p(The)·p(fat|The)
この結合確率が、The fat の合理性、つまり、この文の出現が自然言語の判断基準を満たしているかどうかです。平たく言えば、これは人間の言語であるかどうかを意味します。同様に、連鎖律によれば、The fat cat の結合確率は次のように計算できます。- p(The,fat,cat)=p(The)⋅p(fat|The)⋅p(cat|The,fat)p(The,fat,cat)=p(The)·p(fat|The)·p(cat|The,fat)
前の単語が The cat であることがわかっているので、次の単語が cat である確率を導き出すことができます。- p(猫|太った)=p(太った猫)p(太った)p(猫|太った)=p(太った猫)p(太った)
分子はコーパス内での「The fat cat」の出現回数であり、分母はコーパス内での「The fat」の出現回数です。 したがって、「太った猫はマットの上に座った」という文全体の合理性も推測できます。この文の合理性はその確率です。式は次のように記述されます。- p(S)=p(w1,w2,⋅⋅⋅,wn)=p(w1)⋅p(w2|w1)⋅p(w3|w1,w2)⋅⋅⋅p(wn|w1,w2,w3,⋅⋅⋅,wn−1)p(S)=p(w1,w2,···,wn)=p(w1)·p(w2|w1)·p(w3|w1,w2)···p(wn|w1,w2,w3,···,wn−1)
(式の後の n-1 は添え字、つまりプラグイン問題で、以下同じ) 問題が見つかりました。次の単語の条件付き確率を計算するときはいつでも、前の単語すべての結合確率を計算する必要があります。この計算量はかなり膨大です。さらに、文中にほとんどの単語が同時に出現する確率は非常に小さいことが多く、データは非常にまばらであるため、トレーニングには非常に大きなコーパスが必要になります。 単純な最適化は、次の単語の出現が前の 1 つまたは n 個の単語にのみ関連しているというマルコフ仮定に基づいています。 最も単純なケースでは、次の単語の出現は前の単語にのみ関連しており、これはバイグラムと呼ばれます。- p(S)=p(w1,w2,⋅⋅⋅,wn)=p(w1)⋅p(w2|w1)⋅p(w3|w2)⋅p(w4|w3)⋅⋅⋅p(wn|wn−1)p(S)=p(w1,w2,···,wn)=p(w1)·p(w2|w1)·p(w3|w2)·p(w4|w3)···p(wn|wn−1)
さらに複雑なことに、次の単語の出現は前の 2 つの単語にのみ関連しており、これをトリグラムと呼びます。- p(S)=p(w1,w2,⋅⋅⋅,wn)=p(w1)⋅p(w2|w1)⋅p(w3|w1,w2)⋅p(w4|w2,w3)⋅⋅⋅p(wn|wn−2,wn−1)p(S)=p(w1,w2,···,wn)=p(w1)·p(w2|w1)·p(w3|w1,w2)·p(w4|w2,w3)···p(wn|wn−2,wn−1)
このような条件付き確率は計算が簡単ですが、前の単語に関する多くの情報が失われ、結果に悪影響を与えることがあります。したがって、コンテキスト情報の大部分を保持しながら計算を簡素化できる効果的な n を選択する方法。 上記はすべて従来の言語モデルの説明です。あまり詳しく説明せずに、私たちのタスクは、前の n 個の単語が与えられた場合に次の単語が出現する確率を計算することです。言語モデルを使用して新しいテキストを生成します。 この記事では、RNN を使用して次の単語を推測する方法に重点を置いています。 データの準備TensorFlow の公式ドキュメントでは、Mikolov が準備した PTB データセットが使用されています。ダウンロードして解凍することができます:- $ wget http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz
- $ tar xvf 簡単な例.tgz
部分数据如下,不常用的词转换成了<unk>标记,数字转换成了N:- アスベストが 疑わしい特性
- がある 現在、当社の製品にはアスベストは使用されておりません
- も、労働者を研究した研究者も知っていませんでした ケントタバコの喫煙者に関する研究
- ボストン癌研究所のジェームズ・A氏は、ユーザーが危険にさらされているかどうかについて有用な情報はないと述べた。
- 悪性肺がんとによるn死亡の合計は、研究者が言ったと予想よりもはるかに高かった
ファイル内のデータを読み取り、改行を <eos> に変換してから、単語のリストに変換します。- def _read_words(ファイル名):
- と open (ファイル名、 'r' 、エンコーディング= 'utf-8' )をf:として開きます。
- f.read ()を返します。 ( ' \n' , '<eos>' ).split()を置き換えます。
- f = _read_words( 'simple-examples/data/ptb.train.txt' )
- 印刷(f[:20])
得る:- [ 'aer' 、 'banknote' 、 'berlitz' 、 'calloway' 、 'centtrust' 、 'cluett' 、 'fromstein' 、 'gitano' 、 'guterman' 、 'hydro-quebec' 、 'ipo' 、 'kia' 、 'memotec' 、 'mlx' 、 'nahb' 、 'punts' 、 'rake' 、 'regatta' 、 'rubens' 、 'sim' ]
語彙を構築し、単語と ID を変換します。- def _build_vocab(ファイル名):
- データ = _read_words(ファイル名)
-
- カウンター = カウンター(データ)
- count_pairs = sorted(counter.items(),キー=lambda x: -x[1])
-
- 単語、_ = list(zip(*count_pairs))
- word_to_id = dict(zip(単語、範囲(len(単語))))
-
- 単語を返す、word_to_id
- 単語、words_to_id = _build_vocab( 'simple-examples/data/ptb.train.txt' )
- print(単語[:10])
- print(list(map(lambda x: words_to_id[x], words[:10])))
出力:- ( 'the' 、 '' 、 '<eos>' 、 'n' 、 'of' 、 'to' 、 'a ' in ' 、 ' 、 '' s " ))
- [0、1、2、3、4、5、6、7、8、9]
ファイルを ID 表現に変換します。- def _file_to_word_ids(ファイル名, word_to_id):
- データ = _read_words(ファイル名)
- データ内のxがword_to_id内にある場合、 [word_to_id[x] ]を返します。
- ファイル内の単語 = _file_to_word_ids( 'simple-examples/data/ptb.train.txt' 、 単語のID)
- print(ファイル内の単語[:20])
語彙は単語の頻度に従ってソートされています。最初の文は英語ではないため、その ID は最後にあります。- [9980、9988、9981、9989、9970、9998、9971、9979、9992、9997、9982、9972、9993、9991、9978、9983、9974、9986、9999、9990]
ID のリストから文を単語に戻します。- def to_words(文, 単語):
- リストを返す(map(lambda x: words[x], sentence))
上記の機能を組み合わせると:- def ptb_raw_data(データパス=なし):
- train_path = os.path.join (data_path、 'ptb.train.txt' )
- valid_path = os.path.join (data_path, 'ptb.valid.txt' ) です。
- test_path = os.path.join (data_path, 'ptb.test.txt' ) です。
-
- 単語、word_to_id = _build_vocab(train_path)
- トレーニングデータ = _file_to_word_ids(トレーニングパス、単語ID)
- 有効なデータ = _file_to_word_ids(有効なパス、単語ID)
- test_data = _file_to_word_ids(テストパス、word_to_id)
-
- train_data、valid_data、test_data、words、word_to_idを返します。
上記の部分は公式の例とある程度類似しています。以下の処理は公式の処理とは大きく異なり、主に Keras ルーチン処理ドキュメントの操作を参照します。- def ptb_producer(raw_data、batch_size=64、num_steps=20、stride=1):
- data_len = len(生データ)
-
- 文 = []
- 次の単語 = []
- iが範囲(0, data_len - num_steps, stride)内にある場合:
- sentences.append(raw_data[i:(i + num_steps)])
- next_words.append(raw_data[i + num_steps])
-
- 文 = np.array(文)
- 次の単語 = np.array(次の単語)
-
- batch_len = len(文) // バッチサイズ
- x = np.reshape(文[:(バッチ長 * バッチサイズ)], \
- [バッチ長、バッチサイズ、-1])
-
- y = np.reshape(next_words[:(batch_len * batch_size)], \
- [バッチ長、バッチサイズ])
-
- x, yを返す
パラメータ分析:- raw_data: ptb_raw_data() 関数によって生成されたデータ
- batch_size: ニューラル ネットワークは確率的勾配降下法を使用し、データは複数のバッチで出力されます。これは各バッチのデータ量です。
- num_steps: 各文の長さ。これは前述の n のサイズに相当し、再帰型ニューラル ネットワークの時系列の長さとも呼ばれます。
- ストライド: データのステップ長を取得し、データ量を決定します。
コード分析: この関数は、生データのリストを複数のデータ バッチ (つまり [batch_len、batch_size、num_steps]) に変換します。 まず、プログラムは毎回 num_steps 個の単語を文 (つまり x) として受け取り、これらの num_steps 個の単語の後の単語を次の予測 (つまり y) として受け取ります。このようにして、まず元のデータを batch_len * batch_size の x と y の表現に整理します。これは、x が与えられたときに y を見つける分類問題に似ています。 確率的勾配降下法のニーズを満たすには、データを小さなバッチに整理し、毎回データのバッチを TensorFlow にフィードして重みを更新する必要があります。このようにして、データは [batch_len、batch_size、num_steps] の形式に整理されます。 いくつかのデータを印刷します: - train_data、valid_data、test_data、words、word_to_id = ptb_raw_data( 'simple-examples/data' ) です。
- x_train、y_train = ptb_producer(train_data) です。
- 印刷(x_train.shape)
- 印刷(y_train.shape)
出力:- (14524, 64, 20)
- (14524, 64)
14524バッチのデータを取得し、各バッチのトレーニングセットの次元は[64, 20]であることがわかります。- print( ' ' . join (to_words(x_train[100, 3], words)))
100 番目のバッチの 3 番目の文は次のとおりです。- 安定した売上成長にもかかわらず、マグナは最近四半期配当を半分に削減し、同社のクラスA株
- print(words[np.argmax(y_train[100, 3])])
次の単語は次のとおりです。- の
モデル構成項目の構築- クラス LMConfig(オブジェクト):
- 「言語モデル構成項目」
- batch_size = 64 # 各データバッチのサイズ
- num_steps = 20 # 各文の長さ
- stride = 3 # データ取得時のステップ長
-
- embedding_dim = 64 # 単語ベクトルの次元
- hidden_dim = 128 # RNN 隠し層の次元
- num_layers = 2 # RNN レイヤーの数
-
- learning_rate = 0.05 # 学習率
- dropout = 0.2 # 各レイヤー後のドロップアウト確率
入力を読み取ることで、モデルはデータをバッチで読み取ることができます。- クラスPTBInput(オブジェクト):
- 「データを一括で読み取る」
- def __init__(自己、設定、データ):
- 自己.batch_size = config.batch_size
- 自己.num_steps = config.num_steps
- self.vocab_size = config.vocab_size # 語彙サイズ
-
- self.input_data、self.targets = ptb_producer(データ、
- self.batch_size、self.num_steps) は、
-
- self.batch_len = self.input_data.shape[0] # 合計バッチ
- self.cur_batch = 0 # 現在のバッチ
-
- 次のバッチを定義します(自分自身):
- 「次のバッチを読み取ります」
- x = 自己.入力データ[自己.現在のバッチ]
- y = 自己.ターゲット[自己.現在のバッチ]
-
- # ワンホットエンコーディングに変換する
- y_ = np.zeros((y.shape[0], self.vocab_size), dtype=np.bool)
- iが範囲内(y.shape[0])の場合:
- y_[i][y[i]] = 1
-
- # 最後のバッチに到達したら、最初に戻ります
- self.cur_batch = (self.cur_batch + 1) % self.batch_len
-
- x, y_を返す
モデル- クラスPTBModel(オブジェクト):
- def __init__(self, config, is_training= True ):
-
- 自己.num_steps = config.num_steps
- 自己.vocab_size = config.vocab_size
-
- self.embedding_dim = config.embedding_dim
- 自己.hidden_dim = config.hidden_dim
- 自己.num_layers = config.num_layers
- self.rnn_model = config.rnn_model
-
- 自己学習率 = config.学習率
- 自己.ドロップアウト = config.ドロップアウト
-
- self.placeholders() # 入力プレースホルダー
- self.rnn() # rnn モデルの構築
- self.cost() # コスト関数
- self.optimize() # オプティマイザー
- self.error() # エラー率
-
-
- defプレースホルダー(自己):
- "" "入力データのプレースホルダー" ""
- self._inputs = tf.placeholder(tf.int32, [なし、self.num_steps])
- self._targets = tf.placeholder(tf.int32, [なし、self.vocab_size])
-
-
- def input_embedding(自己):
- "" "入力を単語ベクトル表現に変換します" ""
- tf.device( "/cpu:0" )の場合:
- 埋め込み = tf.get_variable(
- 「埋め込み」 、[self.vocab_size、
- self.embedding_dim]、dtype=tf.float32)
- _inputs = tf.nn.embedding_lookup(埋め込み、self._inputs)
-
- _inputsを返す
-
-
- 定義rnn(自己):
- 「 rnn モデルの構築」
- def lstm_cell(): # 基本的な lstm セル
- tf.contrib.rnn.BasicLSTMCell(self.hidden_dim,を返す
- state_is_tuple = True )
-
- def gru_cell(): # gruセル、より高速
- tf.contrib.rnn.GRUCell(self.hidden_dim)を返します。
-
- def dropout_cell(): # 各セルの後にドロップアウトを追加
- (self.rnn_model == 'lstm'の場合):
- セル = lstm_cell()
- それ以外:
- セル = gru_cell()
- tf.contrib.rnn.DropoutWrapper (セル、
- output_keep_prob=自己ドロップアウト)
-
- セル = [dropout_cell() _の範囲(self.num_layers)]
- cell = tf.contrib.rnn.MultiRNNCell(cells, state_is_tuple= True ) # 多層 rnn
-
- _inputs = 自己入力埋め込み()
- _出力、_ = tf.nn.dynamic_rnn(セル=セル、
- 入力=_inputs、dtype=tf.float32)
-
- # _outputs の形状は [batch_size, num_steps, hidden_dim] です
- last = _outputs[:, -1, :] # 最後の出力のみ必要
-
- # 密とソフトマックスは各単語の確率を見つけるために分類に使用されます
- ロジット = tf.layers.dense(入力 = last 、単位 = self.vocab_size)
- 予測 = tf.nn.softmax(logits)
-
- self._logits = ロジット
- self._pred = 予測
-
- defコスト(自分):
- 「クロスエントロピーコスト関数を計算する」
- クロスエントロピー = tf.nn.softmax_cross_entropy_with_logits(
- logits=self._logits、ラベル=self._targets)
- コスト = tf.reduce_mean(クロスエントロピー)
- 自己コスト = コスト
-
- def 最適化(自己):
- 「アダムオプティマイザーを使用する」
- オプティマイザー = tf.train.AdamOptimizer(学習率 = self.学習率)
- 自己最適化 = オプティマイザ最小化(自己コスト)
-
- defエラー(自己):
- 「エラー率を計算する」
- 間違い = tf.not_equal(
- tf.argmax(self._targets, 1)、tf.argmax(self._pred, 1))
- self.errors = tf.reduce_mean( tf.cast (mistakes, tf.float32))
電車- 定義run_epoch(num_epochs=10):
- config = LMConfig() # 設定項目を読み込む
-
- # ソースデータをロードします。ここではトレーニングセットのみが必要です
- train_data、_、_、単語、word_to_id = \
- ptb_raw_data( '簡単な例/データ' )
- config.vocab_size = len(単語数)
-
- # データのバッチ処理
- input_train = PTBInput(config、train_data) を入力します。
- バッチ長 = input_train.バッチ長
-
- # モデルを構築する
- モデル = PTBModel(config)
-
- # セッションを作成し、変数を初期化する
- sess = tf.Session()
- sess.run(tf.global_variables_initializer())
-
- print( 'トレーニングを開始します...' )
- for epoch in range(num_epochs): # 繰り返しラウンド
- for i in range(batch_len): # 通過したバッチの数
- x_batch、y_batch = input_train.next_batch()
-
- # データのバッチを取得して最適化を実行する
- feed_dict = {model._inputs: x_batch、model._targets: y_batch}
- sess.run(model.optim、feed_dict=feed_dict) を実行します。
-
- # 500バッチごとに中間結果を出力します
- i % 500 == 0 の場合:
- コスト = sess.run(model.cost, feed_dict=feed_dict)
-
- メッセージ = "エポック: {0:>3}、バッチ: {1:>6}、損失: {2:>6.3}"
- print(msg.format(エポック + 1, i + 1, コスト))
-
- # 予測結果を出力する
- pred = sess.run(model._pred、feed_dict = feed_dict)
- word_ids = sess.run(tf.argmax(pred, 1))
- print( '予測:' , ' ' . join (words[w] for w in word_ids))
- true_ids = np.argmax(y_batch, 1)
- print( 'True:' , '' . join (words[w]、 wがtrue_idsの場合))
-
- print( 'トレーニングを終了...' )
- セッションを閉じる()
より合理的な結果を得るには、複数回のトレーニングセッションが必要です。 |