TensolFlowのチュートリアルを全部やってまとめてみました

本記事では、機械学習ライブラリとして有名なTensorFlowの、公式ドキュメントのチュートリアルをまとめて紹介します。TensorFlowのチュートリアルは、それなりに量があるので、必要なものを参照しやすくするために本記事を書きました。

本題に入る前に、今回なぜライブラリとしてTensorFlowを選んだかを簡単にお話ししたいと思います。

f:id:hiyoko9t:20171008163500p:plain
Googleが開発したTensorFlow

と言っても筆者は、機械学習ライブラリを触ったのは、この記事を投稿する一週間ほど前からになるので、他の有名なライブラリ(ChainerやTheano、kerasなど)との違いはほとんどわかりません。

ただ、TensorFlowのマニュアルを流し見していた時に、計算グラフという概念とその可視化が魅力的に映ったので、TensorFlowを選びました。また、世界的にTensorFlowがトップクラスに使用されていることも、導入としてはいいかなと感じ選びました。(ドキュメントの多さなど)

実際、チュートリアルをすべて追ってみて、難しいと感じた部分もありますが、モデルの設計など非常に便利だと感じたので、これから始める人には十分オススメです。

それでは、以下にそれぞれのチュートリアルのリンクと概要、キーワードを示します。本記事では、これらの一つ一つについて、ざっくりとどんなことをしているか、どんなものができあがるか、などを紹介していこうと思います。

チュートリアル 概要 キーワード
Getting Started With TensorFlow  |  TensorFlow tensorflowのインポートから線形モデルを用いた学習 テンソル、計算グラフ、tf.train、tf.estimator
MNIST For ML Beginners  |  TensorFlow MNISTのデータのロード、ソフトマックス回帰を用いた分類 MNIST、ソフトマックス回帰
Deep MNIST for Experts  |  TensorFlow MNISTに対して、ディープな畳み込み層を用いた分類 多層畳み込みネットワーク、畳み込み、プーリング、密結合層、ドロップアウト
TensorFlow Mechanics 101  |  TensorFlow 順伝播型ニューラルネットワークを用いた手書き文字分類器の訓練と評価 順伝播型ニューラルネットワーク
tf.estimator Quickstart  |  TensorFlow high-levelなAPIを用いた分類器の構築 tf.estimator、Iris、DNN
Building Input Functions with tf.estimator  |  TensorFlow 入力関数(input_fn)の作り方 tf.estimator、input_fn、numpy、pandas
TensorBoard: Visualizing Learning  |  TensorFlow TensorBoardを用いた学習の可視化 TensorBoard、学習の可視化、シリアライズ
TensorBoard: Graph Visualization  |  TensorFlow TensorBoardを用いた計算グラフの可視化 TensorBoard、グラフの可視化、tf.name_scope
チュートリアル 概要 キーワード
Using GPUs  |  TensorFlow GPUの使用 CPU、GPU
Image Recognition  |  TensorFlow Inception-v3を用いた画像認識 画像認識、ImageNet、Inception-v3
How to Retrain Inception's Final Layer for New Categories  |  TensorFlow 新しいカテゴリに対するInceptionの最終層の再訓練 Inception、再訓練、最終層
A Guide to TF Layers: Building a Convolutional Neural Network  |  TensorFlow layersモジュールを用いたCNNの構築 CNN、layers、畳み込み層、プーリング層、密結合層
Convolutional Neural Networks  |  TensorFlow CIFAR-10に対するCNNの構築 CNN、CIFAR-10、GPU
Vector Representations of Words  |  TensorFlow 単語のベクトル表現 ベクトル表現、word2vec、embedding、t-SNE
Recurrent Neural Networks  |  TensorFlow 再帰ニューラルネットワーク RNN、LSTM、Pen Tree Bank
Sequence-to-Sequence Models  |  TensorFlow Sequence-to-Sequence Modelsを用いた翻訳 Sequence-to-Sequence Models、ニューラル翻訳
Large-scale Linear Models with TensorFlow  |  TensorFlow tf.estimatorを用いた大規模な線形モデルの構築 大規模な線形モデル、FeatureColumn
TensorFlow Linear Model Tutorial  |  TensorFlow 人口調査データに対する線形モデルを用いた二値分類 線形モデル、二値分類、Census Income Dataset、正則化、ロジスティック回帰
TensorFlow Wide & Deep Learning Tutorial  |  TensorFlow wideモデルとdeepモデルの結合 wideモデル、deepモデル、wide&deepモデル
Improving Linear Models Using Explicit Kernel Methods  |  TensorFlow カーネル法を用いた線形モデルの改善 カーネル法
Mandelbrot Set  |  TensorFlow マンデルブロ集合の可視化 マンデルブロ集合、フラクタル
Partial Differential Equations  |  TensorFlow 偏微分方程式を用いたシミュレーション 偏微分方程式ラプラシアンカーネル、シミュレーション

