The Keras Functional API: چند مثال ساده

API تابعی Keras: پنج مثال ساده

ساخت مدل در Keras، کار ساده و آسانی است. چنانچه این مقاله را میخوانید احتمالا با مدل ترتیبی (Sequential) و استفاده از لایه های مختلف جهت تشکیل مدل های ساده آشنا هستید. اما اگر بخواهید کار پیچیده تری انجام دهید، چی؟

Functional API یا API تابعی درواقع بهترین گزینه برای ساخت مدل های پیچیده به حساب می آید، چراکه قادر به انجام انواع اموری است که با مدل ترتیبی صرف، امکان پذیر نیستند. زمانی که به طراحی معماری هایی می پردازید که دارای ورودی ها و خروجی های متعدد است یا مدل های که دارای لایه های به اشتراک گذاشته شده (Shared) هستند، نیاز دارید از Functional API برای ساخت مدل خود استفاده کنید.

اما استفاده از این API به آسانی مدل ترتیبی نیست و نیاز به داشتن مهارت است. به نظر من نشستن و کد زدن چند مثال ساده که برای شروع مناسب است، با استفاده از API تابعی بسیار مفیداست.

پنج مورد از این مثال ها را در این پست مرور خواهیم کرد. فرض من بر این است که شما یک پیش زمینه در کار با Keras و در شرایط ایده آل، کار با API تابعی، از قبل دارید.

ایجاد داده نمونه (Sample Data)

نخست، نیاز داریم کمی داده را برای استفاده در مثال ها فراهم کنیم و برای این کار از numpy استفاده میکنیم. من، معمولا استفاده از pandas را برای این نوع کارها ترجیح می دهم، اما دیده شده گاها که pandas DataFrames، به خوبی با Keras مجتمع نمی شود و خطاهای عجیب و غریبی را دریافت می کنید.

دو مجموعه داده ایجاد خواهیم کرد: یک مجموعه داده آموزش و یک مجموعه داده آزمون. ما هچنین به طور معمول یک مجموعه اعتبارسنجی متقابل (Cross Validation) ایجاد میکنیم اما برای مثال های آموزشی داشتن یک مجموعه داده آموزش کافی است.

داده هایی که ما آنها کار میکنیم، از سه متغیر مستقل \(x_1, x_2 \And x_3\) و دو متغیر وابسته \(y_{classifier} \And y_{cts}\)  برخوردار خواهند بود که cts مخفف continuous (پیوسته) است. ما، دو متغیر وابسته می خواهیم، چون تعدادی مدل می سازیم که برخی، نتایج گسسته (مانند رگرسیون لجستیک) و بعضی، نتایج پیوسته (همچون رگرسیون خطی) را پیش بینی می کنند.

مقدار متغیر وابسته \(y_{classifier}\) خود را زمانی که \(x_1 + x_2 + \frac{x_3}{3} + \epsilon > 1\) به شرط \(\epsilon \sim N(0,1)\) برابر 1 تعیین می کنیم در غیر این صورت، آن را برابر با صفر درنظر می گیریم.

مقدار متغیر مستقل پیوسته \(y_cts\) خود را \(y_{cts} = x_1 + x_2 + \frac{x_3}{3} + \epsilon\) تعیین می کنیم. هر دو متغیر، به این ترتیب، مشابه بنظر می رسند و در پایان، داده ای خواهیم داشت که به لحاظ خطی قابل تفکیک نیست، ولی برای آموزش مدل ها به اندازه کافی مناسب است.

In [1]:
import numpy as np
 
n_row = 1000
x1 = np.random.randn(n_row)
x2 = np.random.randn(n_row)
x3 = np.random.randn(n_row)
y_classifier = np.array([1 if (x1[i] + x2[i] + (x3[i])/3 + np.random.randn(1) > 1) else 0 for i in range(n_row)])
y_cts = x1 + x2 + x3/3 + np.random.randn(n_row)
dat = np.array([x1, x2, x3]).transpose()
In [2]:
# Take a look at the data
import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=(12,5))
plt.scatter(dat[:,0],dat[:,1], c=y_classifier)
Out[2]:
<matplotlib.collections.PathCollection at 0x28e63a520b8>
In [3]:
plt.figure(figsize=(12,5))
plt.scatter(dat[:,0],dat[:,1], c=y_cts)
Out[3]:
<matplotlib.collections.PathCollection at 0x28e63ac18d0>

