LSTMでHola Javascriptの課題を解決する

最近のHola Javascript Challengeに触発されました。 アルゴリズムを64kbでパックすることはありませんが、適切な精度が得られます。



読者はLSTMがどのように機能するかについて一般的な考えを持っていることが理解されます。 そうでない場合は、 この素晴らしいチュートリアルをご覧ください。



課題の課題は、64kbのすべての付随データに適合するアルゴリズムを開発し、この辞書に属する単語をランダムに生成されたガベージシーケンスと区別できるようにすることです。



このようなデータは、特にリカレントニューラルネットワークの研究にとって、ディープラーニングの分野でのエンターテインメントの素晴らしい遊び場であるように思えました。 単語をシンボルの抽象的なシーケンスとして扱い、それらを定義するいくつかの内部ルールをニューラルネットワークから抽出しようとします。 モデルを64キロバイトに追加しようとはしませんが、さらに興味深い精度(0.92+)が得られます。

ソースデータをもう少し詳しく見てみましょう。



語彙

テストケースの例



問題を次のように再定式化します。バイナリシーケンス分類問題を解決し、真のクラスのすべてのシーケンスをトレーニングセットに含めます。 はい、テストセットのデータはトレーニングと重複する場合がありますが、これは肯定的な例のみです。 負の例は一意です。



データ処理



最初のステップはデータ処理です​​。 ポジティブな例では、すべてが単純です-すべての「良い」単語は辞書にあります。 多くの否定的な例を収集するだけで十分です。 それらを構築するには、holaのテストケースを使用するか、独自のジェネレーター( ここにあります )を使用するか、独自のジェネレーターを作成します。 APIを使用したトレーニングの否定的な例を収集しました。



トレーニングデータセットを作成する前に、まず良い例の辞書を少し調べて、「余分な」ものを除外しましょう。 私たちが興味を持っている主なものは、シーケンスの最大長です。私はそれを最小化したいです(もちろん、合理的な範囲内で)。



長さに沿った単語数の分布を作成します。



画像



はい、良い言葉の大部分は21文字より短いです。



データセット内の最も長い単語は衝撃的です:Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch's(初めて読んでみてください!) そのような死体を考慮することを拒否した場合、私たちは多くを失うとは思わない。 良い例をトリムして、最大22文字にしましょう。 もちろん、負の例も同じ長さのみと見なされます。



フィルタリングされたデータ



良い単語 -600 +単語、最大長-22

悪い単語 -3 + kk個のユニークな単語、最大長-22。



わずかなマージン(300万を少し超えるユニークなシーケンス)を含む悪い単語を収集しましたが、これは非常に多くのことです。



ニューラルネットワークの学習



次に、 TensorFlowをバックエンドとして使用するKerasでの双方向LSTMの実装を使用します。



実験中のネットワークアーキテクチャの進化は、おおよそ次のように見えました。



最初の大騒ぎ 。 最も単純で基本的なLSTM。 入力シーケンスは、次元batch_size * MAX_LENGTH * hidden_​​neuronsのテンソルに埋め込まれ、LSTMセルを通過します。 LSTM出力は、各層に1つずつ、2つの出力に密な層で収集され、最終的なクラスがそれに応じて計算されます。 良い単語と2倍の悪い単語の辞書全体をトレーニングします(この割合により、第1種/第2種のエラーを均等化できます)。



model = Sequential() model.add(Embedding(len(char_list), 500, input_length=MAX_LENGTH, mask_zero=True)) model.add(LSTM(64, init='glorot_uniform', inner_init='orthogonal', activation='tanh', inner_activation='hard_sigmoid', return_sequences=False)) model.add(Dense(len(labels_list))) model.add(Activation('softmax'))
      
      





このアプローチにより、各クラスで約0.80の精度を達成できます。 0.80-かなり多く、このような複雑なモデルをさらに活用したいと思います。



