Pytorch – Studi Kasus Penerapan NLP pada Judul Berita

By | November 27, 2022
Print Friendly, PDF & Email
433 Views

Penerapan NLP pada Judul Berita – Setelah kita belajar Belajar Natural Languange Processing NLP dimulai dari tokenisasi, pembuatan vocab serta mengubah token2 tersebut menjadi tensor. Nah kita akan belajar menerapkan Penerapan Natural Language Processing untuk Judul Berita pada kasus real. Salah satunya yaitu mengklasifikasikan kategori berita berdasarkan judul berita.

Salah satu portal berita yang aktif menyajikan berita terkini yaitu Kompas. Kompas.com mempunyai banyak kanal berita seperti tekno, kesehatan, otomotif, lifestyle, dan edukasi. Misalkan saja untuk kanal berita tekno dan edukasi mempunyai link seperti berikut

  1. tekno berisikan link yang membahas tekno yaitu https://tekno.kompas.com
  2. edukasi berisikan link yang membahas edukasi yaitu https://edukasi.kompas.com\

Tentu untuk mendata setiap link dan judul berita tidak perlu dilakukan secara manual. Kita bisa menarik link tersebut dengan cara crawler/scrapping, saya sudah pernah membahas mengenai scrapping Scraping Situs Secara Rekursif yang menggunakan java. Berdasarkan algoritma tersebut, saya akan membuat scrapping website menggunakan package BeautifulSoap4 di Python.

Nggak perlu ribet2 seperti di Java yang menggunakan Sqlite sebagai database penyimpanan. Nanti di python menggunakan pandas serta disimpan dalam bentuk *.csv saja. (Silahkan kirim email bila ingin mendapatkan kode buat scrapping / crawler website)

Catatan untuk proses Rekursif di Python

Karena proses perayapan menggunakan algoritma rekursif, ada batasan yang kalian harus ketahui. Algoritma yang saya buat tersebut pernah saya setting untuk merayapi 3 ribu link akan tetapi terjadi error Fatal Python error cannto recover from stack overflow

Ternyata batas default kedalaman rekursif yaitu 1000 saja. Kita bisa cek berapa batas maksimal kedalam rekursif

import sys
sys.getrecursionlimit()

import sys
sys.getrecursionlimit()

Return the current value of the recursion limit,
the maximum depth of the Python interpreter stack.
This limit prevents infinite recursion from causing an overflow of the C stack and crashing Python.
It can be set by setrecursionlimit().

Untuk setting nya bisa kalian dinaikan menjadi 10 ribu

https://stackoverflow.com/questions/3323001/what-is-the-maximum-recursion-depth-in-python-and-how-to-increase-it

sys.setrecursionlimit(10000)

Tapi ingat hati2 dengan batas yang besar karena akan banyak sekali memakan RAM dan sebagian dialihkan ke storage/Swap RAM. Kode yang saya buat sudah diberikan batas maksimal link yang bisa dirayapi.  Algoritma tersebut saya kasih batas==500 link saja yang dirayapi dan akan menyimpan title, link, kategori, dan kelas. Tiap kategori akan disimpan dalam bentuk *.csv satu-persatu, jadi ada 5 kategori yaitu

  1. edukasi https://edukasi.kompas.com
  2. kesehatan https://health.kompas.com
  3. lifestyle https://lifestyle.kompas.com
  4. otomotif https://otomotif.kompas.com
  5. tekno https://tekno.kompas.com

Berikut potongan kode yang digunakan untuk merayap situs http://kompas.com disitu tertera 500 link saya yang akan dirayapi yang memakan waktu berdasarkan koneksi internet yang kalian punya. Kalian bisa tingkatkan sampai ribuan link bila memungkinkan, tapi waktunya akan cukup lama.

