Transformer进行序列预测
Transformer用于自然语言处理NLP取得很优秀的效果。尝试使用Transformer的编码器Encoder实现序列的预测。比如根据日期、销量、库等预测采购数量。为了验证模型首先用随机数产生源数据和目标数据,测试模型能否学习数据并做出正确的预测。
生成1000个样本每个样本10个随机数,相当于自然语言1000句话每句话10个词。需要训练的目标2个维度,第0个维度为随机数和源数据的最大值、最小值。第1个维度是随机数,为了增加难度乘以10。
device=torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#数据行数
numberofdata = 1000
#每行维度,相当于每句话有多个词。
srcdim = 10
#目标维度
targetdim = 2
#注意力头数。
number_of_heads=2
#词向量维度
word_embeding_dim=16
trainepoch=200
def loaddata(numberofdata,srcdim,targetdim):
gen = torch.Generator();
gen.manual_seed(2)
data=torch.rand((numberofdata, srcdim), generator=gen)
datay = torch.rand((numberofdata, targetdim), generator=gen)
#偶数行最大值
datay[0::2,0]=data[0::2,:].max(dim=-1).values
#奇数行最小值
datay[1::6,0]=data[1::6,:].min(dim=-1).values
#拉开范围
datay[:,-1]=datay[:,-1]*10.0
data=data.to(device)
datay=datay.to(device)
return data,datay
嵌入层和位置编码的处理。输入数据必须先进行编码,相当于将每个词编码成词向量。输入的每一个随机数当做一个Long类型的词input_ids。为了简化按数据行号和每个数据在样本中的位置进行编码。 位置编码按偶数和奇数计算正弦和余弦计算。
# 位置编码层
class PositionEmbedding(nn.Module):
def __init__(self):
super(PositionEmbedding,self).__init__()
# pos是第几个词,i是词向量位置,d_model是维度总数
def get_pe(pos, i, d_model):
fenmu = 1e4 ** (i / d_model)
pe = pos / fenmu
pe = torch.tensor(pe)
if i % 2 == 0:
return torch.sin(pe)
return torch.cos(pe)
# 初始化位置编码矩阵
pe = torch.empty(srcdim, word_embeding_dim)
for i in range(srcdim):
for j in range(word_embeding_dim):
pe[i, j] = get_pe(i, j, word_embeding_dim)
pe = pe.unsqueeze(0)
# 定义为不更新的常量
self.register_buffer('pe', pe)
# 词编码层
self.embed = nn.Embedding(srcdim*numberofdata, word_embeding_dim)
# 初始化参数
self.embed.weight.data.normal_(0, 0.1)
def forward(self, rowindex, x):
# [8, 50] -> [8, 50, 32]
batchsize=x.shape[0]
#每个input_ids的位置
pos = torch.arange(0,srcdim).unsqueeze(0).repeat(batchsize,1).to(device)
#行位置=行号*每行词向量维度
rowpos = (rowindex * srcdim).unsqueeze(1).repeat(1,srcdim).to(device)
pos = pos + rowpos
embed = self.embed(pos)
#词向量加位置
embed = embed + self.pe
return embed
编码调用Encoder,解码用一个维度1全连接层。输出结果取目标维度行,损失函数使用mse。
class SentenceModel(nn.Module):
def __init__(self):
super(SentenceModel,self).__init__()
self.x_embeding=PositionEmbedding()
num_encode_decode_layers=6
encoderlayer = nn.TransformerEncoderLayer(word_embeding_dim, number_of_heads, batch_first=True, norm_first=True)
self.encoder = nn.TransformerEncoder(encoderlayer, num_layers=num_encode_decode_layers)
self.fc_out=nn.Linear(word_embeding_dim,1)
#rowindex是数据生成时的行号,用于计算词向量
def forward(self,rowindex,x):
x_embeding=self.x_embeding(rowindex,x)
pred = self.encoder(x_embeding)
out = self.fc_out(pred)
return out
经过多次迭代训练后取得了比较理想的效果。8个样本评估差方小于0.1。
def train():
#每批8个
samples_one_batch = 8
loader = torch.utils.data.DataLoader(dataset=dataset, batch_size=samples_one_batch, shuffle=True)
# Initializing a BERT bert-base-uncased style configuration
optim = Adam(model.parameters(), lr=1e-3)
sheduler = torch.optim.lr_scheduler.StepLR(optim, step_size=1000, gamma=0.99)
model.train(True)
for epoch in tqdm(range(trainepoch)):
for batchindex,(rowindex,x,y) in enumerate(loader):
optim.zero_grad()
pred = model(rowindex,x)
pred = pred[:,0:targetdim].reshape(-1)
y = y.reshape(-1)
loss = F.mse_loss(pred,y)
loss.backward()
#torch.nn.utils.clip_grad_norm_(model.parameters(),0.5)
optim.step()
sheduler.step()
#使用交叉熵,softmax+log+nulloss,embedembed
ev = evalmodel(model)
lr =optim.param_groups[0]['lr']
print(f'epoch={epoch},lr={lr},loss={loss},eval={ev}')
目标数据这样的
ys
tensor([0.9216, 1.2265, 0.4586, 5.7319, 0.9877, 1.9658, 0.0550, 0.4252, 0.9247,
7.1420, 0.1124, 4.3715, 0.8366, 9.2239, 0.8488, 6.5507],
device='cuda:0')
预测结果这样的
pred
tensor([0.9448, 1.2840, 0.5323, 5.6820, 0.9052, 1.9387, 0.1148, 0.4421, 0.9674,
7.0415, 0.1527, 4.3014, 0.9212, 9.0716, 0.8516, 6.4683],
device='cuda:0', grad_fn=<UnsafeViewBackward0>)
差方和0.0788。预测效果比较理想。