ホーム » RNN (ページ 2)

RNN」カテゴリーアーカイブ

TensorFlow 画像キャプション・モデル – コンピュータ・ビジョンと自然言語処理の融合

TensorFlow 画像キャプション・モデル – コンピュータ・ビジョンと自然言語処理の融合

今年 (2016) の 9 月に Google 社により、Microsoft COCO 2015 画像キャプショニング・チャレンジで優秀な成績をおさめた、画像キャプション・モデルが TensorFlow 実装のオープンソースとして公開されました。(キャプションは画像の簡単な説明文です。)

動作検証してみましたので、簡単にご報告します。自前の画像でも試してみましたが、確率つきで3つのキャプション候補が表示され、(人間が見ても)十分に理解可能な結果が得られました。

該当する Google Research Blog 記事は以下です :

Show and Tell: image captioning open sourced in TensorFlow | Google Research Blog

このブログによれば、2014 年に Google Brain チームの研究員が画像を正確に記述するキャプションの自動生成のための機械学習システムのトレーニングを実行し、システムの更なる開発によって Microsoft COCO 2015 画像キャプショニング・チャレンジ(= 正確な画像キャプション計算のベストなアルゴリズムを競うコンペティション)においてトップタイの成績をおさめるという成功に導かれたとのことです。

そして更に、その画像キャプション・モデル最新版を TensorFlow 実装のオープンソースモデルとして利用可能にするとのことで、このリリースはキャプショニング・システムのコンピュータ・ビジョン・コンポーネントへの本質的な改善を含み、オリジナル・システムに比較してより詳細で正確な記述を生成できるようです。

該当ペーパーは以下 :

Show and Tell: Lessons learned from the 2015 MSCOCO Image Captioning Challenge

abstact を簡単にまとめておくと以下のような内容で、最後にコンペと TensorFlow への言及もあります :

自動的に画像の内容を説明することは、コンピュータ・ビジョンと自然言語処理を結びつける、人口知能における基本的な問題です。このペーパーでは、深層 recurrent アーキテクチャをベースとする生成モデルを提示しますが、これはコンピュータ・ビジョンと機械翻訳における最近の進歩を結合し、画像を説明する自然言語の文群を生成するために利用可能なものです。モデルは与えられた訓練画像のターゲット説明文の尤度を最大化するようにトレーニングされます。幾つかのデータセット上での実験はモデルの正確さと画像説明から単独で学習した言語の流暢さを示します。モデルは質的にも量的にも非常に正確なことが多々あります。

それから少し違う角度からの関連記事と Google / University of Edinburgh 共著のペーパーです :

Google researchers teach AIs to see the important parts of images — and tell you about them

Discovering the physical parts of an articulated object class from multiple videos

TensorFlow “Show and Tell ” モデル

今回はオープンソース・モデルの単なる動作検証ですので特に難しい話しはありません。
例によって TensorBoard でグラフを確認しておきますと、Inception モデルと LSTM を組み合わせていますね :

im2txt_graph_all2_with_logo

トレーニング

以下は損失と Perplexity(= 言語モデルの評価尺度、複雑さを表します) のグラフです。
もう少しトレーニングした方が良さそうです :
im3txt_total_loss2-50k

im2txt_perplexity2-50k

検証結果

Microsoft COCO 2015 画像で試すのが一番精度が高いだろうと考えて、ランダムに試してみました。
最初はお皿に盛られた肉、ブロッコリそしてポテトです :

coco_val2014_000000224012

キャプション候補は以下の3つ。合っていますが、1つ目と3つ目の違いはピリオドの有無でしょうか :

Captions for image COCO_val2014_000000224012.jpg:
  0) a plate of food with meat and vegetables . (p=0.000409)
  1) a plate of food with meat and broccoli . (p=0.000255)
  2) a plate of food with meat and vegetables (p=0.000025)

次に海とサーファー。このキャプション・モデルの紹介記事では、(異なる画像ですが)良く利用される題材です :
Oregon Surfers

Captions for image COCO_val2014_000000224477.jpg:
  0) a man riding a wave on top of a surfboard . (p=0.034516)
  1) a person riding a wave on a surfboard . (p=0.007086)
  2) a man riding a wave on a surfboard in the ocean . (p=0.004863)

coco_val2014_000000224000

Captions for image COCO_val2014_000000224000.jpg:
  0) a herd of sheep grazing in a field . (p=0.000379)
  1) a herd of sheep walking down a road . (p=0.000219)
  2) a herd of sheep walking down a street . (p=0.000148)

coco_val2014_000000224051

  0) a man riding a bike down a street . (p=0.000337)
  1) a bicycle is parked on the side of the road . (p=0.000283)
  2) a bicycle is parked on the side of a road . (p=0.000143)

coco_val2014_000000224037

Captions for image COCO_val2014_000000224037.jpg:
  0) a group of boats docked in the water . (p=0.000443)
  1) a group of boats floating in the water . (p=0.000402)
  2) a group of boats docked in a harbor . (p=0.000140)

Imagenet の画像も幾つか試してみましたが、結果はあまり芳しくありませんでした。
おそらく、Imagenet の画像は粗すぎるのだと思います。
honda_jazz_7

  0) a white car parked in front of a car . (p=0.000060)
  1) a white car parked in front of a parking meter . (p=0.000031)
  2) a white car parked in front of a car (p=0.000019)

自前の画像でも幾つか試してみました :

classcat_cat

  0) a cat is sitting on the floor next to a laptop . (p=0.000014)
  1) a cat is sitting on the floor next to a computer . (p=0.000008)
  2) a cat is sitting on the floor next to a cat . (p=0.000007)

apple_persimmon

  0) a bunch of apples are sitting on a table (p=0.000065)
  1) a bunch of apples are sitting on a plate (p=0.000026)
  2) a bunch of apples are sitting on a table . (p=0.000024)
 

以上

TensorFlow RNN ( LSTM / GRU ) で NY ダウ株価予測

TensorFlow RNN ( LSTM / GRU ) で NY ダウ株価予測

基本モデルと実装

RNN (Recurrent Neural Network) は自然言語処理分野で最も成果をあげていますが、得意分野としては時系列解析もあげられます。定番ですが Yahoo Finance が Web で提供している株価情報をベースに RNN で株価予測をしてみました。

使用した RNN ネットワークのアーキテクチャは今回はシンプルなものとして深く積層せずに LSTM と GRU で個別に試しています。window サイズは取り敢えず 10 としましたが再考は必要です。実装はもちろん TensorFlow ですが、MAE (Mean Absolute Error, 平均絶対誤差) の算出には sklearn を使用しています。

提供される情報カラムは Daily で Open, High, Low, Close, Volume, Adj Close ですが、今回は Adj Close prices を使用しました。2000 年 1 月 1 日から 2016 年 10 月 31 日までのデータを取得しています。データの割り振りはトレーニングに 90 %、verification に 5 %、そして予測に 5 % を使用しました。つまり最後の約 10 ヶ月分を予測に回していることになります。(トレーニングの収束は高速です。)

予測誤差が小さい例

(1) アップル社

最初に予測が比較的きれいに当てはまった例を幾つかあげておきます。
まず、以下はアップル社の Adj Close の実値をそのままグラフにしたものです :
apple

そして以下は予測曲線を重ね合わせたものですが、誤差によるズレが殆どないのでこれだけでは分かりにくいので :
lstm_predict_apple

予測期間を zoom in してみた画像が以下です。
LSTM における MAE は 1.042638 ; GRU では 1.060532 でした :
lstm_predict_apple2

 
(2) SONY 社
lstm_predict_sony

MAE: 0.428332 :
lstm_predict_sony2

 
(3) Yahoo 社
lstm_predict_yahoo
MAE: 0.080035 :
lstm_predict_yahoo2

予測誤差が大きい例

予測誤差が小さい例だけを見ていると上手くいっているのか判断しづらいので、誤差が大きい例も見てみます。
直感的には stationarity の考慮が必要なものと思われます。

(1) Amazon 社

以下は、実数曲線に予測曲線を重ね合わせたものです :
lstm_predict_amazon

そして zoom in した画像です。MAE は 198.287123 です :
lstm_predict_amazon2

 

以上

TensorFlow の RNN/LSTM/GRU で消費者感情分析

TensorFlow の LSTM / GRU / bidirectional RNN で IMDb 消費者感情分析

IMDb & LSTM

消費者感情分析という用語が定着しているか分かりませんが、sentiment analysis を意訳したつもりです。IMDb は Internet Movie Database の略です。

LSTM による IMDb を利用した sentiment 分析は元々は Theano のチュートリアルで扱われたものですが :

 

TensorFlow で実装してみました。やることは単純で movie review の評価を positive か negative か二値分類するだけなのですが、LSTM だけでなく GRU と bidirectional RNN でも試してみました。

TensorFlow で実装/訓練

モデルは試行錯誤しましたが、最終的に次のようなモデルを利用しました。
以下は GRU の図ですが LSTM でも同様です :

imdb_model_gru2

それから bidirectional RNN :

imdb_model_bidirectional2

 

訓練は 40 epochs 行ないました。GRU の収束が速いですが LSTM の曲線の方が滑らかですね :

imdb_all_loss3

 

validation 精度は 85 % 前後になりましたが、bidirectional-RNN については 80% 強が上限でした :

imdb_all_acc_validation2

bidirectional-RNN の結果については optimizer やモデルをいじってもあまり変わりませんでしたので、より適合するテーマを見つけた方が良さそうです。

 

以上

正弦波 (sine wave) の RNN (LSTM) による予測の TensorFlow による実装

正弦波 (sine wave) の RNN (LSTM) による予測の TensorFlow による実装

定番ですが、sine wave の RNN による予測をやっていなかったので、例によって TensorFlow で簡単に実装してみました。普通の regression と異なるのは、履歴 sequence から next value を予測することです。

訓練サンプルデータは以下のように安直に生成。取りあえずノイズなしです :

x = np.sin(np.arange(0, 10*math.pi, 0.01))

seq = []
next_val = []

steps_of_history = 100
for i in range(0, len(x) - steps_of_history, 1):
    seq.append(x[i: i + steps_of_history])
    next_val.append(x[i + steps_of_history])

seq = np.reshape(seq, [-1, steps_of_history, 1])
next_val = np.reshape(next_val, [-1, 1])

trainX = np.array(seq)
trainY = np.array(next_val)

モデルは下左図の tensorboard の出力のように LSTM 層を3つ並べて FC (全結合)層を追加してみましたが、実際には simple な RNN 層 + FC 層でも十分だったようです。
下右図は損失グラフで、20 epochs も必要なし。

sine_lstm_graph

sine_lstm_loss

問題が単純過ぎたようですが、一応 matplotlib で描画してみました。
履歴 sequence の長さは 100 です :

sine_lstm2

 

以上

TensorFlow による RNN : Character-Level 言語モデルの実装

TensorFlow による Character-Level 言語モデル

RNN & Character-Level 言語モデル

RNN による言語モデルについても少し試しておきます。
以下は TensorFlow のチュートリアルでも参照されている、Stanford の Andrej Karpathy(現在は OpenAI らしい)による RNN の紹介記事ですが :

 
この中で Character-Level 言語モデルが紹介されています。

We’ll train RNN character-level language models. That is, we’ll give the RNN a huge chunk of text and ask it to model the probability distribution of the next character in the sequence given a sequence of previous characters. This will then allow us to generate new text one character at a time.

ついでに python による簡単な実装例も示されています :

TensorFlow による Character-Level 言語モデルの実装

そして TensorFlow で実装するのですが、実際には A. Karpathy の Torch による実装があるのでそちらを参考にしています。character-level 言語モデルで訓練/サンプリングするためのマルチ層の Recurrent Neural Network (RNN, LSTM そして GRU) を実装していて、具体的にはモデルは一つのテキストファイルを入力として、sequence の次の文字を predict することを学習する RNN を訓練します。
(試した限りでは LSTM よりも GRU の方が良い感じでした。)

題材としては取りあえず、Gutenberg から子供向けの古代エジプトについての歴史書を選んでみました。

で、訓練開始。最初のサンプリングはもちろん意味不明 :

