Yapılandırılmış verileri özellik sütunlarıyla sınıflandırma

TensorFlow.org'da görüntüleyin Google Colab'da çalıştırın Kaynağı GitHub'da görüntüleyin Not defterini indir

Bu öğretici, yapılandırılmış verilerin nasıl sınıflandırılacağını gösterir (örneğin, bir CSV'deki tablo verileri). Modeli tanımlamak için Keras'ı ve bir CSV'deki sütunlardan modeli eğitmek için kullanılan özelliklere eşlemek için bir köprü olarak tf.feature_column . Bu eğitici, aşağıdakiler için eksiksiz bir kod içerir:

  • Panda'ları kullanarak bir CSV dosyası yükleyin.
  • tf.data kullanarak satırları toplu hale getirmek ve karıştırmak için bir girdi ardışık düzeni oluşturun.
  • CSV'deki sütunlardan, özellik sütunlarını kullanarak modeli eğitmek için kullanılan özelliklere eşleyin.
  • Keras'ı kullanarak bir model oluşturun, eğitin ve değerlendirin.

Veri Kümesi

PetFinder veri setinin basitleştirilmiş bir versiyonunu kullanacağız. CSV'de birkaç bin satır var. Her satır bir evcil hayvanı ve her sütun bir özelliği tanımlar. Bu bilgiyi evcil hayvanın sahiplenilme hızını tahmin etmek için kullanacağız.

Aşağıda bu veri kümesinin açıklaması yer almaktadır. Hem sayısal hem de kategorik sütunlar olduğuna dikkat edin. Bu eğitimde kullanmayacağımız bir serbest metin sütunu var.

Kolon Açıklama Özellik Türü Veri tipi
Tip Hayvan türü (Köpek, Kedi) Kategorik sicim
Yaş evcil hayvan yaşı Sayısal tam sayı
cins1 Evcil hayvanın birincil cinsi Kategorik sicim
Renk1 Evcil hayvanın rengi 1 Kategorik sicim
Renk2 Evcil hayvanın 2. rengi Kategorik sicim
OlgunlukBoyutu Olgunluktaki boyut Kategorik sicim
Kürk Uzunluğu kürk uzunluğu Kategorik sicim
aşılı Evcil hayvan aşılandı Kategorik sicim
Sterilize Evcil hayvan sterilize edildi Kategorik sicim
Sağlık Sağlık durumu Kategorik sicim
Ücret Evlat Edinme Ücreti Sayısal tam sayı
Açıklama Bu evcil hayvan için profil yazısı Metin sicim
FotoğrafAmt Bu evcil hayvan için yüklenen toplam fotoğraf Sayısal tam sayı
BenimsemeHızı benimseme hızı sınıflandırma tam sayı

TensorFlow ve diğer kitaplıkları içe aktarın

pip install sklearn
tutucu1 l10n-yer
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

Bir veri çerçevesi oluşturmak için Pandaları kullanın

Pandas , yapılandırılmış verileri yüklemek ve bunlarla çalışmak için birçok yararlı yardımcı program içeren bir Python kitaplığıdır. Veri kümesini bir URL'den indirmek ve bir veri çerçevesine yüklemek için Panda'ları kullanacağız.

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
-yer tutucu4 l10n-yer
dataframe.head()

Hedef değişken oluştur

Orijinal veri kümesindeki görev, bir evcil hayvanın sahiplenilme hızını tahmin etmektir (örneğin, ilk hafta, ilk ay, ilk üç ay, vb.). Bunu öğreticimiz için basitleştirelim. Burada bunu bir ikili sınıflandırma problemine dönüştüreceğiz ve sadece evcil hayvanın sahiplenilip alınmadığını tahmin edeceğiz.

Etiket sütununu değiştirdikten sonra 0, evcil hayvanın sahiplenilmediğini ve 1 ise kabul edildiğini gösterecektir.

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

Veri çerçevesini tren, doğrulama ve test olarak ayırın

İndirdiğimiz veri seti tek bir CSV dosyasıydı. Bunu tren, doğrulama ve test setlerine ayıracağız.

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

tf.data kullanarak bir girdi işlem hattı oluşturun

Ardından, veri çerçevelerini tf.data ile saracağız . Bu, Pandas veri çerçevesindeki sütunlardan modeli eğitmek için kullanılan özelliklere eşlemek için özellik sütunlarını bir köprü olarak kullanmamızı sağlayacaktır. Çok büyük bir CSV dosyasıyla (belleğe sığmayacak kadar büyük) çalışıyor olsaydık, onu doğrudan diskten okumak için tf.data kullanırdık. Bu, bu eğitimde ele alınmamıştır.

# 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
tutucu9 l10n-yer
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)

Giriş hattını anlayın

Şimdi girdi ardışık düzenini oluşturduğumuza göre, döndürdüğü verinin biçimini görmek için onu çağıralım. Çıktıyı okunabilir kılmak için küçük bir toplu iş boyutu kullandık.

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 )
tutucu11 l10n-yer
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)

Veri kümesinin, veri çerçevesindeki satırlardan sütun değerleriyle eşleşen bir sütun adları sözlüğü (veri çerçevesinden) döndürdüğünü görebiliriz.

Birkaç tür özellik sütunu gösterin

TensorFlow, birçok türde özellik sütunu sağlar. Bu bölümde, birkaç tür özellik sütunu oluşturacağız ve bunların veri çerçevesinden bir sütunu nasıl dönüştürdüklerini göstereceğiz.

# We will use this batch to demonstrate several types of feature columns
example_batch = next(iter(train_ds))[0]
tutucu13 l10n-yer
# 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())

sayısal sütunlar

