معاينة مختبر آمنة
Intro Keras TF
هذي معاينة منقّحة للقراءة فقط؛ ما فيه أي شيء يشتغل داخل الصفحة.
قراءة فقط
معاينة الدفتر
Intro Keras TF
## مقدمة إلى TensorFlow وKeras
> هالدفتر جزء من منهج الذكاء الاصطناعي للمبتدئين. زوروا صفحة المنهج عشان تلقون مواد التعلّم كاملة.
### أطر الشبكات العصبية
تعلمنا إن تدريب الشبكات العصبية يحتاج منّا:
* نضرب المصفوفات (الموترات) بسرعة
* نحسب التدرجات عشان ننفّذ تحسين الانحدار المتدرج
وش تخلّيكم أطر الشبكات العصبية تسوون:
* تشغّلون عمليات الموترات على وحدة الحوسبة المتوفرة، سواءً CPU أو GPU أو حتى TPU
* تحسبون التدرجات آليًا؛ لأن مشتقات كل دوال الموترات المدمجة مبرمجة داخل الإطار
وتعطيكم—اختياريًا:
* أداة لبناء الشبكة العصبية أو API عالية المستوى تصف الشبكة كسلسلة طبقات
* دوال تدريب بسيطة مثل `fit` في Scikit Learn
* عدة خوارزميات تحسين غير الانحدار المتدرج
* تجريدات للتعامل مع البيانات، والمفروض إنها تشتغل على GPU بعد
### مسارا الإطار في هالدورة
تستخدم هالدورة TensorFlow مع Keras 3 في مسار، وPyTorch في المسار الثاني. TensorFlow ينفذ بحماس افتراضيًا ويدعم رسومًا محسّنة عند الحاجة؛ وKeras يقدم واجهة نماذج عالية المستوى متعددة الخلفيات. الاعتماديات مثبتة في ملف بيئة الدفتر.
import tensorflow as tf
import numpy as np
print(tf.__version__)## المفهوم الأساسي: الموتر
**الموتر** مصفوفة متعددة الأبعاد، ويفيدنا بتمثيل أنواع مختلفة من البيانات:
* 400x400 - صورة بالأبيض والأسود
* 400x400x3 - صورة ملوّنة
* 16x400x400x3 - دفعة مصغّرة من 16 صورة ملوّنة
* 25x400x400x3 - ثانية وحدة من فيديو بمعدل 25 إطارًا بالثانية
* 8x25x400x400x3 - دفعة مصغّرة من 8 مقاطع، مدة كل واحد منها 1 ثانية
### موترات بسيطة
نقدر ننشئ موترات بسيطة بسهولة من قوائم مصفوفات np، أو نولّد موترات عشوائية:
a = tf.constant([[1,2],[3,4]])
print(a)
a = tf.random.normal(shape=(10,3))
print(a)نقدر نستخدم العمليات الحسابية على الموترات، وتتنفّذ عنصرًا بعنصر مثل numpy. وإذا احتجنا بُعدًا إضافيًا، تتوسع الموترات آليًا إلى الأبعاد المطلوبة. ولاستخراج مصفوفة numpy من موتر، نستخدم `.numpy`:
print(a-a[0])
print(tf.exp(a)[0].numpy())## المتغيرات
تفيدنا المتغيرات في تمثيل قيم الموتر اللي نقدر نعدّلها باستخدام `assign` و`assign_add`. وغالبًا نستخدمها لتمثيل أوزان الشبكة العصبية.
كمثال بسيط جدًا، هذي طريقة تجمع كل صفوف الموتر `a`:
s = tf.Variable(tf.zeros_like(a[0]))
for i in a:
s.assign_add(i)
print(s)وهذي طريقة أفضل بكثير:
tf.reduce_sum(a,axis=0)## حساب التدرجات
عشان ننفّذ الانتشار العكسي نحتاج نحسب التدرجات. في TensorFlow نسوي هذا بنمط `tf.GradientTape`:
* نحط كتلة `with tf.GradientTape` حول الحسابات
* نعلّم الموترات اللي نبي نحسب التدرج بالنسبة لها باستدعاء `tape.watch`، مع ملاحظة إن كل المتغيرات تُراقب آليًا
* ننفّذ الحسابات المطلوبة ونبني رسم الحسابات
* نطلع التدرجات باستخدام `tape.gradient`
a = tf.random.normal(shape=(2, 2))
b = tf.random.normal(shape=(2, 2))
with tf.GradientTape() as tape:
tape.watch(a) # Start recording the history of operations applied to `a`
c = tf.sqrt(tf.square(a) + tf.square(b)) # Do some math using `a`
# What's the gradient of `c` with respect to `a`?
dc_da = tape.gradient(c, a)
print(dc_da)## المثال 1: الانحدار الخطي
الحين صار عندنا أساس كافي لحل مسألة **الانحدار الخطي** المعروفة. خلونا نولّد مجموعة البيانات الاصطناعية الصغيرة هذي:
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification, make_regression
from sklearn.model_selection import train_test_split
import randomnp.random.seed(13) # pick the seed for reproducability - change it to explore the effects of random variations
train_x = np.linspace(0, 3, 120)
train_labels = 2 * train_x + 0.9 + np.random.randn(*train_x.shape) * 0.5
plt.scatter(train_x,train_labels)نعرّف الانحدار الخطي بخط مستقيم $f_{W,b}(x) = Wx+b$، حيث $W, b$ معاملات النموذج اللي نحتاج نلقاها. ونقدر نعرّف الخطأ على مجموعة البيانات $\{x_i,y_u\}_{i=1}^N$—ويسمّى بعد **دالة الخسارة**—بمتوسط مربع الخطأ:
$$
\mathcal{L}(W,b) = {1\over N}\sum_{i=1}^N (f_{W,b}(x_i)-y_i)^2
$$
خلونا نعرّف النموذج ودالة الخسارة:
input_dim = 1
output_dim = 1
learning_rate = 0.1
# This is our weight matrix
w = tf.Variable([[100.0]])
# This is our bias vector
b = tf.Variable(tf.zeros(shape=(output_dim,)))
def f(x):
return tf.matmul(x,w) + b
def compute_loss(labels, predictions):
return tf.reduce_mean(tf.square(labels - predictions))بندرّب النموذج على سلسلة من الدفعات المصغّرة. وبنستخدم الانحدار المتدرج ونعدّل معاملات النموذج بالمعادلات هذي:
$$
\begin{array}{l}
W^{(n+1)}=W^{(n)}-\eta\frac{\partial\mathcal{L}}{\partial W} \\
b^{(n+1)}=b^{(n)}-\eta\frac{\partial\mathcal{L}}{\partial b} \\
\end{array}
$$
def train_on_batch(x, y):
with tf.GradientTape() as tape:
predictions = f(x)
loss = compute_loss(y, predictions)
# Note that `tape.gradient` works with a list as well (w, b).
dloss_dw, dloss_db = tape.gradient(loss, [w, b])
w.assign_sub(learning_rate * dloss_dw)
b.assign_sub(learning_rate * dloss_db)
return lossخلونا نبدأ التدريب. بنمر على مجموعة البيانات عدة مرات—وكل مرة نسمّيها **حقبة**—ونقسّمها إلى دفعات مصغّرة، ثم نستدعي الدالة اللي عرّفناها فوق:
# Shuffle the data.
indices = np.random.permutation(len(train_x))
features = tf.constant(train_x[indices],dtype=tf.float32)
labels = tf.constant(train_labels[indices],dtype=tf.float32)batch_size = 4
for epoch in range(10):
for i in range(0,len(features),batch_size):
loss = train_on_batch(tf.reshape(features[i:i+batch_size],(-1,1)),tf.reshape(labels[i:i+batch_size],(-1,1)))
print('Epoch %d: last batch loss = %.4f' % (epoch, float(loss)))الحين حصلنا على القيم المحسّنة للمعاملين $W$ و$b$. لاحظوا إن قيمهم قريبة من القيم الأصلية اللي استخدمناها لما ولّدنا مجموعة البيانات ($W=2, b=1$)
w,bplt.scatter(train_x,train_labels)
x = np.array([min(train_x),max(train_x)])
y = w.numpy()[0,0]*x+b.numpy()[0]
plt.plot(x,y,color='red')## رسم الحسابات والتنفيذ على GPU
كل ما حسبنا تعبيرًا على الموترات، يبني TensorFlow رسم حسابات يقدر ينفّذه على وحدة الحوسبة المتوفرة، مثل CPU أو GPU. وبما إننا استخدمنا دالة Python عادية داخل الكود، ما يقدر TensorFlow يضمّها إلى رسم الحسابات. لذلك، لو شغّلنا الكود على GPU بنضطر ننقل البيانات بين CPU وGPU ذهابًا وإيابًا، وننفّذ الدالة المخصصة على CPU.
يخلّينا TensorFlow نعلّم دالة Python بالمزيّن `@tf.function` عشان تصير جزءًا من رسم الحسابات نفسه. ونقدر نطبّق هالمزيّن على الدوال اللي تستخدم عمليات موترات TensorFlow القياسية.
@tf.function
def train_on_batch(x, y):
with tf.GradientTape() as tape:
predictions = f(x)
loss = compute_loss(y, predictions)
# Note that `tape.gradient` works with a list as well (w, b).
dloss_dw, dloss_db = tape.gradient(loss, [w, b])
w.assign_sub(learning_rate * dloss_dw)
b.assign_sub(learning_rate * dloss_db)
return lossالكود ما تغير، لكن لو شغلتوه على GPU ومع مجموعة بيانات أكبر، بتلاحظون فرق السرعة.
## واجهة مجموعات البيانات (Dataset API)
في TensorFlow واجهة API مريحة للتعامل مع البيانات. خلونا نجرّبها، وبنعيد تدريب النموذج من الصفر بعد.
w.assign([[10.0]])
b.assign([0.0])
# Create a tf.data.Dataset object for easy batched iteration
dataset = tf.data.Dataset.from_tensor_slices((train_x.astype(np.float32), train_labels.astype(np.float32)))
dataset = dataset.shuffle(buffer_size=1024).batch(256)
for epoch in range(10):
for step, (x, y) in enumerate(dataset):
loss = train_on_batch(tf.reshape(x,(-1,1)), tf.reshape(y,(-1,1)))
print('Epoch %d: last batch loss = %.4f' % (epoch, float(loss)))## المثال 2: التصنيف
الحين بناخذ مسألة تصنيف ثنائي. مثال مناسب عليها: التصنيف بين ورم خبيث وورم حميد بناءً على حجمه وعمره.
النموذج الأساسي قريب من نموذج الانحدار، لكننا نحتاج دالة الخسارة تكون مختلفة. نبدأ بتوليد بيانات تجريبية:
np.random.seed(0) # pick the seed for reproducibility - change it to explore the effects of random variations
n = 100
X, Y = make_classification(n_samples = n, n_features=2,
n_redundant=0, n_informative=2, flip_y=0.05,class_sep=1.5)
X = X.astype(np.float32)
Y = Y.astype(np.int32)
split = [ 70*n//100, (15+70)*n//100 ]
train_x, valid_x, test_x = np.split(X, split)
train_labels, valid_labels, test_labels = np.split(Y, split)def plot_dataset(features, labels, W=None, b=None):
# prepare the plot
fig, ax = plt.subplots(1, 1)
ax.set_xlabel('$x_i[0]$ -- (feature 1)')
ax.set_ylabel('$x_i[1]$ -- (feature 2)')
colors = ['r' if l else 'b' for l in labels]
ax.scatter(features[:, 0], features[:, 1], marker='o', c=colors, s=100, alpha = 0.5)
if W is not None:
min_x = min(features[:,0])
max_x = max(features[:,1])
min_y = min(features[:,1])*(1-.1)
max_y = max(features[:,1])*(1+.1)
cx = np.array([min_x,max_x],dtype=np.float32)
cy = (0.5-W[0]*cx-b)/W[1]
ax.plot(cx,cy,'g')
ax.set_ylim(min_y,max_y)
fig.show()plot_dataset(train_x, train_labels)## تطبيع البيانات
قبل التدريب، المعتاد إننا نجيب سمات الإدخال إلى المجال القياسي [0,1] (أو [-1,1]). بنشرح السبب بالتفصيل لاحقًا في الدورة، لكن الفكرة باختصار إننا ما نبي القيم اللي تمر داخل الشبكة تكبر أو تصغر زيادة. عادةً نخلي القيم في مجال صغير قريب من 0؛ لذلك نهيّئ الأوزان بأرقام عشوائية صغيرة ونحافظ على الإشارات في المجال نفسه.
لما نطبّع البيانات، نطرح أصغر قيمة ثم نقسم على المدى. نحسب أصغر قيمة والمدى من بيانات التدريب، وبعدها نطبّع مجموعة البيانات المخصصة للاختبار أو بيانات التحقق باستخدام القيم نفسها المأخوذة من مجموعة التدريب. السبب إننا في الاستخدام الحقيقي نعرف مجموعة التدريب فقط، وما نعرف مسبقًا كل قيمة جديدة بتطلبون من الشبكة تتنبأ بها. أحيانًا تطلع قيمة جديدة برا المجال [0,1]، وهذا مب مشكلة جوهرية.
train_x_norm = (train_x-np.min(train_x)) / (np.max(train_x)-np.min(train_x))
valid_x_norm = (valid_x-np.min(train_x)) / (np.max(train_x)-np.min(train_x))
test_x_norm = (test_x-np.min(train_x)) / (np.max(train_x)-np.min(train_x))## تدريب البيرسيبترون (Perceptron) بطبقة واحدة
خلونا نستخدم آلية TensorFlow لحساب التدرجات وندرّب البيرسيبترون بطبقة واحدة.
بيكون تركيب الشبكة العصبية من مدخلين 2 ومخرج واحد 1. حجم مصفوفة الأوزان $W$ بيكون $2\times1$، وحجم متجه التحيز $b$ بيكون $1$.
النموذج الأساسي هو نفسه في المثال السابق، لكن دالة الخسارة هنا خسارة لوجستية. عشان نطبّقها، نحتاج ناتج الشبكة يكون قيمة **احتمال**؛ يعني نجيب الناتج $z$ إلى المجال [0,1] باستخدام دالة التنشيط `sigmoid`: $p=\sigma(z)$.
إذا طلع لنا الاحتمال $p_i$ لقيمة الإدخال رقم i، وكانت فئتها الفعلية $y_i\in\{0,1\}$، نحسب الخسارة كالتالي: $\mathcal{L_i}=-(y_i\log p_i + (1-y_i)log(1-p_i))$.
في TensorFlow نقدر ننفّذ الخطوتين—تطبيق sigmoid ثم حساب الخسارة اللوجستية—باستدعاء واحد للدالة `sigmoid_cross_entropy_with_logits`. وبما إننا ندرّب الشبكة بدفعات مصغّرة، نحتاج نحسب متوسط الخسارة لكل عناصر الدفعة باستخدام `reduce_mean`:
W = tf.Variable(tf.random.normal(shape=(2,1)),dtype=tf.float32)
b = tf.Variable(tf.zeros(shape=(1,),dtype=tf.float32))
learning_rate = 0.1
@tf.function
def train_on_batch(x, y):
with tf.GradientTape() as tape:
z = tf.matmul(x, W) + b
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=y,logits=z))
dloss_dw, dloss_db = tape.gradient(loss, [W, b])
W.assign_sub(learning_rate * dloss_dw)
b.assign_sub(learning_rate * dloss_db)
return lossبنستخدم دفعات مصغّرة حجمها 16 عنصرًا، وندرّب لعدة حقب:
# Create a tf.data.Dataset object for easy batched iteration
dataset = tf.data.Dataset.from_tensor_slices((train_x_norm.astype(np.float32), train_labels.astype(np.float32)))
dataset = dataset.shuffle(128).batch(2)
for epoch in range(10):
for step, (x, y) in enumerate(dataset):
loss = train_on_batch(x, tf.expand_dims(y,1))
print('Epoch %d: last batch loss = %.4f' % (epoch, float(loss)))عشان نتأكد إن التدريب اشتغل، خلونا نرسم الخط اللي يفصل بين الفئتين. معادلة خط الفصل هي $W\times x + b = 0.5$
plot_dataset(train_x,train_labels,W.numpy(),b.numpy())خلونا نشوف أداء النموذج على بيانات التحقق.
pred = tf.matmul(test_x,W)+b
fig,ax = plt.subplots(1,2)
ax[0].scatter(test_x[:,0],test_x[:,1],c=pred[:,0]>0.5)
ax[1].scatter(test_x[:,0],test_x[:,1],c=valid_labels)عشان نحسب الدقة على بيانات التحقق، نحوّل النوع المنطقي إلى عدد عشري ونحسب المتوسط:
tf.reduce_mean(tf.cast(((pred[0]>0.5)==test_labels),tf.float32))خلونا نفصّل وش يصير هنا:
* `pred` هي القيم اللي تنبأت بها الشبكة. القيم مب احتمالات كاملة لأننا ما استخدمنا دالة التنشيط، لكن القيمة الأكبر من 0.5 تقابل الفئة 1، والأصغر تقابل الفئة 0.
* التعبير `pred[0]>0.5` ينشئ موترًا منطقيًا للنتائج؛ `True` تقابل الفئة 1 و`False` تقابل الفئة 0
* نقارن هالموتر بالتسميات المتوقعة `valid_labels`، فنحصل على متجه منطقي للتنبؤات؛ `True` يعني إن التنبؤ صحيح و`False` يعني إنه خطأ
* نحوّل الموتر إلى أعداد عشرية باستخدام `tf.cast`
* بعدها نحسب المتوسط باستخدام `tf.reduce_mean`، وهذي بالضبط قيمة الدقة اللي نبيها
## استخدام محسّنات TensorFlow/Keras
TensorFlow متكامل بشكل وثيق مع Keras، وفي Keras أدوات كثيرة مفيدة. مثلًا، نقدر نستخدم **خوارزميات تحسين** مختلفة بدل ما نكتفي بالانحدار المتدرج. خلونا نسوي هذا ونطبع الدقة اللي نحصّل عليها أثناء التدريب.
optimizer = tf.keras.optimizers.Adam(0.01)
W = tf.Variable(tf.random.normal(shape=(2,1)))
b = tf.Variable(tf.zeros(shape=(1,),dtype=tf.float32))
@tf.function
def train_on_batch(x, y):
vars = [W, b]
with tf.GradientTape() as tape:
z = tf.sigmoid(tf.matmul(x, W) + b)
loss = tf.reduce_mean(tf.keras.losses.binary_crossentropy(z,y))
correct_prediction = tf.equal(tf.round(y), tf.round(z))
acc = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
grads = tape.gradient(loss, vars)
optimizer.apply_gradients(zip(grads,vars))
return loss,acc
for epoch in range(20):
for step, (x, y) in enumerate(dataset):
loss,acc = train_on_batch(tf.reshape(x,(-1,2)), tf.reshape(y,(-1,1)))
print('Epoch %d: last batch loss = %.4f, acc = %.4f' % (epoch, float(loss),acc))**المهمة 1**: ارسموا منحنيات دالة الخسارة والدقة على بيانات التدريب وبيانات التحقق أثناء التدريب
**المهمة 2**: جرّبوا تحلون مسألة تصنيف MNIST باستخدام هالكود. تلميح: استخدموا `softmax_crossentropy_with_logits` أو `sparse_softmax_cross_entropy_with_logits` كدالة الخسارة. في الحالة الأولى لازم تعطون قيم الخرج المتوقعة بترميز *one hot*، وفي الحالة الثانية تعطونها كرقم فئة صحيح.
## Keras 3
توفر Keras واجهة عالية المستوى للطبقات والنماذج والمحسّنات. تشتغل مع أكثر من خلفية، ومع خلفية TensorFlow تندمج مع tf.data وGradientTape وأدوات النشر. استخدموا Sequential للنماذج الخطية البسيطة، والواجهة الوظيفية للتفرعات والوصلات، وتخصيص Model للسلوك المخصص.
inputs = tf.keras.Input(shape=(2,))
z = tf.keras.layers.Dense(1,kernel_initializer='glorot_uniform',activation='sigmoid')(inputs)
model = tf.keras.models.Model(inputs,z)
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
loss="binary_crossentropy",
metrics=["accuracy"],
)
model.summary()
h = model.fit(train_x_norm,train_labels,batch_size=8,epochs=15)plt.plot(h.history['accuracy'])### API التسلسلية
وبدل هالطريقة، نقدر نفكر في النموذج على إنه **سلسلة طبقات**، ونحدد الطبقات بإضافتها إلى كائن `model`:
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(5,activation='sigmoid',input_shape=(2,)))
model.add(tf.keras.layers.Dense(1,activation='sigmoid'))
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
loss="binary_crossentropy",
metrics=["accuracy"],
)
model.summary()
model.fit(train_x_norm,train_labels,validation_data=(test_x_norm,test_labels),batch_size=8,epochs=15)## دوال الخسارة في التصنيف
مهم نحدد دالة الخسارة ودالة التنشيط في آخر طبقة من الشبكة بشكل صحيح. القواعد الأساسية هي:
* إذا كان للشبكة مخرج واحد (**تصنيف ثنائي**) نستخدم دالة التنشيط **sigmoid**، أما في **التصنيف متعدد الفئات** فنستخدم **softmax**
* إذا مثّلنا فئة الخرج بترميز one-hot، تكون دالة الخسارة **خسارة الانتروبيا المتقاطعة** (categorical cross-entropy). وإذا كان الخرج رقم الفئة، نستخدم **الانتروبيا المتقاطعة الفئوية المتناثرة**. وفي **التصنيف الثنائي** نستخدم **الانتروبيا المتقاطعة الثنائية**، وهي نفسها **log loss**
* **التصنيف متعدد التسميات** يعني إن العنصر قد ينتمي لعدة فئات بنفس الوقت. هنا نرمّز التسميات بترميز one-hot ونستخدم **sigmoid** كدالة التنشيط، عشان يكون احتمال كل فئة بين 0 و1
| نوع التصنيف | صيغة التسمية | دالة التنشيط | الخسارة |
|---------------|-----------------------|-----------------|----------|
| ثنائي | احتمال الفئة 1 | sigmoid | binary crossentropy |
| ثنائي | ترميز one-hot (مخرجان 2) | softmax | categorical crossentropy |
| متعدد الفئات | ترميز one-hot | softmax | categorical crossentropy |
| متعدد الفئات | رقم الفئة | softmax | sparse categorical crossentropy |
| متعدد التسميات | ترميز one-hot | sigmoid | categorical crossentropy |
> نقدر نتعامل مع التصنيف الثنائي كحالة خاصة من التصنيف متعدد الفئات بمخرجين. وفي هالحالة نستخدم **softmax**.
**المهمة 3**:
استخدموا Keras لتدريب مصنّف MNIST:
* انتبهوا إن Keras فيها مجموعات بيانات قياسية، ومنها MNIST. عشان تستخدمون MNIST من Keras ما تحتاجون إلا سطرين تقريبًا من الكود—والتفاصيل [هنا](https://www.tensorflow.org/api_docs/python/tf/keras/datasets/mnist)
* جرّبوا أكثر من إعداد للشبكة، مع تغيير عدد الطبقات والعصبونات ودوال التنشيط
وش أفضل دقة قدرتوا توصلون لها؟
## الخلاصة
* TensorFlow يوفر عمليات الموترات والتفاضل التلقائي وtf.data وtf.keras.layers.
* Keras 3 يبسط بناء النماذج وتدريبها، ويظل يسمح بطبقات وحلقات تدريب مخصصة.
* اختاروا الإطار على أساس البيانات والتشغيل والفريق.
حذفنا المخرجات وعدّادات التشغيل والودجات والمحتوى النشط وقت الاستيراد. شغّل الدفاتر بس في بيئة خارجية تثق فيها.
سجّل تطبيقك
التسجيل اختياري، يفيدك تتذكر وش طبّقت، ولا يمنع إكمال الدورة.