如何在TensorFlow中添加正则化?

96

我发现在很多使用TensorFlow实现的神经网络代码中,正则化项通常是通过手动向损失值添加一个额外的项来实现的。

我的问题是:

  1. 是否有比手动实现更优雅或者推荐的正则化方法?

  2. 我还发现get_variable有一个参数regularizer。它应该如何使用?根据我的观察,如果我们传入一个正则化器(例如tf.contrib.layers.l2_regularizer),则会计算并添加一个表示正则化项的张量到一个名为tf.GraphKeys.REGULARIZATION_LOSSES的图集合中。那个集合将自动被TensorFlow使用(例如,被优化器在训练时使用)吗?还是我应该自己使用那个集合?


1
只是为了非常明确,这样做的方式是 S = tf.get_variable(name='S', regularizer=tf.contrib.layers.l2_regularizer ) 吗? - Charlie Parker
@Pinocchio,你解决了吗? - Euler_Salter
2
@Euler_Salter 对不起,我已经记不清了!我不再使用 Tensorflow 了! - Charlie Parker
10个回答

70

正如您在第二点所说,使用regularizer参数是推荐的方法。您可以在get_variable中使用它,或者在您的variable_scope中设置一次并对所有变量进行正则化。

损失被收集在图中,您需要手动将它们添加到您的代价函数中,就像这样。

  reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
  reg_constant = 0.01  # Choose an appropriate one.
  loss = my_normal_loss + reg_constant * sum(reg_losses)

2
谢谢,我一直以为TensorFlow会有更智能的处理正则化项的方法,看来并不是:P - Lifu Huang
14
顺便提两个建议,如果我说错了请纠正我。第一,我认为 reg_constant 可能不是必须的,因为 TensorFlow 中的正则化器在构造函数中有一个 scale 参数,可以更精细地控制正则项的影响。第二,使用 tf.add_n 比使用 sum 稍微好一些,我认为使用 sum 可能会创建许多张量来存储中间结果。 - Lifu Huang
3
似乎应该使用 tf.reduce_sum 而不是 sum - ComputerScientist
1
@Euler_Salter - tf.get_variable(...) 显然是创建变量的首选方式,尽管 tf.Variable(...) 仍然受到支持。 - Scott Smith
1
@Euler_Salter - 这个正则化方法非常不错:https://greydanus.github.io/2016/09/05/regularization/至于 tf.Variable()tf.get_variable(),这篇文章讲得很清楚:https://dev59.com/-VoU5IYBdhLWcg3wzZA1#37099025 - Scott Smith
显示剩余6条评论

47

我对现有答案的一些方面不是很清楚,因此这里提供一个逐步指南:

  1. 定义一个正则化器。这是设置正则化常数的地方,例如:

  2. regularizer = tf.contrib.layers.l2_regularizer(scale=0.1)
    
  3. 通过以下方式创建变量:

        weights = tf.get_variable(
            name="weights",
            regularizer=regularizer,
            ...
        )
    

    同样地,可以通过常规的 weights = tf.Variable(...) 构造函数创建变量,然后再使用 tf.add_to_collection(tf.GraphKeys.REGULARIZATION_LOSSES, weights)

  4. 定义一些 loss 项并添加正则化项:

  5. reg_variables = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    reg_term = tf.contrib.layers.apply_regularization(regularizer, reg_variables)
    loss += reg_term
    

    注意:看起来tf.contrib.layers.apply_regularization被实现为AddN,因此与sum(reg_variables)更或多或少等效。


10
我认为你在步骤和第三步中都应用了正则化器两次。如果在创建变量时已经指定了正则化器,那么apply_regularization就不是必需的。 - interjay
2
@interjay,请制作一个例子,所有这些答案都超级不清楚!这是因为总有至少一个人在下面写评论说上面的答案有问题。 - Euler_Salter
1
@interjay 我相当确定上次测试时需要同时进行这两个操作。不过我不确定现在是否有所改变。 - bluenote10
1
不,这没有意义,因为你不需要将相同的正则化器传递给两个函数。文档(和名称)清楚地表明REGULARIZATION_LOSSES是从正则化器返回的总损失,所以你实际上是在调用regularizer(regularizer(weight)) - interjay
1
我认为这里的混淆源于“等效”的部分。他描述了两种不同的方法,你选择其中一种,而不是一个涉及两次应用正则化的方法。 - gcp

29

