تقنية وتكنولوجيا

فهم الموترات: تعلم بنية البيانات من خلال 3 أخطاء مزعجة | بواسطة إيفا ريفير


لقد قمت مؤخرًا بتعديل نماذج التعلم العميق في Tensorflow، وبالتالي تعرفت على إدارة البيانات كموترات.

باعتباري مهندس بيانات يعمل طوال اليوم في جداول يمكنني بسهولة تقطيعها وتقطيعها وتصورها، لم يكن لدي أي حدس على الإطلاق حول العمل مع الموترات، ويبدو أنني أواجه باستمرار نفس الأخطاء التي حدثت، خاصة في البداية، رأسي.

ومع ذلك، فقد علمني الغوص العميق فيها الكثير عن Tensors وTensorFlow، وأردت دمج هذه الدروس هنا لاستخدامها كمرجع.

إذا كان لديك خطأ أو حل أو نصيحة لتصحيح الأخطاء مفضلة، فيرجى ترك تعليق!

قبل أن نتعمق في الأخطاء نفسها، أردت توثيق بعض الأجزاء البسيطة وخفيفة الوزن من التعليمات البرمجية التي وجدتها مفيدة في تصحيح الأخطاء. (على الرغم من أنه يجب الإشارة لأسباب قانونية إلى أننا بالطبع نقوم دائمًا بتصحيح الأخطاء باستخدام ميزات تصحيح الأخطاء الرسمية وليس فقط عشرات البيانات المطبوعة 🙂)

رؤية ما بداخل مجموعات بيانات Tensorflow

أولاً، النظر إلى بياناتنا الفعلية. عندما نطبع Dataframe أو SELECT * في SQL، نرى البيانات! عندما نطبع مجموعة بيانات موتر نرى …

<_TensorSliceDataset element_spec=(TensorSpec(shape=(2, 3), dtype=tf.int32, name=None), TensorSpec(shape=(1, 1), dtype=tf.int32, name=None))>

هذه كلها معلومات مفيدة للغاية، لكنها لا تساعدنا على فهم ما يحدث بالفعل في بياناتنا.

لطباعة موتر واحد ضمن الرسم البياني للتنفيذ يمكننا الاستفادة من tf.print. هذه المقالة عبارة عن نظرة عميقة ورائعة إلى tf.print والتي أوصي بها بشدة إذا كنت تخطط لاستخدامها كثيرًا: استخدام tf.Print() في TensorFlow

ولكن عند العمل مع مجموعات بيانات Tensorflow أثناء التطوير، نحتاج أحيانًا إلى رؤية بعض القيم في كل مرة. لذلك يمكننا تكرار وطباعة أجزاء فردية من البيانات مثل هذا:


# Generate dummy 2D data
np.random.seed(42)
num_samples = 100
num_features = 5
X_data = np.random.rand(num_samples, num_features).astype(np.float32)
y_data = 2 * X_data[:, 0] + 3 * X_data[:, 1] - 1.5 * X_data[:, 2] + 0.5 * X_data[:, 3] + np.random.randn(num_samples)

# Turn it into a Tensorflow Dataset
dataset = tf.data.Dataset.from_tensor_slices((X_data, y_data))

# Print the first 10 rows
for i, (features, label) in enumerate(dataset.take(10)):
print(f"Row {i + 1}: Features - {features.numpy()}, Label - {label.numpy()}")

يمكننا أيضًا استخدام تخطي للوصول إلى فهرس محدد:

mini_dataset = dataset.skip(100).take(20)
for i, (features, label) in enumerate(mini_dataset):
print(f"Row {i + 1}: Features - {features.numpy()}, Label - {label.numpy()}")

معرفة مواصفات الموترات لدينا

عند العمل مع الموترات نحتاج أيضًا إلى معرفة شكلها ورتبتها وأبعادها ونوع بياناتها (إذا كانت بعض هذه المفردات غير مألوفة، كما كانت بالنسبة لي في البداية، فلا تقلق، سنعود إليها لاحقًا في شرط). على أية حال، فيما يلي بضعة أسطر من التعليمات البرمجية لجمع هذه المعلومات:


# Create a sample tensor
sample_tensor = tf.constant([[1, 2, 3], [4, 5, 6]])

