neural networks and deep learning

项目地址:https://github.com/mnielsen/neural-networks-and-deep-learning.git

1.使用neural nets识别手写字

  • 感知机(perceptron),阶跃函数,$\epsilon(z)=\begin{cases} 1 & \text{z>0}\ 0 & \text{z<0} \end{cases} $:

    • n个二进制输入xi,一个二进制输出y, y ( x ) = ε ( w ⋅ x + b ) y(x)=\varepsilon(w\cdot x+b) y(x)=ε(wx+b);可模拟基本逻辑函数。
    • 原理是:通过加权证据做出决策
    • 偏置bias:度量感知机激活的难易程度。
    • 组成的网络在权重和偏置的微小变化可能导致输出的剧烈变化
  • 主要使用的神经元模型为sigmoid neuron, σ ( z ) = 1 1 + e − z \sigma(z)=\frac{1}{1+e^{-z}} σ(z)=1+ez1

    • 学习原理:权重和偏置的微小变化可导致输出的微小变化
  • 数据集MNIST,训练集6万(包括1万验证集),测试集1万;大小28*28、灰度图

  • 二次损失函数,均方误差(MSE): C ( w , b ) = 1 2 n ∑ x ∣ ∣ y ( x ) − a ∣ ∣ 2 C(w,b)=\frac{1}{2n}\sum_x ||y(x)-a||^2 C(w,b)=2n1xy(x)a2,衡量预测与真值的近似程度,应尽可能的小。1/2方便求导。

  • 梯度下降(gradient descent):

    • Δ C = ∇ C ⋅ ∇ v = ( ∂ C ∂ w , ∂ C ∂ b ) T ⋅ ( ∇ w , ∇ b ) \Delta C=\nabla C \cdot \nabla v=(\frac{\partial C}{\partial w },\frac{\partial C}{\partial b })^T\cdot(\nabla w,\nabla b) ΔC=Cv=(wC,bC)T(w,b)
    • ∇ v = − η ∇ C \nabla v=-\eta \nabla C v=ηC;则 ∇ C \nabla C C一直为负值,持续迭代则可使 C C C最小, η \eta η学习率
    • 所以分类问题变为最优化问题:
      1. 求梯度 ∇ C \nabla C C
      2. 计算 v → v , = v − η ∇ C v\to v^,=v-\eta \nabla C vv,=vηC,使 w , b w,b w,b向梯度的反方向移动,使 C C C降低。
      3. 重复1,2使 C C C最小。
    • 注意: ∇ C \nabla C C是在整个训练集上进行求解的,如果每次迭代都在整个集合上进行,则非常慢。
  • 随机梯度下降(stochastic gradient descent):用随机抽取的一小样本集上的梯度 ∇ C x \nabla C_x Cx估计总体梯度 ∇ C \nabla C C。即 ∇ C ≈ 1 m ∑ j = 1 m ∇ C x j \nabla C \approx \frac{1}{m} \sum_{j=1}^m \nabla C_{x_j} Cm1j=1mCxj。小样本集即为mini-batch

  • 将整个训练集分为n个互斥mini-batch,对每个mini-batch执行后,称为一个epoch

  • w、b迭代公式为: w k → w k , = w k − η m ∑ j ∂ C x j ∂ w k b l → b l , = b l − η m ∑ j ∂ C x j ∂ b l \begin{aligned}w_k \to w_k^,=w_k-\frac{\eta}{m}\sum_j \frac{\partial C_{x_j}}{\partial w_k} \\ b_l\to b_l^,=b_l-\frac{\eta}{m}\sum_j \frac{\partial C_{x_j}}{\partial b_l}\end{aligned} wkwk,=wkmηjwkCxjblbl,=blmηjblCxj

  • 以上的 w , b w,b w,b代表网络中所有的参数,是矩阵。

  • 整个算法流程

    1. 参数 w , b w,b w,b随机初始化;
    2. mini-batch里的每个样本 x i x_i xi作为输入,通过前向传播(feedforward)计算输出值 y ( x i ) y(x_i) y(xi)
    3. 计算损失函数(cost function),根据链式法则、反向传播(backpropagation)计算所有参数的变化值。
    4. 求mini-batch上的平均值,并更新所有 w , b w,b w,b参数。重复2-4,直到完成规定的epoch或者,损失函数小于一定值。

    准确率95%左右,增加隐藏层到100,达96%左右。增大学习率,准确率明星降低。

