使用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)。·build()方法的作用是通过为每个权重调用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)

我们需要注意的是再保存后加载该模型时需要再字典中加载自定义。

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)

需要注意的是由于使用继承类中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个隐藏层的输入,然后将结果传递到重建层,从而产生重构。·然后call()方法计算重建损失(重建与输入之间的均方差),并使用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 

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针对每个可训练变量(不是所有变量!)计算损失的梯度,然后用优化器来执行“梯度下降”步骤。

然后,我们更新平均损失和指标(在当前轮次内),并显示状态栏。

在每个轮次结束时,我们再次显示状态栏以使其看起来完整并打印换行符,然后重置平均损失和指标的状态。

你可能感兴趣的:(使用tensorflow创建基本神经网络模型并训练)