info = list()
info.append({"url_master":"https://health.kompas.com/","kategori":"kesehatan"})
info.append({"url_master":"https://tekno.kompas.com/","kategori":"tekno"})
info.append({"url_master":"https://otomotif.kompas.com/","kategori":"otomotif"})
info.append({"url_master":"https://lifestyle.kompas.com/","kategori":"lifestyle"})
info.append({"url_master":"https://edukasi.kompas.com/","kategori":"edukasi"})

kelas  = 1
jumlah_link_yang_dirayapi = 500
for f in info:
    url_master = f["url_master"]
    kategori = f["kategori"]
    file_simpan = kategori+".csv"
    #lakukan perayapan
    perayapan(url_master,parse.urlparse(url_master).netloc,0,jumlah_link_yang_dirayapi)    
    dataset = pd.DataFrame(dataset)    
    dataset["kategori"] = kategori
    dataset["kelas"] = kelas    
    #simpan dalam bentuk CSV
    dataset.to_csv("kompas 500/"+str(kelas)+". "+file_simpan)
    #mulai baru lagi
    title_visited = list()
    url_visited = list() 
    dataset = list()
    kelas = kelas + 1

Berikut file csv hasil scrapping yang bisa kalian download

  1. 1. kesehatan.csv
  2. 2. tekno.csv
  3. 3. otomotif.csv
  4. 4. lifestyle.csv
  5. 5. edukasi.csv

title dan kategori diatas didasarkan pada link, saya lihat di kompas.com belum cukup rapi/tidak nyambung antara judul dan kategori beritanya. Saran saya adalah kalian bisa menggunakan portal berita yang lain sebagai referensi

Contoh dataset 1. kesehatan.csv

dataset NLP Kompas Kesehatan

Generated by wpDataTables

Oiya ada satu PR lagi yang harus dikerjakan yaitu agar hasilnya bagus. Agar judul berita tersebut lebih bersih, saya hapus record yang mengandung kata kompas, serta menyatukan semua file csv tersebut menjadi satu, menghapus angka, serta yang lain. Sayangnya penggunaan regex secara langsung pada pandas belum bisa digunakan, oleh hal tersebut saya terpaksa menggunakan iterasi untuk regex nya.

Contoh judul berita yang akan dihapus

  • Halaman anda tidak ditemukan – Kompas.com
  • Berita Kesehatan Terkini – Kompas.com
dataset = pd.DataFrame(columns={"tajuk","kategori","kelas"})
kategori_tajuk = list()
for f in glob.glob("kompas 500/*.csv"):
    data_mentah = pd.read_csv(f)    
    dataset = pd.concat([dataset,data_mentah])    
    kategori_tajuk.append(os.path.basename(f).split(".")[1])

#buang kolom yang tidak perlu
#lakukan pengolahan
dataset["tajuk"] = dataset["tajuk"].str.lower()
dataset["kelas"] = dataset["kelas"]-1
#hilangkan record yang mengandung kata kompas
dataset.drop(dataset.index[dataset['tajuk'].str.contains("kompas.com")],axis=0,inplace=True)
#hilangkan simbol dan angka
regex = lambda x: re.sub('[^A-Za-z]+', ' ',x)
for i in range(0,len(dataset)):
    dataset.iloc[i]["tajuk"] = regex(dataset.iloc[i]["tajuk"])
    dataset.iloc[i]["tajuk"] = dataset.iloc[i]["tajuk"].strip() #hilangkan space depan dan belakang

Dataset CSV tersebut masih mentah tentu akan sulit digunakan kedalam proses ke Pytorch, oleh sebab itu kita akan ubah dataset kedalam Data Loader agar seragam dengan framework PyTorch.

Create Dataset

Melalui Framework Pytorch kita akan dimudahkan dalam membuat dataset. Nggak perlu pusing-pusing mau menggunakan dataframe, list, vektor atau yang lainnya. Cukup inheritance class Datasets pada Pytorch maka dataset yang masih berbentuk *.csv akan dikonvert kedalam class tersebut yang berbentuk iterator – https://towardsdatascience.com/how-to-use-datasets-and-dataloader-in-pytorch-for-custom-text-data-270eed7f7c00. Pada bab sebelumnya kita juga menggunakan collate_batch. Berikut class DataLoader yang kita buat