2.backpropagation如何工作的

  • 符号表示:

    • w j k l w_{jk}^l wjkl:表示连接 l − 1 l-1 l1层的第 k k k个神经元与 l l l层的第 j j j个神经元的权重。

    • b j l b_j^l bjl:表示 l l l层的第 j j j个神经元的偏置。

    • a j l a_j^l ajl:表示 l l l层的第 j j j个神经元的激励,这个神经元向下一层的输出。

      在这里插入图片描述

    • 三者间的关系:
      KaTeX parse error: No such environment: equation* at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲*̲}̲a_j^l=\sigma(\s…
      其中 w l ∈ R M × K w^l\in R^{M\times K} wlRM×K b ∈ R M b\in R^M bRM;M为l层的神经元个数,K为l-1层的神经元个数。

  • The four fundamental equations behind backpropagation

    • 定义l层第j个神经元的误差error δ j l = ∂ C ∂ z j l \delta_j^l=\frac{\partial C}{\partial z_j^l} δjl=zjlC,表示l层第j个神经元上发生 Δ z j l \Delta z_j^l Δzjl的变化,导致C的变化程度,则:
      KaTeX parse error: No such environment: gather* at position 8: \begin{̲g̲a̲t̲h̲e̲r̲*̲}̲ \delta_j^L = …
  • 证明:链式求导法则: z = f ( u , v ) , u = φ ( x , y ) , v = ψ ( x , y ) z=f(u,v),u=\varphi(x,y),v=\psi(x,y) z=f(u,v),u=φ(x,y),v=ψ(x,y),则 ∂ z ∂ x = ∂ z ∂ u ∂ u ∂ x + ∂ z ∂ v ∂ v ∂ x \frac{\partial z}{\partial x}=\frac{\partial z}{\partial u}\frac{\partial u}{\partial x}+\frac{\partial z}{\partial v}\frac{\partial v}{\partial x} xz=uzxu+vzxv

    • δ j L = ∂ C ∂ z j L = ∑ k ∂ C ∂ a k L ∂ a k L ∂ z j L = 当 j ≠ k , 则 后 面 一 项 为 0 ∂ C ∂ a j L σ ′ ( z j L ) \delta_j^L=\frac{\partial C}{\partial z_j^L}=\sum_k \frac{\partial C}{\partial a_k^L}\frac{\partial a_k^L}{\partial z_j^L}\xlongequal{当j\neq k,则后面一项为0}\frac{\partial C}{\partial a_j^L}\sigma'(z_j^L) δjL=zjLC=kakLCzjLakLj=k,0 ajLCσ(zjL)

    • 因为: z k l + 1 = ∑ j w k j l + 1 σ ( z j l ) + b k l + 1 ⇒ ∂ z k l + 1 ∂ z j l = w k j l + 1 σ ′ ( z j l ) z_k^{l+1}=\sum_j w_{kj}^{l+1}\sigma(z_j^l)+b_k^{l+1}\Rightarrow\frac{\partial z_k^{l+1}}{\partial z_j^l}=w_{kj}^{l+1}\sigma'(z_j^l) zkl+1=jwkjl+1σ(zjl)+bkl+1zjlzkl+1=wkjl+1σ(zjl),k为下标;

      所以: δ j l = ∂ C ∂ z j l = ∑ k ∂ C ∂ z k l + 1 ∂ z k l + 1 ∂ z j l = ∑ k δ k l + 1 ∂ z k l + 1 ∂ z j l = ∑ k w k j l + 1 δ k l + 1 σ ′ ( z j l ) \delta_j^l=\frac{\partial C}{\partial z_j^l}=\sum_k\frac{\partial C}{\partial z_k^{l+1}}\frac{\partial z_k^{l+1}}{\partial z_j^l}=\sum_k \delta_k^{l+1}\frac{\partial z_k^{l+1}}{\partial z_j^l}=\sum_k w_{kj}^{l+1}\delta_k^{l+1}\sigma'(z_j^l) δjl=zjlC=kzkl+1Czjlzkl+1=kδkl+1zjlzkl+1=kwkjl+1δkl+1σ(zjl)。k为l+1层神经元的个数;

    • 因为: z j l = ∑ k w j k l a k l − 1 + b j l ⇒ ∂ z j l ∂ b j l = 1 ; ∂ z j l ∂ w j k l = a k l − 1 z_j^l=\sum_k w_{jk}^l a_k^{l-1}+b_j^l \Rightarrow \frac{\partial z_j^l}{\partial b_j^l}=1;\frac{\partial z_j^l}{\partial w_{jk}^l}=a_k^{l-1} zjl=kwjklakl1+bjlbjlzjl=1;wjklzjl=akl1;所以:

      • ∂ C ∂ b j l = ∑ k ∂ C ∂ z k l ∂ z k l ∂ b j l = 当 j ≠ k , 则 后 面 一 项 为 0 δ j l \frac{\partial C}{\partial b_j^l}=\sum_k\frac{\partial C}{\partial z_k^l}\frac{\partial z_k^l}{\partial b_j^l}\xlongequal{当j\neq k,则后面一项为0}\delta_j^l bjlC=kzklCbjlzklj=k,0 δjl
      • ∂ C ∂ w j k l = ∑ i ∂ C ∂ z i l ∂ z i l ∂ w j k l = 当 i ≠ j , 则 后 面 一 项 为 0 δ j l a k l − 1 \frac{\partial C}{\partial w_{jk}^l}=\sum_i\frac{\partial C}{\partial z_i^l}\frac{\partial z_i^l}{\partial w_{jk}^l}\xlongequal{当i\neq j,则后面一项为0}\delta_j^la_k^{l-1} wjklC=izilCwjklzili=j,0 δjlakl1
  • 代码:

    class Network(object):
        def __init__(self , sizes):
            self.num_layers = len(sizes)
            self.sizes = sizes
            self.biases = [np.random.randn(y, 1) for y in sizes[1:]] #第一是输入层,没有偏置
            self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]
            
        def feedforward(self, a):
            for b, w in zip(self.biases, self.weights):
                a = sigmoid(np.dot(w, a)+b)
            return a
        
        def SGD(self, training_data, epochs, mini_batch_size, eta, test_data=None):
            if test_data: 
                n_test = len(test_data)
            n = len(training_data)
            for j in xrange(epochs):
                random.shuffle(training_data)
                mini_batches = [training_data[k:k+mini_batch_size] for k in xrange(0, n, mini_batch_size)]
                for mini_batch in mini_batches:
                    self.update_mini_batch(mini_batch, eta)  #更新参数
                if test_data:
                    print "Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test)
                else:
                    print "Epoch {0} complete".format(j)
                    
        def update_mini_batch(self, mini_batch, eta):
            nabla_b = [np.zeros(b.shape) for b in self.biases]
            nabla_w = [np.zeros(w.shape) for w in self.weights]
            for x, y in mini_batch:
                delta_nabla_b, delta_nabla_w = self.backprop(x, y)  #求每个样本参数的梯度,再求和
                nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
                nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
            self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)] #更新参数
            self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)]
            
        def backprop(self, x, y):
            nabla_b = [np.zeros(b.shape) for b in self.biases]
            nabla_w = [np.zeros(w.shape) for w in self.weights]
            # feedforward
            activation = x
            activations = [x] 
            zs = [] 
            for b, w in zip(self.biases, self.weights):
                z = np.dot(w, activation)+b   #每个神经元的输入
                zs.append(z)
                activation = sigmoid(z)  #每个神经元的输出
                activations.append(activation)
            # backward pass
            delta = self.cost_derivative(activations[-1], y) * sigmoid_prime(zs[-1])  #最后一层的delta
            nabla_b[-1] = delta
            nabla_w[-1] = np.dot(delta, activations[-2].transpose())
            for l in xrange(2, self.num_layers):  #依次往前计算delta_w,delta_b
                z = zs[-l]
                sp = sigmoid_prime(z)
                delta = np.dot(self.weights[-l+1].transpose(), delta) * sp  #计算前面一层的delta^(l-1)
                nabla_b[-l] = delta
                nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
            return (nabla_b, nabla_w)
        
        def evaluate(self, test_data):
            test_results = [(np.argmax(self.feedforward(x)), y) for (x, y) in test_data]
            return sum(int(x == y) for (x, y) in test_results)
    
        def cost_derivative(self, output_activations, y):
            return (output_activations-y)
    
    def sigmoid(z):
        return 1.0/(1.0+np.exp(-z))
    
    def sigmoid_prime(z):
        return sigmoid(z)*(1-sigmoid(z))
    

