pytorch

pytorch基础

1. Tensor数据类型

  • 创建tensor时,默认类型为torch.FloatTensor(32位浮点)
a = torch.Tensor(2,2)
  • tensor数据类型的转换:
1. b = a.double()
2. c = a.type(torch.DoubleTensor) #DoubleTensor64位浮点
3. d = a.type_as(b) #使d与b类型一致

2. tensor的创建与维度查看

2.1 tensor的创建

  • a = torch.Tensor(2,2) #2*2随机数
  • b = torch.DoubleTensor(2,2)
  • c = torch.Tensor([[1,2],[3,4]])
  • d = torch.zeros(2,2)
  • e = torch.eye(2,2) #对角矩阵
  • f = torch.randn(2,2) #默认标准正态分布
  • g = torch.rand(2,2) #默认(0,1)内的均匀分布
  • h = torch.arange(1,6,2) #arange(start,end,step),一维向量;
  • i = torch.linspace(1,6,2) #linspace(start,end,steps),从start到end,一共steps份,一维向量;
  • j = torch.randperm(4) #生成长度为4的随机排列向量,如tensor([1,2,0,3]);
  • k = torch.tensor() #参数可以为list、ndarray;

2.2 tensor的组合与分块

组合:

  • torch.cat([a,b],0) #a和b竖着接,操作后数据的总维数不变!
  • torch.stack([a,b],num) #num=0:竖着叠加,对序列本身叠加;num=1:对第一维(每一行)进行叠加;num=2:对第二维(每一行的每一个元素)进行叠加
a = torch.tensor([[1,2],[3,4],[5,6]])
b = torch.tensor([[7,8],[9,10],[11,12]])
>>>a = tensor([[1, 2],
        [3, 4],
        [5, 6]]) 
   b = tensor([[ 7,  8],
        [ 9, 10],
        [11, 12]])
c = torch.stack([a,b],0)
d = torch.stack([a,b],1)
e = torch.stack([a,b],2)
>>>c = tensor([[[ 1,  2],
         [ 3,  4],
         [ 5,  6]],
        [[ 7,  8],
         [ 9, 10],
         [11, 12]]])
   d = tensor([[[ 1,  2],
         [ 7,  8]],
        [[ 3,  4],
         [ 9, 10]],
        [[ 5,  6],
         [11, 12]]])
   e = tensor([[[ 1,  7],
         [ 2,  8]],
        [[ 3,  9],
         [ 4, 10]],
        [[ 5, 11],
         [ 6, 12]]])

分块:

  • torch.chunk(a,2,0) #沿着第0维分块,一共分成两块
  • torch.split(a,2,0) #沿着第0维分块,每一块维度为2

2.3tensor的索引与变形

索引:

  • a = torch.Tensor([[0,1],[2,3]])
  • 可直接根据下标进行索引,例如a[1],a[0,1]
  • a>0:选择a中大于0的元素,符合条件置为1,不符合的置为0
  • a[a>0]:返回符合条件的元素一维向量
  • torch.nonzero(a):返回非零的元素的坐标
  • a.clamp(1,2):对元素进行限制,设置下限为1,上限为2

变形:

  • a.view(),a.resize(),a.reshape()三个作用相同,都可以在不改变原tensor值的条件下改变形状,且处理后的张量与原张量共享内存,即一个值变,另一个也变。
    注:transpose()、permute()操作可能把tensor在内存中变得不连续,而view操作需要tensor内存连续才能进行,故先需要使用contiguous将内存变为连续的才行,即Tensor.contiguous().view()。而reshape()操作就相当于Tensor.contiguous().view()。故统一使用reshape()操作改变张量形状
  • a.resize_()可直接改变原来tensor的尺寸(后面带个_是原地操作符),依据resize_所给的维度,将原张量数据按顺序放进新的tensor,多的补0
  • a.transpose()和a.permute()两者作用相似,都是用于交换不同维度的内容。但其中torch.transpose()是交换指定的两个维度的内容,permute()则可以一次性交换多个维度。permute()可以视为多次transpose()的组合:
 b = a.permute(2,0,1)
 c = a.transpose(1,2).transpose(0,1)

