(笔记)深度学习入门:基于python的理论与实践

本书主要用numpy来处理运算,用matplotlib将结果可视化。对于没有基础的人十分友好,只需要高中的线代知识就可以看懂。

1.x=x.flatten() 将矩阵及张量转换为一维数组。x>15,结果得到一个由True和False组成的一维列表,x[x>15]即可输出所有大于15的成员。

矩阵与一维数组的乘,(数组长度与矩阵列数相同)将一维数组扩张为矩阵形状后简单相乘,符合交换率,符号为
矩阵与数组dot乘,(数组长度与矩阵列数相同)将一维数组与每行对应索引相乘求和,得到一维数组,长度为矩阵的行数:如[[1, 2], [3, 4], [7, 8]] dot [5,6],则得[15+26, 35+46,75+86],不符合交换律,用np.dot计算。

一次绘图流程:p42

2.感知机:接受一个或多个输入,输出一个信号。有多个输入时,可以给各个输入配置权重。机器学习就是人给机器一些数据,机器根据这些数据选出权重的过程。
P56从图像上,感知机是一条直线,有局限性。叠加多个感知机可以构造类似曲线。

3.对于单层感知机,实际上有两个函数,比如首先有y=b+x1w1+x2w2,其中b为偏置,w1、w2为权重;然后通过激活函数,比如h(x)=1或0(当y<=0时为0,否则为1。这种函数的输出是跳跃的,可称为阶跃函数)。若把激活函数换成其他形式,就接近神经网络的概念了。常用的一个激活函数(sigmoid函数):1/(1+exp(-x)),其中exp(-x)为e的-x次方。

接受numpy数组为参数的阶跃函数见p69。

激活函数也可以使用ReLU函数,其在x<=0时输出0,在x>0时则为线性关系。P74

对于输出层的激活函数,如果是分类问题(类似阶跃函数)用softmax函数,回归问题(连续输出数值)用恒等函数。P89
softmax函数的输出是连续的,总和为1,可以看作概率。为了节省算力,对未知模型进行预测时通常也可以省略输出层的softmax激活函数(接受数据进行学习时可以不省略)。

在分类问题中,输出层的神经元数量通常等于类别的数量。哪个神经元的输出最大,就会归类为该类别。

4.正规化:将数据进行处理,使其限制在某个范围内。正规化是一种预处理。

批处理:相同的神经网络,多个输出,多个结果。批处理可以大大加快运算速度。P101

5.损失函数:描述当前神经网络与测试数据(即监督数据)在多大程度上不拟合。一般可用均方误差函数和交叉熵。均方考虑了非正确解、越接近0(越小)越好,交叉熵只包含正确解、越接近0(越小)越好。
机器学习就是要使得损失函数值最小。如果训练数据太多,可以从中随机选择部分数据来学习,称为mini batch。
不用精确度作为评价标准,因为要设定参数的导数(变化梯度),如果用精确度为指标,容易导致导数为0,参数无法更新(精确度常常是跳跃的,微调无法产生影响,而损失函数是连续变化的)。这也是sigmoid函数优于阶跃函数的地方(激活函数)。

对于矩阵,y[0,2]的写法等同于y[0][2]

梯度指示的方向是损失函数减少量最多的地方,不一定指向损失函数值最小的地方。所以,机器学习可能会陷入局部最小值、鞍点或者学习平原。
鞍点处的梯度也为0,比如一个马鞍形曲面,(0,0,0)处的形状就很像马鞍。

梯度,就是一个一维数组。一个函数有多个输入,那么梯度就是这个多维函数(在某一点处)的导数。梯度数组的大小和输入多少相等,意义是对每一个输入,在保持其他输入不变时求该输入的导数,最终组成一个导数数组。为了使损失函数减小,需要沿负梯度方向下降。

6.反向传播法算梯度的速度比微积分法快许多,主要应用了复合函数的求导(相乘)。

7.有时SGD(随机梯度下降),即沿着梯度最大的地方下山的方法,不一定能最快下山。还有其他一些方法。

Momentum(动量法),如果某一方向上的梯度较小,但是持续很久,就适合用这种方法。模拟静止小球在山中的运动,某一方向累计的梯度会给小球积累该方向的速度,于是在该方向上移动越来越快。

AdaGrad(Adaptive适应梯度法),对梯度中的每一个导数都有一个学习率,该学习率还会不断衰减。AdaGrad是通过记录过去所有梯度的平方和实现的,最终学习率会衰减到0。不想让学习率衰减太快可以用RMSProp法,其会更多地考虑最近的梯度、逐渐减小久远梯度的权重。

Adam(直观上讲,类似于融合了AdaGrad与Momentum法,2015年提出),甚至可对一些超参数调校。