なお、TensorFlowのチュートリアルは、あくまで実装を中心とした解説になっているので、機械学習の各手法やオプティマイザなどの数学的な背景は前提知識として持っていないと理解しづらい部分もあるかと思います。

以前の記事で、CNNの考え方や最適化アルゴリズムの考え方を紹介しているので、興味のある方はそちらもどうぞ。

hiyoko9t.hatenadiary.jp

hiyoko9t.hatenadiary.jp

Getting Started With TensorFlow

以降のチュートリアルすべての基本となる章なので、前回の記事で、TensorFlowのインストールからライブラリ内で用いられる基本的な概念をすべて翻訳して、紹介しています。

hiyoko9t.hatenadiary.jp

MNIST For ML Beginners

このチュートリアルでは、手書き文字データとして有名なMNISTに対して、ソフトマックス回帰を適用し、適切なラベルを与えることを目的としています。

MNISTは、$28\times 28$のグレイスケール画像で次のようなものが挙げられます。また、各データにはそれが数字の何をあらわしているか、というラベルが与えられています。("5"の画像には"5"というラベルが与えられます)

f:id:hiyoko9t:20171008014018p:plain
MNIST

このMNISTを入力として、以下のネットワークであらわされるソフトマックス回帰を行います。

f:id:hiyoko9t:20171008014113p:plain
ソフトマックス回帰

結果として、92%程度の正解率をもつモデルを構築します。

Deep MNIST for Experts

このチュートリアルでは、前半は「MNIST For ML Beginners」で紹介されている内容とほぼ同様の内容を紹介しており、後半では多層畳み込みネットワークの構築を行います。

ここでは、畳み込み層とプーリング層を重ねたものを二層用意し、その後、密結合層を通して、最終的な出力の層へと遷移します。

f:id:hiyoko9t:20171008030155p:plain
多層畳み込みネットワーク

畳み込み層、プーリング層を経て、入力画像のテンソルがどのように変化していくかに注目すると理解しやすいです。また、工夫としてオプティマイザにAdamを利用していたり、ドロップアウトを行うことで過学習を防いでいます。

結果として、99.2%程度の正解率をもつモデルを構築します。

TensorFlow Mechanics 101

このチュートリアルでは、MNISTに対して順伝播型ニューラルネットワークの訓練と評価方法を紹介します。

f:id:hiyoko9t:20171008110859p:plain
部分グラフ

コード自体は、「Deep MNIST for Experts」と共通する部分も多く、それを機能ごとに関数化し、解説しています。
また、あくまで訓練と評価が中心のチュートリアルとなっていますが、可視化やチェックポイント、実行時間の計測など、実践的な機能の実装方法も確認できます。

tf.estimator Quickstart

このチュートリアルでは、モデルの設定や訓練、評価を簡単にするhigh-levelなAPIであるtf.estimatorの使い方を紹介します。ここでは、Irisデータセットを用いて、花の品種を分類するニューラルネットワークを構築します。

Irisデータセットは、次の表に示すように4つの特徴量と品種をあらわすラベルからなります。

がく片の長さ がく片の幅 花びらの長さ 花びらの幅 ラベル
5.1 3.5 1.4 0.2 0
... ... ... ... ...
5.9 3.0 5.1 1.8 2

f:id:hiyoko9t:20171008112331j:plain
Iris(左からsetosa、versicolor、virginica)

チュートリアルのコード内に、Irisデータを取得するために

raw = urllib.urlopen(IRIS_TRAINING_URL).read()

という一行があるのですが、これはpython2.x系の記述なので、python3.x系を使用される方は次のように修正してください。

raw = urllib.request.urlopen(IRIS_TRAINING_URL).read()

得られたIrisデータの特徴量から、品種を分類するニューラルネットワークを構築しますが、「Deep MNIST for Experts」では、層ごとに自分で重みやバイアスを作成していました。

しかし、tf.estimatorを用いれば、隠れ層を3つもつネットワークを次のように簡単に記述できます。

# Build 3 layer DNN with 10, 20, 10 units respectively.
classifier = tf.estimator.DNNClassifier(feature_columns=feature_columns,
                                        hidden_units=[10, 20, 10],
                                        n_classes=3,
                                        model_dir="/tmp/iris_model")

結果として、96%程度の正解率を持つ分類器が構築されます。

Building Input Functions with tf.estimator

このチュートリアルでは、tf.estimatorにおいて、前処理やモデルにデータを与えるための入力関数(input_fn)の作り方を紹介します。

用意した特徴量/ラベルデータがpythonの配列やpandasデータフレームで格納されている場合、次のようにinput_fnを記述することができます。