# Get the size of the tensor (total number of elements)
tensor_size = tf.size(sample_tensor).numpy()

# Get the rank of the tensor
tensor_rank = tf.rank(sample_tensor).numpy()

# Get the shape of the tensor
tensor_shape = sample_tensor.shape

# Get the dimensions of the tensor
tensor_dimensions = sample_tensor.shape.as_list()
# Print the results
print("Tensor Size:", tensor_size)
print("Tensor Rank:", tensor_rank)
print("Tensor Shape:", tensor_shape)
print("Tensor Dimensions:", tensor_dimensions)

المخرجات المذكورة أعلاه:

Tensor Size: 6
Tensor Rank: 2
Tensor Shape: (2, 3)
Tensor Dimensions: [2, 3]

زيادة النموذج.الملخص()

أخيرًا، من المفيد دائمًا أن تكون قادرًا على رؤية كيفية تحرك البيانات عبر النموذج، وكيف يتغير الشكل عبر المدخلات والمخرجات بين الطبقات. سيكون مصدر العديد من الأخطاء هو عدم التطابق بين أشكال المدخلات والمخرجات المتوقعة وشكل موتر معين.

من المؤكد أن model.summary() ينجز المهمة، لكن يمكننا استكمال تلك المعلومات بالمقتطف التالي، الذي يضيف سياقًا أكثر قليلًا مع مدخلات ومخرجات النموذج والطبقة:

print("###################Input Shape and Datatype#####################")
[print(i.shape, i.dtype) for i in model.inputs]
print("###################Output Shape and Datatype#####################")
[print(o.shape, o.dtype) for o in model.outputs]
print("###################Layer Input Shape and Datatype#####################")
[print(l.name, l.input, l.dtype) for l in model.layers]

لذلك دعونا نقفز إلى بعض الأخطاء!

رتبة

خطأ القيمة: يجب أن يكون الشكل في المرتبة x ولكنه في المرتبة y….

طيب، أولاً ما هي الرتبة؟ الرتبة هي مجرد وحدة الأبعاد التي نستخدمها لوصف الموترات. الموتر من الرتبة 0 هو قيمة عددية؛ الموتر من الرتبة الأولى هو ناقل؛ المرتبة الثانية هي مصفوفة، وهكذا بالنسبة لجميع الهياكل ذات الأبعاد n.

خذ على سبيل المثال موتر 5 الأبعاد.

rank_5_tensor = tf.constant([[[[[1, 2], [3, 4]], [[5, 6], [7, 8]]], [[[9, 10], [11, 12]], [[13, 14], [15, 16]]]],
[[[[17, 18], [19, 20]], [[21, 22], [23, 24]]], [[[25, 26], [27, 28]], [[29, 30], [31, 32]]]]])
print("\nRank 5 Tensor:", rank_5_tensor.shape)
Rank 5 Tensor: (2, 2, 2, 2, 2)

يوضح الكود أعلاه أن كل بُعد من الأبعاد الخمسة له حجم اثنين. وإذا أردنا فهرسته فيمكننا القيام بذلك على أي من هذه المحاور. للوصول إلى العنصر الأخير، 32، سنجري شيئًا مثل:

rank_5_tensor.numpy()[1][1][1][1][1]

تحتوي وثائق الموتر الرسمية على بعض التصورات المفيدة حقًا لجعل هذا الأمر أكثر قابلية للفهم.

العودة إلى الخطأ: إنه مجرد إشارة إلى أن الموتر المقدم له بُعد مختلف عما هو متوقع لوظيفة معينة. على سبيل المثال، إذا أعلن الخطأ أن “الشكل يجب أن يكون من المرتبة 1 ولكنه من المرتبة 0…” فهذا يعني أننا نقدم قيمة عددية، ويتوقع موترًا أحادي الأبعاد.

خذ المثال أدناه حيث نحاول ضرب الموترات مع طريقة الماتمول.

import tensorflow as tf
import numpy as np
# Create a TensorFlow dataset with random matrices
num_samples = 5
matrix_size = 3
dataset = tf.data.Dataset.from_tensor_slices(np.random.rand(num_samples, matrix_size, matrix_size))
mul = [1,2,3,4,5,6]