我会提供一个简单正确的答案,因为我没有找到一个。你只需要两个简单的步骤,其余的由TensorFlow魔法完成:

  1. Add regularizers when creating variables or layers:

    tf.layers.dense(x, kernel_regularizer=tf.contrib.layers.l2_regularizer(0.001))
    # or
    tf.get_variable('a', regularizer=tf.contrib.layers.l2_regularizer(0.001))
    
  2. Add the regularization term when defining loss:

    loss = ordinary_loss + tf.losses.get_regularization_loss()
    

如果我通过regularizer = tf.contrib.layers.l2_regularizer(0.001)创建一个正则化操作符,我可以将其传递给多个层的初始化吗?还是我需要为每个层创建单独的正则化器,例如regularizer1=tf.contrib.layers.l2_regularizer(0.001),regularizer2 = ................. regularizer3 = ......等等? - figs_and_nuts
@Nitin 你可以使用相同的正则化器。它只是一个Python函数,将损失应用于权重作为其参数。 - alyaxey
1
这看起来是最优雅的解决方案,但它真的有效吗?这与例如 reg_variables = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES) 和 reg_term = tf.contrib.layers.apply_regularization(regularizer, reg_variables) 有什么不同呢?loss += reg_term - GeorgeOfTheRF
1
我想提一下,tf.contrib.layers.fully_connected 可以替代 tf.layers.dense,并且还可以添加更多的功能。请参考以下链接: this, this, 和 this. - O. Salah

16

使用 contrib.learn 库进行此操作的另一种选择是根据 Tensorflow 网站上的Deep MNIST 教程,按照以下方式进行。首先,假设您已经导入了相关库(例如 import tensorflow.contrib.layers as layers),则可以在单独的方法中定义网络:

def easier_network(x, reg):
    """ A network based on tf.contrib.learn, with input `x`. """
    with tf.variable_scope('EasyNet'):
        out = layers.flatten(x)
        out = layers.fully_connected(out, 
                num_outputs=200,
                weights_initializer = layers.xavier_initializer(uniform=True),
                weights_regularizer = layers.l2_regularizer(scale=reg),
                activation_fn = tf.nn.tanh)
        out = layers.fully_connected(out, 
                num_outputs=200,
                weights_initializer = layers.xavier_initializer(uniform=True),
                weights_regularizer = layers.l2_regularizer(scale=reg),
                activation_fn = tf.nn.tanh)
        out = layers.fully_connected(out, 
                num_outputs=10, # Because there are ten digits!
                weights_initializer = layers.xavier_initializer(uniform=True),
                weights_regularizer = layers.l2_regularizer(scale=reg),
                activation_fn = None)
        return out 

然后,在主方法中,您可以使用以下代码片段:

