使用tensorflow創(chuàng)建基本神經(jīng)網(wǎng)絡(luò)模型并訓(xùn)練

本文是使用tensorflow對神經(jīng)網(wǎng)絡(luò)模型中激活函數(shù),連接權(quán)重,層,指標(biāo),損失函數(shù)以及優(yōu)化器,學(xué)習(xí)率,包括模型的訓(xùn)練過程經(jīng)行創(chuàng)建。

首先我們先定義一個基本的神經(jīng)網(wǎng)絡(luò)模型,分析相應(yīng)常見的參數(shù)以及訓(xùn)練過程,然后對相關(guān)參數(shù),函數(shù)(如)以及訓(xùn)練過程經(jīng)行自定義創(chuàng)建。

1、創(chuàng)建常見模型

model = keras.models.Sequential([
    keras.layers.Dense(5, activation='selu', kernel_initializer='lecun_normal', 
                       kernel_regularizer= keras.regularizers.l1_l2(.2), kernel_constraint= keras.constraints.MaxNorm(1.)),
    keras.layers.Dropout(.3),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(1)
])
model.compile(loss = keras.losses.mse, optimizer=keras.optimizers.SGD(lr=1e-3), metrics =['mae'])
history = model.fit(X_train_scaled, y_train , epochs=10, validation_data=(X_valid_scaled, y_valid))

2、自定義隱藏層

  keras.layers.Dense(5, activation='selu', kernel_initializer='lecun_normal', 
                   kernel_regularizer= keras.regularizers.l1_l2(.2), kernel_constraint= keras.constraints.MaxNorm(1.)),

從這句代碼中不難看出 在定義隱層時,首先我們應(yīng)該將隱層中的相關(guān)函數(shù)定義了,如激活函數(shù),權(quán)重初始化函數(shù)等。他們的定義如下:

2.1、自定義激活函數(shù),權(quán)重初始化,正則化,約束

2.1.1 方法一 通過函數(shù)定義

def my_softplus(z):
    return tf.math.log(tf.exp(z)+1.)

def my_glort_initializer(shape, dtype=tf.float32):
    stddev = tf.sqrt(2./(shape[0]+shape[1]))
    return tf.random.normal(shape, stddev, dtype= dtype)

def my_l1_regularizer(weight):
    return tf.reduce_sum(tf.abs(0.01*weight))

def my_position_weight(weight):
    return tf.where(weight<0, tf.zeros_like(weight), weight)

layer = keras.layers.Dense(1, activation=my_softplus,
                          kernel_initializer = my_glort_initializer,
                          kernel_regularizer = my_l1_regularizer,
                          kernel_constraint = my_position_weight)

我們需要注意的是,在使用自定義函數(shù)保存模型后將模型取出來時要加載自定義對象名稱映射到字典中。如下所示:

model.save('model_with_custom.h5')
model = keras.models.load_model('model_with_custom.h5', 
                                custom_objects={'my_softplus':my_softplus,
                                'my_glort_initializer':my_glort_initializer,
                                'my_l1_regularizer':my_l1_regularizer,
                                'my_position_weight':my_position_weight})

2.1.2 通過繼承類

如果函數(shù)具有需要與模型一起保存的超參數(shù),你需要繼承適當(dāng)?shù)念悾鏺eras.regularizers.Regularizer,keras.constraints.Constraint,keras.initializers.Initializer或keras.layers.Layer(適用于任何層,包括激活函數(shù))。就像我們?yōu)樽远x損失所做的一樣,這是一個用于1正則化的簡單類,它保存了其factor超參數(shù)(這一次我們不需要調(diào)用父類構(gòu)造函數(shù)或get_config()方法,因為它們不是由父類定義的)。方法如下:

class MyL1Regularizer(keras.regularizers.Regularizer):
    def __init__(self, factor):
        self.factor = factor
    def __call__(self, weights):
        return tf.reduce_sum(tf.abs(self.factor * weights))
    def get_config(self):
        return {"factor": self.factor}