z5L8JqA7ôPPêN:t,yUXaw!7.6àÔâ"ç:Uï7Büâœæàbôæ8XZ46:ï6Z*L MX1ïrâ4JÔôü5.GU8œ8Wê[5çtz.yuMfAt7GToüâPbL"9TzZEUXNmMzzqüæq4UzK*ïG7Dïb4ZZ]ôÔXŒïUâMïæfXWPÔ4'ês4êj6êbGU8MA°]ôêœWZUXWïôZFôJà4ôGYOüPA4âNA4ôPGçT*NZê(übob_KnUtw!Uz8üvfUG9 PT_âwé")kb:0"ôMôG:ÔUDâ.zKdTZoP(AXr?,ôUZ4SiXïU*f"s4WÔqü"564](_4àbUÏZzb4ôXp7_LW",HLkT(,8rxübïôUWbUæZpZrX7.TMWê_X,K(DASELGTü_JPTi5âzGb3PzUHvë44ôzfrMô8ræPtXÔUôbM,UjXê"êi4J3Xdüzlé4,P.WÔP-ôL"ÏU*ZêufrZut'z]"4ék?7âRMï6ô_D*oE4M5ï"o1wUHïP,tê:qqâ7

200 epochs。単語としては成立していますが、英語としてはいまいち…

"Herow? We may be proved the Greek lines for Mamessioh, the hands fall into complete determining
roubt what it was slow thee, under Tigolathy, engradus the month, 12, the trade had
regulication with a more inclier feet; which is consuit and involved,
   Whit heart vine upon the Hittite people of the god and in
mystery, and a pezit of a sixteen three more enjoyovies.

The east the government of "Harehæ). The march. The face
of the monarch remained below the two favous. He hadbendy that h

250 epochs。少し英語らしくなってきた?

In whohe,

           And two Nile Egypt, Psamatik had
the Levinites from their monument to land in different broadling the
blossom of the Egyptian conqueror.

   I came which determined, and had carried had
probably beauty and remove mid. The production of Assour, a
general towns reducted the King"--campte a point of each of his wife and brought
between the circumstances of his others, and to have been the dimensions
war, with difficues of Lakes, Naphuled, and hasing his bounth fine

Ablimn the bas a long god-gual-cylem in expedition, the missult
fronty of kings received afterwards amperiescent feet, and we must, must expenditues
that revolt at Karnak was barbarly one of Caired and Greek might be respected to
very remaining a period may be more him and to cut confine it, in his
worst, a deities of the western city of men cometthing of enarxer till shall be calamities
that here indeed in it thought taken pillarners, between their pas

500 epochs。このモデルではこのあたりが限界でしょうか :

 Egypt
possistration obtaining his limbs, lief force. The same monarch
were told as much invade brich emblec, and to make all the purpose of all period. The like took off the
artigious smaller commonly to ultime a hast to the good delights.
  Let stand who were modern Asiatic doing countries will but a
fewer hundred _shupps and Shumad Bœlusaa, Skirwaan; no one-who
had ever pieces is cut and left between the
Greek nine sides traverse, a certain pure, you adm; but
man had been remotle. Pent

The two hordes the first chariots landed history Esya-space of the
Nile in the crown of Egyptian terrible hand, in the jude timed Horuss to south herself. xpresidrages of
successure strengthen after a live death, and all their cities labour is a considerable
establishment of the podtis foreshonous, the Arab to Tash and in the imagine
food; and a residence Ramesses, came long, and forty numerous in 56°z,
in his kindlim, and after ordered the piece of the Nile, much to the sea, 

 which
make it scarcely skin a bettive grew lakes of the other." It is designated
his general. Antefaadich cemertions must advantage of the number, there were
models, or with the sovereignty of Sheshom and Shabak. The head
of considerable quiltly ashess fell into the other wind submitted by
maintain matiries in each year and Cyrus in the old temple battle--Egypt. Only further
recounces who had like agels out at an indecener and accomplishez, and
the first limbuted had yet reigned and carri

 
以上

Recurrent Neural Networks (2) – Python, Numpy と Theano による RNN の実装 (翻訳/要約)

Recurrent Neural Networks (2) – Python, Numpy と Theano による RNN の実装(翻訳/要約)

* Recurrent Neural Networks Tutorial, Part 2 – Implementing a RNN with Python, Numpy and Theano の簡単な要約です。
* 画像図の引用はしませんので、原文を参照しながらお読みください。

 
本文

Code to follow along is on Github.

RNN 全部をスクラッチから実装します。Python を使用して(GPU 上で演算を実行できるライブラリ)Theano で実装を最適化します。

 

言語モデル

目標は RNN を使用して言語モデルを構築することです。これはこういう意味です。m 単語の文があるとしましょう。言語モデルは文を観測する確率を次のように予測することを可能にします :

\begin{aligned}  P(w_1,...,w_m) = \prod_{i=1}^{m} P(w_i \mid w_1,..., w_{i-1})  \end{aligned}

言葉で言うならば、文の確率はそれより前に来る単語群が与えられた時の各単語の確率の生産物です。従って、文 “He went to buy some chocolate” の確率は “He went to buy some” が与えられた時の “chocolate” の確率になり、(“He went to buy some” は)“He went to buy” が与えられた時の “some” の確率を乗算したもので…等々。

何故それが有用なのでしょうか?文の観測に確率を割り当てることを何故望むのでしょうか?

まず、そのようなモデルはスコアリング・メカニズムとして使用できます。例えば、機械翻訳システムは典型的には入力文に対して複数の候補を生成します。 最もありそうな文を選択するために言語モデルを使用できるでしょう。直感的には、(確率的に)最もありそうな文は文法的にも正しいことがありがちです。同様なスコアリングは音声認識システムでも起きるでしょう。

しかし言語モデル問題の解決はまた cool な副次効果もあります。先行する単語群が与えられた時の単語の確率を予測できるのですから、新しいテキストを生成することも可能です。それは 生成モデル です。
単語群の既存の文が与えられた時、予測された確率から次の単語をサンプリングしてこのプロセスを完全な文を得るまで繰り返します。Andrej Karparthy による RNN の有効性 は言語モデルができることを示します。彼のモデルは full の単語群に対して単一の文字上で訓練され、Shakespeare から Linux コードまで何でも生成できます。

上の等式において、各単語の確率は全ての前の単語上と条件付けられている点に注意してください。実践的には、多くのモデルは計算上のあるいはメモリ制約のためにそのような long-term 依存を表現することに苦戦しています。それらは典型的には2、3の前の単語のみを参照することに制限されています。RNN は、理論的には、そのような long-term 依存を捕捉できますが、実践的にはもう少し複雑です。

 

データの訓練と前処理

私たちの言語モデルを訓練するためにはそこから学習するためのテキストが必要です。幸いなことに言語モデルを訓練するためのラベルは不要で、生テキストのみが必要です。15,000 の長めの reddit コメントを dataset available on Google’s BigQuery からダウンロードしました。私たちのモデルにより生成されたテキストは reddit コメンテーターっぽいでしょう(願わくば)!しかし多くの機械学習プロジェクトのようにデータを適正なフォーマットにするための幾つかの前処理が最初に必要です。

1. テキストをトークン化する

生テキストがありますが、単語毎ベースで予測することを望みます。これはコメントを文に、文を単語にトークン化しなければならないことを意味しています。スペース(空白)で各コメントを分割することもできますが、それは句読点を正しく扱えないでしょう。文 “He left!” は 3 トークンであるべきです: “He”, “left”, “!” 。NLTK の word_tokenize と sent_tokenize メソッドを使用します。

2. 稀な単語を取り除く

テキストの殆どの単語は1回か2回現れるだけです。これらの稀な単語を取り除くことは良い考えです。大規模な語彙を持つことはモデルの訓練を遅くし、そしてそのような単語のための多くの文脈上のサンプルを持たないために、それらを正しく使うことを学習することができないでしょう。これは人間の学習方法と良く似ています。単語を適正に使用する方法を実際に理解するためには異なるコンテキストでそれを見る必要があります。

私たちのコードでは語彙を vocabulary_size 最も一般的な単語に制限します。(8000 に設定しましたが、自由に変更してください。)語彙に含まれていない全ての単語は UNKNOWN_TOKEN で置き換えます。例えば、単語 “nonlinearities” が語彙に含まれていないならば、文 “nonlineraties are important in neural networks” は “UNKNOWN_TOKEN are important in Neural Networks” となります。単語 UNKNOWN_TOKEN は語彙の一部となり他の単語と同様に予測します。新しいテキストを生成する時に UNKNOWN_TOKEN を再度置換できます、例えばランダムにサンプリングされた語彙にない単語を選ぶか、あるいは unknown token を含まない文を得るまで文を生成することもできるでしょう。

3. Prepend special start and end tokens

またどの単語が文の start と end になる傾向があるかも学習したいです。これをするために各文に特殊な SENTENCE_START トークンを先頭に追加して、特殊な SENTENCE_END トークンを追加します。最初のトークンが SENTENCE_START であるとして、次の単語は何になりがちでしょう(文の実際の最初の単語)?

4. 訓練データ行列の構築

RNN への入力はベクトルであり、文字列ではありません。そのため、単語とインデックス間のマッピングを作成します、index_to_word と word_to_index です。例えば、単語 “friendly” は index 2001 かもしれません。訓練サンプル x は [0, 179, 341, 416] のように見えるかもしれません、ここで 0 は SENTENCE_START に相当します。該当ラベル y は [179, 341, 416, 1] のようなものです。私たちの目標は次の単語を予測することを思い出してください、従って y は、最後の要素が SENTENCE_END トークンである、一つ位置をシフトしたベクトル x です。換言すれば、上の単語 179 への正しい予測は 341 になります、実際の次の単語です。

vocabulary_size = 8000
unknown_token = "UNKNOWN_TOKEN"
sentence_start_token = "SENTENCE_START"
sentence_end_token = "SENTENCE_END"
 
# データを読み、SENTENCE_START と SENTENCE_END を追加します。
print "Reading CSV file..."
with open('data/reddit-comments-2015-08.csv', 'rb') as f:
    reader = csv.reader(f, skipinitialspace=True)
    reader.next()
    # full コメントを文群に分割します。
    sentences = itertools.chain(*[nltk.sent_tokenize(x[0].decode('utf-8').lower()) for x in reader])
    # SENTENCE_START と SENTENCE_END を追加します。
    sentences = ["%s %s %s" % (sentence_start_token, x, sentence_end_token) for x in sentences]
print "Parsed %d sentences." % (len(sentences))
     
# 文を単語にトークン化します。
tokenized_sentences = [nltk.word_tokenize(sent) for sent in sentences]
 
# 単語の頻度をカウントします。
word_freq = nltk.FreqDist(itertools.chain(*tokenized_sentences))
print "Found %d unique words tokens." % len(word_freq.items())
 
# 最も一般的な単語群を得て、index_to_word と word_to_index ベクトルを構築します。
vocab = word_freq.most_common(vocabulary_size-1)
index_to_word = [x[0] for x in vocab]
index_to_word.append(unknown_token)
word_to_index = dict([(w,i) for i,w in enumerate(index_to_word)])
 
print "Using vocabulary size %d." % vocabulary_size
print "The least frequent word in our vocabulary is '%s' and appeared %d times." % (vocab[-1][0], vocab[-1][1])
 
# 語彙になり全ての単語を unknown token で置換します。
for i, sent in enumerate(tokenized_sentences):
    tokenized_sentences[i] = [w if w in word_to_index else unknown_token for w in sent]
 
print "\nExample sentence: '%s'" % sentences[0]
print "\nExample sentence after Pre-processing: '%s'" % tokenized_sentences[0]
 
# 訓練データを作成します。
X_train = np.asarray([[word_to_index[w] for w in sent[:-1]] for sent in tokenized_sentences])
y_train = np.asarray([[word_to_index[w] for w in sent[1:]] for sent in tokenized_sentences])

ここにテキストからの実際の訓練サンプルを示します :

x:
SENTENCE_START what are n't you understanding about this ? !
[0, 51, 27, 16, 10, 856, 53, 25, 34, 69]

y:
what are n't you understanding about this ? ! SENTENCE_END
[51, 27, 16, 10, 856, 53, 25, 34, 69, 1]
 

RNN を構築する

言語モデルのための RNN がどのようなものか見てみましょう。
入力 x は単語のシーケンスで各 x_t は単一の単語です。しかしもう一つあります: 行列乗算であるため、入力として単語 index (like 36) を単純には使用できません。代わりに、各単語をサイズ vocabulary_size の one-hot ベクトルで表します。例えば、index 36 の単語は全て 0 で位置 36 が 1 のベクトルになるでしょう。従って、各 x_t はベクトルとなり、x は各行が単語を表す行列です。この変換を前処理で行う代わりに、NN コードで遂行します。ネットワークの出力 o も同様のフォーマットです。各 o_t は vocabulary_size 要素のベクトルで各要素はその単語が文の次の単語となる確率を表します。