3. Improving the way neural networks learn

  • 使用sigmoid函数,当输出接近1,变化缓慢;如图右上角最简单的模型,输入1,输出0;对于二次损失函数 C = ( y − a ) 2 2 C=\frac{(y-a)^2}{2} C=2(ya)2,可以求得导数 ∂ C ∂ w = ( a − y ) σ ′ ( z ) x = a σ ′ ( z ) \frac{\partial C}{\partial w}=(a-y)\sigma'(z)x=a\sigma'(z) wC=(ay)σ(z)x=aσ(z) ∂ C ∂ b = ( a − y ) σ ′ ( z ) = a σ ′ ( z ) \frac{\partial C}{\partial b}=(a-y)\sigma'(z)=a\sigma'(z) bC=(ay)σ(z)=aσ(z)。当w,b初始值为2时,学习会如图一样非常慢。

在这里插入图片描述

  • cross-entropy cost function
    C = − 1 n ∑ x [ y ln ⁡ a + ( 1 − y ) ln ⁡ ( 1 − a ) ] (eq1) C=-\frac{1}{n}\sum_x[y\ln a+(1-y)\ln(1-a)]\tag{eq1} C=n1x[ylna+(1y)ln(1a)](eq1)
    偏导为:
    ∂ C ∂ w j = 1 n ∑ x σ ′ ( z ) x j σ ( z ) ( 1 − σ ( z ) ) ( σ ( z ) − y ) (eq2) \frac{\partial C}{\partial w_j}=\frac{1}{n}\sum_x \frac{\sigma'(z)x_j}{\sigma(z)(1-\sigma(z))}(\sigma(z)-y) \tag{eq2} wjC=n1xσ(z)(1σ(z))σ(z)xj(σ(z)y)(eq2)
    sigmoid函数的导数为: σ ′ ( z ) = σ ( z ) ( 1 − σ ( z ) ) \sigma'(z)=\sigma(z)(1-\sigma(z)) σ(z)=σ(z)(1σ(z));则eq2变为:
    ∂ C ∂ w j = 1 n ∑ x x j ( σ ( z ) − y ) (eq3) \frac{\partial C}{\partial w_j}=\frac{1}{n}\sum_x x_j(\sigma(z)-y) \tag{eq3} wjC=n1xxj(σ(z)y)(eq3)
    参数学习的速度正比于输出的偏差,越大变化越快。准确率相比之前提高一点点。

  • Softmax:定义一个新的输出层,在输出层不使用sigmoid函数,而是通过以下公式产生输出的概率发布。
    a j L = e z j L ∑ k e z k L (eq4) a_j^L=\frac{e^{z_j^L}}{\sum_k e^{z_k^L}} \tag{eq4} ajL=kezkLezjL(eq4)
    损失函数: C = − ln ⁡ a j L C=-\ln a_j^L C=lnajL

  • Overfitting and regularization:测试集上的效果并没有像训练集那样好。

在这里插入图片描述

  • early stopping(hold out method):使用验证集来确定训练过程中,在验证集上出现效果最好的时候作为最终模型,

  • 降低过拟合最简单的办法是增加训练集数量。或者降低网络的规模。

  • 或者正则化(regularization):如L2正则化,
    C = C 0 + λ 2 n ∑ w w 2 (eq5) C=C_0+\frac{\lambda}{2n}\sum_w w^2 \tag{eq5} C=C0+2nλww2(eq5)

  • Dropout:通过修改网络结构,通过每次mini-batch随机删掉部分神经元,训练多个网络,然后用如投票的方式组合成更好的网络。

  • Artificially expanding the training data:微调数据,增加训练集数量。

  • Weight initialization

  • rectified linear unit max ⁡ ( 0 , z ) \max(0,z) max(0,z)

  • 代码:

    class QuadraticCost(object):
        @staticmethod
        def fn(a, y):
            return 0.5*np.linalg.norm(a-y)**2
        
        @staticmethod
        def delta(z, a, y):
            return (a-y) * sigmoid_prime(z)
    
    class CrossEntropyCost(object):
        @staticmethod
        def fn(a, y):
            return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a)))
        
        @staticmethod
        def delta(z, a, y):
            return (a-y)
        
    class Network(object):
        ……
        def default_weight_initializer(self):
            self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]
            # 更改初始化方法,效果好一点
            self.weights = [np.random.randn(y,x)/np.sqrt(x) for x, y in zip(self.sizes[:-1], self.sizes[1:])]
    
        def update_mini_batch(self, mini_batch, eta, lmbda, n):
            ……
            self.weights = [(1-eta*(lmbda/n))*w-(eta/len(mini_batch))*nw #添加了正则项
                            for w, nw in zip(self.weights, nabla_w)]
            self.biases = [b-(eta/len(mini_batch))*nb
                           for b, nb in zip(self.biases, nabla_b)]
    
        def backprop(self, x, y):
            ……
            delta = (self.cost).delta(zs[-1], activations[-1], y)
            nabla_b[-1] = delta
            nabla_w[-1] = np.dot(delta, activations[-2].transpose())
            ……
    