# Define a function that uses tf.matmul
def matmul_function(matrix):
return tf.matmul(matrix, mul)

# Apply the matmul_function to the dataset using map
result_dataset = dataset.map(matmul_function)

إذا ألقينا نظرة خاطفة على الوثائق، فإن ماتمول يتوقع موترًا من المرتبة الثانية على الأقل، لذا قم بضرب المصفوفة في [1,2,3,4,5,6]، وهو مجرد مصفوفة، سيثير هذا الخطأ.

ValueError: Shape must be rank 2 but is rank 1 for '{{node MatMul}} = MatMul[T=DT_DOUBLE, transpose_a=false, transpose_b=false](args_0, MatMul/b)' with input shapes: [3,3], [2].

الخطوة الأولى الرائعة لهذا الخطأ هي التعمق في الوثائق وفهم ما تبحث عنه الوظيفة التي تستخدمها (إليك قائمة رائعة بالوظائف المتوفرة في الموترات: Raw_ops.

ثم استخدم طريقة التصنيف لتحديد ما نقدمه بالفعل.

print(tf.rank(mul))
tf.Tensor(1, shape=(), dtype=int32)

فيما يتعلق بالإصلاحات، غالبًا ما يكون tf.reshape خيارًا جيدًا للبدء به. دعونا نتوقف لحظة للحديث قليلاً عن tf.reshape، لأنه سيكون رفيقًا مخلصًا طوال رحلة Tensorflow: tf.reshape(tensor, Shape, name=None)

إعادة التشكيل تأخذ ببساطة الموتر الذي نريد إعادة تشكيله، وموترًا آخر يحتوي على الشكل الذي نريد أن يكون عليه الناتج. على سبيل المثال، دعونا نعيد تشكيل مدخلات الضرب لدينا:

mul = [1,2,3,4,5,6]
tf.reshape(mul, [3, 2]).numpy()
array([[1, 2],
[3, 4],
[5, 6]], dtype=int32)

سوف يتحول المتغير الخاص بنا إلى موتر (3،2) (3 صفوف، عمودين). ملاحظة سريعة، tf.reshape(t، [3, -1].numpy() سوف ينتج نفس الشيء لأن -1 يخبر Tensorflow بحساب حجم البعد بحيث يظل الحجم الإجمالي ثابتًا. عدد العناصر في موتر الشكل هو الرتبة.

بمجرد إنشاء موتر بالرتبة المناسبة، فإن عملية الضرب لدينا سوف تعمل بشكل جيد!

شكل

خطأ القيمة: إدخال الطبقة غير متوافق مع الطبقة….

إن الفهم البديهي لشكل الموتر وكيفية تفاعله وتغيره عبر طبقات النموذج جعل الحياة مع التعلم العميق أسهل بكثير

أولاً، التخلص من المفردات الأساسية: يشير شكل الموتر إلى عدد العناصر على طول كل بعد أو محور للموتر. على سبيل المثال، الموتر ثنائي الأبعاد المكون من 3 صفوف و4 أعمدة له الشكل (3، 4).

إذًا ما الذي يمكن أن يحدث خطأً في الشكل؟ سعيد لأنك سألت، عدد غير قليل من الأشياء!

أولاً وقبل كل شيء، يجب أن يتطابق شكل ورتبة بيانات التدريب الخاصة بك مع شكل الإدخال المتوقع بواسطة طبقة الإدخال. دعونا نلقي نظرة على مثال، شبكة CNN الأساسية:

import tensorflow as tf
from tensorflow.keras import layers, models

# Create a function to generate sample data
def generate_sample_data(num_samples=100):
for _ in range(num_samples):
features = tf.random.normal(shape=(64, 64, 3))
labels = tf.one_hot(tf.random.uniform(shape=(), maxval=10, dtype=tf.int32), depth=10)
yield features, labels

# Create a TensorFlow dataset using the generator function
sample_dataset = tf.data.Dataset.from_generator(generate_sample_data, output_signature=(tf.TensorSpec(shape=(64, 64, 3), dtype=tf.float32), tf.TensorSpec(shape=(10,), dtype=tf.float32)))

# Create a CNN model with an input layer expecting (128, 128, 3)
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))

