【深度学习】transform 学习代码能跑通,但未完全掌握,上面记录了自己的疑问点

import torch 
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset

from tqdm import tqdm 
import numpy as np 
import pandas as pd 
import random
import matplotlib.pyplot as plt 
import seaborn as sns
sns.set_style('white')

引入所需要的库。
在这里插入图片描述
数据集准备:

class CustomDataset(Dataset):
    def __init__(self, seq_len=50, future=50,  max_len=1000):
        super(CustomDataset).__init__()
        # 定义一个开始和一个结束
        self.vocab = {'SOS':1001, 'EOS':1002}
        self.datalist = np.arange(0,max_len)
        self.data, self.targets = self.timeseries(self.datalist, seq_len, future)
        
    def __len__(self):
        #this len will decide the index range in getitem
        return len(self.targets)
    
    def timeseries(self, data, window, future):
        temp = []
        targ = []
        
        for i in range(len(data)-window):
            temp.append(data[i:i+window])
            
        for i in range(len(data)-window -future):
            targ.append(data[i+future:i+window+future])

        return np.array(temp), targ
    
    def __getitem__(self, index):
        x = torch.tensor(self.data[index]).type(torch.Tensor)
        # transformer 需要增加开始和结束的token, torch.LongTensor
        x = torch.cat((torch.tensor([self.vocab['SOS']]), x, torch.tensor([self.vocab['EOS']]))).type(torch.LongTensor)
        
        y = torch.tensor(self.targets[index]).type(torch.Tensor)
        # y值也是transformer 需要增加开始和结束的token.LongTensor
        y = torch.cat((torch.tensor([self.vocab['SOS']]), y, torch.tensor([self.vocab['EOS']]))).type(torch.LongTensor)
        
        return x,y
    
dataset = CustomDataset(seq_len=48, future=5, max_len=1000)
# num_tokens = 1003 是什么情况?? @todo, 是1000 + start +  end + unknow
model = Transformer(num_tokens=1000+3, dim_model=32, num_heads=2, num_layers=2, input_seq=50)

# 这里用的是交叉熵损失,但为啥要用交叉熵损失??
loss_function = nn.CrossEntropyLoss()
learning_rate = 1e-3
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

训练这一套:

for e in tqdm(range(25)):
    i = 0
    avg_loss = []
    for x,y in dataloader:
        optimizer.zero_grad()
        
        #one step behind input and output // Like language modeling 
        y_input = y[:, :-1]         # from starting to -1 position
        y_expected = y[:, 1:]       # from 1st position to last 
        # this is done so that in prediction we see a start of token 
        
        # forward
        predictions = model(x, y_input)
        pred = predictions.permute(0, 2, 1)
        
        # loss
        loss = loss_function(pred, y_expected)
        
        # backward
        loss.backward()

        # optimization
        optimizer.step()
        avg_loss.append(loss.detach().numpy())

        i+=1
        
    if e%5==0:
        avg_loss = np.array(avg_loss)
        print(avg_loss.mean())
predictions.topk(1).indices.shape
def predict(model, input_sequence, max_length=50, SOS_token=1000+1, EOS_token=1000+2):
    model.eval()
    
    input_sequence = torch.tensor(input_sequence)
    input_sequence = torch.cat((torch.tensor([SOS_token]), input_sequence, torch.tensor([EOS_token]))).type(torch.LongTensor) 
    input_sequence = torch.unsqueeze(input_sequence,0) # [50]-->[1,50]
    
    y_input = torch.tensor([1001], dtype=torch.long)
    y_input = torch.unsqueeze(y_input,0)
    # 这里的问题是,虽然是many to many,但是是多步的many to many,单词还是一个一个的往外蹦的?@todo?
    for _ in range(max_length):
        
        predictions = model(input_sequence, y_input)
        
        top = predictions.topk(1).indices
        top = torch.squeeze(top, 2) # 挤出去一个维度
        
        next_item = torch.unsqueeze(top[:,-1],0)
        y_input = torch.cat((y_input, next_item), dim=1) # 这里有回来了
        mask = model.get_mask(y_input.shape[1])
        if next_item == EOS_token:
            break

    return y_input.view(-1).tolist()
d = random.randint(0,900)
t = torch.tensor(np.arange(d,d+48)).type(torch.Tensor)
input_sequence = t
print(t)
r=predict(model, input_sequence)
print(r)
fig = plt.figure(figsize=(16,4))

plt_x = np.arange(0,t.shape[0])
plt_y = t

plt_xp = np.arange(5, t.shape[0]+5)
plt_yp = r[1:-2]

plt.scatter(plt_x, plt_y, s=14, color='r', label="real")
plt.scatter(plt_xp, plt_yp, s=7, color='b', label="predicted")
    
plt.legend()
plt.show()

在这里插入图片描述
问题1:不是说能够并行么,为什么答案还需要一个个的蹦出来???@todo 核心问题
问题2:teacher force的策略选择问题

