#
Vision Transformer Modelini Özel Bir Biyomedikal Verisetiyle Fine-Tune Etmek

Yazar: Emre Albayrak Çevirmen: Onuralp Sezer

Bu rehber, Vision Transformer (ViT) modelini özel bir biyomedikal verisetiyle fine-tune etmek için izlenmesi gereken adımları açıklamaktadır. Verisetini yükleme ve hazırlama, farklı veri bölümleri için görüntü dönüşümlerini ayarlama, ViT modelini yapılandırma ve başlatma, ayrıca eğitim sürecini değerlendirme ve görselleştirme araçlarıyla tanımlama adımlarını içermektedir.

##
Veriseti Bilgisi

Özel veriseti elle oluşturulmuş olup, 780 görüntü ve 3 sınıf içermektedir (iyi huylu, kötü huylu, normal).

##
Model Bilgisi

Fine-tune edilecek model, Google’ın `"vit-large-patch16-224"` modelidir. Model, ImageNet-21k (14M görüntü, 21.843 sınıf) üzerinde eğitilmiş ve ImageNet 2012 (1M görüntü, 1.000 sınıf) üzerinde 224x224 çözünürlükte fine-tune edilmiştir. Google, farklı görüntü boyutları ve yama boyutlarına sahip çeşitli ViT modellerine sahiptir.

Hadi başlayalım!

##
Başlarken

Öncelikle, gerekli python kütüphanelerini yükleyerek başlayalım.

    !pip install datasets transformers accelerate torch torchvision scikit-learn matplotlib wandb huggingface-hub

(İsteğe Bağlı) Modelimizi Hugging Face Hub’a yükleyeceğimiz için giriş yapmamız gerekiyor.

    from huggingface_hub import notebook_login
    notebook_login()

##
Verisetini Hazırlamak

Datasets kütüphanesi, verisetinden görüntüleri ve sınıfları otomatik olarak çeker. Detaylı bilgi için `bu bağlantıyı` ziyaret edebilirsiniz.

    from datasets import load_dataset

    dataset = load_dataset("emre570/breastcancer-ultrasound-images")
    dataset

Verisetimizi aldık. Ancak elimizde bir doğrulama seti yok. Doğrulama setini oluşturmak için, test setinin boyutuna dayalı olarak eğitim setinin bir kısmını doğrulama seti olarak ayıracağız. Daha sonra, eğitim verisetini yeni eğitim ve doğrulama alt altsetlere böleceğiz.

    # Her setin sayısını alalım
    test_num = len(dataset["test"])
    train_num = len(dataset["train"])

    val_size = test_num / train_num

    train_val_split = dataset["train"].train_test_split(test_size=val_size)
    train_val_split

Ayrılmış test setimizi aldım. Şimdi test seti ile birleştirelim

    from datasets import DatasetDict

    dataset = DatasetDict({
        "train": train_val_split["train"],
        "validation": train_val_split["test"],
        "test": dataset["test"]
    })
    dataset

Harika, Verisetimiz hazır olduğuna göre şimdi alt setleri farklı değişkenlere tanımlayarak kolay şekilde kullanabiliriz.

    train_ds = dataset['train']
    val_ds = dataset['validation']
    test_ds = dataset['test']

Şimdi verisetimizdeki resimlerin türünün `PIL.Image` olduğunu, ilk resme bakarak doğrulayabiliriz.

    train_ds[0]

Eğitim setinin özelliklerini ayrıca görebiliriz.

    train_ds.features

Şimdi veriseti içindeki her sınıftan bir tane resim gösterelim.

    >>> import matplotlib.pyplot as plt

    >>> # Initialize a set to keep track of shown labels
    >>> shown_labels = set()

    >>> # Initialize the figure for plotting
    >>> plt.figure(figsize=(10, 10))

    >>> # Loop through the dataset and plot the first image of each label
    >>> for i, sample in enumerate(train_ds):
    ...     label = train_ds.features['label'].names[sample['label']]
    ...     if label not in shown_labels:
    ...         plt.subplot(1, len(train_ds.features['label'].names), len(shown_labels) + 1)
    ...         plt.imshow(sample['image'])
    ...         plt.title(label)
    ...         plt.axis('off')
    ...         shown_labels.add(label)
    ...         if len(shown_labels) == len(train_ds.features['label'].names):
    ...             break

    >>> plt.show()

