Akademisyenler öncülüğünde matematik/fizik/bilgisayar bilimleri soru cevap platformu
1 beğenilme 0 beğenilmeme
1.7k kez görüntülendi
Önceden büyük bir metin verisiyle eğitilmiş BERT modeli nasıl kullanılır?
Veri Bilimi kategorisinde (1.8k puan) tarafından  | 1.7k kez görüntülendi

1 cevap

1 beğenilme 0 beğenilmeme

BERT modelleri son yıllarda doğal dil işleme alanında pek çok alanda diğer modellere üstünlük sağlamayı başardı. Bu modellerin iç işleyişi oldukça karmaşık ve eğitim için de ciddi miktarda veri ve işlemci gücü gerekiyor. Öte yandan hazır eğitilmiş pek çok BERT modeli var. Bunları kullanarak ve hatta yapmak istediğiniz işe bağlı olarak ek eğitim kümeleriyle besleyerek güzel sonuçlar elde etmek mümkün. İşin güzeli yakın zaman önce Türkçe bir BERT modeli de yayınlandı (https://github.com/stefan-it/turkish-bert) ve bu modele Python'da transformers paketiyle erişilebiliyor.

Biz bu modeli Türkçe bir film yorumları verisi üzerinde kullanacağız. Veri setine https://www.kaggle.com/mustfkeskin/turkish-movie-sentiment-analysis-dataset adresinden ulaşabilirsiniz, sadece kaggle'a üye olmanız yeterli. Bu veri setinden yorumları okuyup, yoruma karşılık gelen puana bakarak kullanıcı filmi beğenmiş mi beğenmemiş mi anlamaya çalışacağız. Bir çeşit sentiment (duygu) analizi.

Öncelikle kullanacağımız kütüphaneler.

import numpy as np
import pandas as pd
import torch
import transformers as ppb # pytorch transformers
from torch import nn
import torch.nn.functional as F
from torch import optim

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import confusion_matrix
from sklearn.metrics import roc_auc_score

import os

Veri setini yükleyip nasıl göründüğüne bakalım.

# Veri setlerini kodların olduğu dizin değil farklı bir yerde tutmakta fayda vardır
# Bu örnekte dizin yapısı
# - Proje Dizini
#  |
#  |- Python_Kodu
#  |- Data

# Kodun çalıştığı dizin
dirpath = os.getcwd()
# Kod dizinin içinde olduğu proje dizini
basedir = os.path.split(dirpath)[0]
# Veri setinin olduğu dizin
datafolder = os.path.join(basedir, 'Data/')

df = pd.read_csv(datafolder + 'turkish_movie_sentiment_dataset.csv') 
df.head()

## 
 	comment 	film_name 	point
0 	\n Jean Reno denince zate... 	Sevginin Gücü 	5,0
1 	\n Ekşın falan izlemek is... 	Sevginin Gücü 	5,0
2 	\n Bu yapım hakkında öyle... 	Sevginin Gücü 	5,0
3 	\n finali yeter... (sting... 	Sevginin Gücü 	5,0
4 	\n Jean Reno..\r\nbu adam... 	Sevginin Gücü 	5,0

BERT modellerinin kendilerine ait kelimelere ayırma yöntemleri olduğu için metni temizleme (\n'leri silme vs gibi) işlerini atlayabiliriz. Fakat point sütununda düzeltmemiz gereken bir sorun var, puanlar virgülle ayrılmış bunların nokta olması gerekir. Bunu yapalım ve bu örnek için üzerinde çalışmak için veriden küçük bir kısım seçelim. 

# (Hesaplama zamanını kısaltmak için) 1000 satırlık rastgele bir kısım seç
batch1 = df.sample(n=1000)

# Uzun yorumları çıkart (kısa yorumlara odaklanalım, burada yorum sayısı biraz daha azalacak)
mask = batch1['comment'].str.len() < 300 
batch2 = batch1.loc[mask]

# point sütunundaki virgülleri nokta olarak değiştir, bunları sayı (float) olarak kaydet 
batch2 = batch2.assign(pointc = [x.replace(',', '.') for x in batch2['point']])
batch2.loc[:,'pointc'] = batch2['pointc'].astype(float).copy()

# [0,3] arası puanlara 0, (3,5] arası puanlara 1 diyelim 
batch2 = batch2.assign(lab = pd.cut(batch2['pointc'],bins=[0,3,5],labels=[0,1]).values)
batch2["lab"] = pd.to_numeric(batch2["lab"], downcast="float")

batch2.head()

##
 	comment 	                film_name 	        point 	pointc 	lab
33401 	\n Filmi çok beğendiğimi ... 	Domino 	                2,5 	2.5 	0.0
25270 	\n film çok eğlenceliydi.... 	Sevgi Herşeydir 	2,5 	2.5 	0.0
72177 	\n ilk piranha filmi çok ... 	Pirana 3D 	        3,0 	3.0 	0.0
28791 	\n yaklaşık 2 ay önce izl... 	Arthur ile Minimoylar 	2,5 	2.5 	0.0
65170 	\n film güzeldide seks sa... 	Watchmen 	        0,5 	0.5 	0.0

lab sütununa 3'e kadar (3 dahil) puanlara karşılık 0, daha fazlasına 1 yazdık. Yani 3'ten fazla puan vermişse filmi beğenmiştir diye bir yorumladık puanları.  Şimdi transformers paketinden hazır modeli ve bu modelin kelime ayırıcısını (tokenizer) yükleyelim.

from transformers import AutoModel, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("dbmdz/distilbert-base-turkish-cased")
model = AutoModel.from_pretrained("dbmdz/distilbert-base-turkish-cased")

Şimdi bu tokenizerı kullanarak metinleri kelimelerine ayrıştıralım. Bunu yaparken (şu anda aslında ihtiyacımız olmasa da) her metne dolgu (padding) uygulayalım ki bütün yorumlar eşit uzunlukta olsun. Bu şekilde her bir yorumu tek seferde modele verebiliriz ve bu büyük veri setlerinde ciddi performans kazandırır. Tabii dolgulu metinlerde nereye bakması gerektiğini de modele söylemek lazım, bu da aşağıda attention_mask değişkeninde. 

# Kelimelere ayırma (tokenization)
tokenized = batch2['comment'].apply((lambda x: tokenizer.encode(x, add_special_tokens=True)))

# Maksimum yorum uzunluğunu bul
max_len = 0
for i in tokenized.values:
    if (len(i) > max_len):
        max_len = len(i)
# Maksimum yorum uzunluğuna göre 0'la dolgu yap ve dikkat maskesini (attention_mask) tanımla
padded = np.array([i + [0]*(max_len-len(i)) for i in tokenized.values])
attention_mask = np.where(padded != 0, 1, 0)

Şimdi metinleri BERT modeline verip çıktıları alabiliriz.

input_ids = torch.tensor(padded).to(torch.int64)  
attention_mask = torch.tensor(attention_mask).to(torch.int64)


with torch.no_grad():
    last_hidden_states = model(input_ids, attention_mask=attention_mask)

last_hidden_states değişkeni bir liste ve bu listenin ilk elemanında BERT modelin çıktıları var. Buna yakından bakarsanız 3 boyutlu bir torch tensor nesnesi olduğunu göreceksiniz:

last_hidden_states[0].size()

# 
torch.Size([668, 75, 768])

Bu boyutlardan ilki 668, elimizdeki yorum sayısı, ikincisi bu yorumların maksimum uzunluğu (çünkü bütün yorumları bu uzunluğa gelecek şekilde dolgu ile modele verdik), en sonuncusuda model çıktıları. Yani model her bir metindeki her bir token (kelime) için 768 boyutlu bir vektör üretiyor. 

BERT modelinin bir alameti farikası her metnin başına ve sonuna özel başlangıç ve bitiş işaretleri koyması. Başlangıç işaretine denk gelen model çıktıları ise, aslında modelin metnin tümüne verdiği özellikler olarak değerlendirilebilir. Modelin metinlere verdiği çıktılar olarak bunları kullanacağız. 

# Başlangıç karakterine denk gelen vektörü al

features = last_hidden_states[0][:,0,:].numpy()
labels = batch2['lab']

# Eğitim ve test kümelerine ayır
x_train, x_test, y_train, y_test = train_test_split(features, labels, test_size=0.2)

# Girdi ve çıktı değişkenlerini tensore çevir

x_train, x_test = map(
    torch.tensor, (x_train, x_test)
)

y_train = torch.tensor(y_train.values, dtype=torch.long)
y_test = torch.tensor(y_test.values, dtype=torch.long)

Şimdi basit bir model tanımlayıp eğitelim. Bu tek gizli katmanı olan bir neural network, aslında lojistik regresyona denk gelir.

input_size = len(features[0])
num_classes = len(labels.unique())

# Lineer neural net, 1 gizli katman
class M_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.lin = nn.Linear(input_size, num_classes)

    def forward(self, xb):
        return self.lin(xb)

# Hata fonksiyonu (loss function)
loss_func = F.cross_entropy

# Modeli optimizasyon algoritmasını seçerek başlat, 3e-2 = öğrenme hızı 
def get_model(lr):
    model = M_Logistic()
    return model, optim.SGD(model.parameters(), lr=lr)

m1, opt = get_model(3e-2)

# Model eğitim fonksiyonu

def fit(model, epochs, batch_size):
    for epoch in range(epochs):
        n = len(x_train)
        for i in range((n - 1) // batch_size + 1):
            start_i = i * batch_size
            end_i = start_i + batch_size
            xb = x_train[start_i:end_i]
            yb = y_train[start_i:end_i]
            pred = model(xb)
            loss = loss_func(pred, yb)

            loss.backward()
            opt.step()
            opt.zero_grad()
                
        if epoch % 500 == 499:
              print("Epoch: %d, loss: %1.5f" % (epoch, loss.item()))

# Eğitimi başlat
fit(m1,3000,100)

# 
Epoch: 499, loss: 0.95722
Epoch: 999, loss: 0.66753
Epoch: 1499, loss: 0.51791
Epoch: 1999, loss: 0.42640
Epoch: 2499, loss: 0.40514
Epoch: 2999, loss: 0.36442

Bu kadar gelmişken sonuçlara da bakalım.

preds1 = torch.argmax(m1(x_test), dim=1)
print(roc_auc_score(y_test, preds1))
confusion_matrix(y_test, preds1)

#
0.5868178596739899

[[15, 36],
 [10, 73]]

0.58 AUC muhteşem değil. Fakat neredeyse hiç bir şey yapmadan, ve çok çok küçük bir veri setiyle bu sonuca ulaşmış olmak oldukça iyi.

 

 

(1.8k puan) tarafından 
20,281 soru
21,819 cevap
73,492 yorum
2,504,871 kullanıcı