4. A visual proof that neural nets can compute any function

5.Why are deep neural networks hard to train?

  • 对于一些函数来说,浅层网络相比深层网络,需要的神经元呈指数级的增长。
  • unstable gradients
    • vanishing gradient problem前面的隐藏层比后面的隐藏层学得慢。如图所示,黄色的条表示参数的变化幅度。
    • exploding gradient problem:与vanishing相反,

在这里插入图片描述

6.Deep learning

  • 卷积神经网络的基本概念:

    • Local receptive fields:隐藏层的一个神经元只连接原图的一小部分相邻区域
    • Shared weights and biases:将局部感受域在整个图上滑动,作为隐藏层不同神经元的输入,连接中的权重和偏置不改变。并将该输入层到隐藏层的映射称为一个feature map。一组共享的权重和偏置确定了一个feature map。这组参数也称为kernel or filter
    • Pooling layers:简化卷积层输出的信息,如:L2 poolingMax-pooling

在这里插入图片描述

  • 实践:

    • 之前的网络,测试集上的准确率为97.80%。

    • 卷积神经网络:准确率为98.78%。

    • 继续添加卷积池化层,准确率可达99.06%;第二个卷积层的输入为20个12*12的feature map,设置了40个kernel,每个kernel的参数为20*5*5+1,对应将20个feature map上的同一感受野域作为输入。

  • 将sigmoid函数改为rectified linear units(ReLU),准确率为99.23%。

  • 扩展训练集,准确率为99.37%;再添加一个全连接层,准确率为99.43%;再利用dropout获得99.60%。