def main(_):
    mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)
    x = tf.placeholder(tf.float32, [None, 784])
    y_ = tf.placeholder(tf.float32, [None, 10])

    # Make a network with regularization
    y_conv = easier_network(x, FLAGS.regu)
    weights = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'EasyNet') 
    print("")
    for w in weights:
        shp = w.get_shape().as_list()
        print("- {} shape:{} size:{}".format(w.name, shp, np.prod(shp)))
    print("")
    reg_ws = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES, 'EasyNet')
    for w in reg_ws:
        shp = w.get_shape().as_list()
        print("- {} shape:{} size:{}".format(w.name, shp, np.prod(shp)))
    print("")

    # Make the loss function `loss_fn` with regularization.
    cross_entropy = tf.reduce_mean(
        tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
    loss_fn = cross_entropy + tf.reduce_sum(reg_ws)
    train_step = tf.train.AdamOptimizer(1e-4).minimize(loss_fn)
要使此功能正常工作,您需要按照我早先提供的MNIST教程并导入相关库,但这是一个很好的练习,可以学习TensorFlow并且很容易看到正则化如何影响输出。如果您将正则化作为参数应用,您可以看到以下内容:
- EasyNet/fully_connected/weights:0 shape:[784, 200] size:156800
- EasyNet/fully_connected/biases:0 shape:[200] size:200
- EasyNet/fully_connected_1/weights:0 shape:[200, 200] size:40000
- EasyNet/fully_connected_1/biases:0 shape:[200] size:200
- EasyNet/fully_connected_2/weights:0 shape:[200, 10] size:2000
- EasyNet/fully_connected_2/biases:0 shape:[10] size:10

- EasyNet/fully_connected/kernel/Regularizer/l2_regularizer:0 shape:[] size:1.0
- EasyNet/fully_connected_1/kernel/Regularizer/l2_regularizer:0 shape:[] size:1.0
- EasyNet/fully_connected_2/kernel/Regularizer/l2_regularizer:0 shape:[] size:1.0

请注意,正则化部分会根据可用项给出三个条目。

当使用正则化参数为0、0.0001、0.01和1.0时,我得到的测试准确率分别为0.9468、0.9476、0.9183和0.1135,这表明高正则化项的危险性。


2
非常详细的例子。 - stackoverflowuser2010

9

如果还有人在寻找,我想补充一下,在tf.keras中,您可以通过将它们作为参数传递给您的层来添加权重正则化。以下是从Tensorflow Keras教程网站整理出的添加L2正则化的示例:

model = keras.models.Sequential([
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
                       activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
                       activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

据我所知,使用这种方法不需要手动添加正则化损失。
参考:https://www.tensorflow.org/tutorials/keras/overfit_and_underfit#add_weight_regularization

4

我测试了tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)tf.losses.get_regularization_loss(),发现它们在图中使用一个l2_regularizer返回相同的值。通过观察值的数量,我猜测reg_constant已经通过设置tf.contrib.layers.l2_regularizer的参数对该值产生了影响。


3
如果您有CNN,可以执行以下操作:
在您的模型函数中:
conv = tf.layers.conv2d(inputs=input_layer,
                        filters=32,
                        kernel_size=[3, 3],
                        kernel_initializer='xavier',
                        kernel_regularizer=tf.contrib.layers.l2_regularizer(1e-5),
                        padding="same",
                        activation=None) 
...

在您的损失函数中:
onehot_labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=num_classes)
loss = tf.losses.softmax_cross_entropy(onehot_labels=onehot_labels, logits=logits)
regularization_losses = tf.losses.get_regularization_losses()
loss = tf.add_n([loss] + regularization_losses)

2
cross_entropy = tf.losses.softmax_cross_entropy(
  logits=logits, onehot_labels=labels)

l2_loss = weight_decay * tf.add_n(
     [tf.nn.l2_loss(tf.cast(v, tf.float32)) for v in tf.trainable_variables()])

loss = cross_entropy + l2_loss

1
谢谢您提供这段代码片段,它可能会提供一些有限的、立即的帮助。一个适当的解释将极大地提高它的长期价值,因为它可以展示为什么这是一个好的问题解决方案,并且使它对未来阅读者具有其他相似问题更有用。请编辑您的答案,添加一些解释,包括您所做的假设。 - Maximilian Peters

1

有些答案让我更加困惑。这里我提供两种方法来使其更清晰。

#1.adding all regs by hand
var1 = tf.get_variable(name='v1',shape=[1],dtype=tf.float32)
var2 = tf.Variable(name='v2',initial_value=1.0,dtype=tf.float32)
regularizer = tf.contrib.layers.l1_regularizer(0.1)
reg_term = tf.contrib.layers.apply_regularization(regularizer,[var1,var2])
#here reg_term is a scalar

#2.auto added and read,but using get_variable
with tf.variable_scope('x',
        regularizer=tf.contrib.layers.l2_regularizer(0.1)):
    var1 = tf.get_variable(name='v1',shape=[1],dtype=tf.float32)
    var2 = tf.get_variable(name='v2',shape=[1],dtype=tf.float32)
reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
#here reg_losses is a list,should be summed 

然后,它可以被加入到总损失中。

1

tf.GraphKeys.REGULARIZATION_LOSSES 不会自动添加,但是有一种简单的方法可以添加它们:

reg_loss = tf.losses.get_regularization_loss()
total_loss = loss + reg_loss

tf.losses.get_regularization_loss()使用tf.add_n按元素逐个求和tf.GraphKeys.REGULARIZATION_LOSSES的条目。 tf.GraphKeys.REGULARIZATION_LOSSES通常是一个标量列表,使用正则化函数计算。它获取调用tf.get_variable的条目,该条目指定了regularizer参数。您还可以手动添加到该集合中。当使用tf.Variable并且指定活动正则化器或其他自定义正则化器时,这将非常有用。例如:

#This will add an activity regularizer on y to the regloss collection
regularizer = tf.contrib.layers.l2_regularizer(0.1)
y = tf.nn.sigmoid(x)
act_reg = regularizer(y)
tf.add_to_collection(tf.GraphKeys.REGULARIZATION_LOSSES, act_reg)

在这个例子中,很可能更有效地正则化x,因为对于大的x,y会变得非常平坦。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接