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

725 lines
47 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## എംബെഡിംഗുകൾ\n",
"\n",
"മുൻ ഉദാഹരണത്തിൽ, നാം ഉയർന്ന-ഡൈമെൻഷണൽ ബാഗ്-ഓഫ്-വേർഡ് വെക്ടറുകളുമായി പ്രവർത്തിച്ചിരുന്നു, അവയുടെ നീളം `vocab_size` ആയിരുന്നു, കൂടാതെ നാം താഴ്ന്ന-ഡൈമെൻഷണൽ പൊസിഷണൽ പ്രതിനിധാന വെക്ടറുകളിൽ നിന്ന് സ്പാർസ് വൺ-ഹോട്ട് പ്രതിനിധാനത്തിലേക്ക് വ്യക്തമായി മാറ്റം നടത്തുകയായിരുന്നു. ഈ വൺ-ഹോട്ട് പ്രതിനിധാനം മെമ്മറി കാര്യക്ഷമമല്ല, കൂടാതെ ഓരോ വാക്കും പരസ്പരം സ്വതന്ത്രമായി പരിഗണിക്കപ്പെടുന്നു, അഥവാ വൺ-ഹോട്ട് എൻകോഡുചെയ്ത വെക്ടറുകൾ വാക്കുകൾക്കിടയിലെ സാംസാരിക സമാനതയെ പ്രകടിപ്പിക്കുന്നില്ല.\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",
"**എമ്പെഡ്ഡിംഗ്** എന്ന ആശയം വാക്കുകളെ കുറവ്-ഡൈമെൻഷണൽ സാന്ദ്ര വെക്ടറുകളായി പ്രതിനിധീകരിക്കുന്നതാണ്, അവ വാക്കിന്റെ അർത്ഥപരമായ അർത്ഥം ഏതോ വിധത്തിൽ പ്രതിഫലിപ്പിക്കുന്നു. നാം പിന്നീട് അർത്ഥവത്തായ വാക്ക് എമ്പെഡ്ഡിംഗുകൾ എങ്ങനെ നിർമ്മിക്കാമെന്ന് ചർച്ച ചെയ്യും, പക്ഷേ ഇപ്പോൾ എമ്പെഡ്ഡിംഗുകളെ വാക്ക് വെക്ടറിന്റെ ഡൈമെൻഷൻ കുറയ്ക്കാനുള്ള ഒരു മാർഗമായി മാത്രം കരുതാം.\n",
"\n",
"അതിനാൽ, എമ്പെഡ്ഡിംഗ് ലെയർ ഒരു വാക്ക് ഇൻപുട്ടായി സ്വീകരിച്ച്, നിശ്ചിത `embedding_size` ഉള്ള ഔട്ട്പുട്ട് വെക്ടർ ഉത്പാദിപ്പിക്കും. ഒരു അർത്ഥത്തിൽ, ഇത് `Linear` ലെയറിനോട് വളരെ സമാനമാണ്, പക്ഷേ ഒന്ന്-ഹോട്ട് എൻകോഡുചെയ്ത വെക്ടർ സ്വീകരിക്കുന്നതിന് പകരം, വാക്കിന്റെ നമ്പർ ഇൻപുട്ടായി സ്വീകരിക്കാൻ കഴിയും.\n",
"\n",
"നമ്മുടെ നെറ്റ്‌വർക്കിലെ ആദ്യ ലെയറായി എമ്പെഡ്ഡിംഗ് ലെയർ ഉപയോഗിച്ച്, നാം ബാഗ്-ഓഫ്-വേർഡ്സ് മോഡലിൽ നിന്ന് **എമ്പെഡ്ഡിംഗ് ബാഗ്** മോഡലിലേക്ക് മാറാം, ഇവിടെ ആദ്യം നമ്മുടെ ടെക്സ്റ്റിലെ ഓരോ വാക്കും അനുയോജ്യമായ എമ്പെഡ്ഡിങ്ങിലേക്ക് മാറ്റുകയും, പിന്നീട് ആ എമ്പെഡ്ഡിങ്ങുകളുടെ മേൽ `sum`, `average` അല്ലെങ്കിൽ `max` പോലുള്ള ഏതെങ്കിലും സംഗ്രഹ ഫംഗ്ഷൻ കണക്കാക്കുകയും ചെയ്യും.\n",
"\n",
"![അഞ്ച് സീക്വൻസ് വാക്കുകൾക്കുള്ള എമ്പെഡ്ഡിംഗ് ക്ലാസിഫയർ കാണിക്കുന്ന ചിത്രം.](../../../../../translated_images/ml/embedding-classifier-example.b77f021a7ee67eee.webp)\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",
"ഈ ആർക്കിടെക്ചറിന്റെ ഫലമായി, നമ്മുടെ നെറ്റ്‌വർക്കിലേക്ക് മിനിബാച്ചുകൾ ഒരു പ്രത്യേക രീതിയിൽ സൃഷ്ടിക്കേണ്ടിവരും. മുമ്പത്തെ യൂണിറ്റിൽ, ബാഗ്-ഓഫ്-വേർഡ്സ് ഉപയോഗിക്കുമ്പോൾ, മിനിബാച്ചിലെ എല്ലാ 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",
"മുൻവർഷത്തെ ആർക്കിടെക്ചറിൽ, മിനിബാച്ചിൽ ഫിറ്റ് ചെയ്യാൻ എല്ലാ സീക്വൻസുകളും ഒരേ നീളത്തിലേക്ക് പാഡ് ചെയ്യേണ്ടിവന്നു. വ്യത്യസ്ത നീളമുള്ള സീക്വൻസുകൾ പ്രതിനിധാനം ചെയ്യാനുള്ള ഏറ്റവും കാര്യക്ഷമമായ മാർഗം ഇത് അല്ല - മറ്റൊരു സമീപനം **ഓഫ്സെറ്റ്** വെക്ടർ ഉപയോഗിക്കുകയാണ്, ഇത് ഒരു വലിയ വെക്ടറിൽ സൂക്ഷിച്ചിരിക്കുന്ന എല്ലാ സീക്വൻസുകളുടെ ഓഫ്സെറ്റുകൾ സൂക്ഷിക്കും.\n",
"\n",
"![ഓഫ്സെറ്റ് സീക്വൻസ് പ്രതിനിധാനം കാണിക്കുന്ന ചിത്രം](../../../../../translated_images/ml/offset-sequence-representation.eb73fcefb29b46ee.webp)\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",
" - **കണ്ടിന്യൂവസ് ബാഗ്-ഓഫ്-വേർഡ്സ്** (CBoW) — ഈ ആർക്കിടെക്ചറിൽ, മോഡൽ ചുറ്റുപാടിലുള്ള കോൺടെക്സ്റ്റിൽ നിന്നൊരു വാക്ക് പ്രവചിക്കാൻ പരിശീലിപ്പിക്കുന്നു. n-ഗ്രാം $(W_{-2},W_{-1},W_0,W_1,W_2)$ നൽകിയാൽ, മോഡലിന്റെ ലക്ഷ്യം $(W_{-2},W_{-1},W_1,W_2)$-ൽ നിന്നു $W_0$ പ്രവചിക്കുക എന്നതാണ്.\n",
" - **കണ്ടിന്യൂവസ് സ്കിപ്പ്-ഗ്രാം** CBoW-യുടെ വിപരീതമാണ്. മോഡൽ നിലവിലുള്ള വാക്ക് പ്രവചിക്കാൻ ചുറ്റുപാടിലുള്ള കോൺടെക്സ്റ്റ് വാക്കുകളുടെ വിൻഡോ ഉപയോഗിക്കുന്നു.\n",
"\n",
"CBoW വേഗത്തിൽ പ്രവർത്തിക്കുന്നു, സ്കിപ്പ്-ഗ്രാം മന്ദഗതിയിലാണ്, പക്ഷേ അപൂർവമായ വാക്കുകൾ പ്രതിനിധാനം ചെയ്യുന്നതിൽ മികച്ചതാണ്.\n",
"\n",
"![വാക്കുകളെ വെക്ടറുകളാക്കി മാറ്റാൻ CBoWയു സ്കിപ്പ്-ഗ്രാം ആൽഗോരിതങ്ങളും കാണിക്കുന്ന ചിത്രം.](../../../../../translated_images/ml/example-algorithms-for-converting-words-to-vectors.fbe9207a726922f6.webp)\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 ഉം “പ്രിഡിക്ടീവ്” embeddings ആണ്, കാരണം അവയ്ക്ക് പ്രാദേശിക കോൺടെക്സ്റ്റുകൾ മാത്രമേ പരിഗണിക്കൂ. Word2Vec ആഗോള കോൺടെക്സ്റ്റിന്റെ പ്രയോജനം ഉപയോഗപ്പെടുത്തുന്നില്ല.\n",
"\n",
"**FastText**, Word2Vec-നെ അടിസ്ഥാനമാക്കി, ഓരോ വാക്കിനും അതിലെ അക്ഷര n-ഗ്രാമുകൾക്കും വെക്ടർ പ്രതിനിധാനങ്ങൾ പഠിക്കുന്നു. ഈ പ്രതിനിധാനങ്ങളുടെ മൂല്യങ്ങൾ ഓരോ പരിശീലന ഘട്ടത്തിലും ശരാശരി എടുത്ത് ഒരു വെക്ടറായി മാറ്റുന്നു. ഇതു പ്രീ-ട്രെയിനിംഗിൽ അധിക കണക്കുകൂട്ടലുകൾ കൂട്ടിച്ചേർക്കുന്നുവെങ്കിലും, വാക്കുകളുടെ സബ്-വേർഡ് വിവരങ്ങൾ എൻകോഡ് ചെയ്യാൻ സാധ്യമാക്കുന്നു.\n",
"\n",
"മറ്റൊരു രീതി, **GloVe**, കോ-ഓക്കറൻസ് മാട്രിക്സ് എന്ന ആശയം ഉപയോഗിച്ച്, കോ-ഓക്കറൻസ് മാട്രിക്സ് ന്യുറൽ രീതികളിലൂടെ കൂടുതൽ പ്രകടനശേഷിയുള്ള, നോൺ-ലീനിയർ വേഡ് വെക്ടറുകളായി വിഭജിക്കുന്നു.\n",
"\n",
"gensim വിവിധ വേഡ് എംബെഡ്ഡിംഗ് മോഡലുകൾ പിന്തുണയ്ക്കുന്നതിനാൽ, FastText, GloVe എന്നിവയിലേക്ക് embeddings മാറ്റി ഉദാഹരണം പരീക്ഷിക്കാം.\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",
"* നമ്മുടെ വാക്കുകളുടെ അടിസ്ഥാനത്തിൽ word2vec മോഡൽ വീണ്ടും പരിശീലിപ്പിക്കുക\n",
"* മുൻകൂട്ടി പരിശീലിപ്പിച്ച word2vec മോഡലിലെ വാക്കുകൾ ഉപയോഗിച്ച് dataset ലോഡ് ചെയ്യുക. Dataset ലോഡുചെയ്യുമ്പോൾ ഉപയോഗിക്കുന്ന വാക്കുകൾ വ്യക്തമാക്കാം.\n",
"\n",
"അവസാന മാർഗം എളുപ്പമാണ്, പ്രത്യേകിച്ച് PyTorch `torchtext` ഫ്രെയിംവർക്ക് എംബെഡിംഗുകൾക്ക് ഉൾക്കൊള്ളുന്ന പിന്തുണ ഉള്ളതിനാൽ. ഉദാഹരണത്തിന്, 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": [
"ഇപ്പോൾ നമുക്ക് നമ്മുടെ മോഡൽ പരിശീലിപ്പിച്ച് മികച്ച ഫലങ്ങൾ ലഭിക്കുന്നുണ്ടോ എന്ന് നോക്കാം:\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": [
"നമ്മുടെ കൃത്യതയിൽ വലിയ വർദ്ധനവ് കാണാത്തതിന്റെ ഒരു കാരണമാണ് നമ്മുടെ ഡാറ്റാസെറ്റിലെ ചില വാക്കുകൾ പ്രീ-ട്രെയിൻ ചെയ്ത GloVe വാക്കസഞ്ചാരത്തിൽ ഇല്ലാതിരിക്കുക, അതിനാൽ അവ അടിസ്ഥാനപരമായി അവഗണിക്കപ്പെടുന്നു. ഈ പ്രശ്നം മറികടക്കാൻ, നാം നമ്മുടെ ഡാറ്റാസെറ്റിൽ തന്നെ embeddings പരിശീലിപ്പിക്കാം.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## സാന്ദർഭിക എംബെഡിംഗുകൾ\n",
"\n",
"Word2Vec പോലുള്ള പരമ്പരാഗത പ്രീട്രെയിൻഡ് എംബെഡിംഗ് പ്രതിനിധാനങ്ങളുടെ ഒരു പ്രധാന പരിമിതിയാണ് വാക്കിന്റെ അർത്ഥ വ്യത്യാസം തിരിച്ചറിയാനുള്ള പ്രശ്നം. പ്രീട്രെയിൻഡ് എംബെഡിംഗുകൾ വാക്കുകളുടെ ചില അർത്ഥങ്ങൾ സാന്ദർഭികമായി പിടിച്ചുപറ്റാൻ കഴിയുമ്പോഴും, ഒരു വാക്കിന്റെ എല്ലാ സാധ്യതയുള്ള അർത്ഥങ്ങളും ഒരേ എംബെഡിംഗിൽ കോഡ് ചെയ്യപ്പെടുന്നു. ഇത് ഡൗൺസ്ട്രീം മോഡലുകളിൽ പ്രശ്നങ്ങൾ സൃഷ്ടിക്കാം, കാരണം 'play' പോലുള്ള പല വാക്കുകൾ ഉപയോഗിക്കുന്ന സാന്ദർഭം അനുസരിച്ച് വ്യത്യസ്ത അർത്ഥങ്ങൾ ഉണ്ടാകാം.\n",
"\n",
"ഉദാഹരണത്തിന്, 'play' എന്ന വാക്ക് താഴെ കൊടുത്ത രണ്ട് വ്യത്യസ്ത വാചകങ്ങളിൽ വളരെ വ്യത്യസ്ത അർത്ഥം നൽകുന്നു:\n",
"- ഞാൻ തിയേറ്ററിൽ ഒരു **play** കാണാൻ പോയി.\n",
"- ജോൺ തന്റെ സുഹൃത്തുക്കളുമായി **play** ചെയ്യാൻ ആഗ്രഹിക്കുന്നു.\n",
"\n",
"മുകളിൽ കൊടുത്ത പ്രീട്രെയിൻഡ് എംബെഡിംഗുകൾ ഈ രണ്ട് അർത്ഥങ്ങളും ഒരേ എംബെഡിംഗിൽ പ്രതിനിധാനം ചെയ്യുന്നു. ഈ പരിമിതിയെ മറികടക്കാൻ, വലിയ വാചകശേഖരത്തിൽ പരിശീലിപ്പിച്ച **ഭാഷാ മോഡൽ** അടിസ്ഥാനമാക്കി എംബെഡിംഗുകൾ നിർമ്മിക്കേണ്ടതുണ്ട്, ഇത് വാക്കുകൾ വ്യത്യസ്ത സാന്ദർഭങ്ങളിൽ എങ്ങനെ ചേർക്കാമെന്ന് *അറിയുന്നു*. സാന്ദർഭിക എംബെഡിംഗുകൾ സംബന്ധിച്ച ചർച്ച ഈ ട്യൂട്ടോറിയലിന്റെ പരിധിയിൽ ഇല്ല, പക്ഷേ അടുത്ത യൂണിറ്റിൽ ഭാഷാ മോഡലുകൾക്കുറിച്ച് സംസാരിക്കുമ്പോൾ അവയെക്കുറിച്ച് വീണ്ടും വരാം.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n\n<!-- CO-OP TRANSLATOR DISCLAIMER START -->\n**അസൂയാ**: \nഈ രേഖ AI വിവർത്തന സേവനം [Co-op Translator](https://github.com/Azure/co-op-translator) ഉപയോഗിച്ച് വിവർത്തനം ചെയ്തതാണ്. നാം കൃത്യതയ്ക്ക് ശ്രമിച്ചിട്ടുണ്ടെങ്കിലും, സ്വയം പ്രവർത്തിക്കുന്ന വിവർത്തനങ്ങളിൽ പിശകുകൾ അല്ലെങ്കിൽ തെറ്റുകൾ ഉണ്ടാകാമെന്ന് ദയവായി ശ്രദ്ധിക്കുക. അതിന്റെ മാതൃഭാഷയിലുള്ള യഥാർത്ഥ രേഖയാണ് പ്രാമാണികമായ ഉറവിടം എന്ന് പരിഗണിക്കേണ്ടതാണ്. നിർണായകമായ വിവരങ്ങൾക്ക്, പ്രൊഫഷണൽ മനുഷ്യ വിവർത്തനം ശുപാർശ ചെയ്യപ്പെടുന്നു. ഈ വിവർത്തനം ഉപയോഗിക്കുന്നതിൽ നിന്നുണ്ടാകുന്ന ഏതെങ്കിലും തെറ്റിദ്ധാരണകൾക്കോ തെറ്റായ വ്യാഖ്യാനങ്ങൾക്കോ ഞങ്ങൾ ഉത്തരവാദികളല്ല.\n<!-- CO-OP TRANSLATOR DISCLAIMER END -->\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-11-26T01:45:54+00:00",
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
"language_code": "ml"
}
},
"nbformat": 4,
"nbformat_minor": 2
}