RNN のための等式です :

\begin{aligned}  s_t &= \tanh(Ux_t + Ws_{t-1}) \\  o_t &= \mathrm{softmax}(Vs_t)  \end{aligned}

行列とベクトルの次元を書き出すことは有用です。語彙サイズ C = 8000 と隠れ層サイズ H = 100 をすることを仮定しましょう。隠れ層サイズをネットワークの “メモリ” と考えて良いです。それを大きくすることはより複雑なパターンの学習を可能にしますが、追加の計算もまた引き起こします。従って :

\begin{aligned}  x_t & \in \mathbb{R}^{8000} \\  o_t & \in \mathbb{R}^{8000} \\  s_t & \in \mathbb{R}^{100} \\  U & \in \mathbb{R}^{100 \times 8000} \\  V & \in \mathbb{R}^{8000 \times 100} \\  W & \in \mathbb{R}^{100 \times 100} \\  \end{aligned}

これは価値ある情報です。U,VW はデータから学習したい、ネットワークのパラメータであることを思い出してください。従って、総計 2HC + H^2 パラメータを学習する必要があります。
C=8000H=100 の場合にはそれは 1,610,000 になります。

次元はまたモデルのボトルネックも教えてくれます。x_t は one-hot ベクトルなので、それを U で乗算することは U のカラムを選択することと本質的に同じなので、フルに乗算を遂行する必要はありません。そしてネットワークの最大の行列乗算は Vs_t になります。それが語彙サイズをできれば小さくした理由です。

初期化

RNN クラスを宣言してパラメータの初期化をすることから始めます。このクラスを RNNNumpy と呼びます、それは後で Theano 版を実装するからです。パラメータ U,VW の初期化は少しばかり技巧的です。0 で初期化することはできません、何故ならそれは層全部で対称計算 (symmetric calculations) を引き起こす結果になるからです。(訳注: i.e. 上手く学習できない。)それらをランダムに初期化しなければなりません。適正な初期化は訓練結果にインパクトがあるようですのでこの分野では沢山の研究があります。最良の初期化は活性化関数(私たちの場合は \tanh)に依存することは判明していて、一つの 推奨される アプローチは重みを \left[-\frac{1}{\sqrt{n}}, \frac{1}{\sqrt{n}}\right] からの間隔においてランダムに初期化することです。ここで n は前の層からの入ってくる接続 (incoming connections) の数です。これは非常に複雑に見えるかもしれませんが、必要以上にそれを心配しないでください。パラメータを小さなランダム値で初期化する限りは、通常は上手く動作します。

class RNNNumpy:
    def __init__(self, word_dim, hidden_dim=100, bptt_truncate=4):
        # インスタンス値を割り当てます
        self.word_dim = word_dim
        self.hidden_dim = hidden_dim
        self.bptt_truncate = bptt_truncate
        # ネットワーク・パラメータをランダムに初期化
        self.U = np.random.uniform(-np.sqrt(1./word_dim), np.sqrt(1./word_dim), (hidden_dim, word_dim))
        self.V = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (word_dim, hidden_dim))
        self.W = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (hidden_dim, hidden_dim))

上で、word_dim は語彙のサイズで、hidden_dim は隠れ層のサイズです。(選択可能です。)bptt_truncate パラメータについては今のところは心配しないでください、それが何かは後で説明します。

フォワード・プロパゲーション (Forward Propagation)

次に、上述の等式で定義される(単語の確率を予測する)forward propagation を実装しましょう。

def forward_propagation(self, x):
    # 時間ステップの総数
    T = len(x)
    # forward propagation の間、全ての隠れ状態は s に保存します、何故なら後で必要になるので。
    # 初期隠れ (initial hidden) のための追加要素を追加します、これは 0 に設定します。
    s = np.zeros((T + 1, self.hidden_dim))
    s[-1] = np.zeros(self.hidden_dim)
    # 各時間ステップにおける出力。再度、後のために保存します。
    o = np.zeros((T, self.word_dim))
    # 各時間ステップのために…
    for t in np.arange(T):
        # U を x[t] でインデックスしていることに注意してください。
        # これは U に one-hot ベクトルを乗算することと同じです。
        s[t] = np.tanh(self.U[:,x[t]] + self.W.dot(s[t-1]))
        o[t] = softmax(self.V.dot(s[t]))
    return [o, s]
 
RNNNumpy.forward_propagation = forward_propagation

計算された出力だけでなく、隠れ状態も返します。それらは勾配を計算するために後で使います、そしてそれらをここで返すことにより計算の重複を回避します。各 o_t は語彙の単語を表す確率のベクトルです。しかし時に、例えばモデルを評価する時に、望むことは最も高い確率の次の単語です。関数 predict を呼び出します :

def predict(self, x):
    # forward propagation を遂行して最も高いスコアの index を返します。
    o, s = self.forward_propagation(x)
    return np.argmax(o, axis=1)
 
RNNNumpy.predict = predict

新たに実装したメソッドを試してサンプル出力を見てみましょう :

np.random.seed(10)
model = RNNNumpy(vocabulary_size)
o, s = model.forward_propagation(X_train[10])
print o.shape
print o
(45, 8000)
[[ 0.00012408  0.0001244   0.00012603 ...,  0.00012515  0.00012488
   0.00012508]
 [ 0.00012536  0.00012582  0.00012436 ...,  0.00012482  0.00012456
   0.00012451]
 [ 0.00012387  0.0001252   0.00012474 ...,  0.00012559  0.00012588
   0.00012551]
 ..., 
 [ 0.00012414  0.00012455  0.0001252  ...,  0.00012487  0.00012494
   0.0001263 ]
 [ 0.0001252   0.00012393  0.00012509 ...,  0.00012407  0.00012578
   0.00012502]
 [ 0.00012472  0.0001253   0.00012487 ...,  0.00012463  0.00012536
   0.00012665]]

(訳注: 動作確認済み、コンソール出力一致。)

文の各単語のために(上では 45)、モデルは次の単語の確率を表す 8000 の予測を作成しました。U,V,W をランダム値に初期化したので、現時点でこれらの予測は完全にランダムであることに注意してください。次は、各単語のための最も高い確率予測の index を当てます :

predictions = model.predict(X_train[10])
print predictions.shape
print predictions
(45,)
[1284 5221 7653 7430 1013 3562 7366 4860 2212 6601 7299 4556 2481 238 2539
 21 6548 261 1780 2005 1810 5376 4146 477 7051 4832 4991 897 3485 21
 7291 2007 6006 760 4864 2182 6569 2800 2752 6821 4437 7021 7875 6912 3575]

(訳注: 動作確認済み、コンソール出力一致。)

損失を計算する

ネットワークを訓練するためにはそれが起こすエラーを計測する方法が必要です。これを損失関数 L と呼び、目標は、訓練データのための損失関数を最小化するパラメータ U,VW を見つけることです。損失関数に対する一般的な選択は 交差エントロピー損失 です。もし N 訓練サンプル(テキストの単語)と C クラス(語彙のサイズ)を持つ場合、予測 o と真のラベル y に関連する損失は次で与えられます :

\begin{aligned}  L(y,o) = - \frac{1}{N} \sum_{n \in N} y_{n} \log o_{n}  \end{aligned}

式は少し複雑に見えますが、実際にそれが行っていることは訓練サンプルに渡って合計して予測がどれだけ間違っているかをベースにして損失に加算しているだけです。y(正しい単語)と o(予測)が離れれば離れるほど、損失が大きくなっていきます。関数 calculate_loss を実装します :

def calculate_total_loss(self, x, y):
    L = 0
    # 各文に対して…
    for i in np.arange(len(y)):
        o, s = self.forward_propagation(x[i])
        # 「正しい」単語の予測だけに注意を払います。
        correct_word_predictions = o[np.arange(len(y[i])), y[i]]
        # どれだけ間違えたかをベースに損失に加算します。
        L += -1 * np.sum(np.log(correct_word_predictions))
    return L

def calculate_loss(self, x, y):
    # 訓練サンプルの数で損失合計を除算する
    N = np.sum((len(y_i) for y_i in y))
    return self.calculate_total_loss(x,y)/N

RNNNumpy.calculate_total_loss = calculate_total_loss
RNNNumpy.calculate_loss = calculate_loss

ステップバックしてランダムな予測のための損失とは何かを考えてみましょう。それはベースラインを与えてくれて実装が正しいことを確かにしてくれます。語彙に C 単語を持ちますから、各単語は(平均して)確率 1/C で予測されます、これは L = -\frac{1}{N} N \log\frac{1}{C} = \log C の損失を生むでしょう :

# 時間の節約のために 1000 サンプルに制限します。
print "Expected Loss for random predictions: %f" % np.log(vocabulary_size)
print "Actual loss: %f" % model.calculate_loss(X_train[:1000], y_train[:1000])
Expected Loss for random predictions: 8.987197
Actual loss: 8.987440

(訳注: 動作確認済み、コンソール出力一致。)

非常に近いです! フル・データセット上の損失を評価することは高コストな演算で、沢山のデータを持つならば数時間かかるかもしれません。

SGD と Backpropagation Through Time (BPTT) により RNN を訓練する

訓練データ上の合計損失を最小化するパラメータ U,VW を見つけることを望んでいることを思い出してください。これを行なう最も一般的な方法は SGD (Stochastic Gradient Descent) – 確率的勾配降下法です。SGD の裏にある考えは非常に簡単です。訓練サンプルに渡って反復して、そして各反復においてエラーを減少させる方向にパラメータを少し押してやります。これらの方向は損失上の勾配で与えられます : \frac{\partial L}{\partial U}, \frac{\partial L}{\partial V}, \frac{\partial L}{\partial W}. SGD はまた学習率を必要とします、これは各反復においてどの位の大きさのステップをするかを定義します。SGD は、NN のためだけではなく、多くの他の機械学習アルゴリズムのためにも最も人気のある最適化手法です。それ自体、バッチ、並列性そして適応可能な学習率を使用して SGD をどのように最適化するかについては多くの研究があります。基本的な考え方は単純ですが、実際に効率的な方法で SGD を実装することは非常に複雑です。SGD についてもっと学びたければ これ が始めるに良い地点です。最適化のバックグラウンドさえなくても理解できるはずの SGD の単純版を実装します。

しかし上述したそれらの勾配をどのように計算するのでしょうか?伝統的な NN ではこれを backpropagation アルゴリズムを通して行ないます。RNN では、Backpropagation Through Time (BPTT) と呼ばれる、このアルゴリズムを少し修正した版を使用します。パラメータはネットワークの全ての時間ステップで共有されますので、各出力における勾配は現在の時間ステップの計算上だけではなく、前の時間ステップ群にも依存します。微積分を知っていれば、それは実際に chain rule (合成関数の微分の連鎖律 )を適用するだけです。チュートリアルの次のパートはすべて BPTT についてですので、導関数についてはここでは深入りしません。backpropagation への一般的なイントロは これこの投稿 をチェックしてください。今のところは BPTT をブラックボックスとして扱ってかまいません。それは訓練サンプル (x,y) を入力として取り、勾配 \frac{\partial L}{\partial U}, \frac{\partial L}{\partial V}, \frac{\partial L}{\partial W} を返します。

def bptt(self, x, y):
    T = len(y)
    # forward propagation を遂行する
    o, s = self.forward_propagation(x)
    # これらの変数の勾配を accumulate する
    dLdU = np.zeros(self.U.shape)
    dLdV = np.zeros(self.V.shape)
    dLdW = np.zeros(self.W.shape)
    delta_o = o
    delta_o[np.arange(len(y)), y] -= 1.
    # For each output backwards...
    for t in np.arange(T)[::-1]:
        dLdV += np.outer(delta_o[t], s[t].T)
        # Initial delta 計算
        delta_t = self.V.T.dot(delta_o[t]) * (1 - (s[t] ** 2))
        # Backpropagation through time (for at most self.bptt_truncate steps)
        for bptt_step in np.arange(max(0, t-self.bptt_truncate), t+1)[::-1]:
            # print "Backpropagation step t=%d bptt step=%d " % (t, bptt_step)
            dLdW += np.outer(delta_t, s[bptt_step-1])              
            dLdU[:,x[bptt_step]] += delta_t
            # 次のステップのための delta を更新する
            delta_t = self.W.T.dot(delta_t) * (1 - s[bptt_step-1] ** 2)
    return [dLdU, dLdV, dLdW]