تقسیم داده های خود به یک مجموعه آموزش و یک مجموعه آزمون، به طوری که عملکرد مدل را در زمان آزمایش آن بتوانیم بررسی نماییم، آخرین کاری است که قصد انجام آن را داریم:

In [4]:
from sklearn.model_selection import train_test_split

# Split data into test and train
dat_train, dat_test, \
y_classifier_train, y_classifier_test, \
y_cts_train, y_cts_test = train_test_split(dat, y_classifier, y_cts, test_size=.2)
In [5]:
# setup
from keras.models import Input, Model
from keras.layers import Dense
F:\Anaconda3-5\lib\site-packages\h5py\__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  from ._conv import register_converters as _register_converters
Using TensorFlow backend.

مثال 1- رگرسیون لجستیک

مثال اول ما، ساخت رگرسیون لجستیک با استفاده از مدل تابعی Keras است. این کار، زمانی که برخی نکات کلیدی را بدانید، نسبتا آسان و ساده است.

لایه ورودی باید دارای شکل \(\left( p, \right)\) باشد که p، تعداد ستون های ماتریس آموزش یا به عبارتی دیگر تعداد ویژگی های شماست. در این مثال ما سه ویژگی داریم \(x_1, x_2 \And x_3\)، لذا شکل آن را به صورت \(\left( 3, \right)\) تعیین می کنیم.

In [6]:
input = Input(shape=(3,), name="input")
output = Dense(1, activation='sigmoid', name='dense')(input)
logistic_model = Model(inputs=input, outputs=output)
In [7]:
# Compile the model 
logistic_model.compile(optimizer='sgd',
                       loss = 'binary_crossentropy',
                       metrics=['accuracy'])
In [12]:
from time import time
from keras.callbacks import Callback, EarlyStopping, ModelCheckpoint
from keras.callbacks import TensorBoard

# Callbacks monitor the validation loss
# Save the model weights each time the validation loss has improved
callbacks = [
             TensorBoard(log_dir="logs/logistic_model_{}".format(time()), write_grads=False, histogram_freq=32),
             EarlyStopping('val_loss', patience=2), 
             #ModelCheckpoint('logistic_model_weights.h5', save_best_only=True)
            ]

# Fit on training data
logistic_model.optimizer.lr = 0.001
# logistic_model.fit(x=dat_train, y=y_classifier_train, epochs = 1, verbose=2,
#                    validation_data = (dat_test, y_classifier_test))
logistic_history = logistic_model.fit(x=dat_train, y=y_classifier_train, epochs = 500, verbose=0,
                   validation_data = (dat_test, y_classifier_test), callbacks=callbacks)
# logistic_model.fit(x=dat_train, y=y_classifier_train, epochs = 1, verbose=2,
#                    validation_data = (dat_test, y_classifier_test))
Train on 800 samples, validate on 200 samples
Epoch 1/1
 - 0s - loss: 0.3676 - acc: 0.8300 - val_loss: 0.3643 - val_acc: 0.8250

چنانچه نگاهی به اوزان مدل بیندازید، درمی یابید که مدل لجستیک ما، تقریبا مقدار درستی در رابطه با آنچه آن ها می بایست باشند، دارد:

In [13]:
# true weights: 1,1,1/3
logistic_model.get_weights()
Out[13]:
[array([[1.3910129 ],
        [1.4967757 ],
        [0.54146224]], dtype=float32), array([-1.319879], dtype=float32)]

مثال 2- رگرسیون خطی

یک رگرسیون خطی ساده را در این قسمت می سازیم که در آن، \(y_cts\) به عنوان متغیر وابسته و x1، x2 و x3 متغیرهای مستقل هستند که در درون آرایه dat ذخیره می شوند. مشاهده خواهید کرد که کد رگرسیون خطی، بسیار شبیه رگرسیون لجستیک بالاست و فقط یک تابع فعال سازی خطی را به جای تابع سیگموئید(sigmoid) مورد استفاده قرار می دهیم.