则b=c。
注:这个transpose如果想不清楚,就转坐标。举个例子:

a = tensor([[[1, 2],
         [3, 4]],
        [[5, 6],
         [7, 8]]])
a.transpose(0,1)
>>> tensor([[[1, 2],
         [5, 6]],
         [[3, 4],
         [7, 8]]])

此处3的坐标是a[0][1][0],故对0和1维进行transpose后,3的坐标变为a[1][0][0]。

  • squeeze和unsqueeze:
a = torch.arange(1,4)
>>> tensor([1, 2, 3])
a.shape
>>> torch.Size([3])
a.unsqueeze(0) #将第0维变成1
>>> tensor([[1, 2, 3]])
a.unsqueeze(0).squeeze(0) #第0维如果是1,则去掉该维度,否则不起作用
>>> tensor([1, 2, 3])
  • a.expand()函数将某一维的元素复制并扩展。
a = tensor([[[1.], #a的维度:(2,2,1)
         [2.]],
        [[3.],
         [4.]]])
a.expand(2,2,3)
>>>tensor([[[1., 1., 1.],
         [2., 2., 2.]],
        [[3., 3., 3.],
         [4., 4., 4.]]])

注:expand只适用于存在维度为1的tensor,且只能维度为1的元素乘几倍。例如一个Tensor([1,2]),这个Tensor的size是torch.size([2]),其实也可以理解成torch.size([1,2])。经过expand后,可以expand成torch.size([4,2])或torch.size([6,2])等等。

  • 除了expand,还有expand_as方法。先创建一个Tensor记为a,再创建一个张量b,并使得c = b.expand_as(a)。

2.4 tensor的排序

  • a.sort(0,True)[0]:按照第0维进行排序,即对每一列进行比较,True降序
  • a.max(0):按照第0维选取最大值,组成一维向量,与之对应还有a.min()
a = torch.randn(3,3)
print(a)
b = a.sort(0,True)
print(b)
c = a.max(0)
print(c)
>>>tensor([[-0.7381, -0.0474,  0.4044],
        [ 0.5164,  0.6495,  1.6825],
        [ 0.4069,  0.2583, -0.7948]])
torch.return_types.sort(
values=tensor([[ 0.5164,  0.6495,  1.6825],
        [ 0.4069,  0.2583,  0.4044],
        [-0.7381, -0.0474, -0.7948]]),
indices=tensor([[1, 1, 1],
        [2, 2, 0],
        [0, 0, 2]]))
torch.return_types.max(
values=tensor([0.5164, 0.6495, 1.6825]),
indices=tensor([1, 1, 1]))

注:这里维度想不清楚还是拿坐标想:例如一个张量的坐标分别为

(0,0)(0,1)(1,2)
(1,0)(1,1)(1,2)

如果要比较的是第0维的索引,则需要保证其他维度的索引值一致,比较第0维,例如比较(0,0)和(1,0),这就是为什么要比较第0维,就拿列进行比较。

2.5 tensor的内存共享

  • 用一个张量初始化另一个,或者用tensor的组合、分块、索引、变形等操作初始化另一个tensor,则这两个tensor共享内存,即一个tensor的某个值改变,对应的另一个值也跟着改变。
  • pytorch通过加后缀‘_’实现原地操作,如add_(),resize_(),这些操作会改变张量本身的值
  • tensor转换为numpy:b = a.numpy()
  • numpy转换为tensor:c = torch.from_numpy(b)
  • tensor转换为list:d = a.tolist()

3 Autograd与计算图

3.1 Tensor的自动求导:Autograd

可以通过requires_grad参数来创建支持自动求导机制的Tensor。
a = torch.randn(2,2,requires_grad=True)
require_grad参数表示是否需要对该张量求导,默认为False。设置为True的话,该Tensor需要求导,并且依赖于该Tensor的之后的所有节点都需要求导。
Tensor有两个重要的属性:

  • grad:该张量对应的梯度,类型为Tensor,并与Tensor同维度。
  • grad_fn:指向function对象,即该Tensor经历了什么操作,用于反向传播的梯度计算,如果Tensor由用户创建,则grad_fn为None。
