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.