发布于2021-06-07 21:22 阅读(1210) 评论(0) 点赞(7) 收藏(0)
当堆叠更深的网络结构的时候,网络的性能并没有得到提升。每次计算梯度时 δ E δ w i j − > δ k \frac{\delta E}{\delta w_{ij}}->\delta_k δwijδE−>δk, δ k \delta_k δk算子是通过上一层算出来的,会渐渐产生误差的积累,也就会造成梯度离散(更多)和梯度爆炸的情况。也就是说前面的层数可以很好地更新,因为grad信息还算比较有效的;然后到最开始的几层后,发现grad信息慢慢接近于0,这样会长时间得不到更新,那么网络train的效果会很久但效果不好。
如果网络层很深了,我们希望30层至少也要比22层不差才行,人为设计机制,使得30层再差也会退到22层。加一个短路层(short cut),因为有捷径可走,梯度进行传播的时候,本来通过8层会有一定程度的梯度衰减,有短路层后直接梯度为1。意味着加了短路层后,30层的网络结构再差也能保证前面22层train好,如果8层能train好,意味着网络容量增加了,会比22层略微好一点,如果train不好,直接走短路层,也不会比22层差,这就是ResNet的初衷。
1 unit = conv+relu+conv+relu
Unit的结构
1个unit最好使用2~3个卷积层+short cut(短路),如果输入256,为了能short cut,需要做element width的相加,意味着普通层输出的x’的shape与x完全一样。所以卷积的操作不能做维度的相减,也不能做channel的衰减,因此如果是256,最终得到的结果也是256。
为了减少参数量,把1个unit的kernel size做一个减小。1st层1*1,64,得到[64,224,224];2nd层保持shape不变,变成[64,224,224](做了padding);3rd层,[256,224,224]。输入也是[256,224,224]。所以直接相加会得到[256,224,224]。
这样,参数量从600K变成70K。从而使得显存张量的减少,使得堆叠更深层次的网络结构变为可能。
34层如果短路,就会退化到VGG19。退化到极点就只有1层了,退化的权利是交给网络的,可能一开始是退化到11~19层的,等其他部分train好了之后,网络就会想办法把以前短路的层给利用上来,会尝试着能不能在19层的基础上再提升一点点。ResNet就是这样一步步train出来的。
可以说以后的优化技术变好了,那么ResNet可能变得VGG一样,可能就是中间产物了,目前来看优化技术打不到这个程度,因此我们人为地加短接线,使的train起来更加简单。
在一篇论文中提到,如果加了short cut,曲面非常平滑,可以很快的找到最优解,如果不加short cut,可能找到的曲面很曲折,很可能找到局部极小值。因此,对于优化器来说加了short cut,train起来更方便、快捷、有效地帮助找到最优解,获得更好的性能。
ResNet比以前的网络有很大的提升。
其实残差=short cut。只不过残差有一个数学的概念。差的概念:如果是之前两个简单的卷积层堆叠在一起,做conv,x->x’=H(x)。而ResNet,F(x)+x->H(x),F(x)=H(x)-x,意思是F(x)要学的是H(x)-x的残差,所以这个网络叫做残差网络。
unit = conv + bn(batch normalization) + ReLu
下采样(设置stride),Identity层,目的是把输入变得可以跟经过两个卷积层的输出可以直接相加的操作。
一层和前面的所有层都有机会短接
首先先实现Basic Block
再实现Res Block
ResNet = 预处理层+4个 res block+全连接层,总共“18”层
再开始写训练代码
#resnet.py
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential
class BasicBlock(layers.Layer):
#filter_num/channel/kernel 卷积核通道的数量。
# 指把输入的channel转化到BasicBlock流动的channel
#stride 步长,默认为1
def __init__(self,filter_num,stride=1):
super(BasicBlock, self).__init__()
#第1层(实际有3个层)
#filter_num 通道的数量;kernel_size (3*3)
#关于padding,如果stride为1,那么output会略小于input size;设置padding=‘same’,使得输出=输入的size
#如果stride大于1,如果输入是32*32,卷积是3*3,stride是2,可能输出会返回14*14或15*15的输出;而‘same’会使得padding成输出16*16的shape
self.conv1 = layers.Conv2D(filter_num,(3,3),strides=stride,padding='same')
#习惯:每个卷积层后面跟一个batch normalization
self.bn1 = layers.BatchNormalization()
self.relu = layers.Activation("relu")
#第2层
#这次不再下采样(stride设为1)
self.conv2 = layers.Conv2D(filter_num,(3,3),strides=1,padding='same')
self.bn2 = layers.BatchNormalization()
#经过上面的两层后,在第1层可能会下采样,第2层的维度不会变。因此可能出现:输入[32*32],而输出[16.16]的情况
#短接线如果是[32*32],这样是没办法直接想加的。
# Identity并不是严格意义上的短接,可能是做了1*1的Conv,使得channel通道匹配一致,通过stride使得size匹配到一致。
# 所以这里的identity层并不是严格意义上的短接层,还有维度变换的功能在里面。
if stride != 1:
self.downsample = Sequential() #Sequential 容器
#这里stride依然设置为stride
self.downsample.add(layers.Conv2D(filter_num,(1,1),strides=stride))
else:
#如果没有缩放,则不用下采样,直接返回
self.downsample = lambda x:x
#其实有短接的概念在内即可,有没有缩放(stride)都可
def call(self,inputs,training=None):
#1层的传播
#input为image size,比如[b,h,w,c]
out = self.conv1(inputs)
out = self.bn1(out)
out = self.relu(out)
#2层的传播
out = self.conv2(out)
out = self.bn2(out)
#inputs经过conv1,conv2得到out
#接收的是inputs,inputs经过downsample得到identity
identity = self.downsample(inputs)
#将两者相加
output = layers.add([out,identity])
#再来一个relu
output = tf.nn.relu(output)
return output
class ResNet(keras.Model):
#layer_dims [2,2,2,2],意味着有4个res block,第1个res block包含了2个basic block...
#num_classes 全连接层的输出,取决于有多少类,预设为100
def __init__(self,layer_dims,num_classes=100):
super(ResNet, self).__init__()
#stem根,预处理层
self.stem = Sequential([layers.Conv2D(64,(3,3),strides=(1,1)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.MaxPool2D(pool_size=(2,2),strides=(1,1),padding='same')
])
self.layer1 = self.build_resblock(64,layer_dims[0])
#stride=2,意味着每个层需要在h,w维度有降维的功能,使得feature size会越来越少
self.layer2 = self.build_resblock(128, layer_dims[1],stride=2)
self.layer3 = self.build_resblock(256, layer_dims[2],stride=2)
self.layer4 = self.build_resblock(512, layer_dims[3], stride=2)
#因为输出取决于input和网络具体的参数,还不知道最后一层输出的size
# 假设output:[b,512,h,w];b,512已知;但hw未知,需要运算才知道大概的h和w
#运用这个函数,不管长和宽是多少,它会在某个channel上的所有长和宽的像素值加起来做一个均值
#变成[b,512,1*1]
self.avgpool = layers.GlobalAveragePooling2D()
#创建全连接层,做分类
self.fc = layers.Dense(num_classes)
def call(self,inputs,training=None):
x = self.stem(inputs)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
#[b,c(channel)]
x = self.avgpool(x)
#输出[b,100]
x = self.fc(x)
return x
#filter_num 当前channel的数量;blocks 表示res block会堆叠多少个basic block;stride
def build_resblock(self,filter_num,blocks,stride=1):
res_blocks = Sequential()
# 第1个basic block may down sample(下采样)
res_blocks.add(BasicBlock(filter_num,stride))
#其他的BasicBlock不让它有下采样功能
for _ in range(1,blocks):
res_blocks.add(BasicBlock(filter_num,stride=1))
return res_blocks
def resnet18():
return ResNet([2,2,2,2])
def resnet34():
#resnet34的配置
return ResNet([3, 4, 6, 3])
#resnet_train.py
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import tensorflow as tf
from tensorflow.keras import layers,optimizers,datasets,Sequential
from a115_resnet import resnet18
tf.random.set_seed(2345)
#预处理
def preprocess(x,y):
#->[-1~1]
x = 2* tf.cast(x,dtype=tf.float32) / 255. - 1
y = tf.cast(y,dtype=tf.int32)
return x,y
#获取数据
(x,y),(x_test,y_test) = datasets.cifar100.load_data()
y = tf.squeeze(y,axis=1)
y_test = tf.squeeze(y_test,axis=1)
print(x.shape,y.shape,x_test.shape,y_test.shape)
train_db = tf.data.Dataset.from_tensor_slices((x,y))
#打乱后预处理
train_db = train_db.shuffle(1000).map(preprocess).batch(64)
test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
test_db = test_db.map(preprocess).batch(64)
#查看
sample=next(iter(train_db))
print('sample:',sample[0].shape,sample[1].shape,
tf.reduce_min(sample[0]),tf.reduce_max(sample[0]))
#sample: (64, 32, 32, 3) (64, 1)
# tf.Tensor(0.0, shape=(), dtype=float32) tf.Tensor(1.0, shape=(), dtype=float32)
#label需要从(64,1)改成(64,);上面用tf.squeeze(y,axis=1)
def main():
# [b,32,32,3] => [b,1,1,512]
model = resnet18()
model.build(input_shape=(None,32,32,3))
model.summary()
optimizer = optimizers.Adam(lr=1e-3)
for epoch in range(50):
for step, (x, y) in enumerate(train_db):
with tf.GradientTape() as tape:
# [b,32,32,3] => [b,100]
logits = model(x)
#对标签y进行onehot编码,[b] => [b,100]
y_onehot = tf.one_hot(y,depth=100)
# compute loss,logits与真实值比较
loss = tf.losses.categorical_crossentropy(y_onehot,logits,from_logits=True)
#获得平均的loss
loss = tf.reduce_mean(loss)
grads = tape.gradient(loss,model.trainable_variables)
#更新
optimizer.apply_gradients(zip(grads,model.trainable_variables))
#打印
if step %100 ==0:
print(epoch,step,'loss:',float(loss))
#test
total_num = 0
total_correct = 0
for x,y in test_db:
logits = model(x)
#归一化,多分类结果以概率的形式展现
prob = tf.nn.softmax(logits,axis=1)
#取其中“概率”最大的
pred = tf.argmax(prob,axis=1)
#int64变成int32
pred = tf.cast(pred,dtype=tf.int32)
correct = tf.cast(tf.equal(pred,y),dtype=tf.int32)
correct = tf.reduce_sum(correct)
#每个step的图片数量(batch)加到total_num?
total_num+=x.shape[0]
total_correct += int(correct)
acc= total_correct/total_num
print(epoch,'acc:',acc)
if __name__ == "__main__":
main()
显存不足的措施:
原文链接:https://blog.csdn.net/bill2766/article/details/117590507
作者:我赌你没有子弹
链接:http://www.phpheidong.com/blog/article/89587/890d2120ad39f377ab97/
来源:php黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 php黑洞网 All Rights Reserved 版权所有,并保留所有权利。 京ICP备18063182号-4
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!