一些技巧:
(1)权重的初始值:对于sigmoid或tanh等激活函数,使用Xavier方法,上一层的节点个数为n,本层的初始值大概是标准差1/sqrt(n)的高斯分布;
对relu激活函数,可以使用He初始值。
在手头数据集少时,可以“学习迁移”,即训练完一个数据集,将权重的最终值作为下一次学习的初始值。
(2)调整激活值的分布:使用batch normalization,优点是增大学习率(学的快)、对初始值不会过于敏感、抑制过拟合。
(3)抑制过拟合:过拟合主要是由于训练数据少、网络参数太多(层数多、节点多等)。过拟合的表现常常是训练数据准确度高,测试数据准确度不佳。对简单网络,常用权值衰减来抑制过拟合。对复杂网络,可用Dropout方法,随机删除一些节点。另外,可以用已有的数据生成新的数据,比如对手写数字图片,可对图片进行平移、旋转等操作。
(4)高效寻找超参数:对于网络层数与节点数、batch大小、学习率、权值衰减等超参数,需要高效寻找之。可在训练数据和测试数据之外再分一个验证数据,用来对探讨超参数取什么值,然后让超参数不断*10,在对数尺度上变化,并观测其对学习的影响。一般来说,测试数据应该只在最后用一次。
(5)GPU运算:GPU擅长大量的并行运算,比如矩阵的乘法;而CPU擅长连续的复杂运算。使用GPU,可以将训练和测试时间大大缩短。据书中称,NVIDIA的显卡比AMD的更合适。
(6)分布式学习:和用GPU一样,可缩短学习时间。Google的TensorFlow和微软的CNTK都很重视分布式学习。

8.CNN(Convolutional Neural Network),书中之前实现的网络,隐藏层的结构是Affine+Relu(或者其他激活函数),最后一层是Affine+Softmax,而CNN的隐藏层是Conv(卷积层)+Relu+Pooling(池化层),最后一层不变。普通Affine的问题是没有考虑图片的形状,比如矩阵中相距较远的两个点关联度较小,近大。从原理上,卷积层使用了类似滤波器的方法,用了矩阵的*乘,并且偏置只有一个。*乘,即相同大小矩阵的对应位置乘积之和。
池化层则是将层中的数据减少形成特征化的数据,比如只取最大值、只取平均值。

CNN是图像识别中常用的操作。通过观察,CNN的不同Conv层,大概在进行从基础到抽象的观察。

在各种CNN中,书中介绍了CNN元组LeNet及2012年提出的AlexNet。据称,AlexNet是深度学习热潮的导火索。

Google旗下的Deep Mind公司,开发出了AlphaGo和DQN。其中DQN可以接受游戏的图像输入(比如每秒4帧),学习如何玩游戏,甚至可以做出超越人类的手柄操作。

附上看完书后跟着写出来的通用框架:(还可以改的更简单)

from typing import OrderedDict
import numpy as np
import matplotlib.pylab as plt


def step_function(x):  # 激活函数-阶跃函数
    return np.array(x > 0, dtype=np.int)


def sigmoid(x):  # 激活函数-sigmoid连续函数
    return 1 / (1 + np.exp(-x))


class Sigmoid:
    def __init__(self) -> None:
        self.out = None

    def forward(self, x):
        out = 1 / (1 + np.exp(-x))
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out
        return dx


def sigmoid_grad(x):
    return (1.0 - sigmoid(x)) * sigmoid(x)


def ReLU(x):  # 激活函数-ReLU函数
    return np.maximum(0, x)


class Relu:
    def __init__(self):
        self.mask = None

    def forward(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0
        return out

    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout
        return dx


def softmax(x):  # 激活函数-softmax函数
    if x.ndim == 2:  # 如果传递的是batch而非单个图片的识别结果,则需要一点处理
        x = x.T  # x.T是翻转后的矩阵,如[[1, 2, 3], [4, 5, 6]]变成[[1 4],[2 5],[3 6]]
        # 用x.T是因为广播功能对数组扩增是扩增其行数而非列数
        x = x - np.max(x, axis=0)
        # np.max(x, axis=0)是一个一维数组,通过广播功能使其扩增
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        # np.sum(np.exp(x), axis=0)也是一个一维数组,也通过广播功能使其扩增
        return y.T
    # 对单个图片处理较简单
    x = x - np.max(x)  # 减最大值是为了防止溢出,即e^x指数过大
    return np.exp(x) / np.sum(np.exp(x))


def mean_square_error(y, t):  # 损失函数-均方误差
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    return 0.5 * np.sum((y - t)**2) / y.shape[0]


def cross_entropy_error(y, t):  # 损失函数-交叉熵函数
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    delta = 1e-7  # 避免ln(0)出现负无穷-inf
    return -np.sum(t * np.log(y + delta)) / y.shape[0]


def numerical_diff(f, x):  # 数值微分求导-中心差分
    h = 1e-4
    return (f(x + h) - f(x - h)) / (2 * h)


def numerical_gradient(f, x):  # 求梯度。梯度相当于对函数每个输入加一个带正负的权重,表明改动每个输入的影响
    h = 1e-4
    grad = np.zeros_like(x)
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:  # 对每个x,保持其他x不变,求单个x的导数
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)  # f(x+h)
        x[idx] = tmp_val - h
        fxh2 = f(x)  # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2 * h)  # 这里求的是负梯度。正梯度用后减前
        x[idx] = tmp_val  # 还原值
        it.iternext()
    return grad  # 最后得到导数数组,即梯度