#bikin data loader
from torch.utils.data import Dataset, DataLoader

class Datasetku(Dataset):
    def __init__(self, text, labels):
        self.text = text
        self.labels = labels        
    def __len__(self):
        return len(self.labels)
    def __getitem__(self, idx):
        label = self.labels[idx]
        text = self.text[idx]
        #sample = {"label": label,"text": text}
        return label,text

dataset_loader = Datasetku(list(dataset["tajuk"]),list(dataset["kelas"]))

Pembuatan Vocab

Langkah selanjutnya, kita akan buat vocab. Oiya untuk tokenizer bawaan dari Pytorch masih basic_english. Tapi bisa kok dipakai untuk bahasa lain.

from torchtext.data.utils import get_tokenizer
from torchtext.vocab import build_vocab_from_iterator

#untuk memecah kalimat menjadi suku kata
tokenizer = get_tokenizer('basic_english')

def yield_tokens(list_kalimat):
    for _, text in list_kalimat:
        yield tokenizer(text)

#membuat database vocab/suku kata
vocab = build_vocab_from_iterator(yield_tokens(dataset_loader), specials=["<unk>"])
vocab.set_default_index(vocab["<unk>"])

Persiapan dataset sebagai data training

Langkah selanjutnya, kita menggunakan lambda untuk function yang bersifat iterasi

text_pipeline = lambda x: vocab(tokenizer(x)) #feature
label_pipeline = lambda x: int(x) #class/target

Sama seperti kode sebelumnya, kita membuat function collate_batch untuk loading dataset secara batch

#membuat function untuk data loader per batch
import torch
from torch.utils.data import DataLoader
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

def collate_batch(batch):
    #untuk menangani setiap panjang kalimat yang berbeda-beda
    #maka digunakan sebuah offsets
    #offset index 0 bernilai 0
    label_list, text_list, offsets = [], [], [0]
    for (_label, _text) in batch:
        #kelas/target
        label_list.append(label_pipeline(_label))
        #feature dibuat tensor yang isinya index token dalam vocab
        processed_text = torch.tensor(text_pipeline(_text), dtype=torch.int64)
        text_list.append(processed_text)
        #dapatkan ukuran/size tiap tensor kalimat
        offsets.append(processed_text.size(0))
    #buat tensor kelas/target    
    label_list = torch.tensor(label_list, dtype=torch.int64)
    
    #buat tensor offsets
    #lakukan total kumulatif
    #tapi index terakhir tidak ikut dijumlahkan!
    offsets = torch.tensor(offsets[:-1]).cumsum(dim=0)  
    
    #disusun/digabung
    text_list = torch.cat(text_list)
    return label_list.to(device), text_list.to(device), offsets.to(device)


data_train = DataLoader(dataset_loader, batch_size=2, shuffle=False, collate_fn=collate_batch)

Pembuatan Model untuk Natural Languange Processing Classification

Langkah selanjutnya buat model machine learning untuk klasifikasi teks

from torch import nn

class TextClassificationModel(nn.Module):
    def __init__(self, vocab_size, embed_dim, num_class):
        super(TextClassificationModel, self).__init__()
        self.embedding = nn.EmbeddingBag(vocab_size, embed_dim, sparse=True)
        self.fc = nn.Linear(embed_dim, num_class)
        self.init_weights()

    def init_weights(self):
        initrange = 0.5
        self.embedding.weight.data.uniform_(-initrange, initrange)
        self.fc.weight.data.uniform_(-initrange, initrange)
        self.fc.bias.data.zero_()

    def forward(self, text, offsets):
        embedded = self.embedding(text, offsets)
        return self.fc(embedded)
    
    
    

num_class = len(set([label for (label, text) in dataset_loader]))
vocab_size = len(vocab)
emsize = 8
model = TextClassificationModel(vocab_size, emsize, num_class).to(device)

