Phân loại dữ liệu có cấu trúc với các cột tính năng

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ ghi chép

Hướng dẫn này trình bày cách phân loại dữ liệu có cấu trúc (ví dụ: dữ liệu dạng bảng trong CSV). Chúng tôi sẽ sử dụng Keras để xác định mô hình và tf.feature_column làm cầu nối để ánh xạ từ các cột trong CSV tới các đối tượng địa lý được sử dụng để đào tạo mô hình. Hướng dẫn này chứa mã hoàn chỉnh để:

  • Tải tệp CSV bằng Pandas .
  • Xây dựng một đường dẫn đầu vào để xử lý hàng loạt và xáo trộn các hàng bằng cách sử dụng tf.data .
  • Ánh xạ từ các cột trong CSV đến các đối tượng địa lý được sử dụng để đào tạo mô hình bằng cách sử dụng các cột đối tượng địa lý.
  • Xây dựng, đào tạo và đánh giá một mô hình bằng Keras.

Tập dữ liệu

Chúng tôi sẽ sử dụng phiên bản đơn giản hóa của bộ dữ liệu PetFinder. Có vài nghìn hàng trong CSV. Mỗi hàng mô tả một con vật cưng và mỗi cột mô tả một thuộc tính. Chúng tôi sẽ sử dụng thông tin này để dự đoán tốc độ vật nuôi sẽ được nhận nuôi.

Sau đây là mô tả của tập dữ liệu này. Lưu ý rằng có cả cột số và cột phân loại. Có một cột văn bản miễn phí mà chúng tôi sẽ không sử dụng trong hướng dẫn này.

Cột Sự miêu tả Loại tính năng Loại dữ liệu
Loại Loại động vật (Chó, Mèo) Phân loại chuỗi
Già đi Tuổi của vật nuôi Số số nguyên
Giống1 Giống vật nuôi chính Phân loại chuỗi
Màu1 Màu 1 của thú cưng Phân loại chuỗi
Màu 2 Màu 2 của thú cưng Phân loại chuỗi
MaturitySize Kích thước khi trưởng thành Phân loại chuỗi
FurLength Chiều dài lông Phân loại chuỗi
Đã tiêm phòng Thú cưng đã được tiêm phòng Phân loại chuỗi
Tiệt trùng Thú cưng đã được triệt sản Phân loại chuỗi
Sức khỏe Tình trạng sức khỏe Phân loại chuỗi
Học phí Phí nhận con nuôi Số số nguyên
Sự miêu tả Hồ sơ viết lên cho con vật cưng này Chữ chuỗi
PhotoAmt Tổng số ảnh đã tải lên cho con vật cưng này Số số nguyên
AdoptionSpeed Tốc độ chấp nhận Phân loại số nguyên

Nhập TensorFlow và các thư viện khác

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

Sử dụng Pandas để tạo khung dữ liệu

Pandas là một thư viện Python với nhiều tiện ích hữu ích để tải và làm việc với dữ liệu có cấu trúc. Chúng tôi sẽ sử dụng Pandas để tải xuống tập dữ liệu từ một URL và tải nó vào khung dữ liệu.

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

Tạo biến mục tiêu

Nhiệm vụ trong tập dữ liệu ban đầu là dự đoán tốc độ vật nuôi sẽ được nhận nuôi (ví dụ: trong tuần đầu tiên, tháng đầu tiên, ba tháng đầu tiên, v.v.). Hãy đơn giản hóa điều này cho hướng dẫn của chúng tôi. Ở đây, chúng tôi sẽ chuyển nó thành một bài toán phân loại nhị phân và chỉ đơn giản là dự đoán liệu vật nuôi có được nhận nuôi hay không.

Sau khi sửa đổi cột nhãn, 0 sẽ cho biết vật nuôi không được nhận nuôi và 1 sẽ cho biết nó đã được nhận.

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

Chia khung dữ liệu thành đào tạo, xác thực và kiểm tra

Tập dữ liệu chúng tôi đã tải xuống là một tệp CSV. Chúng tôi sẽ chia điều này thành các tập huấn luyện, xác nhận và thử nghiệm.

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

Tạo một đường dẫn đầu vào bằng tf.data

Tiếp theo, chúng tôi sẽ bọc các khung dữ liệu bằng tf.data . Điều này sẽ cho phép chúng tôi sử dụng các cột đặc điểm làm cầu nối để ánh xạ từ các cột trong khung dữ liệu Pandas tới các đối tượng địa lý được sử dụng để đào tạo mô hình. Nếu chúng tôi đang làm việc với một tệp CSV rất lớn (lớn đến mức nó không vừa với bộ nhớ), chúng tôi sẽ sử dụng tf.data để đọc trực tiếp từ đĩa. Điều đó không được đề cập trong hướng dẫn này.

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

Hiểu đường dẫn đầu vào

Bây giờ chúng ta đã tạo xong đường dẫn đầu vào, hãy gọi nó để xem định dạng dữ liệu mà nó trả về. Chúng tôi đã sử dụng kích thước lô nhỏ để giữ cho đầu ra có thể đọc được.

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)

Chúng ta có thể thấy rằng tập dữ liệu trả về một từ điển tên cột (từ khung dữ liệu) ánh xạ tới các giá trị cột từ các hàng trong khung dữ liệu.

Thể hiện một số loại cột tính năng

TensorFlow cung cấp nhiều loại cột tính năng. Trong phần này, chúng tôi sẽ tạo một số loại cột tính năng và trình bày cách chúng biến đổi một cột từ khung dữ liệu.

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