ref:https://natasha-klingenbrunn.medium.com/transformer-implementation-for-time-series-forecasting-a9db2db5c820

修改点:
https://github.com/matplotlib/matplotlib/issues/25267
plot.py的所有b=True,改成visible=True即可,就能跑起来了。
plt.grid(visible=True, which=‘major’, linestyle = ‘-’)
plt.grid(visible=True, which=‘minor’, linestyle = ‘–’, alpha=0.5)

它这个整体是一个回归问题,用前48预测后24,但是问题是后面24个必须一个一个预测@todo?

时间序列预测上两个不错的up:
北方工大:
https://space.bilibili.com/164045697/channel/series
https://www.bilibili.com/video/BV1454y1v79B/?spm_id_from=333.788.recommend_more_video.1&vd_source=3f7ae4b9d3a2d84bf24ff25f3294d107
北邮电
https://www.bilibili.com/video/BV13j421o7qA/?spm_id_from=333.999.0.0&vd_source=3f7ae4b9d3a2d84bf24ff25f3294d107

注解transformer

ref:https://nlp.seas.harvard.edu/annotated-transformer/

背景

减少序列计算是Extended Neural GPU, ByteNet and ConvS2S的基础,他们都用卷积神经网络来构建模块。
Extended Neural GPU
ByteNet 是一维卷积
ConvS2S sequence to sequence
但它们三个的计算量随着两个输入距离的增加而计算越来越大, ConvS2S是线性的,ByteNet是log级别的。他们学习不了长依赖。
Transformer将计算了缩减到了常数级别。尽管其代价是由于平均注意加权位置而降低了有效分辨率,我们使用多头注意抵消了这一影响。
什么是自注意力?是一种将单个序列的不同位置联系起来以计算该序列的表示的注意机制。
Transformer是第一个完全依赖于自关注来计算其输入和输出表示的转导模型,而不使用序列对齐rnn或卷积。

Part1:Model Architecture

序列转换模型都是encoder-decoder结构。
encoder 映射输入序列(x1,x2…,xn)到连续表示z=(z1,z2,…zn). 有了z,decoder产生输出序列(y1,…ym)序列,一次一个元素。
在每个步骤中,模型都是自动回归的,在生成下一个步骤时,使用先前生成的符号作为附加输入。

Attention 【待完成】

pytorch中的register_buffer()
self.register_buffer(‘my_buffer’, self.tensor):my_buffer是名字,str类型;self.tensor是需要进行register登记的张量。这样我们就得到了一个新的张量,这个张量会保存在model.state_dict()中,也就可以随着模型一起通过.cuda()复制到gpu上。

class my_model(nn.Module):
    def __init__(self):
        super(my_model, self).__init__()
        self.conv = nn.Conv2d(1, 1, 3, 1, 1)
        self.tensor = torch.randn(size=(1, 1, 5, 5))
        self.register_buffer('my_buffer', self.tensor)

    def forward(self, x):
        return self.conv(x) + self.my_buffer  # 这里不再是self.tensor


x = torch.randn(size=(1, 1, 5, 5))
x = x.to('cuda')
model = my_model().cuda()
model(x)
print(model.state_dict())
print('..........')
print(model.tensor)
print(model.my_buffer)
# OrderedDict([('my_buffer', tensor([[[[ 0.0719, -0.5347,  0.5229, -0.5599, -0.5907],
#           [-0.2743, -0.6166, -1.6723, -0.0386,  0.9706],
#           [-1.0789,  0.9852,  0.1703, -0.6299, -0.5167],
#           [ 0.4972, -0.9745, -0.3185,  0.3618,  0.2458],
#           [ 1.5783, -0.5800,  0.1895, -0.9914,  1.1207]]]], device='cuda:0')), ('conv.weight', tensor([[[[-0.0192,  0.0500,  0.0635],
#           [ 0.3025, -0.2644,  0.2325],
#           [ 0.0806,  0.0457, -0.0427]]]], device='cuda:0')), ('conv.bias', tensor([-0.3074], device='cuda:0'))])
# ..........
# tensor([[[[ 0.0719, -0.5347,  0.5229, -0.5599, -0.5907],
#           [-0.2743, -0.6166, -1.6723, -0.0386,  0.9706],
#           [-1.0789,  0.9852,  0.1703, -0.6299, -0.5167],
#           [ 0.4972, -0.9745, -0.3185,  0.3618,  0.2458],
#           [ 1.5783, -0.5800,  0.1895, -0.9914,  1.1207]]]])
# tensor([[[[ 0.0719, -0.5347,  0.5229, -0.5599, -0.5907],
#           [-0.2743, -0.6166, -1.6723, -0.0386,  0.9706],
#           [-1.0789,  0.9852,  0.1703, -0.6299, -0.5167],
#           [ 0.4972, -0.9745, -0.3185,  0.3618,  0.2458],
#           [ 1.5783, -0.5800,  0.1895, -0.9914,  1.1207]]]], device='cuda:0')