##
Veri İşleme

Veriseti hazır, ancak fine-tuning için henüz hazır değiliz. Aşağıdaki adımları sırasıyla takip edeceğiz:

- Etiket Eşleme: Model eğitimi ve değerlendirmesi sırasında faydalı olacak şekilde, etiket kimliklerini (ID) ve bunların karşılık gelen isimlerini dönüştüreceğiz.

- Görüntü İşleme: Daha sonra, `ViTImageProcessor` kullanarak giriş görüntü boyutlarını standartlaştıracağız ve önceden eğitilmiş modele özgü normalizasyon uygulayacağız. Ayrıca, torchvision kullanarak eğitim, doğrulama ve test için farklı dönüşümler tanımlayarak model genellemesini geliştireceğiz.

- Dönüşüm Fonksiyonları: ViT modeline uygun format ve boyutlara dönüştürerek verisetine bu dönüşümleri uygulayan fonksiyonlar implement edeceğiz.

- Veri Yükleme: Görüntü ve etiketleri uygun şekilde gruplamak için özel bir collate fonksiyonu tanımlayacak ve model eğitimi sırasında verimli yükleme ve gruplama için bir DataLoader oluşturacağız.

- Batch Hazırlığı: Örnek bir batch’in veri şekil bilgilerini alacak ve görüntüleyerek işlemenin doğru yapıldığını ve model girişine hazır olduğunu doğrulayacağız.

###
Etiket Eşleme

    id2label = {id:label for id, label in enumerate(train_ds.features['label'].names)}
    label2id = {label:id for id,label in id2label.items()}
    id2label, id2label[train_ds[0]['label']]

###
Görüntü İşleme

    from transformers import ViTImageProcessor

    model_name = "google/vit-large-patch16-224"
    processor = ViTImageProcessor.from_pretrained(model_name)

    from torchvision.transforms import CenterCrop, Compose, Normalize, RandomHorizontalFlip, RandomResizedCrop, ToTensor, Resize

    image_mean, image_std = processor.image_mean, processor.image_std
    size = processor.size["height"]

    normalize = Normalize(mean=image_mean, std=image_std)

    train_transforms = Compose([
        RandomResizedCrop(size),
        RandomHorizontalFlip(),
        ToTensor(),
        normalize,
    ])
    val_transforms = Compose([
        Resize(size),
        CenterCrop(size),
        ToTensor(),
        normalize,
    ])
    test_transforms = Compose([
        Resize(size),
        CenterCrop(size),
        ToTensor(),
        normalize,
    ])

###
Dönüşüm fonksiyonlarını oluşturma

    def apply_train_transforms(examples):
        examples['pixel_values'] = [train_transforms(image.convert("RGB")) for image in examples['image']]
        return examples

    def apply_val_transforms(examples):
        examples['pixel_values'] = [val_transforms(image.convert("RGB")) for image in examples['image']]
        return examples

    def apply_test_transforms(examples):
        examples['pixel_values'] = [val_transforms(image.convert("RGB")) for image in examples['image']]
        return examples

###
Dönüşüm fonksiyonlarını her bir sete uygulayalım

    train_ds.set_transform(apply_train_transforms)
    val_ds.set_transform(apply_val_transforms)
    test_ds.set_transform(apply_test_transforms)

    train_ds.features

    train_ds[0]

Bu işlem ile görüldüğü üzere piksel değerlerini tensorlara dönüştürmüş oluyoruz.

###
Veri Yükleme (DataLoader)

    import torch
    from torch.utils.data import DataLoader

    def collate_fn(examples):
        pixel_values = torch.stack([example["pixel_values"] for example in examples])
        labels = torch.tensor([example["label"] for example in examples])
        return {"pixel_values": pixel_values, "labels": labels}

    train_dl = DataLoader(train_ds, collate_fn=collate_fn, batch_size=4)