深度学习框架PyTorch:入门与实践

项目地址:https://github.com/chenyuntc/pytorch-book

2 快速入门

2.2 PyTorch 入门第一步

  • Tensor是PyTorch中重要的数据结构,可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)以及更高维的数组。

  • autograd:自动微分,autograd.Variable是Autograd中的核心类,它简单封装了Tensor,并支持几乎所有Tensor有的操作。Tensor在被封装为Variable之后,可以调用它的.backward实现反向传播,自动计算所有梯度,Variable主要包含三个属性。

    • data:保存Variable所包含的Tensor
    • grad:保存data对应的梯度,grad也是个Variable,而不是Tensor,它和data的形状一样。 在反向传播过程中是累加的,这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以反向传播之前需把梯度清零。
    • grad_fn:指向一个Function对象,这个Function用来反向传播计算输入的梯度。
  • 小试牛刀:CIFAR-10分类

    import torch as t
    import torchvision as tv
    import torchvision.transforms as transforms
    from torchvision.transforms import ToPILImage
    import torch.nn as nn
    import torch.nn.functional as F
    from torch import optim
    #模型
    class Net(nn.Module):
        # 把网络中具有可学习参数的层放在构造函数__init__中。如果某一层(如ReLU)不具有可学习的参数,
        # 则既可以放在构造函数中,也可以不放,但建议不放在其中,而在forward中使用nn.functional代替。
        def __init__(self):
            super(Net, self).__init__()
            self.conv1 = nn.Conv2d(3, 6, 5) 
            self.conv2 = nn.Conv2d(6, 16, 5)  
            self.fc1   = nn.Linear(16*5*5, 120)  
            self.fc2   = nn.Linear(120, 84)
            self.fc3   = nn.Linear(84, 10)
        # 只要在nn.Module的子类中定义了forward函数,backward函数就会自动被实现(利用autograd)。
        # 在forward 函数中可使用任何tensor支持的函数,还可以使用if、for循环、print、log等Python语法,
        # 写法和标准的Python写法一致。
        def forward(self, x): 
            x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) 
            x = F.max_pool2d(F.relu(self.conv2(x)), 2) 
            x = x.view(x.size()[0], -1) 
            x = F.relu(self.fc1(x))
            x = F.relu(self.fc2(x))
            x = self.fc3(x)        
            return x
        # 网络的可学习参数通过net.parameters()返回,net.named_parameters可同时返回可学习的参数及名称。
    # 训练    
    def train(trainloader,net):
        # 交叉熵损失函数
        criterion = nn.CrossEntropyLoss() 
        # 在反向传播计算完所有参数的梯度后,还需要使用优化方法来更新网络的权重和参数,
        optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
    
        t.set_num_threads(8)  #设置线程
        for epoch in range(2):      
            running_loss = 0.0
            for i, data in enumerate(trainloader, 0): #0表示从0开始
                inputs, labels = data # 输入数据
    
                optimizer.zero_grad() # 梯度清零
                outputs = net(inputs)            
                loss = criterion(outputs, labels) # 计算损失
                loss.backward()   #反向传播
                optimizer.step()  # 更新参数 
    
                running_loss += loss.item() # loss 是一个scalar,需要使用loss.item()来获取数值,不能使用loss[0]
                if i % 2000 == 1999: # 每2000个batch打印一下训练状态
                    print('[%d, %5d] loss: %.3f' % (epoch+1, i+1, running_loss / 2000))
                    running_loss = 0.0
        print('Finished Training')
    
    # 测试    
    def test(testloader,net):
        correct,total = 0,0
        # 由于测试的时候不需要求导,可以暂时关闭autograd,提高速度,节约内存
        with t.no_grad():
            for data in testloader:
                images, labels = data
                outputs = net(images)
                _, predicted = t.max(outputs, 1)
                total += labels.size(0)
                correct += (predicted == labels).sum()
    
        print('10000张测试集中的准确率为: %d %%' % (100 * correct / total))    
        
    def main():
        #数据加载
        transform = transforms.Compose([  # 定义对数据的预处理
                transforms.ToTensor(), # 转为Tensor
                transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),]) # 归一化
        # 训练集
        trainset = tv.datasets.CIFAR10(root='./',train=True, download=True,transform=transform)
        trainloader = t.utils.data.DataLoader(trainset, batch_size=4,shuffle=True, num_workers=2)
        # 测试集
        testset = tv.datasets.CIFAR10('./',train=False, download=True, transform=transform)
        testloader = t.utils.data.DataLoader(testset,batch_size=4, shuffle=False, num_workers=2)
        
        net=Net()    
        train(trainloader,net)
        test(testloader,net)
    