Cột số

Đầu ra của một cột tính năng trở thành đầu vào cho mô hình (sử dụng chức năng demo được định nghĩa ở trên, chúng ta sẽ có thể thấy chính xác từng cột từ khung dữ liệu được chuyển đổi như thế nào). Cột số là loại cột đơn giản nhất. Nó được sử dụng để đại diện cho các tính năng có giá trị thực. Khi sử dụng cột này, mô hình của bạn sẽ nhận được giá trị cột từ khung dữ liệu không thay đổi.

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

Trong tập dữ liệu PetFinder, hầu hết các cột từ khung dữ liệu đều được phân loại.

Cột biketized

Thông thường, bạn không muốn đưa một số trực tiếp vào mô hình mà thay vào đó, hãy chia giá trị của nó thành các danh mục khác nhau dựa trên phạm vi số. Xem xét dữ liệu thô đại diện cho tuổi của một người. Thay vì thể hiện độ tuổi dưới dạng cột số, chúng tôi có thể chia độ tuổi thành nhiều nhóm bằng cách sử dụng cột được bucketized . Lưu ý các giá trị duy nhất bên dưới mô tả độ tuổi mà mỗi hàng phù hợp.

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

Cột phân loại

Trong tập dữ liệu này, Kiểu được biểu diễn dưới dạng một chuỗi (ví dụ: 'Chó' hoặc 'Mèo'). Chúng tôi không thể cấp chuỗi trực tiếp cho một mô hình. Thay vào đó, trước tiên chúng ta phải ánh xạ chúng thành các giá trị số. Các cột từ vựng phân loại cung cấp một cách để biểu diễn các chuỗi dưới dạng một vectơ duy nhất (giống như bạn đã thấy ở trên với các nhóm tuổi). Từ vựng có thể được chuyển dưới dạng danh sách bằng categorical_column_with_vocabulary_list hoặc tải từ tệp bằng 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.]]

Nhúng cột

Giả sử thay vì chỉ có một vài chuỗi khả thi, chúng ta có hàng nghìn (hoặc nhiều hơn) giá trị cho mỗi danh mục. Vì một số lý do, khi số lượng danh mục ngày càng lớn, việc đào tạo mạng nơ-ron bằng cách sử dụng mã hóa một nóng trở nên không khả thi. Chúng ta có thể sử dụng một cột nhúng để khắc phục hạn chế này. Thay vì biểu thị dữ liệu dưới dạng vectơ một nóng của nhiều thứ nguyên, cột nhúng biểu thị dữ liệu đó dưới dạng vectơ dày đặc, chiều thấp hơn, trong đó mỗi ô có thể chứa bất kỳ số nào, không chỉ 0 hoặc 1. Kích thước của nhúng ( 8, trong ví dụ bên dưới) là một tham số phải được điều chỉnh.

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

Các cột tính năng băm

Một cách khác để biểu diễn cột phân loại với một số lượng lớn giá trị là sử dụng cột phân loại_with_hash_bucket . Cột tính năng này tính toán giá trị băm của đầu vào, sau đó chọn một trong các nhóm hash_bucket_size để mã hóa một chuỗi. Khi sử dụng cột này, bạn không cần cung cấp từ vựng và bạn có thể chọn đặt số lượng băm nhỏ hơn đáng kể so với số danh mục thực tế để tiết kiệm dung lượng.

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

Các cột tính năng bị gạch chéo

Việc kết hợp các tính năng thành một đối tượng địa lý, hay còn được gọi là điểm giao nhau giữa các đối tượng địa lý, cho phép một mô hình tìm hiểu các trọng số riêng biệt cho từng kết hợp các đối tượng địa lý. Ở đây, chúng tôi sẽ tạo ra một tính năng mới đó là sự kết hợp giữa Tuổi và Loại. Lưu ý rằng crossed_column không xây dựng bảng đầy đủ của tất cả các kết hợp có thể có (có thể rất lớn). Thay vào đó, nó được hỗ trợ bởi hashed_column , vì vậy bạn có thể chọn kích thước của bảng.

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

Chọn cột để sử dụng

Chúng tôi đã thấy cách sử dụng một số loại cột tính năng. Bây giờ chúng ta sẽ sử dụng chúng để đào tạo một người mẫu. Mục tiêu của hướng dẫn này là hiển thị cho bạn mã hoàn chỉnh (ví dụ: cơ học) cần thiết để làm việc với các cột tính năng. Chúng tôi đã chọn một vài cột để đào tạo mô hình của chúng tôi bên dưới một cách tùy ý.

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

Tạo một lớp tính năng

Bây giờ chúng ta đã xác định các cột tính năng của mình, chúng ta sẽ sử dụng lớp DenseFeatures để nhập chúng vào mô hình Keras của chúng ta.

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

Trước đó, chúng tôi đã sử dụng kích thước lô nhỏ để chứng minh các cột tính năng hoạt động như thế nào. Chúng tôi tạo một đường dẫn đầu vào mới với quy mô lô lớn hơn.

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)

Tạo, biên dịch và đào tạo mô hình

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

Bước tiếp theo

Cách tốt nhất để tìm hiểu thêm về phân loại dữ liệu có cấu trúc là tự mình thử. Chúng tôi khuyên bạn nên tìm một tập dữ liệu khác để làm việc và đào tạo một mô hình để phân loại nó bằng cách sử dụng mã tương tự như trên. Để cải thiện độ chính xác, hãy suy nghĩ cẩn thận về những tính năng nào cần đưa vào mô hình của bạn và cách chúng được thể hiện.