RNNNumpy.bptt = bptt

勾配チェック

backpropagation を実装する時はいつでも勾配チェックも実装することは良い考えです、これは貴方の実装が正しいことを検証する方法です。勾配チェックの背後の考えはパラメータの導関数はその点での傾きに等しいことです、これはパラメータを少し変更して変更で除算することにより見積もることができます。

\begin{aligned}  \frac{\partial L}{\partial \theta} \approx \lim_{h \to 0} \frac{J(\theta + h) - J(\theta -h)}{2h}  \end{aligned}

それから backpropagation を使用して計算した勾配と上の方法で見積もった勾配を比較します。大きな違いがなければ良いでしょう。近似は全てのパラメータに対しての合計損失を計算する必要があるので、勾配チェックは非常に高コストです。(忘れないでください、上のサンプルで 100 万以上のパラメータを持ちました。)
そこで小さい語彙のモデルで遂行するのが良いアイデアです。

def gradient_check(self, x, y, h=0.001, error_threshold=0.01):
    # backpropagation を使用して勾配を計算します。これらが正しいかチェッカーが欲しいです。
    bptt_gradients = self.bptt(x, y)
    # チェックしたいパラメータのリスト。
    model_parameters = ['U', 'V', 'W']
    # 各パラメータのための勾配チェック
    for pidx, pname in enumerate(model_parameters):
        # Get the actual parameter value from the mode, e.g. model.W
        parameter = operator.attrgetter(pname)(self)
        print "Performing gradient check for parameter %s with size %d." % (pname, np.prod(parameter.shape))
        # Iterate over each element of the parameter matrix, e.g. (0,0), (0,1), ...
        it = np.nditer(parameter, flags=['multi_index'], op_flags=['readwrite'])
        while not it.finished:
            ix = it.multi_index
            # Save the original value so we can reset it later
            original_value = parameter[ix]
            # Estimate the gradient using (f(x+h) - f(x-h))/(2*h)
            parameter[ix] = original_value + h
            gradplus = self.calculate_total_loss([x],[y])
            parameter[ix] = original_value - h
            gradminus = self.calculate_total_loss([x],[y])
            estimated_gradient = (gradplus - gradminus)/(2*h)
            # Reset parameter to original value
            parameter[ix] = original_value
            # The gradient for this parameter calculated using backpropagation
            backprop_gradient = bptt_gradients[pidx][ix]
            # calculate The relative error: (|x - y|/(|x| + |y|))
            relative_error = np.abs(backprop_gradient - estimated_gradient)/(np.abs(backprop_gradient) + np.abs(estimated_gradient))
            # If the error is to large fail the gradient check
            if relative_error > error_threshold:
                print "Gradient Check ERROR: parameter=%s ix=%s" % (pname, ix)
                print "+h Loss: %f" % gradplus
                print "-h Loss: %f" % gradminus
                print "Estimated_gradient: %f" % estimated_gradient
                print "Backpropagation gradient: %f" % backprop_gradient
                print "Relative Error: %f" % relative_error
                return 
            it.iternext()
        print "Gradient check for parameter %s passed." % (pname)

RNNNumpy.gradient_check = gradient_check

# To avoid performing millions of expensive calculations we use a smaller vocabulary size for checking.
grad_check_vocab_size = 100
np.random.seed(10)
model = RNNNumpy(grad_check_vocab_size, 10, bptt_truncate=1000)
model.gradient_check([0,1,2,3], [1,2,3,4])

訳注: 実行結果 :

Performing gradient check for parameter U with size 1000.
rnn_numpy.py:115: RuntimeWarning: invalid value encountered in double_scalars
  relative_error = np.abs(backprop_gradient - estimated_gradient)/(np.abs(backprop_gradient) + np.abs(estimated_gradient))
Gradient check for parameter U passed.
Performing gradient check for parameter V with size 1000.
Gradient check for parameter V passed.
Performing gradient check for parameter W with size 100.
Gradient check for parameter W passed.

SGD 実装

パラメータに対する勾配を計算できるようになったので
SGD を実装できます :

1. 関数 sdg_step は勾配を計算して一つのバッチに対して更新を遂行します。
2. 外部ループ (outer loop) は訓練セットを通して反復して学習率を調整します。

# SGD の 1 ステップを実行します。
def numpy_sdg_step(self, x, y, learning_rate):
    # 勾配を計算します。
    dLdU, dLdV, dLdW = self.bptt(x, y)
    # 勾配と学習率に従ってパラメータを変更します。
    self.U -= learning_rate * dLdU
    self.V -= learning_rate * dLdV
    self.W -= learning_rate * dLdW

RNNNumpy.sgd_step = numpy_sdg_step
# Outer SGD ループ
# - model: RNN モデル・インスタンス
# - X_train: 訓練データ・セット
# - y_train: 訓練データ・ラベル
# - learning_rate: SGD のための初期学習率
# - nepoch: 完全なデータセットを通して反復するための回数
# - evaluate_loss_after: この多くの epoch 後の損失を評価する
def train_with_sgd(model, X_train, y_train, learning_rate=0.005, nepoch=100, evaluate_loss_after=5):
    # We keep track of the losses so we can plot them later
    losses = []
    num_examples_seen = 0
    for epoch in range(nepoch):
        # Optionally evaluate the loss
        if (epoch % evaluate_loss_after == 0):
            loss = model.calculate_loss(X_train, y_train)
            losses.append((num_examples_seen, loss))
            time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            print "%s: Loss after num_examples_seen=%d epoch=%d: %f" % (time, num_examples_seen, epoch, loss)
            # Adjust the learning rate if loss increases
            if (len(losses) > 1 and losses[-1][1] > losses[-2][1]):
                learning_rate = learning_rate * 0.5  
                print "Setting learning rate to %f" % learning_rate
            sys.stdout.flush()
        # For each training example...
        for i in range(len(y_train)):
            # One SGD step
            model.sgd_step(X_train[i], y_train[i], learning_rate)
            num_examples_seen += 1
            

Done ! ネットワークを訓練するのにどの程度長くかかるか感じ取ってみましょう :

np.random.seed(10)
model = RNNNumpy(vocabulary_size)
%timeit model.sgd_step(X_train[10], y_train[10], 0.005)

Uh-oh, bad news. 私のラップトップで SGD の 1 ステップは約 350 ミリ秒かかりました。
訓練データには約 80,000 サンプルがありますから、1 epoch(全てのデータセットに渡る反復)は数時間かかります。複数の epoch は数日あるいは数週間さえかかるでしょう!そして私たちは依然として、多くの企業や研究者がそこで使用しているものと比較して、小さなデータセットで作業しています。What now?

幸いなことにコードをスピードアップする多くの方法があります。同じモデルにこだわってコードをより速く動作することもできますし、あるいは計算上のコストを下げるようにモデルを修正しても良いですし、あるいは両方でも良いです。

研究者はモデルを計算上のコストを下げるような沢山の方法を同定してきました、例えば大規模な行列乗算を回避するために階層 softmax を使用したり射影 (projection) 層を追加することによってです。(これ または これ を参照). しかしモデルを単純なままに保持して最初のルートを進みます: GPU を使用して実装を速くします。けれどもそれを行なう前に、小さなデータセットで SGD を実行して損失が実際に減少することをチェックしてみましょう:

np.random.seed(10)
# Train on a small subset of the data to see what happens
model = RNNNumpy(vocabulary_size)
losses = train_with_sgd(model, X_train[:100], y_train[:100], nepoch=10, evaluate_loss_after=1)
2015-09-30 10:08:19: Loss after num_examples_seen=0 epoch=0: 8.987425
2015-09-30 10:08:35: Loss after num_examples_seen=100 epoch=1: 8.976270
2015-09-30 10:08:50: Loss after num_examples_seen=200 epoch=2: 8.960212
2015-09-30 10:09:06: Loss after num_examples_seen=300 epoch=3: 8.930430
2015-09-30 10:09:22: Loss after num_examples_seen=400 epoch=4: 8.862264
2015-09-30 10:09:38: Loss after num_examples_seen=500 epoch=5: 6.913570
2015-09-30 10:09:53: Loss after num_examples_seen=600 epoch=6: 6.302493
2015-09-30 10:10:07: Loss after num_examples_seen=700 epoch=7: 6.014995
2015-09-30 10:10:24: Loss after num_examples_seen=800 epoch=8: 5.833877
2015-09-30 10:10:39: Loss after num_examples_seen=900 epoch=9: 5.710718

(訳注: 以下は検証結果)

2016-03-20 20:41:27: Loss after num_examples_seen=0 epoch=0: 8.987425
2016-03-20 20:41:49: Loss after num_examples_seen=100 epoch=1: 8.976270
2016-03-20 20:42:12: Loss after num_examples_seen=200 epoch=2: 8.960212
2016-03-20 20:42:33: Loss after num_examples_seen=300 epoch=3: 8.930430
2016-03-20 20:42:57: Loss after num_examples_seen=400 epoch=4: 8.862264
2016-03-20 20:43:24: Loss after num_examples_seen=500 epoch=5: 6.913570
2016-03-20 20:43:55: Loss after num_examples_seen=600 epoch=6: 6.302493
2016-03-20 20:44:25: Loss after num_examples_seen=700 epoch=7: 6.014995
2016-03-20 20:44:52: Loss after num_examples_seen=800 epoch=8: 5.833877

Good, 私たちの実装は少なくとも何か有用なことをして損失を減らしているようです、望んだように。

 

ネットワークを Theano と GPU で訓練する

以前に Theno について tutorial を書きました、そしてロジックは正確に同じままなのでここでは再度最適化されたコードを通り抜けはしません。numpy 計算を相当する Theano の計算に置き換える、RNNTheano クラスを定義しました。

np.random.seed(10)
model = RNNTheano(vocabulary_size)
%timeit model.sgd_step(X_train[10], y_train[10], 0.005)

今回は、一つの SGD ステップは私の Mac (without GPU) で 70ms、GPU 装備の Amazon EC2 インスタンス g2.2xlarge 上で 23 ms です。それは初期実装の 15x の改善でモデルを数週間の代わりに数時間/数日で訓練できることを意味しています。依然として数多くの可能な最適化がありますが、当面はこれで十分です。

モデルを訓練するのに数日間を費やすことを貴方が回避することを助けるため、Theano モデルを 50 次元の隠れ層と 8000 の語彙で事前訓練しました。50 epoch に対して約 20 時間の訓練をしました。損失は依然として減少していて、より長時間の訓練は間違いなくより良いモデルになるでしょう。貴方自身で自由に試して長時間訓練してみてください。モデル・パラメータは Github レポジトリの data/trained-model-theano.npz で見つかります。そして load_model_parameters_theano メソッドを使用してそれらをロードします。

from utils import load_model_parameters_theano, save_model_parameters_theano

model = RNNTheano(vocabulary_size, hidden_dim=50)
# losses = train_with_sgd(model, X_train, y_train, nepoch=50)
# save_model_parameters_theano('./data/trained-model-theano.npz', model)
load_model_parameters_theano('./data/trained-model-theano.npz', model)

テキストを生成する

モデルを持った今、それに私たちのために新しいテキストを生成することを頼むことができます。新しい文を生成するためのヘルパー関数を実装しましょう:

def generate_sentence(model):
    # We start the sentence with the start token
    new_sentence = [word_to_index[sentence_start_token]]
    # Repeat until we get an end token
    while not new_sentence[-1] == word_to_index[sentence_end_token]:
        next_word_probs = model.forward_propagation(new_sentence)
        sampled_word = word_to_index[unknown_token]
        # We don't want to sample unknown words
        while sampled_word == word_to_index[unknown_token]:
            samples = np.random.multinomial(1, next_word_probs[-1])
            sampled_word = np.argmax(samples)
        new_sentence.append(sampled_word)
    sentence_str = [index_to_word[x] for x in new_sentence[1:-1]]
    return sentence_str

num_sentences = 10
senten_min_length = 7

for i in range(num_sentences):
    sent = []
    # We want long sentences, not sentences with one or two words
    while len(sent) < senten_min_length:
        sent = generate_sentence(model)
    print " ".join(sent)

2、3の選択された(検閲された)文です。大文字にしてあります。

  • Anyway, to the city scene you’re an idiot teenager.
  • What ? ! ! ! ! ignore!
  • Screw fitness, you’re saying: https
  • Thanks for the advice to keep my thoughts around girls.
  • Yep, please disappear with the terrible generation.

