تصنيف البيانات المهيكلة باستخدام أعمدة الميزة

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

يوضح هذا البرنامج التعليمي كيفية تصنيف البيانات المهيكلة (مثل البيانات الجدولية في ملف CSV). سنستخدم Keras لتعريف النموذج ، و tf.feature_column كجسر للتعيين من الأعمدة في CSV إلى الميزات المستخدمة لتدريب النموذج. يحتوي هذا البرنامج التعليمي على تعليمات برمجية كاملة من أجل:

  • قم بتحميل ملف CSV باستخدام Pandas .
  • قم ببناء خط أنابيب إدخال لدُفعات وخلط الصفوف باستخدام tf.data .
  • تعيين من أعمدة في CSV إلى الميزات المستخدمة لتدريب النموذج باستخدام أعمدة المعالم.
  • بناء وتدريب وتقييم نموذج باستخدام Keras.

مجموعة البيانات

سنستخدم نسخة مبسطة من مجموعة بيانات PetFinder. هناك عدة آلاف من الصفوف في ملف CSV. يصف كل صف حيوانًا أليفًا ، ويصف كل عمود سمة. سنستخدم هذه المعلومات للتنبؤ بالسرعة التي سيتم بها تبني الحيوان الأليف.

فيما يلي وصف لمجموعة البيانات هذه. لاحظ أن هناك أعمدة رقمية وفئوية. هناك عمود نص حر لن نستخدمه في هذا البرنامج التعليمي.

عمود وصف نوع الميزة نوع البيانات
نوع نوع الحيوان (كلب ، قطة) قاطع سلسلة
عمر عمر الحيوان الأليف عددي عدد صحيح
السلالة 1 سلالة أساسية من الحيوانات الأليفة قاطع سلسلة
اللون 1 اللون 1 للحيوان الأليف قاطع سلسلة
اللون 2 لون 2 من حيوان أليف قاطع سلسلة
النضج الحجم الحجم عند الاستحقاق قاطع سلسلة
فورلينجث طول الفراء قاطع سلسلة
تلقيح تم تطعيم حيوان أليف قاطع سلسلة
معقم تم تعقيم الحيوانات الأليفة قاطع سلسلة
الصحة الحالة الصحية قاطع سلسلة
مصاريف رسوم التبني عددي عدد صحيح
وصف كتابة الملف الشخصي لهذا الحيوان الأليف نص سلسلة
الصورة إجمالي الصور التي تم تحميلها لهذا الحيوان الأليف عددي عدد صحيح
السرعة سرعة التبني تصنيف عدد صحيح

استيراد TensorFlow ومكتبات أخرى

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

استخدم Pandas لإنشاء إطار بيانات

Pandas هي مكتبة Python بها العديد من الأدوات المساعدة لتحميل البيانات المنظمة والعمل معها. سنستخدم Pandas لتنزيل مجموعة البيانات من عنوان URL وتحميلها في إطار بيانات.

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()

إنشاء متغير الهدف

تتمثل المهمة في مجموعة البيانات الأصلية في توقع السرعة التي سيتم بها تبني حيوان أليف (على سبيل المثال ، في الأسبوع الأول والشهر الأول والأشهر الثلاثة الأولى وما إلى ذلك). دعونا نبسط هذا في برنامجنا التعليمي. هنا ، سنحول هذا إلى مشكلة تصنيف ثنائي ، ونتوقع ببساطة ما إذا كان الحيوان الأليف قد تم تبنيه أم لا.

بعد تعديل عمود الملصق ، سيشير 0 إلى أنه لم يتم تبني الحيوان الأليف ، وسيشير الرقم 1 إلى أنه تم تبنيها.

# 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'])

قسّم إطار البيانات إلى تدريب وتحقق واختبار

كانت مجموعة البيانات التي نزّلناها عبارة عن ملف 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')
7383 train examples
1846 validation examples
2308 test examples

قم بإنشاء مسار إدخال باستخدام tf.data

بعد ذلك ، سنلف إطارات البيانات بـ tf.data . سيمكننا ذلك من استخدام أعمدة المعالم كجسر للتعيين من الأعمدة في إطار بيانات Pandas إلى الميزات المستخدمة لتدريب النموذج. إذا كنا نعمل مع ملف CSV كبير جدًا (كبير جدًا بحيث لا يتناسب مع الذاكرة) ، فسنستخدم tf.data لقراءته من القرص مباشرة. لم يتم تناول ذلك في هذا البرنامج التعليمي.

# 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)

افهم خط أنابيب الإدخال

الآن بعد أن أنشأنا خط أنابيب الإدخال ، دعنا نسميه لنرى تنسيق البيانات التي يعيدها. لقد استخدمنا حجم دفعة صغيرة لإبقاء الإخراج قابلاً للقراءة.

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)

يمكننا أن نرى أن مجموعة البيانات تُرجع قاموسًا لأسماء الأعمدة (من إطار البيانات) الذي يعيّن قيم الأعمدة من الصفوف في إطار البيانات.

شرح عدة أنواع من أعمدة المعالم

يوفر TensorFlow العديد من أنواع أعمدة المعالم. في هذا القسم ، سننشئ عدة أنواع من أعمدة المعالم ، ونوضح كيف تقوم بتحويل عمود من إطار البيانات.