import numpy as np
# numpy input_fn.
my_input_fn = tf.estimator.inputs.numpy_input_fn(
    x={"x": np.array(x_data)},
    y=np.array(y_data),
    ...)
import pandas as pd
# pandas input_fn.
my_input_fn = tf.estimator.inputs.pandas_input_fn(
    x=pd.DataFrame({"x": x_data}),
    y=pd.Series(y_data),
    ...)

具体例として、Boston House Valueデータをpandasデータフレームで格納し、input_fn関数を通して、回帰問題として、訓練と評価、予測を行なっています。すべて、同一のinput_fn関数を通して実行できることに注意してコードを追ってみてください。

TensorBoard: Visualizing Learning

このチュートリアルでは、DNNのような複雑になりがちなモデルにおいて、理解、デバッグ、最適化を進めるための、データの可視化ツールであるTensorBoardを紹介します。TensorBoardでは、計算グラフや行列、画像データなどの可視化を行うことができます。

実際に、学習を可視化すると、以下のようになります。

f:id:hiyoko9t:20171008152349p:plain
TensorBoardを用いた学習の可視化

TensorBoardは、TensorFlowの実行時に生成することのできるサマリーデータを含むイベントファイルを読み込むことによって、そのデータを可視化します。例として、CNNの訓練を行うとき、学習率とロス関数の値を確認したいとき、そのノードにtf.summary.scalarを取り付けます。

公式のソースコードを実行した後に端末で、次のコマンドを実行してください。

tensorboard --logdir=/tmp/tensorflow/mnist

コマンドを実行した後に、localhost:6006へブラウザでアクセスすると、可視化されたものが見れます。

実際に「MNIST For ML Beginners」で実装したコードをロギングし、可視化したものが次のようになります。

f:id:hiyoko9t:20171008154255p:plain
MNISTに対する学習の可視化

また、チュートリアル内にはありませんが、画像を可視化したものもTensorBoard上で見ることができます。

f:id:hiyoko9t:20171008170139p:plain
MNISTにおける訓練中の画像

TensorBoard: Graph Visualization

このチュートリアルでは、複雑になりがちな計算グラフの理解、デバッグをTensorBoardを用いて可視化します。

学習の可視化と同様に、「MNIST For ML Beginners」で実装したコードをロギングし、計算グラフを可視化したものが次のようになります。

f:id:hiyoko9t:20171008165813p:plain
MNISTに対する学習の計算グラフの可視化

グラフの中で隠れ層、ソフトマックス、クロスエントロピーの計算などが行われていることがわかります。

ここで、計算グラフ上の各シンボルは次のような意味を持っています。

f:id:hiyoko9t:20171008170523p:plain
計算グラフ上における各シンボルの意味

これにより、複雑な計算グラフを視覚的に確認することができます。

Using GPUs

TensorFlowでは、CPUとGPU、二つのデバイスをサポートしています。TensorFlowでは、CPUとGPUで同時に実装が行われているとき、GPUが優先的に用いられます。

実際に、簡単な計算でGPUが使用されていることを確認してみます。

# Creates a graph.
a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a')
b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b')
c = tf.matmul(a, b)
# Creates a session with log_device_placement set to True.
sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
# Runs the op.
print(sess.run(c))

セッションを起動する時に、log_device_placement=Trueを指定することで、使用されたデバイスを確認することができます。

Device mapping:
/job:localhost/replica:0/task:0/gpu:0 -> device: 0, name: Tesla K40c, pci bus
id: 0000:05:00.0
b: /job:localhost/replica:0/task:0/gpu:0
a: /job:localhost/replica:0/task:0/gpu:0
MatMul: /job:localhost/replica:0/task:0/gpu:0
[[ 22.  28.]
 [ 49.  64.]]

このようにGPUが使用されていることがわかります。

さらに、このチュートリアルでは、GPUの使用するメモリの制御、複数のGPUの使用方法などが紹介されています。(GPU環境がないので、この実装は試せていません。GPU環境がある方は、ぜひ試してみてください)

Image Recognition

画像認識は、近年盛んに研究が行われている分野です。画像認識の基本的な考え方は以前の記事で紹介しているので、よければそちらをどうぞ。

f:id:hiyoko9t:20171009094228p:plain
画像認識の例

このチュートリアルでは、Googleの開発したInception-v3の使い方を中心に解説しています。Inception-v3は、画像分類問題のベンチマークであるImageNetに対して訓練されています。これは、「ゼブラ」や「ダルメシアン」といったように画像からそのクラスを1000個に分類する問題となっています。

このようなモデルを評価するための指標として、予測のトップ5に真に求めたいラベルが含まれているかどうか(トップ5エラー率)、という指標が存在します。有名なアルゴリズムとそのトップ5エラー率を次の表に示します。

