TensorFlow : Guide : Estimators : 特徴カラム (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
更新日時 : 07/14/2018
作成日時 : 03/15/2018
* TensorFlow 1.9 でドキュメント構成が変わりましたので調整しました。
* 本ページは、TensorFlow の本家サイトの Guide – Estimators – Feature Columns を翻訳した上で適宜、補足説明したものです:
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
このドキュメントは特徴カラムについて詳述します。特徴カラムは生のデータと Estimator の間の仲介 (= intermediaries) として考えてください。特徴カラムは非常に豊富で、様々な生データを Estimator が利用できるフォーマットに変換することを可能にし、容易な実験を可能にします。
TensorFlow から始める Getting Started において、4つの入力特徴からアイリス花の異なる型を予測するモデルを訓練するために私達は premade Estimator, DNNClassifier を使用しました。そのサンプルは (tf.feature_column.numeric_column 型の) 数値の特徴カラムだけを作成しました。数値の特徴カラムはがく片や花弁の長さを効果的にモデル化しますが、現実世界のデータセットは総ての種類の特徴を含み、それらの多くは非数値です。
深層ニューラルネットワークへの入力
深層ニューラルネットワークはどのような種類のデータの上で動作できるのでしょうか?答えは、もちろん、数値です (例えば、tf.float32)。結局のところ、ニューラルネットワーク内の総てのニューロンは重みと入力データ上で乗算と加算演算を遂行します。現実世界の入力データは、けれども、しばしば非数値 (カテゴリカルな) データを含みます。例えば、次の3つの非数値な値を含むことができる product_class 特徴を考えます :
- 台所用品
- エレクトロニクス
- スポーツ用品
ML モデルは一般にカテゴリカルな値を単純なベクトルで表現します、そこでは 1 は値の存在を表して 0 は値がないことを表します。例えば、product_class がスポーツに設定されるとき、ML モデルは通常は [0, 0, 1] として product_class を表現するでしょう、これは次を意味しています :
- 0: 台所用品はありません
- 0: エレクトロニクスはありません
- 1: スポーツ用は存在します
従って、生データは数値やカテゴリカルであり得ますが、ML モデルは総ての特徴を数値として表現します。
特徴カラム
次の図が提示するように、Estimator (アイリスのためには DNNClassifier) の feature_columns 引数を通してモデルに入力を指定します。特徴カラムは (input_fn で返される) 入力データにモデルとの橋渡しをします。
特徴カラムを作成するためには、tf.feature_column モジュールからの関数を呼ぶ出します。このドキュメントはそのモジュールの9つの関数を説明します。次の図が示すように、総ての9つの関数は、Categorical-Column または Dense-Column オブジェクトを返します。bucketized_column を除いてです、これは両者のクラスから継承します :
これらの関数を詳細に見てみましょう。
数値カラム
アイリス分類器は総ての入力特徴のために tf.feature_column.numeric_column を呼び出します :
- がく片長さ
- がく片幅
- 花弁長さ
- 花弁幅
tf.numeric_column はオプション引数を提供しますが、次のように、引数なしでの tf.numeric_column の呼び出しは貴方のモデルへの入力としてデフォルトのデータ型 (tf.floar32) で数値を指定するための良い方法です :
# Defaults to a tf.float32 scalar. numeric_feature_column = tf.feature_column.numeric_column(key="SepalLength")
非デフォルト数値データ型を指定するためには、dtype 引数を使用します。例えば :
# Represent a tf.float64 scalar. numeric_feature_column = tf.feature_column.numeric_column(key="SepalLength", dtype=tf.float64)
デフォルトでは、数値カラムはシングル値 (スカラー) を作成します。他の shape を指定するためには shape 引数を使用します。例えば :
# Represent a 10-element vector in which each cell contains a tf.float32. vector_feature_column = tf.feature_column.numeric_column(key="Bowling", shape=10) # Represent a 10x5 matrix in which each cell contains a tf.float32. matrix_feature_column = tf.feature_column.numeric_column(key="MyMatrix", shape=[10,5])
Bucketized (= 分類された) カラム
しばしば、数字をモデルに直接的に供給することを望まず、しかし代わりにその値を数字の範囲を基に異なるカテゴリに分割します。それを行なうためには、bucketized column (= 分類されたカラム) を作成します。例えば、家が建てられた年を表わす生データを考えます。その年をスカラー数値カラムで表現する代わりに、年を次の4つのバケツに分割できるでしょう :
モデルはバケツを次のように表わすでしょう :
日付範囲 | Represented as… |
< 1960 | [1, 0, 0, 0] |
>= 1960 but < 1980 | [0, 1, 0, 0] |
>= 1980 but < 2000 | [0, 0, 1, 0] |
> 2000 | [0, 0, 0, 1] |
何故数字 – モデルへの完全に妥当な入力 – をカテゴリカルな値に分割することを望むのでしょう?そうですね、カテゴリ分類は単一の入力数字を 4-要素ベクトルに分割することに注意してください。従って、今ではモデルはただ一つではなく4つの個々の重みを学習可能です ; 4つの重みは1つの重みよりもより豊かなモデルを作成します。より重要なことは、分類はモデルに異なる年カテゴリ間で明確に識別することを可能にします、何故ならば要素の一つだけがセットされて (1) 他の3つの要素はクリアされる (0) からです。単一の数 (年) を入力として使用するだけのとき、モデルは線形関係を学習できるだけです。このように、分類はモデルに (モデルが学習するために利用できる) 追加の柔軟性を提供します。
次のコードは bucketized 特徴をどのように作成するかを示します :
# First, convert the raw input to a numeric column. numeric_feature_column = tf.feature_column.numeric_column("Year") # Then, bucketize the numeric column on the years 1960, 1980, and 2000. bucketized_feature_column = tf.feature_column.bucketized_column( source_column = numeric_feature_column, boundaries = [1960, 1980, 2000])
3-要素の境界ベクトルの指定は 4-要素の bucketized ベクトルを作成することに注意してください。
カテゴリカル恒等 (= identity) カラム
カテゴリカル恒等カラム は bucketized カラムの特殊なケースとして見ることができます。伝統的な bucketized カラムでは、各バケツは値の範囲を表します (例えば、1960 から 1979)。カテゴリカル恒等カラムでは、各バケツは 単一の、一意の整数を表します。例えば、整数範囲 [0, 4) を表現することを望むとします。つまり、整数 0, 1, 2, または 3 を表わすことを望みます。この場合、カテゴリカル恒等マッピングはこのように見えます :
bucketized カラムと同様に、モデルはカテゴリカル恒等カラムでは各クラスのための別々の重みを学習できます。例えば、product_class を表わすために文字列を使用する代わりに、各クラスを一意な整数値で表現しましょう。つまり :
- 0=”kitchenware”
- 1=”electronics”
- 2=”sport”
総ての tf.feature_column.categorical_column_with_identity はカテゴリカル恒等カラムを実装するためです。例えば :
# Create categorical output for an integer feature named "my_feature_b", # The values of my_feature_b must be >= 0 and < num_buckets identity_feature_column = tf.feature_column.categorical_column_with_identity( key='my_feature_b', num_buckets=4) # Values [0, 4) # In order for the preceding call to work, the input_fn() must return # a dictionary containing 'my_feature_b' as a key. Furthermore, the values # assigned to 'my_feature_b' must belong to the set [0, 4). def input_fn(): ... return ({ 'my_feature_a':[7, 9, 5, 2], 'my_feature_b':[3, 1, 2, 2] }, [Label_values])
カテゴリカル語彙カラム
文字列をモデルに直接的に入力することはできません。代わりに、最初に文字列を数値かカテゴリカル値にマップしなければなりません。カテゴリカル語彙カラムは文字列を one-hot ベクトルとして表わすための良い方法を提供します。例えば :
見て取れるように、カテゴリカル語彙カラムはカテゴリカル恒等カラムの enum バージョンの一種です。TensorFlow はカテゴリカル語彙カラムを作成するために2つの異なる関数を提供します。
- tf.feature_column.categorical_column_with_vocabulary_list
- tf.feature_column.categorical_column_with_vocabulary_file
categorical_column_with_vocabulary_list は明示的な語彙リストに基づいて各文字列を整数にマップします。例えば :
# Given input "feature_name_from_input_fn" which is a string, # create a categorical feature by mapping the input to one of # the elements in the vocabulary list. vocabulary_feature_column = tf.feature_column.categorical_column_with_vocabulary_list( key="a feature returned by input_fn()", vocabulary_list=["kitchenware", "electronics", "sports"])
上の関数はかなり straightforward ですが、それは本質的な欠点を持ちます。つまり、語彙リストが長いときには非常に多くのタイピングが必要となります。これらのような場合には、代わりに tf.feature_column.categorical_column_with_vocabulary_file を呼び出します、これは別のファイルに語彙単語を置くことを可能にします。例えば :
# Given input "feature_name_from_input_fn" which is a string, # create a categorical feature to our model by mapping the input to one of # the elements in the vocabulary file vocabulary_feature_column = tf.feature_column.categorical_column_with_vocabulary_file( key="a feature returned by input_fn()", vocabulary_file="product_class.txt", vocabulary_size=3)
product_class.txt は各語彙要素に対して 1 行を含むべきです。私達の場合には :
kitchenware electronics sports
ハッシュド・カラム
ここまでは、素朴に小さい数のカテゴリで作業しました。例えば、私達の product_class サンプルは3つだけのカテゴリを持ちます。けれどもしばしば、カテゴリの数は各語彙単語や整数のために個々のカテゴリを持つことが持つことができないほど非常に大きくなり得ます、何故ならばそれは非常に多くのメモリを消費するからです。これらのケースのためには、代わりに質問の方向を変えて尋ねることができます、「私の入力のためにどれだけの数のカテゴリを私は持つ意志がありますか?」実際に、tf.feature_column.categorical_column_with_hash_bucket 関数はカテゴリの数を指定することを可能にします。次の擬似コードのように、このタイプの特徴カラムに対してはモデルは modulo 演算子を使用して入力のハッシュ値を計算して、そしてそれを hash_bucket_size のカテゴリの一つに置きます :
# pseudocode feature_id = hash(raw_feature) % hash_buckets_size
feature_column を作成するためのコードはこのようなものです :
hashed_feature_column = tf.feature_column.categorical_column_with_hash_bucket( key = "some_feature", hash_buckets_size = 100) # The number of categories
この時点で、貴方は当然のように考えるかもしれません : 「これは馬鹿げている!」結局のところ、異なる入力値をより小さいカテゴリのセットに押し込めています。これは2つの多分関係ない入力が同じカテゴリにマップされ、結果的にニューラルネットワークに対しては同じものであることを意味します。次の図はこのジレンマを図示し、台所用品とスポーツ (用品) がカテゴリ (ハッシュ・バケツ) 12 に割り当てられることを示します :
機械学習における多くの直感に反する現象のように、実際にはハッシュ化はしばしば上手く動作することが判明しています。それはハッシュ・カテゴリがモデルにある種の分離を提供するからです。モデルは台所用品をスポーツ (用品) から更に分離するために追加の特徴を使用することができます。
交差 (= Crossed) カラム
特徴を単一の特徴に結合することは、特徴クロス としてより知られていますが、モデルに特徴の各結合に対する分離した重みを学習することを可能にします。
より具体的に、私達のモデルに Atlanta, GA の不動産価格を計算させることを望むと仮定します。この都市の不動産価格は場所に非常に依存して様々です。緯度と経度を別の特徴として表現することは不動産位置の依存性を識別するために大して有用ではありません ; けれども、緯度と経度の単一の特徴への交差は位置を正確に示します。Atlanta を 100x100 矩形セクションのグリッドとして表わすことを仮定し、緯度と経度の特徴クラスにより 10,000 セクションの各々を識別します。この特徴クロスはモデルを個々のセクションに関係した価格付け条件上で訓練することを可能にします、これは緯度と経度単独よりも遥かに強いシグナルです。
次の図は私達の計画を示し、赤色のテキスト内の都市のコーナーのための緯度 & 経度値を持ちます :
解法として、tf.feature_column.crossed_column 関数で前に見た bucketized_column の結合を使用します。
def make_dataset(latitude, longitude, labels): assert latitude.shape == longitude.shape == labels.shape features = {'latitude': latitude.flatten(), 'longitude': longitude.flatten()} labels=labels.flatten() return tf.data.Dataset.from_tensor_slices((features, labels)) # Bucketize the latitude and longitude usig the `edges` latitude_bucket_fc = tf.feature_column.bucketized_column( tf.feature_column.numeric_column('latitude'), list(atlanta.latitude.edges)) longitude_bucket_fc = tf.feature_column.bucketized_column( tf.feature_column.numeric_column('longitude'), list(atlanta.longitude.edges)) # Cross the bucketized columns, using 5000 hash bins. crossed_lat_lon_fc = tf.feature_column.crossed_column( [latitude_bucket_fc, longitude_bucket_fc], 5000) fc = [ latitude_bucket_fc, longitude_bucket_fc, crossed_lat_lon_fc] # Build and train the Estimator. est = tf.estimator.LinearRegressor(fc, ...)
次のどちらかから特徴クロスを作成できます :
- 特徴名; つまり、input_fn から返される辞書の名前です。
- 任意のカテゴリカル・カラム、categorical_column_with_hash_bucket を除きます (何故ならば crossed_column は入力をハッシュ化するからです )。
特徴カラム columns latitude_bucket_fc と longitude_bucket_fc が交差されるとき、TensorFlow は各サンプルに対して (latitude_fc, longitude_fc) ペアを作成します。これは次のように可能な完全なグリッドを生成するでしょう :
(0,0), (0,1)... (0,99) (1,0), (1,1)... (1,99) ... ... ... (99,0), (99,1)...(99, 99)
但し、完全なグリッドは制限された語彙から成る入力についてだけ扱いやすいでしょう。この、本質的に巨大な入力テーブルを構築する代わりに、crossed_column は hash_bucket_size 引数で要求された数を構築するだけです。特徴カラムは入力のタプル上でハッシュ関数、続いて hash_bucket_size を持つ modulo 演算子を実行することによりサンプルをインデックスに割り当てます。前に議論したように、ハッシュと modulo 関数の遂行はカテゴリの数を制限しますが、カテゴリ衝突を引き起こす可能性があります ; つまり、複数の特徴 (緯度、経度) クロスが同じハッシュ・バケツに終わるでしょう。けれども実際には、特徴クロスは依然として貴方のモデルの学習能力に本質的な値を追加します。
幾分直感に反しますが、特徴クロスを作成するとき、典型的には (先のコードスニペットのように) 貴方のモデルに元の (uncrossed) 特徴を依然として含めるべきです。独立した緯度と経度特徴はモデルに (交差特徴ではハッシュ・コリージョンが発生した) サンプル間の識別を手助けします。
インジケータと埋め込みカラム
インジケータ・カラムと埋め込みカラムは特徴上で直接的には決して動作しませんが、代わりに入力としてカテゴリカル・カラムを取ります。
インジケータ・カラムを使用するとき、私達は TensorFlow にカテゴリカル product_class サンプルで見たことを正確に行なうことを伝えています。つまり、インジケータ・カラム は各カテゴリを one-hot ベクトルの要素として扱い、そこでは適合するカテゴリが値 1 で残りが 0 を持ちます :
tf.feature_column.indicator_column を呼び出すことによりインジケータ・カラムをどのように作成するかがここにあります :
categorical_column = ... # Create any type of categorical column. # Represent the categorical column as an indicator column. indicator_column = tf.feature_column.indicator_column(categorical_column)
さて、丁度3つの可能なクラスを持つ代わりに、100 万を持つことを仮定します。あるいは多分 10 億です。多くの理由で、カテゴリ数が巨大に増大するにつれて、インジケータ・カラムを使用してニューラルネットワークを訓練することは実行不可能になります。
この制限を越えるために埋め込みカラムを使用できます。データを多くの次元の one-hot ベクトルとして表わす代わりに、埋め込みカラムはそのデータをより低い次元のデータ、各セルが、単に 0 か 1 ではなく任意の数を含むことができる通常のベクトルとして表します。総てのセルについてより豊かなパレットの数を許すことにより、埋め込みカラムはインジケータ・カラムよりも遥かにより少ないセルを含みます。
インジケータと埋め込みカラムを比較するサンプルを見てみましょう。私達の入力サンプルが 81 単語だけの制限されたパレットから異なる単語から成ることを仮定します。更にデータセットは 4 つの分かれたサンプルとして次の入力単語を提供することを仮定します :
- "dog"
- "spoon"
- "scissors"
- "guitar"
その場合、次の図が埋め込みカラムまたはインジケータ・カラムのための処理パスを示します。
サンプルが処理されるとき、categorical_column_with... 関数の一つがサンプル文字列を数字のカテゴリカル値にマップします。例えば、関数は "spoon" を [32] にマップします。(32 は私達の想像に由来します — 実際の値はマッピング関数に依存します。) それからこれらの数字のカテゴリカル値を次の2つの方法のいずれかで表現可能です :
- インジケータ・カラム。関数は各数字のカテゴリカル値を 81-要素ベクトルに変換し (何故ならば私達のパレットは 81 単語から成ります)、カテゴリカル値 (0, 32, 79, 80) のインデックスに 1 を他の総ての位置には 0 を置きます。
- 埋め込みカラム。関数は検索テーブルへのインデックスとして数字のカテゴリカル値 (0, 32, 79, 80) を使用します。その検索テーブルの各スロットは 3-要素ベクトルを含みます。
埋め込みベクトル内の値はどのように魔法のように割り当てられるのでしょう?実際には、割り当ては訓練中に起こります。つまり、貴方の問題を解くためにモデルは貴方の入力の数字のカテゴリカル値を埋め込みベクトル値にマップするための最善の方法を学習します。埋め込みカラムは貴方のモデルの性能を増大します、何故ならば埋め込みベクトルは訓練データからのカテゴリ間の新しい関係を学習するからです。
私達のサンプルでは埋め込みベクトルのサイズ 3 なのでしょう?次の「式」は埋め込み次元の数についての一般的なおおまかなルールを提供します :
embedding_dimensions = number_of_categories**0.25
つまり、埋め込みベクトル次元はカテゴリ数の四乗根であるべきです。このサンプルの語彙サイズは 81 ですから、次元に推奨数は 3 です :
3 = 81**0.25
これは一般的なガイドラインに過ぎないことに注意してください ; 貴方が好きなように埋め込み次元の数を設定できます。
次のスニペットで示されるように embedding_column を作成するために tf.feature_column.embedding_column を呼び出します :
categorical_column = ... # Create any categorical column # Represent the categorical column as an embedding column. # This means creating a one-hot vector with one element for each category. embedding_column = tf.feature_column.embedding_column( categorical_column=categorical_column, dimension=dimension_of_embedding_vector)
埋め込みは機械学習内で本質的なトピックです。この情報はそれらを特徴カラムとして使用し始めるためだけのものです。
Estimator に特徴カラムを渡す
次のリストが示すように、総てのタイプの feature_columns 引数 (群) を総ての Estimator が許可するわけではありません :
- LinearClassifier と LinearRegressor: 特徴カラムの総てのタイプを受け取ります。
- DNNClassifier and DNNRegressor: dense カラムを受け取るだけです。他のカラム・タイプは indicator_column か embedding_column にラップされなければなりません。
- DNNLinearCombinedClassifier と DNNLinearCombinedRegressor:
- linear_feature_columns 引数は任意の特徴カラム・タイプを受け取ります。
- dnn_feature_columns 引数は dense カラムを受け取るだけです。
以上