Training Loop کاملاً سفارشی
در بیشتر پروژهها model.fit() انتخاب بهتر است، زیرا کوتاهتر، مطمئنتر و نگهداریپذیرتر است. اما بعضی Architectureها واقعاً به Loop سفارشی نیاز دارند؛ برای نمونه در Wide & Deep ممکن است بخواهیم برای مسیر Wide و Deep Optimizerهای متفاوت داشته باشیم یا Gradient را قبل از اعمال Step با Rule خاصی Transform کنیم.
ابتدا Model سادهای میسازیم و آن را Compile نمیکنیم، زیرا تمام Training را دستی مدیریت خواهیم کرد:
l2_reg = tf.keras.regularizers.l2(0.05)
model = tf.keras.models.Sequential([
tf.keras.layers.Dense(30, activation="relu",
kernel_initializer="he_normal",
kernel_regularizer=l2_reg),
tf.keras.layers.Dense(1, kernel_regularizer=l2_reg)
])
برای مثال یک Function کوچک Random Batch میسازیم و Hyperparameterها، Optimizer، Loss و Metric را تعیین میکنیم:
def random_batch(X, y, batch_size=32):
idx = np.random.randint(len(X), size=batch_size)
return X[idx], y[idx]
n_epochs = 5
batch_size = 32
n_steps = len(X_train) // batch_size
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
loss_fn = tf.keras.losses.mean_squared_error
mean_loss = tf.keras.metrics.Mean(name="mean_loss")
metrics = [tf.keras.metrics.MeanAbsoluteError()]
Loop اصلی:
for epoch in range(1, n_epochs + 1):
print("Epoch {}/{}".format(epoch, n_epochs))
for step in range(1, n_steps + 1):
X_batch, y_batch = random_batch(X_train_scaled, y_train)
with tf.GradientTape() as tape:
y_pred = model(X_batch, training=True)
main_loss = tf.reduce_mean(loss_fn(y_batch, y_pred))
loss = tf.add_n([main_loss] + model.losses)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
mean_loss(loss)
for metric in metrics:
metric(y_batch, y_pred)
for metric in [mean_loss] + metrics:
metric.reset_states()
در هر Epoch روی Batchها Loop میزنیم، درون GradientTape Prediction و Loss را محاسبه میکنیم، Regularization Lossهای موجود در model.losses را اضافه میکنیم، Gradient را فقط نسبت به trainable_variables میگیریم و با apply_gradients() Step را اجرا میکنیم. سپس Metricها بهروزرسانی و در پایان Epoch Reset میشوند.
برای Gradient Clipping میتوان clipnorm یا clipvalue را در Optimizer تنظیم کرد. برای Transformation سفارشی، کافی است Gradientها را قبل از apply_gradients() تغییر دهید. Weight Constraintهای Layer نیز در Loop سفارشی باید بعد از Update بهطور صریح اعمال شوند.
هنگام فراخوانی Model در Training Loop حتماً در صورت نیاز training=True را ارسال کنید؛ در غیر این صورت Layerهایی مانند Dropout یا BatchNormalization رفتار Training را اجرا نمیکنند. اگر Model سفارشی است، این Argument باید تا Layerهای داخلی Propagate شود.
TensorFlow Function و Computation Graph
در TensorFlow 2، Eager Execution حالت عادی است، اما Graph همچنان برای Performance و Portability بسیار مهم است. تبدیل یک Function معمول Python به TensorFlow Function با tf.function ساده است:
def cube(x):
return x ** 3
tf_cube = tf.function(cube)
# روش رایجتر:
@tf.function
def tf_cube(x):
return x ** 3
tf.function() محاسبات Function را تحلیل و Graph معادل تولید میکند. Graph سپس Optimize میشود: Nodeهای استفادهنشده حذف، Expressionهای ثابت ساده و Operationهای مستقل موازی میشوند. برای Functionهای پیچیده، این کار معمولاً نسبت به Python Eager سریعتر است.
با jit_compile=True میتوان XLA را فعال کرد تا چند Operation در Kernel اختصاصی Fuse شوند. این کار هم سرعت را بالا میبرد و هم Temporary Tensorهای بزرگ را حذف میکند. Keras Componentهای سفارشی را معمولاً خودکار به TF Function تبدیل میکند؛ برای XLA کافی است در compile() گزینهٔ مناسب را فعال کنید.
Polymorphism و Cache شدن Graphها
TF Function برای هر ترکیب متمایز Shape و Data Type ورودی Tensor یک Graph میسازد و آن را Cache میکند. دو Scalar Tensor از نوع int32 همان Graph را Reuse میکنند، اما Vector int32 Graph دیگری میسازد. برای Argumentهای Python رفتار متفاوت است: مقدارهای عددی Python میتوانند برای هر Value متمایز Graph جدا بسازند. بنابراین Valueهای Python را برای Hyperparameterهایی با تعداد حالت کم نگه دارید و دادهٔ متغیر را به شکل Tensor ارسال کنید تا از Retracing و مصرف RAM جلوگیری شود.
AutoGraph و Tracing
TensorFlow برای ساخت Graph ابتدا Source Code Function را تحلیل میکند تا ساختارهای Control Flow مانند for، while و if را ثبت کند. این مرحله AutoGraph نام دارد. چون Python برای Statementهایی مانند if و while Magic Method قابل Override ندارد، TensorFlow Source را تبدیل میکند و Control Flow را به Operationهایی مانند tf.while_loop() و tf.cond() تبدیل میکند.
شکل 12-4. تولید Graph با AutoGraph و Tracing
پس از AutoGraph، Function تبدیلشده با Symbolic Tensor فراخوانی میشود. در این مرحله Operationها واقعاً محاسبه نمیشوند؛ هر Operation یک Node به Graph اضافه میکند و Output آن Tensor Symbolic است. این فرایند Tracing نام دارد.
قواعد مهم TF Function
- کد داخل Graph باید تا حد امکان از TensorFlow Operationها استفاده کند. Functionهای NumPy یا Standard Library فقط هنگام Tracing اجرا میشوند و داخل Graph قرار نمیگیرند.
- Random Operation از NumPy فقط هنگام Trace مقدار تولید میکند؛ برای Random Value جدید در هر اجرا از
tf.random استفاده کنید.
- Side Effectهای Python مانند Logging یا افزایش Counter تضمین نمیشوند که در هر فراخوانی رخ دهند، زیرا ممکن است فقط هنگام Tracing اجرا شوند.
tf.py_function() اجازه میدهد Python دلخواه در Graph فراخوانی شود، اما Optimization و Portability را کاهش میدهد و فقط در محیط دارای Python و Libraryهای لازم اجرا میشود.
- Function میتواند Functionهای Python یا TF Functionهای دیگر را فراخوانی کند؛ Operationهای آنها نیز در Graph Capture میشوند.
- Variable یا Object Stateful بهتر است بیرون TF Function یا در
build() ساخته شود. ایجاد Variable داخل TF Function فقط در اولین Call مجاز است.
- Source Code Function باید برای TensorFlow قابل دسترس باشد؛ بعضی محیطهای Interactive یا انتشار فقط فایل
.pyc میتواند AutoGraph را محدود کند.
- برای Loopی که باید داخل Graph Capture شود روی Tensor یا
tf.data.Dataset Iterate کنید؛ مثلاً tf.range() بهجای range().
- هرجا ممکن است Vectorization را به Loop ترجیح دهید.
جمعبندی فصل ۱۲
فصل از Tensorها، Operationها، Variableها و Data Structureهای ویژه شروع کرد؛ سپس Loss، Metric، Activation، Initializer، Regularizer، Constraint، Layer و Model سفارشی را بررسی کرد. بعد با GradientTape و Custom Gradient، کنترل Autodiff و Training Loop را بهدست گرفت و در پایان نشان داد چگونه tf.function، AutoGraph و Tracing کد سفارشی را به Graph بهینه تبدیل میکنند. فصل بعدی روی Load و Preprocess کردن Data با tf.data تمرکز میکند.
تمرینهای فصل ۱۲
- TensorFlow را در یک جمله توصیف کنید و قابلیتهای اصلی آن و چند Library محبوب Deep Learning دیگر را نام ببرید.
- آیا TensorFlow جایگزین Drop-in برای NumPy است؟ مهمترین تفاوتها چیست؟
- خروجی
tf.range(10) و tf.constant(np.arange(10)) را مقایسه کنید.
- شش Data Structure غیر از Tensor معمولی در TensorFlow نام ببرید.
- Loss سفارشی را چه زمانی با Function و چه زمانی با Subclass از
Loss میسازید؟
- برای Metric سفارشی، Function ساده و Subclass از
Metric چه کاربردهای متفاوتی دارند؟
- چه زمانی باید Custom Layer و چه زمانی Custom Model بسازید؟
- چند مورد استفاده که واقعاً به Training Loop سفارشی نیاز دارند بیان کنید.
- آیا Component سفارشی Keras میتواند Python دلخواه داشته باشد یا باید قابل تبدیل به TF Function باشد؟
- قواعد اصلی برای قابلتبدیل بودن Function به TF Function را فهرست کنید.
- Dynamic Keras Model چه زمانی لازم است و چرا همهٔ Modelها را Dynamic نمیسازیم؟
- Layer Normalization سفارشی بسازید که Weightهای Trainable با نامهای α و β داشته باشد و خروجی را به شکل
α ⊗ (X-μ)/(σ+ε)+β نرمال کند؛ سپس نتیجه را با tf.keras.layers.LayerNormalization مقایسه کنید.
- Fashion MNIST را با Training Loop سفارشی Train کنید؛ Loss و Accuracy Training را در هر Iteration و Metricهای Validation را در پایان Epoch نمایش دهید و Optimizer/Learning Rate متفاوت برای Layerهای پایین و بالا آزمایش کنید.
راهحل تمرینها در Notebook همراه فصل در https://homl.info/colab3 موجود است.