Klasyfikuj uporządkowane dane za pomocą kolumn funkcji

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Ten samouczek pokazuje, jak klasyfikować dane strukturalne (np. dane tabelaryczne w pliku CSV). Użyjemy Keras do zdefiniowania modelu, a tf.feature_column jako pomostu do mapowania kolumn w pliku CSV do funkcji używanych do uczenia modelu. Ten samouczek zawiera kompletny kod do:

  • Załaduj plik CSV za pomocą Pandy .
  • Zbuduj potok wejściowy do partii i tasowania wierszy przy użyciu tf.data .
  • Odwzoruj kolumny w pliku CSV do funkcji używanych do trenowania modelu za pomocą kolumn funkcji.
  • Twórz, trenuj i oceniaj model za pomocą Keras.

Zbiór danych

Użyjemy uproszczonej wersji zestawu danych PetFinder. W pliku CSV znajduje się kilka tysięcy wierszy. Każdy wiersz opisuje zwierzaka, a każda kolumna opisuje atrybut. Wykorzystamy te informacje, aby przewidzieć prędkość, z jaką zwierzę zostanie adoptowane.

Poniżej znajduje się opis tego zbioru danych. Zauważ, że istnieją zarówno kolumny liczbowe, jak i kategorialne. Istnieje wolna kolumna tekstowa, której nie będziemy używać w tym samouczku.

Kolumna Opis Typ funkcji Typ danych
Rodzaj Rodzaj zwierzęcia (pies, kot) Kategoryczny strunowy
Wiek Wiek zwierzaka Liczbowy liczba całkowita
Rasa1 Podstawowa rasa zwierzaka Kategoryczny strunowy
Kolor1 Kolor 1 zwierzaka Kategoryczny strunowy
Kolor2 Kolor 2 zwierzaka Kategoryczny strunowy
DojrzałośćRozmiar Rozmiar w terminie dojrzałości Kategoryczny strunowy
Długość futra Długość futra Kategoryczny strunowy
Zaszczepione Zwierzę zostało zaszczepione Kategoryczny strunowy
Wysterylizowany Zwierzę zostało wysterylizowane Kategoryczny strunowy
Zdrowie Stan zdrowia Kategoryczny strunowy
Opłata Opłata adopcyjna Liczbowy liczba całkowita
Opis Opis profilu tego zwierzaka Tekst strunowy
ZdjęcieAmt Wszystkie przesłane zdjęcia tego zwierzaka Liczbowy liczba całkowita
Przyjęcie Szybkość Szybkość adopcji Klasyfikacja liczba całkowita

Importuj TensorFlow i inne biblioteki

pip install sklearn
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

Użyj Pand do stworzenia ramki danych

Pandas to biblioteka Pythona z wieloma pomocnymi narzędziami do ładowania i pracy z danymi strukturalnymi. Użyjemy Pand do pobrania zestawu danych z adresu URL i załadowania go do ramki danych.

import pathlib

dataset_url = 'http://storage.googleapis.com/download.tensorflow.org/data/petfinder-mini.zip'
csv_file = 'datasets/petfinder-mini/petfinder-mini.csv'

tf.keras.utils.get_file('petfinder_mini.zip', dataset_url,
                        extract=True, cache_dir='.')
dataframe = pd.read_csv(csv_file)
Downloading data from http://storage.googleapis.com/download.tensorflow.org/data/petfinder-mini.zip
1671168/1668792 [==============================] - 0s 0us/step
1679360/1668792 [==============================] - 0s 0us/step
dataframe.head()

Utwórz zmienną docelową

Zadaniem w pierwotnym zestawie danych jest przewidzenie szybkości, z jaką zwierzę zostanie adoptowane (np. w pierwszym tygodniu, pierwszym miesiącu, pierwszych trzech miesiącach itd.). Uprośćmy to w naszym samouczku. Tutaj przekształcimy to w problem klasyfikacji binarnej i po prostu przewidzimy, czy zwierzę zostało adoptowane, czy nie.