>>> a = torch.randn(2,2,requires_grad=True)#支持自动求导机制
>>> b = torch.randn(2,2)
>>> a.requires_grad,b.requires_grad
(True, False)
>>> b.requires_grad_()#这个函数可以将Tensor变得需要求导
tensor([[-0.1024, -0.7271],
        [ 0.5306, -0.5362]], requires_grad=True)
>>> c = a+b
>>> c.requires_grad
True
>>> a.grad_fn,b.grad_fn,c.grad_fn
(None, None, <AddBackward0 object at 0x000002629FE324F0>)
>>> d = c.detach()#返回一个新的从当前图中分离的 Variable,返回的 Variable 永远不会需要梯度
>>> d
tensor([[-0.2274, -0.1197],
        [ 2.0254,  0.4900]])
>>> c
tensor([[-0.2274, -0.1197],
        [ 2.0254,  0.4900]], grad_fn=<AddBackward0>)
>>> d.requires_grad
False

3.2 计算图

举个例子,计算图构建方法:
z = wx+b

>>> x = torch.randn(1)
>>> w = torch.ones(1,requires_grad=True)
>>> b = torch.ones(1,requires_grad=True)
>>> x.is_leaf,w.is_leaf,b.is_leaf
(True, True, True)
>>> #自己创建的是叶子节点,经过计算得到的是中间节点,最后的输出是根节点
>>> x.requires_grad,w.requires_grad,b.requires_grad
(False, True, True)
>>> y = w*x
>>> z = y+b
>>> y.requires_grad,z.requires_grad#依赖的变量需要求导
(True, True)
>>> y.grad_fn,z.grad_fn#记录Tensor经历的操作
(<MulBackward0 object at 0x000002629FEA8D90>, <AddBackward0 object at 0x000002629FE6D5E0>)
>>> z.backward(retain_graph=True)#True保持计算图不被销毁
>>> w.grad
tensor([-1.5827])
>>> b.grad
tensor([1.])

4 神经网络工具箱 torch.nn

4.1 nn.Module

若要实现某个神经网络,需要继承nn.Module,在初始化中定义模型结构与参数,在函数forward中编写前向过程即可。下面新建一个perception.py文件作为示例:

import torch
from torch import nn

class Linear(nn.Module):
    def __init__(self,in_dim,out_dim):
        super(Linear,self).__init__()
        # 使用nn.Parameter构造需要学习的参数,此时默认需要求导,即requires_grad为True
        self.w = nn.Parameter(torch.randn(in_dim,out_dim))
        self.b = nn.Parameter(torch.randn(out_dim))
    def forward(self,x):
        x = x.matmul(self.w)
        y = x+self.b.expand_as(x)#将b扩展为x的矩阵大小
        return y

class Perception(nn.Module):
    def __init__(self,in_dim,hid_dim,out_dim):
        super(Perception,self).__init__()
        self.layer1 = Linear(in_dim,hid_dim)
        self.layer2 = Linear(hid_dim,out_dim)
    def forward(self,x):
        x = self.layer1(x)
        y = torch.sigmoid(x)
        y = self.layer2(y)
        y = torch.sigmoid(y)
        return y

perception = Perception(2,3,2)
>>>for name,parameter in perception.named_parameters():
       print(name,parameter)
layer1.w Parameter containing:
tensor([[-2.7053e-03,  1.8488e-01,  1.3976e+00],
        [-2.8497e+00, -1.4688e+00,  1.5465e+00]], requires_grad=True)
layer1.b Parameter containing:
tensor([0.8366, 1.5671, 1.0971], requires_grad=True)
layer2.w Parameter containing:
tensor([[-0.2466, -0.4257],
        [-1.4165,  1.3061],
        [ 0.9819,  0.6569]], requires_grad=True)
