Uçtan Uca Örnek 1: TRUBA Üzerinde Metin Sınıflandırma

Bu örnekte, bir metin sınıflandırman örneği anlatılmış ve TRUBA bilgisayarı üzerinde nasıl çalıştırılabileceği gösterilmiştir.

Verinin Elde Edilmesi

Kullanılacak veriyi terminal üzerinden indirip hazır hale getirebilirsiniz:

curl -O https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
tar -xf aclImdb_v1.tar.gz
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                Dload  Upload   Total   Spent    Left  Speed
100 80.2M  100 80.2M    0     0  45.3M      0  0:00:01  0:00:01 --:--:-- 45.3M

aclImdb klasöründe train ve test alt klasörleri bulunmaktadır. aclImdb/train/pos ve aclImdb/train/neg klasörlerinin her birinde olumlu veya olumsuz görüşler içeren incelemeler bulunmaktadır.

cat aclImdb/train/pos/6248_7.txt

Sadece pos ve neg klasörlerine ihtiyacımız olduğu için geri kalan dosyaları silebiliriz.

rm -r aclImdb/train/unsup

Verinin Okunması

Elimizdeki veri seti hali hazırda yapılandırılmış olduğundan tf.keras.preprocessing.text_dataset_from_directory fonksiyonunu kullanarak kategori değerleri verilmiş bir tf.data.Dataset objesi elde edebiliriz.

Bunu ayrıca training (eğitme), validation (doğrulama) ve test veri setlerini yaratmak için de kullanabiliriz. Validation ve training veri setlerini train klasörünün %80’ini training, %20’sini validation olmak üzere ayıralım. Validation veri setine sahip olmak, eğittiğimiz modelin parametlerini optimize edebilmemiz için gereklidir. Ancak hiperparametreler seçilip modelin kullanıma sunulmasından önce training ve validation setlerinin tamamını modeli eğitmek için kullanmak modelin performansını arttıracaktır.

validation_split ve subset parametleri veri setlerini ayırmak için kullanılırken seed parametresi için de rasgele seçilmiş bir değer iki veri seti için de kullanılmalı ya da shuffle=False parametresi de tf.keras.preprocessing.text_dataset_from_directory fonksiyonuna verilmelidir. Aksi halde train ve test veri setlerinin aynı verileri kullanıp diskteki verilerin bir kısmını hiç okumaması durumuyla karşılaşılabilir.

import text_dataset ##Notu kontrol edin
import tensorflow as tf
import numpy as np

batch_size = 32
raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(
"aclImdb/train",
batch_size=batch_size,
validation_split=0.2,
subset="training",
seed=1337,
)

raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory(
"aclImdb/train",
batch_size=batch_size,
validation_split=0.2,
subset="validation",
seed=1337,
)

raw_test_ds = tf.keras.preprocessing.text_dataset_from_directory(
"aclImdb/test", batch_size=batch_size
)

print(
"Number of batches in raw_train_ds: %d"
% tf.data.experimental.cardinality(raw_train_ds)
)

print(
"Number of batches in raw_val_ds: %d" % tf.data.experimental.cardinality(raw_val_ds)
)

print(
"Number of batches in raw_test_ds: %d"
% tf.data.experimental.cardinality(raw_test_ds)
)

Not

tf.keras.preprocessing.text_dataset_from_directory fonksiyonu eğitimde kullanılan Keras versiyonunda bilinen bir bug içerdiğinden text_dataset.py klasöre kopyalanıp kullanılmıştır. Kullanılan dosya bu adreste bulunabilir

# Okunan verinin eğitim için kullanılmasından önce, bir kısmının kontrol edilmesi
# oluşabilecek hataları önlemek için önemlidir. Okunan tensörler .numpy() fonksiyonu ile görülebilir.
for text_batch, label_batch in raw_train_ds.take(1):
for i in range(5):
    print(text_batch.numpy()[i])
    print(label_batch.numpy()[i])

Verinin Hazırlanması

