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)=ε(w⋅x+b);可模拟基本逻辑函数。
- 原理是:通过加权证据做出决策。
- 偏置bias:度量感知机激活的难易程度。
- 组成的网络在权重和偏置的微小变化可能导致输出的剧烈变化。
-
主要使用的神经元模型为sigmoid neuron, σ ( z ) = 1 1 + e − z \sigma(z)=\frac{1}{1+e^{-z}} σ(z)=1+e−z1:
- 学习原理:权重和偏置的微小变化可导致输出的微小变化。
-
数据集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)=2n1∑x∣∣y(x)−a∣∣2,衡量预测与真值的近似程度,应尽可能的小。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=∇C⋅∇v=(∂w∂C,∂b∂C)T⋅(∇w,∇b)。
- 令 ∇ v = − η ∇ C \nabla v=-\eta \nabla C ∇v=−η∇C;则 ∇ C \nabla C ∇C一直为负值,持续迭代则可使 C C C最小, η \eta η为学习率。
- 所以分类问题变为最优化问题:
- 求梯度 ∇ C \nabla C ∇C。
- 计算 v → v , = v − η ∇ C v\to v^,=v-\eta \nabla C v→v,=v−η∇C,使 w , b w,b w,b向梯度的反方向移动,使 C C C降低。
- 重复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} ∇C≈m1∑j=1m∇Cxj。小样本集即为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} wk→wk,=wk−mηj∑∂wk∂Cxjbl→bl,=bl−mηj∑∂bl∂Cxj
-
以上的 w , b w,b w,b代表网络中所有的参数,是矩阵。
-
整个算法流程:
- 参数 w , b w,b w,b随机初始化;
- mini-batch里的每个样本 x i x_i xi作为输入,通过前向传播(feedforward)计算输出值 y ( x i ) y(x_i) y(xi);
- 计算损失函数(cost function),根据链式法则、反向传播(backpropagation)计算所有参数的变化值。
- 求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 l−1层的第 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} wl∈RM×K, b ∈ R M b\in R^M b∈RM;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=∂zjl∂C,表示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 = …
- 定义l层第j个神经元的误差
-
证明:链式求导法则: 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} ∂x∂z=∂u∂z∂x∂u+∂v∂z∂x∂v。
-
δ 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=∂zjL∂C=∑k∂akL∂C∂zjL∂akL当j=k,则后面一项为0∂ajL∂Cσ′(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+1⇒∂zjl∂zkl+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=∂zjl∂C=∑k∂zkl+1∂C∂zjl∂zkl+1=∑kδkl+1∂zjl∂zkl+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=∑kwjklakl−1+bjl⇒∂bjl∂zjl=1;∂wjkl∂zjl=akl−1;所以:
- ∂ 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 ∂bjl∂C=∑k∂zkl∂C∂bjl∂zkl当j=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} ∂wjkl∂C=∑i∂zil∂C∂wjkl∂zil当i=j,则后面一项为0δjlakl−1。
-
-
代码:
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(y−a)2,可以求得导数 ∂ C ∂ w = ( a − y ) σ ′ ( z ) x = a σ ′ ( z ) \frac{\partial C}{\partial w}=(a-y)\sigma'(z)x=a\sigma'(z) ∂w∂C=(a−y)σ′(z)x=aσ′(z), ∂ C ∂ b = ( a − y ) σ ′ ( z ) = a σ ′ ( z ) \frac{\partial C}{\partial b}=(a-y)\sigma'(z)=a\sigma'(z) ∂b∂C=(a−y)σ′(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+(1−y)ln(1−a)](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} ∂wj∂C=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} ∂wj∂C=n1x∑xj(σ(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λw∑w2(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 pooling
,Max-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
函数实现前向传播过程。
- 自定义层必须继承nn.Module,并且在其构造函数中需调用nn.Module的构造函数,
-
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 data
,from 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`:提供程序的必要说明