###
Batch Hazırlığı

    >>> batch = next(iter(train_dl))
    >>> for k,v in batch.items():
    ...   if isinstance(v, torch.Tensor):
    ...     print(k, v.shape)

    pixel_values torch.Size([4, 3, 224, 224])
    labels torch.Size([4])

Harika! Şimdi fine-tuning işlemi için hazırız.

##
Modeli Fine-Tune Etme

Şimdi modeli yapılandırıp fine-tune edeceğiz. İlk olarak, modeli belirli etiket eşlemeleri ve önceden eğitilmiş ayarlarla başlatarak boyut uyuşmazlıklarını ayarlıyoruz. Eğitim parametreleri, modelin öğrenme sürecini tanımlamak için ayarlandı; bu parametreler arasında kaydetme stratejisi, batch boyutları ve eğitim epoch’ları yer alıyor ve sonuçlar Weights & Biases aracılığıyla kaydediliyor. Ardından, Hugging Face Trainer, eğitim ve değerlendirme süreçlerini yönetmek için özel bir veri collator ve modelin dahili işlemcisini kullanarak başlatılacak. Son olarak, eğitim tamamlandıktan sonra modelin performansı bir test veriseti üzerinde değerlendirilecek ve doğruluk gibi metrikler yazdırılarak modelin başarısı değerlendirilecek.

İlk olarak, modelimizi çağırıyoruz.

    from transformers import ViTForImageClassification

    model = ViTForImageClassification.from_pretrained(model_name, id2label=id2label, label2id=label2id, ignore_mismatched_sizes=True)

Burada önemli bir detay var: `ignore_mismatched_sizes` parametresi.

Önceden eğitilmiş bir modeli yeni bir veriseti üzerinde fine-tune ederken, bazen görüntülerin giriş boyutu veya model mimarisi özellikleri (örneğin, sınıflandırma katmanındaki etiket sayısı) modelin başlangıçta eğitildiği yapı ile tam olarak uyuşmayabilir. Bu durum, genellikle ImageNet gibi doğal görüntüler üzerinde eğitilmiş bir modeli, tıbbi görüntüler veya özel kamera görüntüleri gibi tamamen farklı bir görüntü verisetine uygularken ortaya çıkar.

`ignore_mismatched_sizes` parametresini `True` olarak ayarlamak, modelin boyut farklılıklarına uyum sağlamasına ve hata oluşturmadan katmanlarını ayarlamasına olanak tanır.

Örneğin, bu model 1000 sınıf üzerinde eğitilmiştir ve `torch.Size([1000])` boyutunda bir giriş bekler. Verisetimiz ise 3 sınıfa sahiptir ve bu `torch.Size([3])` boyutundadır. Eğer bunu doğrudan modele verirsek, sınıf sayıları eşleşmediği için bir hata alırız.

Sonrasında, bu model için Google tarafından sağlanan eğitim argümanlarını tanımlayacağız.

(Opsiyonel) `report_to` parametresini `wandb` olarak ayarladığımız için metrikler Weights & Biases’te kaydedilecektir. W&B sizden bir API anahtarı isteyecek, bu yüzden bir hesap oluşturmalı ve API anahtarı almalısınız. Eğer istemiyorsanız, `report_to` parametresini kaldırabilirsiniz.

    from transformers import TrainingArguments, Trainer
    import numpy as np

    train_args = TrainingArguments(
        output_dir = "output-models",
        save_total_limit=2,
        report_to="wandb",
        save_strategy="epoch",
        evaluation_strategy="epoch",
        learning_rate=2e-5,
        per_device_train_batch_size=10,
        per_device_eval_batch_size=4,
        num_train_epochs=40,
        weight_decay=0.01,
        load_best_model_at_end=True,
        logging_dir='logs',
        remove_unused_columns=False,
    )

Artık `Trainer` ile fine-tuning sürecine başlayabiliriz.

    trainer = Trainer(
        model,
        train_args,
        train_dataset=train_ds,
        eval_dataset=val_ds,
        data_collator=collate_fn,
        tokenizer=processor,
    )
    trainer.train()