アルゴリズム トップ5エラー率
AlexNet 15.3%(2012年)
Inception(GoogleNet) 6.67%
BN-Inception-v2 4.9%
Inception-v3 3.46%

表からInception-v3のトップ5エラー率が非常に小さいことがわかります。

Inception-v3は、Python API/C++ APIとして提供されています。ここでは、Python APIに関してのみ紹介します。

Python APIの利用方法は、非常にシンプルです。

  1. GitHub - tensorflow/models: Models built with TensorFlowをgit cloneする
  2. 以下のコマンドを実行する
cd models/tutorials/image/imagenet
python classify_image.py

これにより、パンダの画像が与えられたとき、次のような出力結果が得られます。

f:id:hiyoko9t:20171009101558j:plain
パンダ

giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca (score = 0.88493)
indri, indris, Indri indri, Indri brevicaudatus (score = 0.00878)
lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens (score = 0.00317)
custard apple (score = 0.00149)
earthstar (score = 0.00127)

出力結果から、パンダのスコアが高くなっていることがわかります。

How to Retrain Inception's Final Layer for New Categories

このチュートリアルでは、新しいカテゴリに対するInceptionの最終層の再訓練方法を紹介します。

Inceptionは、ImageNetを用いて訓練されているため、ImageNetに存在しないカテゴリを予測することはできません。そこで、新しいカテゴリを結果として得たいときには、ネットワークを更新する必要があります。しかし、現代の物体認識モデルは何百万、何千万というパラメータを持つので、そのすべてを更新するのは非常に大きな計算量がかかります。そこで、途中の層はそのままで、出力に直接影響のある最終層だけを更新する手法が提案されています。(詳細はこちら) これにより、すべてのパラメータを更新するときと比べて精度は落ちるものの、GPUの無い環境でも30分程度で再訓練することが可能となります。

この手法がうまく動くことは、ImageNetに含まれる1000クラスから訓練した、入力層に近い特徴を取り出す層は、未知のカテゴリに対してもその特徴を取り出すことに一定の効果が見られることを示唆しています。

このチュートリアルでは、新しいカテゴリとして、花の画像を使用します。

f:id:hiyoko9t:20171009111632j:plain
新しいカテゴリ:花

また、自分のデータセットを用いて学習するときの注意点も紹介されています。 例としていくつか下に挙げます。

  • 認識したいカテゴリの画像を少なくとも100枚は用意する
  • 認識したいカテゴリに応じた画像を用意する(アウトドアな物体を認識したいのに、インドアな物体を訓練してはいけない)
  • できるだけ色々な状況における物体画像を用意する(背景が青の部屋で撮った物体と背景が緑の部屋で撮った物体から正しく認識したいとき、背景を除くため)
  • カテゴリの粒度を意識する (「乗り物」という粒度。「車」、「モーターバイク」、「トラック」という粒度。)

また、Inceptionに精度は劣るものの高速で動くアーキテクチャも紹介されています。アプリなどで、物体認識を行うときはこちらを使用すればいいかもしれません。

A Guide to TF Layers: Building a Convolutional Neural Network

このチュートリアルでは、MNISTデータセットからラベル分類を行うため、layersモジュールを用いた畳み込みニューラルネットワーク(Convolutional Neural Network; CNN)の構築方法を説明します。

f:id:hiyoko9t:20171009122402p:plain
MNIST

CNNは主に以下の3つの層から構成されます。

  1. 畳み込み層
    特徴マップを作るために、画像の小領域に対してフィルターを畳み込んで生成します。

  2. プーリング層
    畳み込み層の次元をダウンサンプリングします。例えば、$2\times 2$のフィルターを$28\times 28$の画像に適用することで$14\times 14$の特徴に減らすことができます。

  3. 密結合(全結合)層
    畳み込み層、プーリング層から得られた特徴から分類を行います。

このチュートリアルでは、以下の構造のCNNを実装します。

  1. 畳み込み層 #1:$32$特徴マップ、$5\times 5$フィルター、ReLu
  2. プーリング層 #1:最大値プーリング、$2\times 2$フィルター(ストライド$2$)
  3. 畳み込み層 #2:$64$特徴マップ、$5\times 5$フィルター、ReLu
  4. プーリング層 #2:最大値プーリング、$2\times 2$フィルター(ストライド$2$)
  5. 密結合層 #1:1024ユニット(ドロップアウト率$0.4$)
  6. 密結合層 #2:10ユニット(クラスラベル0から9をあらわす)

これを実装したものが次のようになります。

