Subclassing API، ذخیرهٔ مدل، Callbackها و TensorBoard در Keras
ساخت مدلهای پویا با Subclassing API
Sequential API و Functional API هر دو رویکردی اعلانی دارند: ابتدا لایهها و نحوهٔ اتصالشان را تعریف میکنید و سپس داده برای آموزش یا استنتاج وارد مدل میشود. این روش مزیتهای زیادی دارد؛ مدل را میتوان بهسادگی ذخیره، Clone و بهاشتراک گذاشت، ساختارش را نمایش و تحلیل کرد و Keras میتواند شکل Tensorها و نوع دادهها را پیشاپیش بررسی کند تا خطاها زودتر آشکار شوند. همچنین چون مدل به شکل یک گراف ایستا از لایهها تعریف شده، Debug کردن آن نسبتاً ساده است.
اما همین ایستابودن گاهی محدودیت است. بعضی مدلها به حلقه، شکلهای متغیر، شاخههای شرطی یا رفتارهای پویا نیاز دارند. در چنین موقعیتهایی، یا وقتی سبک برنامهنویسی دستوری را ترجیح میدهید، Subclassing API مناسب است.
در این روش از کلاس Model ارثبری میکنیم، لایههای لازم را در سازنده میسازیم و محاسبات را در متد call() تعریف میکنیم. مثال زیر معادل همان مدل Wide & Deep قبلی است:
class WideAndDeepModel(tf.keras.Model):
def __init__(self, units=30, activation="relu", **kwargs):
super().__init__(**kwargs) # needed to support naming the model
self.norm_layer_wide = tf.keras.layers.Normalization()
self.norm_layer_deep = tf.keras.layers.Normalization()
self.hidden1 = tf.keras.layers.Dense(units, activation=activation)
self.hidden2 = tf.keras.layers.Dense(units, activation=activation)
self.main_output = tf.keras.layers.Dense(1)
self.aux_output = tf.keras.layers.Dense(1)
def call(self, inputs):
input_wide, input_deep = inputs
norm_wide = self.norm_layer_wide(input_wide)
norm_deep = self.norm_layer_deep(input_deep)
hidden1 = self.hidden1(norm_deep)
hidden2 = self.hidden2(hidden1)
concat = tf.keras.layers.concatenate([norm_wide, hidden2])
output = self.main_output(concat)
aux_output = self.aux_output(hidden2)
return output, aux_output
model = WideAndDeepModel(30, activation="relu", name="my_cool_model")
تفاوت اصلی این است که ساخت لایهها در سازنده از استفادهٔ آنها در call() جدا شده و نیازی به ساخت شیءهای Input نداریم؛ ورودی واقعی از آرگومان inputs متد call() میآید. پس از ساخت نمونهٔ مدل، Compile، تنظیم لایههای Normalization با adapt()، آموزش، ارزیابی و پیشبینی دقیقاً مانند Functional API انجام میشود.
مزیت بزرگ این API آزادی بسیار زیاد در call() است: میتوان حلقهٔ for، شرط if، عملیات سطح پایین TensorFlow و تقریباً هر منطق دلخواهی را بهکار برد. این انعطاف برای پژوهش و آزمودن ایدههای جدید بسیار ارزشمند است.
این آزادی هزینه هم دارد. معماری مدل داخل call() پنهان است، بنابراین Keras نمیتواند بهراحتی آن را تحلیل کند؛ مدل با tf.keras.models.clone_model() قابل Clone شدن نیست؛ summary() فقط فهرست لایهها را نشان میدهد و اتصال آنها را مشخص نمیکند؛ و بررسی پیشاپیش شکل و نوع دادهها نیز محدودتر است. بنابراین مگر آنکه واقعاً به این انعطاف نیاز باشد، Sequential یا Functional API معمولاً انتخاب مناسبتری است.
ذخیره و بازیابی مدل
پس از آموزش، ذخیرهٔ مدل Keras بسیار ساده است:
model.save("my_keras_model", save_format="tf")
با save_format="tf" مدل در قالب SavedModel مربوط به TensorFlow ذخیره میشود. خروجی یک پوشه است که چند فایل و زیردایرکتوری دارد. فایل saved_model.pb معماری و منطق مدل را به شکل گراف محاسباتی Serialize شده نگه میدارد، بنابراین برای اجرای مدل در محیط تولید الزاماً به کد منبع Python مدل نیاز ندارید. فایل keras_metadata.pb اطلاعات اضافی مورد نیاز Keras را نگه میدارد. زیردایرکتوری variables مقدار همهٔ پارامترها، شامل وزنها، بایاسها، آمارههای Normalization و وضعیت Optimizer را نگه میدارد و در مدلهای بزرگ ممکن است به چند فایل تقسیم شود. پوشهٔ assets نیز میتواند فایلهای جانبی مانند نمونهٔ داده یا نام ویژگی و کلاس را در خود داشته باشد.
از آنجا که وضعیت Optimizer نیز ذخیره میشود، پس از بارگذاری مدل میتوان آموزش را ادامه داد.
بارگذاری مدل همانقدر ساده است:
model = tf.keras.models.load_model("my_keras_model")
y_pred_main, y_pred_aux = model.predict((X_new_wide, X_new_deep))
اگر فقط پارامترها لازم باشند، save_weights() و load_weights() وزنها، بایاسها، آمارههای پیشپردازش و وضعیت Optimizer را ذخیره و بازیابی میکنند. ذخیرهٔ فقط وزنها سریعتر است و فضای کمتری میگیرد، بنابراین برای Checkpointهای مکرر در آموزشهای طولانی مناسب است.
استفاده از Callbackها
متد fit() آرگومان callbacks دارد که فهرستی از شیءهایی را میگیرد که Keras در نقاط مشخص فرایند آموزش فراخوانی میکند: پیش و پس از کل آموزش، پیش و پس از هر Epoch، و حتی پیش و پس از هر Batch.
برای نمونه، ModelCheckpoint میتواند در پایان هر Epoch وضعیت مدل را ذخیره کند:
checkpoint_cb = tf.keras.callbacks.ModelCheckpoint(
"my_checkpoints", save_weights_only=True)
history = model.fit([...], callbacks=[checkpoint_cb])
اگر مجموعهٔ اعتبارسنجی دارید، با save_best_only=True فقط زمانی Checkpoint ذخیره میشود که عملکرد اعتبارسنجی از تمام دفعات قبلی بهتر باشد. به این ترتیب پس از پایان آموزش کافی است بهترین مدل ذخیرهشده را بازیابی کنید. این روشی برای استفاده از ایدهٔ Early Stopping است، هرچند خود آموزش را زودتر متوقف نمیکند.
Callback دیگری به نام EarlyStopping میتواند وقتی برای تعداد مشخصی Epoch، تعیینشده با patience، پیشرفتی روی دادهٔ اعتبارسنجی دیده نشد آموزش را متوقف کند. با restore_best_weights=True در پایان بهترین وزنها بازگردانده میشوند:
early_stopping_cb = tf.keras.callbacks.EarlyStopping(
patience=10, restore_best_weights=True)
history = model.fit([...], callbacks=[checkpoint_cb, early_stopping_cb])
در این حالت میتوان تعداد Epoch را بزرگ در نظر گرفت و اجازه داد آموزش خودکار در زمان مناسب متوقف شود. باید مراقب نرخ یادگیری بسیار کوچک بود، زیرا ممکن است مدل پیشرفت بسیار آهستهای نشان دهد و مدت زیادی ادامه پیدا کند.
برای کنترل بیشتر میتوان Callback سفارشی نوشت. مثال زیر نسبت Loss اعتبارسنجی به Loss آموزش را در پایان هر Epoch نمایش میدهد تا تشخیص Overfitting آسانتر شود:
class PrintValTrainRatioCallback(tf.keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs):
ratio = logs["val_loss"] / logs["loss"]
print(f"Epoch={epoch}, val/train={ratio:.2f}")
متدهای قابل پیادهسازی شامل on_train_begin()، on_train_end()، on_epoch_begin()، on_epoch_end()، on_batch_begin() و on_batch_end() هستند. برای ارزیابی نیز رویدادهای on_test_... و برای پیشبینی رویدادهای on_predict_... وجود دارند.
بصریسازی با TensorBoard
TensorBoard ابزار تعاملی قدرتمندی برای مشاهدهٔ منحنیهای یادگیری، مقایسهٔ چند اجرای آموزشی، نمایش گراف محاسباتی، تحلیل آمارههای آموزش، مشاهدهٔ تصاویر تولیدشده توسط مدل، تصویرسازی دادههای چندبعدی، Profile کردن شبکه و پیدا کردن گلوگاههای عملکردی است.
TensorBoard همراه TensorFlow نصب میشود، اما برای نمایش دادههای Profiling به افزونهٔ مربوط نیاز است. در Colab میتوان آن را چنین نصب کرد:
%pip install -q -U tensorboard-plugin-profile
برای استفاده از TensorBoard برنامه باید دادههایی را که قرار است نمایش داده شوند در فایلهای لاگ دودویی ویژهای به نام Event File بنویسد. هر رکورد دودویی یک Summary نام دارد. سرور TensorBoard پوشهٔ لاگ را پایش میکند و با تغییر فایلها نمایشها را بهروز میسازد؛ بنابراین میتوان دادهٔ تقریباً زندهٔ آموزش را مشاهده کرد.
بهتر است یک پوشهٔ ریشه برای لاگها تعریف شود و هر اجرای برنامه در زیردایرکتوری جداگانهای بنویسد. تابع زیر نام زیردایرکتوری را بر اساس زمان اجرا میسازد:
from pathlib import Path
from time import strftime
def get_run_logdir(root_logdir="my_logs"):
return Path(root_logdir) / strftime("run_%Y_%m_%d_%H_%M_%S")
run_logdir = get_run_logdir() # e.g., my_logs/run_2022_08_01_17_25_59
Callback آمادهٔ TensorBoard پوشهٔ لاگ را میسازد، Event Fileها را ایجاد میکند، Summaryها را مینویسد و Loss و معیارهای آموزش و اعتبارسنجی را ثبت میکند. همچنین میتواند شبکه را Profile کند:
tensorboard_cb = tf.keras.callbacks.TensorBoard(
run_logdir, profile_batch=(100, 200))
history = model.fit([...], callbacks=[tensorboard_cb])
در این مثال Batchهای ۱۰۰ تا ۲۰۰ از نخستین Epoch Profile میشوند. چند Batch ابتدایی معمولاً زمان گرمشدن شبکه را شامل میشوند و Profiling نیز هزینهٔ محاسباتی دارد، بنابراین بهتر است نه خیلی زود و نه برای همهٔ Batchها انجام شود.
اگر یک اجرای دیگر با نرخ یادگیری متفاوت انجام شود، ساختار پوشهها بهصورت یک دایرکتوری مستقل برای هر Run و زیردایرکتوریهای جدا برای آموزش و اعتبارسنجی شکل میگیرد. این جداسازی امکان مقایسهٔ اجرای مختلف را فراهم میکند.
%load_ext tensorboard
%tensorboard --logdir=./my_logs
در Jupyter یا Colab، افزونهٔ TensorBoard سرور را راهاندازی و رابط کاربری را در Notebook نمایش میدهد. در اجرای محلی نیز میتوان فرمان tensorboard --logdir=./my_logs را در Terminal اجرا کرد و رابط را در پورت معمول ۶۰۰۶ دید.
شکل ۱۰-۱۶. نمایش و مقایسهٔ منحنیهای یادگیری در TensorBoard
تب SCALARS برای منحنیهای معیارها، GRAPHS برای گراف محاسباتی، PROJECTOR برای Projection وزنها و PROFILE برای Traceهای عملکردی استفاده میشود. دکمهٔ Refresh دادهها را تازه میکند و از تنظیمات میتوان Auto-refresh و فاصلهٔ زمانی آن را فعال کرد.
API سطح پایین tf.summary
TensorFlow در بستهٔ tf.summary API سطح پایینتری نیز دارد. با create_file_writer() میتوان Writer ساخت و سپس Scalar، Histogram، تصویر، صوت و متن را ثبت کرد:
test_logdir = get_run_logdir()
writer = tf.summary.create_file_writer(str(test_logdir))
with writer.as_default():
for step in range(1, 1000 + 1):
tf.summary.scalar("my_scalar", np.sin(step / 10), step=step)
data = (np.random.randn(100) + 2) * step / 100 # gets larger
tf.summary.histogram("my_hist", data, buckets=50, step=step)
images = np.random.rand(2, 32, 32, 3) * step / 1000 # gets brighter
tf.summary.image("my_images", images, step=step)
texts = ["The step is " + str(step), "Its square is " + str(step ** 2)]
tf.summary.text("my_text", texts, step=step)
sine_wave = tf.math.sin(tf.range(12000) / 48000 * 2 * np.pi * step)
audio = tf.reshape(tf.cast(sine_wave, tf.float32), [1, -1, 1])
tf.summary.audio("my_audio", audio, sample_rate=48000, step=step)
پس از اجرای این کد، تبهای IMAGES، AUDIO، DISTRIBUTIONS، HISTOGRAMS و TEXT در TensorBoard ظاهر میشوند. TensorBoard به همین دلیل ابزاری عمومی برای تحلیل دادههای آموزشی است و کاربردش به یک مدل یا حتی صرفاً یادگیری عمیق محدود نمیشود.
این ترجمه برای استفادهٔ آموزشی تهیه شده است. انتشار عمومی، فروش یا بازنشر آن باید با رعایت حقوق مؤلف و ناشر و اخذ مجوزهای لازم انجام شود.