1.在用batch normal时发现模型一直不收敛。
解决方法:
在计算loss前加入update_ops。
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
training_operation = optimizer.minimize(loss_operation_color,global_step=global_step) #loss_operation
首先我们先介绍tf.control_dependencies
,该函数保证其辖域中的操作必须要在该函数所传递的参数中的操作完成后再进行。
关于tf.GraphKeys.UPDATE_OPS,这是一个tensorflow的计算图中内置的一个集合,其中会保存一些需要在训练操作之前完成的操作,并配合tf.control_dependencies函数使用。关于在batch_norm中,即为更新mean和variance的操作。
总结来说就是:
训练时,(1)输入参数training=True,(2)计算loss时,要添加以下代码(即添加
update_ops到最后的train_op中)。这样才能计算μ和σ的滑动平均(测试时会用到)
测试时,输入参数training=False,其他就没了。
祥见:https://blog.csdn.net/xys430381_1/article/details/89213369
2.数据增强:
翻转,旋转,椒盐噪声,高斯噪声,亮度调节
def SaltAndPepper(src,percetage):
SP_NoiseImg=src.copy()
SP_NoiseNum=int(percetage*src.shape[0]*src.shape[1])
for i in range(SP_NoiseNum):
randR=np.random.randint(0,src.shape[0]-1)
randG=np.random.randint(0,src.shape[1]-1)
randB=np.random.randint(0,3)
if np.random.randint(0,1)==0:
SP_NoiseImg[randR,randG,randB]=0
else:
SP_NoiseImg[randR,randG,randB]=255
return SP_NoiseImg
def addGaussianNoise(image,percetage):
G_Noiseimg = image.copy()
w = image.shape[1]
h = image.shape[0]
G_NoiseNum=int(percetage*image.shape[0]*image.shape[1])
for i in range(G_NoiseNum):
temp_x = np.random.randint(0,h)
temp_y = np.random.randint(0,w)
G_Noiseimg[temp_x][temp_y][np.random.randint(3)] = np.random.randn(1)[0]
return G_Noiseimg
#dimming
def darker(image,percetage=0.9):
image_copy = image.copy()
w = image.shape[1]
h = image.shape[0]
#get darker
for xi in range(0,w):
for xj in range(0,h):
image_copy[xj,xi,0] = int(image[xj,xi,0]*percetage)
image_copy[xj,xi,1] = int(image[xj,xi,1]*percetage)
image_copy[xj,xi,2] = int(image[xj,xi,2]*percetage)
return image_copy
def brighter(image, percetage=1.5):
image_copy = image.copy()
w = image.shape[1]
h = image.shape[0]
#get brighter
for xi in range(0,w):
for xj in range(0,h):
image_copy[xj,xi,0] = np.clip(int(image[xj,xi,0]*percetage),a_max=255,a_min=0)
image_copy[xj,xi,1] = np.clip(int(image[xj,xi,1]*percetage),a_max=255,a_min=0)
image_copy[xj,xi,2] = np.clip(int(image[xj,xi,2]*percetage),a_max=255,a_min=0)
return image_copy
def rotate(image, angle=15, scale=0.9):
w = image.shape[1]
h = image.shape[0]
#rotate matrix
M = cv2.getRotationMatrix2D((w/2,h/2), angle, scale)
#rotate
image = cv2.warpAffine(image,M,(w,h))
return image
def img_augmentation(path, name_int):
img = cv2.imread(path)
img_flip = cv2.flip(img,1)#flip
img_rotation = rotate(img)#rotation
img_noise1 = SaltAndPepper(img, 0.3)
img_noise2 = addGaussianNoise(img, 0.3)
img_brighter = brighter(img)
img_darker = darker(img)
cv2.imwrite(save_path+'%s' %str(name_int)+'.jpg', img_flip)
cv2.imwrite(save_path+'%s' %str(name_int+1)+'.jpg', img_rotation)
cv2.imwrite(save_path+'%s' %str(name_int+2)+'.jpg', img_noise1)
cv2.imwrite(save_path+'%s' %str(name_int+3)+'.jpg', img_noise2)
cv2.imwrite(save_path+'%s' %str(name_int+4)+'.jpg', img_brighter)
cv2.imwrite(save_path+'%s' %str(name_int+5)+'.jpg', img_darker)
旋转可能会伴随着信息的损失,由于是将几个效果图合到一张图上,rotate部分看上去有点怪
3.忽略特定标签类
一种做法是另你要忽略类的标签设为-1,这种方法在设计网络结构的时候不用添加背景类。但网上鲜有验证这种方式的资料,如果你尝试过并有效,请回复我,谢谢!
一种做法是在模型输出中加入该特定标签类的预测,在计算loss时,为其添加weight权重。
具体实现方法1为:
tf.losses.softmax_cross_entropy()及相邻函数中weights参数的设置。
tf.losses.softmax_cross_entropy(
onehot_labels,
logits,
weights=1.0,
label_smoothing=0,
scope=None,
loss_collection=tf.GraphKeys.LOSSES,
reduction=Reduction.SUM_BY_NONZERO_WEIGHTS
)
其中
- onehot_labels是one_hot编码的label, shape为[batch_size, num_classes]
- logits是神经网络的输出, 注意要求是softmax处理之前的logits, 因为tf.losses.softmax_cross_entropy()方法内部会对logits做softmax处理, shape为[batch_size, num_classes]
- weights可以是一个标量或矩阵. 如果是标量, 就是对算出来的cross_entropy做缩放; 如果是矩阵, 要求shape为[batch_size, ].
可以发现, weights实际上是给batch中每个sample设置一个权重, 而不是给label的不同class设置权重. 因此, 输入的weights需要先做处理:
weights = [0.1] + 49 * [1.0] #label为0的class权重设为0.1, 其余49个class设为1, 输出一个list
weights = tf.convert_to_tensor(weights) #将list转成tensor, shape为[50, ]
weights = tf.reduce_sum(tf.multiply(onehot_labels, weights), -1) #根据labels,将weights转成对每个sample的权重
还有一种更简介的实现方案:
通过乘以logits,您可以按类的权重重新缩放每个类的预测。
例如:
ratio = 31.0 / (500.0 + 31.0)
class_weight = tf.constant([ratio, 1.0 - ratio])
logits = ... # shape [batch_size, 2]
weighted_logits = tf.mul(logits, class_weight) # shape [batch_size, 2]
xent = tf.nn.softmax_cross_entropy_with_logits(
weighted_logits, labels, name="xent_raw")
现在有一个标准损失功能,支持每批重量:
tf.losses.sparse_softmax_cross_entropy(labels=label, logits=logits, weights=weights)
其中权重应从类权重转换为每个示例的权重(具有形状[batch_size])。
使用compute_weighted_loss,这里我使用sigmoid_cross_entropy_with_logits来计算前景/背景分割的丢失。unc是与标签相同的张量形状,unc的值在被忽略标签的位置设置为0,标签的位置设置为1,不应忽略...在这种情况下,最终的损失不计算在忽略标签。实际上,使用这种方法你可以忽略你想要的任何东西......
xentropy = tf.reduce_mean(tf.losses.compute_weighted_loss(weights = tf.cast(unc, tf.float32),losses = tf.nn.sigmoid_cross_entropy_with_logits(logits = logits,labels = tf.cast(label, tf.float32))), name='xentropy')
4.深度学习中,偏置(bias)在什么情况下可以要,可以不要?
参考:https://blog.csdn.net/u013289254/article/details/98785869
卷积之后,如果要接BN操作,最好是不设置偏置,因为不起作用,而且占显卡内存。
5.通过YOLOv3看模型训练技巧
https://github.com/YunYang1994/CodeFun/blob/master/002-deep_learning/YOLOv3.md
5.1权重初始化设置
训练神经网络尤其是深度神经网络所面临的一个问题是,梯度消失或梯度爆炸,也就是说 当你训练深度网络时,导数或坡度有时会变得非常大,或非常小甚至以指数方式变小,这个时候我们看到的损失就会变成了 NaN。假设你正在训练下面这样一个极深的神经网络,为了简单起见,这里激活函数 g(z) = z 并且忽略偏置参数。
其实这里直观的理解是:如果权重 W 只比 1 略大一点,或者说只比单位矩阵大一点,深度神经网络的输出将会以爆炸式增长,而如果 W 比 1 略小一点,可能是 0.9, 0.9,每层网络的输出值将会以指数级递减。因此合适的初始化权重值就显得尤为重要! 下面就写个简单的代码给大家演示一下。
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
x = np.random.randn(2000, 800) * 0.01 # 制作输入数据
stds = [0.1, 0.05, 0.01, 0.005, 0.001] # 尝试使用不同标准差,这样初始权重大小也不一样
for i, std in enumerate(stds):
# 第一层全连接层
dense_1 = tf.keras.layers.Dense(750, kernel_initializer=tf.random_normal_initializer(std), activation='tanh')
output_1 = dense_1(x)
# 第二层全连接层
dense_2 = tf.keras.layers.Dense(700, kernel_initializer=tf.random_normal_initializer(std), activation='tanh')
output_2 = dense_2(output_1)
# 第三层全连接层
dense_3 = tf.keras.layers.Dense(650, kernel_initializer=tf.random_normal_initializer(std), activation='tanh')
output_3 = dense_3(output_2).numpy().flatten()
plt.subplot(1, len(stds), i+1)
plt.hist(output_3, bins=60, range=[-1, 1])
plt.xlabel('std = %.3f' %std)
plt.yticks([])
plt.show()
我们可以看到当标准差较大( std = 0.1 和 0.05 )时,几乎所有的输出值集中在 -1 或1 附近,这表明此时的神经网络发生了梯度爆炸;当标准差较小( std = 0.005 和 0.001)时,我们看到输出值迅速向 0 靠拢,这表明此时的神经网络发生了梯度消失。其实笔者也曾在 YOLOv3 网络里做过实验,初始化权重的标准差如果太大或太小,都容易出现 NaN 。不信,你可以试试看啰?
Xavier initialization 可以解决上面的问题!其初始化方式也并不复杂。Xavier初始化的基本思想是保持输入和输出的方差一致,这样就避免了所有输出值都趋向于零。Xavier initialization 的实现很简单,初始化与输入和输出节点有关。
其实在 keras 实现中很简单,Xavier 正态分布初始化,也称作 Glorot 正态分布初始化方法,参数由0均值,标准差为sqrt(2 / (fan_in + fan_out))的正态分布产生,其中fan_in和fan_out是权重张量的扇入扇出(即输入和输出单元数目)。
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
x = np.random.randn(2000, 800)
for i in range(10):
dense = tf.keras.layers.Dense(750, kernel_initializer="glorot_normal", activation='tanh')
output = dense(x)
x = output
plt.subplot(1, 10, i+1)
plt.hist(output.numpy().flatten(), bins=60, range=[-1, 1])
plt.xlabel("layer %d" %(i+1))
plt.yticks([])
plt.show()
输出值在很多层之后依然保持着良好的分布,这很有利于我们优化神经网络。但是这个例子仅仅说明它对 tanh 很有效。那么,假如我们使用 relu 激活函数结果会如何呢?
这里我就不贴代码了,你就在上面代码的基础上将 'tanh' 替换成 'relu' 就好了。
前面看起来还不错,后面的趋势却是越来越接近零。幸运的是,He initialization 可以用来解决 relu 初始化的问题。He 正态分布初始化方法,参数由 0 均值,标准差为 sqrt(2 / fan_in) 的正态分布产生,其中fan_in权重张量的扇入。
这里我就不贴代码了,你就在上面代码的基础上将 'glorot_normal' 替换成 'he_normal' 就好了。
看起来效果比之前好很多了!
5.2学习率的设置
学习率是最影响性能的超参数之一,如果我们只能调整一个超参数,那么最好的选择就是它。 其实在我们的大多数的炼丹过程中,遇到 loss 变成 NaN 的情况大多数是由于学习率选择不当引起的。有句话讲得好啊,步子大了容易扯到蛋。由于神经网络在刚开始训练的时候是非常不稳定的,因此刚开始的学习率应当设置得很低很低,这样可以保证网络能够具有良好的收敛性。但是较低的学习率会使得训练过程变得非常缓慢,因此这里会采用以较低学习率逐渐增大至较高学习率的方式实现网络训练的“热身”阶段,称为 warmup stage。但是如果我们使得网络训练的 loss 最小,那么一直使用较高学习率是不合适的,因为它会使得权重的梯度一直来回震荡,很难使训练的损失值达到全局最低谷。因此最后采用了这篇论文里的 cosine 的衰减方式,这个阶段可以称为 consine decay stage。
直接现场来看代码吧!
if global_steps < warmup_steps:
lr = global_steps / warmup_steps *cfg.TRAIN.LR_INIT
else:
lr = cfg.TRAIN.LR_END + 0.5 * (cfg.TRAIN.LR_INIT - cfg.TRAIN.LR_END) * (
(1 + tf.cos((global_steps - warmup_steps) / (total_steps - warmup_steps) * np.pi))
)
在 warmup(学习率上升)阶段,学习率渐渐增大是为了避免 Nan 的情况。如果你的 loss 出现了 Nan,不妨增大 warmup epochs 或者减小学习率? 在 decay(学习率下降)阶段,学习率逐渐变小是为了尽量使得网络训练的 loss 到达最低谷。
6 其他技巧
以前,我们在分类网络的最后几层使用fc,后来fc被证明参数量太大泛化性能不好,被global average pooling替代掉了,最早出现在network in network中,
GAP直接把每个通道对应空间特征聚合成一个标量
从此,分类网络的范式变为(Relu已经被隐式带在了onv和deconv里面),
Input-->Conv-->DownSample_x_2-->Conv-->DownSample_x_2-->Conv-->DownSample_x_2-->GAP-->Conv1x1-->Softmax-->Output