構造化されたデータの分類

View on TensorFlow.org Run in Google Colab View source on GitHub

このチュートリアルでは、(例えばCSVファイルに保存された表形式データのような)構造化されたデータをどうやって分類するかを示します。ここでは、モデルの定義にKerasを、feature columnsをCSVファイルの列をモデルを訓練するための特徴量にマッピングするための橋渡し役として使用します。このチュートリアルには、下記のことを行うコードすべてが含まれています。

  • Pandasを使用したCSVファイルの読み込み
  • tf.dataを使用して行データをシャッフルし、バッチ化するための入力パイプライン構築
  • feature columnsを使ったCSVの列のモデル訓練用の特徴量へのマッピング
  • Kerasを使ったモデルの構築と、訓練及び評価

データセット

ここでは、Cleveland Clinic Foundation for Heart Diseaseが提供している小さなデータセットを使用します。このCSVファイルには数百行が含まれています。行が患者を、列がその属性を表します。この情報を使用して、患者が心臓疾患を持っているかを予測します。このデータセットの場合には二値分類タスクとなります。

下記はこのデータセットの說明です。数値列とカテゴリー列があることに注目してください。

說明 特徴量の型 データ型
Age 年齢 数値型 整数
Sex (1 = 男性; 0 = 女性) カテゴリー型 整数
CP 胸痛のタイプ (0, 1, 2, 3, 4) カテゴリー型 整数
Trestbpd 安静時血圧 (単位:mm Hg 入院時) 数値型 整数
Chol 血清コレステロール 単位:mg/dl 数値型 整数
FBS (空腹時血糖 > 120 mg/dl) (1 = 真; 0 = 偽) カテゴリー型 整数
RestECG 安静時心電図の診断結果 (0, 1, 2) カテゴリー型 整数
Thalach 最大心拍数 数値型 整数
Exang 運動誘発狭心症 (1 = はい; 0 = いいえ) カテゴリー型 整数
Oldpeak 安静時と比較した運動時のST低下 数値型 整数
Slope ピーク運動STセグメントの勾配 数値型 浮動小数点数
CA 蛍光透視法によって着色された主要血管の数(0−3) 数値型 整数
Thal 3 = 正常; 6 = 固定欠陥; 7 = 可逆的欠陥 カテゴリー型 文字列
Target 心臓疾患の診断 (1 = 真; 0 = 偽) 分類 整数

TensorFlow他ライブラリのインポート

!pip install -q sklearn
from __future__ import absolute_import, division, print_function, unicode_literals

import numpy as np
import pandas as pd

import tensorflow as tf

from tensorflow import feature_column
from tensorflow.keras import layers
from sklearn.model_selection import train_test_split

Pandasを使ったデータフレーム作成

Pandasは、構造化データの読み込みや操作のための便利なユーティリティを持つPythonのライブラリです。ここでは、Pandasを使ってURLからデータをダウンロードし、データフレームに読み込みます。

URL = 'https://storage.googleapis.com/applied-dl/heart.csv'
dataframe = pd.read_csv(URL)
dataframe.head()

データフレームを、訓練用、検証用、テスト用に分割

ダウンロードしたデータセットは1つのCSVファイルです。これを、訓練用、検証用、テスト用のデータセットに分割します。

train, test = train_test_split(dataframe, test_size=0.2)
train, val = train_test_split(train, test_size=0.2)
print(len(train), 'train examples')
print(len(val), 'validation examples')
print(len(test), 'test examples')
193 train examples
49 validation examples
61 test examples

tf.dataを使った入力パイプラインの構築

次に、tf.dataを使ってデータフレームをラップします。こうすることで、feature columns をPandasデータフレームの列をモデル訓練用の特徴量へのマッピングするための橋渡し役として使うことができます。(メモリに収まらないぐらいの)非常に大きなCSVファイルを扱う場合には、tf.dataを使ってディスクから直接CSVファイルを読み込むことになります。この方法は、このチュートリアルでは扱いません。