def cnn_model_fn(features, labels, mode):
  """Model function for CNN."""
  # Input Layer
  input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])

  # Convolutional Layer #1
  conv1 = tf.layers.conv2d(
      inputs=input_layer,
      filters=32,
      kernel_size=[5, 5],
      padding="same",
      activation=tf.nn.relu)

  # Pooling Layer #1
  pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)

  # Convolutional Layer #2 and Pooling Layer #2
  conv2 = tf.layers.conv2d(
      inputs=pool1,
      filters=64,
      kernel_size=[5, 5],
      padding="same",
      activation=tf.nn.relu)
  pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)

  # Dense Layer
  pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
  dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
  dropout = tf.layers.dropout(
      inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)

  # Logits Layer
  logits = tf.layers.dense(inputs=dropout, units=10)

  predictions = {
      # Generate predictions (for PREDICT and EVAL mode)
      "classes": tf.argmax(input=logits, axis=1),
      # Add `softmax_tensor` to the graph. It is used for PREDICT and by the
      # `logging_hook`.
      "probabilities": tf.nn.softmax(logits, name="softmax_tensor")
  }

  if mode == tf.estimator.ModeKeys.PREDICT:
    return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)

  # Calculate Loss (for both TRAIN and EVAL modes)
  onehot_labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=10)
  loss = tf.losses.softmax_cross_entropy(
      onehot_labels=onehot_labels, logits=logits)

  # Configure the Training Op (for TRAIN mode)
  if mode == tf.estimator.ModeKeys.TRAIN:
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
    train_op = optimizer.minimize(
        loss=loss,
        global_step=tf.train.get_global_step())
    return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)

  # Add evaluation metrics (for EVAL mode)
  eval_metric_ops = {
      "accuracy": tf.metrics.accuracy(
          labels=labels, predictions=predictions["classes"])}
  return tf.estimator.EstimatorSpec(
      mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)

ここで、入力層が次の引数をとっていることに注意してください。

[batch_size, image_width, image_height, channels]

MNISTにこれをあてはめると、特徴から入力への変換は次のようになります。

input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])

ここで、batch_sizeが-1となっているのは、動的に値をとることを示しています。これにより、バッチザイズをハイパーパラメータとして扱うことができます。 例として、バッチサイズが100のとき、入力の要素数は$100\times 28\times 28\times 1 = 78400$となります。

畳み込み層、プーリング層がそれぞれこの引数を受け取っていることが確認できます。

conv1 = tf.layers.conv2d(
    inputs=input_layer,
    filters=32,
    kernel_size=[5, 5],
    padding="same",
    activation=tf.nn.relu)

pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)

全結合層では、上記の実装で得られた特徴量をflattenして入力として受け取ります。

pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])

dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)

さらに、訓練時のみドロップアウトを行い、最後にクラスラベルの数の層へ変換します。

dropout = tf.layers.dropout(
    inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)

logits = tf.layers.dense(inputs=dropout, units=10)

このように、直感的に畳み込み層、プーリング層、全結合層が実装できることがわかります。非常に便利ですね。

これにより97%程度の正解率を持つモデルを構築できます。

Convolutional Neural Networks

ここまで、画像分類のベンチマークとして、MNISTを用いてきました。MNIST同様、有名な画像分類問題のデータセットとしてCIFAR-10 and CIFAR-100 datasetsが挙げられます。これは、$32\times 32$のRGBの画像で、飛行機や鳥、猫といった10個のクラスの画像からなるデータセットとなります。

f:id:hiyoko9t:20171009141422p:plain
CIFAR-10 and CIFAR-100 datasetsより引用

ここでは、あらかじめ準備されているコード(models/tutorials/image/cifar10 at master · tensorflow/models · GitHub)について、その特徴を見ていきます。

  • モデルの入力
    CIFAR-10はもともと$32\times 32$の画像ですが、入力として与えるとき、ここでは$24\times 24$の画像に変換しています。訓練のときは、ランダムな位置から取得し、評価のときは、中心から取得します。また、ランダムな位置から取得するため、その影響をなくすため白色化を行います。さらに、データセットの数を増やすため、明るさやコントラストを変化させます。
    そうして、得られた画像が次のようになります。

f:id:hiyoko9t:20171009144142p:plain
変換されたCIFAR-10の例

  • 予測モデル
    ここで使用するモデルは次のようになります。

f:id:hiyoko9t:20171009144207p:plain
ネットワークの構成

  • 訓練モデル
    ここでは、ネットワークの出力にソフトマックス関数、ロスとして、正規化された予測結果とone-hotラベルに対するクロスエントロピーを用います。

以上より、86%程度の正解率を持つモデルを構築できます。

また、このチュートリアルでは、複数のGPUを用いた訓練方法も紹介されています。(GPU環境がないので、この部分は試せていません。) GPU環境がある方は、ぜひ試してみてください。

Vector Representations of Words

ここまでは、画像を中心に扱ってきましたが、ここからは自然言語について扱います。自然言語を扱う上で有名なモデルとしてword2vecが挙げられます。このモデルでは、単語のベクトル表現を用います。(word embedding)

それでは、なぜ単語のベクトル表現を用いるのでしょうか。これまで見てきたような物体認識や音声認識では、その情報が密なためすべての情報を使用する必要がありました。しかし、自然言語では、伝統的に単語を離散的なシンボル、例えば「犬」を「Id537」、「猫」を「Id143」、といった風に扱ってきました。そのため、データがスパースになり、統計的なモデルを訓練するためにはよりデータが必要となります。そこで、これらを解決するために考えられたのが単語のベクトル表現です。

f:id:hiyoko9t:20171009163635p:plain
情報のイメージ

word2vecは、連続的Bag-of-Words(CBOW)とSkip-Gramモデルという二つのアーキテクチャを持ちます。CBOWは、もととなるコンテクスト (e.g. 'the cat sits on the')からターゲットとなる言葉 ('mat')を予測する一方、Skip-Gramは、ターゲットからもととなるコンテクストを推測します。CBOWは、比較的小さなデータセットに対してうまくいくことが知られており、Skip-Gramは、比較的大きなデータセットに対してうまくいくことが知られています。このチュートリアルではSkip-Gramモデルを中心に扱います。Skip-Gramモデルに関しては、こちらのブログでわかりやすく解説されているので興味のある方はどうぞ。

こうして得られた単語のベクトル表現をt-SNEなどの次元削減を用いて、表現したものが次の図になります。次元削減には、他にも主成分分析などがあり、次元削減の基本的な考え方については、以前のブログで述べているのでそちらをどうぞ。

f:id:hiyoko9t:20171009170354p:plain
単語の関係性

図より、「man」から「woman」、「king」から「queen」へ向かうベクトルがほぼ同じ方向を向いていることがわかります。これは、単語間の関係性をこのベクトル空間上において、うまく表現できていることを示しています。

さらに多くの単語をベクトル表現化し、t-SNEで次元削減をおこなったものが次の図となります。

f:id:hiyoko9t:20171009171738p:plain
単語の関係性

図より、似た意味を持つ単語が近くにプロットされていることが確認できます。

Recurrent Neural Networks

このチュートリアルでは、言語モデルに対する再帰ニューラルネットワーク(Recurrent Neural Networks; RNN)を訓練します。目的としては、与えられた言葉のヒストリーから次の単語を予測することとなります。ここでは有名なベンチマークであるPenn Tree Bankデータセットを用います。

このチュートリアルでは、RNNの特殊な構造を持つLong Short Term Memory(LSTM)を用います。LSTMの詳しい解説はここでは行いませんが、その名前にある通り、従来のRNNではできなかった長期的なデータの記憶を行うことができます。LSTMについて、詳しく知りたい方は、わかるLSTM ~ 最近の動向と共に - QiitaもしくはLSTMネットワークの概要 - Qiitaをご覧ください。

バッチ内における各単語は、以下のように自動的に時間$t$と結び付けられます。

 t=0  t=1    t=2  t=3     t=4
[The, brown, fox, is,     quick]
[The, red,   fox, jumped, high]

words_in_dataset[0] = [The, The]
words_in_dataset[1] = [fox, fox]
words_in_dataset[2] = [is, jumped]
words_in_dataset[3] = [quick, high]
num_batches = 4, batch_size = 2, time_steps = 5

LSTMの主要な実装は、以下の部分となります。

words_in_dataset = tf.placeholder(tf.float32, [num_batches, batch_size, num_features])
lstm = tf.contrib.rnn.BasicLSTMCell(lstm_size)
# Initial state of the LSTM memory.
hidden_state = tf.zeros([batch_size, lstm.state_size])
current_state = tf.zeros([batch_size, lstm.state_size])
state = hidden_state, current_state
probabilities = []
loss = 0.0
for current_batch_of_words in words_in_dataset:
    # The value of state is updated after processing each batch of words.
    output, state = lstm(current_batch_of_words, state)

    # The LSTM output can be used to make next word predictions
    logits = tf.matmul(output, softmax_w) + softmax_b
    probabilities.append(tf.nn.softmax(logits))
    loss += loss_function(probabilities, target_words)

これらをのコードは、こちらから入手することができます。これを実行することで、Penn Tree Bankデータセットに対して訓練を行うことができます。

提供されているコードを実行する時は、次のことに気をつけてください。

  • GPU環境がない方
    GPU環境がない状態でptb_word_lm.pyを実行しようとすると、エラーとなるので、オプションとして--num_gpus=0をつけてください。
