AI-For-Beginners/translations/vi/lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb

724 lines
31 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Nhúng\n",
"\n",
"Trong ví dụ trước, chúng ta đã làm việc với các vector túi từ có độ dài cao `vocab_size`, và chúng ta đã chuyển đổi rõ ràng từ các vector biểu diễn vị trí có độ chiều thấp sang biểu diễn một-hot thưa. Biểu diễn một-hot này không hiệu quả về bộ nhớ, hơn nữa, mỗi từ được xử lý độc lập với nhau, tức là các vector mã hóa một-hot không thể hiện bất kỳ sự tương đồng ngữ nghĩa nào giữa các từ.\n",
"\n",
"Trong bài học này, chúng ta sẽ tiếp tục khám phá bộ dữ liệu **News AG**. Để bắt đầu, hãy tải dữ liệu và lấy một số định nghĩa từ notebook trước.\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": [
"## Nhúng là gì?\n",
"\n",
"Ý tưởng của **nhúng** là biểu diễn các từ bằng các vector dày đặc có kích thước thấp hơn, phản ánh phần nào ý nghĩa ngữ nghĩa của một từ. Chúng ta sẽ thảo luận sau về cách xây dựng các nhúng từ có ý nghĩa, nhưng hiện tại hãy chỉ nghĩ về nhúng như một cách để giảm kích thước của vector từ.\n",
"\n",
"Vì vậy, lớp nhúng sẽ nhận một từ làm đầu vào và tạo ra một vector đầu ra với `embedding_size` được chỉ định. Theo một cách nào đó, nó rất giống với lớp `Linear`, nhưng thay vì nhận vector mã hóa một-hot, nó sẽ có thể nhận số thứ tự của từ làm đầu vào.\n",
"\n",
"Bằng cách sử dụng lớp nhúng làm lớp đầu tiên trong mạng của chúng ta, chúng ta có thể chuyển từ mô hình túi từ (**bag-of-words**) sang mô hình túi nhúng (**embedding bag**), nơi chúng ta đầu tiên chuyển đổi mỗi từ trong văn bản thành nhúng tương ứng, sau đó tính toán một hàm tổng hợp nào đó trên tất cả các nhúng đó, chẳng hạn như `sum`, `average` hoặc `max`.\n",
"\n",
"![Hình ảnh minh họa một bộ phân loại nhúng cho năm từ trong chuỗi.](../../../../../translated_images/vi/embedding-classifier-example.b77f021a7ee67eee.webp)\n",
"\n",
"Mạng neural phân loại của chúng ta sẽ bắt đầu với lớp nhúng, sau đó là lớp tổng hợp, và cuối cùng là bộ phân loại tuyến tính ở trên cùng:\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": [
"### Xử lý kích thước chuỗi biến đổi\n",
"\n",
"Do kiến trúc này, các minibatch cho mạng của chúng ta cần được tạo theo một cách nhất định. Trong bài học trước, khi sử dụng bag-of-words, tất cả các tensor BoW trong một minibatch đều có kích thước bằng nhau là `vocab_size`, bất kể độ dài thực tế của chuỗi văn bản. Khi chuyển sang sử dụng word embeddings, chúng ta sẽ có số lượng từ biến đổi trong mỗi mẫu văn bản, và khi kết hợp các mẫu đó thành minibatch, chúng ta sẽ cần áp dụng một số padding.\n",
"\n",
"Điều này có thể thực hiện bằng cách sử dụng cùng kỹ thuật cung cấp hàm `collate_fn` cho nguồn dữ liệu:\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": [
"### Huấn luyện bộ phân loại embedding\n",
"\n",
"Bây giờ, sau khi đã định nghĩa dataloader phù hợp, chúng ta có thể huấn luyện mô hình bằng cách sử dụng hàm huấn luyện mà chúng ta đã định nghĩa trong phần trước:\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": [
"> **Lưu ý**: Chúng tôi chỉ huấn luyện với 25k bản ghi ở đây (ít hơn một epoch đầy đủ) để tiết kiệm thời gian, nhưng bạn có thể tiếp tục huấn luyện, viết một hàm để huấn luyện trong nhiều epoch, và thử nghiệm với tham số tốc độ học để đạt độ chính xác cao hơn. Bạn nên có thể đạt độ chính xác khoảng 90%.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Lớp EmbeddingBag và Biểu diễn Chuỗi Có Độ Dài Biến Đổi\n",
"\n",
"Trong kiến trúc trước, chúng ta cần đệm tất cả các chuỗi để có cùng độ dài nhằm đưa chúng vào một minibatch. Đây không phải là cách hiệu quả nhất để biểu diễn các chuỗi có độ dài biến đổi - một cách tiếp cận khác là sử dụng **vector offset**, chứa các điểm bắt đầu của tất cả các chuỗi được lưu trữ trong một vector lớn.\n",
"\n",
"![Hình ảnh minh họa biểu diễn chuỗi bằng offset](../../../../../translated_images/vi/offset-sequence-representation.eb73fcefb29b46ee.webp)\n",
"\n",
"> **Note**: Trong hình trên, chúng ta minh họa một chuỗi ký tự, nhưng trong ví dụ của chúng ta, chúng ta đang làm việc với các chuỗi từ. Tuy nhiên, nguyên tắc chung của việc biểu diễn chuỗi bằng vector offset vẫn giữ nguyên.\n",
"\n",
"Để làm việc với biểu diễn offset, chúng ta sử dụng lớp [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html). Nó tương tự như `Embedding`, nhưng nó nhận vector nội dung và vector offset làm đầu vào, và nó cũng bao gồm một lớp tính trung bình, có thể là `mean`, `sum` hoặc `max`.\n",
"\n",
"Dưới đây là mạng đã được sửa đổi sử dụng `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": [
"Để chuẩn bị tập dữ liệu cho việc huấn luyện, chúng ta cần cung cấp một hàm chuyển đổi để chuẩn bị vector độ lệch:\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": [
"Lưu ý rằng, không giống như tất cả các ví dụ trước, mạng của chúng ta bây giờ chấp nhận hai tham số: vector dữ liệu và vector độ lệch, có kích thước khác nhau. Tương tự, trình tải dữ liệu của chúng ta cũng cung cấp 3 giá trị thay vì 2: cả vector văn bản và vector độ lệch đều được cung cấp dưới dạng đặc trưng. Do đó, chúng ta cần điều chỉnh một chút hàm huấn luyện để xử lý điều này:\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",
"Trong ví dụ trước, lớp nhúng của mô hình đã học cách ánh xạ các từ thành dạng biểu diễn vector, tuy nhiên, dạng biểu diễn này không mang nhiều ý nghĩa ngữ nghĩa. Sẽ rất hữu ích nếu chúng ta có thể học được dạng biểu diễn vector mà các từ tương tự hoặc từ đồng nghĩa sẽ tương ứng với các vector gần nhau theo một khoảng cách vector nào đó (ví dụ: khoảng cách Euclide).\n",
"\n",
"Để làm được điều này, chúng ta cần tiền huấn luyện mô hình nhúng trên một tập hợp lớn văn bản theo một cách cụ thể. Một trong những phương pháp đầu tiên để huấn luyện nhúng ngữ nghĩa được gọi là [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Phương pháp này dựa trên hai kiến trúc chính được sử dụng để tạo ra biểu diễn phân tán của từ:\n",
"\n",
" - **Continuous bag-of-words** (CBoW) — trong kiến trúc này, chúng ta huấn luyện mô hình để dự đoán một từ từ ngữ cảnh xung quanh. Với ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$, mục tiêu của mô hình là dự đoán $W_0$ từ $(W_{-2},W_{-1},W_1,W_2)$.\n",
" - **Continuous skip-gram** thì ngược lại với CBoW. Mô hình sử dụng cửa sổ ngữ cảnh xung quanh để dự đoán từ hiện tại.\n",
"\n",
"CBoW nhanh hơn, trong khi skip-gram chậm hơn nhưng làm tốt hơn trong việc biểu diễn các từ ít xuất hiện.\n",
"\n",
"![Hình ảnh minh họa cả hai thuật toán CBoW và Skip-Gram để chuyển đổi từ thành vector.](../../../../../translated_images/vi/example-algorithms-for-converting-words-to-vectors.fbe9207a726922f6.webp)\n",
"\n",
"Để thử nghiệm với nhúng word2vec được tiền huấn luyện trên tập dữ liệu Google News, chúng ta có thể sử dụng thư viện **gensim**. Dưới đây là cách tìm các từ giống nhất với 'neural'\n",
"\n",
"> **Note:** Khi bạn lần đầu tiên tạo vector từ, việc tải xuống chúng có thể mất một chút thời gian!\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": [
"Chúng ta cũng có thể tính toán các vector nhúng từ từ, để sử dụng trong việc huấn luyện mô hình phân loại (chúng tôi chỉ hiển thị 20 thành phần đầu tiên của vector để rõ ràng):\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": [
"Điều tuyệt vời về nhúng ngữ nghĩa là bạn có thể thao tác mã hóa vector để thay đổi ngữ nghĩa. Ví dụ, chúng ta có thể yêu cầu tìm một từ, mà biểu diễn vector của nó gần nhất có thể với các từ *vua* và *phụ nữ*, và xa nhất có thể với từ *đàn ông*:\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": [
"Cả CBoW và Skip-Grams đều là các phương pháp nhúng từ “dự đoán”, vì chúng chỉ xem xét ngữ cảnh cục bộ. Word2Vec không tận dụng ngữ cảnh toàn cục.\n",
"\n",
"**FastText** được xây dựng dựa trên Word2Vec bằng cách học các biểu diễn vector cho từng từ và các n-gram ký tự xuất hiện trong mỗi từ. Các giá trị của các biểu diễn này sau đó được trung bình thành một vector tại mỗi bước huấn luyện. Mặc dù điều này làm tăng đáng kể khối lượng tính toán trong giai đoạn tiền huấn luyện, nhưng nó cho phép các nhúng từ mã hóa thông tin về thành phần con của từ.\n",
"\n",
"Một phương pháp khác, **GloVe**, tận dụng ý tưởng về ma trận đồng xuất hiện, sử dụng các phương pháp học sâu để phân rã ma trận đồng xuất hiện thành các vector từ có tính biểu đạt cao và phi tuyến tính hơn.\n",
"\n",
"Bạn có thể thử nghiệm bằng cách thay đổi nhúng từ sang FastText và GloVe, vì gensim hỗ trợ nhiều mô hình nhúng từ khác nhau.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sử dụng Embedding Đã Được Huấn Luyện Trước trong PyTorch\n",
"\n",
"Chúng ta có thể chỉnh sửa ví dụ trên để điền trước ma trận trong lớp embedding của mình bằng các embedding mang tính ngữ nghĩa, chẳng hạn như Word2Vec. Cần lưu ý rằng từ vựng của embedding đã được huấn luyện trước và tập văn bản của chúng ta có thể không khớp nhau, vì vậy chúng ta sẽ khởi tạo trọng số cho các từ bị thiếu bằng các giá trị ngẫu nhiên:\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": [
"Bây giờ hãy huấn luyện mô hình của chúng ta. Lưu ý rằng thời gian huấn luyện mô hình dài hơn đáng kể so với ví dụ trước, do kích thước lớp nhúng lớn hơn, và do đó số lượng tham số cao hơn nhiều. Ngoài ra, vì lý do này, chúng ta có thể cần huấn luyện mô hình trên nhiều ví dụ hơn nếu muốn tránh hiện tượng 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": [
"Trong trường hợp của chúng ta, không thấy sự gia tăng đáng kể về độ chính xác, điều này có thể do từ vựng khá khác biệt. \n",
"Để khắc phục vấn đề về sự khác biệt từ vựng, chúng ta có thể sử dụng một trong các giải pháp sau: \n",
"* Huấn luyện lại mô hình word2vec trên từ vựng của chúng ta \n",
"* Tải tập dữ liệu của chúng ta với từ vựng từ mô hình word2vec đã được huấn luyện trước. Từ vựng được sử dụng để tải tập dữ liệu có thể được chỉ định trong quá trình tải. \n",
"\n",
"Cách tiếp cận thứ hai có vẻ dễ dàng hơn, đặc biệt vì framework `torchtext` của PyTorch có hỗ trợ tích hợp cho embeddings. Ví dụ, chúng ta có thể khởi tạo từ vựng dựa trên GloVe theo cách sau: \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": [
"Từ vựng đã tải có các thao tác cơ bản sau đây: \n",
"* Từ điển `vocab.stoi` cho phép chúng ta chuyển đổi từ thành chỉ số trong từ điển \n",
"* `vocab.itos` thực hiện ngược lại - chuyển đổi số thành từ \n",
"* `vocab.vectors` là mảng các vector nhúng, vì vậy để lấy vector nhúng của một từ `s`, chúng ta cần sử dụng `vocab.vectors[vocab.stoi[s]]` \n",
"\n",
"Dưới đây là ví dụ về việc thao tác với các vector nhúng để minh họa phương trình **kind-man+woman = queen** (Tôi đã phải điều chỉnh một chút hệ số để làm cho nó hoạt động): \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": [
"Để huấn luyện bộ phân loại bằng cách sử dụng các embeddings đó, trước tiên chúng ta cần mã hóa tập dữ liệu của mình bằng từ vựng 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": [
"Như chúng ta đã thấy ở trên, tất cả các vector embeddings được lưu trữ trong ma trận `vocab.vectors`. Điều này làm cho việc tải các trọng số đó vào trọng số của lớp embedding trở nên cực kỳ dễ dàng bằng cách sao chép đơn giản:\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": []
},
{
"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": [
"Một trong những lý do chúng ta không thấy sự gia tăng đáng kể về độ chính xác là do một số từ trong tập dữ liệu của chúng ta bị thiếu trong từ vựng GloVe đã được huấn luyện trước, và do đó chúng về cơ bản bị bỏ qua. Để khắc phục điều này, chúng ta có thể huấn luyện các embedding của riêng mình trên tập dữ liệu của chúng ta.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ngữ cảnh của Embeddings\n",
"\n",
"Một hạn chế lớn của các biểu diễn embedding được huấn luyện trước truyền thống như Word2Vec là vấn đề phân biệt nghĩa của từ. Mặc dù các embedding được huấn luyện trước có thể nắm bắt một phần ý nghĩa của từ trong ngữ cảnh, mọi ý nghĩa có thể có của một từ đều được mã hóa vào cùng một embedding. Điều này có thể gây ra vấn đề cho các mô hình sau, vì nhiều từ như từ 'play' có các nghĩa khác nhau tùy thuộc vào ngữ cảnh sử dụng.\n",
"\n",
"Ví dụ, từ 'play' trong hai câu sau có ý nghĩa khá khác nhau:\n",
"- Tôi đã đi xem một **vở kịch** tại nhà hát.\n",
"- John muốn **chơi** với bạn bè của mình.\n",
"\n",
"Các embedding được huấn luyện trước ở trên biểu diễn cả hai ý nghĩa này của từ 'play' trong cùng một embedding. Để khắc phục hạn chế này, chúng ta cần xây dựng các embedding dựa trên **mô hình ngôn ngữ**, được huấn luyện trên một tập hợp lớn văn bản và *hiểu* cách các từ có thể được kết hợp trong các ngữ cảnh khác nhau. Việc thảo luận về các embedding theo ngữ cảnh nằm ngoài phạm vi của hướng dẫn này, nhưng chúng ta sẽ quay lại chủ đề này khi nói về các mô hình ngôn ngữ trong bài học tiếp theo.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Tuyên bố miễn trừ trách nhiệm**: \nTài liệu này đã được dịch bằng dịch vụ dịch thuật AI [Co-op Translator](https://github.com/Azure/co-op-translator). Mặc dù chúng tôi cố gắng đảm bảo độ chính xác, xin lưu ý rằng các bản dịch tự động có thể chứa lỗi hoặc không chính xác. Tài liệu gốc bằng ngôn ngữ bản địa nên được coi là nguồn thông tin chính thức. Đối với các thông tin quan trọng, khuyến nghị sử dụng dịch vụ dịch thuật chuyên nghiệp bởi con người. Chúng tôi không chịu trách nhiệm cho bất kỳ sự hiểu lầm hoặc diễn giải sai nào phát sinh từ việc sử dụng bản dịch này.\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:29:43+00:00",
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
"language_code": "vi"
}
},
"nbformat": 4,
"nbformat_minor": 2
}