# Pandasデータフレームからtf.dataデータセットを作るためのユーティリティメソッド
def df_to_dataset(dataframe, shuffle=True, batch_size=32):
  dataframe = dataframe.copy()
  labels = dataframe.pop('target')
  ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels))
  if shuffle:
    ds = ds.shuffle(buffer_size=len(dataframe))
  ds = ds.batch(batch_size)
  return ds
batch_size = 5 # デモ用として小さなバッチサイズを使用
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)

入力パイプラインを理解する

入力パイプラインを構築したので、それが返すデータのフォーマットを見るために呼び出してみましょう。出力を読みやすくするためにバッチサイズを小さくしてあります。

for feature_batch, label_batch in train_ds.take(1):
  print('Every feature:', list(feature_batch.keys()))
  print('A batch of ages:', feature_batch['age'])
  print('A batch of targets:', label_batch )
Every feature: ['chol', 'thal', 'fbs', 'age', 'cp', 'exang', 'sex', 'thalach', 'trestbps', 'ca', 'oldpeak', 'slope', 'restecg']
A batch of ages: tf.Tensor([45 58 68 39 62], shape=(5,), dtype=int32)
A batch of targets: tf.Tensor([0 0 0 1 1], shape=(5,), dtype=int32)

データセットが(データフレームにある)列名からなるディクショナリを返すことがわかります。列名から、データフレームの行に含まれる列の値が得られます。

feature columnsの様々な型の例

TensorFlowにはたくさんの型のfeature columnがあります。このセクションでは、いくつかの型のfeature columnsを作り、データフレームの列をどのように変換しているかを示します。

# いくつかの型のfeature columnsを例示するためこのバッチを使用する
example_batch = next(iter(train_ds))[0]
# feature columnsを作りデータのバッチを変換する
# ユーティリティメソッド
def demo(feature_column):
  feature_layer = layers.DenseFeatures(feature_column)
  print(feature_layer(example_batch).numpy())

数値コラム

feature columnsの出力はモデルへの入力になります(上記で定義したdemo関数を使うと、データフレームの列がどのように変換されるかをつぶさに見ることができます)。数値コラムは、最も単純な型のコラムです。数値コラムは実数特徴量を表現するのに使われます。このコラムを使う場合、モデルにはデータフレームの列の値がそのまま渡されます。

age = feature_column.numeric_column("age")
demo(age)
[[62.]
 [58.]
 [41.]
 [57.]
 [47.]]

心臓疾患データセットでは、データフレームのほとんどの列が数値型です。

バケット化コラム

数値をそのままモデルに入力するのではなく、値の範囲に基づいた異なるカテゴリーに分割したいことがあります。例えば、人の年齢を表す生データを考えてみましょう。バケット化コラムを使うと年齢を数値コラムとして表現するのではなく、年齢をいくつかのバケットに分割できます。下記のワンホット値が、各行がどの年齢範囲にあるかを表していることに注目してください。

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

カテゴリー型コラム

このデータセットでは、Thalは('fixed'、'normal'、'reversible'のような)文字列として表現されています。文字列を直接モデルに入力することはできません。まず、文字列を数値にマッピングする必要があります。categorical vocabulary コラムを使うと、(上記で示した年齢バケットのように)文字列をワンホットベクトルとして表現することができます。カテゴリーを表す語彙(vocabulary)はcategorical_column_with_vocabulary_listを使ってリストで渡すか、categorical_column_with_vocabulary_fileを使ってファイルから読み込むことができます。

thal = feature_column.categorical_column_with_vocabulary_list(
      'thal', ['fixed', 'normal', 'reversible'])

