AI-For-Beginners/translations/id/lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.i...

577 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tugas klasifikasi teks\n",
"\n",
"Seperti yang telah disebutkan, kita akan fokus pada tugas klasifikasi teks sederhana berdasarkan dataset **AG_NEWS**, yaitu mengklasifikasikan judul berita ke dalam salah satu dari 4 kategori: Dunia, Olahraga, Bisnis, dan Sains/Teknologi.\n",
"\n",
"## Dataset\n",
"\n",
"Dataset ini sudah tersedia di modul [`torchtext`](https://github.com/pytorch/text), sehingga kita dapat mengaksesnya dengan mudah.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import torchtext\n",
"import os\n",
"import collections\n",
"os.makedirs('./data',exist_ok=True)\n",
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Di sini, `train_dataset` dan `test_dataset` berisi koleksi yang mengembalikan pasangan label (nomor kelas) dan teks masing-masing, sebagai contoh:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(3,\n",
" \"Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\\\band of ultra-cynics, are seeing green again.\")"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(train_dataset)[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Jadi, mari kita cetak 10 berita utama baru pertama dari dataset kita:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"**Sci/Tech** -> Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\band of ultra-cynics, are seeing green again.\n",
"**Sci/Tech** -> Carlyle Looks Toward Commercial Aerospace (Reuters) Reuters - Private investment firm Carlyle Group,\\which has a reputation for making well-timed and occasionally\\controversial plays in the defense industry, has quietly placed\\its bets on another part of the market.\n",
"**Sci/Tech** -> Oil and Economy Cloud Stocks' Outlook (Reuters) Reuters - Soaring crude prices plus worries\\about the economy and the outlook for earnings are expected to\\hang over the stock market next week during the depth of the\\summer doldrums.\n",
"**Sci/Tech** -> Iraq Halts Oil Exports from Main Southern Pipeline (Reuters) Reuters - Authorities have halted oil export\\flows from the main pipeline in southern Iraq after\\intelligence showed a rebel militia could strike\\infrastructure, an oil official said on Saturday.\n",
"**Sci/Tech** -> Oil prices soar to all-time record, posing new menace to US economy (AFP) AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections.\n"
]
}
],
"source": [
"for i,x in zip(range(5),train_dataset):\n",
" print(f\"**{classes[x[0]]}** -> {x[1]}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Karena dataset adalah iterator, jika kita ingin menggunakan data beberapa kali, kita perlu mengubahnya menjadi daftar:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"train_dataset = list(train_dataset)\n",
"test_dataset = list(test_dataset)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tokenisasi\n",
"\n",
"Sekarang kita perlu mengubah teks menjadi **angka** yang dapat direpresentasikan sebagai tensor. Jika kita ingin representasi tingkat kata, kita perlu melakukan dua hal:\n",
"* menggunakan **tokenizer** untuk membagi teks menjadi **token**\n",
"* membangun **kosakata** dari token-token tersebut.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['he', 'said', 'hello']"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
"tokenizer('He said: hello')"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" counter.update(tokenizer(line))\n",
"vocab = torchtext.vocab.vocab(counter, min_freq=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Dengan kosakata, kita dapat dengan mudah mengkodekan string yang telah di-tokenisasi menjadi sekumpulan angka:\n"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocab size if 95810\n"
]
},
{
"data": {
"text/plain": [
"[599, 3279, 97, 1220, 329, 225, 7368]"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vocab_size = len(vocab)\n",
"print(f\"Vocab size if {vocab_size}\")\n",
"\n",
"stoi = vocab.get_stoi() # dict to convert tokens to indices\n",
"\n",
"def encode(x):\n",
" return [stoi[s] for s in tokenizer(x)]\n",
"\n",
"encode('I love to play with my words')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Representasi teks Bag of Words\n",
"\n",
"Karena kata-kata mewakili makna, terkadang kita dapat memahami arti sebuah teks hanya dengan melihat kata-kata individualnya, tanpa memperhatikan urutannya dalam kalimat. Sebagai contoh, saat mengklasifikasikan berita, kata-kata seperti *cuaca*, *salju* kemungkinan besar menunjukkan *ramalan cuaca*, sementara kata-kata seperti *saham*, *dolar* akan mengarah pada *berita keuangan*.\n",
"\n",
"Representasi vektor **Bag of Words** (BoW) adalah representasi vektor tradisional yang paling umum digunakan. Setiap kata dikaitkan dengan indeks vektor, elemen vektor berisi jumlah kemunculan sebuah kata dalam dokumen tertentu.\n",
"\n",
"![Gambar yang menunjukkan bagaimana representasi vektor bag of words disimpan dalam memori.](../../../../../translated_images/id/bag-of-words-example.606fc1738f1d7ba9.webp) \n",
"\n",
"> **Note**: Anda juga dapat memikirkan BoW sebagai jumlah dari semua vektor one-hot-encoded untuk kata-kata individual dalam teks.\n",
"\n",
"Berikut adalah contoh cara menghasilkan representasi bag of words menggunakan pustaka python Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import CountVectorizer\n",
"vectorizer = CountVectorizer()\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Untuk menghitung vektor bag-of-words dari representasi vektor dataset AG_NEWS kita, kita dapat menggunakan fungsi berikut:\n"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"tensor([2., 1., 2., ..., 0., 0., 0.])\n"
]
}
],
"source": [
"vocab_size = len(vocab)\n",
"\n",
"def to_bow(text,bow_vocab_size=vocab_size):\n",
" res = torch.zeros(bow_vocab_size,dtype=torch.float32)\n",
" for i in encode(text):\n",
" if i<bow_vocab_size:\n",
" res[i] += 1\n",
" return res\n",
"\n",
"print(to_bow(train_dataset[0][1]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Catatan:** Di sini kita menggunakan variabel global `vocab_size` untuk menentukan ukuran default dari kosakata. Karena ukuran kosakata sering kali cukup besar, kita dapat membatasi ukuran kosakata pada kata-kata yang paling sering digunakan. Cobalah menurunkan nilai `vocab_size` dan menjalankan kode di bawah ini, lalu lihat bagaimana hal itu memengaruhi akurasi. Anda seharusnya mengharapkan penurunan akurasi, tetapi tidak terlalu drastis, demi kinerja yang lebih tinggi.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Melatih Klasifikasi BoW\n",
"\n",
"Sekarang setelah kita mempelajari cara membangun representasi Bag-of-Words dari teks kita, mari kita latih sebuah klasifikasi di atasnya. Pertama, kita perlu mengonversi dataset kita untuk pelatihan sedemikian rupa sehingga semua representasi vektor posisi diubah menjadi representasi bag-of-words. Hal ini dapat dilakukan dengan meneruskan fungsi `bowify` sebagai parameter `collate_fn` ke `DataLoader` standar dari torch:\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"from torch.utils.data import DataLoader\n",
"import numpy as np \n",
"\n",
"# this collate function gets list of batch_size tuples, and needs to \n",
"# return a pair of label-feature tensors for the whole minibatch\n",
"def bowify(b):\n",
" return (\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([to_bow(t[1]) for t in b])\n",
" )\n",
"\n",
"train_loader = DataLoader(train_dataset, batch_size=16, collate_fn=bowify, shuffle=True)\n",
"test_loader = DataLoader(test_dataset, batch_size=16, collate_fn=bowify, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sekarang mari kita definisikan jaringan neural classifier sederhana yang mengandung satu lapisan linear. Ukuran vektor input sama dengan `vocab_size`, dan ukuran output sesuai dengan jumlah kelas (4). Karena kita sedang menyelesaikan tugas klasifikasi, fungsi aktivasi akhirnya adalah `LogSoftmax()`.\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"net = torch.nn.Sequential(torch.nn.Linear(vocab_size,4),torch.nn.LogSoftmax(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sekarang kita akan mendefinisikan loop pelatihan PyTorch standar. Karena dataset kita cukup besar, untuk tujuan pengajaran kita akan melatih hanya selama satu epoch, dan terkadang bahkan kurang dari satu epoch (dengan menentukan parameter `epoch_size` memungkinkan kita untuk membatasi pelatihan). Kita juga akan melaporkan akurasi pelatihan yang terakumulasi selama pelatihan; frekuensi pelaporan ditentukan menggunakan parameter `report_freq`.\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.NLLLoss(),epoch_size=None, report_freq=200):\n",
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
" net.train()\n",
" total_loss,acc,count,i = 0,0,0,0\n",
" for labels,features in dataloader:\n",
" optimizer.zero_grad()\n",
" out = net(features)\n",
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" total_loss+=loss\n",
" _,predicted = torch.max(out,1)\n",
" acc+=(predicted==labels).sum()\n",
" count+=len(labels)\n",
" i+=1\n",
" if i%report_freq==0:\n",
" print(f\"{count}: acc={acc.item()/count}\")\n",
" if epoch_size and count>epoch_size:\n",
" break\n",
" return total_loss.item()/count, acc.item()/count"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.8028125\n",
"6400: acc=0.8371875\n",
"9600: acc=0.8534375\n",
"12800: acc=0.85765625\n"
]
},
{
"data": {
"text/plain": [
"(0.026090790722161722, 0.8620069296375267)"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"train_epoch(net,train_loader,epoch_size=15000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BiGrams, TriGrams dan N-Grams\n",
"\n",
"Salah satu keterbatasan pendekatan bag of words adalah bahwa beberapa kata merupakan bagian dari ekspresi multi kata. Sebagai contoh, kata 'hot dog' memiliki arti yang sangat berbeda dibandingkan dengan kata 'hot' dan 'dog' dalam konteks lain. Jika kita selalu merepresentasikan kata 'hot' dan 'dog' dengan vektor yang sama, hal ini dapat membingungkan model kita.\n",
"\n",
"Untuk mengatasi hal ini, **representasi N-gram** sering digunakan dalam metode klasifikasi dokumen, di mana frekuensi setiap kata, pasangan kata, atau tiga kata menjadi fitur yang berguna untuk melatih classifier. Dalam representasi bigram, misalnya, kita akan menambahkan semua pasangan kata ke dalam kosakata, selain kata-kata asli.\n",
"\n",
"Berikut adalah contoh cara menghasilkan representasi bag of words bigram menggunakan Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocabulary:\n",
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
]
},
{
"data": {
"text/plain": [
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
" dtype=int64)"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"bigram_vectorizer.fit_transform(corpus)\n",
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Kelemahan utama pendekatan N-gram adalah ukuran kosakata yang mulai tumbuh dengan sangat cepat. Dalam praktiknya, kita perlu menggabungkan representasi N-gram dengan beberapa teknik reduksi dimensi, seperti *embeddings*, yang akan kita bahas di unit berikutnya.\n",
"\n",
"Untuk menggunakan representasi N-gram dalam dataset **AG News** kita, kita perlu membangun kosakata ngram khusus:\n"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Bigram vocabulary length = 1308842\n"
]
}
],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" l = tokenizer(line)\n",
" counter.update(torchtext.data.utils.ngrams_iterator(l,ngrams=2))\n",
" \n",
"bi_vocab = torchtext.vocab.vocab(counter, min_freq=1)\n",
"\n",
"print(\"Bigram vocabulary length = \",len(bi_vocab))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Kita bisa menggunakan kode yang sama seperti di atas untuk melatih classifier, namun, cara ini akan sangat tidak efisien dalam penggunaan memori. Pada unit berikutnya, kita akan melatih classifier bigram menggunakan embeddings.\n",
"\n",
"> **Catatan:** Anda hanya boleh menyertakan ngram yang muncul dalam teks lebih dari jumlah yang ditentukan. Hal ini akan memastikan bahwa bigram yang jarang muncul akan diabaikan, dan secara signifikan mengurangi dimensi. Untuk melakukannya, atur parameter `min_freq` ke nilai yang lebih tinggi, dan perhatikan perubahan panjang kosakata.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Frekuensi Istilah dan Frekuensi Dokumen Terbalik (TF-IDF)\n",
"\n",
"Dalam representasi BoW, kemunculan kata diberi bobot yang sama, terlepas dari kata itu sendiri. Namun, jelas bahwa kata-kata yang sering muncul, seperti *a*, *in*, dll., jauh kurang penting untuk klasifikasi dibandingkan istilah-istilah khusus. Faktanya, dalam sebagian besar tugas NLP, beberapa kata lebih relevan daripada yang lain.\n",
"\n",
"**TF-IDF** adalah singkatan dari **term frequencyinverse document frequency**. Ini adalah variasi dari bag of words, di mana alih-alih nilai biner 0/1 yang menunjukkan kemunculan sebuah kata dalam dokumen, digunakan nilai floating-point yang terkait dengan frekuensi kemunculan kata dalam korpus.\n",
"\n",
"Secara lebih formal, bobot $w_{ij}$ dari sebuah kata $i$ dalam dokumen $j$ didefinisikan sebagai:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"di mana\n",
"* $tf_{ij}$ adalah jumlah kemunculan $i$ dalam $j$, yaitu nilai BoW yang telah kita lihat sebelumnya\n",
"* $N$ adalah jumlah dokumen dalam koleksi\n",
"* $df_i$ adalah jumlah dokumen yang mengandung kata $i$ dalam seluruh koleksi\n",
"\n",
"Nilai TF-IDF $w_{ij}$ meningkat secara proporsional dengan jumlah kemunculan sebuah kata dalam dokumen dan disesuaikan dengan jumlah dokumen dalam korpus yang mengandung kata tersebut, yang membantu mengoreksi fakta bahwa beberapa kata muncul lebih sering daripada yang lain. Sebagai contoh, jika sebuah kata muncul dalam *setiap* dokumen dalam koleksi, $df_i=N$, dan $w_{ij}=0$, sehingga istilah-istilah tersebut akan sepenuhnya diabaikan.\n",
"\n",
"Anda dapat dengan mudah membuat vektorisasi TF-IDF dari teks menggunakan Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
" 0. , 0. , 0. , 0. , 0. ,\n",
" 0. ]])"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Kesimpulan\n",
"\n",
"Namun, meskipun representasi TF-IDF memberikan bobot frekuensi pada berbagai kata, mereka tidak mampu merepresentasikan makna atau urutan. Seperti yang dikatakan oleh ahli linguistik terkenal J. R. Firth pada tahun 1935, “Makna lengkap sebuah kata selalu bersifat kontekstual, dan tidak ada studi tentang makna yang terlepas dari konteks dapat dianggap serius.”. Kita akan mempelajari nanti dalam kursus ini bagaimana menangkap informasi kontekstual dari teks menggunakan pemodelan bahasa.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Penafian**: \nDokumen ini telah diterjemahkan menggunakan layanan penerjemahan AI [Co-op Translator](https://github.com/Azure/co-op-translator). Meskipun kami berusaha untuk memberikan hasil yang akurat, harap diingat bahwa terjemahan otomatis mungkin mengandung kesalahan atau ketidakakuratan. Dokumen asli dalam bahasa aslinya harus dianggap sebagai sumber yang otoritatif. Untuk informasi yang bersifat kritis, disarankan menggunakan jasa penerjemahan profesional oleh manusia. Kami tidak bertanggung jawab atas kesalahpahaman atau penafsiran yang keliru yang timbul dari penggunaan terjemahan ini.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "7b9040985e748e4e2d4c689892456ad7",
"translation_date": "2025-08-29T16:37:30+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "id"
}
},
"nbformat": 4,
"nbformat_minor": 2
}