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

726 lines
32 KiB
Plaintext

{
"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`، ولكن بدلاً من أخذ متجه مشفر بطريقة one-hot، ستكون قادرة على أخذ رقم الكلمة كمدخل.\n",
"\n",
"باستخدام طبقة التضمين كأول طبقة في شبكتنا، يمكننا الانتقال من نموذج **bag-of-words** إلى نموذج **embedding bag**، حيث نقوم أولاً بتحويل كل كلمة في النص إلى التضمين المقابل لها، ثم نحسب وظيفة تجميعية معينة لجميع تلك التضمينات، مثل `sum` أو `average` أو `max`.\n",
"\n",
"![صورة توضح مصنف تضمين لخمس كلمات متسلسلة.](../../../../../translated_images/ar/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",
"نتيجةً لهذه البنية، يجب إنشاء المجموعات الصغيرة (minibatches) لشبكتنا بطريقة معينة. في الوحدة السابقة، عند استخدام تمثيل الحقيبة من الكلمات (bag-of-words)، كانت جميع مصفوفات BoW في المجموعة الصغيرة لها نفس الحجم `vocab_size`، بغض النظر عن الطول الفعلي لتسلسل النص. بمجرد الانتقال إلى تمثيلات الكلمات (word embeddings)، سنجد أن هناك عددًا متغيرًا من الكلمات في كل عينة نصية، وعند دمج هذه العينات في مجموعات صغيرة، سيكون علينا تطبيق بعض الحشو.\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 ألف سجل هنا (أقل من دورة كاملة) من أجل توفير الوقت، ولكن يمكنك متابعة التدريب، كتابة دالة للتدريب لعدة دورات، وتجربة معلمة معدل التعلم لتحقيق دقة أعلى. يجب أن تكون قادرًا على الوصول إلى دقة حوالي 90%.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### طبقة EmbeddingBag وتمثيل التسلسل ذو الطول المتغير\n",
"\n",
"في البنية السابقة، كنا بحاجة إلى ملء جميع التسلسلات لتكون بنفس الطول لكي تتناسب مع دفعة صغيرة. هذه ليست الطريقة الأكثر كفاءة لتمثيل التسلسلات ذات الطول المتغير - هناك نهج آخر يتمثل في استخدام **متجه الإزاحة**، الذي يحتفظ بإزاحات جميع التسلسلات المخزنة في متجه كبير واحد.\n",
"\n",
"![صورة توضح تمثيل تسلسل باستخدام الإزاحة](../../../../../translated_images/ar/offset-sequence-representation.eb73fcefb29b46ee.webp)\n",
"\n",
"> **ملاحظة**: في الصورة أعلاه، نعرض تسلسلًا من الأحرف، ولكن في مثالنا نحن نعمل مع تسلسلات من الكلمات. ومع ذلك، يبقى المبدأ العام لتمثيل التسلسلات باستخدام متجه الإزاحة كما هو.\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": [
"لاحظ أنه على عكس جميع الأمثلة السابقة، تقبل شبكتنا الآن معلمتين: متجه البيانات ومتجه الإزاحة، وهما بحجمين مختلفين. وبالمثل، يوفر لنا محمل البيانات الخاص بنا أيضًا 3 قيم بدلاً من 2: يتم توفير كل من متجهات النص والإزاحة كميزات. لذلك، نحتاج إلى تعديل وظيفة التدريب الخاصة بنا قليلاً للتعامل مع ذلك:\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) — في هذا الهيكل، نقوم بتدريب النموذج لتوقع كلمة بناءً على السياق المحيط. بالنظر إلى النغرام $(W_{-2},W_{-1},W_0,W_1,W_2)$، هدف النموذج هو توقع $W_0$ بناءً على $(W_{-2},W_{-1},W_1,W_2)$.\n",
"- **التخطي المستمر للكلمات** (Continuous skip-gram) هو عكس CBoW. يستخدم النموذج نافذة الكلمات المحيطة بالسياق لتوقع الكلمة الحالية.\n",
"\n",
"CBoW أسرع، بينما skip-gram أبطأ ولكنه يقوم بتمثيل الكلمات النادرة بشكل أفضل.\n",
"\n",
"![صورة توضح كلا من خوارزميات CBoW و Skip-Gram لتحويل الكلمات إلى متجهات.](../../../../../translated_images/ar/example-algorithms-for-converting-words-to-vectors.fbe9207a726922f6.webp)\n",
"\n",
"لتجربة تضمين word2vec المدرب مسبقًا على مجموعة بيانات أخبار Google، يمكننا استخدام مكتبة **gensim**. أدناه نجد الكلمات الأكثر تشابهًا مع 'neural'\n",
"\n",
"> **ملاحظة:** عند إنشاء متجهات الكلمات لأول مرة، قد يستغرق تنزيلها بعض الوقت!\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": [
"الشيء العظيم بشأن التضمينات الدلالية هو أنه يمكنك التلاعب بترميز المتجه لتغيير الدلالات. على سبيل المثال، يمكننا أن نطلب العثور على كلمة، يكون تمثيلها المتجهي قريبًا قدر الإمكان من الكلمات *ملك* و *امرأة*، وبعيدًا قدر الإمكان عن الكلمة *رجل*:\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-grams للأحرف الموجودة داخل كل كلمة. يتم بعد ذلك حساب متوسط قيم التمثيلات في متجه واحد في كل خطوة تدريب. على الرغم من أن هذا يضيف الكثير من العمليات الحسابية الإضافية أثناء التدريب المسبق، إلا أنه يمكّن تمثيلات الكلمات من تشفير معلومات داخل الكلمة.\n",
"\n",
"طريقة أخرى، **GloVe**، تعتمد على فكرة مصفوفة التكرار المشترك، حيث تستخدم طرقًا عصبية لتحليل مصفوفة التكرار المشترك إلى متجهات كلمات أكثر تعبيرًا وغير خطية.\n",
"\n",
"يمكنك تجربة المثال عن طريق تغيير التمثيلات إلى FastText و GloVe، حيث يدعم gensim عدة نماذج مختلفة لتمثيلات الكلمات.\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",
"النهج الأخير يبدو أسهل، خاصة لأن إطار العمل `torchtext` في PyTorch يحتوي على دعم مدمج للتضمينات. يمكننا، على سبيل المثال، إنشاء مفردات تعتمد على 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 المدربة مسبقًا، وبالتالي يتم تجاهلها بشكل أساسي. للتغلب على هذه المشكلة، يمكننا تدريب التضمينات الخاصة بنا على مجموعة البيانات الخاصة بنا.\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تم ترجمة هذا المستند باستخدام خدمة الترجمة بالذكاء الاصطناعي [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-28T04:32:31+00:00",
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
"language_code": "ar"
}
},
"nbformat": 4,
"nbformat_minor": 2
}