总结
成员变量:不更新,但是不算是模型中的参数(model.state_dict())
通过register_buffer()登记过的张量:会自动成为模型中的参数,随着模型移动(gpu/cpu)而移动,但是不会随着梯度进行更新。

最近发现bn中的running_mean,running_var, num_batches_tracked这三个参数是buffer类型的,这样既可以用state_dict()保存,也不会随着optimizer更新。
此外,我们要注意,state_dict()只会保存parameters和buffers类型的变量,如果我们有变量没有转成这两种类型,最后是不会被保存的!!!

class network(nn.Module):
    def __init__(self):
        super(network, self).__init__()
        self.conv = nn.Conv2d(1, 1, 1, padding=0)
        self.bn = nn.BatchNorm2d(2)

    def forward(self, x):
        return self.bn(self.conv(x))

net = network()
for n, a in net.named_buffers():
    print(n, a)
print('.........')
for w in net.parameters():
    print(w)
print('.........')
for v in net.state_dict():
    print(v)
# bn.running_mean tensor([0., 0.])
# bn.running_var tensor([1., 1.])
# bn.num_batches_tracked tensor(0)
# .........
# Parameter containing:
# tensor([[[[0.1984]]]], requires_grad=True)
# Parameter containing:
# tensor([0.4412], requires_grad=True)
# Parameter containing:
# tensor([1., 1.], requires_grad=True)
# Parameter containing:
# tensor([0., 0.], requires_grad=True)
# .........
# conv.weight
# conv.bias
# bn.weight
# bn.bias
# bn.running_mean
# bn.running_var
# bn.num_batches_tracked

编解码这里的码到底是什么?
香蕉,不管是哪一国的语言,它出现的上下文语境应该是相似的,应该和 黄色、猴子、甜远近相关。抛开了符号、发音等等形式上的不同,剩下来的单纯的语义关系。
编解码:语义关系的码,应该如何设计, 1)需要计算机来处理,数字化的。2)要能体现出语义直接的关系
高纬空间的坐标:
在这里插入图片描述
tokenizer和one-hot 来表示这个码,对文本中最基础的语义单元进行数字化。基础语义单元是什么:字母、单词、词根。
token是分配id
在这里插入图片描述
分词器:把所有的token都投射到了一维空间。------不能表达词和词之间的相互关心, 1,2,3代表水果的时候很合理,但是苹果若是代表手机,应该和华为更近一点。 苹果和香蕉组合起来的语义,按照直觉是1+2=3,但3已经被别人占了
one-hot: 有多少token就有多少维度的高维空间。互相之间是独立的,没了相互关系。相关度都是0, 但是香蕉和梨应该近啊。
这两种表达语义的方式都有缺陷,
分词器只用到了长度属性而没有用到方向属性,太过密集。没有利用维度信息。
one-hot:所有的点都在r=1的球面上,空间维度过于高,语义关系是靠维度之间的关系体现的,只利用了方向性质,而没有利用长度数学。
找到一个潜空间,来表达语义关系。基于分词取升维,基于独热编码去降维。##

Transformer的本质

本质是加权求和,废话,cnn,rnn,所有的神经网络那个不是加权求和。
Transformer的本质是编码器-解码器的结构,和注意力机制。他俩的关系相当于冯-计算机结构和显卡的关系一样。
大框架更重要,而显卡是为了满足特定功能的器件。

编解码的那个码是什么?

码是剖开了符号、发音等形式区别之后的语义关系。
所以需要要用1.数字化表示(方便计算机处理)2.体现语义之间的关系。
token(语义单元)词元
编号:1,2,3… 只强调了长度信息。
one=hot,仅强调了维度信息。
权重的矩阵计算:
行代表输入的原始坐标维度,列代表输出的维度。
多头注意力机制,就是能力更强的cnn。多头注意力中的多头,就是卷积核。

为什么需要位置信息

因为transformer本身并行计算输入,没有序列的位置属性,所以需要位置编码。
不然我打你,和你打我,顺序不一样,意思相反。后续操作如果是池化的话,那么信息就完全一样了。
它这里用加法实现了位置编码,相当于对embedding增加了一个平移操作的偏执项。
所选用的函数(权重)如何体系位置顺序信息?
p函数(矩阵)要能体现出,要能体现出词向量的先后顺序。
把位置坐标,投射到了与embeding维度相同的向量中!!!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
本质是傅里叶级数变换。
位置编码的本质,0,1,2,3…的顺序升维度,到 d维度
d维度就是编码过后的词向量。
在这里插入图片描述

在这里插入图片描述
为什么token的维度是有数的?
从时间:
1.因为位置编码不能重复,位置编码要尽可能的能容纳的多。
2.相对关系,让“距离”变成了"旋转角度"
f(x)->f(x+delta)
从频域:
p的维度互相之间正交(通道互相之间正交)
1/10000^2i/d 在 更大的范围内有更小的“原子”频率
绝对位置编码是直接对数据进行修饰,
相对位置编码是对分数A进行修饰;

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值