Pembuatan training dan evaluate

Serta membuat function train dan evaluate

#Hyperparameters
EPOCHS = 10 # epoch
LR = 5  # learning rate
BATCH_SIZE = 2 # batch size for training

import time
optimizer = torch.optim.SGD(model.parameters(), lr=LR)
criterion = torch.nn.CrossEntropyLoss()
def train(data,epoch):
    model.train()
    total_acc, total_count = 0, 0
    log_interval = 500
    start_time = time.time()

    for idx, (label, text, offsets) in enumerate(data):
        optimizer.zero_grad()
        predicted_label = model(text, offsets)
        loss = criterion(predicted_label, label)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 0.1)
        optimizer.step()
        total_acc += (predicted_label.argmax(1) == label).sum().item()
        total_count += label.size(0)
        if idx % log_interval == 0 and idx > 0:
            elapsed = time.time() - start_time
            print('| epoch {:3d} | {:5d}/{:5d} batches '
                  '| accuracy {:8.3f}'.format(epoch, idx, len(data),
                                              total_acc/total_count))
            total_acc, total_count = 0, 0
            start_time = time.time()

def evaluate(dataloader):
    model.eval()
    total_acc, total_count = 0, 0

    with torch.no_grad():
        for idx, (label, text, offsets) in enumerate(dataloader):
            predicted_label = model(text, offsets)
            loss = criterion(predicted_label, label)
            total_acc += (predicted_label.argmax(1) == label).sum().item()
            total_count += label.size(0)
    return total_acc/total_count

Sesi Training

Sekarang saat nya kita lakukan training (oiya karena nya datanya sedikit, maka training dan testing menggunakan data yang sama)

from torch.utils.data.dataset import random_split
from torchtext.data.functional import to_map_style_dataset


criterion           = torch.nn.CrossEntropyLoss()
optimizer           = torch.optim.SGD(model.parameters(), lr=LR)
scheduler           = torch.optim.lr_scheduler.StepLR(optimizer, 1.0, gamma=0.1)
total_accu          = None
train_iter          = dataset_loader
test_iter           = dataset_loader
train_dataset       = to_map_style_dataset(train_iter)
test_dataset        = to_map_style_dataset(test_iter)
num_train           = int(len(train_dataset) * 0.95)
split_train_, split_valid_ = \
    random_split(train_dataset, [num_train, len(train_dataset) - num_train])

train_dataloader = DataLoader(split_train_, batch_size=BATCH_SIZE,
                              shuffle=True, collate_fn=collate_batch)
valid_dataloader = DataLoader(split_valid_, batch_size=BATCH_SIZE,
                              shuffle=True, collate_fn=collate_batch)
test_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE,
                             shuffle=True, collate_fn=collate_batch)

for epoch in range(1, EPOCHS + 1):
    epoch_start_time = time.time()
    train(train_dataloader,epoch)
    accu_val = evaluate(valid_dataloader)
    if total_accu is not None and total_accu > accu_val:
      scheduler.step()
    else:
       total_accu = accu_val
    print('-' * 59)
    print('| end of epoch {:3d} | time: {:5.2f}s | '
          'valid accuracy {:8.3f} '.format(epoch,
                                           time.time() - epoch_start_time,
                                           accu_val))
    print('-' * 59)




##
print('Checking the results of test dataset.')
accu_test = evaluate(test_dataloader)
print('test accuracy {:8.3f}'.format(accu_test))

hasil run kode diatas yaitu