Po zmodyfikowaniu kolumny etykiety 0 będzie oznaczać, że zwierzę nie zostało adoptowane, a 1 będzie oznaczać, że było.

# In the original dataset "4" indicates the pet was not adopted.
dataframe['target'] = np.where(dataframe['AdoptionSpeed']==4, 0, 1)

# Drop un-used columns.
dataframe = dataframe.drop(columns=['AdoptionSpeed', 'Description'])

Podziel ramkę danych na pociąg, walidację i test

Pobrany przez nas zbiór danych był jednym plikiem CSV. Podzielimy to na zestawy treningowe, walidacyjne i testowe.

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')
7383 train examples
1846 validation examples
2308 test examples

Utwórz potok wejściowy za pomocą tf.data

Następnie owiniemy ramki danych za pomocą tf.data . Umożliwi nam to użycie kolumn funkcji jako pomostu do mapowania kolumn w ramce danych Pandy na funkcje używane do uczenia modelu. Gdybyśmy pracowali z bardzo dużym plikiem CSV (tak dużym, że nie mieści się w pamięci), użylibyśmy tf.data do bezpośredniego odczytu z dysku. Nie jest to omówione w tym samouczku.

# A utility method to create a tf.data dataset from a Pandas Dataframe
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 # A small batch sized is used for demonstration purposes
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)

Zrozum potok wejściowy

Teraz, gdy stworzyliśmy potok wejściowy, wywołajmy go, aby zobaczyć format danych, które zwraca. Użyliśmy małego rozmiaru partii, aby dane wyjściowe były czytelne.

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: ['Type', 'Age', 'Breed1', 'Gender', 'Color1', 'Color2', 'MaturitySize', 'FurLength', 'Vaccinated', 'Sterilized', 'Health', 'Fee', 'PhotoAmt']
A batch of ages: tf.Tensor([ 6  2 36  2  2], shape=(5,), dtype=int64)
A batch of targets: tf.Tensor([1 1 1 1 1], shape=(5,), dtype=int64)

Widzimy, że zestaw danych zwraca słownik nazw kolumn (z ramki danych), które mapują wartości kolumn z wierszy w ramce danych.

Zademonstruj kilka typów kolumn funkcji

TensorFlow udostępnia wiele typów kolumn funkcji. W tej sekcji utworzymy kilka typów kolumn funkcji i zademonstrujemy, w jaki sposób przekształcają one kolumnę z ramki danych.

# We will use this batch to demonstrate several types of feature columns
example_batch = next(iter(train_ds))[0]
# A utility method to create a feature column
# and to transform a batch of data
def demo(feature_column):
  feature_layer = layers.DenseFeatures(feature_column)
  print(feature_layer(example_batch).numpy())

Kolumny numeryczne

Dane wyjściowe kolumny funkcji stają się danymi wejściowymi modelu (korzystając z funkcji demonstracyjnej zdefiniowanej powyżej, będziemy mogli dokładnie zobaczyć, jak każda kolumna z ramki danych jest przekształcana). Kolumna liczbowa to najprostszy typ kolumny. Służy do reprezentowania naprawdę cenionych cech. Podczas korzystania z tej kolumny Twój model otrzyma niezmienioną wartość kolumny z ramki danych.

photo_count = feature_column.numeric_column('PhotoAmt')
demo(photo_count)
[[2.]
 [4.]
 [4.]
 [1.]
 [2.]]

W zestawie danych PetFinder większość kolumn z ramki danych jest kategoryczna.

Kolumny w zasobnikach

Często nie chcesz wprowadzać liczby bezpośrednio do modelu, ale zamiast tego podzielić jej wartość na różne kategorie w oparciu o zakresy liczbowe. Rozważ surowe dane, które reprezentują wiek osoby. Zamiast przedstawiać wiek jako kolumnę liczbową, możemy podzielić wiek na kilka segmentów za pomocą kolumny podzielonej na segmenty . Zwróć uwagę, że poniższe gorące wartości opisują, do którego przedziału wiekowego pasuje każdy wiersz.