Veriyi kontrol ettiğimiz kısma dikkat edilirse, metin verisinin içinde <br /> etikletlerinin olduğu görülebilir. Veriyi işleyebilmek için bunların veri setinden temizlenmesi gerekmektedir.

from tensorflow.keras.layers.experimental.preprocessing import TextVectorization
import string
import re

def custom_standardization(input_data):
    lowercase = tf.strings.lower(input_data)
    stripped_html = tf.strings.regex_replace(lowercase, "<br />", " ")
    return tf.strings.regex_replace(
        stripped_html, "[%s]" % re.escape(string.punctuation), ""
    )


# Model parametleri.
max_features = 20000
embedding_dim = 128
sequence_length = 500


# <br /> etiketlerini temizlemek için yazdığımız için custom_standardization()
# fonksiyonu verinin vektörize edileceği katmanda kullanılabilir.
# Bu katmanda elimizdeki metin verisini normalize edip, bölüp tam sayılara dönüştürmemiz gerekmektedir.
vectorize_layer = TextVectorization(
    standardize=custom_standardization,
    max_tokens=max_features,
    output_mode="int",
    output_sequence_length=sequence_length,
)
# Elimizdeki veriyi bir sözlüğe (vocab) dönüştürelim.

# Kategori değerlerinin silinip
# sadece metinden oluşan bir veri setinin elde edilmesi.
text_ds = raw_train_ds.map(lambda x, y: x)

# adapt() fonksiyonunu bu veri seti üzerinde çağırmak, kelimeleri indeksleyerek bir sözlük üretecektir:
vectorize_layer.adapt(text_ds)

Veriyi Vektörize Etme Seçenekleri

Veriyi eğitim için kullanmadan önce vektörize etmemiz gerektiğinden bahsetmiştik. Veriyi vektörize etmek için elimizde iki seçenek bulunmaktadır:

1: Vektörizasyonun Modelin Bir Parçası Olarak Tanımlanması Vektörizasyon katmanını modelin bir parçası yaparak string veri tipinde girdi alan bir model elde edilebilir.

text_input = tf.keras.Input(shape=(1,), dtype=tf.string, name=text)
x = vectorize_layer(text_input)
x = layers.Embedding(max_features + 1, embedding_dim)(x)
...

2: Vektörizasyonun Veri Setine Uygulanması Elimizdeki metin verisi indekslenerek girdi olarak tam sayı dizisi bekleyen bir modele verilebilir.

İlk seçenekle ikinci seçenek arasındaki en büyük fark, ikinci seçeneğin CPU ve GPU işlemcileri arasında asenkron işleme ve transfer yapmaya müsaade etmesidir. Eğer modelimizi GPU kullanarak eğitmek istiyorsak, vektörizasyon işlemi için ikinci seçeneği kullanmak büyük performans artışı sağlayacaktır.

def vectorize_text(text, label):
    text = tf.expand_dims(text, -1)
    return vectorize_layer(text), label


# Verinin vektörize edilmesi
train_ds = raw_train_ds.map(vectorize_text)
val_ds = raw_val_ds.map(vectorize_text)
test_ds = raw_test_ds.map(vectorize_text)

# Vektörize edilmiş verinin asıl hesaplama (fit) adımı başlamadan önce GPU belleğine kopyalanması.
train_ds = train_ds.cache().prefetch(buffer_size=10)
val_ds = val_ds.cache().prefetch(buffer_size=10)
test_ds = test_ds.cache().prefetch(buffer_size=10)

Modelin Oluşturulması

from tensorflow.keras import layers

# Girdi uzunluğu belli olmayan bir tam sayı dizisi
inputs = tf.keras.Input(shape=(None,), dtype="int64")

# Embedding katmanının eklenmesi
x = layers.Embedding(max_features, embedding_dim)(inputs)
x = layers.Dropout(0.5)(x)

# Conv1D + global max pooling
# Conv1D ve global max pooling katmanlarının eklenmesi
x = layers.Conv1D(128, 7, padding="valid", activation="relu", strides=3)(x)
x = layers.Conv1D(128, 7, padding="valid", activation="relu", strides=3)(x)
x = layers.GlobalMaxPooling1D()(x)

# We add a vanilla hidden layer:
# Hidden layer'ın eklenmesi
x = layers.Dense(128, activation="relu")(x)
x = layers.Dropout(0.5)(x)

# Çıktı katmanı, bu katmandan sadece 1 veya 0 değerleri elde etmek istediğimiz için bu örnek için ayrı bir 'classifier' katmanına ihtiyaç duyulmamaktadır.
# Bunun yerine çıkan değer sigmoid aktivasyon fonksiyonu ile belirlenmektedir.
predictions = layers.Dense(1, activation="sigmoid", name="predictions")(x)

model = tf.keras.Model(inputs, predictions)

# Modelin derlenmesi
model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])

Modelin Eğitilmesi

epochs = 3
model.fit(train_ds, validation_data=val_ds, epochs=epochs)

Modelin Başarısının Değerlendirilmesi

model.evaluate(test_ds)

TRUBA Üzerinde Birden Fazla GPU ile Çalışmak

TRUBA üzerinde hazırladığımız modelin birden fazla GPU ile çalışabilmesi için daha önce de bahsedildiği gibi sadece model değişkenlerinin strategy scope’u içerisinde yaratılması yeterlidir.

Öncelikle TensorFlow için GPU kullanımını aktive edin:

os.environ['TF_XLA_FLAGS'] = '--tf_xla_enable_xla_devices'
# Bu satırla kaç adet GPU'nun kullanıma hazır olduğunu görebilirsiniz.
print("GPUs: ", len(tf.config.experimental.list_physical_devices('GPU')))

Bir strategy objesi yaratın:

strategy = tf.distribute.MirroredStrategy()
# Verinin kaç kopyasını oluşturduğunuzu bu satır ile görebilirsiniz.
# Bu satırın çıktısının GPU ile aynı olmaması durumunda loss fonksiyonu yanlış sonuç döndürecektir.
print("Number of devices: {}".format(strategy.num_replicas_in_sync))

Bir model değişkeni içeren her satırı yarattığınız objenin scope’unda yazın:

with strategy.scope():

    inputs = tf.keras.Input(shape=(None,), dtype="int64")

    x = layers.Embedding(max_features, embedding_dim)(inputs)
    x = layers.Dropout(0.5)(x)

    x = layers.Conv1D(128, 7, padding="valid", activation="relu", strides=3)(x)
    x = layers.Conv1D(128, 7, padding="valid", activation="relu", strides=3)(x)
    x = layers.GlobalMaxPooling1D()(x)

    x = layers.Dense(128, activation="relu")(x)
    x = layers.Dropout(0.5)(x)

    predictions = layers.Dense(1, activation="sigmoid", name="predictions")(x)

    model = tf.keras.Model(inputs, predictions)

    model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])

BETİK DOSYASININ HAZIRLANMASI

İşinizi kuyruğa göndermek için hazırlamanız gereken betik dosyası aşağıdaki gibidir:

#!/bin/bash
#SBATCH -J text_gpu             #sırada görünmek üzere kuyruğa yolladığınız işe bir isim verin
#SBATCH -A kullanici_adiniz     #kullanıcı adınızı girin
#SBATCH -p akya-cuda            #işin yollanacağı kuyruğu girin
#SBATCH --nodes=1               #işin toplamda çalışacağı node sayısını girin
#SBATCH --ntasks=1              #toplam iş parçacığı sayısını girin. Bu örnekte bir node üzerinde bir iş parçacığı çalıştıracağız.
#SBATCH --gres=gpu:2            #toplam kaç adet GPU istediğinizi girin
#SBATCH --cpus-per-task=1       #iş parçacığı başına kaç çekirdek istediğinizi girin
#SBATCH --time=00:15:00         #işin çalışacağı maksimum süreyi girin, bu değeri doğru girmek önemlidir, aksi halde işiniz kuyrukta askıda kalabilir.

module purge #Olası hataları önlemek için bütün ortam modüllerini temizleyin
eval "$(/truba/home/ftasyaran/miniconda3/bin/conda shell.bash hook)" #Conda komutlarını aktif hale getirin
conda activate tf-gpu-env #Yarattığınız conda ortamını aktive edin
module load centos7.9/lib/cuda/11.4 #CUDA modülünü yükleyin

python3 text_classification_gpu.py

Bu örnekte anlatılan kodun tamamını aşağıda bulabilirsiniz:

"""
Title: Text classification from scratch
Authors: Mark Omernick, Francois Chollet
Date created: 2019/11/06
Last modified: 2021/08/10 ~ For this education material
Description: Text sentiment classification starting from raw text files.
"""


import text_dataset
import tensorflow as tf
import numpy as np

import os

from tensorflow.keras.layers.experimental.preprocessing import TextVectorization
import string
import re

from tensorflow.keras import layers

os.environ['TF_XLA_FLAGS'] = '--tf_xla_enable_xla_devices'
print("GPUs: ", len(tf.config.experimental.list_physical_devices('GPU')))

from tensorflow.python.client import device_lib
print(device_lib.list_local_devices())

strategy = tf.distribute.MirroredStrategy()
print("Number of devices: {}".format(strategy.num_replicas_in_sync))

batch_size = 32
raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(
    "aclImdb/train",
    batch_size=batch_size,
    validation_split=0.2,
    subset="training",
    seed=1337,
)

raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory(
    "aclImdb/train",
    batch_size=batch_size,
    validation_split=0.2,
    subset="validation",
    seed=1337,
)

raw_test_ds = tf.keras.preprocessing.text_dataset_from_directory(
    "aclImdb/test", batch_size=batch_size
)

print(
    "Number of batches in raw_train_ds: %d"
    % tf.data.experimental.cardinality(raw_train_ds)
)

print(
    "Number of batches in raw_val_ds: %d" % tf.data.experimental.cardinality(raw_val_ds)
)

print(
    "Number of batches in raw_test_ds: %d"
    % tf.data.experimental.cardinality(raw_test_ds)
)

def custom_standardization(input_data):
    lowercase = tf.strings.lower(input_data)
    stripped_html = tf.strings.regex_replace(lowercase, "<br />", " ")
    return tf.strings.regex_replace(
        stripped_html, "[%s]" % re.escape(string.punctuation), ""
    )

max_features = 20000
embedding_dim = 128
sequence_length = 500

vectorize_layer = TextVectorization(
    standardize=custom_standardization,
    max_tokens=max_features,
    output_mode="int",
    output_sequence_length=sequence_length,
)

text_ds = raw_train_ds.map(lambda x, y: x)
vectorize_layer.adapt(text_ds)

def vectorize_text(text, label):
    text = tf.expand_dims(text, -1)
    return vectorize_layer(text), label

train_ds = raw_train_ds.map(vectorize_text)
val_ds = raw_val_ds.map(vectorize_text)
test_ds = raw_test_ds.map(vectorize_text)

train_ds = train_ds.cache().prefetch(buffer_size=10)
val_ds = val_ds.cache().prefetch(buffer_size=10)
test_ds = test_ds.cache().prefetch(buffer_size=10)


with strategy.scope():

    inputs = tf.keras.Input(shape=(None,), dtype="int64")

    x = layers.Embedding(max_features, embedding_dim)(inputs)
    x = layers.Dropout(0.5)(x)

    x = layers.Conv1D(128, 7, padding="valid", activation="relu", strides=3)(x)
    x = layers.Conv1D(128, 7, padding="valid", activation="relu", strides=3)(x)
    x = layers.GlobalMaxPooling1D()(x)

    x = layers.Dense(128, activation="relu")(x)
    x = layers.Dropout(0.5)(x)

    predictions = layers.Dense(1, activation="sigmoid", name="predictions")(x)

    model = tf.keras.Model(inputs, predictions)

    model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])

epochs = 3
model.fit(train_ds, validation_data=val_ds, epochs=epochs)
model.evaluate(test_ds)