使用tensorflow創建基本神經網絡模型并訓練

本文是使用tensorflow對神經網絡模型中激活函數,連接權重,層,指標,損失函數以及優化器,學習率,包括模型的訓練過程經行創建。

首先我們先定義一個基本的神經網絡模型,分析相應常見的參數以及訓練過程,然后對相關參數,函數(如)以及訓練過程經行自定義創建。

1、創建常見模型

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.)),

從這句代碼中不難看出 在定義隱層時,首先我們應該將隱層中的相關函數定義了,如激活函數,權重初始化函數等。他們的定義如下:

2.1、自定義激活函數,權重初始化,正則化,約束

2.1.1 方法一 通過函數定義

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)

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

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 通過繼承類

如果函數具有需要與模型一起保存的超參數,你需要繼承適當的類,例如keras.regularizers.Regularizer,keras.constraints.Constraint,keras.initializers.Initializer或keras.layers.Layer(適用于任何層,包括激活函數)。就像我們為自定義損失所做的一樣,這是一個用于1正則化的簡單類,它保存了其factor超參數(這一次我們不需要調用父類構造函數或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"])

需要注意的是由于類中get_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 自定義層

方法一:函數定義

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)

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

現在可以像其他任何層一樣使用此層,但是當然只能使用函數式和子類API,而不能使用順序API(僅接受具有一個輸入和一個輸出的層)。如果你的層在訓練期間和測試期間需要具有不同的行為(例如如果使用Dropout或BatchNormalization層),則必須將訓練參數添加到call()方法并使用此參數來決定要做什么。讓我們創建一個在訓練期間(用于正則化)添加高斯噪聲但在測試期間不執行任何操作的層(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'])

從上述代碼中我們不難看出,我們應該首先將,損失函數,優化函數和指標函數定義了

3.1 損失函數的定義

方法一:函數定義

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')

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

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

3.2 自定義指標

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

如下所示
方法一:同上面定義的損失函數一樣
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)

基于模型內部的損失和指標

    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)

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

4、訓練循環

1、創建一個隨機抽取實例的函數,訓練時模型時按照小批量送入

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 "="
    #縮放到當前迭代次數
    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、設置基本參數

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()

我們創建了兩個嵌套循環:一個用于輪次,另一個用于輪次內的批處理。·然后從訓練集中抽取一個隨機批次。

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

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

接下來,我們要求tape針對每個可訓練變量(不是所有變量?。┯嬎銚p失的梯度,然后用優化器來執行“梯度下降”步驟。

然后,我們更新平均損失和指標(在當前輪次內),并顯示狀態欄。

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容