# Compile the model
model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=['accuracy'])
# Fit the model using the dataset
model.fit(sample_dataset.batch(32).repeat(), epochs=5, steps_per_epoch=100, validation_steps=20)

ستؤدي محاولة تشغيل الكود أعلاه إلى:

ValueError: Input 0 of layer "sequential_5" is incompatible with the layer: expected shape=(None, 128, 128, 3), found shape=(None, 64, 64, 3)

وذلك لأن نموذجنا يتوقع أن يكون موتر الإدخال بالشكل (128، 128، 3) والبيانات التي تم إنشاؤها هي (64، 64، 3).

في مثل هذا الموقف، يمكن لصديقنا العزيز، reshape، أو وظيفة Tensorflow أخرى، تغيير الحجم، أن يساعد. إذا كنا، كما في الحالة أعلاه، نعمل مع الصور، فيمكننا ببساطة تشغيل تغيير الحجم أو تغيير توقعات مدخلات نموذجنا:

def resize_image(image, label):
resized_image = tf.image.resize(image, size=target_shape)
return resized_image, label

# Apply the resize function to the entire dataset
resized_dataset = sample_dataset.map(resize_image)

في هذا السياق، من المفيد معرفة القليل عن كيفية توقع الأنواع الشائعة من النماذج وطبقات النماذج مدخلات من أشكال مختلفة، لذلك دعونا ننعطف قليلاً.

تأخذ الشبكات العصبية العميقة للطبقات الكثيفة موترات ذات بعد واحد (أو ثنائية الأبعاد، اعتمادًا على ما إذا كنت تقوم بتضمين حجم الدُفعة، لكننا سنتحدث عن حجم الدُفعة قليلًا) للتنسيق (feature_size، ) حيث feature_size هو عدد الميزات في كل عينة.

تستقبل الشبكات العصبية التلافيفية البيانات التي تمثل الصور، باستخدام موترات ثلاثية الأبعاد (العرض والارتفاع والقنوات) حيث تكون القنوات هي نظام الألوان، و1 للتدرج الرمادي، و3 لـ RBG.

وأخيرًا، الشبكات العصبية المتكررة مثل LTSMs تأخذ بعدين (الخطوات الزمنية، feature_size)

لكن العودة إلى الأخطاء! هناك سبب شائع آخر في أخطاء شكل Tensorflow يتعلق بكيفية تغير الشكل مع مرور البيانات عبر طبقات النموذج. كما ذكرنا سابقًا، تأخذ الطبقات المختلفة أشكالًا مختلفة للمدخلات، ويمكنها أيضًا إعادة تشكيل المخرجات.

وبالعودة إلى مثال CNN من الأعلى، فلنفصله مرة أخرى، من خلال رؤية ما يحدث عندما نزيل الطبقة المسطحة. إذا حاولنا تشغيل الكود فسنرى

ValueError: Shapes (None, 10) and (None, 28, 28, 10) are incompatible

هذا هو المكان الذي تكون فيه طباعة كافة أشكال الإدخال والإخراج النموذجية الخاصة بنا جنبًا إلى جنب مع أشكال البيانات الخاصة بنا أمرًا مفيدًا لمساعدتنا في تحديد مكان عدم التطابق.

سيُظهر لنا model.summary()‎

Layer (type) Output Shape Param #
=================================================================
conv2d_15 (Conv2D) (None, 126, 126, 32) 896
max_pooling2d_10 (MaxPooli (None, 63, 63, 32) 0
ng2D)
conv2d_16 (Conv2D) (None, 61, 61, 64) 18496
max_pooling2d_11 (MaxPooling2D) (None, 30, 30, 64) 0
conv2d_17 (Conv2D) (None, 28, 28, 64) 36928
flatten_5 (Flatten) (None, 50176) 0
dense_13 (Dense) (None, 64) 3211328
dense_14 (Dense) (None, 10) 650
=================================================================
Total params: 3268298 (12.47 MB)
Trainable params: 3268298 (12.47 MB)
Non-trainable params: 0 (0.00 Byte)

وسيكشف تشخيصنا الإضافي

