深度学习loss值变为0_Tensorflow2.0深度学习示例代码与详解(三)

深度学习loss值变为0_Tensorflow2.0深度学习示例代码与详解(三)_第1张图片
import tensorflow as tfimport numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport seaborn as snsfrom tensorflow import kerasfrom tensorflow.keras import datasets, layers , Sequential, optimizers, lossesfrom mpl_toolkits.mplot3d import Axes3Dfrom sklearn.datasets import *from sklearn.model_selection import *##################### 卷积神经网络 ###################### 卷积神经网络:局部连接、共享权值的网络。# 在深度学习中,权值函数( )称为卷积核(Kernel),其他资料也叫Filter,Weight等。# 在信号处理领域,1D 连续信号的卷积运算被定义2个函数的积分:函数(),# 函数(),其中()经过了翻转(−)和平移后变成( − )。卷积的“卷”是指翻转平移操 作,“积”是指积分运算。# 单通道输入,单卷积核# 多通道输入,单卷积核:一般来说,一个固定的卷积核只能完成某种逻辑的特征提取。# 多通道输入,多卷积核:当需要同时提取多种逻辑特征时,可以通过增加多个卷积核来得到多种特征。# 当出现多卷积核时,第( ∈ 为卷积核个数)个卷积核与输入X运算得到第个输出矩阵(也称为输出张量O的通道),# 最后全部的输出矩阵在通道维度上进行拼接(Stack 操作,创建输出通道数 的新维度),产生输出张量O,O包含了个通道数。# 卷积神经层的输出尺寸h'和w'由卷积核的数量,卷积核的大小k,步长s,# 填充数p(只考虑上下填充数量ph相同,左右填充数量pw相同的情况),以及输入X的高宽h/w共同决定:# h' = ⌊(h + 2∗ph − k)/s⌋ + 1# w' = ⌊(w + 2∗pw − k)/s⌋ + 1# 网络实现# 在TensorFlow中,通过tf.nn.conv2d函数可以方便地实现2D卷积运算。# tf.nn.conv2d 基于输入X:[b, h, , ]和卷积核W:[, , , ]进行卷积运算,# 得到输出O:[b, h′,′, ],其中表示输入通道数,表示卷积核的数量,也是输出特征图的通道数。# 模拟输入,3通道,高宽为5 x = tf.random.normal([2,5,5,3]) # 需要根据[k,k,cin,cout]格式创建W张量,4个3x3大小卷积核w = tf.random.normal([3,3,3,4])# 步长为1, padding为0, 其中 padding 参数的设置格式为: padding=[[0,0],[上,下],[左,右],[0,0]]out = tf.nn.conv2d(x,w,strides=1,padding=[[0,0],[0,0],[0,0],[0,0]])# 特别地,通过设置参数padding='SAME',strides=1可以直接得到输入、输出同大小的卷积层。# 当>1时,设置padding='SAME'将使得输出高、宽将成1倍地减少:1/# 如下创建了4个3x3大小的卷积核的卷积层,步长为 1,padding方案为'SAME':layer = layers.Conv2D(4,kernel_size=3,strides=1,padding='SAME')# 如下创建4个3x4大小的卷积核,竖直方向移动步长h = 2,水平方向移动步长 = 1:layer = layers.Conv2D(4,kernel_size=(3,4),strides=(2,1),padding='SAME')# 创建完成后,通过调用实例(的__call__方法)即可完成前向计算:# 创建卷积层类layer = layers.Conv2D(4,kernel_size=3,strides=1,padding='SAME') out = layer(x) # 前向计算out.shape# 在类Conv2D中,保存了卷积核张量W和偏置b,可以通过类成员trainable_variables直接返回W,b的列表:# 返回所有待优化张量列表layer.trainable_variables# 我们基于MNIST手写数字图片数据集训练LeNet-5网络,并测试其最终准确度。network = Sequential([layers.Conv2D(6,kernel_size=3,strides=1), # 第一个卷积层, 6个3x3卷积核 layers.MaxPooling2D(pool_size=2,strides=2), # 高宽各减半的池化层layers.ReLU(), # 激活函数layers.Conv2D(16,kernel_size=3,strides=1), # 第二个卷积层, 16个3x3卷积核 layers.MaxPooling2D(pool_size=2,strides=2), # 高宽各减半的池化层 layers.ReLU(), # 激活函数layers.Flatten(), # 打平层,方便全连接层处理layers.Dense(120, activation='relu'), # 全连接层,120个节点layers.Dense(84, activation='relu'), # 全连接层,84节点 layers.Dense(10) # 全连接层,10个节点])# build一次网络模型,给输入X的形状,其中4为随意给的batch_sizenetwork.build(input_shape=(4, 30, 30, 1))# 统计网络信息network.summary()# 创建损失函数的类,在实际计算时直接调用类实例即可criteon = losses.CategoricalCrossentropy(from_logits=True)# 训练部分实现如下:# 构建梯度记录环境with tf.GradientTape() as tape:# 插入通道维度,=>[b,28,28,1]x = tf.expand_dims(x, axis=3)# 前向计算,获得10类别的概率分布,[b, 784] => [b, 10] out = network(x)# 真实标签one-hot编码,[b] => [b, 10]y_onehot = tf.one_hot(y, depth=10) # 计算交叉熵损失函数,标量loss = criteon(y_onehot, out)# 获得损失值后,通过TensorFlow的梯度记录器tf.GradientTape()来计算损失函数loss,# 对网络参数network.trainable_variables之间的梯度,并通过optimizer对象自动更新网络权值参数。# 自动计算梯度grads = tape.gradient(loss, network.trainable_variables)# 自动更新参数optimizer.apply_gradients(zip(grads, network.trainable_variables))# 重复上述步骤若干次后即可完成训练工作。# 接下来开始测试:# 记录预测正确的数量,总样本数量correct, total = 0,0for x,y in db_test: # 遍历所有训练集样本# 插入通道维度,=>[b,28,28,1]x = tf.expand_dims(x,axis=3)# 前向计算,获得10类别的预测分布,[b, 784] => [b, 10] out = network(x)# 真实的流程时先经过softmax,再argmax,但是由于softmax不改变元素的大小相对关系,故省去pred = tf.argmax(out, axis=-1)y = tf.cast(y, tf.int64)# 统计预测正确数量correct += float(tf.reduce_sum(tf.cast(tf.equal(pred, y), tf.float32)))# 统计预测样本总数total += x.shape[0] # 计算准确率print('test acc:', correct/total)# 池化层同样基于局部相关性的思想,通过从局部相关的一组元素中进行采样或信息聚合,从而得到新的元素值。# 特别地,最大池化层(Max Pooling)从局部相关元素集中选取最大的一个元素值,# 平均池化层(Average Pooling)从局部相关元素集中计算平均值并返回。# BatchNorm层# 我们将BN层的输入记为,输出记为̃。# ← ∗ +( −)∗,2 ← ∗ 2 + ( − ) ∗ 2# 其中momentum是需要设置一个超参数,用于平衡,2的更新幅度:当momentum = 0时,和2直接被设置为最新一个Batch的和2;# 当momentum = 1时,和2保持不变,忽略最新一个Batch的和2,在TensorFlow中,momentum默认设置为0.99。# 对于输入X:[h, , ],BN层并不是计算每个点的 2,而是在通道轴c上面统计每个通道上面所有数据的 2,# 因此 2是每个通道上所有其他维度的均值和方差。# 构造输入 x=tf.random.normal([100,32,32,3]) # 将其他维度合并,仅保留通道维度x=tf.reshape(x,[-1,3])# 计算其他维度的均值 ub=tf.reduce_mean(x,axis=0)# Layer Norm:统计每个样本的所有特征的均值和方差# Instance Norm:统计每个样本的每个通道上特征的均值和方差# GroupNorm:将c通道分成若干组,统计每个样本的通道组内的特征均值和方差# BN层实现network = Sequential([ # 网络容器 layers.Conv2D(6,kernel_size=3,strides=1), # 插入BN层layers.BatchNormalization(), layers.MaxPooling2D(pool_size=2,strides=2), layers.ReLU(), layers.Conv2D(16,kernel_size=3,strides=1), # 插入BN层layers.BatchNormalization(), layers.MaxPooling2D(pool_size=2,strides=2), layers.ReLU(),layers.Flatten(),layers.Dense(120, activation='relu'),# 此处也可以插入BN层layers.Dense(84, activation='relu'), # 此处也可以插入BN层layers.Dense(10)])# 在训练阶段,需要设置网络的参数training=True以区分BN层是训练还是测试模型:with tf.GradientTape() as tape: # 插入通道维度x = tf.expand_dims(x,axis=3)# 前向计算,设置计算模式,[b, 784] => [b, 10] out = network(x, training=True)# 在测试阶段,需要设置training=False,避免BN层采用错误的行为:for x,y in db_test: # 遍历测试集# 插入通道维度x = tf.expand_dims(x,axis=3)# 前向计算,测试模式out = network(x, training=False)# 经典卷积网络# 在线下载,加载CIFAR10数据集(x,y), (x_test, y_test) = datasets.cifar100.load_data() # 删除y的一个维度,[b,1] => [b]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(128)# 构建测试集对象test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test)) test_db = test_db.map(preprocess).batch(128)# 从训练集中采样一个Batch,并观察sample = next(iter(train_db))print('sample:', sample[0].shape, sample[1].shape, tf.reduce_min(sample[0]), tf.reduce_max(sample[0]))# 我们将网络实现为2个子网络:卷积子网络和全连接子网络。# 卷积子网络由5个子模块构成,每个子模块包含了Conv-Conv-MaxPooling单元结构:# 先创建包含多层的列表conv_layers = [ # Conv-Conv-Pooling单元1# 64个3x3卷积核, 输入输出同大小layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),# 高宽减半layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),# Conv-Conv-Pooling单元2,输出通道提升至128,高宽大小减半layers.Conv2D(128, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),layers.Conv2D(128, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),# Conv-Conv-Pooling单元3,输出通道提升至256,高宽大小减半layers.Conv2D(256, kernel_size=[3, 3], padding="same", activation=tf.nn. relu),layers.Conv2D(256, kernel_size=[3, 3], padding="same", activation=tf.nn. relu),layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),# Conv-Conv-Pooling单元4,输出通道提升至512,高宽大小减半layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn. relu),layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn. relu),layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),# Conv-Conv-Pooling单元5,输出通道提升至512,高宽大小减半layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn. relu),layers.Conv2D(512, kernel_size=[3, 3], padding="same", activation=tf.nn. relu),layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same') ]# 利用前面创建的层列表构建网络容器 conv_net = Sequential(conv_layers)# 全连接子网络包含了3个全连接层,每层添加ReLU非线性激活函数,最后一层除外。# 创建3层全连接层子网络 fc_net = Sequential([layers.Dense(256, activation=tf.nn.relu), layers.Dense(128, activation=tf.nn.relu), layers.Dense(10, activation=None),])# build2个子网络,并打印网络参数信息conv_net.build(input_shape=[4, 32, 32, 3]) fc_net.build(input_shape=[4, 512]) conv_net.summary()fc_net.summary()# 由于我们将网络实现为2个子网络,在进行梯度更新时,需要合并2个子网络的待优化参数列表# 列表合并,合并2个子网络的参数variables = conv_net.trainable_variables + fc_net.trainable_variables # 对所有参数求梯度grads = tape.gradient(loss, variables)# 自动更新optimizer.apply_gradients(zip(grads, variables))# 卷积层变种# 空洞卷积在不增加网络参数的条件下,提供了更大的感受野窗口。# 但是在使用空洞卷积设置网络模型时,需要精心设计dilation rate参数来避免出现网格效应,# 同时较大的dilation rate参数并不利于小物体的检测、语义分割等任务。# 模拟输入x = tf.random.normal([1,7,7,1]) # 空洞卷积,1个3x3的卷积核layer = layers.Conv2D(1,kernel_size=3,strides=1,dilation_rate=2) out = layer(x) # 前向计算out.shape# 转置卷积(Transposed Convolution/Fractionally Strided Convolution,# 部分资料也称之为反卷积/Deconvolution,实际上反卷积在数学上定义为卷积的逆过程,# 但转置卷积并不能恢复出原卷积的输入,因此称为反卷积并不妥当)# 通过在输入之间填充大量的padding来实现输出高宽大于输入高宽的效果,从而实现向上采样的目的。# 创建X矩阵,高宽为5x5# o+2p-k为s倍数,本例中5+0-3=2=sx = tf.range(25)+1# Reshape为合法维度的张量x = tf.reshape(x,[1,5,5,1]) x = tf.cast(x, tf.float32)# 创建固定内容的卷积核矩阵w = tf.constant([[-1,2,-3.],[4,-5,6],[-7,8,-9]]) # 调整为合法维度的张量w = tf.expand_dims(w,axis=2)w = tf.expand_dims(w,axis=3)# 进行普通卷积运算out = tf.nn.conv2d(x,w,strides=2,padding='VALID')# 普通卷积的输出作为转置卷积的输入,进行转置卷积运算 xx = tf.nn.conv2d_transpose(out, w, strides=2, padding='VALID', output_shape=[1,5,5,1])# o+2p-k不为s倍数x = tf.random.normal([1,6,6,1]) # 6x6的输入经过普通卷积out = tf.nn.conv2d(x,w,strides=2,padding='VALID')# 恢复出6x6大小xx = tf.nn.conv2d_transpose(out, w, strides=2, padding='VALID', output_shape=[1,6,6,1])# 转置卷积具有“放大特征图”的功能,在生成对抗网络、语义分割等中得到了广泛应用,# 如DCGAN(Radford, Metz, & Chintala, 2015)中的生成器通过堆叠转置卷积层实现逐层“放大”特征图,最后获得十分逼真的生成图片。# 在使用 tf.nn.conv2d_transpose 进行转置卷积运算时,需要额外手动设置输出的高宽。# tf.nn.conv2d_transpose并不支持自定义padding设置,只能设置为VALID或者SAME。 # 如果设置 padding=’VALID’时,输出大小表达为: = ( − 1)∗ + # 如果设置 padding=’SAME’时,输出大小表达为: = ( − 1)∗ + 1# 分离卷积# 卷积核的每个通道与输入的每个通道进行卷积运算,得到多个通道的中间特征。# 这个多通道的中间特征张量接下来进行多个1x1卷集核的普通卷积运算,得到多个高宽不变的输出。# 这些输出在通道轴上面进行拼接,从而产生最终的分离卷积层的输出。# 可以看到,分离卷积层包含了两步卷积运算,第一步卷积运算是单个卷积核,第二个卷积运算包含了多个卷积核。# 同样的输入和输出,采用Separable Convolution的参数量约是普通卷积的1/3。# 深度残差网络# ResNet通过在卷积层的输入和输出之间添加Skip Connection实现层数回退机制。# 输入通过两个卷积层,得到特征变换后的输出F(),与输入进行对应元素的相加运算,得到最终输出:# H() = + F() 其中H()叫做残差模块(Residual Block,ResBlock)。# 由于被Skip Connection包围的卷积神经网络需要学习映射F() = H() − ,故称为残差网络。# 残差网络实现class BasicBlock(layers.Layer): # 残差模块类def __init__(self, filter_num, stride=1):super(BasicBlock, self).__init__() # f(x)包含了2个普通卷积层,创建卷积层1self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, padding='same')self.bn1 = layers.BatchNormalization()self.relu = layers.Activation('relu')# 创建卷积层2self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')self.bn2 = layers.BatchNormalization()# 当F()的形状与不同时,无法直接相加,我们需要新建identity(x)卷积层,来完成的形状转换:# 插入identity层if stride != 1: self.downsample = Sequential() self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=stride))# 否则,直接连接else: self.downsample = lambda x:x# 在前向传播时,只需要将F()与identity()相加,并添加ReLU激活函数即可。# 前向传播函数def call(self, inputs, training=None): # 通过第一个卷积层 out = self.conv1(inputs) out = self.bn1(out)out = self.relu(out)# 通过第二个卷积层out = self.conv2(out) out = self.bn2(out)# 输入通过identity()转换identity = self.downsample(inputs)# f(x)+x运算output = layers.add([out, identity]) # 再通过激活函数并返回output = tf.nn.relu(output)return output# DenseNet:将前面所有层的特征图信息通过Skip Connection与当前层输出进行聚合,# 与ResNet 的对应位置相加不同,DenseNet采用在通道轴c维度进行拼接操作,聚合特征信息。# 残差网络完整示例class BasicBlock(layers.Layer):# 残差模块def __init__(self, filter_num, stride=1):super(BasicBlock, self).__init__()# 第一个卷积单元self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, padding='same')self.bn1 = layers.BatchNormalization()self.relu = layers.Activation('relu')# 第二个卷积单元self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')self.bn2 = layers.BatchNormalization()if stride != 1:# 通过 1x1 卷积完成 shape 匹配self.downsample = Sequential()self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=stride))else:# shape匹配,直接短接 self.downsample = lambda x:xdef call(self, inputs, training=None):# [b, h, w, c],通过第一个卷积单元 out = self.conv1(inputs)out = self.bn1(out)out = self.relu(out)# 通过第二个卷积单元out = self.conv2(out)out = self.bn2(out)# 通过 identity 模块identity = self.downsample(inputs) # 2条路径输出直接相加output = layers.add([out, identity])# 激活函数 output = tf.nn.relu(output) return output# 辅助函数,堆叠filter_num个BasicBlockdef build_resblock(self, filter_num, blocks, stride=1): res_blocks = Sequential()# 只有第一个BasicBlock的步长可能不为1,实现下采样 res_blocks.add(BasicBlock(filter_num, stride))for _ in range(1, blocks):#其他BasicBlock步长都为1 res_blocks.add(BasicBlock(filter_num, stride=1))return res_blocksclass ResNet(keras.Model):# 通用的ResNet实现类# [2, 2, 2, 2]def __init__(self, layer_dims, num_classes=10): super(ResNet, self).__init__()# 根网络,预处理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')])# 堆叠4个Block,每个block包含了多个BasicBlock,设置步长不一样self.layer1 = self.build_resblock(64, layer_dims[0]) 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)# 通过Pooling层将高宽降低为1x1self.avgpool = layers.GlobalAveragePooling2D() # 最后连接一个全连接层分类self.fc = layers.Dense(num_classes)def call(self, inputs, training=None): # 通过根网络x = self.stem(inputs)# 一次通过4个模块x = self.layer1(x)x = self.layer2(x) x = self.layer3(x) x = self.layer4(x)# 通过池化层x = self.avgpool(x) # 通过全连接层x = self.fc(x)return x# 通过调整每个ResBlock的堆叠数量和通道数可以产生不同的ResNet,# 如通过64-64-128-128-256-256-512-512通道数配置,共8个ResBlock,# 可得到ResNet18的网络模型。每个ResBlock包含了2个主要的卷积层,# 因此主要卷积层数量是8*2=16,加上网络最前面的普通卷积层和全连接层,共18层。def resnet18():# 通过调整模块内部BasicBlock的数量和配置实现不同的ResNet return ResNet([2, 2, 2, 2])def resnet34():# 通过调整模块内部BasicBlock的数量和配置实现不同的ResNet return ResNet([3, 4, 6, 3])# 数据加载(x,y), (x_test, y_test) = datasets.cifar10.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(512)#构建测试集test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test)) # 随机打散,预处理,批量化 test_db = test_db.map(preprocess).batch(512)# 采样一个样本sample = next(iter(train_db))print('sample:', sample[0].shape, sample[1].shape, tf.reduce_min(sample[0]), tf.reduce_max(sample[0]))# 数据的预处理def preprocess(x, y): # 将数据映射到-1~1x = 2*tf.cast(x, dtype=tf.float32) / 255. - 1 y = tf.cast(y, dtype=tf.int32) # 类型转换 return x,y# 网络训练for epoch in range(50): # 训练 epochfor step, (x,y) in enumerate(train_db):with tf.GradientTape() as tape:# [b, 32, 32, 3] => [b, 10],前向传播logits = model(x)# [b] => [b, 10],one-hot 编码y_onehot = tf.one_hot(y, depth=10)# 计算交叉熵loss = tf.losses.categorical_crossentropy(y_onehot, logits, from_logits=True)loss = tf.reduce_mean(loss)# 计算梯度信息grads = tape.gradient(loss, model.trainable_variables)# 更新网络参数optimizer.apply_gradients(zip(grads, model.trainable_variables))

你可能感兴趣的:(深度学习loss值变为0)