layer2.b Parameter containing:
tensor([-0.3707,  0.1869], requires_grad=True)
>>>data = torch.randn(4,2)#可以看出,传入的参数也可以是多维的
#维数变化:(4,2)->(4,3)->(4,2)
>>>output = perception(data)
tensor([[0.2077, 0.7972],
        [0.2024, 0.7924],
        [0.2303, 0.7362],
        [0.3459, 0.8331]], grad_fn=<SigmoidBackward>)
  • nn.Module与nn.functional库:若用后者定义网络层,则网络层不能自动学习参数,还需要用nn.Parameter封装。对于一些不需要学习参数的层,例如激活层、BN层,可以使用nn.functional。
  • nn.sequential()模块:此模块可用于快速搭建模型,示例如下:
class Perception(nn.Module):
    def __init__(self,in_dim,hid_dim,out_dim):
        super().__init__()
        self.layer = nn.Sequential(
            nn.Linear(in_dim,hid_dim),
            nn.Sigmoid(),
            nn.Linear(hid_dim,out_dim),
            nn.Sigmoid()
        )
    def forward(self,x):
        y = self.layer(x)
        return y
model = Perception(2,3,2)

可以看出,这样搭建时使用了nn.Linear,从而无需像前面那个示例中手动设置w和b,我们可以打印一下这个模型的参数,可以看到有weight和bias两个参数:

>>>print(model)
Perception(
  (layer): Sequential(
    (0): Linear(in_features=2, out_features=3, bias=True)
    (1): Sigmoid()
    (2): Linear(in_features=3, out_features=2, bias=True)
    (3): Sigmoid()
  )
)
>>>for name,parameter in model.named_parameters():
       print(name,parameter)
layer.0.weight Parameter containing:
tensor([[-0.4221, -0.1116],
        [ 0.4323, -0.2017],
        [-0.6153, -0.6678]], requires_grad=True)
layer.0.bias Parameter containing:
tensor([-0.5091,  0.2183, -0.1671], requires_grad=True)
layer.2.weight Parameter containing:
tensor([[ 0.0124,  0.0361,  0.4764],
        [-0.2019, -0.1501,  0.3386]], requires_grad=True)
layer.2.bias Parameter containing:
tensor([ 0.4866, -0.4072], requires_grad=True)

4.2 损失函数

torch.nn和torch.nn.functional都提供了各种损失函数,但是由于损失函数不含可学习的参数,故两者在功能上基本没有区别。

import torch.nn.functional as F
label = torch.Tensor([0,1,1,0]).long()
criterion = nn.CrossEntropyLoss()
loss = criterion(output,label)
print(loss)
loss_F = F.cross_entropy(output,label)
print(loss_F)

得到两个loss算出来的结果都是:

tensor(0.7191, grad_fn=<NllLossBackward>)
tensor(0.7191, grad_fn=<NllLossBackward>)

我们可以验算一下:
打印出output为(这里output我已经进行了归一化,对列归一化):

tensor([[0.2485, 0.1833],
        [0.4827, 0.1668],
        [0.1240, 0.3584],
        [0.1449, 0.2915]], grad_fn=<SoftmaxBackward>)

则交叉熵为:
( l n 0.4827 + l n 0.1240 + l n 1668 + l n 0.3584 ) / 8 = 0.70 (ln0.4827+ln0.1240+ln1668+ln0.3584)/8=0.70 (ln0.4827+ln0.1240+ln1668+ln0.3584)/8=0.70
较为接近。pytorch的crossentrophy的计算是先对每一列特征进行归一化,再对某一列的特征的标签与对应的ln值相乘,完毕后再求个均值。

  • 这里插入一点对交叉熵的理解:
    例如我要求4*2张量的交叉熵,则标签必须为0或1:
data =  tensor([[0.2485, 0.1833],
        [0.4827, 0.1668],
        [0.1240, 0.3584],
        [0.1449, 0.2915]], grad_fn=<SoftmaxBackward>)
label =  tensor([0, 1, 1, 0])

可以得到交叉熵为:tensor(0.7191, grad_fn=<NllLossBackward>)
假如将2也作为标签加入label,则会报错:

data =  tensor([[0.2485, 0.1833],
        [0.4827, 0.1668],
        [0.1240, 0.3584],
        [0.1449, 0.2915]], grad_fn=<SoftmaxBackward>)