###################Input Shape and Datatype#####################
(None, 128, 128, 3) <dtype: 'float32'>
###################Output Shape and Datatype#####################
(None, 10) <dtype: 'float32'>
###################Layer Input Shape and Datatype#####################
conv2d_15 KerasTensor(type_spec=TensorSpec(shape=(None, 128, 128, 3), dtype=tf.float32, name="conv2d_15_input"), name="conv2d_15_input", description="created by layer 'conv2d_15_input'") float32
max_pooling2d_10 KerasTensor(type_spec=TensorSpec(shape=(None, 126, 126, 32), dtype=tf.float32, name=None), name="conv2d_15/Relu:0", description="created by layer 'conv2d_15'") float32
conv2d_16 KerasTensor(type_spec=TensorSpec(shape=(None, 63, 63, 32), dtype=tf.float32, name=None), name="max_pooling2d_10/MaxPool:0", description="created by layer 'max_pooling2d_10'") float32
max_pooling2d_11 KerasTensor(type_spec=TensorSpec(shape=(None, 61, 61, 64), dtype=tf.float32, name=None), name="conv2d_16/Relu:0", description="created by layer 'conv2d_16'") float32
conv2d_17 KerasTensor(type_spec=TensorSpec(shape=(None, 30, 30, 64), dtype=tf.float32, name=None), name="max_pooling2d_11/MaxPool:0", description="created by layer 'max_pooling2d_11'") float32
flatten_5 KerasTensor(type_spec=TensorSpec(shape=(None, 28, 28, 64), dtype=tf.float32, name=None), name="conv2d_17/Relu:0", description="created by layer 'conv2d_17'") float32
dense_13 KerasTensor(type_spec=TensorSpec(shape=(None, 50176), dtype=tf.float32, name=None), name="flatten_5/Reshape:0", description="created by layer 'flatten_5'") float32
dense_14 KerasTensor(type_spec=TensorSpec(shape=(None, 64), dtype=tf.float32, name=None), name="dense_13/Relu:0", description="created by layer 'dense_13'") float32

إنه كثير من المخرجات، ولكن يمكننا أن نرى أن الطبقة الكثيفة 13 تبحث عن مدخلات بالشكل (لا شيء، 50176). ومع ذلك، مخرجات طبقة conv2d_17 (لا شيء، 28، 28، 64)

تعمل الطبقات المسطحة على تحويل المخرجات متعددة الأبعاد من الطبقات السابقة إلى متجه أحادي البعد (مسطح) تتوقعه الطبقة الكثيفة.

تقوم طبقات Conv2d وMax Pooling بتغيير بيانات الإدخال الخاصة بها بطرق أخرى مثيرة للاهتمام أيضًا، ولكنها خارج نطاق هذه المقالة. للحصول على تفاصيل رائعة، قم بإلقاء نظرة على: الدليل النهائي لشكل الإدخال وتعقيد النموذج في الشبكات العصبية

ولكن ماذا عن حجم الدفعة؟! أنا لم أنس!

إذا قمنا بكسر الكود الخاص بنا مرة أخرى عن طريق إزالة .batch(32) من مجموعة البيانات في model.fit فسوف نحصل على الخطأ:

ValueError: Input 0 of layer "sequential_10" is incompatible with the layer: expected shape=(None, 128, 128, 3), found shape=(128, 128, 3)

وذلك لأن البعد الأول لمدخل الطبقة محجوز لحجم الدفعة أو عدد العينات التي نريد أن يعمل النموذج من خلالها في المرة الواحدة. للحصول على تعمق عميق، اقرأ الفرق بين الدفعة والعصر.

حجم الدفعة الافتراضي هو لا شيء قبل الملاءمة، كما يمكننا أن نرى في مخرجات ملخص النموذج، ويتوقع نموذجنا أن نقوم بتعيينه في مكان آخر، اعتمادًا على كيفية ضبط المعلمة الفائقة. يمكننا أيضًا فرض ذلك في طبقة الإدخال لدينا باستخدام Batch_input_size بدلاً من input_size، لكن هذا يقلل من مرونتنا في اختبار القيم المختلفة.

يكتب

خطأ في الكتابة: فشل في تحويل كائن من النوع إلى Tensor. نوع الكائن غير معتمد

وأخيرا، دعونا نتحدث قليلا عن بعض تفاصيل نوع البيانات في Tensors.

