一. 向量范数
范数, norm, 是数学中是一种类似“长度”的概念, 其实也就是一类函数.
在机器学习中的正则化(Regularization)以及稀疏编码(Sparse Coding)有非常有趣的应用.
对于向量
a
∈
R
n
a\in R^n
a∈Rn, 它的 Lp 范数为
∣
∣
a
∣
∣
p
=
(
∑
i
n
∣
a
i
∣
p
)
1
p
(1)
||a||_p=(\sum_i^n |a_i|^p)^{\frac 1 p} \tag 1
∣∣a∣∣p=(i∑n∣ai∣p)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∑(θTxi−yi)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∑(θTxi−yi)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 thesqrt
: 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正则化损失, 降低过拟合风险.
通常分两步:
-
将 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), 自动获取.
- regularizer, 该形参接受一个函数, 签名为
-
见下方代码, 会自动计算
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=x−gradient∗λ, 为了防止震荡, 让训练更稳定, 可以对 梯度作裁剪(截断), 限定其值在一定范围内.
梯度截断能否缓解这种训练毛刺?
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 alist
of(gradients, variables)
tuple
s and
returns the same thing with the gradients modified.
- clip_gradients: float, callable or
该 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 valuest_list[i]
are set to:
t_list[i] * clip_norm / max(global_norm, clip_norm)
Args:
t_list: A tuple or list of mixedTensors
,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
参考
- 他人blog, tensorflow中添加L2正则化损失