Bir özellik sütununun çıktısı, modelin girdisi olur (yukarıda tanımlanan demo işlevini kullanarak, veri çerçevesinden her bir sütunun tam olarak nasıl dönüştürüldüğünü görebileceğiz). Sayısal sütun , en basit sütun türüdür. Gerçek değerli özellikleri temsil etmek için kullanılır. Bu sütunu kullanırken, modeliniz veri çerçevesinden sütun değerini değişmeden alacaktır.

photo_count = feature_column.numeric_column('PhotoAmt')
demo(photo_count)
tutucu15 l10n-yer
[[2.]
 [4.]
 [4.]
 [1.]
 [2.]]

PetFinder veri kümesinde, veri çerçevesindeki çoğu sütun kategoriktir.

Paketlenmiş sütunlar

Çoğu zaman, modele doğrudan bir sayı beslemek istemezsiniz, bunun yerine değerini sayısal aralıklara dayalı olarak farklı kategorilere bölersiniz. Bir kişinin yaşını temsil eden ham verileri düşünün. Yaşı sayısal bir sütun olarak göstermek yerine, bir gruplandırılmış sütun kullanarak yaşı birkaç bölüme ayırabiliriz. Aşağıdaki tek-sıcak değerlerin, her satırın hangi yaş aralığıyla eşleştiğini açıkladığına dikkat edin.

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

kategorik sütunlar

Bu veri kümesinde, Tür bir dize olarak temsil edilir (örneğin, 'Köpek' veya 'Kedi'). Dizeleri doğrudan bir modele besleyemeyiz. Bunun yerine, önce onları sayısal değerlerle eşlemeliyiz. Kategorik sözcük dağarcığı sütunları, dizeleri tek-sıcak bir vektör olarak göstermenin bir yolunu sağlar (yukarıda yaş gruplarıyla gördüğünüz gibi). Sözcük, categorical_column_with_vocabulary_list kullanılarak bir liste olarak geçirilebilir veya categorical_column_with_vocabulary_file kullanılarak bir dosyadan yüklenebilir.

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)
tutucu19 l10n-yer
[[1. 0.]
 [1. 0.]
 [1. 0.]
 [1. 0.]
 [0. 1.]]

Sütunları gömme

Birkaç olası dizgeye sahip olmak yerine, kategori başına binlerce (veya daha fazla) değerimiz olduğunu varsayalım. Bir dizi nedenden dolayı, kategorilerin sayısı arttıkça, tek-sıcak kodlamalar kullanarak bir sinir ağını eğitmek olanaksız hale gelir. Bu sınırlamanın üstesinden gelmek için bir gömme sütunu kullanabiliriz. Bir gömme sütunu , verileri birçok boyuttaki tek-sıcak vektör olarak temsil etmek yerine, bu verileri daha düşük boyutlu, yoğun bir vektör olarak temsil eder; burada her hücre yalnızca 0 veya 1 değil, herhangi bir sayı içerebilir. Gömmenin boyutu ( 8, aşağıdaki örnekte) ayarlanması gereken bir parametredir.

# 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)
tutucu21 l10n-yer
[[-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 ]]

Karma özellik sütunları

Çok sayıda değer içeren kategorik bir sütunu temsil etmenin başka bir yolu da categorical_column_with_hash_bucket kullanmaktır. Bu özellik sütunu, girişin bir karma değerini hesaplar, ardından bir dizeyi kodlamak için hash_bucket_size birini seçer. Bu sütunu kullanırken, kelime bilgisi sağlamanız gerekmez ve yerden tasarruf etmek için hash_buckets sayısını gerçek kategori sayısından önemli ölçüde daha küçük yapmayı seçebilirsiniz.

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

Çapraz özellik sütunları

Unsurları tek bir unsurda birleştirmek, daha çok unsur çaprazları olarak bilinir, bir modelin her unsur kombinasyonu için ayrı ağırlıklar öğrenmesini sağlar. Burada, Yaş ve Tür çaprazı olan yeni bir özellik oluşturacağız. crossed_column tüm olası kombinasyonların (çok büyük olabilir) tam tablosunu oluşturmadığını unutmayın. Bunun yerine, bir hashed_column tarafından desteklenir, böylece tablonun ne kadar büyük olduğunu seçebilirsiniz.

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

Hangi sütunların kullanılacağını seçin

Birkaç tür özellik sütununun nasıl kullanılacağını gördük. Şimdi onları bir modeli eğitmek için kullanacağız. Bu öğreticinin amacı, özellik sütunlarıyla çalışmak için gereken kodun tamamını (ör. mekanik) size göstermektir. Aşağıdaki modelimizi keyfi olarak eğitmek için birkaç sütun seçtik.

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

Bir özellik katmanı oluşturun

Özellik sütunlarımızı tanımladığımıza göre, bunları Keras modelimize girmek için bir DenseFeatures katmanı kullanacağız.

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

Daha önce, özellik sütunlarının nasıl çalıştığını göstermek için küçük bir toplu iş boyutu kullandık. Daha büyük bir parti boyutuna sahip yeni bir girdi işlem hattı oluşturuyoruz.

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)

Modeli oluşturun, derleyin ve eğitin

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>
yer tutucu35 l10n-yer
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)
73/73 [==============================] - 0s 6ms/step - loss: 0.4812 - accuracy: 0.7543
Accuracy 0.7543327808380127

Sonraki adımlar

Yapılandırılmış verileri sınıflandırma hakkında daha fazla bilgi edinmenin en iyi yolu, onu kendiniz denemektir. Çalışmak için başka bir veri kümesi bulmanızı ve yukarıdakine benzer bir kod kullanarak sınıflandırmak için bir model eğitmenizi öneririz. Doğruluğu artırmak için modelinize hangi özellikleri ekleyeceğinizi ve bunların nasıl temsil edilmesi gerektiğini dikkatlice düşünün.