age = feature_column.numeric_column('Age')
age_buckets = feature_column.bucketized_column(age, boundaries=[1, 3, 5])
demo(age_buckets)
[[0. 0. 0. 1.]
 [0. 1. 0. 0.]
 [0. 0. 0. 1.]
 [0. 0. 1. 0.]
 [0. 1. 0. 0.]]

Kolumny kategorialne

W tym zestawie danych typ jest reprezentowany jako ciąg (np. „Pies” lub „Kot”). Nie możemy podawać ciągów bezpośrednio do modelu. Zamiast tego musimy najpierw zmapować je na wartości liczbowe. Kolumny słownictwa kategorialnego umożliwiają reprezentowanie ciągów jako jednego gorącego wektora (podobnie jak widzieliśmy powyżej w przypadku grup wiekowych). Słownik może być przekazany jako lista przy użyciu categorical_column_with_vocabulary_list lub załadowany z pliku przy użyciu categorical_column_with_vocabulary_file .

animal_type = feature_column.categorical_column_with_vocabulary_list(
      'Type', ['Cat', 'Dog'])

animal_type_one_hot = feature_column.indicator_column(animal_type)
demo(animal_type_one_hot)
[[1. 0.]
 [1. 0.]
 [1. 0.]
 [1. 0.]
 [0. 1.]]

Osadzanie kolumn

Załóżmy, że zamiast mieć tylko kilka możliwych ciągów, mamy tysiące (lub więcej) wartości na kategorię. Z wielu powodów, wraz ze wzrostem liczby kategorii, trenowanie sieci neuronowej przy użyciu kodowania jednokrotnego staje się niewykonalne. Aby przezwyciężyć to ograniczenie, możemy użyć kolumny osadzania. Zamiast przedstawiać dane jako jeden gorący wektor o wielu wymiarach, kolumna osadzania przedstawia te dane jako niskowymiarowy, gęsty wektor, w którym każda komórka może zawierać dowolną liczbę, a nie tylko 0 lub 1. Rozmiar osadzania ( 8, w poniższym przykładzie) to parametr, który należy dostroić.

# Notice the input to the embedding column is the categorical column
# we previously created
breed1 = feature_column.categorical_column_with_vocabulary_list(
      'Breed1', dataframe.Breed1.unique())
breed1_embedding = feature_column.embedding_column(breed1, dimension=8)
demo(breed1_embedding)
[[-0.22380038 -0.09379731  0.21349265  0.33451992 -0.49730566  0.05174963
   0.2668497   0.27391028]
 [-0.5484653  -0.03492585  0.05648395 -0.09792244  0.02530896 -0.15477926
  -0.10695003 -0.45474145]
 [-0.22380038 -0.09379731  0.21349265  0.33451992 -0.49730566  0.05174963
   0.2668497   0.27391028]
 [ 0.10050306  0.43513173  0.375823    0.5652766   0.40925583 -0.03928828
   0.4901914   0.20637617]
 [-0.2319875  -0.21874283  0.12272807  0.33345345 -0.4563055   0.21609035
  -0.2410521   0.4736915 ]]

Zaszyfrowane kolumny funkcji

Innym sposobem reprezentowania kolumny kategorialnej z dużą liczbą wartości jest użycie categorical_column_with_hash_bucket . Ta kolumna funkcji oblicza wartość hash danych wejściowych, a następnie wybiera jeden z hash_bucket_size do zakodowania ciągu. Korzystając z tej kolumny, nie musisz podawać słownictwa i możesz wybrać, aby liczba hash_buckets była znacznie mniejsza niż liczba rzeczywistych kategorii, aby zaoszczędzić miejsce.

breed1_hashed = feature_column.categorical_column_with_hash_bucket(
      'Breed1', hash_bucket_size=10)
demo(feature_column.indicator_column(breed1_hashed))
[[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. 1.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]]

Skrzyżowane kolumny funkcji