3 Tensor and Autograd

3.1 tensor

  • 从接口的角度来讲,对tensor的操作可分为两类:

    • torch.function,如torch.save等。
    • tensor.function,如tensor.view等
  • 函数名以_结尾的都是inplace方式, 即会修改调用者自己的数据,在实际应用中需加以区分。

  • Numpy和Tensor共享内存,当numpy的数据类型和Tensor的类型不一样的时候,数据会被复制,不会共享内存。

  • 小试牛刀:线性回归

    import torch as t
    from matplotlib import pyplot as plt
    
    device = t.device('cpu') #如果你想用gpu,改成t.device('cuda:0')
    
    def get_fake_data(batch_size=8):
        ''' 产生随机数据:y=x*2+3,加上了一些噪声'''
        global device
        x = t.rand(batch_size, 1, device=device) * 5
        y = x * 2 + 3 +  t.randn(batch_size, 1, device=device)
        return x, y
    
    def main():
        global device
        t.manual_seed(1000) # 设置随机数种子,保证在不同电脑上运行时下面的输出一致
        w = t.rand(1, 1).to(device) # 随机初始化参数
        b = t.zeros(1, 1).to(device)
        lr =0.02  # 学习率
        for ii in range(50):
            x, y = get_fake_data(batch_size=4)
            # forward:计算loss
            y_pred = x.mm(w) + b.expand_as(y) # x@W等价于x.mm(w);for python3 only
            loss = 0.5 * (y_pred - y) ** 2 # 均方误差
            loss = loss.mean()
            # backward:手动计算梯度
            dloss = 1
            dy_pred = dloss * (y_pred - y)
            dw = x.t().mm(dy_pred)
            db = dy_pred.sum()
            # 更新参数
            w.sub_(lr * dw)
            b.sub_(lr * db)
            # 可视化
            plt.ion() # 打开交互模式
            if ii%10 ==0:
                plt.cla() #清除原有图像
                x = t.arange(0, 6).view(-1, 1)
                y = x.float().mm(w) + b.expand_as(x)
                x2, y2 = get_fake_data(batch_size=32) 
                # 画图
                plt.plot(x.cpu().numpy(), y.cpu().numpy()) # 预测线
                plt.scatter(x2.numpy(), y2.numpy()) # 真实散点图,与训练的不是一批数据
                plt.xlim(0, 5)
                plt.ylim(0, 13)
                plt.pause(0.5)
        print('w: ', w.item(), 'b: ', b.item())
    

