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

725 lines
29 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 임베딩\n",
"\n",
"이전 예제에서는 길이가 `vocab_size`인 고차원 bag-of-words 벡터를 사용했으며, 저차원 위치 표현 벡터를 희소한 원-핫 표현으로 명시적으로 변환했습니다. 하지만 이 원-핫 표현은 메모리 효율적이지 않을 뿐만 아니라, 각 단어가 서로 독립적으로 처리됩니다. 즉, 원-핫 인코딩된 벡터는 단어 간의 의미적 유사성을 전혀 표현하지 못합니다.\n",
"\n",
"이번 단원에서는 **News AG** 데이터셋을 계속 탐구할 것입니다. 시작하기 위해 데이터를 로드하고 이전 노트북에서 사용한 몇 가지 정의를 가져오겠습니다.\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": [
"## 임베딩이란 무엇인가?\n",
"\n",
"**임베딩(embedding)**의 개념은 단어를 낮은 차원의 밀집 벡터로 표현하는 것입니다. 이 벡터는 단어의 의미를 어느 정도 반영합니다. 나중에 의미 있는 단어 임베딩을 만드는 방법에 대해 논의하겠지만, 지금은 임베딩을 단어 벡터의 차원을 줄이는 방법으로 생각해 봅시다.\n",
"\n",
"임베딩 레이어는 단어를 입력으로 받아 지정된 `embedding_size` 크기의 출력 벡터를 생성합니다. 어떤 면에서는 `Linear` 레이어와 매우 유사하지만, 원-핫 인코딩된 벡터를 입력으로 받는 대신 단어 번호를 입력으로 받을 수 있습니다.\n",
"\n",
"네트워크의 첫 번째 레이어로 임베딩 레이어를 사용하면, 우리가 사용하는 모델을 **임베딩 백(embedding bag)** 모델로 전환할 수 있습니다. 이 모델에서는 텍스트의 각 단어를 해당 임베딩으로 변환한 다음, `sum`, `average`, `max`와 같은 집계 함수를 사용해 모든 임베딩에 대해 계산을 수행합니다.\n",
"\n",
"![다섯 개의 시퀀스 단어에 대한 임베딩 분류기를 보여주는 이미지.](../../../../../lessons/5-NLP/14-Embeddings/images/embedding-classifier-example.png)\n",
"\n",
"우리의 분류기 신경망은 임베딩 레이어로 시작하여, 그 다음 집계 레이어, 그리고 그 위에 선형 분류기로 구성됩니다:\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": [
"### 가변적인 시퀀스 크기 처리\n",
"\n",
"이 아키텍처의 결과로 인해, 네트워크에 전달할 미니배치를 특정 방식으로 생성해야 합니다. 이전 단원에서 Bag-of-Words를 사용할 때는, 미니배치 내의 모든 BoW 텐서가 텍스트 시퀀스의 실제 길이와 상관없이 `vocab_size`로 동일한 크기를 가졌습니다. 하지만 단어 임베딩으로 전환하면, 각 텍스트 샘플에 포함된 단어 수가 달라지게 됩니다. 이러한 샘플들을 미니배치로 결합할 때는 패딩을 적용해야 합니다.\n",
"\n",
"이 작업은 데이터 소스에 `collate_fn` 함수를 제공하는 동일한 기술을 사용하여 수행할 수 있습니다:\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": [
"### 임베딩 분류기 학습\n",
"\n",
"적절한 데이터 로더를 정의했으니, 이전 단원에서 정의한 학습 함수를 사용하여 모델을 학습시킬 수 있습니다:\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": [
"> **참고**: 여기서는 시간 절약을 위해 25,000개의 레코드(전체 에포크보다 적음)만 학습하지만, 여러 에포크 동안 학습을 계속하고 학습률 매개변수를 실험하여 더 높은 정확도를 달성할 수 있습니다. 약 90%의 정확도에 도달할 수 있어야 합니다.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### EmbeddingBag 레이어와 가변 길이 시퀀스 표현\n",
"\n",
"이전 아키텍처에서는 미니배치에 맞추기 위해 모든 시퀀스를 동일한 길이로 패딩해야 했습니다. 이는 가변 길이 시퀀스를 표현하는 가장 효율적인 방법은 아닙니다. 다른 접근법으로는 **offset** 벡터를 사용하는 것이 있습니다. 이 벡터는 하나의 큰 벡터에 저장된 모든 시퀀스의 오프셋을 포함합니다.\n",
"\n",
"![오프셋 시퀀스 표현을 보여주는 이미지](../../../../../lessons/5-NLP/14-Embeddings/images/offset-sequence-representation.png)\n",
"\n",
"> **Note**: 위 그림에서는 문자 시퀀스를 보여주고 있지만, 우리의 예제에서는 단어 시퀀스를 다룹니다. 그러나 오프셋 벡터로 시퀀스를 표현하는 일반적인 원리는 동일합니다.\n",
"\n",
"오프셋 표현을 사용하기 위해 [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html) 레이어를 사용합니다. 이 레이어는 `Embedding`과 유사하지만, 콘텐츠 벡터와 오프셋 벡터를 입력으로 받으며, 평균화 레이어를 포함합니다. 이 평균화는 `mean`, `sum`, 또는 `max`로 설정할 수 있습니다.\n",
"\n",
"다음은 `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": [
"훈련을 위한 데이터셋을 준비하려면 오프셋 벡터를 준비할 변환 함수를 제공해야 합니다.\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": [
"이전에 나온 모든 예제와 달리, 이제 우리의 네트워크는 서로 다른 크기의 데이터 벡터와 오프셋 벡터라는 두 개의 매개변수를 받습니다. 마찬가지로, 우리의 데이터 로더도 2개 대신 3개의 값을 제공합니다: 텍스트와 오프셋 벡터가 모두 특징으로 제공됩니다. 따라서, 이를 처리하기 위해 우리의 훈련 함수를 약간 조정해야 합니다:\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": [
"## 시맨틱 임베딩: Word2Vec\n",
"\n",
"이전 예제에서 모델의 임베딩 레이어는 단어를 벡터 표현으로 매핑하는 방법을 학습했지만, 이 표현은 의미론적 의미가 많지 않았습니다. 비슷한 단어나 동의어가 특정 벡터 거리(예: 유클리드 거리) 측면에서 서로 가까운 벡터로 대응되는 벡터 표현을 학습할 수 있다면 좋을 것입니다.\n",
"\n",
"이를 위해서는 특정 방식으로 대규모 텍스트 컬렉션에서 임베딩 모델을 사전 학습해야 합니다. 시맨틱 임베딩을 학습하는 초기 방법 중 하나는 [Word2Vec](https://en.wikipedia.org/wiki/Word2vec)이라고 불립니다. 이는 단어의 분산 표현을 생성하기 위해 사용되는 두 가지 주요 아키텍처를 기반으로 합니다:\n",
"\n",
" - **연속적 Bag-of-Words** (CBoW) — 이 아키텍처에서는 주변 문맥으로부터 단어를 예측하도록 모델을 학습시킵니다. n그램 $(W_{-2},W_{-1},W_0,W_1,W_2)$가 주어졌을 때, 모델의 목표는 $(W_{-2},W_{-1},W_1,W_2)$로부터 $W_0$를 예측하는 것입니다.\n",
" - **연속적 Skip-Gram** — CBoW와 반대입니다. 이 모델은 현재 단어를 예측하기 위해 주변 문맥 단어의 윈도우를 사용합니다.\n",
"\n",
"CBoW는 더 빠르지만, Skip-Gram은 더 느리며 드문 단어를 표현하는 데 더 효과적입니다.\n",
"\n",
"![단어를 벡터로 변환하는 CBoW와 Skip-Gram 알고리즘을 보여주는 이미지.](../../../../../lessons/5-NLP/14-Embeddings/images/example-algorithms-for-converting-words-to-vectors.png)\n",
"\n",
"Google News 데이터셋에서 사전 학습된 Word2Vec 임베딩을 실험하려면 **gensim** 라이브러리를 사용할 수 있습니다. 아래는 'neural'과 가장 유사한 단어를 찾는 예제입니다.\n",
"\n",
"> **Note:** 처음으로 단어 벡터를 생성할 때, 다운로드하는 데 시간이 걸릴 수 있습니다!\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": [
"우리는 단어로부터 벡터 임베딩을 계산하여 분류 모델 훈련에 사용할 수 있습니다 (명확성을 위해 벡터의 첫 20개 구성 요소만 표시합니다):\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": [
"문장의 임베딩의 훌륭한 점은 벡터 인코딩을 조작하여 의미를 변경할 수 있다는 것입니다. 예를 들어, 우리는 *king*과 *woman*이라는 단어에 최대한 가까우면서 *man*이라는 단어와는 최대한 멀리 떨어진 벡터 표현을 가진 단어를 찾을 수 있습니다:\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": [
"CBoW와 Skip-Grams는 모두 \"예측 기반\" 임베딩으로, 로컬 컨텍스트만을 고려합니다. Word2Vec은 글로벌 컨텍스트를 활용하지 않습니다.\n",
"\n",
"**FastText**는 Word2Vec을 기반으로 각 단어와 단어 내에서 발견되는 문자 n-그램에 대한 벡터 표현을 학습합니다. 이 표현 값들은 각 학습 단계에서 하나의 벡터로 평균화됩니다. 이는 사전 학습에 많은 추가 계산을 요구하지만, 단어 임베딩이 서브워드 정보를 인코딩할 수 있도록 합니다.\n",
"\n",
"또 다른 방법인 **GloVe**는 공기행렬(co-occurrence matrix)의 아이디어를 활용하며, 공기행렬을 더 표현력 있고 비선형적인 단어 벡터로 분해하기 위해 신경망 방법을 사용합니다.\n",
"\n",
"gensim은 여러 가지 단어 임베딩 모델을 지원하므로, FastText와 GloVe로 임베딩을 변경하여 예제를 실험해볼 수 있습니다.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## PyTorch에서 사전 학습된 임베딩 사용하기\n",
"\n",
"위의 예제를 수정하여 임베딩 레이어의 행렬을 Word2Vec과 같은 의미적 임베딩으로 미리 채울 수 있습니다. 사전 학습된 임베딩의 어휘와 우리의 텍스트 코퍼스의 어휘가 일치하지 않을 가능성이 높으므로, 누락된 단어에 대한 가중치는 랜덤 값으로 초기화해야 합니다:\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": [
"이제 모델을 훈련시켜 봅시다. 모델을 훈련시키는 데 걸리는 시간이 이전 예제보다 훨씬 더 길다는 점에 유의하세요. 이는 더 큰 임베딩 레이어 크기와 훨씬 더 많은 매개변수 때문입니다. 또한, 이러한 이유로 과적합을 피하려면 더 많은 예제에서 모델을 훈련시켜야 할 수도 있습니다.\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": [
"우리의 경우, 정확도가 크게 향상되지 않는 것을 볼 수 있는데, 이는 아마도 상당히 다른 어휘 때문일 가능성이 높습니다. \n",
"서로 다른 어휘 문제를 해결하기 위해 다음과 같은 방법 중 하나를 사용할 수 있습니다: \n",
"* 우리의 어휘로 word2vec 모델을 다시 학습시키기 \n",
"* 사전 학습된 word2vec 모델의 어휘를 사용하여 데이터셋을 로드하기. 데이터셋을 로드할 때 사용할 어휘는 로드 과정에서 지정할 수 있습니다. \n",
"\n",
"후자의 접근 방식이 더 쉬워 보이는데, 특히 PyTorch의 `torchtext` 프레임워크가 임베딩에 대한 내장 지원을 포함하고 있기 때문입니다. \n",
"예를 들어, GloVe 기반 어휘를 다음과 같은 방식으로 인스턴스화할 수 있습니다: \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": [
"로드된 어휘는 다음과 같은 기본 작업을 제공합니다:\n",
"* `vocab.stoi` 사전은 단어를 사전 인덱스로 변환할 수 있도록 해줍니다.\n",
"* `vocab.itos`는 반대로 숫자를 단어로 변환합니다.\n",
"* `vocab.vectors`는 임베딩 벡터의 배열로, 단어 `s`의 임베딩을 얻으려면 `vocab.vectors[vocab.stoi[s]]`를 사용해야 합니다.\n",
"\n",
"다음은 임베딩을 조작하여 **kind-man+woman = queen**이라는 방정식을 보여주는 예제입니다 (작동하도록 계수를 약간 조정했습니다):\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": [
"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": [
"위에서 본 바와 같이, 모든 벡터 임베딩은 `vocab.vectors` 매트릭스에 저장됩니다. 간단한 복사를 통해 임베딩 레이어의 가중치에 이러한 가중치를 로드하는 것이 매우 쉽습니다.\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": [
"우리 데이터셋의 일부 단어가 사전 학습된 GloVe 어휘에 없기 때문에 정확도가 크게 증가하지 않는 이유 중 하나입니다. 따라서 이러한 단어들은 사실상 무시됩니다. 이 문제를 해결하기 위해, 우리는 데이터셋에서 자체 임베딩을 학습시킬 수 있습니다.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 문맥적 임베딩\n",
"\n",
"Word2Vec와 같은 전통적인 사전 학습 임베딩 표현의 주요 한계 중 하나는 단어 의미의 중의성 문제입니다. 사전 학습된 임베딩은 단어의 문맥적 의미를 어느 정도 포착할 수 있지만, 단어의 모든 가능한 의미가 동일한 임베딩에 인코딩됩니다. 이는 'play'와 같은 많은 단어가 사용되는 문맥에 따라 다른 의미를 가지기 때문에, 후속 모델에서 문제를 일으킬 수 있습니다.\n",
"\n",
"예를 들어, 'play'라는 단어는 다음 두 문장에서 매우 다른 의미를 가집니다:\n",
"- 나는 극장에서 **연극**을 봤다.\n",
"- 존은 친구들과 **놀고** 싶어한다.\n",
"\n",
"위의 사전 학습된 임베딩은 'play'라는 단어의 두 가지 의미를 동일한 임베딩으로 표현합니다. 이러한 한계를 극복하기 위해서는 **언어 모델**을 기반으로 한 임베딩을 구축해야 합니다. 언어 모델은 방대한 텍스트 코퍼스에서 학습되며, 단어들이 다양한 문맥에서 어떻게 조합될 수 있는지를 *이해*합니다. 문맥적 임베딩에 대한 논의는 이 튜토리얼의 범위를 벗어나지만, 다음 단원에서 언어 모델을 다룰 때 다시 논의할 것입니다.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\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-31T14:06:05+00:00",
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
"language_code": "ko"
}
},
"nbformat": 4,
"nbformat_minor": 2
}