In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Note: これらのドキュメントは私たちTensorFlowコミュニティが翻訳したものです。コミュニティによる 翻訳はベストエフォートであるため、この翻訳が正確であることや英語の公式ドキュメントの 最新の状態を反映したものであることを保証することはできません。 この翻訳の品質を向上させるためのご意見をお持ちの方は、GitHubリポジトリtensorflow/docsにプルリクエストをお送りください。 コミュニティによる翻訳やレビューに参加していただける方は、 docs-ja@tensorflow.org メーリングリストにご連絡ください。
このガイドでは機械学習を使ってアヤメの花を品種によって分類します。TensorFlowを使って下記のことを行います。
このガイドでは、次のような TensorFlow の高レベルの概念を用います。
このチュートリアルは、多くの TensorFlow のプログラムと同様に下記のように構成されています。
TensorFlow とその他必要な Python のモジュールをインポートします。TensorFlow は既定で Eager Execution を使って演算結果を即時に評価し、後で実行される 計算グラフ を構築する代わりに具体的な値を返します。REPL や python の対話的コンソールを使っている方にとっては、馴染みのあるものです。
In [ ]:
import os
import matplotlib.pyplot as plt
In [ ]:
import tensorflow as tf
In [ ]:
print("TensorFlow version: {}".format(tf.__version__))
print("Eager execution: {}".format(tf.executing_eagerly()))
あなたが植物学者で、発見したアヤメの花を分類する自動的な方法を探しているとします。機械学習には花を統計的に分類するためのアルゴリズムがたくさんあります。たとえば、洗練された機械学習プログラムを使うと、写真を元に花を分類することができます。私達の願望はもう少し控えめです。アヤメの花を sepals(萼片) と petals(花弁) の長さと幅を使って分類することにしましょう。
アヤメ属にはおよそ300の種がありますが、ここで扱うプログラムは次の3つの種類のみを分類します。
|
|
|
図1. Iris setosa (by Radomil, CC BY-SA 3.0), Iris versicolor, (by Dlanglois, CC BY-SA 3.0), and Iris virginica (by Frank Mayfield, CC BY-SA 2.0). |
幸いにして、萼片と花弁の計測値を使った 120のアヤメのデータセット を作ってくれた方がいます。これは初歩の機械学習による分類問題でよく使われる古典的なデータセットです。
データセットファイルをダウンロードし、この Python プログラムで使えるような構造に変換します。
tf.keras.utils.get_file 関数を使って訓練用データセットをダウンロードします。この関数は、ダウンロードしたファイルのパスを返します。
In [ ]:
train_dataset_url = "https://storage.googleapis.com/download.tensorflow.org/data/iris_training.csv"
train_dataset_fp = tf.keras.utils.get_file(fname=os.path.basename(train_dataset_url),
origin=train_dataset_url)
print("Local copy of the dataset file: {}".format(train_dataset_fp))
In [ ]:
!head -n5 {train_dataset_fp}
こうしてデータセットを見ることで次のことがわかります。
コードを書いてみましょう。
In [ ]:
# CSV ファイルの列の順序
column_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']
feature_names = column_names[:-1]
label_name = column_names[-1]
print("Features: {}".format(feature_names))
print("Label: {}".format(label_name))
ラベルはそれぞれ文字列の名前(たとえば、"setosa")に関連付けられていますが、機械学習は大抵の場合、数値に依存しています。ラベルの数値は名前の表現にマップされます。たとえば次のようになります。
0: Iris setosa1: Iris versicolor2: Iris virginica特徴量とラベルについてもっと知りたい場合には、 ML Terminology section of the Machine Learning Crash Course を参照してください。
In [ ]:
class_names = ['Iris setosa', 'Iris versicolor', 'Iris virginica']
tf.data.Dataset の作成TensorFlow の Dataset API はデータのモデルへのロードについて、多くの一般的なケースを扱います。この API は、データを読み込んで訓練に使われる形式へ変換するための高レベル API です。詳しくは、 Datasets Quick Start guide を参照してください。
今回のデータセットは CSV 形式のテキストファイルなので、データをパースして適切なフォーマットに変換するため、 make_csv_dataset 関数を使います。この関数はモデルの訓練のためのデータを生成するので、既定の動作はデータをシャッフルし (shuffle=True, shuffle_buffer_size=10000) 、データセットを永遠に繰り返すこと (num_epochs=None) です。また、 batch_size パラメータも設定します。
In [ ]:
batch_size = 32
train_dataset = tf.data.experimental.make_csv_dataset(
train_dataset_fp,
batch_size,
column_names=column_names,
label_name=label_name,
num_epochs=1)
make_csv_dataset 関数は、 (features, label) というペアの tf.data.Dataset を返します。ここで、 features は、 {'feature_name': value} というディクショナリです。
この Dataset オブジェクトはイテラブルです。特徴量のバッチを1つ見てみましょう。
In [ ]:
features, labels = next(iter(train_dataset))
print(features)
「特徴量のようなもの」はグループ化、言い換えると バッチ化 されることに注意してください。それぞれのサンプル行のフィールドは、対応する特徴量配列に追加されます。これらの特徴量配列に保存されるサンプルの数を設定するには、 batch_size を変更します。
上記のバッチから特徴量のいくつかをプロットしてみると、いくつかのクラスタが見られます。
In [ ]:
plt.scatter(features['petal_length'],
features['sepal_length'],
c=labels,
cmap='viridis')
plt.xlabel("Petal length")
plt.ylabel("Sepal length")
plt.show()
モデル構築のステップを単純化するため、特徴量のディクショナリを (batch_size, num_features) という形状の単一の配列にパッケージし直す関数を作成します。
この関数は、テンソルのリストから値を受け取り、指定された次元で結合されたテンソルを作成する tf.stack メソッドを使っています。
In [ ]:
def pack_features_vector(features, labels):
"""特徴量を1つの配列にパックする"""
features = tf.stack(list(features.values()), axis=1)
return features, labels
次に、 tf.data.Dataset.map メソッドを使って (features,label) ペアそれぞれの features を訓練用データセットにパックします。
In [ ]:
train_dataset = train_dataset.map(pack_features_vector)
この Dataset の特徴量要素は、(batch_size, num_features) の形状をした配列になっています。 最初のサンプルをいくつか見てみましょう。
In [ ]:
features, labels = next(iter(train_dataset))
print(features[:5])
モデル(model) は特徴量とラベルの間の関係です。アヤメの分類問題の場合、モデルは萼片と花弁の計測値と予測されるアヤメの種類の関係を定義します。単純なモデルであれば、2、3行の数式で記述できますが、複雑な機械学習モデルには多数のパラメータがあり、かんたんに要約できるものではありません。
機械学習を使わずにこれらの4つの特徴量とアヤメの品種の関係を決定することは可能でしょうか?言い換えると、従来のプログラミング技術(たとえば、たくさんの条件文)を使ってモデルを作ることは可能でしょうか?おそらく、十分に時間をかけてデータセットを分析して、萼片と花弁の計測値と特定の品種との関係を決定すれば可能でしょう。もっと複雑なデータセットの場合には、これは困難であり、おそらく不可能です。機械学習を使ったよいアプローチでは、あなたに代わってモデルを決定してくれます。適切なタイプの機械学習モデルに、十分な量の典型的なサンプルを投入すれば、プログラムがあなたのためにこの関係性をみつけ出してくれるでしょう。
訓練すべきモデルの種類を決める必要があります。モデルにはたくさんの種類があり、よいものを選択するには経験が必要です。このチュートリアルでは、アヤメの分類問題を解くために、ニューラルネットワークを使用します。ニューラルネットワーク(Neural networks) は、特徴量とラベルの間の複雑な関係をみつけることができます。ニューラルネットワークは、1つかそれ以上の 隠れ層(hidden layers) で構成された高度なグラフ構造です。それぞれの隠れ層には1つ以上の ニューロン(neurons) があります。ニューラルネットワークにはいくつかのカテゴリーがありますが、このプログラムでは、全結合ニューラルネットワーク(fully-connected neural network または dense neural network) を使用します。全結合ニューラルネットワークでは、1つの層の中のニューロンすべてが、前の層のすべてのニューロンからの入力を受け取ります。例として、図2に入力層、2つの隠れ層、そして、出力層からなる密なニューラルネットワークを示します。
|
|
|
図2. 特徴量と隠れ層、予測をもつニューラルネットワーク |
図2のモデルが訓練されてラベルの付いていないサンプルを受け取ったとき、モデルは3つの予測値を返します。予測値はサンプルの花が与えられた3つの品種のそれぞれである可能性を示します。この予測は、 推論(inference) とも呼ばれます。このサンプルでは、出力の予測値の合計は 1.0 になります。図2では、この予測値は Iris setosa が 0.02 、 Iris versicolor が 0.95 、 Iris virginica が 0.03 となっています。これは、モデルがこのラベルのない花を、95% の確率で Iris versicolor であると予測したことを意味します。
TensorFlow の tf.keras API は、モデルと層を作成するためのおすすめの方法です。Keras がすべてを結びつけるという複雑さを引き受けてくれるため、モデルや実験の構築がかんたんになります。
tf.keras.Sequential モデルは層が一列に積み上げられたものです。このクラスのコンストラクタは、レイヤーインスタンスのリストを引数として受け取ります。今回の場合、それぞれ 10 のノードをもつ 2つの Dense レイヤーと、ラベルの予測値を表す3つのノードからなる出力レイヤーです。最初のレイヤーの input_shape パラメータがデータセットの特徴量の数に対応しており、これは必須パラメータです。
In [ ]:
model = tf.keras.Sequential([
tf.keras.layers.Dense(10, activation=tf.nn.relu, input_shape=(4,)), # input shape required
tf.keras.layers.Dense(10, activation=tf.nn.relu),
tf.keras.layers.Dense(3)
])
活性化関数(activation function) は、そのレイヤーの各ノードの出力の形を決定します。この関数の非線形性は重要であり、それがなければモデルは 1層しかないものと等価になってしまいます。利用可能な活性化関数 はたくさんありますが、隠れ層では ReLU が一般的です。
理想的な隠れ層の数やニューロンの数は問題やデータセットによって異なります。機械学習のさまざまな側面と同様に、ニューラルネットワークの最良の形を選択するには、知識と経験の両方が必要です。経験則から、一般的には隠れ層やニューロンの数を増やすとより強力なモデルを作ることができますが、効果的に訓練を行うためにより多くのデータを必要とします。
In [ ]:
predictions = model(features)
predictions[:5]
ご覧のように、サンプルのそれぞれは、各クラスの ロジット(logit) 値を返します。
これらのロジット値を各クラスの確率に変換するためには、 softmax 関数を使用します。
In [ ]:
tf.nn.softmax(predictions[:5])
クラス間で tf.argmax を取ると、予測されたクラスのインデックスが得られます。しかし、このモデルはまだ訓練されていないので、予測はよいものではありません。
In [ ]:
print("Prediction: {}".format(tf.argmax(predictions, axis=1)))
print(" Labels: {}".format(labels))
訓練(Training) は、機械学習において、モデルが徐々に最適化されていく、あるいはモデルがデータセットを学習する段階です。目的は、見たことのないデータについて予測を行うため、訓練用データセットの構造を十分に学習することです。訓練用データセットを学習しすぎると、予測は見たことのあるデータに対してしか有効ではなく、一般化できません。この問題は 過学習(overfitting) と呼ばれ、問題の解き方を理解するのではなく答えを丸暗記するようなものです。
アヤメの分類問題は、 教師あり学習(supervised machine learning) の1種で、モデルはラベルの付いたサンプルを学習します。教師なし学習(unsupervised machine learning) では、サンプルにラベルはありません。そのかわり、一般的にはモデルが特徴量からパターンを発見します。
訓練段階と評価段階では、モデルの 損失(loss) を計算する必要があります。損失とは、モデルの予測が望ましいラベルからどれくらい離れているかを測定するものです。言い換えると、どれくらいモデルの性能が悪いかを示します。この値を最小化、または最適化したいのです。
今回のモデルでは、 tf.keras.losses.SparseCategoricalCrossentropy 関数を使って損失を計算します。この関数は、モデルのクラスごとの予測確率とラベルの取るべき値を使って、サンプル間の平均損失を返します。
In [ ]:
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
In [ ]:
def loss(model, x, y):
y_ = model(x)
return loss_object(y_true=y, y_pred=y_)
l = loss(model, features, labels)
print("Loss test: {}".format(l))
tf.GradientTape コンテキストを使って、モデルを最適化する際に使われる 勾配(gradients) を計算しましょう。
In [ ]:
def grad(model, inputs, targets):
with tf.GradientTape() as tape:
loss_value = loss(model, inputs, targets)
return loss_value, tape.gradient(loss_value, model.trainable_variables)
オプティマイザ(optimizer) は、loss 関数を最小化するため、計算された勾配をモデルの変数に適用します。損失関数は、曲面として考えることができ(図3 参照)、歩き回ることによって最小となる点をみつけたいのです。勾配は、一番急な上りの方向を示すため、逆方向に進んで丘を下ることになります。バッチごとに損失と勾配を繰り返し計算することにより、訓練中のモデルを調節します。モデルは徐々に、損失を最小化する重みとバイアスの最適な組み合わせをみつけます。損失が小さいほど、モデルの予測がよくなります。
|
|
|
図3. 3次元空間における最適化アルゴリズムの時系列可視化。 (Source: Stanford class CS231n, MIT License, Image credit: Alec Radford) |
TensorFlow には、訓練に使える 最適化アルゴリズム(optimization algorithms) がたくさんあります。このモデルでは、確率的勾配降下法(stochastic gradient descent) (SGD) アルゴリズムを実装した tf.train.GradientDescentOptimizer を使用します。learning_rate (学習率)は、イテレーションごとに丘を下る際のステップのサイズを設定します。通常これはよりよい結果を得るために調整する ハイパーパラメータ(hyperparameter) です。
オプティマイザーを設定しましょう。
In [ ]:
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)
これを使って、最適化を1ステップ分計算してみます。
In [ ]:
loss_value, grads = grad(model, features, labels)
print("Step: {}, Initial Loss: {}".format(optimizer.iterations.numpy(),
loss_value.numpy()))
optimizer.apply_gradients(zip(grads, model.trainable_variables))
print("Step: {}, Loss: {}".format(optimizer.iterations.numpy(),
loss(model, features, labels).numpy()))
すべての部品が揃ったので、モデルの訓練ができるようになりました。訓練ループは、モデルにデータセットのサンプルを供給し、モデルがよりよい予測を行えるようにします。下記のコードブロックは、この訓練のステップを構成します。
Dataset(データセット) のサンプルひとつずつから、その features(特徴量) (x) と label(ラベル) (y) を取り出して繰り返し処理します。optimizer を使って、モデルの変数を更新します。num_epochs 変数は、データセットのコレクションを何回繰り返すかという数字です。直感には反しますが、モデルを長く訓練すれば必ずよいモデルが得られるというわけではありません。num_epochs は、ハイパーパラメータ(hyperparameter) であり、チューニングできます。適切な数を選択するには、経験と実験の両方が必要です。
In [ ]:
## Note: このセルを再実行すると同じモデル変数が使われます
# 結果をグラフ化のために保存
train_loss_results = []
train_accuracy_results = []
num_epochs = 201
for epoch in range(num_epochs):
epoch_loss_avg = tf.keras.metrics.Mean()
epoch_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
# 訓練ループ - 32個ずつのバッチを使用
for x, y in train_dataset:
# モデルの最適化
loss_value, grads = grad(model, x, y)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
# 進捗の記録
epoch_loss_avg(loss_value) # Add current batch loss 現在のバッチの損失を加算
# 予測ラベルと実際のラベルを比較
epoch_accuracy(y, model(x))
# エポックの終わり
train_loss_results.append(epoch_loss_avg.result())
train_accuracy_results.append(epoch_accuracy.result())
if epoch % 50 == 0:
print("Epoch {:03d}: Loss: {:.3f}, Accuracy: {:.3%}".format(epoch,
epoch_loss_avg.result(),
epoch_accuracy.result()))
モデルの訓練の進み方をプリントするのも役立ちますが、普通はこの進捗を見るほうがもっと役に立ちます。TensorBoard は TensorFlow に付属する優れた可視化ツールですが、
matplotlib モジュールを使って基本的なグラフを描画することもできます。
これらのグラフを解釈するにはある程度の経験が必要ですが、損失が低下して、正解率が上昇するのをぜひ見たいと思うでしょう。
In [ ]:
fig, axes = plt.subplots(2, sharex=True, figsize=(12, 8))
fig.suptitle('Training Metrics')
axes[0].set_ylabel("Loss", fontsize=14)
axes[0].plot(train_loss_results)
axes[1].set_ylabel("Accuracy", fontsize=14)
axes[1].set_xlabel("Epoch", fontsize=14)
axes[1].plot(train_accuracy_results)
plt.show()
モデルの訓練が終わったら、その性能の指標を得ることができます。
評価とは、モデルの予測がどれだけ効果的であるかどうかを決定することを言います。アヤメの分類でモデルの有効性を見定めるには、モデルに萼片と花弁の測定値をいくつか与え、どのアヤメの品種であるかを予測させます。そして、モデルの予測と実際のラベルを比較します。たとえば、あるモデルが入力サンプルの半分で正解の品種をえらんだとすると、正解率(accuracy) は 0.5 ということになります。図4で示すモデルはもう少し効果的であり、5つの予測中4つで正解し、正解率は 80% です。
| サンプルの特徴量 | ラベル | モデルの予測値 | |||
|---|---|---|---|---|---|
| 5.9 | 3.0 | 4.3 | 1.5 | 1 | 1 |
| 6.9 | 3.1 | 5.4 | 2.1 | 2 | 2 |
| 5.1 | 3.3 | 1.7 | 0.5 | 0 | 0 |
| 6.0 | 3.4 | 4.5 | 1.6 | 1 | 2 |
| 5.5 | 2.5 | 4.0 | 1.3 | 1 | 1 |
|
図4. 正解率 80% のアヤメ分類器 | |||||
モデルの評価はモデルの訓練と同様です。もっとも大きな違いは、サンプルが訓練用データセットではなくテスト用データセット(test set) からのものであるという点です。モデルの有効性を正しく評価するには、モデルの評価に使うサンプルは訓練用データセットのものとは違うものでなければなりません。
テスト用 Dataset の設定は、訓練用 Dataset の設定と同様です。CSV ファイルをダウンロードし、値をパースしてからすこしシャッフルを行います。
In [ ]:
test_url = "https://storage.googleapis.com/download.tensorflow.org/data/iris_test.csv"
test_fp = tf.keras.utils.get_file(fname=os.path.basename(test_url),
origin=test_url)
In [ ]:
test_dataset = tf.data.experimental.make_csv_dataset(
test_fp,
batch_size,
column_names=column_names,
label_name='species',
num_epochs=1,
shuffle=False)
test_dataset = test_dataset.map(pack_features_vector)
訓練段階とは異なり、モデルはテスト用データの 1エポック(epoch) だけで行います。下記のコードセルでは、テスト用データセットのサンプルをひとつずつ処理し、モデルの予測と実際のラベルを比較します。この結果を使ってテスト用データセット全体でのモデルの正解率を計算します。
In [ ]:
test_accuracy = tf.keras.metrics.Accuracy()
for (x, y) in test_dataset:
logits = model(x)
prediction = tf.argmax(logits, axis=1, output_type=tf.int32)
test_accuracy(prediction, y)
print("Test set accuracy: {:.3%}".format(test_accuracy.result()))
たとえば最後のバッチを見ると、モデルはだいたい正確です。
In [ ]:
tf.stack([y,prediction],axis=1)
モデルを訓練し、それが、完全ではないにせよアヤメの品種の分類でよい性能を示すことを「証明」しました。次は、この訓練済みモデルを使って ラベルなしサンプル(unlabeled examples) つまり、特徴量だけでラベルのないサンプルについて予測を行ってみましょう。
現実世界では、ラベルなしサンプルはアプリや CSV ファイル、データフィードなどさまざまな異なるソースからやってきます。ここでは、ラベルを予測するため、3つのラベルなしサンプルを手動で与えることにします。ラベルの番号は、下記のように名前を表していることを思い出してください。
0: Iris setosa1: Iris versicolor2: Iris virginica
In [ ]:
predict_dataset = tf.convert_to_tensor([
[5.1, 3.3, 1.7, 0.5,],
[5.9, 3.0, 4.2, 1.5,],
[6.9, 3.1, 5.4, 2.1]
])
predictions = model(predict_dataset)
for i, logits in enumerate(predictions):
class_idx = tf.argmax(logits).numpy()
p = tf.nn.softmax(logits)[class_idx]
name = class_names[class_idx]
print("Example {} prediction: {} ({:4.1f}%)".format(i, name, 100*p))