3.2 autograd

  • torch.autograd就是为方便用户使用,而专门开发的一套自动求导引擎,它能够根据输入和前向传播过程自动构建计算图,并执行反向传播。计算图(Computation Graph)是现代深度学习框架如PyTorch和TensorFlow等的核心,其为高效自动求导算法——反向传播(Back Propogation)提供了理论支持。

  • 可以认为需要求导(requires_grad)的tensor即Variable。autograd记录对tensor的操作记录用来构建计算图。Variable提供了大部分tensor支持的函数,但其不支持部分inplace函数,因这些函数会修改tensor自身,而在反向传播中,variable需要缓存原来的tensor来计算反向传播梯度。如果想要计算各个Variable的梯度,只需调用根节点variable的backward方法,autograd会自动沿着计算图反向传播,计算每一个叶子节点的梯度。variable.backward(gradient=None, retain_graph=None, create_graph=None)

  • 如果想要修改tensor的数值,但是又不希望被autograd记录,那么我么可以对tensor.data进行操作

  • 扩展autograd:写一个Function,实现它的前向传播和反向传播代码,Function对应于计算图中的矩形, 它接收参数,计算并返回结果。

  • 小试牛刀: 用Variable实现线性回归

    import torch as t
    from matplotlib import pyplot as plt
    import numpy as np
    …… 
    def main():
        ……
        # 随机初始化参数
        w = t.rand(1,1, requires_grad=True)
        b = t.zeros(1,1, requires_grad=True)
        losses = np.zeros(500)
        lr =0.02 # 学习率
        for ii in range(500):
            ……
            losses[ii] = loss.item()
            # backward:手动计算梯度
            loss.backward()
            # 更新参数
            w.data.sub_(lr * w.grad.data) # w.data
            b.data.sub_(lr * b.grad.data)
            # 梯度清零
            w.grad.data.zero_()
            b.grad.data.zero_()
            ……
        plt.cla() 
        plt.ioff() # 关闭交互模式
        plt.plot(losses)
        plt.show()
    

4 神经网络工具箱nn

  • torch.nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络/层。

    • 自定义层必须继承nn.Module,并且在其构造函数中需调用nn.Module的构造函数,super(Linear, self).__init__()
    • 在构造函数__init__中必须自己定义可学习的参数,并封装成Parameter之类的,
    • forward函数实现前向传播过程。
  • PyTorch实现了神经网络中绝大多数的layer,这些layer都继承于nn.Module,封装了可学习参数parameter,并实现了forward函数,且很多都专门针对GPU运算进行了CuDNN优化,其速度和性能都十分优异。注意:输入的不是单个数据,而是一个batch。输入只有一个数据,则必须调用tensor.unsqueeze(0) 或 tensor[None]将数据伪装成batch_size=1的batch

  • 将每一层的输出直接作为下一层的输入,这种网络称为前馈传播网络(feedforward neural network)。对于此类网络如果每次都写复杂的forward函数会有些麻烦,在此就有两种简化方式,ModuleList和Sequential。其中Sequential是一个特殊的module,它包含几个子Module,前向传播时会将输入一层接一层的传递下去。ModuleList也是一个特殊的module,可以包含几个子module,可以像用list一样使用它,但不能直接把输入传给ModuleList,当在Module中使用它的时候,就能自动识别为子module。

  • 小试牛刀:搭建ResNet

    from torch import  nn
    import torch as t
    from torch.nn import  functional as F
    
    class ResidualBlock(nn.Module):
        '''
        实现子module: Residual Block
        '''
        def __init__(self, inchannel, outchannel, stride=1, shortcut=None):
            super(ResidualBlock, self).__init__()
            self.left = nn.Sequential(
                    nn.Conv2d(inchannel,outchannel,3,stride, 1,bias=False),
                    nn.BatchNorm2d(outchannel),
                    nn.ReLU(inplace=True),
                    nn.Conv2d(outchannel,outchannel,3,1,1,bias=False),
                    nn.BatchNorm2d(outchannel) )
            self.right = shortcut
    
        def forward(self, x):
            out = self.left(x)
            residual = x if self.right is None else self.right(x)
            out += residual
            return F.relu(out)
    
    class ResNet(nn.Module):
        '''
        实现主module:ResNet34
        ResNet34 包含多个layer,每个layer又包含多个residual block
        用子module来实现residual block,用_make_layer函数来实现layer
        '''
        def __init__(self, num_classes=1000):
            super(ResNet, self).__init__()
            # 前几层图像转换
            self.pre = nn.Sequential(
                    nn.Conv2d(3, 64, 7, 2, 3, bias=False),
                    nn.BatchNorm2d(64),
                    nn.ReLU(inplace=True),
                    nn.MaxPool2d(3, 2, 1))
            
            # 重复的layer,分别有3,4,6,3个residual block
            self.layer1 = self._make_layer( 64, 64, 3)
            self.layer2 = self._make_layer( 64, 128, 4, stride=2)
            self.layer3 = self._make_layer( 128, 256, 6, stride=2)
            self.layer4 = self._make_layer( 256, 512, 3, stride=2)
    
            #分类用的全连接
            self.fc = nn.Linear(512, num_classes)
        
        def _make_layer(self,  inchannel, outchannel, block_num, stride=1):
            '''
            构建layer,包含多个residual block
            '''
            shortcut = nn.Sequential(
                    nn.Conv2d(inchannel,outchannel,1,stride, bias=False),
                    nn.BatchNorm2d(outchannel))
            
            layers = []
            layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))
            
            for i in range(1, block_num):
                layers.append(ResidualBlock(outchannel, outchannel))
            return nn.Sequential(*layers)
            
        def forward(self, x):
            x = self.pre(x)
            
            x = self.layer1(x)
            x = self.layer2(x)
            x = self.layer3(x)
            x = self.layer4(x)
    
            x = F.avg_pool2d(x, 7)
            x = x.view(x.size(0), -1)
            return self.fc(x)
    