model = keras.models.Sequential([
    keras.layers.Dense(30, activation="selu", kernel_initializer="lecun_normal",
                       input_shape=input_shape),
    keras.layers.Dense(1, activation=my_softplus,
                       kernel_regularizer=MyL1Regularizer(0.01),
                       kernel_constraint=my_positive_weights,
                       kernel_initializer=my_glorot_initializer),
])
model.compile(loss="mse", optimizer="nadam", metrics=["mae"])

需要注意的是由于類中g(shù)et_config()的方法將值包含了進去,所以在加載對象時不需要再填入值。

model.save("my_model_with_many_custom_parts.h5")
model = keras.models.load_model(
    "my_model_with_many_custom_parts.h5",
    custom_objects={
       "MyL1Regularizer": MyL1Regularizer,
       "my_positive_weights": my_positive_weights,
       "my_glorot_initializer": my_glorot_initializer,
       "my_softplus": my_softplus,
    })

2.2 自定義層

方法一:函數(shù)定義

exponential_layer = keras.layers.Lambda(lambda x : tf.exp(x))
model = keras.models.Sequential([
    keras.layers.Dense(30, activation='relu'),
    keras.layers.Dense(1),
    exponential_layer
])
model.compile(loss="mse", optimizer="sgd")
model.fit(X_train_scaled, y_train, epochs=5,
          validation_data=(X_valid_scaled, y_valid))
model.evaluate(X_test_scaled, y_test)

方法二:繼承類

class MyDense(keras.layers.Layer):
    def __init__(self, units, activation=None, **kwargs):
        super().__init__(**kwargs)
        self.units = units
        self.activation = keras.activations.get(activation)
        
    def build(self, batch_input_shape):
        self.kernel = self.add_weight(name='kernel', shape=[batch_input_shape[-1], self.units],
                                     initializer='glorot_normal'),
        self.bias = self.add_weight(name='bias', shape=[self.units], initializer='zero')
        super().build(batch_input_shape)
        
    def call(self, X):
        return self.activation(X @ self.kernel + self*bias)
    
    def compute_output_shape(self, batch_input_shape):
        return tf.TensorShape(batch_input_shape.as_list()[:-1] + [self.units])
    
    def get_config(self):
        base_config = super().get_config()
        return {**base_config, 'units': self.units, 'activation':keras.activations.serialize(self.activation)}


modle = keras.models.Sequential([
    MyDense(30, activation='relu'),
    MyDense(1)
])
model.compile(loss="mse", optimizer="nadam")
model.fit(X_train_scaled, y_train, epochs=2,
          validation_data=(X_valid_scaled, y_valid))
model.evaluate(X_test_scaled, y_test)

