726 lines
28 KiB
Plaintext
726 lines
28 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Embeddings\n",
|
|
"\n",
|
|
"Dalam contoh sebelumnya, kita bekerja dengan vektor bag-of-words berdimensi tinggi dengan panjang `vocab_size`, dan kita secara eksplisit mengonversi dari vektor representasi posisi berdimensi rendah ke representasi one-hot yang jarang. Representasi one-hot ini tidak efisien dalam penggunaan memori, selain itu, setiap kata diperlakukan secara independen satu sama lain, yaitu vektor one-hot encoded tidak menunjukkan kesamaan semantik antara kata-kata.\n",
|
|
"\n",
|
|
"Dalam unit ini, kita akan melanjutkan eksplorasi dataset **News AG**. Untuk memulai, mari kita muat data dan mengambil beberapa definisi dari notebook sebelumnya.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Loading dataset...\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\train.csv: 29.5MB [00:01, 18.8MB/s] \n",
|
|
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\test.csv: 1.86MB [00:00, 11.2MB/s] \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Building vocab...\n",
|
|
"Vocab size = 95812\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()\n",
|
|
"vocab_size = len(vocab)\n",
|
|
"print(\"Vocab size = \",vocab_size)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Apa itu embedding?\n",
|
|
"\n",
|
|
"Ide **embedding** adalah merepresentasikan kata-kata dengan vektor berdimensi rendah yang padat, yang mencerminkan makna semantik dari sebuah kata. Kita akan membahas cara membangun embedding kata yang bermakna nanti, tetapi untuk saat ini, anggap saja embedding sebagai cara untuk menurunkan dimensi vektor kata.\n",
|
|
"\n",
|
|
"Jadi, lapisan embedding akan menerima sebuah kata sebagai input, dan menghasilkan vektor output dengan ukuran `embedding_size` yang ditentukan. Dalam beberapa hal, ini sangat mirip dengan lapisan `Linear`, tetapi alih-alih menerima vektor one-hot encoded, lapisan ini dapat menerima nomor kata sebagai input.\n",
|
|
"\n",
|
|
"Dengan menggunakan lapisan embedding sebagai lapisan pertama dalam jaringan kita, kita dapat beralih dari model bag-of-words ke model **embedding bag**, di mana kita pertama-tama mengonversi setiap kata dalam teks kita menjadi embedding yang sesuai, lalu menghitung beberapa fungsi agregat atas semua embedding tersebut, seperti `sum`, `average`, atau `max`.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Jaringan saraf pengklasifikasi kita akan dimulai dengan lapisan embedding, kemudian lapisan agregasi, dan pengklasifikasi linear di atasnya:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class EmbedClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" x = self.embedding(x)\n",
|
|
" x = torch.mean(x,dim=1)\n",
|
|
" return self.fc(x)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Mengatasi Ukuran Urutan Variabel\n",
|
|
"\n",
|
|
"Sebagai hasil dari arsitektur ini, minibatch untuk jaringan kita perlu dibuat dengan cara tertentu. Pada unit sebelumnya, saat menggunakan bag-of-words, semua tensor BoW dalam sebuah minibatch memiliki ukuran yang sama `vocab_size`, terlepas dari panjang sebenarnya dari urutan teks kita. Setelah kita beralih ke word embeddings, kita akan memiliki jumlah kata yang bervariasi di setiap sampel teks, dan saat menggabungkan sampel-sampel tersebut ke dalam minibatch, kita harus menerapkan padding.\n",
|
|
"\n",
|
|
"Hal ini dapat dilakukan dengan menggunakan teknik yang sama, yaitu menyediakan fungsi `collate_fn` ke sumber data:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def padify(b):\n",
|
|
" # b is the list of tuples of length batch_size\n",
|
|
" # - first element of a tuple = label, \n",
|
|
" # - second = feature (text sequence)\n",
|
|
" # build vectorized sequence\n",
|
|
" v = [encode(x[1]) for x in b]\n",
|
|
" # first, compute max length of a sequence in this minibatch\n",
|
|
" l = max(map(len,v))\n",
|
|
" return ( # tuple of two tensors - labels and features\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",
|
|
" )\n",
|
|
"\n",
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Melatih pengklasifikasi embedding\n",
|
|
"\n",
|
|
"Sekarang setelah kita mendefinisikan dataloader yang sesuai, kita dapat melatih model menggunakan fungsi pelatihan yang telah kita definisikan di unit sebelumnya:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6415625\n",
|
|
"6400: acc=0.6865625\n",
|
|
"9600: acc=0.7103125\n",
|
|
"12800: acc=0.726953125\n",
|
|
"16000: acc=0.739375\n",
|
|
"19200: acc=0.75046875\n",
|
|
"22400: acc=0.7572321428571429\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.889799795315499, 0.7623160588611644)"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
|
"train_epoch(net,train_loader, lr=1, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Catatan**: Kami hanya melatih untuk 25k catatan di sini (kurang dari satu epoch penuh) demi menghemat waktu, tetapi Anda dapat melanjutkan pelatihan, menulis fungsi untuk melatih selama beberapa epoch, dan bereksperimen dengan parameter tingkat pembelajaran untuk mencapai akurasi yang lebih tinggi. Anda seharusnya dapat mencapai akurasi sekitar 90%.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Lapisan EmbeddingBag dan Representasi Urutan dengan Panjang Variabel\n",
|
|
"\n",
|
|
"Dalam arsitektur sebelumnya, kita perlu menambahkan padding pada semua urutan agar memiliki panjang yang sama untuk dimasukkan ke dalam minibatch. Ini bukan cara yang paling efisien untuk merepresentasikan urutan dengan panjang variabel - pendekatan lain adalah menggunakan vektor **offset**, yang akan menyimpan offset dari semua urutan yang disimpan dalam satu vektor besar.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"> **Note**: Pada gambar di atas, kami menunjukkan urutan karakter, tetapi dalam contoh ini kami bekerja dengan urutan kata. Namun, prinsip umum merepresentasikan urutan dengan vektor offset tetap sama.\n",
|
|
"\n",
|
|
"Untuk bekerja dengan representasi offset, kita menggunakan lapisan [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html). Lapisan ini mirip dengan `Embedding`, tetapi menerima vektor konten dan vektor offset sebagai input, dan juga mencakup lapisan penghitungan rata-rata, yang bisa berupa `mean`, `sum`, atau `max`.\n",
|
|
"\n",
|
|
"Berikut adalah jaringan yang dimodifikasi yang menggunakan `EmbeddingBag`:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class EmbedClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim)\n",
|
|
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, text, off):\n",
|
|
" x = self.embedding(text, off)\n",
|
|
" return self.fc(x)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Untuk mempersiapkan dataset untuk pelatihan, kita perlu menyediakan fungsi konversi yang akan mempersiapkan vektor offset:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def offsetify(b):\n",
|
|
" # first, compute data tensor from all sequences\n",
|
|
" x = [torch.tensor(encode(t[1])) for t in b]\n",
|
|
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
|
" o = [0] + [len(t) for t in x]\n",
|
|
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
|
" return ( \n",
|
|
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
|
" torch.cat(x), # text \n",
|
|
" o\n",
|
|
" )\n",
|
|
"\n",
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Perhatikan bahwa, tidak seperti pada semua contoh sebelumnya, jaringan kita sekarang menerima dua parameter: vektor data dan vektor offset, yang memiliki ukuran berbeda. Demikian pula, pemuat data kita juga memberikan 3 nilai kepada kita, bukan 2: baik vektor teks maupun vektor offset disediakan sebagai fitur. Oleh karena itu, kita perlu sedikit menyesuaikan fungsi pelatihan kita untuk mengatasi hal tersebut:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6153125\n",
|
|
"6400: acc=0.6615625\n",
|
|
"9600: acc=0.6932291666666667\n",
|
|
"12800: acc=0.715078125\n",
|
|
"16000: acc=0.7270625\n",
|
|
"19200: acc=0.7382291666666667\n",
|
|
"22400: acc=0.7486160714285715\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(22.771553103007037, 0.7551983365323096)"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
|
"\n",
|
|
"def train_epoch_emb(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.CrossEntropyLoss(),epoch_size=None, report_freq=200):\n",
|
|
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
|
" loss_fn = loss_fn.to(device)\n",
|
|
" net.train()\n",
|
|
" total_loss,acc,count,i = 0,0,0,0\n",
|
|
" for labels,text,off in dataloader:\n",
|
|
" optimizer.zero_grad()\n",
|
|
" labels,text,off = labels.to(device), text.to(device), off.to(device)\n",
|
|
" out = net(text, off)\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\n",
|
|
"\n",
|
|
"\n",
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Semantic Embeddings: Word2Vec\n",
|
|
"\n",
|
|
"Dalam contoh sebelumnya, lapisan embedding model belajar memetakan kata-kata ke representasi vektor, namun representasi tersebut tidak memiliki banyak makna semantis. Akan lebih baik jika kita dapat mempelajari representasi vektor di mana kata-kata yang serupa atau sinonim memiliki vektor yang dekat satu sama lain berdasarkan jarak vektor tertentu (misalnya, jarak euclidean).\n",
|
|
"\n",
|
|
"Untuk mencapai hal tersebut, kita perlu melatih model embedding kita terlebih dahulu pada kumpulan teks yang besar dengan cara tertentu. Salah satu metode pertama untuk melatih embedding semantis disebut [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Metode ini didasarkan pada dua arsitektur utama yang digunakan untuk menghasilkan representasi terdistribusi dari kata-kata:\n",
|
|
"\n",
|
|
" - **Continuous bag-of-words** (CBoW) — dalam arsitektur ini, kita melatih model untuk memprediksi sebuah kata berdasarkan konteks di sekitarnya. Diberikan ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$, tujuan model adalah memprediksi $W_0$ dari $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
|
" - **Continuous skip-gram** adalah kebalikan dari CBoW. Model menggunakan jendela kata-kata konteks di sekitar untuk memprediksi kata saat ini.\n",
|
|
"\n",
|
|
"CBoW lebih cepat, sedangkan skip-gram lebih lambat, tetapi lebih baik dalam merepresentasikan kata-kata yang jarang muncul.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Untuk bereksperimen dengan embedding word2vec yang telah dilatih sebelumnya pada dataset Google News, kita dapat menggunakan pustaka **gensim**. Di bawah ini kita menemukan kata-kata yang paling mirip dengan 'neural'\n",
|
|
"\n",
|
|
"> **Note:** Saat pertama kali membuat vektor kata, mengunduhnya mungkin memerlukan waktu!\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import gensim.downloader as api\n",
|
|
"w2v = api.load('word2vec-google-news-300')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"neuronal -> 0.7804799675941467\n",
|
|
"neurons -> 0.7326500415802002\n",
|
|
"neural_circuits -> 0.7252851724624634\n",
|
|
"neuron -> 0.7174385190010071\n",
|
|
"cortical -> 0.6941086649894714\n",
|
|
"brain_circuitry -> 0.6923246383666992\n",
|
|
"synaptic -> 0.6699118614196777\n",
|
|
"neural_circuitry -> 0.6638563275337219\n",
|
|
"neurochemical -> 0.6555314064025879\n",
|
|
"neuronal_activity -> 0.6531826257705688\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"for w,p in w2v.most_similar('neural'):\n",
|
|
" print(f\"{w} -> {p}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Kita juga dapat menghitung penyematan vektor dari kata, untuk digunakan dalam pelatihan model klasifikasi (kami hanya menunjukkan 20 komponen pertama dari vektor untuk kejelasan):\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
|
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
|
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
|
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
|
" dtype=float32)"
|
|
]
|
|
},
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"w2v.word_vec('play')[:20]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Hal hebat tentang penyematan semantik adalah bahwa Anda dapat memanipulasi pengkodean vektor untuk mengubah semantik. Sebagai contoh, kita dapat meminta untuk menemukan sebuah kata, yang representasi vektornya sedekat mungkin dengan kata *raja* dan *wanita*, serta sejauh mungkin dari kata *pria*:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"('queen', 0.7118192911148071)"
|
|
]
|
|
},
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Baik CBoW maupun Skip-Grams adalah embedding \"prediktif\", karena hanya mempertimbangkan konteks lokal. Word2Vec tidak memanfaatkan konteks global.\n",
|
|
"\n",
|
|
"**FastText** mengembangkan Word2Vec dengan mempelajari representasi vektor untuk setiap kata dan n-gram karakter yang ditemukan dalam setiap kata. Nilai dari representasi tersebut kemudian dirata-rata menjadi satu vektor pada setiap langkah pelatihan. Meskipun ini menambah banyak perhitungan tambahan pada pra-pelatihan, metode ini memungkinkan embedding kata untuk menyandikan informasi sub-kata.\n",
|
|
"\n",
|
|
"Metode lain, **GloVe**, memanfaatkan ide matriks ko-occurence, menggunakan metode neural untuk mendekomposisi matriks ko-occurence menjadi vektor kata yang lebih ekspresif dan non-linear.\n",
|
|
"\n",
|
|
"Anda dapat mencoba contoh ini dengan mengganti embedding ke FastText dan GloVe, karena gensim mendukung beberapa model embedding kata yang berbeda.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Menggunakan Embedding yang Sudah Dilatih di PyTorch\n",
|
|
"\n",
|
|
"Kita dapat memodifikasi contoh di atas untuk mengisi matriks dalam lapisan embedding kita dengan embedding semantis, seperti Word2Vec. Kita perlu mempertimbangkan bahwa kosakata dari embedding yang sudah dilatih dan korpus teks kita kemungkinan besar tidak akan cocok, sehingga kita akan menginisialisasi bobot untuk kata-kata yang hilang dengan nilai acak:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Embedding size: 300\n",
|
|
"Populating matrix, this will take some time...Done, found 41080 words, 54732 words missing\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"embed_size = len(w2v.get_vector('hello'))\n",
|
|
"print(f'Embedding size: {embed_size}')\n",
|
|
"\n",
|
|
"net = EmbedClassifier(vocab_size,embed_size,len(classes))\n",
|
|
"\n",
|
|
"print('Populating matrix, this will take some time...',end='')\n",
|
|
"found, not_found = 0,0\n",
|
|
"for i,w in enumerate(vocab.get_itos()):\n",
|
|
" try:\n",
|
|
" net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w))\n",
|
|
" found+=1\n",
|
|
" except:\n",
|
|
" net.embedding.weight[i].data = torch.normal(0.0,1.0,(embed_size,))\n",
|
|
" not_found+=1\n",
|
|
"\n",
|
|
"print(f\"Done, found {found} words, {not_found} words missing\")\n",
|
|
"net = net.to(device)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Sekarang mari kita latih model kita. Perhatikan bahwa waktu yang diperlukan untuk melatih model jauh lebih lama dibandingkan dengan contoh sebelumnya, karena ukuran lapisan embedding yang lebih besar, dan dengan demikian jumlah parameter yang jauh lebih tinggi. Selain itu, karena hal ini, kita mungkin perlu melatih model kita pada lebih banyak contoh jika kita ingin menghindari overfitting.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6359375\n",
|
|
"6400: acc=0.68109375\n",
|
|
"9600: acc=0.7067708333333333\n",
|
|
"12800: acc=0.723671875\n",
|
|
"16000: acc=0.73625\n",
|
|
"19200: acc=0.7463541666666667\n",
|
|
"22400: acc=0.7560714285714286\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(214.1013875559821, 0.7626759436980166)"
|
|
]
|
|
},
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Dalam kasus kami, kami tidak melihat peningkatan akurasi yang signifikan, kemungkinan disebabkan oleh perbedaan kosakata yang cukup besar. \n",
|
|
"Untuk mengatasi masalah perbedaan kosakata, kita dapat menggunakan salah satu solusi berikut: \n",
|
|
"* Melatih ulang model word2vec pada kosakata kita \n",
|
|
"* Memuat dataset kita dengan kosakata dari model word2vec yang sudah dilatih sebelumnya. Kosakata yang digunakan untuk memuat dataset dapat ditentukan selama proses pemuatan. \n",
|
|
"\n",
|
|
"Pendekatan yang terakhir tampaknya lebih mudah, terutama karena kerangka kerja PyTorch `torchtext` memiliki dukungan bawaan untuk embeddings. Sebagai contoh, kita dapat membuat kosakata berbasis GloVe dengan cara berikut: \n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"100%|█████████▉| 399999/400000 [00:15<00:00, 25411.14it/s]\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"vocab = torchtext.vocab.GloVe(name='6B', dim=50)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Kosakata yang dimuat memiliki operasi dasar berikut:\n",
|
|
"* Kamus `vocab.stoi` memungkinkan kita mengubah kata menjadi indeks dalam kamusnya\n",
|
|
"* `vocab.itos` melakukan kebalikan - mengubah angka menjadi kata\n",
|
|
"* `vocab.vectors` adalah array vektor embedding, jadi untuk mendapatkan embedding dari sebuah kata `s` kita perlu menggunakan `vocab.vectors[vocab.stoi[s]]`\n",
|
|
"\n",
|
|
"Berikut adalah contoh manipulasi embedding untuk menunjukkan persamaan **kind-man+woman = queen** (saya harus menyesuaikan koefisien sedikit agar berhasil):\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'queen'"
|
|
]
|
|
},
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# get the vector corresponding to kind-man+woman\n",
|
|
"qvec = vocab.vectors[vocab.stoi['king']]-vocab.vectors[vocab.stoi['man']]+1.3*vocab.vectors[vocab.stoi['woman']]\n",
|
|
"# find the index of the closest embedding vector \n",
|
|
"d = torch.sum((vocab.vectors-qvec)**2,dim=1)\n",
|
|
"min_idx = torch.argmin(d)\n",
|
|
"# find the corresponding word\n",
|
|
"vocab.itos[min_idx]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Untuk melatih classifier menggunakan embedding tersebut, kita pertama-tama perlu mengenkripsi dataset kita menggunakan kosakata GloVe:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def offsetify(b):\n",
|
|
" # first, compute data tensor from all sequences\n",
|
|
" x = [torch.tensor(encode(t[1],voc=vocab)) for t in b] # pass the instance of vocab to encode function!\n",
|
|
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
|
" o = [0] + [len(t) for t in x]\n",
|
|
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
|
" return ( \n",
|
|
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
|
" torch.cat(x), # text \n",
|
|
" o\n",
|
|
" )"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Seperti yang telah kita lihat di atas, semua vektor embedding disimpan dalam matriks `vocab.vectors`. Hal ini membuatnya sangat mudah untuk memuat bobot tersebut ke dalam bobot lapisan embedding dengan penyalinan sederhana:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"net = EmbedClassifier(len(vocab),len(vocab.vectors[0]),len(classes))\n",
|
|
"net.embedding.weight.data = vocab.vectors\n",
|
|
"net = net.to(device)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Sekarang mari kita latih model kita dan lihat apakah kita mendapatkan hasil yang lebih baik:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6271875\n",
|
|
"6400: acc=0.68078125\n",
|
|
"9600: acc=0.7030208333333333\n",
|
|
"12800: acc=0.71984375\n",
|
|
"16000: acc=0.7346875\n",
|
|
"19200: acc=0.7455729166666667\n",
|
|
"22400: acc=0.7529464285714286\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(35.53972978646833, 0.7575175943698017)"
|
|
]
|
|
},
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Salah satu alasan mengapa kita tidak melihat peningkatan akurasi yang signifikan adalah karena beberapa kata dari dataset kita tidak ada dalam kosakata GloVe yang telah dilatih sebelumnya, sehingga kata-kata tersebut pada dasarnya diabaikan. Untuk mengatasi hal ini, kita dapat melatih embedding kita sendiri pada dataset kita.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Embedding Kontekstual\n",
|
|
"\n",
|
|
"Salah satu keterbatasan utama dari representasi embedding pra-latih tradisional seperti Word2Vec adalah masalah disambiguasi makna kata. Meskipun embedding pra-latih dapat menangkap sebagian makna kata dalam konteks, semua kemungkinan makna dari sebuah kata dikodekan ke dalam embedding yang sama. Hal ini dapat menyebabkan masalah pada model hilir, karena banyak kata seperti kata 'play' memiliki makna yang berbeda tergantung pada konteks penggunaannya.\n",
|
|
"\n",
|
|
"Sebagai contoh, kata 'play' dalam dua kalimat berikut memiliki makna yang cukup berbeda:\n",
|
|
"- Saya pergi menonton sebuah **play** di teater.\n",
|
|
"- John ingin **play** dengan teman-temannya.\n",
|
|
"\n",
|
|
"Embedding pra-latih di atas merepresentasikan kedua makna kata 'play' tersebut dalam embedding yang sama. Untuk mengatasi keterbatasan ini, kita perlu membangun embedding berdasarkan **model bahasa**, yang dilatih pada korpus teks yang besar, dan *memahami* bagaimana kata-kata dapat disusun dalam berbagai konteks. Membahas embedding kontekstual berada di luar cakupan tutorial ini, tetapi kita akan kembali membahasnya saat berbicara tentang model bahasa 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": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "py37_pytorch",
|
|
"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": "f50b026abce5cf36783a560ea72cb9b1",
|
|
"translation_date": "2025-08-29T16:30:50+00:00",
|
|
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
|
"language_code": "id"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |