pytorch 实现gru_实战 | 手把手教你用PyTorch实现图像描述(附完整代码)

原标题:实战 | 手把手教你用PyTorch实现图像描述(附完整代码) 作者 | 李理环信人工智能研发中心 VP,十多年自然语言处理和人工智能研发经验。主持研发过多款智能硬件的问答和对话系统,负责环信中文语义分析开放平台和环信智能机器人的设计与研发。想要详细了解该系列文章,营长建议你先阅读上篇:一文详解循环神经网络的基本概念(代码版)Tensor和TensorFlow 类似,PyTorch 的核心...
摘要由CSDN通过智能技术生成

原标题:实战 | 手把手教你用PyTorch实现图像描述(附完整代码)

作者 | 李理

环信人工智能研发中心 VP,十多年自然语言处理和人工智能研发经验。主持研发过多款智能硬件的问答和对话系统,负责环信中文语义分析开放平台和环信智能机器人的设计与研发。

想要详细了解该系列文章,营长建议你先阅读上篇:一文详解循环神经网络的基本概念(代码版)

Tensor

和TensorFlow 类似,PyTorch 的核心对象也是Tensor。下面是创建Tensor 的代码:

x = torch.Tensor(5, 3)

print(x)

对应的下标是5,那么在这个下标的值为1,而其余的值为0,因此一个词只有一个位置不为0,所以叫作one-hot 的表示方法。这种表示方法的缺点是它是一种“稀疏”的表示方法,两个词,不论语义是相似还是不同,都无法通过这个向量表示出来。比如我们计算两个向量的内积,相同的词内积为1(表示相似度很高);而不同的词为0(表示完全不同)。但实际我们希望“猫”和“狗”的相似度要高于“猫”和“石头”,使用one-hot 就无法表示出来。

Word Embedding 的思想是把高维的稀疏向量映射到一个低维的稠密向量,要求是两个相似的词会映射到低维空间里距离比较近的两个点;而不相似的距离较远。我们可以这样来“理解”这个低维的向量——假设语义可以用n 个基本的“正交”的“原子”语义表示的话,那么向量的不同的维代表这个词在这个原子语义上的“多少”。

当然这只是一种假设,但实际这个语义空间是否存在,或者即使存在也可能和人类理解的不同,但是只要能达到前面的要求——相似的词的距离近而不相似的远,也就可以了。

举例来说,假设向量的第一维表示动物,那么猫和狗应该在这个维度上有较大的值,而石头应该较小。

Embedding 一般有两种方式得到,一种是通过与任务无直接关系的无监督任务中学习,比如早期的RNN 语言模型,它的一个副产品就是Word Embedding,包括后来的专门Embedding 方法如Word to Vector 或者GloVe 等,本书后面的章节会详细介绍。另外一种方式就是在当前任务中让它自己学习出最合适的Word Embedding来。前一种方法的好处是可以利用海量的无监督数据,但是由于领域有差别以及它不是针对具体任务的最优化表示,它的效果可能不会很好;而后一种方法它针对当前任务学习出最优的表示(和模型的参数配合),但是它需要海量的训练数据,这对很多任务来说是无法满足的条件。在实践中,如果领域的数据非常少,我们可能直接用在其它任务中Pretraining 的Embedding 并且fix 住它;而如果领域数据较多的时候我们会用Pretraining 的Embedding 作为初始值,然后用领域数据驱动它进行微调。

PyTorch 基础知识

▌Tensor

和TensorFlow 类似,PyTorch 的核心对象也是Tensor。下面是创建Tensor 的代码:

x = torch.Tensor(5, 3)

print(x)

输出:

0.24550.15160.5319

0.98660.99180.0626

0.01720.64710.1756

0.89640.73120.9922

0.62640.01900.0041

[torch.FloatTensor of size 5x3]

我们可以得到Tensor 的大小:

print(x.size())

输出:

torch.Size([5, 3])

▌Operation

和TensorFlow 一样,有了Tensor 之后就可以用Operation 进行计算了。但是和TensorFlow 不同,TensorFlow 只是定义计算图但不会立即“执行”,而Pytorch 的Operation 是马上“执行”的。所以PyTorch 使用起来更加简单,当然PyTorch 也有计算图的执行引擎,但是它不对用户可见,它是“动态”编译的。

首先是加分操作:

y = torch.rand(5, 3)

print(x + y)

上面的加法会产生一个新的Tensor,但是我们可以提前申请一个Tensor 来存储Operation 的结果:

result = torch.Tensor(5, 3)

torch.add(x, y, out=result)

print(result)

也可以in-place 的修改:

# adds x to y

y.add_(x)

print(y)

一般来说,如果一个方法已_ 结尾,那么这个方法一般来说就是in-place 的函数。

PyTorch 支持numpy 的索引操作,比如取第一列:

print(x[:, 1])

我们也可以用view 来修改Tensor 的shape,注意view 要求新的Tensor 的元素个数和原来是一样的。

x = torch.randn(4, 4)