生成テキストを見ると注意すべき2、3の興味深いことがあります。モデルはシンタックスを成功的に学習しています。コンマを(通常は and や or の前に)正しく置いて文を句点で終了させます。時々それは複数の感嘆符やスマイリーのようなインターネット・スピーチを真似します。

けれども、生成テキストの大半は意味をなさないか、文法的なエラーがあります。(上では実際には最良なものを選んでいます。)一つの理由はネットワークを十分に訓練していないことです。(あるいは十分な訓練データを使用していないことです。)それは多分正しいです、しかし主要な理由ではおそらくないでしょう。私たちの vanilla RNN は意味のあるテキストを生成できません、何故なら幾つかのステップが離れた単語間の依存性を学習できないからです。それは RNN が最初に創られた時に人気の獲得に失敗した理由でもあります。それらは理論的には美しいですが実践的には上手く動作しませんでした、そして私たちは何故かを直ちに理解しませんでした。

幸いなことに、RNN を訓練する難しさは現在では 非常に良く理解されて います。このチュートリアルの次のパートでは Backpropagation Through Time (BPTT) アルゴリズムをより詳しく探求します。そして vanishing gradient problem(勾配消失問題)と呼ばれるものをデモします。これは、LSTM のようなより洗練された RNN モデルへと進む動機となります。これは NLP の多くのタスクに対して最新の技術です(そして非常に良い reddit コメントを生成できます!)。このチュートリアルで学んだこと全てはLSTM と他の RNN モデルにも当てはまりますので、vanilla RNN の結果が期待以下だとしてもがっかりしないでください。

 

以上

Recurrent Neural Networks (1) – RNN への序説 (翻訳/要約)

Recurrent Neural Networks (1) – RNN への序説 (翻訳/要約)

* Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs の簡単な要約です。
* 原文の図を参照しながらお読みください。

 
本文

チュートリアルの一部として RNN ベースの言語モデルを実装します。言語モデルのアプリケーションは2つの fold を持ちます : 最初に、現実世界で起きることの尤もらしさをベースに任意の文に点数をつけることを可能にします。これは文法的、意味的な正確性の尺度を提供します。そのようなモデルは典型的には機械翻訳システムの一部として使用されます。2つ目に、言語モデルは新しいテキストの生成を可能にします。(よりクールなアプリケーションでしょう。)Shakespeare 上で言語モデルを訓練すれば Shakespeare ライクなテキストの生成を可能にします。Andrej Karpathy による 興味深い投稿要約)は RNN ベースの文字レベル言語モデルが何ができるかを示しています。

基本的な NN にある程度慣れていることを仮定しています。そうでないなら、Implementing A Neural Network From Scratch へ進むことを望むかもしれません、これは Non-RNN の背後のアイデアや実装を紹介ます。

 

RNN とは何か?

RNN の背後のアイデアはシーケンシャル情報を利用することです。伝統的な NN では全ての入力(そして出力)は互いに独立です。しかしそれは多くのタスクに対しては非常に悪い考えです。文で次の単語を予測したければその前にどの単語が来たか知る方が良いでしょう。RNN は “recurrent (再発的・周期的)” と呼ばれますが、これはシークエンスの全ての要素に対して(前の計算に依存する出力とともに)同じタスクを遂行するからです。RNN について考える他の方法はそれが “メモリ” を持つことです、これはそれまでに何が計算されたかについての情報を捕捉します。理論的には RNN は任意の長さのシーケンスの情報を利用できますが、しかし実践的には 2, 3 ステップ(後でそれ以上)を look back することに制限されます。典型的な RNN がどのように見えるかをここに示します : (図/省略)

上の図は RNN がフル・ネットワークに展開される (unrolled, unfolded) ことを示しています。この展開は、完全なシーケンスのためのネットワークを書き出していることを単に意味しています。例えば、気にかけるシーケンスが5つの単語の文の場合、ネットワークは 5-層 NN に展開されます、各単語のために 1 層です。RNN で起きる計算を統治する数式は以下のようなものです :

  • x_t はタイム・ステップ t における入力です。例えば、x_1 は文の2番目の単語に相当する one-hot ベクトルです。
  • s_t はタイム・ステップ t における隠れ状態 (hidden state) です。これはネットワークの「メモリ」です。
    s_t は前の隠れ状態と現在のステップの入力をベースに計算されます : s_t=f(Ux_t + Ws_{t-1})
    関数 f は通常は tanh あるいは ReLU のような非線形性です。
    s_{-1}、これは最初の隠れ状態の計算に必要ですが、典型的には全て 0 に初期化されます。
  • o_t はステップ t における出力です。例えば、文の次の単語を予測することを望む場合、それは確率のベクトルになります。 o_t = \mathrm{softmax}(Vs_t).

