معاينة مختبر آمنة
CBo W-Py Torch
هذي معاينة منقّحة للقراءة فقط؛ ما فيه أي شيء يشتغل داخل الصفحة.
قراءة فقط
معاينة الدفتر
CBo W-Py Torch
> **ملاحظة بيئة التشغيل المدمجة:** هالمعاينة تستخدم عيّنة صغيرة وثابتة وآمنة من ناحية الحقوق عشان تكون النتايج قابلة للتكرار. النتايج بالحجم الكامل تحتاج مجموعة البيانات أو النموذج الموثّق بالدرس داخل بيئة خارجية معتمدة.
## تدريب نموذج حقيبة الكلمات المستمر (CBoW)
هالدفتر جزء من منهج الذكاء الاصطناعي للمبتدئين.
في هالمثال بنشوف كيف ندرّب نموذج لغة CBoW عشان نبني فضاء تضمين Word2Vec خاص فينا. وبنستخدم مجموعة البيانات AG News مصدرًا للنصوص.
import collections
import random
import re
import numpy as np
import torch
# course-edition deterministic NLP utilities v2
COURSE_NEWS_CLASSES = ["World", "Sports", "Business", "Sci/Tech"]
# This compact corpus was written for this edition. It keeps the notebooks
# deterministic, network-free, and quick enough to run on a learner's CPU.
COURSE_NEWS_TRAIN = [
(1, "Paris hosted a regional summit where diplomats discussed water security, peaceful trade routes, and a shared emergency plan for neighboring communities."),
(1, "The elected council approved a cross-border health agreement after delegates reviewed hospital capacity, medicine access, and transparent public reporting."),
(1, "A coastal city welcomed observers for a public vote, while local groups published clear guidance about polling places and accessible transportation."),
(1, "The king and queen met a woman who leads the relief agency and a man who coordinates volunteers, then the group announced a neutral humanitarian corridor."),
(1, "Ministers signed a climate adaptation pledge that funds drought monitoring, resilient farms, and open scientific exchange across several countries."),
(1, "Community mediators opened a week of talks focused on civilian safety, reliable food deliveries, and practical steps toward a lasting ceasefire."),
(2, "The basketball team completed a patient comeback in the final quarter, using quick passes, strong defense, and a balanced scoring plan to win the tournament."),
(2, "A young runner broke the course record after months of careful training, recovery sessions, nutrition planning, and support from her local athletics club."),
(2, "The football coach praised disciplined play after the squad protected an early lead and created chances through accurate movement on both wings."),
(2, "Fans filled the arena for a close volleyball final in which both teams served aggressively, defended long rallies, and respected every referee decision."),
(2, "The tennis champion returned after injury and won a demanding match by varying pace, placing serves carefully, and staying calm during two tie breaks."),
(2, "Organizers added inclusive swimming events to the city games so more athletes can compete with safe facilities, trained officials, and fair timing equipment."),
(3, "Microsoft announced a small-business program that combines cloud credits, security workshops, and practical accounting support for new local companies."),
(3, "Global funds moved cautiously after the central bank held interest rates steady and asked lenders to publish clearer information about consumer borrowing costs."),
(3, "A neighborhood market expanded its delivery service, hired twelve workers, and invested revenue in reusable packaging supplied by another local business."),
(3, "The manufacturer reported stable quarterly sales while noting that shipping delays and higher material prices could reduce margins later in the year."),
(3, "Two payment companies agreed to share fraud signals through a privacy-preserving system designed to protect customers without exposing purchase details."),
(3, "A cooperative offered farmers transparent contracts and faster invoices, helping members plan equipment repairs before the next growing season begins."),
(4, "Researchers trained a compact neural network to identify damaged solar panels, then published the evaluation data and documented where the model still fails."),
(4, "A university technology lab released an open sensor design that measures classroom air quality and stores only anonymous readings for public analysis."),
(4, "Engineers improved a battery recycling process by recovering more useful minerals at lower temperatures, reducing energy use during the pilot study."),
(4, "The space telescope captured a detailed spectrum from a distant planet, giving scientists new evidence about clouds and molecules in its atmosphere."),
(4, "A software team tested an accessibility assistant with keyboard users and screen-reader experts before changing the interface and publishing the results."),
(4, "Students built a small robot that maps indoor obstacles with inexpensive sensors, explains each route choice, and says hello when a test run begins."),
]
COURSE_NEWS_TEST = [
(1, "Regional delegates published a joint disaster response schedule after reviewing evacuation routes and communication gaps."),
(1, "Election monitors confirmed the final count and recommended clearer access rules for voters who need assistance."),
(2, "The basketball captain scored late, but credited the victory to defense, passing, and careful preparation across the whole season."),
(2, "A cycling club opened a safe youth race with trained marshals, marked turns, and free equipment checks."),
(3, "Technology shares rose while retail funds remained cautious after companies issued mixed forecasts for the next quarter."),
(3, "The family business secured a modest loan to replace old equipment and expand its apprenticeship program."),
(4, "A research team released a smaller language model with documented energy measurements and a public evaluation set."),
(4, "Scientists used open satellite data to improve flood warnings and shared the software with local emergency teams."),
]
def course_tokenize(text):
return re.findall(r"[a-z0-9]+(?:'[a-z0-9]+)?", str(text).lower())
def course_ngrams_iterator(tokens, ngrams=1):
tokens = list(tokens)
for token in tokens:
yield token
for size in range(2, max(1, ngrams) + 1):
for start in range(0, len(tokens) - size + 1):
yield " ".join(tokens[start:start + size])
class CourseVocabulary:
def __init__(self, counter, min_freq=1, max_tokens=None):
ordered = sorted(
(token for token, count in counter.items() if count >= min_freq),
key=lambda token: (-counter[token], token),
)
if max_tokens is not None:
ordered = ordered[:max(0, max_tokens - 1)]
self.itos = ["<unk>"] + [token for token in ordered if token != "<unk>"]
self.stoi = {token: index for index, token in enumerate(self.itos)}
def __len__(self):
return len(self.itos)
def __getitem__(self, token):
return self.stoi.get(token, 0)
def get_stoi(self):
return dict(self.stoi)
def get_itos(self):
return list(self.itos)
def build_course_vocab(dataset, ngrams=1, min_freq=1, max_tokens=None, tokenizer=course_tokenize):
counter = collections.Counter()
for _label, text in dataset:
counter.update(course_ngrams_iterator(tokenizer(text), ngrams=ngrams))
return CourseVocabulary(counter, min_freq=min_freq, max_tokens=max_tokens)
def load_course_fixture(ngrams=1, min_freq=1, vocab_size=None, lines_cnt=None):
global vocab, tokenizer
tokenizer = course_tokenize
train_dataset = list(COURSE_NEWS_TRAIN)
test_dataset = list(COURSE_NEWS_TEST)
vocabulary_rows = train_dataset if lines_cnt is None else train_dataset[:lines_cnt]
vocab = build_course_vocab(
vocabulary_rows,
ngrams=ngrams,
min_freq=min_freq,
max_tokens=vocab_size,
tokenizer=tokenizer,
)
return train_dataset, test_dataset, list(COURSE_NEWS_CLASSES), vocab
def encode(text, voc=None, unk=0, tokenizer=course_tokenize):
selected_vocab = vocab if voc is None else voc
stoi = selected_vocab.get_stoi()
return [stoi.get(token, unk) for token in tokenizer(text)]
def train_epoch(net, dataloader, lr=0.01, optimizer=None, loss_fn=None, epoch_size=None, report_freq=200):
optimizer = optimizer or torch.optim.Adam(net.parameters(), lr=lr)
loss_fn = (loss_fn or torch.nn.CrossEntropyLoss()).to(device)
net.train()
total_loss, accuracy, count, batch_index = 0.0, 0, 0, 0
for labels, features in dataloader:
optimizer.zero_grad()
features, labels = features.to(device), labels.to(device)
output = net(features)
loss = loss_fn(output, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
accuracy += (output.argmax(1) == labels).sum().item()
count += len(labels)
batch_index += 1
if batch_index % report_freq == 0:
print(f"{count}: acc={accuracy / count:.4f}")
if epoch_size and count > epoch_size:
break
return total_loss / max(count, 1), accuracy / max(count, 1)
def padify(batch, voc=None, tokenizer=course_tokenize):
vectors = [encode(item[1], voc=voc, tokenizer=tokenizer) for item in batch]
max_length = max(map(len, vectors))
return (
torch.LongTensor([item[0] - 1 for item in batch]),
torch.stack([
torch.nn.functional.pad(torch.tensor(vector), (0, max_length - len(vector)), value=0)
for vector in vectors
]),
)
def offsetify(batch, voc=None):
vectors = [torch.tensor(encode(item[1], voc=voc)) for item in batch]
offsets = torch.tensor(([0] + [len(vector) for vector in vectors])[:-1]).cumsum(dim=0)
return torch.LongTensor([item[0] - 1 for item in batch]), torch.cat(vectors), offsets
def train_epoch_emb(net, dataloader, lr=0.01, optimizer=None, loss_fn=None, epoch_size=None, report_freq=200, use_pack_sequence=False):
optimizer = optimizer or torch.optim.Adam(net.parameters(), lr=lr)
loss_fn = (loss_fn or torch.nn.CrossEntropyLoss()).to(device)
net.train()
total_loss, accuracy, count, batch_index = 0.0, 0, 0, 0
for labels, text, offsets in dataloader:
optimizer.zero_grad()
labels, text = labels.to(device), text.to(device)
offsets = offsets.to("cpu" if use_pack_sequence else device)
output = net(text, offsets)
loss = loss_fn(output, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
accuracy += (output.argmax(1) == labels).sum().item()
count += len(labels)
batch_index += 1
if batch_index % report_freq == 0:
print(f"{count}: acc={accuracy / count:.4f}")
if epoch_size and count > epoch_size:
break
return total_loss / max(count, 1), accuracy / max(count, 1)
random.seed(42)
np.random.seed(42)
torch.manual_seed(42)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
tokenizer = course_tokenize
vocab = Nonedevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")بالبداية خلونا نحمّل مجموعة البيانات ونعرّف أداة تجزئة النص والمفردات. بنضبط `vocab_size` على 5000 عشان نقلّل العمليات الحسابية شوي.
def load_cbow_dataset(ngrams=1, min_freq=1, vocab_size=5000, lines_cnt=500):
train_dataset, test_dataset, classes, vocabulary = load_course_fixture(
ngrams=ngrams,
min_freq=min_freq,
vocab_size=vocab_size,
lines_cnt=lines_cnt,
)
return train_dataset, test_dataset, classes, vocabulary, tokenizertrain_dataset, test_dataset, _, vocab, tokenizer = load_cbow_dataset()def encode(x, vocabulary, tokenizer=tokenizer):
return [vocabulary[token] for token in tokenizer(x)]## نموذج CBoW
يتعلّم نموذج CBoW يتنبأ بكلمة اعتمادًا على $2N$ من الكلمات المجاورة لها. مثلًا، إذا كان $N=1$، بنطلع بالأزواج التالية من الجملة *I like to train networks*: (like,I)، (I, like)، (to, like)، (like,to)، (train,to)، (to, train)، (networks, train)، (train,networks). الكلمة الأولى في كل زوج هي الكلمة المجاورة اللي تدخل للنموذج، والثانية هي الكلمة اللي نبي النموذج يتنبأ بها.
عشان نبني شبكة تتنبأ بالكلمة الهدف، نعطيها كلمة مجاورة مدخلًا ونأخذ رقم الكلمة المطلوبة مخرجًا. بنية شبكة CBoW كالتالي:
* تمر الكلمة المدخلة عبر طبقة التضمين. هالطبقة نفسها بتكون تضمين Word2Vec حقنا، ولذلك بنعرّفها لحالها في المتغير `embedder`. بنستخدم في هالمثال حجم تضمين = 30، وتقدرون تجرّبون أبعادًا أكبر (حجم Word2Vec الفعلي هو 300).
* بعدها يمر متجه التضمين إلى طبقة خطية تتنبأ بالكلمة الناتجة؛ لذلك يكون عدد عصبوناتها مساويًا لـ `vocab_size`.
أما المخرجات، فإذا استخدمنا `CrossEntropyLoss` بوصفها دالة الخسارة، يكفينا نعطي النموذج أرقام الكلمات بوصفها النتائج المتوقعة، من دون ترميز one-hot.
vocab_size = len(vocab)
embedder = torch.nn.Embedding(num_embeddings=vocab_size, embedding_dim=30)
model = torch.nn.Sequential(
embedder,
torch.nn.Linear(in_features=30, out_features=vocab_size),
).to(device)
print(model)## تجهيز بيانات التدريب
الحين بنكتب الدالة الأساسية اللي تحسب أزواج كلمات CBoW من النص. نقدر نحدد لها حجم النافذة، وترجع لنا أزواجًا من كلمة مدخلة وكلمة ناتجة. انتبهوا إن الدالة تشتغل على الكلمات وعلى المتجهات/الموترات بعد؛ وهالشي يخلينا نحوّل النص إلى أرقام قبل ما نمرره إلى الدالة `to_cbow`.
def to_cbow(sent,window_size=2):
res = []
for i,x in enumerate(sent):
for j in range(max(0,i-window_size),min(i+window_size+1,len(sent))):
if i!=j:
res.append([sent[j],x])
return res
print(to_cbow(['I','like','to','train','networks']))
print(to_cbow(encode('I like to train networks', vocab)))خلونا نجهّز مجموعة البيانات الخاصة بالتدريب. بنمر على الأخبار كلها، ونستدعي `to_cbow` عشان نحصل على أزواج الكلمات، ثم نضيفها إلى `X` و`Y`. اختصارًا للوقت، بناخذ أول 10k خبر بس. إذا عندكم وقت أطول وتبغون تضمينات أفضل، احذفوا هالقيد بكل سهولة :)
X = []
Y = []
for i, x in zip(range(10000), train_dataset):
for w1, w2 in to_cbow(encode(x[1], vocab), window_size = 5):
X.append(w1)
Y.append(w2)
X = torch.tensor(X)
Y = torch.tensor(Y)وبنحوّل هالبيانات كلها إلى **مجموعة البيانات** نفسها، ثم ننشئ محمّل بيانات:
class SimpleIterableDataset(torch.utils.data.IterableDataset):
def __init__(self, X, Y):
super().__init__()
self.data = [(Y[index], X[index]) for index in range(len(X))]
random.Random(42).shuffle(self.data)
def __iter__(self):
return iter(self.data)وبنحوّل هالبيانات كلها إلى **مجموعة البيانات** نفسها، ثم ننشئ محمّل بيانات:
ds = SimpleIterableDataset(X, Y)
dl = torch.utils.data.DataLoader(ds, batch_size = 256)الحين نبدأ التدريب الفعلي. بنستخدم المحسّن `SGD` بمعدل تعلّم مرتفع نسبيًا، وتقدرون تجرّبون محسّنات ثانية مثل `Adam`. بنبدأ بالتدريب مدة 10 حقبات، وإذا تبغون قيمة خسارة أقل أعيدوا تشغيل هالخلية.
def train_epoch(net, dataloader, lr = 0.01, optimizer = None, loss_fn = torch.nn.CrossEntropyLoss(), epochs = None, report_freq = 1):
optimizer = optimizer or torch.optim.Adam(net.parameters(), lr = lr)
loss_fn = loss_fn.to(device)
net.train()
for i in range(epochs):
total_loss, j = 0, 0,
for labels, features in dataloader:
optimizer.zero_grad()
features, labels = features.to(device), labels.to(device)
out = net(features)
loss = loss_fn(out, labels)
loss.backward()
optimizer.step()
total_loss += loss
j += 1
if i % report_freq == 0:
print(f"Epoch: {i+1}: loss={total_loss.item()/j}")
return total_loss.item()/jtrain_epoch(net = model, dataloader = dl, optimizer = torch.optim.SGD(model.parameters(), lr = 0.1), loss_fn = torch.nn.CrossEntropyLoss(), epochs = 10)## تجربة Word2Vec
عشان نستخدم Word2Vec، خلونا نستخرج المتجهات المقابلة لكل الكلمات في مفرداتنا:
with torch.no_grad():
vectors = torch.stack([
embedder(torch.tensor(vocab[token], device=device)).cpu()
for token in vocab.itos
], 0)مثلًا، خلونا نشوف كيف تُمثَّل كلمة **Paris** بمتجه:
with torch.no_grad():
paris_vec = embedder(torch.tensor(vocab["paris"], device=device)).cpu()
print(paris_vec)من التطبيقات الممتعة لـ Word2Vec البحث عن المرادفات. الدالة الجاية ترجع `n` من أقرب الكلمات إلى الكلمة المدخلة. عشان نلقاها، نحسب معيار $|w_i - v|$، بحيث إن $v$ هو متجه الكلمة المدخلة و$w_i$ هو تضمين الكلمة رقم $i$ في المفردات. بعدها نرتّب المصفوفة ونرجع الفهارس المقابلة باستخدام `argsort`، ثم نأخذ أول `n` عناصر؛ وهي مواقع أقرب الكلمات في المفردات.
def close_words(word, n=5):
with torch.no_grad():
vector = embedder(torch.tensor(vocab[word], device=device)).cpu()
nearest = np.linalg.norm(vectors.numpy() - vector.numpy(), axis=1).argsort()[:n]
return [vocab.itos[index] for index in nearest]
close_words("microsoft")close_words('basketball')close_words('funds')## الزبدة
بتقنيات ذكية مثل CBoW نقدر ندرّب نموذج Word2Vec. جرّبوا بعد تدرّبون نموذج skip-gram يتعلّم يتنبأ بالكلمة المجاورة انطلاقًا من الكلمة اللي بالنص، وقارنوا أداءه.
حذفنا المخرجات وعدّادات التشغيل والودجات والمحتوى النشط وقت الاستيراد. شغّل الدفاتر بس في بيئة خارجية تثق فيها.
سجّل تطبيقك
التسجيل اختياري، يفيدك تتذكر وش طبّقت، ولا يمنع إكمال الدورة.