机器学习中的正则项及梯度截断

本文介绍了向量范数的基本概念及其在机器学习中的应用,包括L1和L2范数,并详细阐述了如何使用正则化来避免过拟合问题。此外,还介绍了TensorFlow中范数计算与梯度截断的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 向量范数

范数, norm, 是数学中是一种类似“长度”的概念, 其实也就是一类函数.
在机器学习中的正则化(Regularization)以及稀疏编码(Sparse Coding)有非常有趣的应用.
对于向量 a ∈ R n a\in R^n aRn, 它的 Lp 范数为
∣ ∣ a ∣ ∣ p = ( ∑ i n ∣ a i ∣ p ) 1 p (1) ||a||_p=(\sum_i^n |a_i|^p)^{\frac 1 p} \tag 1 ∣∣ap=(inaip)p1(1)
常用的有:

  • L0范数
    向量中非0的元素的个数.
  • L1范数
    向量中各个元素绝对值之和.
  • L2范数
    向量各元素的平方和然后求平方根.

1.1 几何意义

向量表示的是点与点之间的大小与方向, 那么不同的范数就对应着不同的距离.
常用的有曼哈顿距离与欧氏距离. 当向量为2维时, 可以方便地图形化表达:

  • Manhattan distance
    曼哈顿距离, 对应L1范数.
    曼哈顿是一座城市, 可以想象为围棋棋盘, 从点A到点B的路程, 只能沿着棋盘上的路线左右走或上下走.
    以L1距离做度量, 距离原点路程为1的点的集合见图2-1 的上图.
  • Euclidean distance
    欧氏距离, 也叫欧几里得距离, 对应L2范数.
    以L2距离做度量, 距离原点路程为1的点的集合见图2-1 的中图.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YPRao5Fu-1578233684389)(https://upload.wikimedia.org/wikipedia/commons/6/60/Vector_norms.png)]
图2-1 拥有不同范数的单位圆

二. 正则化

正则化, regularization, 就是正则表达式中那个正则. 注意它不同于 正规化 .
正则化用来防止过拟合,提升模型的泛化能力.

回归问题中, 损失函数为 差平方
L ( x ; θ ) = ∑ i ( θ T x i − y i ) 2 (3-1) L( x;\theta)=\sum_i (\theta^Tx_i-y_i)^2 \tag {3-1} L(x;θ)=i(θTxiyi)2(3-1)
目标函数就是求它的最小值.
根据向量 θ \theta θ 中非0元素个数的不同, 会得到下图中三种拟合曲线:


图3-1 欠拟合, 理想情况 与 过拟合

参数越复杂, 表达能力越强, 但容易出现上图中的过拟合现象, 特别是训练集中有噪音的时候, 我们不期望自己的模型去把一些噪声离群点也拟合掉, 这样会加大测试集中的误差.
所以我们会把目标函数中添加 θ \theta θ 的L2范数作正则项:
L ( x ; θ ) = ∑ i ( θ T x i − y i ) 2 + λ ∣ ∣ θ ∣ ∣ 2 (3-2) L( x;\theta)=\sum_i (\theta^Tx_i-y_i)^2+\lambda ||\theta||_2 \tag {3-2} L(x;θ)=i(θTxiyi)2+λ∣∣θ2(3-2)
其中 λ \lambda λ 为正则项的系数.
这样, 参数约复杂, 它的L2范数就越大, 所以(3-2) 的表达式可以约束参数的复杂度.

三. tf-api

3.1 范数计算

正则项的值 = 系数 ∗ 范数 正则项的值=系数*范数 正则项的值=系数范数

  • tensorflow.contrib.layers.python.layers.regularizers.l2_regularizer(scale, scope=None)
    返回的是一个方法, l2(weights), 它返回的就是 系数*范数=scale*sum(weights ** 2) / 2 . 它又会调用下方方法:
  • tf.nn.l2_loss(t, name=None)
    Computes half the L2 norm of a tensor without the sqrt: output = sum(t ** 2) / 2.

代码示例见下:

import tensorflow as tf
import tensorflow.contrib as contrib

weight = tf.constant([[1.0, -2.0], [-3.0, 4.0]])
with tf.Session() as sess:
    # 输出为(|1|+|-2|+|-3|+|4|)*0.5=5.0
    print(sess.run(contrib.layers.l1_regularizer(0.5)(weight)))
    # 输出为(1²+(-2)²+(-3)²+4²)/2*0.5=7.5
    # TensorFlow会将L2的正则化损失值除以2使得求导得到的结果更加简洁
    print(sess.run(contrib.layers.l2_regularizer(0.5)(weight)))
    # l1_regularizer+l2_regularizer=12.5
    print(sess.run(contrib.layers.l1_l2_regularizer(0.5, 0.5)(weight)))

3.2 loss 加入正则项

loss 构造方法一

在 task-specified loss 后, 可以添加L2正则化损失, 降低过拟合风险.
通常分两步:

  1. 将 variable 添加到 集合中. tf.add_to_collection(tf.GraphKeys.WEIGHTS, variable)

    • tensorflow.contrib.layers.python.layers.regularizers.apply_regularization(regularizer, weights_list=None)
      args 解读.
      • regularizer, 该形参接受一个函数, 签名为 f(weights)
      • weights_list, 当不显式地传入, weights_list = ops.get_collection(ops.GraphKeys.WEIGHTS), 自动获取.
  2. 见下方代码, 会自动计算GraphKeys.WEIGHTS对应的 weight lists 的正则惩罚的和.

regularizer = tf.contrib.layers.l2_regularizer(FLAGS.l2_reg) # L2正则项系数
reg_loss = tf.contrib.layers.apply_regularization(regularizer)

loss 构造方法二

dense layer 构造中方法中, 传入 regularizer 参数, 相应范数会放到 REGULARIZATION_LOSSES 集合中.

reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
self.reg_losses = tf.reduce_sum(self.reg_losses)

四. 梯度截断及api

训练的迭代过程是 x = x − g r a d i e n t ∗ λ x=x - gradient*\lambda x=xgradientλ, 为了防止震荡, 让训练更稳定, 可以对 梯度作裁剪(截断), 限定其值在一定范围内.
在这里插入图片描述
梯度截断能否缓解这种训练毛刺?

tf-api

有个高度封装的,

  • tensorflow.contrib.layers.python.layers.optimizers.optimize_loss(clip_gradients,...)
    • clip_gradients: float, callable or None. If float, is provided, a global
      clipping is applied to prevent the norm of the gradient to exceed this
      value. Alternatively, a callable can be provided e.g.: adaptive_clipping.
      This callable takes a list of (gradients, variables) tuples and
      returns the same thing with the gradients modified.

该 api 内部会调用以下 low-level 的 api.

compute

Optimizer.compute_gradients(self, loss, var_list,...)
返回 a list of (gradient, variable) pairs. Variable is always present, but gradient can be None.

在这里插入图片描述
图: 需要注意, 其中的 gradient 类型不固定, 属于 {tf.Tensor, Indexed Slices, None} 中一种.

clip

  • tf.global_norm(t_list, name=None)
    相当于把list中的各个tensor的 element 拼接在一起, 统一计算 global_norm = sqrt(sum([l2norm(t)**2 for t in t_list]))
  • tf.clip_by_norm(t, clip_norm, axes=None,)
    梯度裁剪, 保证返回的梯度的L2范数<=形参clip_norm. 内部实现为, 如果 l2norm(t) > clip_norm , 则缩放系数为clip_norm / l2norm(t), 会返回 t * clip_norm / l2norm(t).
    可以令clip_norm=5, 避开Indexed Slices.
  • tf.clip_by_global_norm(t_list, clip_norm, use_norm=None,...)
    To perform the clipping, the values t_list[i] are set to:
    t_list[i] * clip_norm / max(global_norm, clip_norm)
    Args:
    t_list: A tuple or list of mixed Tensors, IndexedSlices, or None.

apply

  • Optimizer.apply_gradients(self, grads_and_vars, global_step=None
    返回 train_op.

snippet 示例

nn_var_list = list(set(tf.trainable_variables()) - set(wide_var_list))

######## 梯度截断
grads_and_vars = nn_optimizer.compute_gradients(loss, var_list=nn_var_list)
# pai-tf 分布式, 会卡住, 尝试去掉 IndexedSlices
grads_and_vars = [(grad, var) for grad, var in grads_and_vars
                    if
                    grad is not None and not isinstance(grad, ops.IndexedSlices)]
clipped_gradients_and_vars = [(tf.clip_by_norm(grad, self.flags.clip_norm), var) for grad, var in grads_and_vars
                                if
                                grad is not None and not isinstance(grad, ops.IndexedSlices)]
# for histogram
for grad, var in grads_and_vars:
    if grad is not None:
        tf.summary.histogram('gradients_before_clip', grad)
for grad, var in clipped_gradients_and_vars:
    tf.summary.histogram('gradients_after_clip', grad)
######## 梯度截断 ends

参考

  1. 他人blog, tensorflow中添加L2正则化损失
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值