python ptb_word_lm.py --data_path=$HOME/simple-examples/data/ --model=small --num_gpus=0
  • Python 3.x系を使っている方
    ptb_word_lm.pyのコード255行目、487行目にdict.iteritems()というメソッドがありますが、これをdict.items()に置き換えてください。
for name, op in ops.items():

Sequence-to-Sequence Models

このチュートリアルでは、Sequence-to-Sequenceモデルを用いたニューラル翻訳を行います。

Sequence-to-Sequenceモデルを用いて、英語の文章をフランス語へ変換した結果は次のようになります。

>  Who is the president of the United States?
 Qui est le président des États-Unis ?

フランス語はわかりませんがすごいですね。

これを実現するSequence-to-Sequenceモデルは、入力を処理するエンコーダと出力を生成するデコーダという二つのRNNから構成されます。

f:id:hiyoko9t:20171009185007p:plain
Sequence-to-Sequenceモデルのアーキテクチャ

図の中のボックスは、RNNのセルをあらわしており、GRUセルやLSTMセルがよく用いられます。

基本的なモデルは、上の図の通りですが、デコーダがより直接的に入力にアクセスすることのできる多層Sequence-to-Sequenceモデルも提案されています。

f:id:hiyoko9t:20171009185448p:plain
多層Sequence-to-Sequenceモデル

TensorFlowで、上に挙げた基本的な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はGRUCellもしくはLSTMCellを用います。

これにより、返り値(outputs)として、最初の図のW、X、Y、Z、EOSが返ります。

ここでは、Sequence-to-Sequenceモデルの基本的な構造しか紹介しませんが、さらに精度を高める工夫として、ソフトマックスサンプル、バケッティングやパディングなども紹介されています。

Large-scale Linear Models with TensorFlow

tf.estimator APIは、線形モデルに関する様々なツールを提供しています。

線形モデルは、特徴量に対して一つの重み付けされた和を用います。
ディープニューラルネットワークが非常に高い精度を誇る中、線形モデルを用いる利点は次のようなものが挙げられます。

  • 訓練が速い
  • 非常に大きな特徴量に対してうまく動く
  • 学習率など多くの面倒な処理を行わずに済む
  • 解釈やデバッグがしやすい、また、大きな意味のある特徴量がわかる
  • 機械学習の導入としてわかりやすい
  • 産業で広く用いられている

このチュートリアルでは、tf.estimatorを用いた線形分類/回帰モデルの実装、離散値、連続値の特徴量の扱い方、線形モデルとディープニューラルネットワークの結合方法が紹介されています。

TensorFlow Linear Model Tutorial

このチュートリアルでは、tf.estimator APIを用いて、二値分類を行います。

入力データとして、年齢、性別、教育、職業などが与えられたとき、年収が50000ドルを超えているかどうかを判別します。

また、ここではロジスティック回帰モデルを訓練することとします。

このチュートリアルでは、以下のような構成でそれぞれの実装を行なっています。

  1. 人口調査データの読み込み
  2. データをテンソルへ変換
  3. 特徴量の選択
  4. ロジスティック回帰モデルの定義
  5. モデルの訓練と評価
  6. 過学習を避けるための正則化

ざっくりと各トピックで注目すべきポイントを挙げます。

  • 人口調査データの読み込み
    データには、categoricalなものとcontinuousなもの、離散値と連続値、が存在します。例えば、出身国(日本、アメリカ、インドなど)はcategoricalなデータであり、身長(170.1、159.2、160.1など)はcontinuousなデータとなります。

  • データをテンソルへ変換
    tf.estimator.Estimatorのtrainやevaluateメソッドを使用するためにデータをテンソルへ変換する必要があります。入力関数は、TensorもしくはSparseTensors型の特徴量feature_colusとTensor型のlabelを返さなければなりません。

  • 特徴量の選択
    特徴量は、データをそのまま特徴量とすることもあれば、もとの特徴量から新しい特徴量を作ることもあります。例として、continuousなデータを、連続値の境界を定めることによって、categoricalなデータへ変換することができます。(bucketization) 具体的には、年齢はそのままのデータはcontinuousですが、18歳以下、18歳〜25歳以下など境界を定めることによって、categoricalな特徴量を作ることができます。

age_buckets = tf.feature_column.bucketized_column(
    age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])

他にも、複数の特徴量を組み合わせて、新たな特徴量を作ることができます。 (線形モデルだと特徴量間の相関が考慮できないため)

  • 過学習を避けるための正則化
    モデルが複雑になればなるほど、訓練データに過剰に適合してしまい、未知のデータへの汎化性能が落ちてしまうことを過学習(overfiting)と言います。これを避けるための手法が、正則化(regularization)であり、ロス関数に足された正則化項は、モデルの複雑さを制御します。
    また、正則化には種類があり、$l_1$正則化はより解がスパースになりやすいといった性質があったり、$l_2$正則化微分可能であるといった特徴があります。(数式で確認するとよくわかると思います。)