In [17]:
inputs = Input(shape=(3,))
output = Dense(1, activation='linear')(inputs)
linear_model = Model(inputs, output)
 
linear_model.compile(optimizer='sgd', loss='mse')
callbacks = [
             TensorBoard(log_dir="logs/linear_model_{}".format(time())),
             EarlyStopping('loss', patience=2), 
             #ModelCheckpoint('linear_model_weights.h5', save_best_only=True)
            ]
linear_history = linear_model.fit(x=dat_test, y=y_cts_test, epochs=50, verbose=0, callbacks=callbacks)
# linear_model.fit(x=dat_test, y=y_cts_test, epochs=1, verbose=1)

توجه به بعضی از خطاها و بررسی وجود یک الگو، یک راه پی بردن به این امر است که آیا مدل ما، برازش خوبی دارد.

In [18]:
preds = linear_model.predict(dat_test)
plt.scatter(x=dat_test[:,0], y= np.array(preds) - np.array(y_cts_test).reshape(200,1))
Out[18]:
<matplotlib.collections.PathCollection at 0x28e701401d0>

مثال 3- شبکه عصبی ساده

نحوه پیاده سازی و اجرای یک شبکه عصبی ساده ی دارای یک لایه پنهان در قسمت زیر ارائه می گردد. این امر نیز نسبتا ساده است.

In [19]:
inputs = Input(shape=(3,))
x = Dense(50, activation='relu')(inputs)
output = Dense(1, activation = 'sigmoid')(x)
n_net = Model(inputs, output )

n_net.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

callbacks = [
             TensorBoard(log_dir="logs/n_net_{}".format(time())),
             EarlyStopping('loss', patience=2), 
             #ModelCheckpoint('linear_model_weights.h5', save_best_only=True)
            ]

n_net.fit(x=dat_train, y=y_classifier_train, epochs=50,
verbose=0, validation_data=(dat_test, y_classifier_test), callbacks=callbacks)
Out[19]:
<keras.callbacks.History at 0x28e701bb0b8>

مثال 4- شبکه عصبی عمیق

یک شبکه عصبی ساده ی دارای تعداد زیاد لایه های پنهان را در قسمت زیر آموزش می دهیم. همچنین از Dropout برای کاهش بیش برازش استفاده می کنیم. بکارگیری حلقه های for و دستورهای if در زمان استفاده از API تابعی به ویژه برای مدل های پیچیده می تواند مفید فایده واقع شود.

In [23]:
from keras.layers import Dropout
# specify how many hidden layers to add (min 1)
n_layers = 5

inputs = Input(shape=(3,))
x = Dense(200, activation='relu')(inputs)
x = Dropout(0.4)(x)
for layer in range(n_layers - 1):
    x = Dense(200, activation='relu')(x)
    x = Dropout(0.3)(x)
output = Dense(1, activation='sigmoid')(x)
deep_n_net = Model(inputs, output)

deep_n_net.compile(optimizer = 'adam', loss= 'binary_crossentropy', metrics=['accuracy'])
callbacks = [
             TensorBoard(log_dir="logs/deep_n_net_{}".format(time())),
             #EarlyStopping('val_loss', patience=2), 
             #ModelCheckpoint('linear_model_weights.h5', save_best_only=True)
            ]
deep_n_net.fit(dat_train, y_classifier_train, epochs = 50, verbose=0,
validation_data = (dat_test, y_classifier_test), callbacks=callbacks)
# deep_n_net.fit(dat_train, y_classifier_train, epochs = 1, verbose=1,
# validation_data = (dat_test, y_classifier_test))
Out[23]:
<keras.callbacks.History at 0x28e71acbd68>

این مدل به مثابه شبکه عصبی قبلی عمل میکند. البته به بهینه سازی پارامترهای شبکه مانند مقدار Dropout یا تعداد لایه های شبکه میتوان به مدل بهتری رسید.

مثال 5- شبکه عصبی عمیق به اضافه فراداده (Metadata)

