AI-For-Beginners/translations/id/lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.ipynb

414 lines
22 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Jaringan Generatif\n",
"\n",
"Recurrent Neural Networks (RNNs) dan varian sel ber-gate seperti Long Short Term Memory Cells (LSTMs) dan Gated Recurrent Units (GRUs) menyediakan mekanisme untuk pemodelan bahasa, yaitu mereka dapat mempelajari urutan kata dan memberikan prediksi untuk kata berikutnya dalam sebuah urutan. Hal ini memungkinkan kita menggunakan RNN untuk **tugas generatif**, seperti generasi teks biasa, terjemahan mesin, dan bahkan pembuatan keterangan gambar.\n",
"\n",
"Dalam arsitektur RNN yang kita bahas di unit sebelumnya, setiap unit RNN menghasilkan keadaan tersembunyi berikutnya sebagai output. Namun, kita juga dapat menambahkan output lain ke setiap unit rekuren, yang memungkinkan kita menghasilkan sebuah **urutan** (yang panjangnya sama dengan urutan asli). Selain itu, kita dapat menggunakan unit RNN yang tidak menerima input di setiap langkah, dan hanya mengambil beberapa vektor keadaan awal, lalu menghasilkan urutan output.\n",
"\n",
"Di notebook ini, kita akan fokus pada model generatif sederhana yang membantu kita menghasilkan teks. Untuk kesederhanaan, mari kita membangun **jaringan tingkat karakter**, yang menghasilkan teks huruf demi huruf. Selama pelatihan, kita perlu mengambil korpus teks, dan membaginya menjadi urutan huruf.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading dataset...\n",
"Building vocab...\n"
]
}
],
"source": [
"import torch\n",
"import torchtext\n",
"import numpy as np\n",
"from torchnlp import *\n",
"train_dataset,test_dataset,classes,vocab = load_dataset()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Membangun Kosakata Karakter\n",
"\n",
"Untuk membangun jaringan generatif tingkat karakter, kita perlu memecah teks menjadi karakter individu, bukan kata. Hal ini dapat dilakukan dengan mendefinisikan tokenizer yang berbeda:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocabulary size = 82\n",
"Encoding of 'a' is 1\n",
"Character with code 13 is c\n"
]
}
],
"source": [
"def char_tokenizer(words):\n",
" return list(words) #[word for word in words]\n",
"\n",
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" counter.update(char_tokenizer(line))\n",
"vocab = torchtext.vocab.vocab(counter)\n",
"\n",
"vocab_size = len(vocab)\n",
"print(f\"Vocabulary size = {vocab_size}\")\n",
"print(f\"Encoding of 'a' is {vocab.get_stoi()['a']}\")\n",
"print(f\"Character with code 13 is {vocab.get_itos()[13]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Mari kita lihat contoh bagaimana kita dapat mengenkripsi teks dari dataset kita:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"tensor([ 0, 1, 2, 2, 3, 4, 5, 6, 3, 7, 8, 1, 9, 10, 3, 11, 2, 1,\n",
" 12, 3, 7, 1, 13, 14, 3, 15, 16, 5, 17, 3, 5, 18, 8, 3, 7, 2,\n",
" 1, 13, 14, 3, 19, 20, 8, 21, 5, 8, 9, 10, 22, 3, 20, 8, 21, 5,\n",
" 8, 9, 10, 3, 23, 3, 4, 18, 17, 9, 5, 23, 10, 8, 2, 2, 8, 9,\n",
" 10, 24, 3, 0, 1, 2, 2, 3, 4, 5, 9, 8, 8, 5, 25, 10, 3, 26,\n",
" 12, 27, 16, 26, 2, 27, 16, 28, 29, 30, 1, 16, 26, 3, 17, 31, 3, 21,\n",
" 2, 5, 9, 1, 23, 13, 32, 16, 27, 13, 10, 24, 3, 1, 9, 8, 3, 10,\n",
" 8, 8, 27, 16, 28, 3, 28, 9, 8, 8, 16, 3, 1, 28, 1, 27, 16, 6])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def enc(x):\n",
" return torch.LongTensor(encode(x,voc=vocab,tokenizer=char_tokenizer))\n",
"\n",
"enc(train_dataset[0][1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Melatih RNN Generatif\n",
"\n",
"Cara kita melatih RNN untuk menghasilkan teks adalah sebagai berikut. Pada setiap langkah, kita akan mengambil urutan karakter dengan panjang `nchars`, dan meminta jaringan untuk menghasilkan karakter keluaran berikutnya untuk setiap karakter masukan:\n",
"\n",
"![Gambar menunjukkan contoh RNN menghasilkan kata 'HELLO'.](../../../../../translated_images/id/rnn-generate.56c54afb52f9781d.webp)\n",
"\n",
"Bergantung pada skenario sebenarnya, kita mungkin juga ingin menyertakan beberapa karakter khusus, seperti *akhir urutan* `<eos>`. Dalam kasus kita, kita hanya ingin melatih jaringan untuk menghasilkan teks tanpa henti, sehingga kita akan menetapkan ukuran setiap urutan sama dengan `nchars` token. Akibatnya, setiap contoh pelatihan akan terdiri dari `nchars` masukan dan `nchars` keluaran (yang merupakan urutan masukan yang digeser satu simbol ke kiri). Minibatch akan terdiri dari beberapa urutan seperti itu.\n",
"\n",
"Cara kita akan menghasilkan minibatch adalah dengan mengambil setiap teks berita dengan panjang `l`, dan menghasilkan semua kemungkinan kombinasi masukan-keluaran darinya (akan ada `l-nchars` kombinasi semacam itu). Kombinasi tersebut akan membentuk satu minibatch, dan ukuran minibatch akan berbeda pada setiap langkah pelatihan.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(tensor([[ 0, 1, 2, ..., 28, 29, 30],\n",
" [ 1, 2, 2, ..., 29, 30, 1],\n",
" [ 2, 2, 3, ..., 30, 1, 16],\n",
" ...,\n",
" [20, 8, 21, ..., 1, 28, 1],\n",
" [ 8, 21, 5, ..., 28, 1, 27],\n",
" [21, 5, 8, ..., 1, 27, 16]]),\n",
" tensor([[ 1, 2, 2, ..., 29, 30, 1],\n",
" [ 2, 2, 3, ..., 30, 1, 16],\n",
" [ 2, 3, 4, ..., 1, 16, 26],\n",
" ...,\n",
" [ 8, 21, 5, ..., 28, 1, 27],\n",
" [21, 5, 8, ..., 1, 27, 16],\n",
" [ 5, 8, 9, ..., 27, 16, 6]]))"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nchars = 100\n",
"\n",
"def get_batch(s,nchars=nchars):\n",
" ins = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
" outs = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
" for i in range(len(s)-nchars):\n",
" ins[i] = enc(s[i:i+nchars])\n",
" outs[i] = enc(s[i+1:i+nchars+1])\n",
" return ins,outs\n",
"\n",
"get_batch(train_dataset[0][1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sekarang mari kita definisikan jaringan generator. Jaringan ini dapat didasarkan pada sel berulang apa pun yang telah kita bahas di unit sebelumnya (simple, LSTM, atau GRU). Dalam contoh kita, kita akan menggunakan LSTM.\n",
"\n",
"Karena jaringan menerima karakter sebagai input, dan ukuran kosakata cukup kecil, kita tidak memerlukan lapisan embedding, input yang dienkode satu-hot dapat langsung masuk ke sel LSTM. Namun, karena kita memberikan nomor karakter sebagai input, kita perlu mengubahnya menjadi satu-hot sebelum diteruskan ke LSTM. Hal ini dilakukan dengan memanggil fungsi `one_hot` selama proses `forward`. Encoder output akan berupa lapisan linear yang akan mengubah state tersembunyi menjadi output yang dienkode satu-hot.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"class LSTMGenerator(torch.nn.Module):\n",
" def __init__(self, vocab_size, hidden_dim):\n",
" super().__init__()\n",
" self.rnn = torch.nn.LSTM(vocab_size,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, vocab_size)\n",
"\n",
" def forward(self, x, s=None):\n",
" x = torch.nn.functional.one_hot(x,vocab_size).to(torch.float32)\n",
" x,s = self.rnn(x,s)\n",
" return self.fc(x),s"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Selama pelatihan, kita ingin dapat mengambil sampel teks yang dihasilkan. Untuk melakukannya, kita akan mendefinisikan fungsi `generate` yang akan menghasilkan string keluaran dengan panjang `size`, dimulai dari string awal `start`.\n",
"\n",
"Cara kerjanya adalah sebagai berikut. Pertama, kita akan melewatkan seluruh string awal melalui jaringan, dan mengambil status keluaran `s` serta karakter berikutnya yang diprediksi `out`. Karena `out` dikodekan dalam bentuk one-hot, kita menggunakan `argmax` untuk mendapatkan indeks karakter `nc` dalam kosakata, lalu menggunakan `itos` untuk mengetahui karakter sebenarnya dan menambahkannya ke daftar karakter hasil `chars`. Proses menghasilkan satu karakter ini diulangi sebanyak `size` kali untuk menghasilkan jumlah karakter yang diperlukan.\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def generate(net,size=100,start='today '):\n",
" chars = list(start)\n",
" out, s = net(enc(chars).view(1,-1).to(device))\n",
" for i in range(size):\n",
" nc = torch.argmax(out[0][-1])\n",
" chars.append(vocab.get_itos()[nc])\n",
" out, s = net(nc.view(1,-1),s)\n",
" return ''.join(chars)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sekarang mari kita mulai pelatihan! Loop pelatihan hampir sama seperti di semua contoh sebelumnya, tetapi alih-alih mencetak akurasi, kita mencetak teks yang dihasilkan setiap 1000 epoch.\n",
"\n",
"Perhatian khusus perlu diberikan pada cara kita menghitung loss. Kita perlu menghitung loss berdasarkan output yang telah dienkode satu-hot `out`, dan teks yang diharapkan `text_out`, yang merupakan daftar indeks karakter. Untungnya, fungsi `cross_entropy` mengharapkan output jaringan yang belum dinormalisasi sebagai argumen pertama, dan nomor kelas sebagai argumen kedua, yang persis seperti yang kita miliki. Fungsi ini juga secara otomatis melakukan rata-rata berdasarkan ukuran minibatch.\n",
"\n",
"Kita juga membatasi pelatihan dengan sampel sebanyak `samples_to_train`, agar tidak memakan waktu terlalu lama. Kami mendorong Anda untuk bereksperimen dan mencoba pelatihan yang lebih lama, mungkin selama beberapa epoch (dalam hal ini Anda perlu membuat loop lain di sekitar kode ini).\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Current loss = 4.398899078369141\n",
"today sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr s\n",
"Current loss = 2.161320447921753\n",
"today and to the tor to to the tor to to the tor to to the tor to to the tor to to the tor to to the tor t\n",
"Current loss = 1.6722588539123535\n",
"today and the court to the could to the could to the could to the could to the could to the could to the c\n",
"Current loss = 2.423795223236084\n",
"today and a second to the conternation of the conternation of the conternation of the conternation of the \n",
"Current loss = 1.702607274055481\n",
"today and the company to the company to the company to the company to the company to the company to the co\n",
"Current loss = 1.692358136177063\n",
"today and the company to the company to the company to the company to the company to the company to the co\n",
"Current loss = 1.9722288846969604\n",
"today and the control the control the control the control the control the control the control the control \n",
"Current loss = 1.8705692291259766\n",
"today and the second to the second to the second to the second to the second to the second to the second t\n",
"Current loss = 1.7626899480819702\n",
"today and a security and a security and a security and a security and a security and a security and a secu\n",
"Current loss = 1.5574463605880737\n",
"today and the company and the company and the company and the company and the company and the company and \n",
"Current loss = 1.5620026588439941\n",
"today and the be that the be the be that the be the be that the be the be that the be the be that the be t\n"
]
}
],
"source": [
"net = LSTMGenerator(vocab_size,64).to(device)\n",
"\n",
"samples_to_train = 10000\n",
"optimizer = torch.optim.Adam(net.parameters(),0.01)\n",
"loss_fn = torch.nn.CrossEntropyLoss()\n",
"net.train()\n",
"for i,x in enumerate(train_dataset):\n",
" # x[0] is class label, x[1] is text\n",
" if len(x[1])-nchars<10:\n",
" continue\n",
" samples_to_train-=1\n",
" if not samples_to_train: break\n",
" text_in, text_out = get_batch(x[1])\n",
" optimizer.zero_grad()\n",
" out,s = net(text_in)\n",
" loss = torch.nn.functional.cross_entropy(out.view(-1,vocab_size),text_out.flatten()) #cross_entropy(out,labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" if i%1000==0:\n",
" print(f\"Current loss = {loss.item()}\")\n",
" print(generate(net))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Contoh ini sudah menghasilkan teks yang cukup baik, tetapi masih dapat ditingkatkan dalam beberapa cara:\n",
"\n",
"* **Peningkatan pembuatan minibatch**. Cara kita mempersiapkan data untuk pelatihan adalah dengan menghasilkan satu minibatch dari satu sampel. Ini tidak ideal, karena ukuran minibatch berbeda-beda, dan beberapa bahkan tidak dapat dihasilkan karena teksnya lebih kecil dari `nchars`. Selain itu, minibatch yang kecil tidak cukup memanfaatkan GPU secara optimal. Akan lebih bijaksana untuk mengambil satu potongan besar teks dari semua sampel, kemudian menghasilkan semua pasangan input-output, mengacaknya, dan membuat minibatch dengan ukuran yang sama.\n",
"\n",
"* **LSTM multilayer**. Ada baiknya mencoba 2 atau 3 lapisan sel LSTM. Seperti yang disebutkan di unit sebelumnya, setiap lapisan LSTM mengekstrak pola tertentu dari teks, dan dalam kasus generator tingkat karakter, kita dapat mengharapkan lapisan LSTM yang lebih rendah bertanggung jawab untuk mengekstrak suku kata, dan lapisan yang lebih tinggi - untuk kata dan kombinasi kata. Ini dapat dengan mudah diimplementasikan dengan memberikan parameter jumlah lapisan ke konstruktor LSTM.\n",
"\n",
"* Anda juga mungkin ingin bereksperimen dengan **unit GRU** dan melihat mana yang memberikan performa lebih baik, serta dengan **ukuran lapisan tersembunyi yang berbeda**. Lapisan tersembunyi yang terlalu besar dapat menyebabkan overfitting (misalnya jaringan akan mempelajari teks secara persis), sedangkan ukuran yang lebih kecil mungkin tidak menghasilkan hasil yang baik.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Generasi teks lunak dan suhu\n",
"\n",
"Dalam definisi sebelumnya tentang `generate`, kita selalu memilih karakter dengan probabilitas tertinggi sebagai karakter berikutnya dalam teks yang dihasilkan. Hal ini menyebabkan teks sering \"berulang\" pada urutan karakter yang sama berulang kali, seperti dalam contoh berikut:\n",
"```\n",
"today of the second the company and a second the company ...\n",
"```\n",
"\n",
"Namun, jika kita melihat distribusi probabilitas untuk karakter berikutnya, bisa saja perbedaan antara beberapa probabilitas tertinggi tidak terlalu besar, misalnya satu karakter memiliki probabilitas 0.2, sementara karakter lain - 0.19, dan sebagainya. Sebagai contoh, ketika mencari karakter berikutnya dalam urutan '*play*', karakter berikutnya bisa saja berupa spasi, atau **e** (seperti dalam kata *player*).\n",
"\n",
"Hal ini membawa kita pada kesimpulan bahwa tidak selalu \"adil\" untuk memilih karakter dengan probabilitas lebih tinggi, karena memilih karakter dengan probabilitas kedua tertinggi masih bisa menghasilkan teks yang bermakna. Akan lebih bijaksana untuk **mengambil sampel** karakter dari distribusi probabilitas yang diberikan oleh keluaran jaringan.\n",
"\n",
"Pengambilan sampel ini dapat dilakukan menggunakan fungsi `multinomial` yang mengimplementasikan apa yang disebut sebagai **distribusi multinomial**. Fungsi yang mengimplementasikan generasi teks **lunak** ini didefinisikan di bawah:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--- Temperature = 0.3\n",
"Today and a company and complete an all the land the restrational the as a security and has provers the pay to and a report and the computer in the stand has filities and working the law the stations for a company and with the company and the final the first company and refight of the state and and workin\n",
"\n",
"--- Temperature = 0.8\n",
"Today he oniis its first to Aus bomblaties the marmation a to manan boogot that pirate assaid a relaid their that goverfin the the Cappets Ecrotional Assonia Cition targets it annight the w scyments Blamity #39;s TVeer Diercheg Reserals fran envyuil that of ster said access what succers of Dour-provelith\n",
"\n",
"--- Temperature = 1.0\n",
"Today holy they a 11 will meda a toket subsuaties, engins for Chanos, they's has stainger past to opening orital his thempting new Nattona was al innerforder advan-than #36;s night year his religuled talitatian what the but with Wednesday to Justment will wemen of Mark CCC Camp as Timed Nae wome a leaders\n",
"\n",
"--- Temperature = 1.3\n",
"Today gpone 2.5 fech atcusion poor cocles toparsdorM.cht Line Pamage put 43 his calt lowed to the book, that has authh-the silia rruch ailing to'ory andhes beutirsimi- Aefffive heading offil an auf eacklets is charged evis, Gunymy oy) Mony has it after-sloythyor loveId out filme, the Natabl -Najuntaxiggs \n",
"\n",
"--- Temperature = 1.8\n",
"Today plary, P.slan chly\\401 mardregationly #39;t 8.1Mide) closes ,filtcon alfly playin roven!\\grea.-QFBEP: Iss onfarchQ/itilia CCf Zivesigntwasta orce.-Peul-aw.uicrin of fuglinfsut aftaningwo, MIEX awayew Aice Woiduar Corvagiugge oppo esig ThusBratourid canthly-RyI.co lagitems\\eexciaishes.conBabntusmor I\n",
"\n"
]
}
],
"source": [
"def generate_soft(net,size=100,start='today ',temperature=1.0):\n",
" chars = list(start)\n",
" out, s = net(enc(chars).view(1,-1).to(device))\n",
" for i in range(size):\n",
" #nc = torch.argmax(out[0][-1])\n",
" out_dist = out[0][-1].div(temperature).exp()\n",
" nc = torch.multinomial(out_dist,1)[0]\n",
" chars.append(vocab.get_itos()[nc])\n",
" out, s = net(nc.view(1,-1),s)\n",
" return ''.join(chars)\n",
" \n",
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
" print(f\"--- Temperature = {i}\\n{generate_soft(net,size=300,start='Today ',temperature=i)}\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Kami telah memperkenalkan satu parameter lagi yang disebut **temperature**, yang digunakan untuk menunjukkan seberapa keras kita harus berpegang pada probabilitas tertinggi. Jika temperature adalah 1.0, kita melakukan sampling multinomial yang adil, dan ketika temperature meningkat tak terhingga - semua probabilitas menjadi sama, dan kita secara acak memilih karakter berikutnya. Dalam contoh di bawah ini, kita dapat mengamati bahwa teks menjadi tidak bermakna ketika kita meningkatkan temperature terlalu tinggi, dan teks tersebut menyerupai teks \"berulang\" yang dihasilkan secara keras ketika mendekati 0.\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": "7673cd150d96c74c6d6011460094efb4",
"translation_date": "2025-08-29T15:44:46+00:00",
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.ipynb",
"language_code": "id"
}
},
"nbformat": 4,
"nbformat_minor": 4
}