neural networks and deep learning
项目地址:/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 = …
证明:链式求导法则: 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 = sizesself.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 adef 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]# feedforwardactivation = xactivations = [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 passdelta = self.cost_derivative(activations[-1], y) * sigmoid_prime(zs[-1]) #最后一层的deltanabla_b[-1] = deltanabla_w[-1] = np.dot(delta, activations[-2].transpose())for l in xrange(2, self.num_layers): #依次往前计算delta_w,delta_bz = zs[-l]sp = sigmoid_prime(z)delta = np.dot(self.weights[-l+1].transpose(), delta) * sp #计算前面一层的delta^(l-1)nabla_b[-l] = deltanabla_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):@staticmethoddef fn(a, y):return 0.5*np.linalg.norm(a-y)**2@staticmethoddef delta(z, a, y):return (a-y) * sigmoid_prime(z)class CrossEntropyCost(object):@staticmethoddef fn(a, y):return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a)))@staticmethoddef 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))*nbfor 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] = deltanabla_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:入门与实践
项目地址:/chenyuntc/pytorch-book
2 快速入门
2.2 PyTorch 入门第一步
Tensor
是PyTorch中重要的数据结构,可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)以及更高维的数组。
autograd
:自动微分,autograd.Variable是Autograd中的核心类,它简单封装了Tensor,并支持几乎所有Tensor有的操作。Tensor在被封装为Variable之后,可以调用它的.backward实现反向传播,自动计算所有梯度,Variable主要包含三个属性。
data:保存Variable所包含的Tensorgrad:保存data对应的梯度,grad也是个Variable,而不是Tensor,它和data的形状一样。 在反向传播过程中是累加的,这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以反向传播之前需把梯度清零。grad_fn:指向一个Function对象,这个Function用来反向传播计算输入的梯度。
小试牛刀:CIFAR-10分类
import torch as timport torchvision as tvimport torchvision.transforms as transformsfrom torchvision.transforms import ToPILImageimport torch.nn as nnimport torch.nn.functional as Ffrom 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.0for 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.0print('Finished Training')# 测试 def test(testloader,net):correct,total = 0,0# 由于测试的时候不需要求导,可以暂时关闭autograd,提高速度,节约内存with t.no_grad():for data in testloader:images, labels = dataoutputs = net(images)_, predicted = t.max(outputs, 1)total += labels.size(0)correct += (predicted == labels).sum()print('10000张测试集中的准确率为: %d %%' % (100 * correct / total)) def main():#数据加载transform = pose([ # 定义对数据的预处理transforms.ToTensor(), # 转为Tensortransforms.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 tfrom matplotlib import pyplot as pltdevice = t.device('cpu') #如果你想用gpu,改成t.device('cuda:0')def get_fake_data(batch_size=8):''' 产生随机数据:y=x*2+3,加上了一些噪声'''global devicex = t.rand(batch_size, 1, device=device) * 5y = x * 2 + 3 + t.randn(batch_size, 1, device=device)return x, ydef main():global devicet.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:计算lossy_pred = x.mm(w) + b.expand_as(y) # x@W等价于x.mm(w);for python3 onlyloss = 0.5 * (y_pred - y) ** 2 # 均方误差loss = loss.mean()# backward:手动计算梯度dloss = 1dy_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 tfrom matplotlib import pyplot as pltimport 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.datab.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 nnimport torch as tfrom torch.nn import functional as Fclass 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 = shortcutdef forward(self, x):out = self.left(x)residual = x if self.right is None else self.right(x)out += residualreturn F.relu(out)class ResNet(nn.Module):'''实现主module:ResNet34ResNet34 包含多个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 blockself.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 = pose()
计算机视觉工具包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 = pose()
计算机视觉工具包torchvision:models、datasets、transforms转GPU,.cuda()
6.文件组织结构
- `checkpoints/`: 用于保存训练好的模型,可使程序在异常退出后仍能重新载入模型,恢复训练- `data/`:数据相关操作,包括数据预处理、dataset实现等- `models/`:模型定义,可以有多个模型,例如上面的AlexNet和ResNet34,一个模型对应一个文件- `utils/`:可能用到的工具函数,在本次实验中主要是封装了可视化工具- `config.py`:配置文件,所有可配置的变量都集中在此,并提供默认值- `main.py`:主文件,训练和测试程序的入口,可通过不同的命令来指定不同的操作和参数- `requirements.txt`:程序依赖的第三方库- `README.md`:提供程序的必要说明