ここで2、3の注意すべき点があります :

  • 隠れ状態 s_t はネットワークのメモリとして考えて良いです。s_t は全ての前の時間ステップにおいて発生したことについての情報を捕捉します。ステップ o_t における出力は時間 t におけるメモリをベースに単独で計算されます。簡単に上述したように、実践ではもう少し複雑です、何故ならば s_t は通常は非常に多くの過去の時間ステップから情報を捕捉することはできないからです。
  • 各層で異なるパラメータを使用する伝統的な DNN と違って、RNN は同じパラメータ ((U, V, W) を全てのステップに渡り共有します。これは各ステップで同じタスクを、異なる入力だけで、遂行しているという事実を反映しています。これは学習しなければならないパラメータの総数を大きく減らします。
  • 上図は各時間ステップで出力を持ちますが、タスクによってはこれは必要ないかもしれません。例えば、文の感情を予測する時、各単語の後の感情ではなくて、最終的な出力のみに注意を払うかもしれません。同様に、各時間ステップで入力は必要ないかもしれません。RNN の主要な特徴はその隠れ状態にあります、これはシーケンスについての幾つかの情報を捕捉します。
 

RNN は何ができるか?

RNN は多くの NLP タスクで大きな成功を見せてきました。ここで RNN の最も共通的に使用されるタイプは LSTM であることに言及すべきでしょう。これは long-term 依存を捕捉する点で vanilla RNN よりも遥に優れています。しかし心配しないでください、LSTM は本質的にはこのチュートリアルで開発るる RNN と同じものです、それらは隠れ状態の異なる計算方法を持つだけです。

ここで NLP における RNN の幾つかの例となるアプリケーションを示します。

言語モデルとテキスト生成

単語のシーケンスが与えられた時、前の単語群が与えられた時の各単語の確率を予測したいです。言語モデルは文の尤もらしさを計測することを可能にし、これは機械翻訳のための重要な入力となります。(何故なら高い確率の文は典型的には正しいからです。)次の単語を予測することができる副次的効果は生成モデルを得られることです、これは出力確率からのサンプリングによる新しいテキストの生成を可能にします。そして訓練データに依存して あらゆるもの が生成できます。 言語モデルでは入力は典型的には(例えば one-hot ベクトルとしてエンコードされた)単語のシーケンスで、出力は予測された単語のシーケンスです。ネットワークを訓練する時には o_t = x_{t+1} とします、何故ならステップ t における出力を実際の次の単語として望むからです。

言語モデルとテキスト生成についての研究論文です :

機械翻訳

機械学習は、入力がソース言語(例えばドイツ語)における単語のシーケンスである、という言語モデルと同様です。ターゲット言語(例えば英語)における単語のシーケンスを出力することを望みます。鍵となる違いは、出力が完全な入力を見た後でのみ始まることです。何故なら訳文の最初の単語は完全な入力文から捕捉された情報を必要とするかもしれないからです。

機械翻訳についての研究論文です :

音声認識

音波からの音響信号の入力シーケンスが与えられた時、音声セグメント (phonetic segments) を確率と一緒に予測できます。

音声認識についての研究論文です :

画像説明(文)を生成する

畳み込み NN と一緒に、RNN は unlabeled 画像のための generate descriptions へのモデルの一部として使用されています。これがどれほど上手く動作しているか驚異的です。combined モデルは、画像で見られる特徴と生成された単語を連携させる (align) ことさえします。

 

RNN を訓練する

RNN の訓練は伝統的な NN の訓練と同様です。backpropagation アルゴリズムもまた使いますが、少し捻っています。パラメータがネットワークの全ての時間ステップで共有されますので、各出力の勾配は現在の時間ステップの計算上だけではなく、前のステップにも依存します。例えば、t=4 における勾配を計算するためには 3 ステップを backpropagate して勾配を加算する必要があるでしょう。これは BPTTBackpropagation Through Time と呼ばれます。今のところ、BPTT で訓練された vanilla RNN は long-term 依存(= 遠く離れたステップ間の依存)を学習するには、勾配消失/発散問題 (vanishing/exploding gradient problem) と呼ばれるもののために 困難を持つ という事実を知っておいてください。これらの問題を扱うために幾つかの機械が存在して、(LSTM のような)あるタイプの RNN がこれを回避するために特に設計されました。

 

RNN 拡張

何年もの間、vanilla RNN モデルの欠点の幾つかに対処するために、研究者は RNN のより洗練されたタイプを開発してきました。この章はモデルの分類に慣れるように簡単な概要を提供します。

Bidirectional RNN は時間 t における出力がシーケンスの前の要素だけではなく、未来の要素にも依存するというアイデアをベースにしています。例えば、文の欠けた単語を予測するためには左側と右側のコンテキストを見たいでしょう。Bidirectional RNN は非常に単純です。それらは互いのトップに積み重ねた、単に2つの RNN です。そして出力は両方の RNN の隠れ状態をベースにして計算されます。

Deep (Bidirectional) RNN は Bidirectional RNN と同様ですが、時間ステップ毎に多層を持ちます。実際にこれはより高い学習キャパシティを与えてくれます。(しかしまた沢山の訓練データが必要になりますが。)

LSTM ネットワーク は最近極めて人気があり上でも簡単に話題にしました。LSTM は基本的には RNN と違うアーキテクチャを持つわけではありませんが、隠れ状態を計算するために異なる関数を使用します。LSTM のメモリはセルと呼ばれ、それらを、入力として前の状態 h_{t-1} と現在の入力 x_t を取るブラックボックスと考えることができます。内部的にはこれらのセルは何をメモリに保持するか(そしてそこから何を消すか)を決めます。そしてそれらは前の状態、現在のメモリ、そして入力を結合します。これらのタイプのユニットは long-term 依存を捕捉するために非常に効率的であることが分かっています。

 

以上

LSTM ネットワークの理解 (翻訳/要約)

LSTM ネットワークの理解 (翻訳/要約)

* TensorFlow : コード解説 : RNN – リカレント・ニューラルネットワーク & LSTM で参照されている Understanding LSTM Networks の要約です。原文の図を参照しながらお読みください。

 

RNNリカレント・ニューラルネットワーク

人間は刻々とゼロから思考を始めるわけではありません。この文書を読む時でも、前の単語の理解を元に各単語を理解しており、全てを捨ててゼロから再度考え始めるわけではありません。人間の思考には持続性があります。伝統的な NN はこれができません。大きな欠点です。例えば、映画の全てのポイントでどのような種類のイベントが起きているか分類することを望むとして、伝統的な NN が映画の前のイベントについての情報を後のイベントに伝えるための方法が判然としません。

RNNリカレント・ニューラルネットワーク はこの問題を処理します。RNN はそれらの中にループを持つことで情報を持続することを可能にします。NN の塊 A がある入力 xt を見て値 ht を出力するとします。ループは情報がネットワークの一つのステップから次へ渡されることを可能にします。

これらのループは RNN を幾分神秘的にしていますが、普通の NN とそれほど違うわけではありません。RNN は個々が継承者にメッセージを渡す同じネットワークの複数のコピーとして考えられます。ループを展開して得られる、鎖状 (chain-like) の性質は RNN がシークエンスとリストに親密に関係することを示しています。これらはそのようなデータを使用するために自然なアーキテクチャです。

ここ2、3年で RNN を各種の問題に適用することについて大きな成功があります : 音声認識、言語モデリング、翻訳、画像キャプション …。RNN の成果についての議論は Andrej Karpathy のブログポスト – The Unreasonable Effectiveness of Recurrent Neural Networks に委ねますが、驚異的です。

これらの成功の本質は LSTM の使用にあります。これは RNN の特殊な種類ですが、多くのタスクに対して標準判よりも非常に上手く動作します。RNN 上の殆どの素晴らしい結果はこれによって達成されています。

 

Long-Term 依存の問題

RNN の主張の一つは、以前の情報を現在のタスクに結合できるということです。例えば、以前のビデオフレームの利用が現在のフレームの理解に情報を与えるというようにです。もし RNN がこれを行なうのであれば非常に有用ですが、本当にできるのでしょうか?それは場合によります。

時として現在のタスクを遂行するために最近の情報を見ることだけが必要となります。以前の単語をベースに次の単語を予測を試みる言語モデルを考えましょう。“the clouds are in the sky.” における最後の単語の予測を試みる場合、それ以上のコンテキスト情報は必要ありません – 次の単語が sky になることは明白です。

しかし更なるコンテキストが必要な場合もあります。テキスト “I grew up in France… I speak fluent French.” の最後の単語を予測する場合、 最近の情報が次の単語は言語名と提案します、しかしどの言語か狭めようとするならば、更に戻ったところから、フランス語のコンテキストが必要となります。関係情報とそれが必要なポイントのギャップが非常に大きくなる可能性があります。

残念ながらギャップが大きくなれば、RNN は情報を結合して学習することができなくなります。理論的には RNN はそのような “long-term 依存” を処理できますが、実践では RNN はそれらを学習できるようには見えません。

LSTM はこの問題がありません。

 

LSTM ネットワーク

Long Short Term Memory ネットワーク – 通常は LSTM と呼称 – は RNN の特別な種類で long-term 依存を学習することができます。LSTM は Hochreiter & Schmidhuber (1997) で導入されました。

LSTM は long-term 依存問題を回避するためにデザインされ、情報を長時間記憶しておくことがデフォルトの挙動です。

全ての RNN は NN の反復モジュールのチェイン形式を持ちます。標準的な RNN では、この反復モジュールは tanh 単層のような非常に単純な構造を持ちます。LSTM はまたチェイン状の構造を持ちますが、反復モジュールは異なる構造を持ちます。単一のニューラルネットワーク層を持つ代わりに、4つの NN 層があり、特殊な方法で相互作用します。

 

LSTM の背後の核心の考え

LSTM への鍵はセル状態 (cell state) で、セル状態はベルトコンベアのようなものです。幾つかの小さな線形作用だけを伴い鎖全体をストレートに走り抜けます。情報は変更されないままそれに沿って流れることは簡単です。

LSTM は情報を削除したりセル状態に追加したりする能力を持ち、ゲート と呼ばれる構造により注意深く統制されます。ゲートは選択的に情報を通り抜けさせる手段です。これらはシグモイド NN 層と単点の乗算演算から成ります。シグモイド層は 0 と1の間の数を出力し各コンポーネントのどれだけを通り抜けさせるかを記述します。0 の値は「何も通さない」を、1 の値は「全てを通す」を意味しています。

LSTM はセル状態を保護して制御するためにこれらゲートを3つ持ちます、

 

Step-by-Step LSTM ウォークスルー

LSTM の最初のステップはセル状態からどの情報を捨てるかを決めることです。この決定は「忘却ゲート層」と呼ばれるシグモイド層により行なわれます。それは \(h_{t-1}\) と \(x_t\) を見て、セル状態 \(C_{t-1}\) の各数値のために 0 と 1 の間を数値を出力します。1 は「完全に保持する」を表す一方で、0 は「完全にこれを除去する」を表します。

全ての前の単語をもとに次の単語の予測を試みる言語モデルの例に戻りましょう。そのような問題では、セル状態が現在の主語の性別を含むことにより正しい代名詞が使用できることになります。新しい主語を見る時には、古い主語の性別は忘れたいです。

次のステップはどのような新しい情報をセル状態にストアするかを決定します。これは2つの部分を持ちます。まず、「入力ゲート層」と呼ばれるシグモイド層がどの値を更新するか決めます。次に、状態に追加される可能性のある、新しい候補値のベクタ \(\tilde{C}_t\) を tanh 層が作成します。次のステップは、これら2つを結合して状態への更新を作成します。

言語モデルの例では、忘れようとしている古いものを置き換えるために、新しい主語の性別をセル状態に追加することを望んでいます。

そして今、古いセル状態 \(C_{t-1}\) を新しいセル状態 \(C_t\) に更新する時です。前のステップが既に何をするか決めているで、実際にそれを行なうだけです。

古い状態に \(f_t\) を乗算して、前に忘れると決めたことを忘れます。それから \(i_t*\tilde{C}_t\) を加算します。これは各状態値を更新すると決めた数によりスケールされた、新しい候補値です。

言語モデルの場合には、ここでは前のステップで決定したように、実際に古い主語の性別についての情報をドロップして新しい情報を追加します。

最後に、何を出力するかを決める必要があります。この出力はセル状態の基づきますが、フィルターされたバージョンになります。まず、シグモイド層を実行します、これはセル状態のどのパートを出力するかを決めます。それから、セル状態に(-1 と 1 の間に値を収めるために) \(\tanh\) を通してからシグモイド・ゲートの出力を乗算します、結果として決めたパートだけを出力します。

言語モデルの例で言えば、単に主語を見てからは、(次に来るのが動詞である場合には)動詞に関連した情報を出力したいかもしれません。例えば、主語が単数か複数かを出力するかもしれません、その結果、(次に来るとして)動詞がどのような形に活用すべきかを知ります。

 

Long Short Term Memory の変形 (Variants)

ここまで説明してきたものは通常の LSTM です。しかし全ての LSTM が上と同じというわけではありません。事実、LSTM を含む殆ど全ての論文は少し違ったバージョンを使用しています。違いは大きなものではありませんが、幾つかについては言及する価値があります。

Gers & Schmidhuber (2000) で紹介された、一つの有名な LSTM の変形は 「のぞき穴接続 (peephole connections)」を追加します。これはゲート層にセル状態を見させることを意味しています。

のぞき穴を全てのゲートに追加する場合もありますが、多くの論文では幾つかにのぞき穴を与え、他には与えません。

他のバリエーションはカップリングされた忘却と入力ゲートを使用します。何を忘れて新しい情報を何に追加すべきかを別々に決定する代わりに、それらの決定を一緒に行ないます。何かをその場所に入力する時にだけ忘れます。古い何かを忘れる時にだけ新しい値を状態に入力します。

少しばかりよりドラマチックな LSTM バリエーションは、Cho, et al. (2014) で紹介された GRU – Gated Recurrent Unit です。これは忘却と入力ゲートを単一の「更新ゲート (update gate)」に結合します。それはまたセル状態と隠れ状態をマージして、幾つかの他の変更を行ないます。結果としてのモデルは標準的な LSTM モデルよりも単純で、段々と一般的なものになってきています。

これらは最も有名な LSTM バリエーションの2、3に過ぎません。他にも Yao, et al. (2015) による Depth Gated RNNs のようなものがたくさんあります。また Koutnik, et al. (2014) による Clockwork RNN のような、long-term 依存にタックルするための完全に異なる幾つかのアプローチもあります。

これらのバリエーションのどれがベストでしょうか?違いは重要でしょうか?Greff, et al. (2015) は人気のあるバリエーションの良い比較を行ない、すべて大体同じあることを見出しています。 Jozefowicz, et al. (2015) は 10,000 以上の RNN アーキテクチャをテストし、あるタスクでは幾つかは LSTM よりも良く動作したとしています。

 

結論

LSTM は RNN で達成できることの中で大きなステップでした。他の大きなステップはあるのか? と疑問を抱くのは自然なことです。研究者の共通の意見は : 「はい!次のステップがありそれは『attention』です!」アイデアは、RNN の全てのステップに、情報のある大きなコレクションから、情報を見るために pick させることです。例えば、画像を説明するキャプションを作成するために RNN を使用している場合、それは出力する全ての単語のために、画像の一部を見るために pick するかもしれません。実際に、Xu, et al. (2015) は正確にこれを行なっています – attention を探検することを望むならば楽しい開始点かもしれません。attention を使用した本当にエキサイティングな結果が幾つかあり、角を曲がればより沢山ありそうです…

attention だけが RNN 研究における唯一のエキサイティングな話題ではありません。
例えば、Grid LSTMs by Kalchbrenner, et al. (2015) による Grid LSTM は特に有望に思われます。生成モデルにおいて RNN を使用したワーク – 例えば Gregor, et al. (2015), Chung, et al. (2015), あるいは Bayer & Osendorfer (2015) – はまた非常に興味深く見えます。

ここ2、3年は RNN にとってエキサイティングな時間でしたが、これからの2、3年もそうあるでしょう。

 

以上

TensorFlow : RNN : Sequence-to-Sequence モデル(コード解説)

TensorFlow : コード解説 : RNN : Sequence-to-Sequence モデル

* TensorFlow : Tutorials : Sequence-to-Sequence モデル (翻訳/解説) に、数式排除/コード重視の方針で詳細な解説を加筆したものです。

 
本文

既に RNN & LSTM 解説 で議論したように RNN は言語のモデル化を学習できます。これは興味深い質問を引き起こします: ある入力に対して生成された単語を条件として、意味のあるレスポンスを生成できるでしょうか? 例えば、ニューラルネットワークを英語からフランス語に翻訳するために訓練することができるでしょうか? 答えは yes であることが判明しています。

このチュートリアルはそのようなシステムをどのように構築して訓練するかを示します。pip パッケージを通してインストール済みで、tensorflow git レポジトリをクローンし、そして git ツリーのルートにいることを仮定しています。

そして翻訳プログラムの実行で開始することができます:

cd tensorflow/models/rnn/translate
python translate.py --data_dir [your_data_directory]

これは WMT’15 Web サイト から英語フランス語翻訳データをダウンロードして訓練のための準備をし、そして訓練します。約 20 GB のディスク容量を取り、そしてダウンロードと準備(詳細は後で)にしばらくの時間がかかりますので、貴方はこのチュートリアルを読みながら始めて実行したままにするのが良いでしょう。

このチュートリアルは models/rnn からの次のファイルを参照します。

ファイル

What’s in it? (内容)
seq2seq.py

sequence-to-sequence モデルを構築するライブラリ。
translate/seq2seq_model.py

ニューラル翻訳 sequence-to-sequence モデル。
translate/data_utils.py

翻訳データを準備するためのヘルパー関数。
translate/translate.py

翻訳モデルを訓練して実行するバイナリ。
 

Sequence-to-Sequence 基本

基本的な sequence-to-sequence モデルは、Cho et al., 2014 (pdf) で紹介されているように、2つの RNN から成ります: 入力を処理するエンコーダと出力を生成するデコーダです。この基本的なアーキテクチャは下のようなものです :

上の図の各ボックスは RNN のセルを表しています、より一般的には GRU セルまたは LSTM セルです (それらの説明については RNN チュートリアル を参照)。エンコーダとデコーダは重みを共有することができますがあるいは、より一般的に、異なるパラメータセットを使用することもできます。多層セルは sequence-to-sequence モデルでも成功的に使用されています。例えば翻訳のために Sutskever et al., 2014 (pdf)。

上に描かれた基本モデルでは、デコーダに渡される唯一のものとして、全ての入力は固定サイズの状態ベクタにエンコードされなければなりません、デコーダにより直接的な入力へのアクセスを可能にするため、attention メカニズムが Bahdanau et al., 2014 (pdf) で紹介されました。私たちは attention メカニズムの詳細には踏み込みませんが(論文参照)、それはデコーダに全てのデコーディング・ステップで入力をピークすることを許可するとだけ言っておけば十分でしょう。LSTM セルと attention メカニズムによる多層 sequence-to-sequence ネットワークはこのように見えます。

 

TensorFlow seq2seq ライブラリ

上で見たように、多くの異なる sequence-to-sequence があります。これらのモデルの各々は異なる RNN セルを使用することができますが、これら全てはエンコーダ入力とデコーダ入力を受け取り、これは TensorFlow seq2seq ライブラリ (models/rnn/seq2seq.py) の I/F への興味を起こさせます。
(訳注: 実装の実態は tensorflow/python/ops/seq2seq.py に移されています。)

基本的な RNN encoder-decoder sequence-to-sequence モデルは次のように動作します。

outputs, states = basic_rnn_seq2seq(encoder_inputs, decoder_inputs, cell)

この関数呼び出しにおいて、encoder_inputs はエンコーダへの入力を表すテンソルのリストです。上の最初の図の文字 A, B, C に相当します。

同様に、decoder_inputs はデコーダへの入力を表すテンソルで、最初の図の GO, W, X, Y, Z に相当します。

cell 引数は models.rnn.rnn_cell.RNNCell クラスのインスタンスで、どのセルがモデル内部で使用されるかを決定します。GRUCell または LSTMCell のような既存のセルを使用できますし、あるいは貴方自身のものを書くこともできます。

更に、rnn_cell は多層セルを構築し、セル入力または出力にドロップアウトを追加し、あるいは他の変換を行なうためのラッパーを提供します。

basic_rnn_seq2seq の呼び出しの戻り値は2つです: outputs と states です。両方とも decoder_inputs と同じ長さのテンソルのリストです。もちろん、outputs は各時間ステップのデコーダの出力に相当します。上の最初の図ではそれは W, X, Y, Z, EOS でした。返された states は全ての時間ステップにおけるデコーダの内部状態を表します。

sequence-to-sequence モデルの多くのアプリケーションでは、時刻 t におけるデコーダの出力はフィードバックされ、時刻 t+1 におけるデコーダの入力になります。テスト時、シークエンスをデコードする時、このようにシークエンスが構築されます。訓練時には、他方、デコーダが前にミスをしていた場合でも一般に全ての時間ステップで正しい入力をデコーダに提供します。seq2seq.py の関数は feed_previous 引数を使って両方のモードをサポートします。例えば、埋め込み RNN モデルの次の用法を解析しましょう。

outputs, states = embedding_rnn_seq2seq(
    encoder_inputs, decoder_inputs, cell,
    num_encoder_symbols, num_decoder_symbols,
    output_projection=None, feed_previous=False)

embedding_rnn_seq2seq では、全ての入力は(both encoder_inputs と decoder_inputs 両方について)離散した値を表す整数テンソルです。これらは密な表現に埋め込まれます(埋め込みについての詳細は ベクタ表現チュートリアル を参照)、しかしこれらの埋め込みを構築するには、次のように現れる離散シンボルの最大値を指定する必要があります: エンコーダ側の num_encoder_symbols とデコーダ側の num_decoder_symbols です。

上記の呼び出しでは、feed_previous を False に設定しました。これはデコーダが decoder_inputs テンソルを提供されたものとして使用することを意味しています。もし feed_previous を True に設定した場合には、デコーダは decoder_inputs の最初の要素だけを使います。このリストからの他の全てのテンソルは無視され、代わりにエンコードの前の出力が使用されます。これは、私たちの翻訳モデルで翻訳をデコードするために使われますが、モデルを自身の誤りに対して堅固にするために、訓練時にもまた使うことができます。Bengio et al., 2015 (pdf) と同様です。

上で使われているもう一つの重要な引数は output_projection です。指定されない場合は、埋め込みモデルの出力は、各々の生成されたシンボルのロジットを表す、バッチ・サイズ x num_decoder_symbols の shape のテンソルになります。大規模出力語彙でモデルを訓練する時、すなわち num_decoder_symbols が大きい時、これらの大規模なテンソルをストアするのは実践的ではありません。代わりに、より小さな出力テンソルを返すのが良いです。これらは output_projection を使って後で大規模出力テンソルに射影されます。これは Jean et. al., 2014 (pdf) に記述されているように、私たちの seq2seq モデルを sampled softmax 損失とともに使うことを可能にします。

basic_rnn_seq2seq と embedding_rnn_seq2seq に加えて、seq2seq.py には2、3の更なる sequence-to-sequence モデルがありますので、見てください。それらは全て同様のインターフェイスを持ちますので、詳細は説明しません。下の翻訳モデルのためには embedding_attention_seq2seq を使います。

 

ニューラル翻訳モデル

sequence-to-sequence モデルの核が models/rnn/seq2seq.py の関数で構築される一方で、言及するに値する2、3のトリックがまだあります。それは models/rnn/translate/seq2seq_model.py における翻訳モデルで使われます。

Sampled softmax と出力射影 (projection)

上で既に述べたような理由で、大規模出力語彙を処理するために sampled softmax を使いたいです。それからデコードするために、出力射影を追跡する必要があります。sampled softmax 損失と出力射影の両者は seq2seq_model.py の次のコードにより構築されます。

  if num_samples > 0 and num_samples < self.target_vocab_size:
    w = tf.get_variable("proj_w", [size, self.target_vocab_size])
    w_t = tf.transpose(w)
    b = tf.get_variable("proj_b", [self.target_vocab_size])
    output_projection = (w, b)

    def sampled_loss(inputs, labels):
      labels = tf.reshape(labels, [-1, 1])
      return tf.nn.sampled_softmax_loss(w_t, b, inputs, labels, num_samples,
                                        self.target_vocab_size)

最初に、サンプルの数 (デフォルト 512) がターゲット語彙サイズよりも小さい場合にのみ sampled softmax を構築することに注意してください。512 より小さい語彙のためには標準 softmax 損失だけを使うのはより良い考えでしょう。

そして、見て取れるように、出力射影を構築します。それはペアで、重み行列とバイアスベクタから成ります。使用された場合、rnn セルは shape batch-size x size の shape のベクタを返します、batch-size x target_vocab_size ではありません。ロジットを取り出すためには、seq2seq_model.py の 124-126 行目で行なわれているように、重み行列で乗算してバイアスを加算する必要があります。

if output_projection is not None:
  self.outputs[b] = [tf.matmul(output, output_projection[0]) +
                     output_projection[1] for ...]

Bucketing とパディング

sampled softmax に加えて、私たちの翻訳モデルはまた bucketing を使います、これは異なる長さの文を効率的に処理するための方法です。まず問題をクリアにしましょう。英語からフランス語に翻訳する時、入力として異なる長さ L1 の英語の文があるでしょう、そして出力として異なる長さの L2 のフランス語の文があります。英語の文は encoder_inputs として渡され、フランス語の文は(GO シンボルで prefix される)decoder_inputs として現れますから、原理上は英語とフランス語の文の長さの全てのペア (L1, L2+1) のために seq2seq モデルを作成すべきです。これは多くの非常に類似したサブグラフから成る巨大なグラフという結果になるでしょう。一方で、特殊な PAD シンボルで全ての文をパディングすることもできます。そうすればパディングされた長さのため、一つの seq2seq モデルだけが必要になります。しかしより短い文では私たちのモデルは非効率的となり、必要のない、多くの PAD シンボルをエンコードしてデコードすることになります。長さの全てのペアのためのグラフを構築することと単一の長さにパディングすることの間の妥協として、幾つかのバケツを使いそして個々の文をその長さを超えてバケツの長さにパディングします。

buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]

