• 人工智能实践丨Tensorflow笔记


    神经网络计算过程及模型搭建

    人工智能三学派

    人工智能就是让机器具备人的思维和意识。人工智能主要有三个学派,即行为主义、符号主义和连接主义。

    行为主义 基于控制论,是在构建感知、动作的控制系统。比如单脚站立(通过感知要摔倒的方向,控制两只手的动作,保持身体的平衡)

    符号主义 基于算数逻辑表达式,求解问题时,先把问题描述为表达式,再求解表达式。在求解某个问题时,利用条件语句和若干计算公式描述出来,如专家系统。符号主义是能用公式描述的人工智能,它让计算机具备了理性思维。

    连接主义 仿造人脑内的神经元连接关系,使人类不仅具备理性思维,还具备无法用公式描述的感性思维,如对某些知识产生记忆。

    神经网络设计过程

    准备 准备数据,数据量越大越好,要构成特征和标签对。如要识别猫,就要有大量猫的图片和这个图片是猫的标签,构成特征标签对。

    搭建 搭建神经网络的网络结构,并通过反向传播,优化连线的权重,直到模型的识别准确率达到要求,得到最优的连线权重,把这个模型保存起来。

    预测 用保存的模型,输入从未见过的新数据,它会通过前向传播,输出概率值,概率值最大的一个,就是分类或预测的结果。

    数据集介绍

    采用鸢尾花数据集,此数据集包含鸢尾花花萼长、花萼宽、花瓣长、花瓣宽及对应的类别。其中前 4 个属性作为输入特征,类别作为标签, 0代表狗尾草鸢尾, 1代表杂色鸢尾, 2代表弗吉尼亚鸢尾。人们通过对数据进行分析总结出了规律:通过测量花的花萼长、花萼宽、花瓣长、花瓣宽,可以得出鸢尾花的类别(如:花萼长>花萼宽且花瓣长/花瓣宽>2 ,则杂色鸢尾)。

    由上述可知,可通过ifcase语句构成专家系统,进行判别分类。在本讲中,采用搭建神经网络的办法对其进行分类,即将鸢尾花花萼长、花萼宽、花瓣长、花瓣宽四个输入属性喂入搭建好的神经网络,网络优化参数得到模型,输出分类结果。

    网络搭建与训练

    本讲中,我们搭建包含输入层与输出层的神经网络模型,通过对输入值乘权值,并于偏置值求和的方式得到输出值.

    通过输出y可以看出结果不一定正确,这是由于最初的参数w和b是随机产生的,现在输出的结果是蒙的。为了修正这一结果,我们用损失函数,定义预测值y和标准答案(标签)y_的差距,损失函数可以定量的判断当前这组参数w和b的优劣,当损失函数最小时,即可得到最优w的值和b的值。

    损失函数的定义有多种方法,均方误差就是一种常用的损失函数,它计算每个前向传播输出y和标准答案_y 的差求平方再求和再除以n求平均值,表征了网络前向传播推理结果和标准答案之间的差距。

    为了找到最优w值和b值,我们采用梯度下降的方法。损失函数的梯度表示损失函数对各参数求偏导后的向量,损失函数梯度下降的方向,就是是损失函数减小的方向。梯度下降法即沿着损失函数梯度下降的方向,寻找损失函数的最小值,从而得到最优的参数。

    上式中,lr表示学习率,是一个超参数,表征梯度下降的速度。如学习率设置过小,参数更新会很慢,如果学习率设置过大,参数更新可能会跳过最小值,上述梯度下降更新的过程为反向传播。

    TensorFlow 2.1 基本概念与常见函数

    基本概念

    Tensor

    TensorFlow中的Tensor表示张量,是多维数组、多维列表,用阶表示张量的维数。0阶张量叫做标量,表示的是一个单独的数,如123;1阶张量叫作向量,表示的是一个一维数组如[1,2,3];2阶张量叫作矩阵,表示的是一个二维数组,它可以有i行j列个元素,每个元素用它的行号和列号共同索引到,如在[[1,2,3],[4,5,6],[7,8,9]]中,2的索引即为第0行第1列。张量的阶数与方括号的数量相同,可以表示0阶到n阶的数组。也可通过reshape得到更高维度数组,举例如下:

    c = np.arange(24).reshape(2,4,3)
    print(c)
    [[[ 0 1 2]
      [ 3 4 5]
      [ 6 7 8]
      [ 9 10 11]]
     [[12 13 14]
      [15 16 17]
      [18 19 20]
      [21 22 23]]]
    

    数据类型

    数据类型包括32位整型(tf.int32)、32位浮点(tf.float32)、64位浮点(tf.float64)、布尔型(tf.bool)、字符串型(tf.string)

    创建张量方法

    #1
    a = tf.constant([1,5],dtype=tf.int64)
    #去掉dtype项可能因数据默认值不同导致程序bug
    
    #2
    a = np.arange(0,5)
    b = tf.convert_to_tensor(a,dtype=tf.int64)
    
    #3
    a = tf.zeros([2,3])
    b = tf.ones(4)
    c = tf.fill([2,2],9)
    
    #4
    d = tf.random.normal([2,2],mean=0.5,stddev=1)
    #生成正态分布的随机数,默认均值为0,标准差为1
    
    e = tf.random.truncated_normal([2,2],mean=0.5,stddev=1)
    #生成截断式正态分布的随机数,能使生成的这些随机数更集中一些
    #如果随机生成数据的取值在(µ-2σ,u+2σ)之外则重新进行生成,保证了生成值在均值附近
    
    f = tf.random.uniform([2,2],minval=0,maxval=1)
    #生成指定维度的均匀分布随机数,范围是[minval,maxval)
    

    常用函数

    1. tf.cast tf.reduce_min/reduce_max
    x1 = tf.constant ([1., 2., 3.], dtype=tf.float64)
    print(x1)
    x2 = tf.cast (x1, tf.int32)
    print(x2)
    print (tf.reduce_min(x2), tf.reduce_max(x2))
    
    #输出结果:
    tf.Tensor([1. 2. 3.], shape=(3,), dtype=float64)
    tf.Tensor([1 2 3], shape=(3,), dtype=int32)
    tf.Tensor(1, shape=(), dtype=int32)
    tf.Tensor(3, shape=(), dtype=intt32)
    
    1. tf.reduce_mean tf.reduce_sum
    x=tf.constant([[1,2,3],[1,2,3]])
    print(x)
    print(tf.reduce_mean(x))
    print(tf.reduce_sum(x,axis=1))
    
    #输出结果:
    tf.Tensor([[1 2 3] [1 2 3]], shape=(2,3), dtype=int32)
    tf.Tensor(2, shape=(), dtype=int32) (对所有元素求均值)
    tf.Tensor([6 6], shape=(2,), dtype=int32) (横向求和,两行分别为 6 和 7)
    
    1. tf.Variable(initial_value,trainable,validate_shape,name)
      可以将变量标记为可训练的.被它标记了的变量,会在反向传播中记录自己的梯度信息。其中 initial_value 默认为 None,可以搭配 tensorflow 随机生成函数来初始化参数; trainable 默认为 True,表示可以后期被算法优化的,如果不想该变量被优化,即改为 False; validate_shape 默认为 True,形状不接受更改,如果需要更改, validate_shape=False; name 默认为 None,给变量确定名称

    2. tf.add/subtract/multiply/divide
      只有维度相同的两个张量才可以做四则运算

    3. tf.square/pow/sqrt
      幂次/平方/开方运算

    4. tf.matmul
      矩阵乘法,需要满足[i,j]/[j/k]

    5. tf.data.Dataset.from_tensor_slices(features,lables)
      切分传入张量的第一维度,生成输入特征/标签对,构建数据集,此函数对Tensor格式与Numpy格式均适用,其切分的是第一维度,表征数据集中数据的数量

    6. tf.GradientTape()
      搭配 with 结构计算损失函数在某一张量处的梯度

    with tf.GradientTape() as tape:
        w = tf.Variable(tf.constant(3.0))
        loss = tf.pow(w,2)
    grad = tape.gradient(loss,w)
    print(grad)
    
    1. enumerate
      函数枚举出每一个元素,并在元素前配上对应的索引号,常在for循环中使用

    2. tf.one_hot(lables, depth=classes)
      实现用独热码表示标签,在分类问题中很常见。标记类别为为1和0,其中1表示是,0表示非

    classes = 3
    labels = tf.constant([1,0,2])
    output = tf.one_hot( labels, depth=classes)
    
    1. tf.nn.softmax()
      使前向传播的输出值符合概率分布,进而与独热码形式的标签作比较

    2. assign_sub
      对参数实现自更新。使用此函数前需利用tf.Variable定义变量w为可训练

    w = tf.Variable(4)
    w.assign_sub(1)#可以换成别的
    print(w)
    #输出结果: 
    <tf.Variable 'Variable:0' shape=() dtype=int32, numpy=3> 
    #即实现了参数w自减1
    #直接调用tf.assign_sub会报错,要用w.assign_sub。
    
    1. tf.argmax(name,axis=axisname)
      返回张量沿指定维度最大值的索引

    程序实现鸢尾花数据集分类

    我们用神经网络实现鸢尾花分类仅需要三步:
    (1)准备数据,包括数据集读入、数据集乱序,把训练集和测试集中的数据配成输入特征和标签对,生成train 和test即永不相见的训练集和测试集;
    (2)搭建网络,定义神经网络中的所有可训练参数;
    (3)优化这些可训练的参数,利用嵌套循环在with结构中求得损失函数loss对每个可训练参数的偏导数,更改这些可训练参数,为了查看效果,程序中可以加入每遍历一次数据集显示当前准确率,还可以画出准确率acc和损失函数loss的变化曲线图。

    #(1)数据集读入
    import tensorflow as tf
    from sklearn import datasets
    from matplotlib import pyplot as plt
    import numpy as np
    features_data = datasets.load_iris().data   #返回 iris 数据集所有输入特征
    labels_data = datasets.load_iris().target #返回 iris 数据集所有标签
    
    #(2)数据集乱序
    np.random.seed(0)        #使用相同的seed,使输入特征/标签一一对应
    np.random.shuffle(features_data)
    np.random.seed(0)
    np.random.shuffle(labels_data)
    tf.random.set_seed(0)
    
    #(3)数据集分割成永不相见的训练集和测试集
    features_train = features_data[:-30]
    labels_train = labels_data[:-30]
    features_test = features[-30:]
    labels_test = labels_data[-30:]
    #转换x的数据类型,否则后面矩阵相乘时会因数据类型不一致报错
    features_train = tf.cast(features_train, tf.float32)
    features_test = tf.cast(features_test, tf.float32)
    
    #(4)配成[输入特征,标签]对,每次喂入一小撮(batch)
    train_db = tf.data.Dataset.from_tensor_slices((features_train, labels_train)).batch(32)
    test_db = tf.data.Dataset.from_tensor_slices((features_test, labels_test)).batch(32)
    
    #(5)定义神经网路中所有可训练参数和其他参数
    w1 = tf.Variable(tf.random.truncated_normal([4,3], stddev=0.1, seed=1))
    b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))
    lr = 0.1                 #学习率为0.1
    train_loss_results = []  #将每轮的loss记录在此列表中,为后续画loss曲线提供数据
    test_acc = []            #将每轮的acc记录在此列表中,为后续画acc曲线提供数据
    epoch = 500              #循环500轮
    loss_all = 0             #每轮分4个step,loss_all记录四个step生成的4个loss的和
    
    #(6)训练部分
    for epoch in range(epoch): 
    #数据集级别的循环,每个epoch循环一次数据集
        for step, (x_train, y_train) in enumerate(train_db): 
        #batch级别的循环 ,每个step循环一个batch
            with tf.GradientTape() as tape: 
            #with结构记录梯度信息
                y = tf.matmul(x_train, w1) + b1
                #神经网络乘加运算
                y = tf.nn.softmax(y) 
                #使输出y符合概率分布(此操作后与独热码同量级,可相减求loss)
                y_ = tf.one_hot(y_train, depth=3)
                #将标签值转换为独热码格式,方便计算loss和accuracy
                loss = tf.reduce_mean(tf.square(y_ - y)) 
                #采用均方误差损失函数mse = mean(sum(y-out)^2)
                loss_all += loss.numpy()
                #将每个step计算出的loss累加,为后续求loss平均值提供数据,这样计算的loss更准确
            grads = tape.gradient(loss, [w1, b1])#计算loss对各个参数的梯度
            w1.assign_sub(lr * grads[0])  # 参数w1自更新
            b1.assign_sub(lr * grads[1])  # 参数b自更新
    
        # 每个epoch,打印loss信息
        print("Epoch {}, loss: {}".format(epoch, loss_all/4))
        train_loss_results.append(loss_all / 4)  # 将4个step的loss求平均记录在此变量中
        loss_all = 0  # loss_all归零,为记录下一个epoch的loss做准备
    
    #(7)测试部分
        # total_correct为预测对的样本个数, total_number为测试的总样本数,将这两个变量都初始化为0
        total_correct, total_number = 0, 0
        for x_test, y_test in test_db:
            # 使用更新后的参数进行预测
            y = tf.matmul(x_test, w1) + b1
            y = tf.nn.softmax(y)
            pred = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即预测的分类
            # 将pred转换为y_test的数据类型
            pred = tf.cast(pred, dtype=y_test.dtype)
            # 若分类正确,则correct=1,否则为0,将bool型的结果转换为int型
            correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
            # 将每个batch的correct数加起来
            correct = tf.reduce_sum(correct)
            # 将所有batch中的correct数加起来
            total_correct += int(correct)
            # total_number为测试的总样本数,也就是x_test的行数,shape[0]返回变量的行数
            total_number += x_test.shape[0]
        # 总的准确率等于total_correct/total_number
        acc = total_correct / total_number
        test_acc.append(acc)
        print("Test_acc:", acc)
        print("--------------------------")
    
    # 绘制 loss 曲线
    plt.title('Loss Function Curve')  # 图片标题
    plt.xlabel('Epoch')  # x轴变量名称
    plt.ylabel('Loss')  # y轴变量名称
    plt.plot(train_loss_results, label="$Loss$")  # 逐点画出trian_loss_results值并连线,连线图标是Loss
    plt.legend()  # 画出曲线图标
    plt.show()  # 画出图像
    
    # 绘制 Accuracy 曲线
    plt.title('Acc Curve')  # 图片标题
    plt.xlabel('Epoch')  # x轴变量名称
    plt.ylabel('Acc')  # y轴变量名称
    plt.plot(test_acc, label="$Accuracy$")  # 逐点画出test_acc值并连线,连线图标是Accuracy
    plt.legend()
    plt.show()
    
    

    !

    扩展方法见本地文件 ensorflowmoocTensorFlow笔记1.pdf

    神经网络优化

    相关代码

    1. tf.where(condition, x=None, y=None, name=None)
      根据condition,取x或y中的值。如果为True,对应位置取x的值
      如果为False,对应位置取y的值
      condition: bool型张量
      x: 与y shape相同的张量
      y: 与x shape相同的张量
      返回:shape与x相同的张量

    2. np.random.RandomState.rand(维度,seed=可选)
      返回一个[0,1)之间的随机数或者矩阵

    3. np.vstack(np1, np2)
      将两个数组按垂直方向叠加

    4. np.mgrid[start:end:step]
      用法:返回多维结构,常见的如2D图形,3D图形。
      第1返回值为第1维数据在最终结构中的分布,
      第2返回值为第2维数据在最终结构中的分布,以此类推。(分布以矩阵形式呈现)
      mgrid[[1:3:3j, 4:5:2j]]
      3j:3个点
      步长为复数表示点数,左闭右闭
      步长为实数表示间隔,左闭右开

    >>> x,y=np.mgrid[-5:5:3j,-2:2:3j]
    >>> x
    array([[-5., -5., -5.],
           [ 0.,  0.,  0.],
           [ 5.,  5.,  5.]])
    >>> y
    array([[-2.,  0.,  2.],
           [-2.,  0.,  2.],
           [-2.,  0.,  2.]])
    

    其中x沿着水平向右的方向扩展(即是:每列都相同),观察x。
    y沿着垂直的向下的方向扩展(即是:每行都相同)。观察y。

    1. x.ravel()
      把x变成一维数组

    2. np.c_[数组1,数组2,...]
      使返回的间隔数值点配对

    学习率策略

    指数衰减

    指数衰减学习率是先使用较大的学习率来快速得到一个较优的解,然后随着迭代的继续,逐步减小学习率,使得模型在训练后期更加稳定。指数型学习率衰减法是最常用的衰减方法。
    指数衰减学习率=初始学习率*学习率衰减率(当前轮数/多少轮衰减一次)

    分段常数衰减

    分段常数衰减可以让调试人员针对不同任务设置不同的学习率,进行精细调参,在任意步长后下降任意数值的learning rate,要求调试人员对模型和数据集有深刻认识

    激活函数

    激活函数是用来加入非线性因素的,因为线性模型的表达能力不够。引入非线性激活函数,可使深层神经网络的表达能力更加强大
    非线性:激活函数非线性时,多层神经网络可逼近所有函数
    可微性:优化器大多用梯度下降更新参数
    单调性:当激活函数是单调的,能保证单层网络的损失函数是凸函数
    近似恒等性:f(x)~x,当参数初始化为随机小值时,神经网络更稳定
    激活函数输出值的范围:
    激活函数输出为有限值时,基于梯度的优化方法更稳定
    激活函数输出为无限值时,建议调小学习率
    常见的激活函数有:

    • tf.math.sigmoid
      优点:
    1. 输出映射在(0,1)之间,单调连续,输出范围有限,优化稳定,可用作输出层;
    2. 求导容易。
      缺点:
    3. 易造成梯度消失;
    4. 输出非0均值,收敛慢;
    5. 幂运算复杂,训练时间长。
      sigmoid函数可应用在训练过程中。然而,当处理分类问题作出输出时,sigmoid却无能为力。简单地说,sigmoid函数只能处理两个类,不适用于多分类问题。而softmax可以有效解决这个问题,并且softmax函数大都运用在神经网路中的最后一层网络中,使得值得区间在(0,1)之间,而不是二分类的。
    • tf.math.tanh
      优点:
    1. 比sigmoid函数收敛速度更快。
    2. 相比sigmoid函数,其输出以0为中心。
      缺点:
    3. 易造成梯度消失;
    4. 幂运算复杂,训练时间长。
    • tf.nn.relu
      优点:
    1. 解决了梯度消失问题(在正区间);
    2. 只需判断输入是否大于0,计算速度快;
    3. 收敛速度远快于sigmoid和tanh,因为sigmoid和tanh涉及很多expensive的操作
    4. 提供了神经网络的稀疏表达能力
      缺点:
    5. 输出非0均值,收敛慢;
    6. Dead ReLU问题:某些神经元可能永远不会被激活,导致相应的参数永远不能被更新
    • tf.nn.leaky_relu
      理论上来讲,Leaky ReLU有ReLU的所有优点,外加不会有Dead ReLU问题,但是在实际操作当
      中,并没有完全证明Leaky ReLU总是好于ReLU。

    • tf.nn.softmax
      对神经网络全连接层输出进行变换,使其服从概率分布,即每个值都位于[0,1]区间且和为1

    此外还有:PReLU,RReLU,ELU(Exponential Linear Units),softplus,softsign,softmax

    建议

    1. 首选ReLU激活函数;
    2. 学习率设置较小值;
    3. 输入特征标准化,即让输入特征满足以0为均值,1为标准差的正态分布;
    4. 初始化问题:初始参数中心化,即让随机生成的参数满足以0为均值,sqrt(2/当前层输入特征个数)为标准差的正态分布。

    损失函数

    神经网络模型的效果及优化的目标是通过损失函数来定义的。回归和分类是监督学习中的两个大类。

    均方误差(Mean Square Error)

    回归问题最常用的损失函数。回归问题解决的是对具体数值的预测,比如房价预测、销量预测等。这些问题需要预测的不是一个事先定义好的类别,而是一个任意实数。

    其中y_i为一个batch中第i个数据的真实值,而y_i'为神经网络的预测值
    tf.keras.losses.MSE

    交叉熵(Cross Entropy)

    表征两个概率分布之间的距离,交叉熵越小说明二者分布越接近,是分类问题中使用较广泛的损失函数。

    其中y_代表数据的真实值,y代表神经网络的预测值。对于多分类问题,神经网络的输出一般不是概率分布,因此需要引入softmax层,使得输出服从概率分布。
    tf.keras.losses.categorical_crossentropy
    tf.nn.softmax_cross_entropy_with_logits
    tf.nn.sparse_softmax_cross_entropy_with_logits

    自定义损失函数

    根据具体任务和目的,可设计不同的损失函数。从老师课件和讲解中对于酸奶预测损失函数的设计,我们可以得知损失函数的定义能极大影响模型预测效果。好的损失函数设计对于模型训练能够起到良好的引导作用。
    例如,我们可以看目标检测中的多种损失函数。目标检测的主要功能是定位和识别,损失函数的功
    能主要就是让定位更精确,识别准确率更高。目标检测任务的损失函数由分类损失(Classificition Loss)和回归损失(Bounding Box Regeression Loss)两部分构成。近几年来回归损失主要有Smooth L1 Loss(2015), IoU Loss(2016 ACM), GIoU Loss(2019 CVPR), DIoU Loss & CIoU Loss(2020AAAI)等,分类损失有交叉熵、softmax loss、logloss、focal loss等。在此由于篇幅原因不细究,有兴趣的同学可自行研究。需要针对特定的背景、具体的任务设计损失函数。

    欠拟合与过拟合

    欠拟合的解决方法:
    . 增加输入特征项
    . 增加网络参数
    . 减少正则化参数
    过拟合的解决方法:
    . 数据清洗
    . 增大训练集
    . 采用正则化
    . 增大正则化参数

    优化器

    优化算法可以分成一阶优化和二阶优化算法,其中一阶优化就是指的梯度算法及其变种,而二阶优化一般是用二阶导数(Hessian 矩阵)来计算,如牛顿法,由于需要计算Hessian阵和其逆矩阵,计算量较大,因此没有流行开来。这里主要总结一阶优化的各种梯度下降方法。
    深度学习优化算法经历了SGD -> SGDM -> NAG ->AdaGrad -> AdaDelta -> Adam -> Nadam这样的发展历程。

    SGD

    tf.keras.optimizers.SGD

    1. vanilla SGD
      最大的缺点是下降速度慢,而且可能会在沟壑的两边持续震荡,停留在一个局部最优点。
    # sgd
    w1.assign_sub(learning_rate * grads[0])
    b1.assign_sub(learning_rate * grads[1])
    
    1. SGD with Momentum(SGDM)
      动量法是一种使梯度向量向相关方向加速变化,抑制震荡,最终实现加速收敛的方法。t时刻的下降方向,不仅由当前点的梯度方向决定,而且由此前累积的下降方向决定。beta的经验值为0.9,这就意味着下降方向主要偏向此前累积的下降方向,并略微偏向当前时刻的下降方向。
    # sgd-momentun
    beta = 0.9
    m_w = beta * m_w + (1 - beta) * grads[0]
    m_b = beta * m_b + (1 - beta) * grads[1]
    w1.assign_sub(learning_rate * m_w)
    b1.assign_sub(learning_rate * m_b)
    
    1. SGD with Nesterov Acceleration(NAG,Nesterov Accelerated Gradient)
      我们知道在时刻t的主要下降方向是由累积动量决定的,自己的梯度方向说了也不算,那与其看当前梯度方向,不如先看看如果跟着累积动量走了一步,那个时候再怎么走。因此,NAG在步骤1不计算当前位置的梯度方向,而是计算如果按照累积动量走了一步,考虑这个新地方的梯度方向。
    2. AdaGrad(自适应学习率Ada=Adaptive)
      对于频繁更新的参数,不希望被单个样本影响太大,我们给它们很小的学习率;对于偶尔出现的参数,希望能多得到一些信息,我们给它较大的学习率.
      AdaGrad 在稀疏数据场景下表现最好。因为对于频繁出现的参数,学习率衰减得快;对于稀疏的参数,学习率衰减得更慢。然而在实际很多情况下,二阶动量呈单调递增,累计从训练开始的梯度,学习率会很快减至 0 ,导致参数不再更新,训练过程提前结束。
    # adagrad
    v_w += tf.square(grads[0])
    v_b += tf.square(grads[1])
    w1.assign_sub(learning_rate * grads[0] / tf.sqrt(v_w))
    b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))
    
    1. RMSProp(Root Mean Square Prop)
      由于 AdaGrad 的学习率衰减太过激进,考虑改变二阶动量的计算策略:不累计全部梯度,只关注过去某一窗口内的梯度。修改的思路很直接,前面我们说过,指数移动平均值大约是过去一段时间的平均值,反映“局部的”参数信息.
    # RMSProp
    beta = 0.9
    v_w = beta * v_w + (1 - beta) * tf.square(grads[0])
    v_b = beta * v_b + (1 - beta) * tf.square(grads[1])
    w1.assign_sub(learning_rate * grads[0] / tf.sqrt(v_w))
    b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))
    
    1. AdaDelta
      为解决AdaGrad的学习率递减太快的问题,RMSProp和AdaDelta几乎同时独立被提出。
    # AdaDelta
    beta = 0.999
    v_w = beta * v_w + (1 - beta) * tf.square(grads[0])
    v_b = beta * v_b + (1 - beta) * tf.square(grads[1])
    delta_w = tf.sqrt(u_w) * grads[0] / tf.sqrt(v_w)
    delta_b = tf.sqrt(u_b) * grads[1] / tf.sqrt(v_b)
    u_w = beta * u_w + (1 - beta) * tf.square(delta_w)
    u_b = beta * u_b + (1 - beta) * tf.square(delta_b)
    w1.assign_sub(delta_w)
    b1.assign_sub(delta_b)
    
    1. Adam(adaptive moment estimation)
      它们是前述方法的集大成者。我们看到,SGDM在SGD基础上增加了一阶动量,AdaGrad、RMSProp和AdaDelta在SGD基础上增加了二阶动量。把一阶动量和二阶动量结合起来,再修正偏差,就是Adam了.
    # adam
    m_w = beta1 * m_w + (1 - beta1) * grads[0]
    m_b = beta1 * m_b + (1 - beta1) * grads[1]
    v_w = beta2 * v_w + (1 - beta2) * tf.square(grads[0])
    v_b = beta2 * v_b + (1 - beta2) * tf.square(grads[1])
    m_w_correction = m_w / (1 - tf.pow(beta1, int(global_step)))
    m_b_correction = m_b / (1 - tf.pow(beta1, int(global_step)))
    v_w_correction = v_w / (1 - tf.pow(beta2, int(global_step)))
    v_b_correction = v_b / (1 - tf.pow(beta2, int(global_step)))
    w1.assign_sub(learning_rate * m_w_correction / tf.sqrt(v_w_correction))
    b1.assign_sub(learning_rate * m_b_correction / tf.sqrt(v_b_correction))
    

    优化器选择

    很难说某一个优化器在所有情况下都表现很好,我们需要根据具体任务选取优化器。一些优化器在计算机视觉任务表现很好,另一些在涉及RNN网络时表现很好,甚至在稀疏数据情况下表现更出色。

    总结上述,基于原始SGD增加动量和Nesterov动量,RMSProp是针对AdaGrad学习率衰减过快的改进,它与AdaDelta非常相似,不同的一点在于AdaDelta采用参数更新的均方根(RMS)作为分子。Adam在RMSProp的基础上增加动量和偏差修正。如果数据是稀疏的,建议用自适用方法,即Adagrad, RMSprop, Adadelta, Adam。RMSprop, Adadelta, Adam 在很多情况下的效果是相似的。随着梯度变的稀疏,Adam 比 RMSprop 效果会好。总的来说,Adam整体上是最好的选择。

    然而很多论文仅使用不带动量的vanilla SGD和简单的学习率衰减策略。SGD通常能够达到最小点,但是相对于其他优化器可能要采用更长的时间。采取合适的初始化方法和学习率策略,SGD更加可靠,但也有可能陷于鞍点和极小值点。因此,当在训练大型的、复杂的深度神经网络时,我们想要快速收敛,应采用自适应学习率策略的优化器。

    如果是刚入门,优先考虑Adam或者SGD+Nesterov Momentum。

    算法没有好坏,最适合数据的才是最好的,永远记住:No free lunch theorem

    对它的简单易懂的解释就是:

    1、一种算法(算法A)在特定数据集上的表现优于另一种算法(算法B)的同时,一定伴随着算法A在另外某一个特定的数据集上有着不如算法B的表现;

    2、具体问题(机器学习领域内问题)具体分析(具体的机器学习算法选择)。

    优化算法的常用tricks

    1. 首先,各大算法孰优孰劣并无定论。如果是刚入门,优先考虑SGD+Nesterov Momentum或者Adam.
    2. 选择你熟悉的算法——这样你可以更加熟练地利用你的经验进行调参。
    3. 充分了解你的数据——如果模型是非常稀疏的,那么优先考虑自适应学习率的算法。
    4. 根据你的需求来选择——在模型设计实验过程中,要快速验证新模型的效果,可以先用Adam进行快速实验优化;在模型上线或者结果发布前,可以用精调的SGD进行模型的极致优化。
    5. 先用小数据集进行实验。有论文研究指出,随机梯度下降算法的收敛速度和数据集的大小的关系不大。因此可以先用一个具有代表性的小数据集进行实验,测试一下最好的优化算法,并通过参数搜索来寻找最优的训练参数。
    6. 考虑不同算法的组合。先用Adam进行快速下降,而后再换到SGD进行充分的调优。
    7. 充分打乱数据集(shuffle)。这样在使用自适应学习率算法的时候,可以避免某些特征集中出现,而导致的有时学习过度、有时学习不足,使得下降方向出现偏差的问题。在每一轮迭代后对训练数据打乱是一个不错的主意。
    8. 训练过程中持续监控训练数据和验证数据上的目标函数值以及精度或者AUC等指标的变化情况。对训练数据的监控是要保证模型进行了充分的训练——下降方向正确,且学习率足够高;对验证数据的监控是为了避免出现过拟合。
    9. 制定一个合适的学习率衰减策略。可以使用分段常数衰减策略,比如每过多少个epoch就衰减一次;或者利用精度或者AUC等性能指标来监控,当测试集上的指标不变或者下跌时,就降低学习率。
    10. Early stopping。如Geoff Hinton所说:“Early Stopping是美好的免费午餐”。你因此必须在训练的过程中时常在验证集上监测误差,在验证集上如果损失函数不再显著地降低,那么应该提前结束训练。
    11. 算法参数的初始值选择。 初始值不同,获得的最小值也有可能不同,因此梯度下降求得的只是局部最小值;当然如果损失函数是凸函数则一定是最优解。由于有局部最优解的风险,需要多次用不同初始值运行算法,关键损失函数的最小值,选择损失函数最小化的初值。

    扩展方法见本地文件 ensorflowmoocTensorFlow笔记_第二讲.pdf

    神经网络搭建八股

    tf.keras 搭建网络八股

    keras 介绍

    tf.keras 是 tensorflow2 引入的高封装度的框架, 可以用于快速搭建神经网络模型, keras 为支持快速实验而生,能够把想法迅速转换为结果, 是深度学习框架之中最终易上手的一个,它提供了一致而简洁的 API,能够极大地减少一般应用下的工作量,提高代码地封装程度和复用性。

    tf.keras 搭建神经网络六部法

    第一步: import 相关模块,如 import tensorflow as tf。
    第二步: 指定输入网络的训练集和测试集,如指定训练集的输入 x_train 和标签y_train,测试集的输入 x_test 和标签 y_test。
    第三步: 逐层搭建网络结构, model = tf.keras.models.Sequential()。
    第四步: 在 model.compile()中配置训练方法,选择训练时使用的优化器、损失函数和最终评价指标。
    第五步: 在 model.fit()中执行训练过程,告知训练集和测试集的输入值和标签、每个 batch 的大小(batchsize)和数据集的迭代次数(epoch)。
    第六步: 使用 model.summary()打印网络结构,统计参数数目。

    函数用法介绍

    1. tf.keras.models.Sequential()
      Sequential 函数是一个容器, 描述了神经网络的网络结构,在 Sequential函数的输入参数中描述从输入层到输出层的网络结构。
      如:
      拉直层:tf.keras.layers.Flatten()
      拉直层可以变换张量的尺寸,把输入特征拉直为一维数组,是不含计算参数的层。
      全连接层:tf.keras.layers.Dense(神经元个数,activation=激活函数,kernel_regularizer=正则化方式)
      其中:
      activation(字符串给出)可选 relu、 softmax、 sigmoid、 tanh 等
      kernel_regularizer 可选 tf.keras.regularizers.l1()、tf.keras.regularizers.l2()
      卷积层: tf.keras.layers.Conv2D( filter = 卷积核个数,kernel_size = 卷积核尺寸,strides = 卷积步长,padding = “valid” or “same”)
      LSTM 层: tf.keras.layers.LSTM()。
      本章只使用拉直层和全连接层,卷积层和循环神经网络层将在之后的章节介绍。

    2. Model.compile( optimizer = 优化器,loss = 损失函数,metrics = [“准确率”])
      **Compile **用于配置神经网络的训练方法,告知训练时使用的优化器、损失函数和准确率评测标准。
      其中:
      optimizer 可以是字符串形式给出的优化器名字,也可以是函数形式,使用函数形式可以设置学习率、动量和超参数。可选项包括:
      ‘sgd’or tf.optimizers.SGD( lr=学习率,decay=学习率衰减率,momentum=动量参数)
      ‘adagrad’or tf.keras.optimizers.Adagrad(lr=学习率,decay=学习率衰减率)
      ‘adadelta’or tf.keras.optimizers.Adadelta(lr=学习率,decay=学习率衰减率)
      ‘adam’or tf.keras.optimizers.Adam (lr=学习率,decay=学习率衰减率)

    **Loss **可以是字符串形式给出的损失函数的名字,也可以是函数形式。可选项包括:
    ‘mse’or tf.keras.losses.MeanSquaredError()
    ‘sparse_categorical_crossentropy’ or tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False)

    损失函数常需要经过 softmax 等函数将输出转化为概率分布的形式。from_logits 则用来标注该损失函数是否需要转换为概率的形式, 取 False 时表示转化为概率分布,取 True 时表示没有转化为概率分布,直接输出。

    Metrics 标注网络评测指标。
    可选项包括:
    ‘accuracy’: y_和 y 都是数值,如 y_=[1] y=[1]。
    ‘categorical_accuracy’: y_和 y 都是以独热码和概率分布表示。如 y_=[0, 1, 0], y=[0.256, 0.695, 0.048]。
    ‘sparse_ categorical_accuracy’: y_是以数值形式给出, y 是以独热码形式给出。如 y_=[1],y=[0.256, 0.695, 0.048]。

    model.fit(训练集的输入特征, 训练集的标签, batch_size, epochs,validation_data = (测试集的输入特征,测试集的标签),validataion_split = 从测试集划分多少比例给训练集,validation_freq = 测试的 epoch 间隔次数)
    fit 函数用于执行训练过程

    model.summary()
    summary 函数用于打印网络结构和参数统计

    iris 数据集代码复现

    见本地代码
    注意:使用 Sequential 可以快速搭建网络结构,但是如果网络包含跳连等其他复杂网络结构, Sequential 就无法表示了。 这就需要使用 class 来声明网络结构。

    MNIST 数据集

    MNIST 数据集一共有 7 万张图片,是 28×28 像素的 0 到 9 手写数字数据集,其中 6 万张用于训练, 1 万张用于测试。每张图片包括 784(28×28)个像素点,使用全连接网络时可将 784 个像素点组成长度为 784 的一维数组,作为输入特征。

    Fashion_mnist 数据集

    Fashion_mnist 数据集具有 mnist 近乎所有的特征,包括 60000 张训练图片和 10000 张测试图片,图片被分为十类,每张图像为 28×28 的分辨率。

    神经网络八股功能扩展

    自制数据集,应对特定应用

    观察数据集数据结构,配成特征标签对

    数据增强

    增大数据量

    image_gen_train=tf.keras.preprocessing.image.ImageDataGenerator(增强方法)
    image_gen_train.fit(x_train)
    

    缩放系数:rescale=所有数据将乘以提供的值
    随机旋转:rotation_range=随机旋转角度数范围
    宽度偏移:width_shift_range=随机宽度偏移量
    高度偏移:height_shift_range=随机高度偏移量
    水平翻转:horizontal_flip=是否水平随机翻转
    随机缩放:zoom_range=随机缩放的范围 [1-n, 1+n]

    image_gen_train = ImageDataGenerator(
    rescale=1./255, #原像素值 0~255 归至 0~1
    rotation_range=45, #随机 45 度旋转
    width_shift_range=.15, #随机宽度偏移 [-0.15,0.15)
    height_shift_range=.15, #随机高度偏移 [-0.15,0.15)
    horizontal_flip=True, #随机水平翻转
    zoom_range=0.5 #随机缩放到 [1-50%, 1+50%]
    

    注:1、 最后model.fit(x_train,y_train,batch_size=32,……)变为model.fit(image_gen_train.flow(x_train, y_train,batch_size=32), ……);
    2、数据增强函数的输入要求是 4 维,通过 reshape 调整; 3、如果报错:缺少scipy 库, pip install scipy 即可。

    数据增强可视化

    代码 show_augmented _images.py

    断点续训,存取模型

    读取模型

    load_weights(路径文件名)

    保存模型

    借助 tensorflow 给出的回调函数,直接保存参数和网络

    cp_callback=tf.keras.callbacks.ModelCheckpoint(
        filepath=路径文件名,
        save_weights_only=True,
        monitor='val_loss', # val_loss or loss
        save_best_only=True)
    history = model.fit(x_train, y_train, batch_size=32,
        epochs=5,validation_data=(x_test, y_test), validation_freq=1)
    callbacks=[cp_callback])
    

    注: monitor 配合 save_best_only 可以保存最优模型,包括:训练损失最小模型、测试损失最小模型、训练准确率最高模型、测试准确率最高模型等。

    参数提取,写至文本

    提取可训练参数

    model.trainable_variables 模型中可训练的参数

    设置 print 输出格式

    np.set_printoptions(precision=小数点后按四舍五入保留几位,threshold=数组元素数量少于或等于门槛值,打印全部元素;否则打印门槛值+1个元素,中间用省略号补充)

    >>> np.set_printoptions(precision=5)
    >>> print(np.array([1.123456789]))
    [1.12346]
    >>> np.set_printoptions(threshold=5)
    >>> print(np.arange(10))
    [0 1 2 … , 7 8 9]
    

    注: precision=np.inf 打印完整小数位; threshold=np.nan /np.inf打印全部数组元素。

    acc/loss 可视化,查看效果

    history=model.fit(训练集数据, 训练集标签, batch_size=, epochs=,validation_split=用作测试数据的比例,validation_data=测试集,validation_freq=测试频率)
    history:
    loss:训练集 loss
    val_loss:测试集 loss
    sparse_categorical_accuracy:训练集准确率
    val_sparse_categorical_accuracy:测试集准确率

    应用程序, 给图识物


    注: 1、输出结果 pred 是张量,需要用 tf.print, print 打印出来是 tf.Tensor([1],shape=(1,), dtype=int64);
    2、去掉二值化,出现无法收敛问题,需要对数据集进行归一化。
    3、load fashion_mnist 数据集 x_train.shape: (60000, 28, 28)无通道数;送入卷积前先 reshape 成 x_train.shape: (60000, 28, 28, 1)

    卷积神经网络

    **卷积的概念: **卷积可以认为是一种有效提取图像特征的方法。 一般会用一个正方形的卷积核,按指定步长,在输入特征图上滑动,遍历输入特征图中的每个像素点。每一个步长,卷积核会与输入特征图出现重合区域,重合区域对应元素相乘、求和再加上偏置项得到输出特征的一个像素点。
    对于彩色图像(多通道) 来说, 卷积核通道数与输入特征一致, 套接后在对应位置上进行乘加和操作,利用三通道卷积核对三通道的彩色特征图做卷积计算。
    用多个卷积核可实现对同一层输入特征的多次特征提取, 卷积核的个数决定输出层的通道(channels)数,即输出特征图的深度。
    **感受野(Receptive Field) 的概念: **卷积神经网络各输出层每个像素点在原始图像上的映射区域大小。
    通常使用:例如2层3/3卷积核来替换1层5/5卷积核的方法。
    **全零填充(padding): **为了保持输出图像尺寸与输入图像一致, 经常会在输入图像周围进行全零填充,如图 5-9 所示,在 5×5 的输入图像周围填 0,则输出特征尺寸不变。

    CBAPD组成

    卷积层C

    tf.keras.layers.Conv2D(
          input_shape = (高, 宽, 通道数), #仅在第一层有
          filters = 卷积核个数, eg 16
          kernel_size = 卷积核尺寸, eg (3, 3)
          strides = 卷积步长, default = 1
          padding = ‘SAME’ or ‘VALID’,
          activation = ‘relu’ or ‘sigmoid’ or ‘tanh’ or ‘softmax’等 #如有 BN 则此处不用写
    )
    

    批标准化层B

    **Batch Normalization(批标准化,BN): **对一小批数据在网络各层的输出做标准化处理(标准化:使数据符合 0 均值, 1 为标准差的分布 )其目的是解决神经网络中梯度消失的问题
    BN 操作通常位于卷积层之后,激活层之前,在 Tensorflow 框架中,通常使用 Keras 中tf.keras.layers.BatchNormalization 函数来构建 BN 层。
    在调用此函数时,需要注意的一个参数是 training,此参数只在调用时指定,在模型进行前向推理时产生作用,当 training = True 时, BN 操作采用当前 batch 的均值和标准差;当training = False 时, BN 操作采用滑动平均(running)的均值和标准差。在 Tensorflow 中,通常会指定 training = False,可以更好地反映模型在测试集上的真实效果。
    滑动平均(running) 的解释:滑动平均,即通过一个个 batch 历史的叠加,最终趋向数据集整体分布的过程,在测试集上进行推理时,滑动平均的参数也就是最终保存的参数。
    此外, Tensorflow 中的 BN 函数其实还有很多参数,其中比较常用的是 momentum,即动量参数, 与 sgd 优化器中的动量参数含义类似但略有区别, 具体作用为滑动平均 running =momentum * running + (1 – momentum) * batch, 一般设置一个比较大的值, 在 Tensorflow 框架中默认为 0.99。

    激活层A

    Activation('relu'),

    池化层P

    **池化(pooling): **池化的作用是减少特征数量(降维)。 最大值池化可提取图片纹
    理,均值池化可保留背景特征.
    在 Tensorflow 框架下,可以利用 Keras 来构建池化层,使用的是 tf.keras.layers.MaxPool2D
    函数或者 tf.keras.layers.AveragePooling2D 函数,具体的使用方法如下:

    #最大值池化
    tf.keras.layers.MaxPool2D(
          pool_size = 池化核尺寸,
          strides = 池化步长,
          padding = ‘SAME’ or ‘VALID’)
    #平均值池化
    tf.keras.layers.AveragePooling2D(
          pool_size = 池化核尺寸,
          strides = 池化步长,
          padding = ‘SAME’ or ‘VALID’)
    

    舍弃层D

    舍弃(Dropout): 在神经网络的训练过程中, 将一部分神经元按照一定概率从神经
    网络中暂时舍弃,使用时被舍弃的神经元恢复链接
    在 Tensorflow 框架下,利用 tf.keras.layers.Dropout 函数构建 Dropout 层,参数为舍弃的
    概率(大于 0 小于 1)。

    总结

    构建神经网络的“八股”套路:
    A) import 引入 tensorflow 及 keras、 numpy 等所需模块。
    B) 读取数据集,课程中所利用的 MNIST、 cifar10 等数据集比较基础,可以直接从 sklearn等模块中引入,但是在实际应用中,大多需要从图片和标签文件中读取所需的数据集。
    C) 搭建所需的网络结构,当网络结构比较简单时,可以利用keras模块中的
    tf.keras.Sequential 来搭建顺序网络模型;但是当网络不再是简单的顺序结构,而是有其它特殊结构出现时(例如 ResNet 中的跳连结构),便需要利用 class 来定义自己的网络结构。前者使用起来更加方便,但实际应用中往往需要利用后者来搭建网络。
    D) 对搭建好的网络进行编译(compile) ,通常在这一步指定所采用的优化器(如 Adam、sgd、 RMSdrop 等)以及损失函数(如交叉熵函数、均方差函数等),选择哪种优化器和损失函数往往对训练的速度和效果有很大的影响,至于具体如何进行选择,前面的章节中有比较详细的介绍。
    E) 将数据输入编译好的网络来进行训练(model.fit) ,在这一步中指定训练轮数 epochs 以及 batch_size 等信息,由于神经网络的参数量和计算量一般都比较大,训练所需的时间也会比较长,尤其是在硬件条件受限的情况下,所以在这一步中通常会加入断点续训以及模型参数保存等功能,使训练更加方便,同时防止程序意外停止导致数据丢失的情况发生。
    F) 将神经网络模型的具体信息打印出来(model.summary) ,包括网络结构、网络各层的参数等,便于对网络进行浏览和检查。

    cifar10应用实例

    import tensorflow as tf
    import os
    import numpy as np
    from matplotlib import pyplot as plt
    from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense
    from tensorflow.keras import Model
    
    np.set_printoptions(threshold=np.inf)
    
    cifar10 = tf.keras.datasets.cifar10
    (x_train, y_train), (x_test, y_test) = cifar10.load_data()
    x_train, x_test = x_train / 255.0, x_test / 255.0
    
    
    class Baseline(Model):
        def __init__(self):
            super(Baseline, self).__init__()
            self.c1 = Conv2D(filters=6, kernel_size=(5, 5), padding='same')  # 卷积层
            self.b1 = BatchNormalization()  # BN层
            self.a1 = Activation('relu')  # 激活层
            self.p1 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')  # 池化层
            self.d1 = Dropout(0.2)  # dropout层
    
            self.flatten = Flatten()
            self.f1 = Dense(128, activation='relu')
            self.d2 = Dropout(0.2)
            self.f2 = Dense(10, activation='softmax')
    
        def call(self, x):
            x = self.c1(x)
            x = self.b1(x)
            x = self.a1(x)
            x = self.p1(x)
            x = self.d1(x)
    
            x = self.flatten(x)
            x = self.f1(x)
            x = self.d2(x)
            y = self.f2(x)
            return y
    
    
    model = Baseline()
    
    model.compile(optimizer='adam',
                  loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
                  metrics=['sparse_categorical_accuracy'])
    
    checkpoint_save_path = "./checkpoint/Baseline.ckpt"
    if os.path.exists(checkpoint_save_path + '.index'):
        print('-------------load the model-----------------')
        model.load_weights(checkpoint_save_path)
    
    cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                     save_weights_only=True,
                                                     save_best_only=True)
    
    history = model.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_test, y_test), validation_freq=1,
                        callbacks=[cp_callback])
    model.summary()
    
    # print(model.trainable_variables)
    file = open('./weights.txt', 'w')
    for v in model.trainable_variables:
        file.write(str(v.name) + '
    ')
        file.write(str(v.shape) + '
    ')
        file.write(str(v.numpy()) + '
    ')
    file.close()
    
    ##################  show  ##################
    
    # 显示训练集和验证集的acc和loss曲线
    acc = history.history['sparse_categorical_accuracy']
    val_acc = history.history['val_sparse_categorical_accuracy']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    
    plt.subplot(1, 2, 1)
    plt.plot(acc, label='Training Accuracy')
    plt.plot(val_acc, label='Validation Accuracy')
    plt.title('Training and Validation Accuracy')
    plt.legend()
    
    plt.subplot(1, 2, 2)
    plt.plot(loss, label='Training Loss')
    plt.plot(val_loss, label='Validation Loss')
    plt.title('Training and Validation Loss')
    plt.legend()
    plt.show()
    

    CNN 经典网络

    LeNet, AlexNet、 VGGNet、 InceptionNet以及 ResNet

    LeNet

    借鉴点:共享卷积核,减少网络参数。


    总体上看,诞生于 1998 年的 LeNet5 与如今一些主流的 CNN 网络相比,其结构可以说是相当简单, 不过它成功地利用“卷积提取特征→全连接分类”的经典思路解决了手写数字识别的问题,对神经网络研究的发展有着很重要的意义。

    AlexNet

    借鉴点:激活函数使用 Relu,提升训练速度; Dropout 防止过拟合
    A) 由五层卷积、三层全连接组成,输入图像尺寸为 224 * 224 * 3,网络规模远大于 LeNet5;
    B) 使用了 Relu 激活函数;
    C) 进行了舍弃( Dropout)操作,以防止模型过拟合,提升鲁棒性;
    D) 增加了一些训练上的技巧,包括数据增强、学习率衰减、权重衰减( L2 正则化)等

    VGGNet

    借鉴点:小卷积核减少参数的同时,提高识别准确率;网络结构规整,适合并行加速。
    A) 第一部分: 两次卷积(64 个 3 * 3 卷积核、 BN、 Relu 激活) →最大池化→Dropout
    B) 第二部分: 两次卷积(128 个 3 * 3 卷积核、 BN、 Relu 激活) →最大池化→Dropout
    C) 第三部分: 三次卷积(256 个 3 * 3 卷积核、 BN、 Relu 激活) →最大池化→Dropout
    D) 第四部分: 三次卷积(512 个 3 * 3 卷积核、 BN、 Relu 激活) →最大池化→Dropout
    E) 第五部分: 三次卷积( 512 个 3 * 3 卷积核、 BN、 Relu 激活) →最大池化→Dropout
    F) 第六部分: 全连接( 512 个神经元) →Dropout→全连接( 512 个神经元) →Dropout→全连接( 10 个神经元)

    InceptionNet ????????不会

    InceptionNet旨在通过增加网络的宽度来提升网络的能力,与 VGGNet 通过卷积层堆叠的方式(纵向)相比,是一个不同的方向(横向)。显然, InceptionNet 模型的构建与 VGGNet 及之前的网络会有所区别,不再是简单的纵向堆叠,要理解 InceptionNet 的结构,首先要理解它的基本单元。

    可以看到, InceptionNet 的基本单元中,卷积部分是比较统一的 C、 B、 A 典型结构,即卷积→BN→激活, 激活均采用 Relu 激活函数, 同时包含最大池化操作。
    在 Tensorflow 框架下利用 Keras 构建 InceptionNet 模型时,可以将 C、 B、 A 结构封装在一起,定义成一个新的 ConvBNRelu 类,以减少代码量,同时更便于阅读。

    参数 ch 代表特征图的通道数,也即卷积核个数; kernelsz 代表卷积核尺寸; strides 代表
    卷积步长; padding 代表是否进行全零填充。
    完成了这一步后,就可以开始构建 InceptionNet 的基本单元了,同样利用 class 定义的
    方式,定义一个新的 InceptionBlk 类

    ResNet ????????不会

    借鉴点:层间残差跳连,引入前方信息,减少梯度消失,使神经网络层数变身成为可能

    ResNet 的核心是残差结构。在残差结构中, ResNet 不再让下一层直接拟合我们想得到的底层映射,而是令其对一种残差映射进行拟合。若期望得到的底层映射为H(x), 我们令堆叠的非线性层拟合另一个映射F(x)=H(x)–x, 则原有映射变为F(x)+x。对这种新的残差映射进行优化时,要比优化原有的非相关映射更为容易。不妨考虑极限情况,如果一个恒等映射是最优的,那么将残差向零逼近显然会比利用大量非线性层直接进行拟合更容易。
    值得一提的是,这里的相加与 InceptionNet 中的相加是有本质区别的, Inception 中的相加是沿深度方向叠加, 像“千层蛋糕”一样, 对层数进行叠加; ResNet 中的相加则是特征图对应元素的数值相加,类似于 python 语法中基本的矩阵相加。
    ResNet 引入残差结构最主要的目的是解决网络层数不断加深时导致的梯度消失问题,从之前介绍的 4 种 CNN 经典网络结构我们也可以看出,网络层数的发展趋势是不断加深的。这是由于深度网络本身集成了低层/中层/高层特征和分类器,以多层首尾相连的方式存在,所以可以通过增加堆叠的层数(深度)来丰富特征的层次,以取得更好的效果。
    但如果只是简单地堆叠更多层数,就会导致梯度消失(爆炸)问题,它从根源上导致了函数无法收敛。然而,通过标准初始化( normalized initialization)以及中间标准化层(intermediate normalization layer),已经可以较好地解决这个问题了,这使得深度为数十层的网络在反向传播过程中,可以通过随机梯度下降(SGD)的方式开始收敛。
    但是,当深度更深的网络也可以开始收敛时,网络退化的问题就显露了出来:随着网络深度的增加,准确率先是达到瓶颈(这是很常见的),然后便开始迅速下降。需要注意的是,这种退化并不是由过拟合引起的。对于一个深度比较合适的网络来说,继续增加层数反而会导致训练错误率的提升。
    ResNet 解决的正是这个问题,其核心思路为:对一个准确率达到饱和的浅层网络,在它后面加几个恒等映射层(即 y = x,输出等于输入),增加网络深度的同时不增加误差。这使得神经网络的层数可以超越之前的约束,提高准确率。




    总结

  • 相关阅读:
    简单的实现UIpicker上面的取消确定按钮
    ios 笔记
    KVO 简单使用
    iOS 返回到根目录实现
    ios 实现简单的断点续传下载 nsurlconnection
    cocos2d 安装mac
    iOS 自定义自动锁屏时间
    PHP面向对象——单例模式
    PHP面向对象——构造函数、析构函数
    PHP面向对象——多态
  • 原文地址:https://www.cnblogs.com/hujunyao37201/p/13368611.html
Copyright © 2020-2023  润新知