thal_one_hot = feature_column.indicator_column(thal)
demo(thal_one_hot)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4273: IndicatorColumn._variable_shape (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4328: VocabularyListCategoricalColumn._num_buckets (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
[[0. 1. 0.]
 [0. 0. 1.]
 [0. 1. 0.]
 [0. 0. 1.]
 [0. 1. 0.]]

より複雑なデータセットでは、たくさんの列がカテゴリー型(例えば文字列)であることでしょう。feature columns はカテゴリー型データを扱う際に最も役に立ちます。このデータセットでは、カテゴリー型コラムは1つだけですが、他のデータセットを扱う際に使用できるいくつかの重要な型のfeature columnsを紹介するために、この列を使用することにします。

埋め込み型コラム

数種類の候補となる文字列ではなく、カテゴリー毎に数千(あるいはそれ以上)の値があるとしましょう。カテゴリーの数が多くなってくると、様々な理由から、ワンホットエンコーディングを使ってニューラルネットワークを訓練することが難しくなります。埋込み型コラムを使うと、こうした制約を克服することが可能です。埋込み型コラムは、データを多次元のワンホットベクトルとして表すのではなく、セルの値が0か1かだけではなく、どんな数値でもとれるような密な低次元ベクトルとして表現します。埋め込みのサイズ(下記の例では8)は、チューニングが必要なパラメータです。

キーポイント:カテゴリー型コラムがたくさんの選択肢を持つ場合、埋め込み型コラムを使用することが最善の方法です。ここでは例を一つ示しますので、今後様々なデータセットを扱う際には、この例を参考にしてください。

# この埋込み型コラムの入力は、先程作成したカテゴリ型コラムであることに注意
thal_embedding = feature_column.embedding_column(thal, dimension=8)
demo(thal_embedding)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:353: Layer.add_variable (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
Please use `layer.add_weight` method instead.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/ops/embedding_ops.py:802: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
[[-0.29959285 -0.23672958  0.00703682 -0.08282246  0.3523626  -0.5325947
   0.06156043 -0.03468445]
 [-0.13846171  0.16489413 -0.23681253 -0.31470183  0.3283862  -0.36614552
  -0.16393359 -0.49591506]
 [-0.29959285 -0.23672958  0.00703682 -0.08282246  0.3523626  -0.5325947
   0.06156043 -0.03468445]
 [-0.13846171  0.16489413 -0.23681253 -0.31470183  0.3283862  -0.36614552
  -0.16393359 -0.49591506]
 [-0.29959285 -0.23672958  0.00703682 -0.08282246  0.3523626  -0.5325947
   0.06156043 -0.03468445]]

ハッシュ化特徴コラム

値の種類が多いカテゴリー型コラムを表現するもう一つの方法が、categorical_column_with_hash_bucketを使う方法です。このfeature columnは文字列をエンコードするために入力のハッシュ値を計算し、hash_bucket_size個のバケットの中から1つを選択します。このコラムを使用する場合には、語彙を用意する必要はありません。また、スペースの節約のために、実際のカテゴリー数に比べて極めて少ないバケット数を選択することも可能です。

キーポイント:この手法の重要な欠点の一つは、異なる文字列が同じバケットにマッピングされるというハッシュ値の衝突が起きることです。実務上は、データセットによっては、この問題を無視できることがあります。

thal_hashed = feature_column.categorical_column_with_hash_bucket(
      'thal', hash_bucket_size=1000)
demo(feature_column.indicator_column(thal_hashed))
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4328: HashedCategoricalColumn._num_buckets (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
[[0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]]

クロスフィーチャーコラム

複数の特徴量をまとめて1つの特徴量にする、フィーチャークロスとして知られている手法は、モデルが特徴量の組み合わせの一つ一つに別々の重みを学習することを可能にします。ここでは年齢とThalをクロスさせて新しい特徴量を作ってみます。交差列(crossed_column)が、起こりうるすべての組み合わせ全体のテーブル(これは非常に大きくなる可能性があります)を作るものではないことに注意してください。クロスフィーチャーコラムは、代わりにバックエンドとしてハッシュ化コラムを使用しているため、テーブルの大きさを選択することができます。

crossed_feature = feature_column.crossed_column([age_buckets, thal], hash_bucket_size=1000)
demo(feature_column.indicator_column(crossed_feature))
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4328: CrossedColumn._num_buckets (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
[[0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]]

使用するコラムを選択する

これまで、いくつかのfeature columnの使い方を見てきました。いよいよモデルの訓練にそれらを使用することにします。このチュートリアルの目的は、feature columnsを使うのに必要な完全なコード(いわば力学)を示すことです。以下ではモデルを訓練するための列を適当に選びました。

キーポイント:正確なモデルを構築するのが目的である場合には、できるだけ大きなデータセットを使用して、どの特徴量を含めるのがもっとも意味があるのかや、それらをどう表現したらよいかを、慎重に検討してください。

feature_columns = []

# 数値コラム
for header in ['age', 'trestbps', 'chol', 'thalach', 'oldpeak', 'slope', 'ca']:
  feature_columns.append(feature_column.numeric_column(header))

# バケット化コラム
age_buckets = feature_column.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])
feature_columns.append(age_buckets)

# インジケーター(カテゴリー型)コラム
thal = feature_column.categorical_column_with_vocabulary_list(
      'thal', ['fixed', 'normal', 'reversible'])
thal_one_hot = feature_column.indicator_column(thal)
feature_columns.append(thal_one_hot)

# 埋め込み型コラム
thal_embedding = feature_column.embedding_column(thal, dimension=8)
feature_columns.append(thal_embedding)

# クロスフィーチャーコラム
crossed_feature = feature_column.crossed_column([age_buckets, thal], hash_bucket_size=1000)
crossed_feature = feature_column.indicator_column(crossed_feature)
feature_columns.append(crossed_feature)

特徴量層の構築

feature columnsを定義し終わったので、次にDenseFeatures層を使ってKerasモデルへの入力とします。

feature_layer = tf.keras.layers.DenseFeatures(feature_columns)

これまでは、feature columnsの働きを見るため、小さなバッチサイズを使ってきました。ここではもう少し大きなバッチサイズの新しい入力パイプラインを作ります。

batch_size = 32
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)

モデルの構築、コンパイルと訓練

model = tf.keras.Sequential([
  feature_layer,
  layers.Dense(128, activation='relu'),
  layers.Dense(128, activation='relu'),
  layers.Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

model.fit(train_ds, 
          validation_data=val_ds, 
          epochs=5)
Epoch 1/5
7/7 [==============================] - 2s 284ms/step - loss: 1.9196 - accuracy: 0.5181 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/5
7/7 [==============================] - 0s 11ms/step - loss: 1.0222 - accuracy: 0.5389 - val_loss: 0.6590 - val_accuracy: 0.7755
Epoch 3/5
7/7 [==============================] - 0s 11ms/step - loss: 0.9718 - accuracy: 0.6995 - val_loss: 0.6603 - val_accuracy: 0.7347
Epoch 4/5
7/7 [==============================] - 0s 10ms/step - loss: 0.6354 - accuracy: 0.7202 - val_loss: 0.6012 - val_accuracy: 0.7551
Epoch 5/5
7/7 [==============================] - 0s 10ms/step - loss: 0.8104 - accuracy: 0.7098 - val_loss: 0.9684 - val_accuracy: 0.4082

<tensorflow.python.keras.callbacks.History at 0x7f24d04cbc50>
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)
2/2 [==============================] - 0s 232ms/step - loss: 1.1807 - accuracy: 0.3607
Accuracy 0.36065573

キーポイント:一般的に、ディープラーニングが最良の結果となるのは、もっと大きくて、もっと複雑なデータセットです。この例のように小さなデータセットを使用する際には、強固なベースラインとして、決定木やランダムフォレストを使うことをおすすめします。このチュートリアルの目的は、訓練により正確なモデルを得ることではなく、構造化データの使い方をデモすることです。今後ご自分のデータセットに取り組まれる際の出発点として、これらのコードをお使いください。

次のステップ

構造化データの分類について更に多くのことを学ぶためには、自分自身で試してみることです。別のデータセットを見つけ、上記と同様のコードを使って、それを分類するモデルを訓練してみてください。正解率を上げるためには、モデルにどの特徴量を含めたらよいかや、その特徴量をどのように表現すべきかをじっくり考えてください。