y = x.view(16)

z = x.view(-1, 8) # the size -1 is inferred from other dimensions

print(x.size(), y.size(), z.size())

输出:torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

▌numpy ndarray 的转换

我们可以很方便的把Tensor 转换成numpy 的ndarray 或者转换回来,注意它们是共享内存的,修改Tensor 会影响numpy 的ndarray,反之亦然。

Tensor 转numpy

a = torch.ones(5)

b = a.numpy()

a.add_(1) # 修改a会影响b

numpy 转Tensor

import numpy as np

a = np.ones(5)

b = torch.from_numpy(a)

np.add(a, 1, out=a) # 修改a会影响b

▌CUDA Tensor

Tensor 可以移到GPU 上用GPU 来加速计算:

# let us run this cell only if CUDA is available

if torch.cuda.is_available():

x = x.cuda()

y = y.cuda()

x + y 在GPU上计算

图5.18: PyTorch 的变量

▌Autograd

autograd 是PyTorch 核心的包,用于实现前面我们提到的自动梯度算法。首先我们介绍其中的变量。

▌Variable

autograd.Variable 是Tensor 的封装,我们定义(也就计算)好了最终的变量(一般是Loss) 后,我们可以调用它的backward() 方法,PyTorch 就会自动的计算好梯度。如图5.18所示,PyTorch 的变量值会存储到data 里,而梯度值会存放到grad 里,此外还有一个grad_fn,它是用来计算梯度的函数。除了用户创建的Tensor 之外,通过Operatioon 创建的变量会记住它依赖的变量,从而形成一个有向无环图。计算这个变量的梯度的时候会自动计算它依赖的变量的梯度。

我们可以这样定义变量,参数requires_grad 说明这个变量是否参与计算梯度:

x= Variable(torch.ones(2, 2), requires_grad=True)

▌Gradient

我们可以用backward() 来计算梯度,它等价于variable.backward(torch.Tensor([1.0])),梯度会往后往前传递,最后的变量一般传递的就是1,然后往前计算梯度的时候会把之前的值累积起来,PyTorch 会自动处理这些东西,我们不需要考虑。

x = Variable(torch.ones(2, 2), requires_grad=True)

y=x+2

z = y * y * 3

out = z.mean()

out.backward() # 计算所有的dout/dz,dout/dy,dout/dx

print(x.grad) # x.grad就是dout/dx

输出为:

4.5000 4.5000

4.5000 4.5000

[torch.FloatTensor of size 2x2]

我们手动来验证一下:

注意每次调用backward() 都会计算梯度然后累加到原来的值之上,所以如果每次计算梯度之前要调用变量的zero_grad() 函数。

▌变量的requires_grad 和volatile

每个变量有两个flag:requires_grad 和volatile,它们可以细粒度的控制一个计算图的某个子图不需要计算梯度,从而可以提高计算速度。一个Operation 如果所有的输入都不需要计算梯度(requires_grad==False),那么这个Operation 的requires_grad就是False,而只要有一个输入,那么这个Operation 就需要计算梯度。比如下面的代码片段:

>>> x = Variable(torch.randn(5, 5))

>>> y = Variable(torch.randn(5, 5))

>>> z = Variable(torch.randn(5, 5), requires_grad=True)

>>> a = x + y

>>> a.requires_grad

False

>>> b = a + z

>>> b.requires_grad

True

如果你想固定住模型的某些参数,或者你知道某些参数的梯度不会被用到,那么就可以把它们的requires_grad 设置成False。比如我们想细调(fine-tuing) 预先训练好的一个CNN,我们会固定所有最后全连接层之前的卷积池化层参数,我们可以这样:

model = torchvision.models.resnet18(pretrained=True)

for param in model.parameters():

param.requires_grad = False

# 把最后一个全连接层替换成新构造的全连接层

# 默认的情况下,新构造的模块的requires_grad=True

model.fc = nn.Linear(512, 100)

# 优化器只调整新构造的全连接层的参数。

optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)

volatile 在0.4.0 之后的版本以及deprecated 了,不过我们后面的代码会用到它之前的版本,因此还是需要了解一下。它适用于预测的场景,在这里完全不需要调用backward() 函数。它比requires_grad 更加高效,而且如果volatile 是True,那么它会强制requires_grad 也是True。它和requires_grad 的区别在于:如果一个Operation的所有输入的requires_grad 都是False 的时候,这个Operation 的requires_grad 才是False,这时这个Operation 就不参与梯度的计算;而如果一个Operation 的一个输入是volatile 是True,那么这个Operation 的volatile 就是True 了,那么这个Operation 就不参与梯度的计算了。因此它很适合的预测场景时:不修改模型的任何定义,只是把输入变量(的一个)设置成volatile,那么计算forward 的时候就不会保留任何用于backward 的中间结果,这样就会极大的提高预测的速度。下面是示例代码:

>>>regular_input = Variable(torch.randn(1, 3, 227, 227))

>>>volatile_inpu

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值