label =  tensor([0, 1, 2, 0])

报错内容是:IndexError: Target 2 is out of bounds.
对于原始数据,行表示多个对象,而列表示种类,每个数据意味着这个对象是这个种类的概率。label的值意味着这几个对象的真实标签。这个地方有两列,故标签只可能是0或1,若给的标签有2,则不符合原始数据的含义。并且此处标签只能是0或1,而不能是1或2。类似地,若数据是n*3维度,则标签只能是0、1、2.

4.3 优化器nn.optim

常用的优化器:SGD和Adam。
需要传入的参数:网络种需要学习优化的Tensor对象、学习率、权值衰减。

from torch import optim
optimizer = optim.SGD(model.parameters(),lr=0.001,momentum=0.9)
optimizer = optim.Adam([var1,var2],lr=0.001)

优化器的使用方法:

optimizer = optim.Adam(params=mlp.parameters(),lr = 0.001)
loss = criterion(pred,label)
optimizer.zero_grad() #清空梯度,每次优化前都需要此操作
loss.backward() #损失的反向传播
optimizer.step() #利用优化器进行梯度更新

4.4 模型处理

  • 可使用torchvision.modules调用经典的网络结构和预训练模型。
from torchvision import models
alex = models.AlexNet()
print(alex.classifier) #分类层
print(alex.features) #特征层
>Sequential(
  (0): Dropout(p=0.5, inplace=False)
  (1): Linear(in_features=9216, out_features=4096, bias=True)
  (2): ReLU(inplace=True)
  (3): Dropout(p=0.5, inplace=False)
  (4): Linear(in_features=4096, out_features=4096, bias=True)
  (5): ReLU(inplace=True)
  (6): Linear(in_features=4096, out_features=1000, bias=True)
)
Sequential(
  (0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))
  (1): ReLU(inplace=True)
  (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  (3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
  (4): ReLU(inplace=True)
  (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (7): ReLU(inplace=True)
  (8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (9): ReLU(inplace=True)
  (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (11): ReLU(inplace=True)
  (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
)
  • 加载预训练模型:
  • 第一种方法:直接利用torchvision.models种自带的与训练模型,将pretrained参数设为True即可——vgg = models.vgg16(pretrained=True)
  • 第二种方法:通过model.load_state_dict()加载本地预训练模型。
vgg = models.vgg16()
state_dict = torch.load('your path')
vgg.load_state_dict({k:v for k,v in state_dict_items() if k in vgg.state_dict()})

4.5 数据集处理

数据加载的过程分为三步,分别是:继承Dataset类、增加数据变换、继承Dataloader。

  1. 为了处理数据集,需要继承torch.utils.data.Dataset这个抽象类。
from torch.utils.data import Dataset
class my_data(Dataset):
    def __init__(self,image_path,annotation_path,transform=None):
        #初始化,读取数据集
    def __len__(self):
        #获取数据集的总大小
    def __getitem__(self):
        #对于指定的id,读取该数据并返回
dataset = my_data('your image path','your annotation path')
for data in dataset:
    print(data)
  1. 数据变换与增强:torchvision.transforms。此外,可以利用transforms.Compose将多个变换整合起来。
from torchvision import transforms
dataset = my_data('your image path','your annotation path',transform=transforms.Compose([
    transforms.Resize(256)
    transforms.RandomHorizontalFlip()
]))
  1. 继承dataloader,进行封装,从而可以进行批量处理、随机选取等操作。
from torch.utils.data import DataLoader
dataloader = DataLoader(dataset,batch_size=4,shuffle=True,num_workers=0)
data_iter = iter(dataloader)#dataloader是一个可迭代对象
for step in range(iterations):
    data = next(data_iter)

关于imagefolder

imagefolder来自torchvision.datasets.Imagefolder,其作用主要是对二分类这种类别较少的分类任务进行数据包装。它默认你已经将不同类别的图片分为不同的文件夹,并且文件夹名就是类名。这样对于猫狗分类这样的二分类任务比较合适,但对于手写数字识别,最好还是自己手写my_data类,再在torch.utils.data.Dataloader中进行图片和标签的包装。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值