الخطأ أعلاه هو خطأ آخر، وهو أنه إذا كنت معتادًا على العمل في أنظمة قواعد البيانات باستخدام جداول مبنية من جميع أنواع البيانات، فقد يكون ذلك محيرًا بعض الشيء، ولكنه من أكثر الأخطاء سهولة في التشخيص والإصلاح، على الرغم من وجود بعض الأخطاء اثنين من الأسباب الشائعة للبحث عنها.

المشكلة الرئيسية هي أنه على الرغم من أن الموترات تدعم مجموعة متنوعة من أنواع البيانات، فعندما نقوم بتحويل مصفوفة NumPy إلى موترات (تدفق شائع في التعلم العميق)، يجب أن تكون أنواع البيانات عائمة. يقوم البرنامج النصي أدناه بتهيئة مثال مفتعل لإطار بيانات بدون أي نقاط بيانات وسلسلة. دعنا نتعرف على بعض المشكلات والإصلاحات لهذا المثال:

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
data = [
[None, 0.2, '0.3'],
[0.1, None, '0.3'],
[0.1, 0.2, '0.3'],
]
X_train = pd.DataFrame(data=data, columns=["x1", "x2", "x3"])
y_train = pd.DataFrame(data=[1, 0, 1], columns=["y"])

# Create a TensorFlow dataset
train_dataset = tf.data.Dataset.from_tensor_slices((X_train.to_numpy(), y_train.to_numpy()))
# Define the model
model = Sequential()
model.add(Dense(1, input_dim=X_train.shape[1], activation='sigmoid'))
model.compile(optimizer="adam", loss="binary_crossentropy", metrics=['accuracy'])
# Fit the model using the TensorFlow dataset
model.fit(train_dataset.batch(3), epochs=3)

سيؤدي تشغيل هذا الرمز إلى إعلامنا بما يلي:

ValueError: Failed to convert a NumPy array to a Tensor (Unsupported object type float).

المشكلة الأكثر وضوحًا هي أنك ترسل مصفوفة NumPy تحتوي على كائن من النوع غير العائم. إذا كان لديك عمود فعلي من البيانات الفئوية، فهناك العديد من الطرق لتحويل ذلك إلى بيانات رقمية (ترميز طلقة واحدة، وما إلى ذلك) ولكن هذا خارج نطاق هذه المناقشة.

يمكننا تحديد ذلك إذا قمنا بتشغيل print(X_train.dtypes)، والذي سيخبرنا بما هو موجود في إطار البيانات الخاص بنا والذي لا يحبه Tensorflow.

x1 float64
x2 float64
x3 object
dtype: object

إذا كنا نواجه نقاط بيانات غير عائمة، فإن السطر أدناه سوف يحل جميع مشاكلنا بطريقة سحرية:

X_train = np.asarray(X_train).astype('float32')

شيء آخر يجب التحقق منه هو ما إذا كان لديك لا شيء أو np.nan في أي مكان.

لمعرفة ذلك يمكننا استخدام بضعة أسطر من التعليمات البرمجية مثل:

null_mask = X_train.isnull().any(axis=1)
null_rows = X_train[null_mask]
print(null_rows)

مما يخبرنا أن لدينا قيمًا فارغة في الصفين 0 و1:

x1 x2 x3
0 NaN 0.2 0.3
1 0.1 NaN 0.3

إذا كان الأمر كذلك، وكان ذلك متوقعًا/متعمدًا، فنحن بحاجة إلى استبدال تلك القيم ببديل مقبول. يمكن لفيلا أن تساعدنا هنا.

X_train.fillna(value=0, inplace=True)

مع هذه التغييرات في الكود أدناه، سيتم تحويل مصفوفة NumPy الخاصة بنا بنجاح إلى مجموعة بيانات موتر ويمكننا تدريب نموذجنا!

كثيرًا ما أجد أنني أتعلم أكثر عن تقنية معينة عندما يتعين علي العمل من خلال الأخطاء، وآمل أن يكون هذا مفيدًا لك أيضًا إلى حد ما!

إذا كانت لديك نصائح وحيل رائعة أو أخطاء ممتعة في Tensorflow، فيرجى تمريرها إلينا!

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

زر الذهاب إلى الأعلى