Łączenie cech w jedną cechę, lepiej znaną jako krzyżyki cech , umożliwia modelowi poznanie oddzielnych wag dla każdej kombinacji cech. Tutaj stworzymy nową cechę, czyli skrzyżowanie Wieku i Typu. Zauważ, że crossed_column nie tworzy pełnej tabeli wszystkich możliwych kombinacji (które mogą być bardzo duże). Zamiast tego jest wspierany przez hashed_column , dzięki czemu możesz wybrać wielkość tabeli.

crossed_feature = feature_column.crossed_column([age_buckets, animal_type], hash_bucket_size=10)
demo(feature_column.indicator_column(crossed_feature))
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [1. 0. 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. 0.]]

Wybierz kolumny, których chcesz użyć

Widzieliśmy, jak korzystać z kilku typów kolumn funkcji. Teraz użyjemy ich do trenowania modelu. Celem tego samouczka jest pokazanie pełnego kodu (np. mechaniki) potrzebnego do pracy z kolumnami funkcji. Wybraliśmy kilka kolumn, aby arbitralnie trenować nasz model poniżej.

feature_columns = []

# numeric cols
for header in ['PhotoAmt', 'Fee', 'Age']:
  feature_columns.append(feature_column.numeric_column(header))
# bucketized cols
age = feature_column.numeric_column('Age')
age_buckets = feature_column.bucketized_column(age, boundaries=[1, 2, 3, 4, 5])
feature_columns.append(age_buckets)
# indicator_columns
indicator_column_names = ['Type', 'Color1', 'Color2', 'Gender', 'MaturitySize',
                          'FurLength', 'Vaccinated', 'Sterilized', 'Health']
for col_name in indicator_column_names:
  categorical_column = feature_column.categorical_column_with_vocabulary_list(
      col_name, dataframe[col_name].unique())
  indicator_column = feature_column.indicator_column(categorical_column)
  feature_columns.append(indicator_column)
# embedding columns
breed1 = feature_column.categorical_column_with_vocabulary_list(
      'Breed1', dataframe.Breed1.unique())
breed1_embedding = feature_column.embedding_column(breed1, dimension=8)
feature_columns.append(breed1_embedding)
# crossed columns
age_type_feature = feature_column.crossed_column([age_buckets, animal_type], hash_bucket_size=100)
feature_columns.append(feature_column.indicator_column(age_type_feature))

Utwórz warstwę obiektów

Teraz, gdy zdefiniowaliśmy nasze kolumny funkcji, użyjemy warstwy DenseFeatures , aby wprowadzić je do naszego modelu Keras.

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

Wcześniej używaliśmy małego rozmiaru partii, aby zademonstrować, jak działają kolumny funkcji. Tworzymy nowy potok wejściowy o większym rozmiarze partii.

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)

Twórz, kompiluj i trenuj model

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