このことは、もし入力が 3 トークンの英語文であり、そして対応する出力が 6 トークンのフランス語文である場合、これらは最初のバケツに入れられエンコーダ入力のために長さ 5 に、そしてデコーダ入力のためには長さ 10 にパディングされることを意味しています。もし8 トークンの英語文があり対応するフランス語文が 18 トークンである場合には、(10, 15) バケツにはフィットしませんので、(20, 25) バケツが使用されます。すなわち、英語文は 20 にパディングされ、フランス語文は 25 にパディングされます。

デコーダ入力を構築する時、特殊な GO シンボルを入力データに追加することを思い出してください。

これは seq2seq_model.py の get_batch() 関数で行なわれます、これはまた入力英文の反転もします。入力の反転はニューラル翻訳モデルに対して結果を改善することが Sutskever et al., 2014 (pdf) で示されています。すべてを理解するために、"I go." という文があり、入力として ["I", "go", "."] としてトークン分割されそして出力は "Je vais." という文で、["Je", "vais", "."] とトークン分割されていることを想像してください。それは (5, 10) バケツに入れられます、[PAD PAD "." "go" "I"] を表すエンコーダ入力と デコーダ入力 [GO "Je" "vais" "." EOS PAD PAD PAD PAD PAD] と共に。

 

Let's Run It

上述のモデルを訓練するためには、大規模な英仏コーパスが必要です。私たちは訓練のために WMT'15 Website からの10^9-英仏 コーパスを使用して、開発セットとして同じサイトから 2013 ニューステストを使用します。次のコマンドが実行された時、両方のデータセットが data_dir にダウンロードされて訓練が始まり、train_dir にチェックポイントが保存されます。

python translate.py
  --data_dir [your_data_directory] --train_dir [checkpoints_directory]
  --en_vocab_size=40000 --fr_vocab_size=40000

訓練コーパスを準備するためには約 18 GB のディスク空間を必要とし数時間かかります。それはアンパックされ、語彙ファイルは data_dir で作成され、そしてコーパスはトークン分割されて整数 id に変換されます。語彙サイズを決定するパラメータに注意してください。上の例では、40 K のもっとも一般的なもの以外の全ての単語は未知の単語を表す UNK トークンに変換されます。もし語彙サイズを変更するのであれば、バイナリはコーパスをトークン-id に再度マップし直します。

データが準備された後、訓練が始まります。translate(.py) のデフォルト・パラメータは非常に大きな値に設定されています。長時間かけて訓練されたラージ・モデルは良い結果を与えますが、時間がかかり過ぎたり貴方の GPU のメモリを消費し過ぎたりします。次の例のようにより小さなモデルを訓練するように要求することもできます。

python translate.py
  --data_dir [your_data_directory] --train_dir [checkpoints_directory]
  --size=256 --num_layers=2 --steps_per_checkpoint=50

上のコマンドは 2 層(デフォルトは 3 層)、各層は 256 ユニット(デフォルトは 1024)のモデルを訓練して、50 ステップ毎にチェックポイントを保存します(デフォルトは 200)。貴方はこれらのパラメータと遊ぶことでどの程度の大きさのモデルが貴方の GPU メモリに適合することができるか見つけられます。訓練の間、steps_per_checkpoint ステップ毎にバイナリは新しいステップから統計情報をプリントアウトします。デフォルト・パラメータ(サイズ 1024 の 3 層)では、最初のメッセージはこのようなものです。

global step 200 learning rate 0.5000 step-time 1.39 perplexity 1720.62
  eval: bucket 0 perplexity 184.97
  eval: bucket 1 perplexity 248.81
  eval: bucket 2 perplexity 341.64
  eval: bucket 3 perplexity 469.04
global step 400 learning rate 0.5000 step-time 1.38 perplexity 379.89
  eval: bucket 0 perplexity 151.32
  eval: bucket 1 perplexity 190.36
  eval: bucket 2 perplexity 227.46
  eval: bucket 3 perplexity 238.66

各ステップがちょうど 1.4 秒以下かかること、各バケツのための訓練セット上の perplexity と開発セット上の perplexity が見て取れます。約 30K ステップ後、短い文(バケツ 0 と 1)の perplexity が一桁に入ることを見ます。訓練コーパスは ~ 22M 文を含みますので、一つの epoch(訓練データを一巡すること) はバッチサイズ 64 で約 340K ステップかかります。この時点でモデルは --decode オプションで英語文をフランス語に翻訳するために使用することができます。

python translate.py --decode
  --data_dir [your_data_directory] --train_dir [checkpoints_directory]

Reading model parameters from /tmp/translate.ckpt-340000
>  Who is the president of the United States?
 Qui est le président des États-Unis ?
 

What Next?

上の例は貴方自身の英仏翻訳機をどのようにビルドするかを示しています、端から端まで。それを実行してモデルがどのように遂行するかを貴方自身で見てください。それは reasonable な品質を持つ一方で、デフォルト・パラメータはベストな翻訳モデルを提示してはいません。貴方が改良できる2、3のことがここにあります。

まず最初に、私たちは非常にプリミティブな tokenizer (トークン解析器) - data_utils の basic_tokenizer 関数を使用しています。より良い tokenizer は WMT'15 Web サイト で見つかります。その tokenizer、そしてより大規模な語彙を使用することは、翻訳を改良します。

また、翻訳モデルのデフォルト・パラメータはチューニングされていません。学習率、減衰を変更してみる、あるいはモデルの重みを違う方法で初期化してみることができます。また seq2seq_model.py のデフォルト GradientDescentOptimizer をより進んだもの、AdagradOptimizer のようなものに変更することもできます。これらのことにトライしてそれらがどのように結果を改善するか見てみましょう。

最後に、上で提示されたモデルは翻訳だけではなく、任意の sequence-to-sequence タスクに使用できます。例えば解析木を生成するためにシークエンスをツリーに変換することを望む場合、Vinyals & Kaiser et al., 2014 (pdf) で示されてるように、上と同じモデルが最高水準の結果を与えることができます。従って貴方自身の翻訳器をビルドできるだけでなく、解析器、チャットボット、あるいは思いつく任意のプログラムをビルドすることもできます。 Experiment!

 

以上

