https://handbook.pytorch.wiki/chapter1/1.3-deep-learning-with-pytorch-60-minute-blitz.html
改变jupter notebook默认文件夹位置https://blog.csdn.net/limanjihe/article/details/106317245
安装pytorch
1 2 import torchtorch.__version__
1 tensor Tensor的基本数据类型有五种: - 32位浮点型:torch.FloatTensor。 (默认) - 64位整型:torch.LongTensor。 - 32位整型:torch.IntTensor。 - 16位整型:torch.ShortTensor。 - 64位浮点型:torch.DoubleTensor。
除以上数字类型外,还有 byte和chart型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 x=torch.empty(5 ,4 ) print (x)x=torch.rand(5 ,4 ) print (x)x = torch.zeros(5 , 3 , dtype=torch.long) print (x)x = torch.tensor([5.5 , 3 ]) print (x)x = torch.randn_like(x, dtype=torch.float ) print (x) torch.Size([5 , 3 ,3 ])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 y=torch.rand(5 ,3 ) print (y)x=torch.rand(5 ,3 ) print (x)y.add_(x) print (y)print (y[:,1 ])print (y[:1 ])x = torch.randn(4 , 4 ) y = x.view(16 ) z = x.view(-1 , 8 ) print (x.size(), y.size(), z.size())print (x, y, z)x = torch.randn(1 ) print (x)print (x.item())import numpyt=x.numpy() print (t)a=numpy.ones(4 ) b=torch.from_numpy(a) numpy.add(a,1 ,out=a) print (b)
1 2 3 4 5 6 7 8 9 10 11 print (torch.cuda.is_available())if torch.cuda.is_available(): device = torch.device("cuda" ) y = torch.ones_like(x, device=device) x = x.to(device) z = x + y print (z) print (z.to("cpu" , torch.double))
2 Autograd
https://pytorch.org/docs/autograd
当完成计算后通过调用 .backward(),自动计算所有的梯度, 这个张量的所有梯度将会自动积累到 .grad 属性。
要阻止张量跟踪历史记录,可以调用.detach()方法将其与计算历史记录分离,并禁止跟踪它将来的计算记录。
为了防止跟踪历史记录(和使用内存),可以将代码块包装在with torch.no_grad():中。 在评估模型时特别有用,因为模型可能具有requires_grad = True的可训练参数,但是我们不需要梯度计算。
Tensor 和 Function互相连接并生成一个非循环图,它表示和存储了完整的计算历史。 每个张量都有一个.grad_fn属性,这个属性引用了一个创建了Tensor的Function(除非这个张量是用户手动创建的,即,这个张量的 grad_fn 是 None)。
https://zhuanlan.zhihu.com/p/83172023
grad: 该Tensor的梯度值,每次在计算backward时都需要将前一时刻的梯度归零,否则梯度值会一直累加。
grad_fn: 叶子节点通常为None,只有结果节点的grad_fn才有效,用于指示梯度函数是哪种类型。例如上面示例代码中的y.grad_fn=<PowBackward0 at 0x213550af048>, z.grad_fn=<AddBackward0 at 0x2135df11be0>
torch.autograd.backward( tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)
tensor: 用于计算梯度的tensor。也就是说这两种方式是等价的
torch.autograd.backward(z) == z.backward()
grad_tensors: 在计算矩阵的梯度时会用到。他其实也是一个tensor,shape一般需要和前面的tensor保持一致。
retain_graph: 通常在调用一次backward后,pytorch会自动把计算图销毁,所以要想对某个变量重复调用backward,则需要将该参数设置为True
create_graph: 当设置为True的时候可以用来计算更高阶的梯度
grad_variables: 这个官方说法是grad_variables' is deprecated. Use 'grad_tensors' instead.也就是说这个参数后面版本中应该会丢弃,直接使用grad_tensors就好了。
torch.autograd.grad( outputs, inputs, grad_outputs=None, retain_graph=None, create_graph=False, only_inputs=True, allow_unused=False)
grad_outputs: 类似于backward方法中的grad_tensors
only_inputs: 默认为True, 如果为True, 则只会返回指定input的梯度值。 若为False,则会计算所有叶子节点的梯度,并且将计算得到的梯度累加到各自的.grad属性上去。
allow_unused: 默认为False, 即必须要指定input,如果没有指定的话则报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import torchx=torch.ones(2 ,2 ,requires_grad=True ) print (x)y=x+2 print (y)print (y.grad_fn) z = y * y * 3 +y out = z.mean() print (z, out)z.grad_fn.next_functions
dir(z)返回很多,我们直接排除掉一些Python中特殊方法(以__开头和结束的)和私有方法(以_开头的,直接看几个比较主要的属性: .is_leaf:记录是否是叶子节点。通过这个属性来确定这个变量的类型 在官方文档中所说的“graph leaves”,“leaf variables”,都是指像x,y这样的手动创建的、而非运算得到的变量,这些变量称为创建变量。 像z这样的,是通过计算后得到的结果称为结果变量。
next_functions就是grad_fn的精华
在PyTorch的反向图计算中,AccumulateGrad类型代表的就是叶子节点类型,也就是计算图终止节点。AccumulateGrad类中有一个.variable属性指向叶子节点。
x_leaf.variable
当我们执行z.backward()的时候。这个操作将调用z里面的grad_fn这个属性,执行求导的操作。
这个操作将遍历grad_fn的next_functions,然后分别取出里面的Function(AccumulateGrad),执行求导操作。这部分是一个递归的过程直到最后类型为叶子节点。
计算出结果以后,将结果保存到他们对应的variable 这个变量所引用的对象(x和y)的 grad这个属性里面。
求导结束。所有的叶节点的grad变量都得到了相应的更新
最终当我们执行完c.backward()之后,a和b里面的grad值就得到了更新。
扩展Autogradhttps://handbook.pytorch.wiki/chapter2/2.1.2-pytorch-basics-autograd.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 x = torch.ones(2 ,requires_grad=True ) z = x + 2 z.backward(torch.ones_like(z)) print (x.grad)x = torch.tensor([2. , 1. ], requires_grad=True ) y = torch.tensor([[1. , 2. ], [3. , 4. ]], requires_grad=True ) z = torch.mm(x.view(1 , 2 ), y) print (f"z:{z} " )z.backward(torch.Tensor([[1. , 0 ]]), retain_graph=True ) print (f"x.grad: {x.grad} " )print (f"y.grad: {y.grad} " )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 x = torch.randn(3 , requires_grad=True ) y = x * 2 while y.data.norm() < 1000 : y = y * 2 print (y)gradients = torch.tensor([0.1 , 1.0 , 0.0001 ], dtype=torch.float ) y.backward(gradients,retain_graph=True ) print (x.grad)print (y)y.backward(torch.ones(3 ),retain_graph=True ) print (x.grad)y.backward(torch.ones_like(x)) print (x.grad)
1 2 3 4 5 6 7 8 print (x.requires_grad)print ((x ** 2 ).requires_grad)with torch.no_grad(): print ((x ** 2 ).requires_grad)
3 neural network
https://pytorch.org/docs/stable/nn.html
一个nn.Module包含各个层和一个forward(input)方法,该方法返回output。 神经网络的典型训练过程如下:
定义包含一些可学习的参数(或者叫权重)神经网络模型; 在数据集上迭代; 通过神经网络处理输入; 计算损失(输出结果和正确值的差值大小); 将梯度反向传播回网络的参数; 更新网络的参数,主要使用如下简单的更新原则: weight = weight - learning_rate * gradient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import torchimport torch.nn as nnimport torch.nn.functional as Fclass Net (nn.Module): def __init__ (self ): super (Net, self).__init__() self.conv1 = nn.Conv2d(1 , 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 ) 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(-1 , self.num_flat_features(x)) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x def num_flat_features (self, x ): size = x.size()[1 :] num_features = 1 for s in size: num_features *= s return num_features net = Net() print (net)params = list (net.parameters()) print (len (params))print (params[0 ].size()) input = torch.randn(1 , 1 , 32 , 32 )out = net(input ) print (out)net.zero_grad() out.backward(torch.randn(1 , 10 ))
激活函数
为什么激活函数都是非线性的 在神经网络的计算过程中,每层都相当于矩阵相乘,无论神经网络有多少层输出都是输入的线性组合,就算我们有几千层的计算,无非还是个矩阵相乘,和一层矩阵相乘所获得的信息差距不大,所以需要激活函数来引入非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中,增加了神经网络模型泛化的特性。
sigmoid 在sigmoid函数中我们可以看到,其输出是在(0,1)这个开区间,它能够把输入的连续实值变换为0和1之间的输出,如果是非常大的负数,那么输出就是0;如果是非常大的正数输出就是1,起到了抑制的作用。 但是sigmod由于需要进行指数运算(这个对于计算机来说是比较慢,相比relu),再加上函数输出不是以0为中心的(这样会使权重更新效率降低),当输入稍微远离了坐标原点,函数的梯度就变得很小了(几乎为零)。在神经网络反向传播的过程中不利于权重的优化,这个问题叫做梯度饱和,也可以叫梯度弥散。这些不足,所以现在使用到sigmod基本很少了,基本上只有在做二元分类(0,1)时的输出层才会使用。
tanh tanh是双曲正切函数,输出区间是在(-1,1)之间,而且整个函数是以0为中心的 与sigmoid函数类似,当输入稍微远离了坐标原点,梯度还是会很小,但是好在tanh是以0为中心点,如果使用tanh作为激活函数,还能起到归一化(均值为0)的效果。 一般二分类问题中,隐藏层用tanh函数,输出层用sigmod函数,但是随着Relu的出现所有的隐藏层基本上都使用relu来作为激活函数了
relu z>0时,梯度始终为1,从而提高神经网络基于梯度算法的运算速度。然而当 z<0时,梯度一直为0。 ReLU函数只有线性关系(只需要判断输入是否大于0)不管是前向传播还是反向传播,都比sigmod和tanh要快很多 当输入是负数的时候,ReLU是完全不被激活的,这就表明一旦输入到了负数,ReLU就会死掉。但是到了反向传播过程中,输入负数,梯度就会完全到0,这个和sigmod函数、tanh函数有一样的问题。 但是实际的运用中,该缺陷的影响不是很大。
Leaky Relu 为了解决relu函数z<0时的问题出现了 Leaky ReLU函数,该函数保证在z<0的时候,梯度仍然不为0。 理论上来讲,Leaky ReLU有ReLU的所有优点,但是在实际操作当中,并没有完全证明Leaky ReLU总是好于ReLU。
ReLU目前仍是最常用的activation function,在隐藏层中推荐优先尝试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import torchimport torch.nn.functional as Fimport matplotlib.pyplot as pltimport numpy as npx= torch.linspace(-10 ,10 ,60 ) ax = plt.gca() ax.spines['right' ].set_color('none' ) ax.spines['top' ].set_color('none' ) ax.xaxis.set_ticks_position('bottom' ) ax.spines['bottom' ].set_position(('data' , 0 )) ax.yaxis.set_ticks_position('left' ) ax.spines['left' ].set_position(('data' , 0 )) plt.ylim((0 , 1 )) sigmod=torch.sigmoid(x) plt.plot(x.numpy(),sigmod.numpy()) ax = plt.gca() ax.spines['right' ].set_color('none' ) ax.spines['top' ].set_color('none' ) ax.xaxis.set_ticks_position('bottom' ) ax.spines['bottom' ].set_position(('data' , 0 )) ax.yaxis.set_ticks_position('left' ) ax.spines['left' ].set_position(('data' , 0 )) plt.ylim((-1 , 1 )) tanh=torch.tanh(x) plt.plot(x.numpy(),tanh.numpy()) ax = plt.gca() ax.spines['right' ].set_color('none' ) ax.spines['top' ].set_color('none' ) ax.xaxis.set_ticks_position('bottom' ) ax.spines['bottom' ].set_position(('data' , 0 )) ax.yaxis.set_ticks_position('left' ) ax.spines['left' ].set_position(('data' , 0 )) plt.ylim((-3 , 10 )) relu=F.relu(x) plt.plot(x.numpy(),relu.numpy()) ax = plt.gca() ax.spines['right' ].set_color('none' ) ax.spines['top' ].set_color('none' ) ax.xaxis.set_ticks_position('bottom' ) ax.spines['bottom' ].set_position(('data' , 0 )) ax.yaxis.set_ticks_position('left' ) ax.spines['left' ].set_position(('data' , 0 )) plt.ylim((-3 , 10 )) l_relu=F.leaky_relu(x,0.1 ) plt.plot(x.numpy(),l_relu.numpy())
torch.nn
只支持小批量输入。整个 torch.nn
包都只支持小批量样本,而不支持单个样本。 例如,nn.Conv2d
接受一个4维的张量, 每一维分别是sSamples * nChannels * Height * Width(样本数*通道数*高*宽)
。 如果你有单个样本,只需使用 input.unsqueeze(0)
来添加其它的维数
损失函数
nn.L1Loss: 差的绝对值
nn.NLLLoss: 用于多分类的负对数似然损失函数。NLLLoss中如果传递了weights参数,会对损失进行加权
nn.MSELoss: 均方损失函数
nn.CrossEntropyLoss: 多分类用的交叉熵损失函数,LogSoftMax和NLLLoss集成到一个类中,会调用nn.NLLLoss函数,可以理解为CrossEntropyLoss()=log_softmax() + NLLLoss()。因为使用了NLLLoss,所以也可以传入weight参数。一般多分类用这个
nn.BCELoss: 计算 x 与 y 之间的二进制交叉熵。与NLLLoss类似,也可以添加权重参数。用的时候需要在该层前面加上 Sigmoid 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 output = net(input ) target = torch.randn(10 ) target = target.view(1 , -1 ) criterion = nn.MSELoss() loss = criterion(output, target) print (loss)print (target)print (loss.grad_fn)print (loss.grad_fn.next_functions[0 ][0 ]) print (loss.grad_fn.next_functions[0 ][0 ].next_functions[0 ][0 ])
1 2 3 4 5 6 7 8 9 10 11 net.zero_grad() print ('conv1.bias.grad before backward' )print (net.conv1.bias.grad)loss.backward() print ('conv1.bias.grad after backward' )print (net.conv1.bias.grad)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 learning_rate = 0.01 for f in net.parameters(): f.data.sub_(f.grad.data * learning_rate) import torch.optim as optimoptimizer = optim.SGD(net.parameters(), lr=0.01 ) optimizer.zero_grad() output = net(input ) loss = criterion(output, target) loss.backward() optimizer.step()
4 cifar10 数据。可以使用标准的Python包来加载数据到一个numpy数组中。 然后把这个数组转换成torch.tensor 图像可以使用 Pillow, OpenCV, torchvision 音频可以使用 scipy, librosa 文本可以使用原始Python和Cython来加载,或者使用 NLTK或 SpaCy 处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import torchimport torchvisionimport torchvision.transforms as transformstransform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5 , 0.5 , 0.5 ), (0.5 , 0.5 , 0.5 ))]) trainset = torchvision.datasets.CIFAR10(root='./data' , train=True , download=True , transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=4 , shuffle=True , num_workers=2 ) testset = torchvision.datasets.CIFAR10(root='./data' , train=False , download=True , transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=4 , shuffle=False , num_workers=2 ) classes = ('plane' , 'car' , 'bird' , 'cat' , 'deer' , 'dog' , 'frog' , 'horse' , 'ship' , 'truck' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import matplotlib.pyplot as pltimport numpy as npdef imshow (img ): img = img / 2 + 0.5 npimg = img.numpy() plt.imshow(np.transpose(npimg, (1 , 2 , 0 ))) dataiter = iter (trainloader) images, labels = dataiter.next () imshow(torchvision.utils.make_grid(images)) print (' ' .join('%5s' % classes[labels[j]] for j in range (4 )))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 import torch.nn as nnimport torch.nn.functional as Fclass Net (nn.Module): def __init__ (self ): super (Net, self).__init__() self.conv1 = nn.Conv2d(3 , 6 , 5 ) self.pool = nn.MaxPool2d(2 , 2 ) 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 ) def forward (self, x ): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1 , 16 * 5 * 5 ) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x net = Net() print (net)for parameters in net.parameters(): print (parameters) for name,parameters in net.named_parameters(): print (name,':' ,parameters.size()) import torch.optim as optimcriterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001 , momentum=0.9 ) for epoch in range (2 ): running_loss = 0.0 for i, data in enumerate (trainloader, 0 ): inputs, labels = data optimizer.zero_grad() outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 2000 == 1999 : print ('[%d, %5d] loss: %.3f' % (epoch + 1 , i + 1 , running_loss / 2000 )) running_loss = 0.0 print ('Finished Training' )dataiter = iter (testloader) images, labels = dataiter.next () imshow(torchvision.utils.make_grid(images)) print ('GroundTruth: ' , ' ' .join('%5s' % classes[labels[j]] for j in range (4 )))outputs=net(images) _,predicted=torch.max (outputs,1 ) print ('Predicted: ' , ' ' .join('%5s' % classes[predicted[j]] for j in range (4 ))) correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data outputs = net(images) _, predicted = torch.max (outputs.data, 1 ) total += labels.size(0 ) correct += (predicted == labels).sum ().item() print ('Accuracy of the network on the 10000 test images: %d %%' % ( 100 * correct / total))
注意:torch.nn只支持mini-batches,不支持一次只输入一个样本,即一次必须是一个batch。
也就是说,就算我们输入一个样本,也会对样本进行分批,所以,所有的输入都会增加一个维度,我们对比下刚才的input,nn中定义为3维,但是我们人工创建时多增加了一个维度,变为了4维,最前面的1即为batch-sizehttps://handbook.pytorch.wiki/chapter2/2.1.3-pytorch-basics-nerual-network.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class_correct = list (0. for i in range (10 )) class_total = list (0. for i in range (10 )) with torch.no_grad(): for data in testloader: images, labels = data outputs = net(images) _, predicted = torch.max (outputs, 1 ) c = (predicted == labels).squeeze() for i in range (4 ): label = labels[i] class_correct[label] += c[i].item() class_total[label] += 1 for i in range (10 ): print ('Accuracy of %5s : %2d %%' % ( classes[i], 100 * class_correct[i] / class_total[i]))
1 2 3 4 5 6 7 8 9 10 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu" ) print (device)net.to(device) inputs, labels = inputs.to(device), labels.to(device)
数据并行处理 https://handbook.pytorch.wiki/chapter1/data_parallel_tutorial.html
https://handbook.pytorch.wiki/chapter1/neural_networks_tutorial.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from torch.utils.data import Datasetimport pandas as pdclass BulldozerDataset (Dataset ): def __init__ (self, csv_file ): self.df=pd.read_csv(csv_file) def __len__ (self ): return len (self.df) def __getitem__ (self, idx ): return self.df.iloc[idx].SalePrice
DataLoader为我们提供了对Dataset的读取操作,常用参数有:batch_size(每个batch的大小)、 shuffle(是否进行shuffle操作)、 num_workers(加载数据的时候使用几个子进程)。
1 2 3 4 5 6 7 8 9 10 dl = torch.utils.data.DataLoader(ds_demo, batch_size=10 , shuffle=True , num_workers=0 ) idata=iter (dl) print (next (idata))for i, data in enumerate (dl): print (i,data) break
torchvision不仅提供了常用图片数据集,还提供了训练好的模型,可以加载之后,直接使用,或者在进行迁移学习 torchvision.models模块的 子模块中包含以下模型结构。 - AlexNet - VGG - ResNet - SqueezeNet - DenseNet
transforms 模块提供了一般的图像转换操作类,用作数据处理和数据增强
1 2 3 4 5 6 import torchvision.datasets as datasetstrainset = datasets.MNIST(root='./data' , train=True , download=True , transform=None )
1 2 3 import torchvision.models as modelsresnet18 = models.resnet18(pretrained=True )
1 2 3 4 5 6 7 8 from torchvision import transforms as transformstransform = transforms.Compose([ transforms.RandomCrop(32 , padding=4 ), transforms.RandomHorizontalFlip(), transforms.RandomRotation((-45 ,45 )), transforms.ToTensor(), transforms.Normalize((0.4914 , 0.4822 , 0.4465 ), (0.229 , 0.224 , 0.225 )), ])
(0.485, 0.456, 0.406), (0.2023, 0.1994, 0.2010) 这几个数字是什么意思?
官方的这个帖子有详细的说明: https://discuss.pytorch.org/t/normalization-in-the-mnist-example/457/21 这些都是根据ImageNet训练的归一化参数,可以直接使用,我们认为这个是固定值就可以