ارزیابی، پیشبینی و Functional API در Keras؛ مدلهای Wide & Deep و چندورودی/چندخروجی
منحنیهای یادگیری و ارزیابی مدل
شیء History که متد fit() برمیگرداند، مقادیر Loss و معیارها را در پایان هر Epoch برای مجموعهٔ آموزش و، در صورت وجود، مجموعهٔ اعتبارسنجی نگه میدارد. با تبدیل دیکشنری history.history به یک DataFrame در Pandas میتوان منحنیهای یادگیری را رسم کرد:
import matplotlib.pyplot as plt
import pandas as pd
pd.DataFrame(history.history).plot(
figsize=(8, 5), xlim=[0, 29], ylim=[0, 1], grid=True, xlabel="Epoch",
style=["r--", "r--.", "b-", "b-*"])
plt.show()
شکل ۱۰-۱۱. منحنیهای یادگیری؛ میانگین Loss و دقت آموزش در طول هر Epoch و میانگین Loss و دقت اعتبارسنجی در پایان هر Epoch
در یک روند آموزشی سالم، دقت آموزش و اعتبارسنجی بهتدریج افزایش مییابد و Loss هر دو کاهش پیدا میکند. اگر منحنیهای آموزش و اعتبارسنجی به مرور از هم فاصله بگیرند، نشانهای از Overfitting است. گاهی در ابتدای آموزش به نظر میرسد مدل روی اعتبارسنجی بهتر از آموزش عمل میکند؛ علت این است که خطای اعتبارسنجی در پایان Epoch اندازهگیری میشود، در حالی که خطای آموزش میانگین در حال حرکت در طول همان Epoch است. اگر منحنی آموزش را حدود نیم Epoch به چپ منتقل کنیم، این دو در ابتدای آموزش تقریباً روی هم قرار میگیرند.
پس از آموزش طولانیتر معمولاً عملکرد آموزش از اعتبارسنجی بهتر میشود. اگر Loss اعتبارسنجی همچنان در حال کاهش باشد، مدل هنوز کاملاً همگرا نشده و میتوان fit() را دوباره فراخوانی کرد؛ Keras آموزش را از همان نقطهای که متوقف شده ادامه میدهد.
اگر عملکرد رضایتبخش نباشد، نخستین ابرپارامتری که باید بررسی شود نرخ یادگیری است. سپس میتوان Optimizer را تغییر داد و پس از هر تغییر دوباره نرخ یادگیری را تنظیم کرد. اگر هنوز لازم بود، تعداد لایهها، تعداد نورونهای هر لایه، نوع تابع فعالسازی و اندازهٔ Batch را نیز تنظیم کنید. وقتی از دقت اعتبارسنجی راضی شدید، پیش از استقرار مدل باید آن را روی مجموعهٔ آزمون ارزیابی کنید تا خطای تعمیم تخمین زده شود:
>>> model.evaluate(X_test, y_test)
313/313 - loss: 0.3243 - sparse_categorical_accuracy: 0.8864
[0.32431697845458984, 0.8863999843597412]
عملکرد آزمون معمولاً کمی پایینتر از اعتبارسنجی است، زیرا ابرپارامترها روی اعتبارسنجی تنظیم میشوند. نباید برای بهترشدن عدد آزمون، ابرپارامترها را با خود مجموعهٔ آزمون تنظیم کرد؛ در غیر این صورت برآورد خطای تعمیم بیش از حد خوشبینانه خواهد شد.
پیشبینی روی نمونههای جدید
برای پیشبینی میتوان از predict() استفاده کرد. در این مثال سه نمونهٔ نخست مجموعهٔ آزمون را بهعنوان نمونهٔ جدید در نظر میگیریم:
>>> X_new = X_test[:3]
>>> y_proba = model.predict(X_new)
>>> y_proba.round(2)
array([[0. , 0. , 0. , 0. , 0. , 0.01, 0. , 0.02, 0. , 0.97],
[0. , 0. , 0.99, 0. , 0.01, 0. , 0. , 0. , 0. , 0. ],
[0. , 1. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ]],
dtype=float32)
مدل برای هر نمونه یک احتمال برای هر کلاس، از صفر تا ۹، تخمین میزند. برای تصویر نخست، احتمال کلاس ۹ یعنی نیمبوت حدود ۹۷٪ و احتمال کلاسهای مربوط به کفش ورزشی و صندل بسیار کمتر است. اگر فقط کلاس با بیشترین احتمال مهم باشد، از argmax() استفاده میکنیم:
>>> import numpy as np
>>> y_pred = y_proba.argmax(axis=-1)
>>> y_pred
array([9, 2, 1])
>>> np.array(class_names)[y_pred]
array(['Ankle boot', 'Pullover', 'Trouser'], dtype='<U11')
>>> y_test[:3]
array([9, 2, 1], dtype=uint8)
شکل ۱۰-۱۲. سه تصویر Fashion MNIST که مدل آنها را بهدرستی طبقهبندی کرده است
ساخت MLP رگرسیون با Sequential API
اکنون مسئلهٔ مسکن کالیفرنیا را دوباره با یک MLP سهلایه، هر لایه شامل ۵۰ نورون، حل میکنیم؛ این بار با Keras. تفاوتهای اصلی با مدل طبقهبندی عبارتاند از: لایهٔ خروجی فقط یک نورون و بدون تابع فعالسازی دارد؛ Loss میانگین مربع خطا است؛ معیار RMSE در نظر گرفته میشود؛ و از Optimizer نوع Adam استفاده میکنیم. به Flatten نیازی نداریم و نخستین لایه، Normalization است که کاری مشابه StandardScaler انجام میدهد. این لایه باید پیش از fit() با adapt() روی دادهٔ آموزش تنظیم شود.
tf.random.set_seed(42)
norm_layer = tf.keras.layers.Normalization(input_shape=X_train.shape[1:])
model = tf.keras.Sequential([
norm_layer,
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(1)
])
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
model.compile(loss="mse", optimizer=optimizer, metrics=["RootMeanSquaredError"])
norm_layer.adapt(X_train)
history = model.fit(X_train, y_train, epochs=20,
validation_data=(X_valid, y_valid))
mse_test, rmse_test = model.evaluate(X_test, y_test)
X_new = X_test[:3]
y_pred = model.predict(X_new)
Sequential API برای بسیاری از مدلها تمیز و مستقیم است، اما گاهی به توپولوژی پیچیدهتر یا چند ورودی و چند خروجی نیاز داریم. در این شرایط Functional API مناسبتر است.
ساخت مدلهای پیچیده با Functional API
یکی از نمونههای شبکهٔ غیرترتیبی، معماری Wide & Deep است که در سال ۲۰۱۶ معرفی شد. این معماری همه یا بخشی از ورودی را مستقیماً به لایهٔ خروجی متصل میکند تا شبکه بتواند هم الگوهای عمیق را از مسیر Deep و هم قواعد ساده را از مسیر کوتاه Wide یاد بگیرد. در یک MLP معمولی همهٔ داده مجبور است از کل پشتهٔ لایهها عبور کند و در این مسیر ممکن است بعضی الگوهای ساده بر اثر تبدیلهای پیدرپی دچار اعوجاج شوند.
شکل ۱۰-۱۳. شبکهٔ عصبی Wide & Deep که مسیر کوتاه و مسیر عمیق را همزمان در اختیار مدل میگذارد
مدل Wide & Deep برای مسئلهٔ مسکن کالیفرنیا را میتوان چنین ساخت:
normalization_layer = tf.keras.layers.Normalization()
hidden_layer1 = tf.keras.layers.Dense(30, activation="relu")
hidden_layer2 = tf.keras.layers.Dense(30, activation="relu")
concat_layer = tf.keras.layers.Concatenate()
output_layer = tf.keras.layers.Dense(1)
input_ = tf.keras.layers.Input(shape=X_train.shape[1:])
normalized = normalization_layer(input_)
hidden1 = hidden_layer1(normalized)
hidden2 = hidden_layer2(hidden1)
concat = concat_layer([normalized, hidden2])
output = output_layer(concat)
model = tf.keras.Model(inputs=[input_], outputs=[output])
پنج خط نخست لایههای مورد نیاز را میسازند. سپس لایهها مانند تابع روی ورودیهای نمادین فراخوانی میشوند و در پایان یک شیء Model با ورودی و خروجی مشخص ساخته میشود. Input دادهٔ واقعی نیست؛ فقط مشخصات ورودی، شامل شکل و در صورت نیاز نوع داده، را تعریف میکند. خروجیهای میانی مثل normalized و hidden1 نیز نمادیناند و برای ساخت گراف مدل بهکار میروند.
پس از ساخت این مدل، مراحل Compile، adapt()، آموزش، ارزیابی و پیشبینی دقیقاً مانند مدلهای قبلی است.
مدل با چند ورودی
ممکن است بخواهیم زیرمجموعهای از ویژگیها را از مسیر Wide و زیرمجموعهای دیگر، حتی با همپوشانی، را از مسیر Deep عبور دهیم. فرض کنید ویژگیهای صفر تا چهار به مسیر Wide و ویژگیهای دو تا هفت به مسیر Deep بروند:
input_wide = tf.keras.layers.Input(shape=[5]) # features 0 to 4
input_deep = tf.keras.layers.Input(shape=[6]) # features 2 to 7
norm_layer_wide = tf.keras.layers.Normalization()
norm_layer_deep = tf.keras.layers.Normalization()
norm_wide = norm_layer_wide(input_wide)
norm_deep = norm_layer_deep(input_deep)
hidden1 = tf.keras.layers.Dense(30, activation="relu")(norm_deep)
hidden2 = tf.keras.layers.Dense(30, activation="relu")(hidden1)
concat = tf.keras.layers.concatenate([norm_wide, hidden2])
output = tf.keras.layers.Dense(1)(concat)
model = tf.keras.Model(inputs=[input_wide, input_deep], outputs=[output])
شکل ۱۰-۱۴. استفاده از دو ورودی جدا برای مسیرهای Wide و Deep
در این مثال لایههای Dense در همان خطی که ساخته میشوند فراخوانی شدهاند، که الگوی رایجی در Functional API است. برای لایههای Normalization این کار را نمیکنیم، زیرا بعداً برای فراخوانی adapt() به مرجع لایه نیاز داریم. تابع tf.keras.layers.concatenate() نیز یک لایهٔ Concatenate میسازد و همان لحظه آن را با ورودیهای دادهشده فراخوانی میکند.
هنگام آموزش باید بهجای یک ماتریس، یک جفت ماتریس متناظر با دو ورودی به مدل بدهیم:
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
model.compile(loss="mse", optimizer=optimizer, metrics=["RootMeanSquaredError"])
X_train_wide, X_train_deep = X_train[:, :5], X_train[:, 2:]
X_valid_wide, X_valid_deep = X_valid[:, :5], X_valid[:, 2:]
X_test_wide, X_test_deep = X_test[:, :5], X_test[:, 2:]
X_new_wide, X_new_deep = X_test_wide[:3], X_test_deep[:3]
norm_layer_wide.adapt(X_train_wide)
norm_layer_deep.adapt(X_train_deep)
history = model.fit((X_train_wide, X_train_deep), y_train, epochs=20,
validation_data=((X_valid_wide, X_valid_deep), y_valid))
mse_test = model.evaluate((X_test_wide, X_test_deep), y_test)
y_pred = model.predict((X_new_wide, X_new_deep))
مدل با چند خروجی
چند خروجی در چند سناریوی مهم کاربرد دارد: مسئله ممکن است ذاتاً چند وظیفه داشته باشد، مثل مکانیابی و طبقهبندی همزمان شیء در تصویر؛ چند وظیفهٔ مستقل ممکن است از ویژگیهای مشترک بهره ببرند، مثل تشخیص حالت چهره و عینکزدن؛ یا یک خروجی کمکی میتواند بهعنوان منظمسازی استفاده شود تا بخشهای پایین شبکه مجبور شوند بهتنهایی نمایش مفیدی یاد بگیرند.
شکل ۱۰-۱۵. افزودن خروجی کمکی برای منظمسازی یک شبکهٔ چندخروجی
برای افزودن خروجی کمکی کافی است آن را به لایهٔ مناسب متصل و در فهرست خروجیهای مدل ثبت کنیم:
# Same as above, up to the main output layer
output = tf.keras.layers.Dense(1)(concat)
aux_output = tf.keras.layers.Dense(1)(hidden2)
model = tf.keras.Model(inputs=[input_wide, input_deep],
outputs=[output, aux_output])
هر خروجی به تابع Loss خودش نیاز دارد. اگر فقط یک Loss بدهیم، Keras همان را برای همهٔ خروجیها بهکار میبرد. بهطور پیشفرض Lossها با هم جمع میشوند، اما چون خروجی اصلی مهمتر از خروجی کمکی است میتوان وزن متفاوت تعیین کرد:
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
model.compile(loss=("mse", "mse"), loss_weights=(0.9, 0.1), optimizer=optimizer,
metrics=["RootMeanSquaredError"])
اگر خروجیها نامگذاری شوند میتوان دیکشنری Loss و loss_weights داد که در مدلهای چندخروجی خواناتر است.
در زمان آموزش باید برای هر خروجی Label ارائه شود. در این مثال خروجی اصلی و کمکی هر دو همان هدف را پیشبینی میکنند:
norm_layer_wide.adapt(X_train_wide)
norm_layer_deep.adapt(X_train_deep)
history = model.fit(
(X_train_wide, X_train_deep), (y_train, y_train), epochs=20,
validation_data=((X_valid_wide, X_valid_deep), (y_valid, y_valid))
)
در ارزیابی، Keras مجموع وزندار Lossها، Loss هر خروجی و معیارهای هر خروجی را بازمیگرداند:
eval_results = model.evaluate((X_test_wide, X_test_deep), (y_test, y_test))
weighted_sum_of_losses, main_loss, aux_loss, main_rmse, aux_rmse = eval_results
متد predict() نیز برای هر خروجی یک آرایهٔ پیشبینی برمیگرداند:
y_pred_main, y_pred_aux = model.predict((X_new_wide, X_new_deep))
y_pred_tuple = model.predict((X_new_wide, X_new_deep))
y_pred = dict(zip(model.output_names, y_pred_tuple))
به این ترتیب Functional API امکان ساخت طیف گستردهای از معماریهای دارای شاخه، ورودیهای متعدد و خروجیهای متعدد را فراهم میکند.
این ترجمه برای استفادهٔ آموزشی تهیه شده است. انتشار عمومی، فروش یا بازنشر آن باید با رعایت حقوق مؤلف و ناشر و اخذ مجوزهای لازم انجام شود.