前言
在深度学习的初始阶段,每个深度学习研究者都需要写大量的重发代码。为了提高工作效率,这些研究者就开发出了很多深度学习框架,我们熟知的就有Tensorflow、PyTorch、Caffe等。
每种框架都有自己的特点和短板,每个人可以根据自己的需求进行选择。
对于我来说,PyTorch的代码比较简洁,很容易理解。
PyTorch的下载:https://pytorch.org/
一、张量(Tensor)
张量是PyTorch处理的最基本的对象,它表示的是一个多维的矩阵。比如零维就是一个点,一维就是一个向量,二维就是一个普通的矩阵,多维就相当于一个多维的数组。这一点和numpy是非常相似的,并且PyTorch的tensor和numpy的ndarray是可以相互转化的。不同的是PyTorch可以在GPU上运行,而numpy的ndarray只能在cpu上运行。
- 定义一个矩阵
a = torch.Tensor([[1,2],[3,4]])
print('a is : \n{}'.format(a))
#输出结果如下:
#a is :
#tensor([[1., 2.],
# [3., 4.]])
我们输入的数据类型是整型的,输出结果却是浮点型的,这里就涉及到tensor的数据类型。torch.Tensor默认的就是浮点型torch.FloatTensor,torch.FloatTensor表示的是32位浮点型,其它数据类型有64位浮点型torch.DoubleTensor、16位整型torch.ShortTensor、32位整型torch.IntTensor和64位整型torch.LongTensor
a = torch.IntTensor([[1,2],[3,4]])
print('a is : \n{}'.format(a))
#a is :
#tensor([[1, 2],
# [3, 4]], dtype=torch.int32)
- 矩阵大小
在numpy中是通过shape来获取矩阵的大小,而在tensor中,使用size来获得矩阵大小。
a = torch.Tensor([[1,2],[3,4]])
print('a size is : \n{}'.format(a.size()))
#a size is :
#torch.Size([2, 2])
- 定义全是0的空tensor
b = torch.zeros((3,2))
print('b is : \n{}'.format(b))
#b is :
#tensor([[0., 0.],
# [0., 0.],
# [0., 0.]])
- 定义全是1的tensor
a = torch.ones((2,2))
print(a)
#tensor([[1., 1.],
# [1., 1.]])
- 定义随机值的tensor
c = torch.randn((3,2))
print('c is : \n{}'.format(c))
#c is :
#tensor([[-1.0598, 1.2067],
# [-0.8201, -1.9696],
# [ 0.9314, -0.6350]])
- 改变矩阵大小
c = torch.randn((3,2))
print('c is : \n{}'.format(c))
c = c.view(2,3)
print('c is : \n{}'.format(c))
#c is :
#tensor([[ 0.6022, 0.1773],
# [-2.2036, 0.1484],
# [ 1.9957, 1.2197]])
#c is :
#tensor([[ 0.6022, 0.1773, -2.2036],
# [ 0.1484, 1.9957, 1.2197]])
#上下两个对比可以发现,改变矩阵大小是按每行从左到右的顺序
b = torch.zeros((3,2))
b = b.view(1,-1) #-1表示自动补齐
print('b is : \n{}'.format(b))
#b is :
#tensor([[0., 0., 0., 0., 0., 0.]])
- 索引
我们可以像numpy一样通过索引获取元素或者改变某个值
b = torch.zeros((3,2))
print(b[0,0]) #tensor(0.)
b[0,0] = 100
print('b is : \n{}'.format(b))
#b is :
#tensor([[100., 0.],
# [ 0., 0.],
# [ 0., 0.]])
- 数学运算
torch.Tensor 支持大量数学操作符,如+,-,*,/等
e = torch.Tensor([[3,2],[5,4],[7,6]])
f = torch.Tensor([[1,1],[1,1],[1,1]])
g = e+f
print('g is : \n{}'.format(g))
#g is :
#tensor([[4., 3.],
# [6., 5.],
# [8., 7.]])
也可以使用Tensor的内置函数,如add()和add_()等。这里需要提一下的就是 add 和 add_ 的区别,使用add()函数会生成一个新的Tensor变量, 而add_ 函数会直接在当前Tensor变量上进行操作(对于其它末尾带“_”的函数都是相似的功能。)
e = torch.Tensor([[3,2],[5,4],[7,6]])
h = e.add(10)
print('e is : \n{}'.format(e))
#e is :
#tensor([[3., 2.],
# [5., 4.],
# [7., 6.]])
print('h is : \n{}'.format(h))
#h is :
#tensor([[13., 12.],
# [15., 14.],
# [17., 16.]])
e.add_(10)
print('e is : \n{}'.format(e))
#e is :
#tensor([[13., 12.],
# [15., 14.],
# [17., 16.]])
- 转置
e = torch.Tensor([[3,2],[5,4],[7,6]])
print('e is : \n{}'.format(e))
#e is :
#tensor([[3., 2.],
# [5., 4.],
# [7., 6.]])
print('e is : \n{}'.format(e.t())) #t()表示转置
#e is :
#tensor([[3., 5., 7.],
# [2., 4., 6.]])
- Tensor和Numpy的相互转换
i = np.ones((2,3))
j = torch.from_numpy(i) #numpy->tensor
print(j)
#tensor([[1., 1., 1.],
# [1., 1., 1.]], dtype=torch.float64)
l = j.numpy() #tensor->numpy
print(l)
#[[1. 1. 1.]
# [1. 1. 1.]]
二、变量(Variable)
调用Variable的方法是torch.autograd.Variable,autograd提供了自动求导的功能,这个概念在numpy里面就没有了,这是神经网络计算图里的特有概念。所谓计算图,是神经网络在做运算的时候构造出来的,然后在里面进行前向传播和反向传播。
Variable和Tensor本质上没有区别,不过Variable会被放入一个计算图中,然后进行前向传播、反向传播、自动求导。
将tensor变为Variable也很简单,如将tensor a变成Variable,只需要Variable(a)就可以。
- Variable封装了Tensor,并且支持了几乎所有Tensor的操作
#如add函数
a = torch.ones((2,2))
b = Variable(a)
c = b.add(10)
print(c)
#tensor([[11., 11.],
# [11., 11.]])
- 一旦完成张量计算之后就可以调用
.backward()
函数,它会把所有的梯度计算好 - 通过Variable的
.data
属性可以获取到张量 - 通过Variabe的
.grad
属性可以获取到梯度
值得注意的是,pytorch官方文档指出不推荐使用Variable,但是目前Variable仍然可以继续工作,具体做了哪些改动我不太清楚。
三、数据集
我们在训练模型的时候,需要将训练集全部输入到模型中去,一般方法是使用迭代。而pytorch内已经定义好了一个数据迭代器—DataLoader,它的调用方法是torch.utils.data.DataLoader。
比较重要的几个参数有batch_size(批大小,表示一次将多少数据输入模型), shuffle(是否将数据打乱), num_workers(多线程读取数据)
train_loader = DataLoader(train_dataset, batch_size = 50, shuffle=True, num_workers=0)
#num_workers=0表示只在主线程中加载数据
另外,torchvision内有一个关于计算机视觉的数据读取类ImageFolder,它的调用方式是torchvision.datasets.ImageFolder,主要功能是读取图片数据,且要求图片是下图这种存放方式。
然后这样来调用类:
train_dataset = ImageFolder(root='./data/train/',transform=data_transform)
root表示根目录,transform表示数据预处理方式。
这种方式将train目录下的cat和dog文件夹内的所有图片作为训练集,而文件夹名cat和dog作为标签数据进行训练。
四、nn.Module
在pytorch内编写神经网络,所有的层结构和损失函数都来自于torch.nn,所有的模型构建都是从nn.Module继承的,于是有了如下模板:
class net_name(nn.Module):
def __init__(self, other_arguments):
super(net_name, self).__init__()
self.conv1 == nn.Conv2d(in_channels, out_channels, kernel_size)
#other network layer
def forward(self,x):
x = self.conv1(x)
return x
这样就建立了一个计算图,并且这个结构可以复用多次,每次调用相当于用该结构图定义的相同参数做一次前向传播。得益于pytorch的自动求导功能,我们不需要自己编写反向传播。
定义完模型后,我们需要通过nn这个包来定义损失函数,如均方误差、交叉熵等。
#定义交叉熵
criterion = nn.CrossEntropyLoss()
#计算损失函数
loss = criterion(output, target)
五、模型的优化
在深度学习中,我们需要不断调整网络参数来使损失函数最小化(或最大化),而模型的优化是参数更新的策略。
常用的优化算法有SGD, Adam等。
比如我们用随机梯度下降来定义模型优化:
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
这样我们就把模型的参数作为需要更新的参数传入优化器,lr是学习率,momentum表示动量,动量是使模型跳出局部最优解的有效方法。
在模型训练中,优化之前需要将梯度归零,即optimizer.zero_grad(),然后通过loss.backward()反向传播,自动求导得到每个参数的梯度,最后只需要optimizer.step()就可以通过梯度进行参数更新。
六、模型的保存与加载
pytorch中保存模型有两种方式:
- 保存整个模型的结构信息和参数信息
- 保存模型的参数
torch.save(model, path)
torch.save(model.state_dict(), path)
第一个参数是保存对象,第二个参数是保存路径及名称。
加载模型对应于保存模型也有两种方式:
- 加载整个模型
- 加载参数信息
load_model=torch.load('model.pth') #方式一
model.load_state_dic(torch.load('model_state.pth')) #方式二
第二种方式在加载前需要先导入模型结构。
在网络较大的时候不推荐第一种方式,加载时间很长,存储的空间也很大。
参考
廖星宇《深度学习入门之PyTorch》
PyTorch官方文档