以上を実装することにより、83%程度の正解率を持つモデルを構築することができます。

TensorFlow Wide & Deep Learning Tutorial

このチュートリアルでは、wideなモデルとdeepな順伝播型ニューラルネットワークのtf.estimator APIを用いた結合方法を説明します。

wideなモデルとは、スパースな幅広の集合と交差した特徴量(二つ以上の特徴量から作られた特徴量)を持つ線形モデルを指します。

一方、deepなモデルは、これまでのチュートリアルでみてきたような順伝播型ニューラルネットワークを指します。特徴としては、はじめに高次元のスパースなベクトルを低次元の密な実数値ベクトルに変換することが挙げられます。(これをembeddingと言います) こうして作成されたベクトルともともと連続なベクトルを結合して、入力とします。

deepなモデルにより、まだ見ていないデータに対する汎化性能が上がることが知られています。しかし、もともとの特徴量が高次元でスパースであるとき、密な低次元ベクトルを学習するのは難しく、ここで線形モデルがもとの高次元のベクトルの情報を保持するのに役立ちます。手法の詳しい説明は論文をどうぞ([1606.07792] Wide & Deep Learning for Recommender Systems)。

f:id:hiyoko9t:20171009030542p:plain
WideなモデルとDeepなモデルの結合

結果として、84%程度の正解率を持つモデルを構築できます。

Improving Linear Models Using Explicit Kernel Methods

このチュートリアルでは、劇的に精度を上げるための線形モデルとカーネル法の結合を紹介します。

「MNIST For ML Beginners」で、MNISTに対してシンプルなソフトマックス回帰を行なった結果、その正解率はおよそ92%程度でした。また、ロス関数を最小化するためのアルゴリズムは降下勾配法でしたが、それを$l_2$正則化を用いたアルゴリズム、Follow-The-Regularized-Leader(FTRL)に置き換えても正解率はおよそ93%程度です。

これより、パラメータの値に関わらず、この線形モデルの限界はおおよそ93%程度であることがわかります。すなわち、データセットが線形分離不可能であることを示唆しています。

この7%程度のエラーを小さくするためにカーネル法を用います。カーネル法は、線形分離が不可能な特徴を線形分離が可能となるような高次元に特徴を写像する手法です。

f:id:hiyoko9t:20171009093011p:plain
高次元空間への写像

ここでは、MNISTの次元$28\times 28 = 784$を$2000$次元の空間へ写像することにより、(より)線形分離を可能としています。

これにより、正解率97%程度のモデルが構築できます。

では、線形分離を可能とするためにとにかく高次元に写像すれば良いのでしょうか。実際は、一定の閾値を越えるとほとんど正解率が変わらなくなることが知られています。次の図は、横軸が写像後の次元、縦軸が正解率を示しています。

f:id:hiyoko9t:20171009093406p:plain
写像後の次元に対する正解率

次元を大きくしていっても、一定の値を越えるとほとんど正解率が変わらなくなることが確認できます。

Mandelbrot Set

TensorFlowは、機械学習に特化したライブラリですが、他の数学の問題に対しても使用することができます。

その一つの例として、マンデルブロ集合の可視化を行います。マンデルブロ集合の詳細についてはこちら

実際に、TensorFlowを用いて、マンデルブロ集合を可視化したものが次のようになります。

f:id:hiyoko9t:20171008182748j:plain
マンデルブロ集合

Partial Differential Equations

このチュートリアルでは、偏微分方程式のふるまいをシミュレーションした様子を紹介します。

まず、$500\times 500$の正方形の画像に雨粒を落とします。(ランダムで生成した点をプロットします)

f:id:hiyoko9t:20171008185413j:plain
雨粒を落とした画像

ここで、ラプラシアンカーネルを作成して、畳み込み計算を行います。そして、適当な重みを与えて、もとの画像と足し合わせることで、次のさざなみが立ったようなシミュレーションが得られます。

f:id:hiyoko9t:20171008190913g:plain
さざなみが立っていく様子

綺麗ですね。

まとめ

長い記事になりましたが、目を通された方はお疲れ様です。(ありがとうございます。)

本記事では、TensorFlowのチュートリアルを紹介してきました。筆者は3日ほどかけて、チュートリアルを読みながらコードを実行してきましたが、正直「じゃあ自分のモデルを書くぞ!」ってスラスラ書ける気はあんまりしません。何回も参照し直して、憶えていくしかなさそうですね。

今回でチュートリアルを一通り終えることができたので、今後は論文の実装なども行なっていければなと思います。

不定期更新ですがよろしくお願いします。 何かあればコメントやツイッター(@hiyoko9t)でお気軽にお声がけください。