model.compile(optimizer='adam',
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'])

model.fit(train_ds,
          validation_data=val_ds,
          epochs=10)
Epoch 1/10
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'Type': <tf.Tensor 'IteratorGetNext:11' shape=(None,) dtype=string>, 'Age': <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=int64>, 'Breed1': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>, 'Gender': <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=string>, 'Color1': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'Color2': <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>, 'MaturitySize': <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>, 'FurLength': <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>, 'Vaccinated': <tf.Tensor 'IteratorGetNext:12' shape=(None,) dtype=string>, 'Sterilized': <tf.Tensor 'IteratorGetNext:10' shape=(None,) dtype=string>, 'Health': <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=string>, 'Fee': <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=int64>, 'PhotoAmt': <tf.Tensor 'IteratorGetNext:9' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'Type': <tf.Tensor 'IteratorGetNext:11' shape=(None,) dtype=string>, 'Age': <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=int64>, 'Breed1': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>, 'Gender': <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=string>, 'Color1': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'Color2': <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>, 'MaturitySize': <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>, 'FurLength': <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>, 'Vaccinated': <tf.Tensor 'IteratorGetNext:12' shape=(None,) dtype=string>, 'Sterilized': <tf.Tensor 'IteratorGetNext:10' shape=(None,) dtype=string>, 'Health': <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=string>, 'Fee': <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=int64>, 'PhotoAmt': <tf.Tensor 'IteratorGetNext:9' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
231/231 [==============================] - ETA: 0s - loss: 0.6759 - accuracy: 0.6802WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor. Received: inputs={'Type': <tf.Tensor 'IteratorGetNext:11' shape=(None,) dtype=string>, 'Age': <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=int64>, 'Breed1': <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>, 'Gender': <tf.Tensor 'IteratorGetNext:6' shape=(None,) dtype=string>, 'Color1': <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>, 'Color2': <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>, 'MaturitySize': <tf.Tensor 'IteratorGetNext:8' shape=(None,) dtype=string>, 'FurLength': <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>, 'Vaccinated': <tf.Tensor 'IteratorGetNext:12' shape=(None,) dtype=string>, 'Sterilized': <tf.Tensor 'IteratorGetNext:10' shape=(None,) dtype=string>, 'Health': <tf.Tensor 'IteratorGetNext:7' shape=(None,) dtype=string>, 'Fee': <tf.Tensor 'IteratorGetNext:4' shape=(None,) dtype=int64>, 'PhotoAmt': <tf.Tensor 'IteratorGetNext:9' shape=(None,) dtype=int64>}. Consider rewriting this model with the Functional API.
231/231 [==============================] - 4s 10ms/step - loss: 0.6759 - accuracy: 0.6802 - val_loss: 0.5361 - val_accuracy: 0.7351
Epoch 2/10
231/231 [==============================] - 2s 9ms/step - loss: 0.5742 - accuracy: 0.7054 - val_loss: 0.5178 - val_accuracy: 0.7411
Epoch 3/10
231/231 [==============================] - 2s 9ms/step - loss: 0.5369 - accuracy: 0.7231 - val_loss: 0.5031 - val_accuracy: 0.7438
Epoch 4/10
231/231 [==============================] - 2s 9ms/step - loss: 0.5161 - accuracy: 0.7214 - val_loss: 0.5115 - val_accuracy: 0.7259
Epoch 5/10
231/231 [==============================] - 2s 9ms/step - loss: 0.5034 - accuracy: 0.7296 - val_loss: 0.5173 - val_accuracy: 0.7237
Epoch 6/10
231/231 [==============================] - 2s 8ms/step - loss: 0.4983 - accuracy: 0.7301 - val_loss: 0.5153 - val_accuracy: 0.7254
Epoch 7/10
231/231 [==============================] - 2s 9ms/step - loss: 0.4912 - accuracy: 0.7412 - val_loss: 0.5258 - val_accuracy: 0.7010
Epoch 8/10
231/231 [==============================] - 2s 9ms/step - loss: 0.4890 - accuracy: 0.7360 - val_loss: 0.5066 - val_accuracy: 0.7221
Epoch 9/10
231/231 [==============================] - 2s 9ms/step - loss: 0.4824 - accuracy: 0.7443 - val_loss: 0.5091 - val_accuracy: 0.7481
Epoch 10/10
231/231 [==============================] - 2s 9ms/step - loss: 0.4758 - accuracy: 0.7466 - val_loss: 0.5159 - val_accuracy: 0.7492
<keras.callbacks.History at 0x7f06b52a1810>
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)
73/73 [==============================] - 0s 6ms/step - loss: 0.4812 - accuracy: 0.7543
Accuracy 0.7543327808380127

Następne kroki

Najlepszym sposobem, aby dowiedzieć się więcej o klasyfikowaniu uporządkowanych danych, jest wypróbowanie tego samodzielnie. Sugerujemy znalezienie innego zestawu danych do pracy i wytrenowanie modelu, aby sklasyfikować go przy użyciu kodu podobnego do powyższego. Aby zwiększyć dokładność, zastanów się dokładnie, które elementy należy uwzględnić w modelu i jak powinny być reprezentowane.