TensorFlow : RNN – リカレント・ニューラルネットワーク & LSTM (コード解説)

TensorFlow : コード解説 : RNNリカレント・ニューラルネットワーク & LSTM

* TensorFlow : Tutorials : リカレント・ニューラルネットワーク に、数式排除/コード重視の方針で詳細な解説を加筆したものです。

 

LSTM ネットワーク

RNNリカレント・ニューラルネットワーク、特に LSTM への入門としては Understanding LSTM Networks を参照のこと。要点は LSTM ネットワークの理解 (翻訳/要約) に簡単にまとめておきました。

 

言語モデリング

このチュートリアルではリカレント・ニューラルネットワークをどのように訓練するかを示します。言語モデリングの挑戦的なタスクです。ゴールは文に確率を割り当てる確率モデルを最適化することです。前の単語の履歴を与えられたテキストで次の単語を予測することで達成します。

言語モデリングは、音声認識、機械翻訳あるいは画像キャプションのような多くの興味ある問題へのキーで将来有望です – こちら をご覧ください。

このチュートリアルでは、PTB データセット上で非常に良い結果を達成してる、Zaremba et al., 2014 (pdf) からの結果を再現します。

Penn Tree Bank (PTB) を使用します。これは訓練するに小さくて比較的速く、これらのモデルのクオリティを計測するための人気のあるベンチマークです。

 

チュートリアル・ファイル

このチュートリアルではコード: models/rnn/ptb から次のファイルを参照します :

ファイル

目的
ptb_word_lm.py

PTB データセット上で言語モデルを訓練するコード。
reader.py

データセットを読むコード。
 

データのダウンロードと準備

このチュートリアルに必要なデータは Tomas Mikolov の web ページからの
PTB データセットの data/ ディレクトリにあります :
    http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz

データセットは既に前処理されていて、文の最後のマーカーと珍しい単語のための特殊なシンボル (<unk>) を含む、全体で 10000 の異なる単語を含みます。

ニューラルネットワークが処理しやすいようにこれら全部を reader.py で一意の整数識別子に変換します。

ptb_word_lm.py#main は、最初に reader.py#ptb_raw_data を呼び出してデータセットを取得します。

def main(unused_args):
  if not FLAGS.data_path:
    raise ValueError("Must set --data_path to PTB data directory")
  raw_data = reader.ptb_raw_data(FLAGS.data_path)
  train_data, valid_data, test_data, _ = raw_data
def ptb_raw_data(data_path=None):
  """
  PTB 生データをデータディレクトリ "data_path" からロードする。
  PTB テキストファイルを読み込み、文字列を整数値 id に変換、
  入力のミニ・バッチを遂行します。
  The PTB dataset comes from Tomas Mikolov's webpage:
  http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz
  Args:
    data_path: string path to the directory where simple-examples.tgz has
      been extracted.
  Returns:
    tuple (train_data, valid_data, test_data, vocabulary)
    where each of the data objects can be passed to PTBIterator.
  """
  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)
  train_data = _file_to_word_ids(train_path, word_to_id)
  valid_data = _file_to_word_ids(valid_path, word_to_id)
  test_data = _file_to_word_ids(test_path, word_to_id)
  vocabulary = len(word_to_id)
  return train_data, valid_data, test_data, vocabulary
 

モデル

LSTM

モデルの核は LSTM セルから構成されます。このセルは、一度に一つの単語を処理し文の(可能性のある)継続性の確率を計算します。ネットワークのメモリ状態は 0 のベクタで初期化されて各単語を読んだ後で更新されます。また、計算上の理由で、サイズ batch_size のミニ・バッチでデータを処理します。

基本的な擬似コードは次のようなものです :

lstm = rnn_cell.BasicLSTMCell(lstm_size)
# LSTM メモリの初期状態。
state = tf.zeros([batch_size, lstm.state_size])

loss = 0.0
for current_batch_of_words in words_in_dataset:
    # 状態の値は単語の各バッチ処理の後で更新されます。
    output, state = lstm(current_batch_of_words, state)

    # LSTM 出力は次の単語予測をするために使用できます。
    logits = tf.matmul(output, softmax_w) + softmax_b
    probabilities = tf.nn.softmax(logits)
    loss += loss_function(probabilities, target_words)

PTBModel コンストラクタ

モデルの実装は PTBModel クラスで、コンストラクタで構築されます。

class PTBModel(object):

  def __init__(self, is_training, config):
    self.batch_size = batch_size = config.batch_size
    self.num_steps = num_steps = config.num_steps
    size = config.hidden_size
    vocab_size = config.vocab_size

    # プレースホルダー
    self._input_data = tf.placeholder(tf.int32, [batch_size, num_steps])
    self._targets = tf.placeholder(tf.int32, [batch_size, num_steps])

    #  1 で初期化された忘却ゲート・バイアスによれば幾分良い結果が得られますが、
    # モデルのハイパーパラメータは論文での報告とは異なるものである必要があります。
    lstm_cell = rnn_cell.BasicLSTMCell(size, forget_bias=0.0)
    if is_training and config.keep_prob < 1:
      lstm_cell = rnn_cell.DropoutWrapper(
          lstm_cell, output_keep_prob=config.keep_prob)
    cell = rnn_cell.MultiRNNCell([lstm_cell] * config.num_layers)

    self._initial_state = cell.zero_state(batch_size, tf.float32)

    with tf.device("/cpu:0"):
      embedding = tf.get_variable("embedding", [vocab_size, size])
      inputs = tf.nn.embedding_lookup(embedding, self._input_data)
    if is_training and config.keep_prob < 1:
      inputs = tf.nn.dropout(inputs, config.keep_prob)

    # tensorflow.models.rnn.rnn.py の rnn() の簡易版。
    # チュートリアル目的のみで unrolled LSTM を構築します。
    # 一般的には、rnn.py の rnn() か state_saving_rnn() を使用します。
    #
    # The alternative version of the code below is:
    #
    # from tensorflow.models.rnn import rnn
    # inputs = [tf.squeeze(input_, [1])
    #           for input_ in tf.split(1, num_steps, inputs)]
    # outputs, states = rnn.rnn(cell, inputs, initial_state=self._initial_state)
    outputs = []
    states = []
    state = self._initial_state
    with tf.variable_scope("RNN"):
      for time_step in range(num_steps):
        if time_step > 0: tf.get_variable_scope().reuse_variables()
        (cell_output, state) = cell(inputs[:, time_step, :], state)
        outputs.append(cell_output)
        states.append(state)

    output = tf.reshape(tf.concat(1, outputs), [-1, size])
    softmax_w = tf.get_variable("softmax_w", [size, vocab_size])
    softmax_b = tf.get_variable("softmax_b", [vocab_size])
    logits = tf.matmul(output, softmax_w) + softmax_b
    loss = seq2seq.sequence_loss_by_example([logits],
                                            [tf.reshape(self._targets, [-1])],
                                            [tf.ones([batch_size * num_steps])],
                                            vocab_size)
    self._cost = cost = tf.reduce_sum(loss) / batch_size
    self._final_state = states[-1]

    if not is_training:
      return

    self._lr = tf.Variable(0.0, trainable=False)
    tvars = tf.trainable_variables()
    grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars),
                                      config.max_grad_norm)
    optimizer = tf.train.GradientDescentOptimizer(self.lr)
    self._train_op = optimizer.apply_gradients(zip(grads, tvars))

Truncated バックプロパゲーション

学習プロセスを扱いやすくするために、バックプロパゲーションのための勾配を展開されたステップの固定数 (num_steps) で打ち切る (truncate) ことは一般的に実践されます。これは、一度に num_steps の長さの入力を供給して各反復後に後方へパスを行なうことにより実装は容易です。

truncated バックプロパゲーションのためのグラフ作成のためのコードの簡易版です :

# 与えられた反復における入力のためのプレースホルダー。
words = tf.placeholder(tf.int32, [batch_size, num_steps])

lstm = rnn_cell.BasicLSTMCell(lstm_size)
# LSTM メモリの初期状態。
initial_state = state = tf.zeros([batch_size, lstm.state_size])

for i in range(len(num_steps)):
    # 状態値は単語のバッチ毎処理後に更新されます。
    output, state = lstm(words[:, i], state)

    # 残りのコード。
    # ...

final_state = state

そしてこれがデータセット全体に渡る反復の実装です。

# 単語の各バッチ後の LSTM の状態を保持する numpy 配列。
numpy_state = initial_state.eval()
total_loss = 0.0
for current_batch_of_words in words_in_dataset:
    numpy_state, current_loss = session.run([final_state, loss],
        # 以前の反復からの LSTM 状態の初期化。
        feed_dict={initial_state: numpy_state, words: current_batch_of_words})
    total_loss += current_loss

入力

単語 ID は LSTM に供給される前に密な表現(単語のベクタ表現チュートリアル 参照)に埋め込まれます。これはモデルに、特定の単語についての知識を効率的に表現することを可能にします。コードを書くのもまた簡単です:

# embedding_matrix は形状 [vocabulary_size, embedding size] のテンソルです。
word_embeddings = tf.nn.embedding_lookup(embedding_matrix, word_ids)

埋め込み行列はランダムに初期化されてモデルはデータを検索するだけで単語の意味を識別できるようにするために学習します。

損失関数

ターゲット単語の確率の対数の負の平均を最小化したいです :

\[ \text{loss} = -\frac{1}{N}\sum_{i=1}^{N} \ln p_{\text{target}_i} \]

関数 sequence_loss_by_example が利用可能です。

    output = tf.reshape(tf.concat(1, outputs), [-1, size])
    softmax_w = tf.get_variable("softmax_w", [size, vocab_size])
    softmax_b = tf.get_variable("softmax_b", [vocab_size])
    logits = tf.matmul(output, softmax_w) + softmax_b
    loss = seq2seq.sequence_loss_by_example([logits],
                                            [tf.reshape(self._targets, [-1])],
                                            [tf.ones([batch_size * num_steps])],
                                            vocab_size)
    self._cost = cost = tf.reduce_sum(loss) / batch_size

論文で報告されている典型的な尺度は平均単語毎 (per-word) perplexity (単に perplexity とも)で、これは以下に等しいです

\[e^{-\frac{1}{N}\sum_{i=1}^{N} \ln p_{\text{target}_i}} = e^{\text{loss}} \]

そして私たちは訓練プロセスを通じてこの値をモニタします。

複数 LSTM のスタック

モデルにより表現力を与えるために、LSTM の複数層を追加できます。
第1層の出力は第2層の入力、等々になります。

MultiRNNCell と呼ばれるクラスを持ち、これは実装をシームレスにします :

lstm = rnn_cell.BasicLSTMCell(lstm_size)
stacked_lstm = rnn_cell.MultiRNNCell([lstm] * number_of_layers)

initial_state = state = stacked_lstm.zero_state(batch_size, tf.float32)
for i in range(len(num_steps)):
    # 単語の各バッチの処理後、状態値は更新されます。
    output, state = stacked_lstm(words[:, i], state)

    # 残りのコード。
    # ...

final_state = state

実際のコード :

    lstm_cell = rnn_cell.BasicLSTMCell(size, forget_bias=0.0)
    if is_training and config.keep_prob < 1:
      lstm_cell = rnn_cell.DropoutWrapper(
          lstm_cell, output_keep_prob=config.keep_prob)
    cell = rnn_cell.MultiRNNCell([lstm_cell] * config.num_layers)
 

コードを実行する

pip パッケージのインストールを行ない、tensorflow git レポジトリをクローンし、そして git ツリーのルートにいることを仮定しています。(もしソースからビルドしているならば、bazel でターゲット tensorflow/models/rnn/ptb:ptb_word_lm をビルドします。)

次に: cd tensorflow/models/rnn/ptb python ptb_word_lm --data_path=/tmp/simple-examples/data/ --model small

チュートリアル・コードには3つのサポートされたモデル構成があります: "small", "medium" そして "large" です。これらの違いは LSTM のサイズと訓練に使われるハイパーパラメータのセットにあります。

より大きなモデルは、より良い結果を得られるでしょう。small モデルはテストセット上で 120 以下の perplexity に達することができ、large モデルは 80 以下です、訓練に数時間かかるかもしれませんが。

 

What Next?

モデルを良くするための私たちが言及していない幾つかのトリックあります、これは次を含みます:

  • 学習率を減少するスケジュール
  • LSTM 層間のドロップアウト

コードを研究してモデルを更に改善するように修正しましょう。

 

以上

AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com