TensorFlow : ML 初心者のための Getting Started (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 02/26/2018
* TensorFlow の Getting Started が再構成され、機械学習初心者のためのドキュメントが追加されましたので新たに翻訳しました。
* 本ページは、TensorFlow の本家サイトの Get Started – Getting Started for ML Beginners
を翻訳した上で適宜、補足説明したものです:
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
このドキュメントはアイリス花を種 (しゅ) で分類 (カテゴリー分け) するために機械学習をどのように使用するかを説明します。このドキュメントはそれを正確に行なう TensorFlow コードへと深く潜り、その途中で ML 基礎を説明していきます。
もし次のリストが貴方について記述している場合には、貴方は正しい場所にいます :
- 機械学習について殆ど知らない。
- どのように TensorFlow プログラムを書くかを学ぶことを望む。
- Python で (少なくとも少しは) コードが書ける。
アイリス分類問題
貴方が植物学者で見つけたアイリス花の各々を分類するための自動化された方法を求めている、と想像してください。機械学習は花を分類する多くの方法を提供します。例えば、洗練された機械学習プログラムは写真を基にして花を分類できるでしょう。私達の野望はより控えめなものです — アイリス花をそれらのがく片 (= sepal) と花弁 (= petal) の長さと幅だけを基にして分類します。アイリス属は約 300 種を伴いますが、私達のプログラムは以下の3つだけを分類します :
- アイリス・セトサ
- アイリス・バージカラー
- アイリス・バージニカ
左から右へ、アイリス・セトサ (by Radomil, CC BY-SA 3.0)、アイリス・バージカラー (by Dlanglois, CC BY-SA 3.0)、そして アイリス・バージニカ (by Frank Mayfield, CC BY-SA 2.0) です。
幸いなことに、がく片と花弁の測定を持つ 120 アイリス花のデータセット を既に誰かが作成しています。このデータセットは機械学習分類問題への規範的なイントロダクションの一つになっています。(MNIST データベース、これは手書き数字を含みます、はもう一つのポピュラーな分類問題です。) アイリス・データセットの最初の 5 エントリは次のように見えます :
がく片長さ | がく片幅 | 花弁長さ | 花弁幅 | 種 |
---|---|---|---|---|
6.4 | 2.8 | 5.6 | 2.2 | 2 |
5.0 | 2.3 | 3.3 | 1.0 | 1 |
4.9 | 2.5 | 4.5 | 1.7 | 2 |
4.9 | 3.1 | 1.5 | 0.1 | 0 |
5.7 | 3.8 | 1.7 | 0.3 | 0 |
幾つかの用語を紹介しましょう :
- 最後のカラム (種) はラベルと呼ばれます ; 最初の4つのカラムは特徴と呼ばれます。特徴はサンプルの特質で、一方でラベルは予測しようとしているものです。
- サンプルは一つ実例の花のための特徴のセットとラベルから成ります。先のテーブルは 120 サンプルのデータセットからの5つのサンプルを示します。
各ラベルは自然に文字列 (例えば、”setosa”) ですが、機械学習は典型的には数値に依拠します。それ故に、誰かが各文字列を数字にマップしました。表現スキームがここにあります :
- 0 は「セトサ」を表します。
- 1 は「バージカラー」を表します。
- 2 は「バージニカ」を表します。
モデルと訓練
モデルは特徴とラベルの間の関係性です。アイリス問題については、モデルはがく片と花弁測定とアイリス種の間の関係性を定義します。ある単純なモデルは代数の 2, 3 行で記述できます ; より複雑な機械学習モデルは数学的に要約することが難しくなるような数学的関数とパラメータの巨大な数の組み合わせを含みます。
貴方は4つの特徴とアイリス種の間の関係性を機械学習を利用することなく決定できますか?つまり、貴方は伝統的なプログラミング技術 (例えば、多くの条件ステートメント) を使用してモデルを作成できますか?多分 (= Maybe)。特定の種へのがく片と花弁測定の正しい関係性を決定するために貴方はデータセットと長時間遊ぶことはできるでしょう。けれども、良い機会学習アプローチは貴方のためにモデルを決定します。つまり、正しい機械学習モデルのタイプに十分な典型的なサンプルを供給すれば、プログラムはがく片、花弁、そして種の関係性を決定するでしょう。
訓練はモデルが徐々に最適化 (学習) されていく機械学習のステージです。アイリス問題は教師あり機械学習のサンプルで、そこではモデルはラベルを含むサンプルから訓練されます。(教師なし機械学習では、サンプルはラベルを含みません。代わりに、モデルは典型的には特徴の中のパターンを見つけます。)
サンプル・プログラムを取得する
このドキュメントのサンプル・コードで遊ぶ前に、以下を行なってください :
- TensorFlow をインストールします。
- virtualenv か Anaconda で TensorFlow をインストールした場合、貴方の TensorFlow 環境を activate します。
- 次のコマンドを実行して pandas をインストールまたはアップグレードします :
pip install pandas
サンプル・プログラムを取得するために次のステップを取ります :
- TensorFlow Models レポジトリを次のコマンドを入力して github から clone します :
git clone https://github.com/tensorflow/models
- そのブランチ内のこのドキュメントで使用するサンプルを含む位置へディレクトリを変更します :
cd models/samples/core/get_started/
その get_started ディレクトリで、premade_estimator.py という名前のプログラムを見つけるでしょう。
サンプル・プログラムを実行する
任意の Python プログラムを実行するように TensorFlow プログラムを実行します。従って、premade_estimators.py を実行するためにはコマンドラインから次のコマンドを実行します :
python premade_estimator.py
プログラムの実行は次のような3つの予測行で終わる非常に多くの情報を出力するはずです :
... Prediction is "Setosa" (99.6%), expected "Setosa" Prediction is "Versicolor" (99.8%), expected "Versicolor" Prediction is "Virginica" (97.9%), expected "Virginica"
プログラム予測の代わりにエラーを生成する場合は、次の質問を貴方自身で尋ねてください :
- TensorFlow を正しくインストールしましたか?
- TensorFlow の正しいバージョンを使用していますか?premade_estimators.py プログラムは少なくとも TensorFlow v1.4 を必要とします。
- TensorFlow を virtualenv か Anaconda でインストールした場合、環境を activate しましたか?
TensorFlow プログラミング・スタック
次の図が示すように、TensorFlow は複数の API 層から成るプログラミング・スタックを提供します :
TensorFlow プログラミング環境
TensorFlow プログラムを書き始めるとき、次の2つの高位 API に焦点を当てることを強く推奨します :
- Estimators
- Datasets
他の API から必要な際の便利関数につかまりはしますが、このドキュメントは上の2つの API に焦点を当てます。
プログラムそれ自身
お待たせしました ; コードを調べましょう。premade_estimator.py — そして多くの他の TensorFlow プログラム — の一般的なアウトラインは次のようなものです :
- データセットをインポートして解析します。
- データを表わすために特徴カラムを作成します。
- モデルのタイプを選択します。
- モデルを訓練します。
- モデルの有効性を評価します。
- 訓練されたモデルに予測させます。
以下の副セクションは各パートを詳述します。
データセットをインポートして解析する
アイリス・プログラムは次の2つの .csv ファイルからのデータを必要とします :
- http://download.tensorflow.org/data/iris_training.csv, これは訓練セットを含みます。
- http://download.tensorflow.org/data/iris_test.csv, これはテストセットを含みます。
訓練セットはモデルを訓練するために使用するサンプルを含みます ; テストセットは訓練されたモデルの有効性を評価するために使用するサンプルを含みます。
訓練セットとテストセットは単一のデータセットとして始まります。それから、誰かがサンプルを分割します、大部分は訓練セットに入りそして残りはテストセットに入ります。訓練セットへのサンプルの追加は通常はより良いモデルを構築します ; けれども、テストセットへの更なるサンプルの追加はモデルの有効性をより良く測定することを可能にします。分割にかかわらず、テストセットのサンプルは訓練セットのサンプルとは別にしなければなりません。そうでなければ、モデルの有効性を正確に決定することはできません。
premade_estimators.py プログラムは訓練セットとテストセットを読み込み解析するために近接する iris_data.py ファイルの load_data 関数に依拠します。関数の数多くコメントされたバージョンがここにあります :
TRAIN_URL = "http://download.tensorflow.org/data/iris_training.csv" TEST_URL = "http://download.tensorflow.org/data/iris_test.csv" CSV_COLUMN_NAMES = ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth', 'Species'] ... def load_data(label_name='Species'): """Parses the csv file in TRAIN_URL and TEST_URL.""" # Create a local copy of the training set. train_path = tf.keras.utils.get_file(fname=TRAIN_URL.split('/')[-1], origin=TRAIN_URL) # train_path now holds the pathname: ~/.keras/datasets/iris_training.csv # Parse the local CSV file. train = pd.read_csv(filepath_or_buffer=train_path, names=CSV_COLUMN_NAMES, # list of column names header=0 # ignore the first row of the CSV file. ) # train now holds a pandas DataFrame, which is data structure # analogous to a table. # 1. Assign the DataFrame's labels (the right-most column) to train_label. # 2. Delete (pop) the labels from the DataFrame. # 3. Assign the remainder of the DataFrame to train_features train_features, train_label = train, train.pop(label_name) # Apply the preceding logic to the test set. test_path = tf.keras.utils.get_file(TEST_URL.split('/')[-1], TEST_URL) test = pd.read_csv(test_path, names=CSV_COLUMN_NAMES, header=0) test_features, test_label = test, test.pop(label_name) # Return four DataFrames. return (train_features, train_label), (test_features, test_label)
Keras はオープンソースの機械学習ライブラリです ; tf.keras は Keras の TensorFlow 実装です。premade_estimator.py プログラムは一つの tf.keras 関数にアクセスするだけです、すなわち、tf.keras.utils.get_file 便利関数で、これはリモート CSV ファイルをローカルファイルシステムにコピーします。
load_data への呼び出しは訓練とテストセットそれぞれのために2つの (特徴, ラベル) のペアを返します :
# Call load_data() to parse the CSV file. (train_feature, train_label), (test_feature, test_label) = load_data()
pandas は幾つかの TensorFlow 関数で利用されるオープンソースの Python ライブラリです。pandas DataFrame は名前付けられたカラムヘッダと番号付けられた行を持つテーブルです。load_data により返された特徴は DataFrame 内にパックされます。例えば、test_feature DataFrame は次のように見えます :
SepalLength SepalWidth PetalLength PetalWidth 0 5.9 3.0 4.2 1.5 1 6.9 3.1 5.4 2.1 2 5.1 3.3 1.7 0.5 ... 27 6.7 3.1 4.7 1.5 28 6.7 3.3 5.7 2.5 29 6.4 2.9 4.3 1.3
データを記述する
特徴カラムは各特徴においてデータをどのように解釈するかを貴方のモデルに教えるデータ構造です。
アイリス問題では、モデルに各特徴のデータをその浮動小数点値リテラルとして解釈することを望みます ; つまり、モデルに 5.4 のような入力値を 5.4 として解釈することを望みます。けれども、他の機械学習問題では、データを文字通りではなく解釈することがしばしば望ましいです。データを解釈するために特徴カラムを使用することはそれに ドキュメント 全体をかけるようなリッチなトピックです。
コードの観点からは、tf.feature_column モジュールからの関数を呼び出して feature_column オブジェクトのリストを構築します。各オブジェクトはモデルへの入力を記述します。モデルにデータを浮動小数点値として解釈するように伝えるためには、@{tf.feature_column.numeric_column) を呼び出します。premade_estimator.py では、4つ総ての特徴は浮動小数点値リテラルとして解釈されるべきですから、特徴カラムを作成するコードは次のようになります :
# Create feature columns for all features. my_feature_columns = [] for key in train_x.keys(): my_feature_columns.append(tf.feature_column.numeric_column(key=key))
エレガントではありませんが、多分よりクリアな、前のブロックをエンコードする他の方法がここにあります :
my_feature_columns = [ tf.feature_column.numeric_column(key='SepalLength'), tf.feature_column.numeric_column(key='SepalWidth'), tf.feature_column.numeric_column(key='PetalLength'), tf.feature_column.numeric_column(key='PetalWidth') ]
モデルのタイプを選択する
訓練されるモデルの種類を選択する必要があります。多くのモデル・タイプがあります ; 理想的なタイプを選択するには経験が必要です。私達はアイリス問題を解くためにニューラルネットワークを選択しました。ニューラルネットワーク は特徴とラベルの間の複雑な関係性を見つけることができます。ニューラルネットワークは高度に構造化されたグラフで、一つまたはそれ以上の 隠れ層 に組織化されています。各隠れ層は一つまたはそれ以上の ニューロン から成ります。ニューラルネットワークの幾つかのカテゴリーがあります。私達は 完全結合ニューラルネットワーク を使用します、これは一つの層のニューロンが前の層の総てのニューロンからの入力を取ることを意味します。例えば、下の図は3つの隠れ層から成る完全結合ニューラルネットワークを示します :
- 最初の隠れ層は4つのニューロンを含みます。
- 2番目の隠れ層は3つのニューロンを含みます。
- 3番目の隠れ層は2つのニューロンを含みます。
モデルのタイプを指定するには、Estimator クラスをインスタンス化します。TensorFlow は Estimator の2つのカテゴリを提供します :
- pre-made Estimator、これは他の誰かが既に貴方のために書いたものです。
- カスタム Estimator、これは貴方自身でコードを書かなければなりません、少なくとも部分的には。
ニューラルネットワークを実装するために、premade_estimators.py プログラムは tf.estimator.DNNClassifier という名前の pre-made Estimator を使用します。この Estimator はサンプルを分類するニューラルネットワークを構築します。次の呼び出しは DNNClassifier をインスタンス化します :
classifier = tf.estimator.DNNClassifier( feature_columns=my_feature_columns, hidden_units=[10, 10], n_classes=3)
ニューラルネットワークの各隠れ層のニューロン数を定義するためには hidden_units パラメータを使用してください。このパラメータにはリストを割り当てます。例えば :
hidden_units=[10, 10],
hidden_units に割り当てられたリストの長さは隠れ層の数を識別します (この場合は 2 です)。リストの各値は特定の隠れ層のニューロン数を表します (最初の隠れ層が 10 で2番目の隠れ層が 10 です)。隠れ層やニューロンの数を変更するには、単に hidden_units パラメータに異なるリストを割り当てます。
隠れ層とニューロンの理想的な数は問題とデータセットに依存します。機械学習の多くの様相のように、ニューラルネットワークの理想的な shape を選択することは知識と経験のある種の混合を必要とします。経験則では、隠れ層とニューロンの数を増やすことは典型的にはよりパワフルなモデルを作成し、これは効果的に訓練するためにはよりデータを必要とします。
n_classes パラメータはニューラルネットワークが予測可能な起こりうる値の数です。アイリス問題は3つのアイリス種を分類しますので、n_classes を 3 に設定します。
tf.Estimator.DNNClassifier のためのコンストラクタは optimizer という名前のオプションの引数を取ります、これは私達のサンプル・コードでは指定していません。optimizer はモデルがどのように訓練するかを制御します。貴方が機械学習においてより専門技術を身に付けるにつれて、optimizer と learning rate (学習率) は非常に重要になっていくでしょう。
モデルを訓練する
tf.Estimator.DNNClassifier のインスタンス化はモデルを学習するためのフレームワークを作成します。基本的には、私達はネットワークを配線しましたがしかしまだデータをそれに通して流していません。ニューラルネットワークを訓練するためには、Estimator オブジェクトの train メソッドを呼び出します。例えば :
classifier.train( input_fn=lambda:train_input_fn(train_feature, train_label, args.batch_size), steps=args.train_steps)
steps 引数は指定した反復数後に訓練をやめることを train に伝えます。steps を増やすことはモデルが訓練する時間の総量を増やします。直感に反して、モデルをより長く訓練することはより良いモデルを保証しません。args.train_steps のデフォルト値は 1000 です。訓練するためのステップ数は貴方が調整可能なハイパーパラメータ です。ステップの適正な数を選択することは通常は経験と実験の両者を必要とします。
input_fn パラメータは訓練データを供給する関数を識別します。train メソッドへの呼び出しは train_input_fn 関数が訓練データを供給することを示します。ここにそのメソッドのシグネチャがあります :
def train_input_fn(features, labels, batch_size):
私達は train_input_fn に次の引数を渡しています :
- train_feature は Python 辞書でそこでは :
- 各キーは特徴の名前です。
- 各値は訓練セットの各サンプルのための値を含む配列です。
- train_label は訓練セットの総てのサンプルのためのラベルの値を含む配列です。
- args.batch_size は バッチサイズ を定義する整数です。
train_input_fn function は Dataset API に依拠します。これはデータを読みそれを train メソッドが必要とする形式に変換するための高位な TensorFlow API です。次の呼び出しは入力特徴とラベルを tf.data.Dataset オブジェクトに変換します、これは Dataset API の基底クラスです :
dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))
tf.dataset クラスは訓練のためのサンプルを用意するために多くの有用な関数を提供します。次の行はこれらの関数の3つを呼び出します :
dataset = dataset.shuffle(buffer_size=1000).repeat(count=None).batch(batch_size)
訓練サンプルがランダムオーダーにある場合に訓練は最も良く動作します。サンプルをランダム化するためには、tf.data.Dataset.shuffle を呼び出します。buffer_size をサンプル (120) の数よりも大きい値に設定することはデータが上手くシャッフルされることを確実にします。
訓練の間、train メソッドは典型的にはサンプルを複数回処理します。どのような引数も持たない tf.data.Dataset.repeat メソッドの呼び出しは train メソッドが (今はシャッフルされた) 訓練セット・サンプルの無限の供給を持ちます。
train メソッドはサンプルの バッチ を一度に処理します。tf.data.Dataset.batch メソッドは複数のサンプルを鎖状に繋ぐことによりバッチを作成します。プログラムはデフォルトのバッチサイズを 100 に設定します、これは batch メソッドが 100 サンプルのグループを結合していくことを意味します。理想的なバッチサイズは問題に依存します。経験則では、より小さいバッチサイズは通常は (時に) 精度を犠牲にして train メソッドにモデルをより高速に訓練することを可能にします。
次の return ステートメントは呼び出し側 (train メソッド) にサンプルのバッチを返します。
return dataset.make_one_shot_iterator().get_next()
モデルを評価する
評価することはモデルがどのように効果的に予測を行なうかを決定することを意味します。アイリス分類モデルの有効性を決定するために、幾つかのがく片と花弁測定を見てるに渡してそれらがどのアイリス種を表わすかを予測するためにモデルに問い合わせます。それからモデルの予測を実際のラベルに対して比較します。例えば、入力サンプルの半分で正しい種を選択するモデルは 0.5 の 精度 を持つことになります。以下はより効果的なモデルを提示します :
テストセット | – | ||||
---|---|---|---|---|---|
特徴 | ラベル | 予測 | |||
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 |
モデルの有効性を評価するために、各 Estimator は evaluate メソッドを提供します。premade_estimator.py プログラムは evaluate を次のように呼び出します :
# Evaluate the model. eval_result = classifier.evaluate( input_fn=lambda:eval_input_fn(test_x, test_y, args.batch_size)) print('\nTest set accuracy: {accuracy:0.3f}\n'.format(**eval_result))
classifier.evaluate への呼び出しは classifier.train への呼び出しと同様です。最大の違いは classifier.evaluate はそのサンプルを訓練セットではなくテストセットから取得しなければならないことです。換言すれば、構成にモデルの有効性にアクセスするためには、モデルを評価するために使用されるサンプルはモデルを訓練するために使用されたサンプルとは異なっていなければなりません。eval_input_fn 関数はテストセットからサンプルのバッチをサーブします。ここに eval_input_fn があります :
def eval_input_fn(features, labels=None, batch_size=None): """An input function for evaluation or prediction""" if labels is None: # No labels, use only features. inputs = features else: inputs = (features, labels) # Convert inputs to a tf.dataset object. dataset = tf.data.Dataset.from_tensor_slices(inputs) # Batch the examples assert batch_size is not None, "batch_size must not be None" dataset = dataset.batch(batch_size) # Return the read end of the pipeline. return dataset.make_one_shot_iterator().get_next()
要するに、eval_input_fn は classifier.evaluate から呼び出されたときに次を行ないます :
- テストセットからの特徴とラベルを tf.dataset オブジェクトに変換します。
- テストセットのサンプルのバッチを作成します。(テストセットのサンプルをシャッフルしたり反復したりする必要性はありません。)
- テストセットのサンプルのそのバッチを classifier.evaluate に返します。
コードの実行は次の出力を生成します (あるいはそれに近いもの) :
Test set accuracy: 0.967
精度 0.967 は、私達の訓練されたモデルはテストセットの 30 のアイリス種から 20 を正しく分類したことを包含しています。
予測する
さて私達はモデルを訓練してそれはアイリス種を良く分類する – しかし完全ではない – ことが「証明」されました。ラベル付けされていないサンプル 上で幾つか予測を行なうために訓練モデルを使用しましょう ; つまり、特徴を含むけれどもラベルは含まないサンプル上です。
現実には、ラベル付けされていないサンプルは apps、CSV ファイル、そしてデータ供給を含む異なる多くのソースに由来するでしょうが。今は、単純に次の3つのラベル付けされていないサンプルを手動で提供します :
predict_x = { 'SepalLength': [5.1, 5.9, 6.9], 'SepalWidth': [3.3, 3.0, 3.1], 'PetalLength': [1.7, 4.2, 5.4], 'PetalWidth': [0.5, 1.5, 2.1], }
総ての Estimator は predict メソッドを提供し、これは premade_estimator.py が次のように呼び出します :
predictions = classifier.predict( input_fn=lambda:eval_input_fn(predict_x, batch_size=args.batch_size))
evaluate メソッドと同様に、predict メソッドもまた eval_input_fn メソッドからサンプルを集めます。
予測を行なうときは、eval_input_fn にラベルを渡しません。従って、eval_input_fn は次を行ないます :
- 作成したばかりの 3-要素のマニュアルセットから特徴を変換します。
- マニュアルセットから 3 サンプルのバッチを作成します。
- そのサンプルのバッチを classifier.predict に返します。
predict メソッドは python iterable を返し、各サンプルのための予測結果の辞書を生成します。辞書は幾つかのキーを含みます。probabilities キーは3つの浮動小数点値のリストを保持し、各々は入力サンプルが特定のアイリス種である確率を表します。例えば、次の probabilities リストを考えます :
'probabilities': array([ 1.19127117e-08, 3.97069454e-02, 9.60292995e-01])
前のリストは次を示しています :
- アイリスがセトサである可能性は無視できます。
- アイリスがバージカラーである可能性は 3.97 % です。
- アイリスがバージニカである可能性は 96.0 % です
class_ids キーは最も可能性のある種を識別する 1-要素配列を保持します。例えば :
'class_ids': array([2])
ナンバー 2 はバージニカに対応します。次のコードは各予測について報告するために返された予測を通してイテレートします :
for pred_dict, expec in zip(predictions, expected): template = ('\nPrediction is "{}" ({:.1f}%), expected "{}"') class_id = pred_dict['class_ids'][0] probability = pred_dict['probabilities'][class_id] print(template.format(SPECIES[class_id], 100 * probability, expec))
プログラムの実行は次の出力を生成します :
... Prediction is "Setosa" (99.6%), expected "Setosa" Prediction is "Versicolor" (99.8%), expected "Versicolor" Prediction is "Virginica" (97.9%), expected "Virginica"
以上