在网上搜到别人用 Transformer 来预测的代码
他给了代码仓库:
如果暂时不看 Transformer 的具体结构的话,只当作黑盒子用还是很简单的
可能需要修改的东西是 myfunction.py
比如把这里的 input 和 output 改成读取第 0 个元素和读取第 1 个元素,因为我不知道它第一列元素有什么用
#!/usr/bin/env python3
# encoding: utf-8
"""
@Time : 2021/7/7 15:10
@Author : Xie Cheng
@File : myfunction.py
@Software: PyCharm
@desc: 一些自定义的函数
"""
import numpy as np
from torch.utils.data import Dataset
# 导入数据集的类
class MyDataset(Dataset):
def __init__(self, csv_file):
self.lines = open(csv_file).readlines()
def __getitem__(self, index):
# 获取索引对应位置的一条数据
cur_line = self.lines[index].split(',')
sin_input = np.float32(cur_line[0].strip())
cos_output = np.float32(cur_line[1].strip())
return sin_input, cos_output
def __len__(self):
return len(self.lines) # MyDataSet的行数
我觉得他那个自定义的数据集里面之所以写成 sin_input 和 cos_output,主要原因是因为他本来就是用 sin 和 cos 做测试的
可以使用一段简单的代码来生成 sin 为 input,cos 为 output 的数据集
import numpy as np
import pandas as pd
t = np.linspace(0, 6 * np.pi, 100)
input = np.sin(t)
output = np.cos(t)
# Set up empty DataFrame
data = pd.DataFrame({'Column_1' : []})
data['Column_' + str(0)] = input
data['Column_' + str(1)] = output
data.to_csv('sin_input_cos_output_1.csv', index=False, header=False)
然后在 train_transformer.py 和 test_transformer.py 中修改相应的文件名就好了
train_transformer.py
#!/usr/bin/env python3
# encoding: utf-8
import sys
sys.path.append("../")
import torch
from torch import nn
from torch.utils.data import DataLoader
import numpy as np
from torch.autograd import Variable
from myfunction import MyDataset
from Transformer.transformer import TransformerTS
# device GPU or CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print('You are using: ' + str(device))
# batch size
batch_size_train = 7
out_put_size = 3
data_bound = batch_size_train - out_put_size
# total epoch(总共训练多少轮)
total_epoch = 1000
# 1. 导入训练数据
# 这里注意要改
filename = '../data/sin_input_cos_output_1.csv'
dataset_train = MyDataset(filename)
train_loader = DataLoader(dataset_train, batch_size=batch_size_train, shuffle=False, drop_last=True)
# 2. 构建模型,优化器
# 输入特征维度可能要改
tf = TransformerTS(input_dim=1,
dec_seq_len=batch_size_train-out_put_size,
out_seq_len=out_put_size,
d_model=32, # 编码器/解码器输入中预期特性的数量
nhead=8,
num_encoder_layers=3,
num_decoder_layers=3,
dim_feedforward=32,
dropout=0.1,
activation='relu',
custom_encoder=None,
custom_decoder=None).to(device)
optimizer = torch.optim.Adam(tf.parameters(), lr=0.001)
#scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=2000, gamma=0.1) # Learning Rate Decay
criterion = nn.MSELoss() # mean square error
train_loss_list = [] # 每次epoch的loss保存起来
total_loss = 31433357277 # 网络训练过程中最大的loss
# 3. 模型训练
def train_transformer(epoch):
global total_loss
mode = True
tf.train(mode=mode) # 模型设置为训练模式
loss_epoch = 0 # 一次epoch的loss总和
for idx, (sin_input, cos_output) in enumerate(train_loader):
sin_input_np = sin_input.numpy()[:data_bound] # 1D
# 因为他一开始是想输入 1 2 3 4 预测 5 6 7
#cos_output = sin_input[data_bound:]
cos_output = cos_output.numpy()[data_bound:]
cos_output = torch.tensor(cos_output)
# 这一句需要注意,应该是因为输入是一维向量,所以才在前后各加了一个维度
# 如果输入不是一维的,比如是二维的,那么套用这个就会出错
sin_input_torch = Variable(torch.from_numpy(sin_input_np[np.newaxis, :, np.newaxis])) # 3D sin_input_torch : [1, data_bound, 1]
# 这一句是二维的,[batch_size, features_dim] => [1, batch_size, features_dim]
#sin_input_torch = Variable(torch.from_numpy(sin_input_np[np.newaxis, :]))
prediction = tf(sin_input_torch.to(device)) # torch.Size([batch size])
# loss = criterion(prediction, cos_output.to(device)) # MSE loss
loss = criterion(prediction, cos_output.to(device))
optimizer.zero_grad() # clear gradients for this training step
loss.backward() # back propagation, compute gradients
optimizer.step() # apply gradients
#scheduler.step()
#print(scheduler.get_lr())
loss_epoch += loss.item() # 将每个batch的loss累加,直到所有数据都计算完毕
if epoch % 100 == 0:
if idx == len(train_loader) - 1:
print('Train Epoch:{}\tLoss:{:.9f}'.format(epoch, loss_epoch))
train_loss_list.append(loss_epoch)
if loss_epoch < total_loss:
total_loss = loss_epoch
# 这里也注意要改!
torch.save(tf, '../model/tf_model3.pkl') # save model
if __name__ == '__main__':
# 模型训练
print("Start Training...")
for i in range(total_epoch): # 模型训练1000轮
train_transformer(i)
print("Stop Training!")
test_transformer.py
#!/usr/bin/env python3
# encoding: utf-8
import sys
sys.path.append("../")
import torch
from torch import nn
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import DataLoader
from torch.autograd import Variable
from myfunction import MyDataset
# device GPU or CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# print('You are using: ' + str(device))
# batch size
batch_size_test = 7
out_put_size = 3
data_bound = batch_size_test - out_put_size
# 导入数据
# 这里注意要根据数据集的名称来改!
filename = '../data/sin_input_cos_output_1.csv'
dataset_test = MyDataset(filename)
test_loader = DataLoader(dataset_test, batch_size=batch_size_test, shuffle=False, drop_last=True)
criterion = nn.MSELoss() # mean square error
# rnn 测试
def test_transformer():
# 这里注意要根据模型文件的名称来改!
net_test = torch.load('../model/tf_model3.pkl') # load model
test_loss = 0
net_test.eval()
with torch.no_grad():
for idx, (sin_input, cos_output) in enumerate(test_loader):
sin_input_np = sin_input.numpy()[:data_bound] # 1D
# 因为他一开始是想输入 1 2 3 4 预测 5 6 7
# 我不知道为什么他的 enc_input_len 也就是 data_bound = batch_size_train - out_put_size 就是 dec_seq_len
# 这样的话,其实在他的 transformer 架构中:
# 输入一个 dec_input 的长度本身是 enc_input_len = dec_seq_len
# 他又对输入在第 0 维度上取第 dec_seq_len 行到最后一行,作为 dec_input,也就是 dec_input 的长度也是 dec_seq_len
# 那就是说其实 enc_input 和 dec_input 是相等的
# cos_output = sin_input[data_bound:]
cos_output = cos_output.numpy()[data_bound:]
cos_output = torch.tensor(cos_output)
# 这里注意要改!
# 这一句需要注意,应该是因为输入是一维向量,所以才在前后各加了一个维度
# 如果输入不是一维的,比如是二维的,那么套用这个就会出错
sin_input_torch = Variable(torch.from_numpy(sin_input_np[np.newaxis, :, np.newaxis])) # 3D sin_input_torch : [1, data_bound, 1]
# 这一句是二维的,[batch_size, features_dim] => [1, batch_size, features_dim]
sin_input_torch = Variable(torch.from_numpy(sin_input_np[np.newaxis, :]))
prediction = net_test(sin_input_torch.to(device)) # torch.Size([batch size])
print("-------------------------------------------------")
print("输入:", sin_input_np)
print("预期输出:", cos_output)
print("实际输出:", prediction)
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
if idx == 0:
predict_value = prediction
real_value = cos_output
else:
predict_value = torch.cat([predict_value, prediction], dim=0)
real_value = torch.cat([real_value, cos_output], dim=0)
loss = criterion(prediction, cos_output.to(device))
test_loss += loss.item()
print('Test set: Avg. loss: {:.9f}'.format(test_loss))
return predict_value, real_value
if __name__ == '__main__':
# 模型测试
print("testing...")
p_v, r_v = test_transformer()
# 对比图
plt.plot(p_v.cpu(), c='green')
plt.plot(r_v.cpu(), c='orange', linestyle='--')
plt.show()
print("stop testing!")
测试:
实线是预测值,虚线是真实值
可以看到预测得还是不错的
多维输入一维输出
现在只是使用了一维的输入,我会想想怎么使用多维的输入
多维输入一维输出的话就是这样:
# 这一句需要注意,应该是因为输入是一维向量,所以才在前后各加了一个维度
# 如果输入不是一维的,比如是二维的,那么套用这个就会出错
sin_input_torch = Variable(torch.from_numpy(sin_input_np[np.newaxis, :, np.newaxis])) # 3D sin_input_torch : [1, data_bound, 1]
# 这一句是二维的,[batch_size, features_dim] => [1, batch_size, features_dim]
#sin_input_torch = Variable(torch.from_numpy(sin_input_np[np.newaxis, :]))
因为你 Debug 的时候可以看到模型里面 [4, 1, 32] 4 是 data_bound = batch_size_test - out_put_size,1 不知道是啥,32 是 input_dim 到 d_model
可以看到 embed_encoder_input 和 embed_decoder_input 的大小都是 [batchsize, input_length, feature_dimension],也就是这里的 [data_bound, 1, d_model]
此外 data_loader 也要相应改变
import numpy as np
import pandas as pd
t = np.linspace(0, 6 * np.pi, 100)
input_1 = np.sin(t)
input_2 = np.sin(t + np.pi/2)
output = np.cos(t)
# Set up empty DataFrame
data = pd.DataFrame({'Column_1' : []})
data['Column_' + str(0)] = input_1
data['Column_' + str(1)] = input_2
data['Column_' + str(2)] = output
data.to_csv('sin_input_cos_output.csv', index=False, header=False)
看 PyTorch 官方代码
官方的数据处理
他这个输入和输出都是一维的
经过一个区分 batch 的操作变成 [length, batch_size]
他这里的源 src 就是 [seq_len, batch_size],目标 target 是 [seq_len * batch_size]
然后在评价损失的时候就把 output 的 [seq_len, batch_size] view 到 [seq_len * batch_size]
然后他这个交叉熵的计算,输入是 [seq_len * batch_size, vocabulary_size] 目标是 [seq_len * batch_size]
discuss.pytorch.org/t/nn-crosse…
output: [batch_size, nb_classes, *]
target: [batch_size, *]
看到了别人的手动实现
还看了别人的示例
criterion = nn.CrossEntropyLoss()
# output shape: torch.Size([4, 2])
output = torch.FloatTensor([[0.8, 0.2],
[0.6, 0.4],
[0.3, 0.7],
[0.1, 0.9]])
# label shape: torch.Size([4])
label = torch.LongTensor([0, 0, 1, 0])
# tensor(0.6799)
loss = criterion(output, label)
print(loss)