یک مورد کاربرد API تابعی، زمانی است که منابع داده متعددی دارید و می خواهید آن ها را در یک مدل جمع سازید.
مثلا چنانچه کار شما، طبقه بندی عکس باشد، شما می توانید مدل ترتیبی را برای ساخت یک شبکه عصبی کانوولوشن بکار برید که روی عکس ها اجرا می شود. چنانچه تصمیم دارید API تابعی را به جای مدل ترتیبی مورد استفاده قرار دهید، شما می توانید فراداده های عکس خود را در مدل تان جای دهید که شاید شامل اندازه آن، زمان گرفته شدن عکس یا مکان برچسب خورده ی آن باشد.
این تکنیک را با ترکیب منابع متعدد داده با استفاده از ایجاد دو بردار «فراداده» برای استفاده به همراه دادگان خود نشان می دهیم. این کار در صورت مفید بودن خیلی خوبه، از این رو برای مثال مقدار \(y_classifier\) را به اضافه نویز تصادفی به عنوان فراداده در نظر می گیریم.
معماری مدل ما در قسمت زیر، از دولایه پنهان به همراه فراداده ای تشکیل می شود که پس از مورد نخست آن ها اضافه می شود.

In [25]:
metadata_1 = y_classifier + np.random.gumbel(scale = 0.6, size = n_row)
metadata_2 = y_classifier - np.random.laplace(scale = 0.5, size = n_row)
metadata = np.array([metadata_1,metadata_2]).T
 
# Split data into test and train
dat_train, dat_test, \
y_classifier_train, y_classifier_test, \
y_cts_train, y_cts_test, \
metadata_train, metadata_test = train_test_split(dat, y_classifier, y_cts, metadata, test_size=.2)

ما، به لایه Concatenate برای ادغام دو منبع داده نیاز داریم.
حال بیایید این مدل را بسازیم. توجه داشته باشید که ما، دو لایه داریم: یک لایه برای داده های اولیه و دیگری، برای فراداده است. هر دو دادگان از یک لایه dense و یک لایه dropout عبور می کنند. آن ها، آنگاه، با لایه Concatenate ترکیب می شوند و پیش از آنکه یک لایه متراکم نهایی، مقادیر خروجی را نتیجه دهد، از یک لایه متراکم و حذف تصادفی دیگر گذر می کنند.

In [27]:
from keras.layers import concatenate

input_dat = Input(shape=(3,)) # for the three columns of dat_train
n_net_layer = Dense(50, activation='relu') # first dense layer
x1 = n_net_layer(input_dat)
x1 = Dropout(0.5)(x1)
 
input_metadata = Input(shape=(2,))
x2 = Dense(25, activation= 'relu')(input_metadata)
x2 = Dropout(0.3)(x2)
 
con = concatenate(inputs = [x1,x2] ) # merge in metadata
x3 = Dense(50)(con)
x3 = Dropout(0.3)(x3)
output = Dense(1, activation='sigmoid')(x3)
meta_n_net = Model(inputs=[input_dat, input_metadata], outputs=output)
 
meta_n_net.compile(optimizer='sgd', loss='binary_crossentropy', metrics=['accuracy'])

callbacks = [
             TensorBoard(log_dir="logs/meta_n_net_{}".format(time())),
             #EarlyStopping('val_loss', patience=2), 
             #ModelCheckpoint('linear_model_weights.h5', save_best_only=True)
            ]
meta_n_net.fit(x=[dat_train, metadata_train], y=y_classifier_train, epochs=50, verbose=0,
validation_data=([dat_test, metadata_test], y_classifier_test), callbacks=callbacks)
Out[27]:
<keras.callbacks.History at 0x28e79513e10>

اکنون امید آن است که دانش کافی در رابطه با قابلیت API تابعی در رمزگشایی مدل های پیچیده داشته باشید. API تابعی، کاربردی بسیار بیشتر از آنچه که در این پست توصیف شد، دارد.
خیلی خوشحال میشم سوالات، نظرات یا مدلی که میسازید را در قسمت کامنت ها با من در میان بگذارید.

آیا سوالی دارید؟

سوال خود را در بخش نظرات بپرسید، سعی میکنم به آنها جواب مناسب بدم.

دیدگاهتان را بنویسید