赞
踩
最近在尝试用Pytorch实现Transformer,这里记录下我在复现时遇到的bug以及Pytorch一些语法技巧等~
import torch
import numpy as np
import torch.nn.functional as F
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
这里使用matplotlib.use(‘agg’)是因为服务器上直接调用matplotlib.pyplot会报no module named Tkinter的错误,而安装Tkinter对我这种非root用户很麻烦,因此使用 matplotlib.use(‘agg’) 语句可以避免这个错误,但美中不足是因此就不无法使用plt.show()函数,而是得用plt.savefig()来保存图片后再去看图片~
如果自己是root用户可以直接用yum或者pip来下载的话,推荐参考这篇博客自行安装Tkinter。
如果需要新建一个全是0的Tensor
pe = torch.zeros(5, 4)
如果需要直接按自己想法初始化里面的数值
pe=torch.Tensor([[1,2,3,4,5],[4,5,6,7,8],[7,8,9,10,11]])
这是个二维的Tensor,当然,类似的也可以定义三维Tensor或者四维Tensor,比如
a = torch.randn(4, 3, 28, 28) #四维Tensor,其中维度分别为4,3,28,28
这里用这个pe为例子演示
pe=torch.Tensor([[1,2,3,4,5],[4,5,6,7,8],[7,8,9,10,11]])
按维度选择数据
pe0=pe[:,0:2] #这里第一个:表示第一个维度全选,0:2表示选择第二个维度的第0到第2列内容(不包括第2列)不同维度中用','隔开
# 这里pe0就是对pe的行全选,并只其中第0和第1列的数据
按维度进行跳步的选择数据
pe1=pe[:,0::2] #第一个:表示行数据全选,而0::2表示从第0列开始到最后一列,隔一个数选一个数据
# 这也可用来表示选择偶数列的数据
pe2=pe[:,1::2] #第一个:表示行数据全选,而1::2表示从第1列开始到最后一列,隔一个数选一个数据
# 这也可用来表示选择奇数列的数据
pe2=pe[:,0:3:2] #第一个:表示行数据全选,而0:3:2表示从第0列开始到第3列,隔一个数选一个数据
# 这样就可以自己定范围了
继续使用pe为例子演示
pe.size() #直接输出pe的矩阵大小信息
当然,也可以在size()函数中输入来控制输出信息,比如
pe.size(0) #输出pe矩阵第0维信息,这里直接输出3
pe.size(1) #输出pe矩阵第1维信息,这里输出5
pe.size(-1) #输出pe矩阵最后一维的信息,这里输出5
transpose函数和permute函数都是用来转置矩阵数据的,两者最大的不同就是transpose只能转置矩阵中的两维,permute可以转置多维,可以看以下例子:
这里使用一个随机生成的三维pe数据来为例子(使用randint函数,其中0表示最低值,10表示最高值(取不到),(2,3,4)是生成矩阵的大小)
pe = torch.randint(0,10,(2,3,4))
#randint(low=0, high, size, out=None, dtype=None)
pe.transpose(1,2)
这里transpose是转置第1,2维,第0维没变,并且pe.transpose(1,2)和pe.transpose(2,1)输出是一样的,所以输入值的位置对transpose函数输出无影响,transpose只是把输入值对应的维度相互转置,并且transpose函数只能传入两个维度信息。
permute函数则更能操作高维的矩阵转置,核心思想是:把输入值对应的维度信息转置到输入值的位置对应的维度上。比较拗口,继续用pe为例:
pe.permute(0,1,2)
可以看出,pe没变,因为permute(0,1,2),第0维用第0维数据,第1维用第1维数据,第2维用第2维数据,因此没变~
pe.permute(0,2,1)
permute(0,2,1),及第0维不变,第1维用第2维数据,第2维用第1维数据,实现了与transpose(2,1)一样的功能~
接下来继续看permute(1,2,0)
pe.permute(1,2,0)
这样看值可能有点不直观,我们直接看他的尺寸可以更容易理解
pe.permute(1,2,0).size()
原本pe尺寸为(2,3,4),经过permute(1,2,0)后尺寸变为(3,4,2),第0维和第1位换位了,第1维和第2维换了,第2维和第1维换了,这就很直白了,所以permute核心思想是:把输入值对应的维度信息转置到输入值的位置对应的维度上,因此permute函数能处理矩阵的高维转置。
由此,transpose函数只能转置两个维度,但permute能转置多个维度;transpose只需也只能传入两个值,而permute得把矩阵所有维度信息都传入才能运行~
view函数的作用为重构张量的维度,在日常对Tensor处理中使用较多的用法有torch.view(参数a,参数b…),view(-1),以及 view(参数a,-1…) 的用法,这里直接用例子阐明:
pe = torch.randint(0,5,(1,2,3))
#randint(low=0, high, size, out=None, dtype=None)
>>>tensor([[[1, 0, 2],
[1, 1, 0]]])
pe.view(1,1,6)
>>>tensor([[[1, 0, 2, 1, 1, 0]]])
所以view(1,1,6)其实是把原来(1,2,3)维的pe拉平了~
pe.view(-1)
>>>tensor([1, 0, 2, 1, 1, 0])
这里看到view(-1)也是把矩阵拉平,但是直接拉平成一维向量,维度为[6],之前view(1,1,6)的输出是一个三维矩阵,维度为[1,1,6];
pe.view(1,-1)
>>>tensor([[1, 0, 2, 1, 1, 0]])
这里view(1,-1)可以理解为,把pe变成两维矩阵,第0维的尺寸为1,第1维尺寸设为-1,-1可以看成是,确定了第0维后,把后续的值在第一维拉平,所以view(1,-1)之后得到的矩阵尺寸为[1,6];
这里再用几个例子做以阐明:
pe.view(2,-1) >>>tensor([[1, 0, 2], [1, 1, 0]]) pe.view(2,-1,1) >>>tensor([[[1], [0], [2]], [[1], [1], [0]]]) pe.view(3,-1) >>>tensor([[1, 0], [2, 1], [1, 0]]) pe.view(3,-1,1) >>>tensor([[[1], [0]], [[2], [1]], [[1], [0]]])
所以对view(参数a,-1,参数b…)来说,-1在哪就说明那一维度拉平,按别的确定维度(参数a,参数b等)来确定-1那一维度应该是多少维~
其实这个大家上网查查都有资料,但是都讲的很浅,比如如下:
a = lambda x,y : x*y
a(2,3)
>>>6
所以lambda是干嘛的呢,他其实本质是希望让函数更简单,比如上述例子实现的其实是:
def a(x,y):
return x*y
a(2,3)
>>>6
好了,这就是lambda的基本解释了,然后网上就默认大家都弄懂了,但今天看代码看到一个很有趣的lambda用法,个人想了很久才想懂,这里用个简单示例来阐明并解释:
(这个例子是和Annotated Transformer对应的,用来解释为什么要用lambda函数)
import torch import torch.nn as nn class Top(nn.Module): def __init__(self): super(Top, self).__init__() self.self_attn = MultiHeadedAttention() self.sublayer = SublayerConnection() def forward(self, x, mask): x = self.sublayer(x, lambda x : self.self_attn(x, x, x, mask)) return x class SublayerConnection(nn.Module): def __init__(self): super(SublayerConnection, self).__init__() def forward(self, x, layer): return x + layer(x+1) class MultiHeadedAttention(nn.Module): def __init__(self): super(MultiHeadedAttention, self).__init__() self.attn = None def forward(self, query, key, value, mask=None): x = attention(query, key, value, mask=mask) return x def attention(query, key, value, mask=None, dropout=None): return query*key*value - mask #instance top = Top() top(2,1) >>> 28
大家可以看下为什么这里等于28~
这里的有趣点是,当我们调用Top时,里面代码对应的
x = self.sublayer(x, lambda x : self.self_attn(x, x, x, mask))
这是在干嘛呢?以及这个最终结果28是怎么得来的呢?
这里我就开门见山的说了,在Top里,self_attn对应的MultiHeadedAttention的forward函数里得传入四个参数,分别为(query, key, value, mask),sublayer对应的SublayerConnection类的forward需要传入(x, layer),其中layer是个对象,但在SublayerConnection类的forward可以看到
return x + layer(x+1)
这里layer只有一个(x+1)这一个输入,当layer对应到MultiHeadedAttention这种需要四个输入的时候,是无法直接使用的,因此需要一个函数来对其进行操作,这就是需要这个lambda的时候了,这个lambda匿名函数的实际操作类似于如下
def lambda1(self, x, mask):
return self.self_attn(x, x, x, mask)
但对应到原代码中,这个匿名函数并不是把self.self_attn(x, x, x, mask) 运行完得到值了再把值传到sublayer中的,而是直接把这个self.self_attn(x, x, x, mask)这个对象传了进去,这个期间并没有运行!他是到了SublayerConnection类的forward函数里的return之后才一起运行的!
在例子里面就是:
x = self.sublayer(x, lambda x : self.self_attn(x, x, x, mask))
# 当程序到这后会先进行lambda匿名函数的操作
# 把self.self_attn(x, x, x, mask)这个对象直接传入sublayer()的第二项中,
# 与sublayer对应的SublayerConnection类中的forward的layer进行结合,
# 从原来的
return x + layer(x+1)
#变成
return x + self.self_attn(x+1,x+1,x+1,mask)
# 直到这一步,整个return才开始运行!
# 这样也完成了layer(x+1)的x+1直接对应到self.self_attn(x, x, x, mask)的x中
# 由此完成整体操作
因此在最后调用top(2,1)时,全局的计算应该就为
2+(2+1)*(2+1)*(2+1)-1=28
之前对此还有个疑问,就是,这里为什么要用lambda,不直接用下列就行了?不也是直接把self.self_attn这个对象传进去了?
x = self.sublayer(x, self.self_attn(x, x, x, mask))
就像我上面所述,这样直接传会报错,因为SublayerConnection类中的return x + layer(x+1),这个layer(x+1)只有这一个输入,self_attn有四个输入,对应不上,所以才需要另加一个函数进行对应操作才能正常运行,这里用匿名函数也正式为了让代码更加简洁,不用再另起几行写个新函数了~
import sys
class Logger(object):
def __init__(self, fileN='Default.log'):
self.terminal = sys.stdout
self.log = open(fileN, 'a')
def write(self, message):
self.terminal.write(message)
self.log.write(message)
def flush(self):
pass
sys.stdout = Logger('./test.txt') //
这样直接调用的print函数便可以直接在终端输出以及写在txt文档中。
import csv
f = open('./test.csv', 'w')
csv_write = csv.writer(f)
a=1
csv_write.writerow(a)
这是将a的内容输入到csv文件中,也可以传入自己想要的内容~
2022.03.23
以上就是今天记录的内容了,之后遇到了新技巧也会继续更新的~
对内容有疑问也可以联系博主,博主会找空回复的 fuyz@stu.pku.edu.cn
对了,这里分享下博主觉得写得很好的一篇讲Transformer的文章,博主也是跟着这个学习Pytorch的,希望可以帮到大家~
2022.03.27
今天更新了对size(),transpose(),以及permute()函数的考量,其实size(-1)这个用法在实际使用中还是挺常见的,嗯,继续努力吧~
2022.03.30
更新了view()函数
2022.03.31
更新了lambda 匿名函数,对代码中一个神秘用法给了一定解释~
2022.04.06
更新了print输出到txt文件以及csv文件中的代码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。