目录
一、引入torch
二、 make_batch
三、定义模型class NNLM(nn.Module)
1、super(Model, self).__init__()
(1)self
(2)__ init__ ()方法
(3)super(MyModel, self).__init__()
2、nn.Embedding
3、nn.Linear(in_features, out_features, bias=True)
4、nn.Parameter()
nn.Parameter()和nn.Linear()
5、.view()
四、定义参数
五、for epoch
六、Predict Test
参考:
!博客内容就是自己想仔细研究一下这个代码里面各个方法的具体用法
https://github.com/graykode/nlp-tutorial
nlp-tutorial/1-1.NNLM at master · graykode/nlp-tutorial · GitHub
import torch
import torch.nn
import torch.optim as optim
创建语料
# 语料
sentences = ["I like dog", "I love coffee", "I hate milk"]
创建word2idx字典
word_list = " ".join(sentences).split() # 每句话所有词切分出来
word_list = list(set(word_list)) # 去重
word_dict = {w: i for i, w in enumerate(word_list)} # word:idx word_dict['word']=idx
number_dict = {i: w for i, w in enumerate(word_list)} # idx:word number_dict[idx]='word'
n_class = len(word_dict) # number of Vocabulary # 字典大小
构造batch
def make_batch():
input_batch = [] # 输入batch
target_batch = [] # 目标batch
for sen in sentences: # 扫描每句话,构建batch
# 把一句话的每个词切分
# ["I like coffee"] --> ["I", "like", "coffee"]
word = sen.split() # space tokenizer
# 最后一个词作为target,其他词作为input
input = [word_dict[n] for n in word[:-1]] # create (1~n-1) as input
target = word_dict[word[-1]] # create (n) as target, We usually call this 'casual language model'
# 将一句话切分后分别放入input_batch和target_batch
input_batch.append(input)
target_batch.append(target)
return input_batch, target_batch
class NNLM(nn.Module):
def __init__(self):
super(NNLM, self).__init__()
self.C = nn.Embedding(n_class, m)
self.H = nn.Linear(n_step * m, n_hidden, bias=False)
self.d = nn.Parameter(torch.ones(n_hidden))
self.U = nn.Linear(n_hidden, n_class, bias=False)
self.W = nn.Linear(n_step * m, n_class, bias=False)
self.b = nn.Parameter(torch.ones(n_class))
def forward(self, X):
X = self.C(X) # X : [batch_size, n_step, m]
X = X.view(-1, n_step * m) # [batch_size, n_step * m]
tanh = torch.tanh(self.d + self.H(X)) # [batch_size, n_hidden]
output = self.b + self.W(X) + self.U(tanh) # [batch_size, n_class]
return output
自己写模型类的优势就是可以自定义层与层之间的连接关系,自定义数据流x的流向。
self指的是实例Instance本身,在Python类中规定,函数的第一个参数是实例对象本身,并且约定俗成,把其名字写为self,也就是说,类中的方法的第一个参数一定要是self,而且不能省略。
在python中创建类后,通常会创建一个 __ init__ ()方法,这个方法会在创建类的实例的时候自动执行。 __ init__ ()方法必须包含一个self参数,而且要是第一个参数。
__ init__ ()方法在实例化的时候就已经自动执行了,但是如果不是 __ init__ ()方法,那肯定就只有调用才执行。如果 __ init__ ()方法中还需要传入另一个参数name,但是我们在创建Bob的实例的时候没有传入name,那么程序就会报错, 说我们少了一个__ init__ ()方法的参数,因为__ init__ ()方法是会在创建实例的过程中自动执行的,这个时候发现没有name参数,肯定就报错了。
当我们认为一些属性、操作是在创建实例的时候就有的时候,就应该把这个量定义在__ init__ ()方法中。我们写神经网络的代码的时候,一些网络结构的设置,也最好放在__ init__ ()方法中。
简单理解就是子类把父类的__init__()放到自己的__init__()当中,这样子类就有了父类的__init__()的那些东西。
#建立词向量层
embed = torch.nn.Embedding(n_vocabulary,embedding_size)
实际上,上面通过随机初始化建立了词向量层后,建立了一个“二维表”,存储了词典中每个词的词向量。
!
- nn.embedding 的输入只能是编号,不能是隐藏变量,比如one-hot,或者其他。这种情况可以自己建一个自定义维度的线性网络层,参数训练可以单独训练或者跟随整个网络一起训练。
in_features
指的是输入的二维张量的大小,即输入的[batch_size, size]
中的size
。out_features
指的是输出的二维张量的大小,即输出的二维张量的形状为[batch_size,output_size]
输入[batch_size, in_features] --> [batch_size, out_features]
self.v = torch.nn.Parameter(torch.FloatTensor(hidden_size))
含义是将一个固定不可训练的tensor转换成可以训练的类型parameter,并将这个parameter绑定到这个module里面(net.parameter()中就有这个绑定的parameter,所以在参数优化的时候可以进行优化的),所以经过类型转换这个self.v变成了模型的一部分,成为了模型中根据训练可以改动的参数了。使用这个函数的目的也是想让某些变量在学习的过程中不断的修改其值以达到最优化。
linear里面的weight和bias就是parameter类型,且不能够使用tensor类型替换,还有linear里面的weight甚至可能通过指定一个不同于初始化时候的形状进行模型的更改。一般是多维的可训练tensor。
import torch
a = torch.range(1,30)
print(a)
b = a.view(2,3,5)
print(b)
print(b.view(b.size(0),-1))
print(b.view(b.size(1),-1))
print(b.view(b.size(2),-1))
a:tensor([ 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.])
b:tensor([[[ 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.]]])
b.size(0):
tensor([[ 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.]])
b.size(1):
tensor([[ 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.]])
b.size(2):
tensor([[ 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.]])
n_step = 2 # number of steps, n-1 in paper
n_hidden = 2 # number of hidden size, h in paper
m = 2 # embedding size, m in paper
model = NNLM()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
input_batch, target_batch = make_batch()
input_batch = torch.LongTensor(input_batch)
target_batch = torch.LongTensor(target_batch)
# Training
for epoch in range(5000):
# Pytorch 为什么每一轮batch需要设置optimizer.zero_grad?
# 根据pytorch中的backward()函数的计算,当网络参量进行反馈时,梯度是被积累的而不是被替换掉;但是在每一个batch时毫无疑问并不需要将两个batch的梯度混合起来累积,因此这里就需要每个batch设置一遍zero_grad 了。
optimizer.zero_grad()
# __call__方法一般会是调用forward方法,实际上是:
# outputs = model.__call__(forward(inputs))
output = model(input_batch)
# output : [batch_size, n_class], target_batch : [batch_size]
loss = criterion(output, target_batch)
if (epoch + 1) % 1000 == 0:
print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
# 官方:如果需要计算导数,可以在Tensor上调用.backward()。
# 1. 如果Tensor是一个标量(即它包含一个元素的数据),则不需要为backward()指定任何参数
# 2. 但是如果它有更多的元素,则需要指定一个gradient参数,它是形状匹配的张量。
# backward()里面的参数实际上就是每一个输出对输入求导后的权重
loss.backward()
# 所有的optimizer都实现了step()方法,这个方法会更新所有的参数
optimizer.step()
# Predict
predict = model(input_batch).data.max(1, keepdim=True)[1]
# Test
print([sen.split()[:2] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])
[1] Python类中self参数 / __ init__ ()方法 /super(Model, self).__init__()是什么_Just Jump的博客-CSDN博客
[2] self参数 - __ init__ ()方法 super(Net, self).__init__()是什么_Chou_pijiang的博客-CSDN博客
[3] 通俗讲解pytorch中nn.Embedding原理及使用 - 简书
[4] python中的view用法_agoodboy1997的博客-CSDN博客_python view
[5] 对Pytorch中backward()函数的理解_beebabo的博客-CSDN博客_backward
https://github.com/graykode/nlp-tutorial