Epoch | Eğitim Kaybı | Doğrulama Kaybı | Doğruluk
--- | --- | --- | ---
40 | 0.174700 | 0.596288 | 0.903846

Fine-tuning süreci tamamlandı. Şimdi, modeli test verisetinde değerlendirmeye devam edelim.

    >>> outputs = trainer.predict(test_ds)
    >>> print(outputs.metrics)

    &#123;'test_loss': 0.40843912959098816, 'test_runtime': 4.9934, 'test_samples_per_second': 31.242, 'test_steps_per_second': 7.81}

`{'test_loss': 0.3219967782497406, 'test_accuracy': 0.9102564102564102, 'test_runtime': 4.0543, 'test_samples_per_second': 38.478, 'test_steps_per_second': 9.619}`

###
(Opsiyonel) Modeli Hub’a Yükleme

Modelimizi `push_to_hub` fonksiyonunu kullanarak Hugging Face Hub’a yükleyebiliriz.

    model.push_to_hub("your_model_name")

##
Sonuçlar

Fine-tuning işlemini tamamladık. Şimdi, modelimizin sınıfları nasıl tahmin ettiğini görelim. Bunun için scikit-learn’ün Confusion Matrix Display fonksiyonunu kullanarak Confusion Matrix’i görselleştirecek ve Recall Skoru göstereceğiz.

###
Confusion Matrix Nedir?

Confusion Matrix, bir algoritmanın, genellikle bir denetimli öğrenme modelinin, gerçek değerlerin bilindiği bir test veriseti üzerindeki performansını görselleştirmeye olanak tanıyan belirli bir tablo düzenidir. Özellikle bir sınıflandırma modelinin ne kadar iyi performans gösterdiğini kontrol etmek için kullanışlıdır, çünkü gerçek ve tahmin edilen etiketlerin sıklığını gösterir.

Hadi, modelimizin Confusion Matrix’ini çizelim.

    >>> from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

    >>> y_true = outputs.label_ids
    >>> y_pred = outputs.predictions.argmax(1)

    >>> labels = train_ds.features['label'].names
    >>> cm = confusion_matrix(y_true, y_pred)
    >>> disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=labels)
    >>> disp.plot(xticks_rotation=45)

###
Recall Skoru Nedir?

Recall skoru, sınıflandırma görevlerinde kullanılan bir performans metriğidir ve bir modelin bir veriseti içindeki tüm ilgili örnekleri doğru bir şekilde tanımlama yeteneğini ölçer. Özellikle recall, gerçek pozitiflerin model tarafından doğru bir şekilde pozitif olarak tahmin edilen oranını değerlendirir.

Hadi, scikit-learn kullanarak recall skorlarını yazdıralım.

    >>> from sklearn.metrics import recall_score

    >>> # Calculate the recall scores
    >>> # 'None' calculates recall for each class separately
    >>> recall = recall_score(y_true, y_pred, average=None)

    >>> # Print the recall for each class
    >>> for label, score in zip(labels, recall):
    ...     print(f'Recall for {label}: {score:.2f}')

    Recall for benign: 0.90
    Recall for malignant: 0.86
    Recall for normal: 0.78

`Recall for benign: 0.90, Recall for malignant: 0.86, Recall for normal: 0.78`

##
Sonuç

Bu cookbook’ta, bir ViT modelini bir tıbbi verisetiyle nasıl eğiteceğimizi ele aldık. Veriseti hazırlığı, görüntü ön işleme, model yapılandırması, eğitim, değerlendirme ve sonuçların görselleştirilmesi gibi önemli adımları kapsadık. Hugging Face’in Transformers kütüphanesi, scikit-learn ve PyTorch Torchvision’u kullanarak, verimli model eğitimi ve değerlendirme süreçlerini kolaylaştırdık. Bu süreç, modelin performansı ve biyomedikal görüntüleri doğru şekilde sınıflandırma yeteneği hakkında değerli bilgiler sunmaktadır.

Update on GitHub