# 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())

أعمدة رقمية

يصبح ناتج عمود الميزة هو المدخلات إلى النموذج (باستخدام وظيفة العرض التوضيحي المحددة أعلاه ، سنتمكن من رؤية كيفية تحويل كل عمود من إطار البيانات بالضبط). العمود الرقمي هو أبسط نوع من الأعمدة. يتم استخدامه لتمثيل الميزات القيمة الحقيقية. عند استخدام هذا العمود ، سيتلقى نموذجك قيمة العمود من إطار البيانات دون تغيير.

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

في مجموعة بيانات PetFinder ، تكون معظم الأعمدة من إطار البيانات قاطعة.

أعمدة دلو

في كثير من الأحيان ، لا تريد إدخال رقم مباشرة في النموذج ، ولكن بدلاً من ذلك قسم قيمته إلى فئات مختلفة بناءً على النطاقات الرقمية. ضع في اعتبارك البيانات الأولية التي تمثل عمر الشخص. بدلاً من تمثيل العمر كعمود رقمي ، يمكننا تقسيم العمر إلى عدة مجموعات باستخدام عمود مُجمع . لاحظ أن القيم الساخنة الواحدة أدناه تصف النطاق العمري الذي يطابقه كل صف.

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.]]

أعمدة فئوية

في مجموعة البيانات هذه ، يتم تمثيل النوع كسلسلة (مثل "Dog" أو "Cat"). لا يمكننا إطعام السلاسل مباشرة إلى نموذج. بدلاً من ذلك ، يجب علينا أولاً تعيينها لقيم رقمية. توفر أعمدة المفردات الفئوية طريقة لتمثيل السلاسل كمتجه واحد ساخن (يشبه إلى حد كبير ما رأيته أعلاه مع مجموعات العمر). يمكن تمرير المفردات كقائمة باستخدام categorical_column_with_vocabulary_list أو تحميلها من ملف باستخدام 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.]]

تضمين الأعمدة

افترض أنه بدلاً من وجود عدد قليل من السلاسل الممكنة ، لدينا الآلاف (أو أكثر) من القيم لكل فئة. لعدد من الأسباب ، مع زيادة عدد الفئات ، يصبح من غير المجدي تدريب شبكة عصبية باستخدام ترميز واحد ساخن. يمكننا استخدام عمود التضمين للتغلب على هذا القيد. بدلاً من تمثيل البيانات كمتجه واحد ساخن للعديد من الأبعاد ، يمثل عمود التضمين تلك البيانات كمتجه منخفض الأبعاد كثيف حيث يمكن أن تحتوي كل خلية على أي رقم ، وليس فقط 0 أو 1. حجم التضمين ( 8 ، في المثال أدناه) هي معلمة يجب ضبطها.

# 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 ]]

أعمدة ميزة مجزأة

هناك طريقة أخرى لتمثيل عمود فئوي بعدد كبير من القيم وهي استخدام categorical_column_with_hash_bucket . يحسب عمود الميزة هذا قيمة تجزئة للإدخال ، ثم يحدد إحدى مجموعات hash_bucket_size لتشفير سلسلة. عند استخدام هذا العمود ، لا تحتاج إلى توفير المفردات ، ويمكنك اختيار جعل عدد hash_buckets أصغر بكثير من عدد الفئات الفعلية لتوفير المساحة.

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.]]

أعمدة المعالم المتقاطعة

يتيح دمج الميزات في ميزة واحدة ، والمعروفة باسم تقاطعات الميزات ، للنموذج معرفة أوزان منفصلة لكل مجموعة من الميزات. هنا ، سننشئ ميزة جديدة وهي تقاطع العمر والنوع. لاحظ أن crossed_column لا يُنشئ الجدول الكامل لجميع المجموعات الممكنة (والتي قد تكون كبيرة جدًا). بدلاً من ذلك ، يتم دعمه بواسطة hashed_column ، بحيث يمكنك اختيار حجم الجدول.

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.]]

اختر الأعمدة التي تريد استخدامها

لقد رأينا كيفية استخدام عدة أنواع من أعمدة المعالم. الآن سوف نستخدمهم لتدريب نموذج. الهدف من هذا البرنامج التعليمي هو إظهار الشفرة الكاملة (مثل الميكانيكا) اللازمة للعمل مع أعمدة الميزات. لقد اخترنا بضعة أعمدة لتدريب نموذجنا أدناه بشكل تعسفي.

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))

قم بإنشاء طبقة معالم

الآن بعد أن حددنا أعمدة الميزات الخاصة بنا ، سنستخدم طبقة DenseFeatures لإدخالها في نموذج Keras الخاص بنا.

feature_layer = tf.keras.layers.DenseFeatures(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.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

الخطوات التالية

أفضل طريقة لمعرفة المزيد حول تصنيف البيانات المنظمة هي تجربتها بنفسك. نقترح العثور على مجموعة بيانات أخرى للعمل معها ، وتدريب نموذج لتصنيفها باستخدام رمز مشابه لما ورد أعلاه. لتحسين الدقة ، فكر جيدًا في الميزات التي يجب تضمينها في نموذجك ، وكيف ينبغي تمثيلها.