5. PyTorch常用工具模块

from torch.utils import datafrom torchvision import transforms as T

  • 加载数据:自定义的数据集对象需要继承Dataset。实现两个方法:__getitem__:返回一条数据,或一个样本。obj[index]等价于obj.__getitem__(index)__len__:返回样本的数量。len(obj)等价于obj.__len__()。如果所有的文件按文件夹保存,每个文件夹下存储同一个类别的图片,文件夹名为类名,可用ImageFolder(root, transform=None, target_transform=None, loader=default_loader);label是按照文件夹名顺序排序后存成字典,即{类名:类序号(从0开始)}。
    • 批处理: DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=default_collate, pin_memory=False, drop_last=False)
  • 数据处理:transform = T.Compose()
    • 计算机视觉工具包torchvision:models、datasets、transforms
    • 转GPU,.cuda()

6.文件组织结构

- `checkpoints/`: 用于保存训练好的模型,可使程序在异常退出后仍能重新载入模型,恢复训练
- `data/`:数据相关操作,包括数据预处理、dataset实现等
- `models/`:模型定义,可以有多个模型,例如上面的AlexNet和ResNet34,一个模型对应一个文件
- `utils/`:可能用到的工具函数,在本次实验中主要是封装了可视化工具
- `config.py`:配置文件,所有可配置的变量都集中在此,并提供默认值
- `main.py`:主文件,训练和测试程序的入口,可通过不同的命令来指定不同的操作和参数
- `requirements.txt`:程序依赖的第三方库
- `README.md`:提供程序的必要说明
  • 批处理: DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=default_collate, pin_memory=False, drop_last=False)
  • 数据处理:transform = T.Compose()
    • 计算机视觉工具包torchvision:models、datasets、transforms
    • 转GPU,.cuda()

6.文件组织结构

- `checkpoints/`: 用于保存训练好的模型,可使程序在异常退出后仍能重新载入模型,恢复训练
- `data/`:数据相关操作,包括数据预处理、dataset实现等
- `models/`:模型定义,可以有多个模型,例如上面的AlexNet和ResNet34,一个模型对应一个文件
- `utils/`:可能用到的工具函数,在本次实验中主要是封装了可视化工具
- `config.py`:配置文件,所有可配置的变量都集中在此,并提供默认值
- `main.py`:主文件,训练和测试程序的入口,可通过不同的命令来指定不同的操作和参数
- `requirements.txt`:程序依赖的第三方库
- `README.md`:提供程序的必要说明
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