class Affine:
    def __init__(self, W, b):
        self.W = W
        self.b = b

        self.x = None
        self.original_x_shape = None
        # 权重和偏置参数的导数
        self.dW = None
        self.db = None

    def forward(self, x):
        # 对应张量
        self.original_x_shape = x.shape
        x = x.reshape(x.shape[0], -1)
        self.x = x

        out = np.dot(self.x, self.W) + self.b

        return out

    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)

        dx = dx.reshape(*self.original_x_shape)  # 还原输入数据的形状(对应张量)
        return dx


class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None
        self.y = None
        self.x = None

    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        return self.loss

    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        dx = (self.y - self.t) / batch_size
        return dx


class ThreeLayerNet:
    def __init__(self,
                 input_size,
                 hidden_size1,
                 hidden_size2,
                 output_size,
                 weight_init_std=0.01):
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(
            input_size, hidden_size1)
        self.params['b1'] = np.zeros(hidden_size1)
        self.params['W2'] = weight_init_std * np.random.randn(
            hidden_size1, hidden_size2)
        self.params['b2'] = np.zeros(hidden_size2)
        self.params['W3'] = weight_init_std * np.random.randn(
            hidden_size2, output_size)
        self.params['b3'] = np.zeros(output_size)
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
        self.layers['Relu2'] = Relu()
        self.layers['Affine3'] = Affine(self.params['W3'], self.params['b3'])
        self.lastlayer = SoftmaxWithLoss()

    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        return x

    def loss(self, x, t):
        y = self.predict(x)
        return self.lastlayer.forward(y, t)

    def accuracy(self, x, t):  # 监督数据的形式为batch*10
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy

    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        grads['W3'] = numerical_gradient(loss_W, self.params['W3'])
        grads['b3'] = numerical_gradient(loss_W, self.params['b3'])
        return grads

    def gradient(self, x, t):  # 反向传播法高速求梯度
        # 正向一次
        self.loss(x, t)
        # 反向求梯度
        dout = 1
        dout = self.lastlayer.backward(dout)
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        grads = {}
        grads['W1'] = self.layers['Affine1'].dW
        grads['b1'] = self.layers['Affine1'].db
        grads['W2'] = self.layers['Affine2'].dW
        grads['b2'] = self.layers['Affine2'].db
        grads['W3'] = self.layers['Affine3'].dW
        grads['b3'] = self.layers['Affine3'].db

        return grads


class SGD:  # 随机梯度下降法
    def __init__(self, lr=0.01):
        self.lr = lr

    def update(self, params, grads):
        for key in params.keys():
            params[key] -= self.lr * grads[key]


class Momentum:  #动量法
    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.v = None

    def update(self, params, grads):
        if self.v is None:
            self.v = {}
            for key, val in params.items():
                self.v[key] = np.zeros_like(val)

        for key in params.keys():
            self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]
            params[key] += self.v[key]


class AdaGrad:  # 学习率简单衰减法
    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None

    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)

        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)


class Adam:  # 类似于融合了AdaGrad和Momentum
    """Adam (http://arxiv.org/abs/1412.6980v8)"""
    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None

    def update(self, params, grads):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)

        self.iter += 1
        lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (
            1.0 - self.beta1**self.iter)

        for key in params.keys():
            #self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]
            #self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)
            self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])

            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)

            #unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias
            #unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias
            #params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)


if __name__ == '__main__':
    from check_download_pickle_mnist import load_mnist
    (i_train, l_train), (i_test, l_test) = load_mnist(one_hot_label=True)
    train_loss_list = []
    train_acc_list = []
    test_acc_list = []
    iter_per_epoch = 60000 // 100
    network = ThreeLayerNet(input_size=784,
                            hidden_size1=50,
                            hidden_size2=100,
                            output_size=10)
    optimizer = Adam()

    for i in range(10000):
        batch_mask = np.random.choice(60000, 100)
        i_batch = i_train[batch_mask]
        l_batch = l_train[batch_mask]

        grads = network.gradient(i_batch, l_batch)
        params = network.params
        optimizer.update(params, grads)

        loss = network.loss(i_batch, l_batch)
        train_loss_list.append(loss)

        if i % iter_per_epoch == 0:
            train_acc = network.accuracy(i_train, l_train)
            test_acc = network.accuracy(i_test, l_test)
            train_acc_list.append(train_acc)
            test_acc_list.append(test_acc)
            print("train acc, test acc | " + str(train_acc) + ", " +
                  str(test_acc))

    x = range(len(train_loss_list))
    plt.rcParams['font.sans-serif'] = ['SimHei']  #显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  #这两行需要手动设置
    plt.plot(x, train_loss_list)
    plt.xlabel('次数')
    plt.ylabel('loss')
    plt.show()

    x = range(len(train_acc_list))
    plt.plot(x, train_acc_list, label='train')
    plt.plot(x, test_acc_list, linestyle='--', label='test')
    plt.xlabel('epoch')
    plt.ylabel('acc')
    plt.legend()
    plt.show()

你可能感兴趣的:(python,深度学习,python,神经网络,深度学习)