構(gòu)造函數(shù)將所有超參數(shù)用作參數(shù)(在此示例中為units和activation),重要的是它還接受**kwargs參數(shù)。它調(diào)用父類構(gòu)造函數(shù),并將其傳遞給kwargs:這負責(zé)處理標(biāo)準(zhǔn)參數(shù),例如input_shape、trainable和name。然后,它將超參數(shù)保存為屬性,使用keras.activations.get()函數(shù)將激活參數(shù)轉(zhuǎn)換為適當(dāng)?shù)募せ詈瘮?shù)(它接受函數(shù)、標(biāo)準(zhǔn)字符串(如"relu"或"selu",或None)。·build()方法的作用是通過為每個權(quán)重調(diào)用add_weight()方法來創(chuàng)建層的變量。首次使用該層時,將調(diào)用build()方法。在這一點上,Keras知道該層的輸入形狀,并將其傳遞給build()方法,這對于創(chuàng)建某些權(quán)重通常是必需的。例如,我們需要知道上一層中神經(jīng)元的數(shù)量,以便創(chuàng)建連接權(quán)重矩陣(即"Kernel"):這對應(yīng)于輸入的最后維度的大小。在build()方法的最后(并且僅在最后),你必須調(diào)用父類的build()方法:這告訴Keras這一層被構(gòu)建了(它只是設(shè)置了self.built=True)。call()方法執(zhí)行所需的操作。在這種情況下,我們計算輸入X與層內(nèi)核的矩陣乘積,添加偏置向量,并對結(jié)果應(yīng)用激活函數(shù),從而獲得層的輸出。

現(xiàn)在可以像其他任何層一樣使用此層,但是當(dāng)然只能使用函數(shù)式和子類API,而不能使用順序API(僅接受具有一個輸入和一個輸出的層)。如果你的層在訓(xùn)練期間和測試期間需要具有不同的行為(例如如果使用Dropout或BatchNormalization層),則必須將訓(xùn)練參數(shù)添加到call()方法并使用此參數(shù)來決定要做什么。讓我們創(chuàng)建一個在訓(xùn)練期間(用于正則化)添加高斯噪聲但在測試期間不執(zhí)行任何操作的層(Keras具有相同功能的層:keras.layers.GaussianNoise):

class ResidualBlock(keras.layers.Layer):
    def __init__(self, n_layers, n_neurous, **kwargs):
        super().__init__(**kwargs)
        self.hidden = [keras.layers.Dense(n_neurous, activation='elu' ,
                                          kernel_initializer='he_normal') for _ in range(n_layers)]
        
    def call(self, inputs):
        Z = inputs
        for layer in self.hidden:
            Z = layer(Z)
        return inputs + Z

3、 定義模型

  model.compile(loss = keras.losses.mse, optimizer=keras.optimizers.SGD(lr=1e-3), metrics =['mae'])

從上述代碼中我們不難看出,我們應(yīng)該首先將,損失函數(shù),優(yōu)化函數(shù)和指標(biāo)函數(shù)定義了

3.1 損失函數(shù)的定義

方法一:函數(shù)定義

def create_huber(threshold = 1.):
  def huber_fn(y_true, y_pred):
      error = y_true - y_pred
      is_small_erroe = tf.abs(error)<threshold
      squared_loss = tf.square(error)/2
      linear_loss = threshold* tf.abs(error) - threshold**2/2
      return tf.where(is_small_erroe, squared_loss, linear_loss)
  return huber_fn



model = keras.models.Sequential([
  keras.layers.Dense(30, activation='selu', kernel_initializer='lecun_normal'),
  keras.layers.Dense(10, activation='selu', kernel_initializer='lecun_normal'),
  keras.layers.Dense(5, activation='selu', kernel_initializer='lecun_normal'),
  keras.layers.Dense(1)
])
huber_fn = create_huber()
model.compile(loss=huber_fn, optimizer="nadam", metrics=["accuracy"])
model.fit(X_train_scaled, y_train, epochs=2,
        validation_data=(X_valid_scaled, y_valid))

我們需要注意的是再保存后加載該模型時需要再字典中加載自定義。

model.save('my_model_with_custom.h5')
model_a = keras.models.load_model('my_model_with_custom.h5', custom_objects={'huber_fn':huber_fn(.2)})
model_a.fit(X_train_scaled, y_train, epochs=2,
          validation_data=(X_valid_scaled, y_valid))

方法二:繼承類

class HuberLoss(keras.losses.Loss):
    def __init__(self, threshold=1.0, **kwargs):
        self.threshold = threshold
        super().__init__(**kwargs)
    
    def call(self, y_true, y_pred):
        error = y_true - y_pred
        threshold = self.threshold
        is_small_erroe = tf.abs(error)<threshold
        squared_loss = tf.square(error)/2
        linear_loss = threshold* tf.abs(error) - threshold**2/2
        return tf.where(is_small_erroe, squared_loss, linear_loss)
    
    def get_config(self):
        base_config = super().get_config()
        return { **base_config, 'threshold':self.threshold}

model = keras.models.Sequential([
    keras.layers.Dense(30, activation='selu', kernel_initializer = 'lecun_normal'),
    keras.layers.Dense(1)
])
model.compile(loss=HuberLoss(2.), optimizer='nadam', metrics='mae')

需要注意的是由于使用繼承類中g(shù)et_config()方法所以我們不需要再定義一次值,保存后讀取如下所示:

model.save('my_model_with_loss.h5')
model = keras.models.load_model('my_model_with_loss.h5', custom_objects={'HuberLoss':HuberLoss})

3.2 自定義指標(biāo)

損失和指標(biāo)在概念上不是一回事:損失(例如交叉熵)被梯度下降用來訓(xùn)練模型,因此它們必須是可微的(至少是在求值的地方),并且梯度在任何地方都不應(yīng)為0。另外,如果人類不容易解釋它們也沒有問題。相反,指標(biāo)(例如準(zhǔn)確率)用于評估模型,它們必須更容易被解釋,并且可以是不可微的或在各處具有0梯度。也就是說,在大多數(shù)情況下,定義一個自定義指標(biāo)函數(shù)與定義一個自定義損失函數(shù)完全相同。實際上,我們甚至可以將之前創(chuàng)建的Huber損失函數(shù)用作指標(biāo)。

如下所示
方法一:同上面定義的損失函數(shù)一樣
model.compile(loss='mse', optimizer="nadam", metrics=[create_huber(2.)])

方法二:繼承類

class HuberMetric(keras.metrics.Metric):
    def __init__(self, threshold=1.0, **kwargs):
        super().__init__(**kwargs)
        self.threshold = threshold
        self.huber_fn = create_huber(threshold)
        self.total = self.add_weight('total', initializer='zeros')
        self.count = self.add_weight('count', initializer='zeros')
        
    def update_state(self, y_true, y_pred, sample_weight=None):
        metrics = self.huber_fn(y_true, y_pred)
        self.total.assign_add(tf.reduce_sum(metrics))
        self.count.assign_add(tf.cast(tf.size(y_pred), tf.float32))
        
    def result(self):
        return self.total/self.count
    
    def get_config(self):
        base_config = super().get_config()
        return {**base_config, 'threshold':self.threshold}


model = keras.models.Sequential([
    keras.layers.Dense(30, activation='selu',kernel_initializer='lecun_normal',
                      input_shape=[8]),
    keras.layers.Dense(1)
])
model.compile(loss='mse', optimizer="nadam", metrics=[HuberMetric(2.0)])

3.3 自定義模型

class ResidualRegressor(keras.models.Model):
    def __init__(self, ouput, **kwargs):
        super().__init__()
        self.hidden1 = keras.layers.Dense(30, activation= 'elu',kernel_initializer='he_normal')
        self.block1 = ResidualBlock(2,30)
        self.block2 = ResidualBlock(2,30)
        self.out = keras.layers.Dense(ouput)
        
    def call(self, inputs):
        print(type(inputs))
        Z = self.hidden1(inputs)
        print('z:',type(Z))
        for _ in range(2):
            Z = self.block1(Z)
        Z = self.block2(Z)
        return self.out(Z)

基于模型內(nèi)部的損失和指標(biāo)

    def __init__(self, output_dim, **kwargs):
        super().__init__(**kwargs)
        self.hidden = [keras.layers.Dense(30, activation="selu",
                                          kernel_initializer="lecun_normal")
                       for _ in range(5)]
        self.out = keras.layers.Dense(output_dim)
        self.reconstruct = keras.layers.Dense(8) # workaround for TF issue #46858
        self.reconstruction_mean = keras.metrics.Mean(name="reconstruction_error")


    def call(self, inputs, training=None):
        Z = inputs
        for layer in self.hidden:
            Z = layer(Z)
        reconstruction = self.reconstruct(Z)
        recon_loss = tf.reduce_mean(tf.square(reconstruction - inputs))
        self.add_loss(0.05 * recon_loss)
        if training:
            result = self.reconstruction_mean(recon_loss)
            self.add_metric(result)
        return self.out(Z)

構(gòu)造函數(shù)創(chuàng)建具有5個密集隱藏層和一個密集輸出層的DNN。
build()方法創(chuàng)建一個額外的密集層,該層用于重建模型的輸入。必須在此處創(chuàng)建它,因為它的單元數(shù)必須等于輸入數(shù),并且在調(diào)用build()方法之前,此數(shù)是未知的。
call()方法處理所有5個隱藏層的輸入,然后將結(jié)果傳遞到重建層,從而產(chǎn)生重構(gòu)。·然后call()方法計算重建損失(重建與輸入之間的均方差),并使用add_loss()方法將其添加到模型的損失列表中。
請注意,我們通過將其乘以0.05(這是你可以調(diào)整的超參數(shù))按比例縮小了重建。這確保了重建損失不會在主要損失中占大部分。
最后,call()方法將隱藏層的輸出傳遞到輸出層并返回其輸出。

4、訓(xùn)練循環(huán)

1、創(chuàng)建一個隨機抽取實例的函數(shù),訓(xùn)練時模型時按照小批量送入

def random_batch(X, y ,batch_size=32):
    idx = np.random.randint(len(X),size = batch_size)
    return X[idx], y[idx]

2、編寫進度條和損失值

def progress_bar(iteration, total, size=30):
    running = iteration < total
    c = ">" if running else "="
    #縮放到當(dāng)前迭代次數(shù)
    p = (size - 1) * iteration // total
    #fmt = "{{:-{}d}}/{{}} [{{}}]".format(len(str(total)))
    fmt = '{}/{} [{}]'
    params = [iteration, total, "=" * p + c + "." * (size - p - 1)]
    return fmt.format(*params)


def print_status_bar(iteration, total, loss, metrics=None):
    metrics = '-'.join(['{}: {:.4f}'.format(m.name, m.result()) for m in [loss] + (metrics or [])])
    end = '' if iteration <total else '\n'
    print('\r {}/{} -'.format(progress_bar(iteration, total) ,metrics), end=end)

3、設(shè)置基本參數(shù)

n_epochs = 5
batch_size = 32
n_steps = len(X_train) // batch_size
optimizer = keras.optimizers.Nadam(lr=0.01)
loss_fn = keras.losses.mean_squared_error
mean_loss = keras.metrics.Mean()
metrics = [keras.metrics.MeanAbsoluteError()]

4、

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)
            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))
        
        for variable in model.variables:
            if variable.constraint is not None:
                variable.assign(variable.constraint(variable))
                
        mean_loss(loss)
        for metric in metrics:
            metric(y_batch, y_pred)
        print_status_bar(step * batch_size, len(y_train), mean_loss, metrics)
    print_status_bar(len(y_train), len(y_train), mean_loss, metrics)
    for metric in [mean_loss] + metrics:
        metric.reset_states()

我們創(chuàng)建了兩個嵌套循環(huán):一個用于輪次,另一個用于輪次內(nèi)的批處理。·然后從訓(xùn)練集中抽取一個隨機批次。

在tf.GradientTape()塊中,我們對一個批次進行了預(yù)測(使用模型作為函數(shù)),并計算了損失:它等于主損失加其他損失(在此模型中,每層都有一個正則化損失)。由于mean_squared_error()函數(shù)每個實例返回一個損失,因此我們使用tf.reduce_mean()計算批次中的均值(如果要對每個實例應(yīng)用不同的權(quán)重,則可以在這里進行操作)。

正則化損失已經(jīng)歸約到單個標(biāo)量,因此我們只需要對它們進行求和(使用tf.add_n()即可對具有相同形狀和數(shù)據(jù)類型的多個張量求和)。

接下來,我們要求tape針對每個可訓(xùn)練變量(不是所有變量!)計算損失的梯度,然后用優(yōu)化器來執(zhí)行“梯度下降”步驟。

然后,我們更新平均損失和指標(biāo)(在當(dāng)前輪次內(nèi)),并顯示狀態(tài)欄。

在每個輪次結(jié)束時,我們再次顯示狀態(tài)欄以使其看起來完整并打印換行符,然后重置平均損失和指標(biāo)的狀態(tài)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容