使用 Transformer 做一维向量的预测笔记

982 阅读4分钟

在网上搜到别人用 Transformer 来预测的代码

blog.csdn.net/u013177138/…

他给了代码仓库:

github.com/fengjun321/…

如果暂时不看 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!")

测试:

图片.png

实线是预测值,虚线是真实值

可以看到预测得还是不错的

多维输入一维输出

现在只是使用了一维的输入,我会想想怎么使用多维的输入

多维输入一维输出的话就是这样:

# 这一句需要注意,应该是因为输入是一维向量,所以才在前后各加了一个维度
# 如果输入不是一维的,比如是二维的,那么套用这个就会出错
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_dimd_model

图片.png

可以看到 embed_encoder_inputembed_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 官方代码

pytorch.org/tutorials/b…

官方的数据处理

图片.png

他这个输入和输出都是一维的

经过一个区分 batch 的操作变成 [length, batch_size]

图片.png

他这里的源 src 就是 [seq_len, batch_size],目标 target[seq_len * batch_size]

图片.png

然后在评价损失的时候就把 output[seq_len, batch_size] view 到 [seq_len * batch_size]

图片.png

然后他这个交叉熵的计算,输入是 [seq_len * batch_size, vocabulary_size] 目标是 [seq_len * batch_size]

discuss.pytorch.org/t/nn-crosse…

output: [batch_size, nb_classes, *]

target: [batch_size, *]

看到了别人的手动实现

blog.csdn.net/weixin\_472…

还看了别人的示例

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)

图片.png