| epoch   1 |   500/ 1090 batches | accuracy    0.998
| epoch   1 |  1000/ 1090 batches | accuracy    0.995
-----------------------------------------------------------
| end of epoch   1 | time:  1.59s | valid accuracy    0.913 
-----------------------------------------------------------
| epoch   2 |   500/ 1090 batches | accuracy    0.997
| epoch   2 |  1000/ 1090 batches | accuracy    0.997
-----------------------------------------------------------
| end of epoch   2 | time:  1.48s | valid accuracy    0.913 
-----------------------------------------------------------
| epoch   3 |   500/ 1090 batches | accuracy    0.997
| epoch   3 |  1000/ 1090 batches | accuracy    0.996
-----------------------------------------------------------
| end of epoch   3 | time:  1.36s | valid accuracy    0.913 
-----------------------------------------------------------
| epoch   4 |   500/ 1090 batches | accuracy    0.995
| epoch   4 |  1000/ 1090 batches | accuracy    0.999
-----------------------------------------------------------
| end of epoch   4 | time:  1.41s | valid accuracy    0.913 
-----------------------------------------------------------
| epoch   5 |   500/ 1090 batches | accuracy    0.998
| epoch   5 |  1000/ 1090 batches | accuracy    0.995
-----------------------------------------------------------
| end of epoch   5 | time:  1.38s | valid accuracy    0.913 
-----------------------------------------------------------
| epoch   6 |   500/ 1090 batches | accuracy    0.997
| epoch   6 |  1000/ 1090 batches | accuracy    0.996
-----------------------------------------------------------
| end of epoch   6 | time:  1.40s | valid accuracy    0.913 
-----------------------------------------------------------
| epoch   7 |   500/ 1090 batches | accuracy    0.997
| epoch   7 |  1000/ 1090 batches | accuracy    0.996
-----------------------------------------------------------
| end of epoch   7 | time:  1.44s | valid accuracy    0.913 
-----------------------------------------------------------
| epoch   8 |   500/ 1090 batches | accuracy    0.996
| epoch   8 |  1000/ 1090 batches | accuracy    0.998
-----------------------------------------------------------
| end of epoch   8 | time:  1.37s | valid accuracy    0.913 
-----------------------------------------------------------
| epoch   9 |   500/ 1090 batches | accuracy    0.997
| epoch   9 |  1000/ 1090 batches | accuracy    0.996
-----------------------------------------------------------
| end of epoch   9 | time:  1.40s | valid accuracy    0.913 
-----------------------------------------------------------
| epoch  10 |   500/ 1090 batches | accuracy    1.000
| epoch  10 |  1000/ 1090 batches | accuracy    0.993
-----------------------------------------------------------
| end of epoch  10 | time:  1.41s | valid accuracy    0.913 
-----------------------------------------------------------

dengan accuracy 93%

Checking the results of test dataset.
test accuracy    0.993

Contoh Penerapan NLP pada Judul Berita

Yuk kita coba Penerapan Natural Language Processing untuk Judul Berita

def predict(text, text_pipeline):
    with torch.no_grad():
        #bersihkan kata
        text = regex(text)        
        text = text.lower()
        #text = text.replace("okezone"," ")
        #text = text.replace("techno"," ")
        text = text.strip()
        # print(text)
        text = torch.tensor(text_pipeline(text))
        output = model(text, torch.tensor([0]))
        return output.argmax(1).item()

menggunakan tajuk berikut

Bisnis PC Diprediksi Menurun, Lebih Lesu dari Tablet dan Ponsel

tajuk = "Bisnis PC Diprediksi Menurun, Lebih Lesu dari Tablet dan Ponsel"
model = model.to("cpu")
print("tajuk ",tajuk)
print("prediksi tajuk berita: %s" %kategori_tajuk[predict(tajuk, text_pipeline)])

hasilnya tajuk berita diatas adalah tekno

tajuk  Bisnis PC Diprediksi Menurun, Lebih Lesu dari Tablet dan Ponsel
prediksi tajuk berita:  tekno

Penerapan Natural Language Processing untuk Judul Berita berjalan dengan baik, kalian bisa meningkatkan akurasinya dengan menambah jumlah link ataupun menggunakan referensi website lain seperti www.detik.com, www.okezone.com agar tajuk berita lebih banyak dan menambah akurasi

See also  Pytorch General Dataset

Leave a Reply

Your email address will not be published.




Enter Captcha Here :