2回目の試行 。 学習プロセスで学習率を変更して、精度を高めてみましょう。 Kerasには、このために十分な便利なコールバックメカニズムがあります。 時代の終わりに検証セットでより良い結果が表示されない場合は、lrを絶対に不格好な方法で10回カットします。 ここでそのようなメソッドの可能な実装について議論します 。 ユーザーjiumemによって提案されたコードはほとんど機能します。 スポイラーの下でTensorFlowを使用した正しい操作のためのマイナーな修正の結果:



Keras + TFのLRアニーリングコールバック
 class LrReducer(Callback): def __init__(self, patience=0, reduce_rate=0.1, reduce_nb=5, verbose=1): super(Callback, self).__init__() self.patience = patience self.wait = 0 self.best_score = float("inf") self.reduce_rate = reduce_rate self.current_reduce_nb = 0 self.reduce_nb = reduce_nb self.verbose = verbose def on_epoch_end(self, epoch, logs={}): current_score = logs['val_loss'] print("cur score:", current_score, "current best:", self.best_score) if current_score <= self.best_score: self.best_score = current_score self.wait = 0 self.current_reduce_nb = 0 if self.verbose > 0: print('---current best val accuracy: %.3f' % current_score) else: if self.wait >= self.patience: self.current_reduce_nb += 1 if self.current_reduce_nb <= self.reduce_nb: lr = self.model.optimizer.lr.initialized_value() self.model.optimizer.lr.assign(lr*self.reduce_rate) else: if self.verbose > 0: print("Epoch %d: early stopping" % (epoch)) self.model.stop_training = True self.wait += 1
      
      







前に提案したアーキテクチャでこのコールバックを使用すると、分類精度を0.83に上げることができます。 印象的ではありません。



3番目の試み、最後の試み、興味深いアーキテクチャ-双方向LSTMについて言及しました。 実際、2つのネットワークでシーケンスを2回実行します。1つは右から左に読み取り、もう1つは逆です。 同様の構造が音声認識の分野でうまく使用されています。 同じサイズ、500セル、ただし双方向のネットワークを構築しましょう。



Kerasの双方向LSTM
  hidden_units = 500 left = Sequential() left.add(Embedding(len(char_list), hidden_units, input_length=MAX_LENGTH, mask_zero=True)) left.add(LSTM(output_dim=hidden_units, init='uniform', inner_init='uniform', forget_bias_init='one', return_sequences=False, activation='tanh', inner_activation='sigmoid')) right = Sequential() right.add(Embedding(len(char_list), hidden_units, input_length=MAX_LENGTH, mask_zero=True)) right.add(LSTM(output_dim=hidden_units, init='uniform', inner_init='uniform', forget_bias_init='one', return_sequences=False, activation='tanh', inner_activation='sigmoid', go_backwards=True)) model = Sequential() model.add(Merge([left, right], mode='sum')) model.add(Dense(len(labels_list))) model.add(Activation('softmax'))
      
      







同じデータでlrアニーリングも使用します。



このアプローチにより、ランダムに選択された20万語から構成される検証データの精度0.91〜0.93を安定して取得することができました。その半分はソースディクショナリからのポジティブな例であり、残りの1つはトレーニングセットにないユニークでランダムに選択されたネガティブな例です。 この結果はすでに非常に良好であり、トレーニング時間を増やすことなく安定して改善することはできませんでした。



収集されたすべてのデータにトレーニング済みモデルを設定しようとすることができます(はい、トレーニング中にモデルがほとんどの負の例を見ることができるため、これは完全に正しいわけではありません)。 これを実行して、美しい混同マトリックスを作成しましょう。



画像



トレーニング時間



少し叙情的な余談:GTX960ではすべてが考慮されました。 最後のネットワークは13時代の領域で収束し始め、1時代はほぼ50分と見なされます。



おわりに



かなり妥当な時間で90%を超える精度を受け取りました。 はい、そのようなモデルを64キロバイトに配置する方法はありませんが、それでも、これはLSTMが内部構造をシーケンスでキャプチャする能力、または存在しないことを示すかなり興味深いデモです。



All Articles