479 lines
24 KiB
Plaintext
479 lines
24 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Jaringan Saraf Rekuren\n",
|
|
"\n",
|
|
"Dalam modul sebelumnya, kita telah menggunakan representasi semantik yang kaya dari teks, dan sebuah pengklasifikasi linear sederhana di atas embedding. Arsitektur ini menangkap makna agregat dari kata-kata dalam sebuah kalimat, tetapi tidak memperhitungkan **urutan** kata-kata, karena operasi agregasi pada embedding menghilangkan informasi ini dari teks asli. Karena model-model ini tidak mampu memodelkan urutan kata, mereka tidak dapat menyelesaikan tugas yang lebih kompleks atau ambigu seperti pembuatan teks atau menjawab pertanyaan.\n",
|
|
"\n",
|
|
"Untuk menangkap makna dari urutan teks, kita perlu menggunakan arsitektur jaringan saraf lain, yang disebut **jaringan saraf rekuren**, atau RNN. Dalam RNN, kita melewatkan kalimat kita melalui jaringan satu simbol pada satu waktu, dan jaringan menghasilkan beberapa **state**, yang kemudian kita teruskan kembali ke jaringan bersama simbol berikutnya.\n",
|
|
"\n",
|
|
"Diberikan urutan token $X_0,\\dots,X_n$, RNN menciptakan urutan blok jaringan saraf, dan melatih urutan ini secara end-to-end menggunakan back propagation. Setiap blok jaringan mengambil pasangan $(X_i,S_i)$ sebagai input, dan menghasilkan $S_{i+1}$ sebagai hasil. State akhir $S_n$ atau output $X_n$ masuk ke pengklasifikasi linear untuk menghasilkan hasil akhir. Semua blok jaringan berbagi bobot yang sama, dan dilatih secara end-to-end menggunakan satu kali proses back propagation.\n",
|
|
"\n",
|
|
"Karena vektor state $S_0,\\dots,S_n$ diteruskan melalui jaringan, RNN mampu mempelajari ketergantungan berurutan antar kata. Sebagai contoh, ketika kata *tidak* muncul di suatu tempat dalam urutan, RNN dapat belajar untuk menegasikan elemen tertentu dalam vektor state, yang menghasilkan negasi.\n",
|
|
"\n",
|
|
"> Karena bobot semua blok RNN pada gambar dibagikan, gambar yang sama dapat direpresentasikan sebagai satu blok (di sebelah kanan) dengan loop umpan balik rekuren, yang meneruskan state output jaringan kembali ke input.\n",
|
|
"\n",
|
|
"Mari kita lihat bagaimana jaringan saraf rekuren dapat membantu kita mengklasifikasikan dataset berita kita.\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",
|
|
"from torchnlp import *\n",
|
|
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
|
"vocab_size = len(vocab)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Klasifikasi RNN Sederhana\n",
|
|
"\n",
|
|
"Dalam kasus RNN sederhana, setiap unit rekuren adalah jaringan linear sederhana, yang menerima vektor input yang digabungkan dengan vektor status, dan menghasilkan vektor status baru. PyTorch merepresentasikan unit ini dengan kelas `RNNCell`, dan jaringan dari unit-unit tersebut sebagai lapisan `RNN`.\n",
|
|
"\n",
|
|
"Untuk mendefinisikan sebuah klasifikasi RNN, kita akan terlebih dahulu menerapkan lapisan embedding untuk menurunkan dimensi kosakata input, lalu menambahkan lapisan RNN di atasnya:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class RNNClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.hidden_dim = hidden_dim\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" batch_size = x.size(0)\n",
|
|
" x = self.embedding(x)\n",
|
|
" x,h = self.rnn(x)\n",
|
|
" return self.fc(x.mean(dim=1))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Catatan:** Di sini kita menggunakan lapisan embedding yang belum dilatih untuk kesederhanaan, tetapi untuk hasil yang lebih baik kita dapat menggunakan lapisan embedding yang telah dilatih sebelumnya dengan Word2Vec atau GloVe embeddings, seperti yang dijelaskan di unit sebelumnya. Untuk pemahaman yang lebih baik, Anda mungkin ingin menyesuaikan kode ini agar bekerja dengan embedding yang telah dilatih sebelumnya.\n",
|
|
"\n",
|
|
"Dalam kasus kita, kita akan menggunakan data loader yang telah dipad, sehingga setiap batch akan memiliki sejumlah urutan yang dipad dengan panjang yang sama. Lapisan RNN akan mengambil urutan tensor embedding, dan menghasilkan dua output: \n",
|
|
"* $x$ adalah urutan output sel RNN di setiap langkah\n",
|
|
"* $h$ adalah keadaan tersembunyi akhir untuk elemen terakhir dari urutan\n",
|
|
"\n",
|
|
"Kemudian kita menerapkan pengklasifikasi linier yang sepenuhnya terhubung untuk mendapatkan jumlah kelas.\n",
|
|
"\n",
|
|
"> **Catatan:** RNN cukup sulit untuk dilatih, karena setelah sel RNN diurai sepanjang panjang urutan, jumlah lapisan yang terlibat dalam propagasi balik menjadi cukup besar. Oleh karena itu, kita perlu memilih tingkat pembelajaran yang kecil, dan melatih jaringan pada dataset yang lebih besar untuk menghasilkan hasil yang baik. Proses ini bisa memakan waktu cukup lama, sehingga penggunaan GPU lebih disarankan.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.3090625\n",
|
|
"6400: acc=0.38921875\n",
|
|
"9600: acc=0.4590625\n",
|
|
"12800: acc=0.511953125\n",
|
|
"16000: acc=0.5506875\n",
|
|
"19200: acc=0.57921875\n",
|
|
"22400: acc=0.6070089285714285\n",
|
|
"25600: acc=0.6304296875\n",
|
|
"28800: acc=0.6484027777777778\n",
|
|
"32000: acc=0.66509375\n",
|
|
"35200: acc=0.6790056818181818\n",
|
|
"38400: acc=0.6929166666666666\n",
|
|
"41600: acc=0.7035817307692308\n",
|
|
"44800: acc=0.7137276785714286\n",
|
|
"48000: acc=0.72225\n",
|
|
"51200: acc=0.73001953125\n",
|
|
"54400: acc=0.7372794117647059\n",
|
|
"57600: acc=0.7436631944444444\n",
|
|
"60800: acc=0.7503947368421052\n",
|
|
"64000: acc=0.75634375\n",
|
|
"67200: acc=0.7615773809523809\n",
|
|
"70400: acc=0.7662642045454545\n",
|
|
"73600: acc=0.7708423913043478\n",
|
|
"76800: acc=0.7751822916666666\n",
|
|
"80000: acc=0.7790625\n",
|
|
"83200: acc=0.7825\n",
|
|
"86400: acc=0.7858564814814815\n",
|
|
"89600: acc=0.7890513392857142\n",
|
|
"92800: acc=0.7920474137931034\n",
|
|
"96000: acc=0.7952708333333334\n",
|
|
"99200: acc=0.7982258064516129\n",
|
|
"102400: acc=0.80099609375\n",
|
|
"105600: acc=0.8037594696969697\n",
|
|
"108800: acc=0.8060569852941176\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)\n",
|
|
"net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
|
"train_epoch(net,train_loader, lr=0.001)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Long Short Term Memory (LSTM)\n",
|
|
"\n",
|
|
"Salah satu masalah utama pada RNN klasik adalah masalah yang disebut **vanishing gradients**. Karena RNN dilatih secara end-to-end dalam satu proses back-propagation, sulit untuk menyebarkan error ke lapisan pertama jaringan, sehingga jaringan tidak dapat mempelajari hubungan antara token yang berjauhan. Salah satu cara untuk menghindari masalah ini adalah dengan memperkenalkan **manajemen keadaan secara eksplisit** menggunakan apa yang disebut **gates**. Ada dua arsitektur paling terkenal dari jenis ini: **Long Short Term Memory** (LSTM) dan **Gated Relay Unit** (GRU).\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Jaringan LSTM diorganisasikan dengan cara yang mirip dengan RNN, tetapi ada dua keadaan yang diteruskan dari lapisan ke lapisan: keadaan aktual $c$, dan vektor tersembunyi $h$. Pada setiap unit, vektor tersembunyi $h_i$ digabungkan dengan input $x_i$, dan mereka mengontrol apa yang terjadi pada keadaan $c$ melalui **gates**. Setiap gate adalah jaringan neural dengan aktivasi sigmoid (output dalam rentang $[0,1]$), yang dapat dianggap sebagai masker bitwise ketika dikalikan dengan vektor keadaan. Berikut adalah gates yang ada (dari kiri ke kanan pada gambar di atas):\n",
|
|
"* **forget gate** mengambil vektor tersembunyi dan menentukan komponen mana dari vektor $c$ yang perlu dilupakan, dan mana yang perlu diteruskan.\n",
|
|
"* **input gate** mengambil beberapa informasi dari input dan vektor tersembunyi, lalu memasukkannya ke dalam keadaan.\n",
|
|
"* **output gate** mengubah keadaan melalui beberapa lapisan linear dengan aktivasi $\\tanh$, kemudian memilih beberapa komponennya menggunakan vektor tersembunyi $h_i$ untuk menghasilkan keadaan baru $c_{i+1}$.\n",
|
|
"\n",
|
|
"Komponen dari keadaan $c$ dapat dianggap sebagai beberapa flag yang dapat diaktifkan atau dinonaktifkan. Sebagai contoh, ketika kita menemukan nama *Alice* dalam urutan, kita mungkin ingin mengasumsikan bahwa itu merujuk pada karakter perempuan, dan mengaktifkan flag dalam keadaan bahwa kita memiliki kata benda perempuan dalam kalimat. Ketika kita kemudian menemukan frasa *and Tom*, kita akan mengaktifkan flag bahwa kita memiliki kata benda jamak. Dengan demikian, dengan memanipulasi keadaan, kita dapat secara teoritis melacak sifat gramatikal dari bagian-bagian kalimat.\n",
|
|
"\n",
|
|
"> **Note**: Sumber yang sangat bagus untuk memahami struktur internal LSTM adalah artikel hebat ini [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) oleh Christopher Olah.\n",
|
|
"\n",
|
|
"Meskipun struktur internal sel LSTM mungkin terlihat kompleks, PyTorch menyembunyikan implementasi ini di dalam kelas `LSTMCell`, dan menyediakan objek `LSTM` untuk merepresentasikan seluruh lapisan LSTM. Dengan demikian, implementasi classifier LSTM akan sangat mirip dengan RNN sederhana yang telah kita lihat sebelumnya:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class LSTMClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.hidden_dim = hidden_dim\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
|
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" batch_size = x.size(0)\n",
|
|
" x = self.embedding(x)\n",
|
|
" x,(h,c) = self.rnn(x)\n",
|
|
" return self.fc(h[-1])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.259375\n",
|
|
"6400: acc=0.25859375\n",
|
|
"9600: acc=0.26177083333333334\n",
|
|
"12800: acc=0.2784375\n",
|
|
"16000: acc=0.313\n",
|
|
"19200: acc=0.3528645833333333\n",
|
|
"22400: acc=0.3965625\n",
|
|
"25600: acc=0.4385546875\n",
|
|
"28800: acc=0.4752777777777778\n",
|
|
"32000: acc=0.505375\n",
|
|
"35200: acc=0.5326704545454546\n",
|
|
"38400: acc=0.5557552083333334\n",
|
|
"41600: acc=0.5760817307692307\n",
|
|
"44800: acc=0.5954910714285714\n",
|
|
"48000: acc=0.6118333333333333\n",
|
|
"51200: acc=0.62681640625\n",
|
|
"54400: acc=0.6404779411764706\n",
|
|
"57600: acc=0.6520138888888889\n",
|
|
"60800: acc=0.662828947368421\n",
|
|
"64000: acc=0.673546875\n",
|
|
"67200: acc=0.6831547619047619\n",
|
|
"70400: acc=0.6917897727272727\n",
|
|
"73600: acc=0.6997146739130434\n",
|
|
"76800: acc=0.707109375\n",
|
|
"80000: acc=0.714075\n",
|
|
"83200: acc=0.7209134615384616\n",
|
|
"86400: acc=0.727037037037037\n",
|
|
"89600: acc=0.7326674107142858\n",
|
|
"92800: acc=0.7379633620689655\n",
|
|
"96000: acc=0.7433645833333333\n",
|
|
"99200: acc=0.7479032258064516\n",
|
|
"102400: acc=0.752119140625\n",
|
|
"105600: acc=0.7562405303030303\n",
|
|
"108800: acc=0.76015625\n",
|
|
"112000: acc=0.7641339285714286\n",
|
|
"115200: acc=0.7677777777777778\n",
|
|
"118400: acc=0.7711233108108108\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.03487814127604167, 0.7728)"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
|
"train_epoch(net,train_loader, lr=0.001)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Urutan yang Dikemas\n",
|
|
"\n",
|
|
"Dalam contoh kita, kita harus mengisi semua urutan dalam minibatch dengan vektor nol. Meskipun ini mengakibatkan pemborosan memori, dengan RNN lebih kritis bahwa sel-sel RNN tambahan dibuat untuk item input yang diisi, yang ikut serta dalam pelatihan, tetapi tidak membawa informasi input yang penting. Akan jauh lebih baik jika melatih RNN hanya pada ukuran urutan yang sebenarnya.\n",
|
|
"\n",
|
|
"Untuk melakukan itu, format khusus penyimpanan urutan yang diisi diperkenalkan di PyTorch. Misalkan kita memiliki minibatch input yang diisi yang terlihat seperti ini:\n",
|
|
"```\n",
|
|
"[[1,2,3,4,5],\n",
|
|
" [6,7,8,0,0],\n",
|
|
" [9,0,0,0,0]]\n",
|
|
"```\n",
|
|
"Di sini 0 mewakili nilai yang diisi, dan vektor panjang sebenarnya dari urutan input adalah `[5,3,1]`.\n",
|
|
"\n",
|
|
"Untuk melatih RNN secara efektif dengan urutan yang diisi, kita ingin memulai pelatihan grup pertama sel RNN dengan minibatch besar (`[1,6,9]`), tetapi kemudian mengakhiri pemrosesan urutan ketiga, dan melanjutkan pelatihan dengan minibatch yang lebih pendek (`[2,7]`, `[3,8]`), dan seterusnya. Dengan demikian, urutan yang dikemas direpresentasikan sebagai satu vektor - dalam kasus kita `[1,6,9,2,7,3,8,4,5]`, dan vektor panjang (`[5,3,1]`), dari mana kita dapat dengan mudah merekonstruksi minibatch yang diisi asli.\n",
|
|
"\n",
|
|
"Untuk menghasilkan urutan yang dikemas, kita dapat menggunakan fungsi `torch.nn.utils.rnn.pack_padded_sequence`. Semua lapisan berulang, termasuk RNN, LSTM, dan GRU, mendukung urutan yang dikemas sebagai input, dan menghasilkan output yang dikemas, yang dapat didekode menggunakan `torch.nn.utils.rnn.pad_packed_sequence`.\n",
|
|
"\n",
|
|
"Untuk dapat menghasilkan urutan yang dikemas, kita perlu memberikan vektor panjang ke jaringan, dan dengan demikian kita memerlukan fungsi yang berbeda untuk mempersiapkan minibatch:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def pad_length(b):\n",
|
|
" # build vectorized sequence\n",
|
|
" v = [encode(x[1]) for x in b]\n",
|
|
" # compute max length of a sequence in this minibatch and length sequence itself\n",
|
|
" len_seq = list(map(len,v))\n",
|
|
" l = max(len_seq)\n",
|
|
" return ( # tuple of three tensors - labels, padded features, length sequence\n",
|
|
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
|
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v]),\n",
|
|
" torch.tensor(len_seq)\n",
|
|
" )\n",
|
|
"\n",
|
|
"train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Jaringan aktual akan sangat mirip dengan `LSTMClassifier` di atas, tetapi proses `forward` akan menerima baik minibatch yang telah dipadatkan maupun vektor panjang urutan. Setelah menghitung embedding, kita menghitung packed sequence, meneruskannya ke lapisan LSTM, dan kemudian membongkar hasilnya kembali.\n",
|
|
"\n",
|
|
"> **Catatan**: Sebenarnya kita tidak menggunakan hasil yang telah dibongkar `x`, karena kita menggunakan output dari lapisan tersembunyi dalam perhitungan berikutnya. Oleh karena itu, kita dapat menghapus proses pembongkaran sepenuhnya dari kode ini. Alasan kita meletakkannya di sini adalah agar Anda dapat dengan mudah memodifikasi kode ini, jika Anda perlu menggunakan output jaringan dalam perhitungan lebih lanjut.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class LSTMPackClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.hidden_dim = hidden_dim\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
|
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x, lengths):\n",
|
|
" batch_size = x.size(0)\n",
|
|
" x = self.embedding(x)\n",
|
|
" pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)\n",
|
|
" pad_x,(h,c) = self.rnn(pad_x)\n",
|
|
" x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)\n",
|
|
" return self.fc(h[-1])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.285625\n",
|
|
"6400: acc=0.33359375\n",
|
|
"9600: acc=0.3876041666666667\n",
|
|
"12800: acc=0.44078125\n",
|
|
"16000: acc=0.4825\n",
|
|
"19200: acc=0.5235416666666667\n",
|
|
"22400: acc=0.5559821428571429\n",
|
|
"25600: acc=0.58609375\n",
|
|
"28800: acc=0.6116666666666667\n",
|
|
"32000: acc=0.63340625\n",
|
|
"35200: acc=0.6525284090909091\n",
|
|
"38400: acc=0.668515625\n",
|
|
"41600: acc=0.6822596153846154\n",
|
|
"44800: acc=0.6948214285714286\n",
|
|
"48000: acc=0.7052708333333333\n",
|
|
"51200: acc=0.71521484375\n",
|
|
"54400: acc=0.7239889705882353\n",
|
|
"57600: acc=0.7315277777777778\n",
|
|
"60800: acc=0.7388486842105263\n",
|
|
"64000: acc=0.74571875\n",
|
|
"67200: acc=0.7518303571428572\n",
|
|
"70400: acc=0.7576988636363636\n",
|
|
"73600: acc=0.7628940217391305\n",
|
|
"76800: acc=0.7681510416666667\n",
|
|
"80000: acc=0.7728125\n",
|
|
"83200: acc=0.7772235576923077\n",
|
|
"86400: acc=0.7815393518518519\n",
|
|
"89600: acc=0.7857700892857142\n",
|
|
"92800: acc=0.7895043103448276\n",
|
|
"96000: acc=0.7930520833333333\n",
|
|
"99200: acc=0.7959072580645161\n",
|
|
"102400: acc=0.798994140625\n",
|
|
"105600: acc=0.802064393939394\n",
|
|
"108800: acc=0.8051378676470589\n",
|
|
"112000: acc=0.8077857142857143\n",
|
|
"115200: acc=0.8104600694444445\n",
|
|
"118400: acc=0.8128293918918919\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.029785829671223958, 0.8138166666666666)"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
|
"train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Catatan:** Anda mungkin telah memperhatikan parameter `use_pack_sequence` yang kami berikan ke fungsi pelatihan. Saat ini, fungsi `pack_padded_sequence` membutuhkan tensor urutan panjang berada di perangkat CPU, dan oleh karena itu fungsi pelatihan perlu menghindari memindahkan data urutan panjang ke GPU saat pelatihan. Anda dapat melihat implementasi fungsi `train_emb` dalam file [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py).\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## RNN Bidirectional dan Multilayer\n",
|
|
"\n",
|
|
"Dalam contoh-contoh kita, semua jaringan rekuren bekerja dalam satu arah, dari awal hingga akhir sebuah urutan. Hal ini terlihat alami, karena menyerupai cara kita membaca dan mendengarkan percakapan. Namun, karena dalam banyak kasus praktis kita memiliki akses acak ke urutan input, masuk akal untuk menjalankan komputasi rekuren dalam kedua arah. Jaringan seperti ini disebut **RNN bidirectional**, dan dapat dibuat dengan menambahkan parameter `bidirectional=True` pada konstruktor RNN/LSTM/GRU.\n",
|
|
"\n",
|
|
"Saat bekerja dengan jaringan bidirectional, kita memerlukan dua vektor state tersembunyi, satu untuk setiap arah. PyTorch mengkodekan vektor-vektor tersebut sebagai satu vektor dengan ukuran dua kali lebih besar, yang cukup praktis, karena biasanya Anda akan meneruskan state tersembunyi yang dihasilkan ke lapisan linear fully-connected, dan Anda hanya perlu memperhitungkan peningkatan ukuran ini saat membuat lapisan tersebut.\n",
|
|
"\n",
|
|
"Jaringan rekuren, baik satu arah maupun bidirectional, menangkap pola tertentu dalam sebuah urutan, dan dapat menyimpannya ke dalam vektor state atau meneruskannya ke output. Seperti pada jaringan konvolusional, kita dapat membangun lapisan rekuren lain di atas lapisan pertama untuk menangkap pola tingkat yang lebih tinggi, yang dibangun dari pola tingkat rendah yang diekstraksi oleh lapisan pertama. Ini membawa kita pada konsep **RNN multilayer**, yang terdiri dari dua atau lebih jaringan rekuren, di mana output dari lapisan sebelumnya diteruskan ke lapisan berikutnya sebagai input.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"*Gambar dari [postingan luar biasa ini](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) oleh Fernando López*\n",
|
|
"\n",
|
|
"PyTorch mempermudah pembuatan jaringan seperti ini, karena Anda hanya perlu menambahkan parameter `num_layers` pada konstruktor RNN/LSTM/GRU untuk secara otomatis membangun beberapa lapisan rekuren. Hal ini juga berarti bahwa ukuran vektor state/tersembunyi akan meningkat secara proporsional, dan Anda perlu memperhitungkan hal ini saat menangani output dari lapisan rekuren.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## RNN untuk tugas lainnya\n",
|
|
"\n",
|
|
"Dalam unit ini, kita telah melihat bahwa RNN dapat digunakan untuk klasifikasi urutan, tetapi sebenarnya, RNN dapat menangani banyak tugas lainnya, seperti pembuatan teks, penerjemahan mesin, dan lainnya. Kita akan membahas tugas-tugas tersebut di unit berikutnya.\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": "522ee52ae3d5ae933e283286254e9a55",
|
|
"translation_date": "2025-08-29T16:17:44+00:00",
|
|
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
|
|
"language_code": "id"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |