paper:Traffic Flow Prediction With Big Data: A Deep Learning Approach SAE模型

Autoencoder

理论讲解

paper的任务是基于历史的交通数据预测未来的交通流量数据,并且发现交通流量的数据是具有一定的周期性的,因此其提出了 SAE的模型,其希望可以先记住交通流量的数据,然后在进行推导

paper提出的SAE的流程图

在这里插入图片描述

通过堆叠的Autoencoder自编码器来进行预测的任务

Autoencoder 的模型

在这里插入图片描述

y

(

x

)

=

f

(

W

1

x

+

b

)

      

Z

(

x

)

=

g

(

W

2

y

(

x

)

+

c

)

y(x)=f(W_1x+b)~~~~~~Z(x)=g(W_2y(x)+c)

y(x)=f(W1x+b)      Z(x)=g(W2y(x)+c)

在这里插入图片描述

预训练

Autoencoder自编码器在预训练的时候是一个逐层贪心算法,而且是无监督的算法,在预训练阶段,自编码器的目标就是可以重现输入,即输出和输入要尽可能相同,并且在训练阶段,其每一次只训练一个网络层,训练好了该层,然后再训练下一个网络层,直到整个网络所有的层都训练完成后,才开始整个网络一起进行训练微调。

但是由于输入的维度可能会隐藏层的维度要小,导致直接记住每一个数据,只有部分权重起作用,但是paper不希望这样,因此他在loss中加入了KL散度,来惩罚这样的事情。

S

A

O

=

L

(

X

,

Z

)

+

γ

j

=

1

H

D

K

L

(

ρ

ρ

^

j

)

K

L

(

(

ρ

ρ

^

j

)

=

ρ

l

o

g

ρ

ρ

j

^

+

(

1

ρ

)

l

o

g

1

ρ

1

ρ

j

^

L

(

X

,

Z

)

表示的就是

l

o

s

s

γ

是一个平衡参数,

ρ

是一个超参数,

ρ

^

是数据进过了该层后的平均值

SAO=L(X,Z)+γ∑^{H_D}_{j=1}KL(ρ||hat{ρ}_j) \ KL((ρ||hat{ρ}_j)=ρlogfrac{ρ}{hat{ρ_j}}+(1-ρ)logfrac{1-ρ}{1-hat{ρ_j}}\ L(X,Z)表示的就是loss,γ是一个平衡参数,ρ是一个超参数,hat{ρ}是数据进过了该层后的平均值

SAO=L(X,Z)+γj=1HDKL(ρ∣∣ρ^j)KL((ρ∣∣ρ^j)=ρlogρj^ρ+(1ρ)log1ρj^1ρL(X,Z)表示的就是lossγ是一个平衡参数,ρ是一个超参数,ρ^是数据进过了该层后的平均值
KL散度(相对熵),用来衡量两个分布之间的差异(分布1:以超参数ρ为均值对构成的伯努利分布,分布2:所有的训练集的data通过hidden_layer后的值的平均为均值的伯努利分布,之间的差异)由于loss函数中有使用所有训练集data在通过hidden_layer后的平均值后计算的,因此得先过一遍训练集,然后才可以正常预训练

真正训练

真正训练的时候就是有监督的算法,输入数据,输出预测,与label进行计算loss,然后反向传播,更新参数,与普通的网络算法没什么区别

但是注意Autoencoder预训练时候包括x–>y,输入到隐藏层;y–>z,隐藏层到输出层,但是真正训练的时候只有x–>y,而y–>z则不包含。

代码讲解

参数

sae=SAE()
loss_fn=nn.MSELoss()
optimizer=optim.Adam(sae.parameters(),lr=1e-3)
seq_len=96  # 已知的时间序列的长度
pred_len=96 # 预测的时间序列的长度
epoches=10
rou=0.005

预训练代码

计算经过该层网络后的平均激活,也就是计算过了该层网络后的数据的平均值

# 计算平均激活,就是经过当前层后的值的平均值,在计算KL散度的时候会使用
def rou_hat_cala(i,self,xx):
    '''
    Args:
        (i//2)+1 是训练哪一层fc
        self是使用哪个网络(sae)
        xx是输入数据
    '''
    if i == 0:
        pred = torch.sigmoid(self.fc1(xx))
        rou_hat1 = torch.mean(pred) + 1e-5  # 计算loss时需要使用,加上一个很小的数,防止为0
    elif i == 2:
        pred = torch.sigmoid(self.fc1(xx))
        pred = torch.sigmoid(self.fc2(pred))
        rou_hat1 = torch.mean(pred) + 1e-5  # 计算loss时需要使用,加上一个很小的数,防止为0
    elif i == 4:
        pred = torch.sigmoid(self.fc1(xx))
        pred = torch.sigmoid(self.fc2(pred))
        pred = torch.sigmoid(self.fc3(pred))
        rou_hat1 = torch.mean(pred) + 1e-5  # 计算loss时需要使用,加上一个很小的数,防止为0
    elif i == 6:
        pred = torch.sigmoid(self.fc1(xx))
        pred = torch.sigmoid(self.fc2(pred))
        pred = torch.sigmoid(self.fc3(pred))
        pred = torch.sigmoid(self.fc4(pred))
        rou_hat1 = torch.mean(pred) + 1e-5  # 计算loss时需要使用,加上一个很小的数,防止为0

    elif i == 8:
        pred = torch.sigmoid(self.fc1(xx))
        pred = torch.sigmoid(self.fc2(pred))
        pred = torch.sigmoid(self.fc3(pred))
        pred = torch.sigmoid(self.fc4(pred))
        pred = torch.sigmoid(self.fc5(pred))
        rou_hat1 = torch.mean(pred) + 1e-5  # 计算loss时需要使用,加上一个很小的数,防止为0
    else:rou_hat1=0
    return rou_hat1

预训练的完整代码

首先先把所有的层都冻住,requires_grad=False,这里我和理论讲解处不同的地方在于,我是经过整个网络后计算输出和输入的loss,而不是经过一层计算输入和输出的loss,即我这里不会而外增加从隐藏层到输出的FC

开始进行逐层训练,一次只训练一层网络,require_gard=Ture,然后都是根据公式按部就班的计算,改层网络训练50个epoches,然后就把这一层网络冻住,开始训练下一层网络

#预训练,把不训练的其它层全部冻住(requires_grad=Fasle)
def pre_train(self,train_data):
    rou_hat = 0
    param_lst=[] #把每一层的权重的名字装起来
    #将所有的可训练参数全部设置为False
    for param in self.named_parameters(): #name_parameters()会返回层名字和权重
        param[1].requires_grad=False
        param_lst.append(param[0])

    for i in range(len(param_lst)):
        lst=list(self.named_parameters())#得到网络权重和名称
        if i%2==0:
            lst[i][1].requires_grad=True
            lst[i+1][1].requires_grad=True #逐层训练

            total_len= pred_len + seq_len

            #把训练集的数据都经过一遍网络,然后在计算经过隐藏层的平均激活
            for j in range(train_data.shape[0] // total_len):  # 总共可以取多少个total_len
                x = train_data[j * total_len:(j + 1) * total_len, :]  # 每一次取total长度
                xx = x[:seq_len, :].clone()  # 每一次已知的时间序列(seq_len,dim)
                xx = xx.unsqueeze(0)  # 升维度(1,seq_len,dim)
                xx = xx.permute(0, 2, 1)  # 输出维度是(1,dim,seq_len)

                #计算rou_hat,平均激活
                rou_hat+=rou_hat_cala(i,self,xx)
            rou_hat=rou_hat/(j+1)+1e-5 # +1e-5是为了为0
            for epoch in range(epoches):
                runing_loss = 0
                for j in range(train_data.shape[0] // total_len):  # 总共可以取多少个total_len
                    x = train_data[i * total_len :(i + 1) * total_len, :]  # 每一次取total_len长度
                    xx = x[:seq_len, :].clone()  # 每一次已知的时间序列(seq_len,dim)
                    xx = xx.unsqueeze(0)  # 升维度(1,seq_len,dim)
                    xx = xx.permute(0, 2, 1)  # 输出维度是(1,dim,seq_len)
                    pred = sae(xx)  # 输出是(1,dim,seq_len)
                    optimizer.zero_grad()
                    pred = pred.squeeze()
                    pred=pred.permute(1,0)#输出是(seq-len,dim)
                    kl=rou*torch.log(rou/rou_hat)+(1-rou)*torch.log((1-rou)/(1-rou_hat)) #计算KL散度
                    loss = loss_fn(pred, x[seq_len:, :])+kl  # 计算loss
                    loss.backward()
                    runing_loss += loss
                    optimizer.step()
                    rou_hat = rou_hat_cala(i, self, xx)
                print('第{0}个epoch的loss:{1}'.format(epoch + 1, round((runing_loss / (i + 1)).item(), 2)))
            lst[i][1].requires_grad = False
            lst[i + 1][1].requires_grad = False # 把训练好的层再次冻住

逐层训练完毕后,整一个网络整体进行训练,进行微调,这一部分就和普通的网络进行训练一样

def train(net,loss_fn,optimizer,train_data,seq_len,pred_len,epoches):
    '''
    Args:
        net是需要训练的网络
        loss_fn是使用的损失函数,train_data是整个训练集
        seq_len是已知的时间序列的长度,pred_len是需要预测的时间序列长度
        epoches是外循环多少次,
    '''
    net.train()
    total_len=pred_len+seq_len
    for epoch in range(epoches):
        runing_loss = 0
        for i in range(train_data.shape[0] // total_len):  # 总共可以取多少个total_len
            x=train_data[i*total_len:(i+1)*total_len,:]#每一次取total_len长度
            xx=x[:seq_len,:].clone()#每一次已知的时间序列(seq_len,dim)
            xx=xx.unsqueeze(0)#升维度(1,seq_len,dim)

            xx=xx.permute(0,2,1)#输出维度是(1,dim,seq_len)
            pred=net(xx)#输出是(1,dim,pred_len)
            optimizer.zero_grad()
            pred=pred.contiguous().squeeze(0).permute(1,0)#输出是(pred_len,dim)
            loss=loss_fn(pred,x[seq_len:,:])#计算loss
            loss.backward()
            runing_loss+=loss
            optimizer.step()
        print('第{0}个epoch的loss:{1}'.format(epoch+1,round((runing_loss/(i+1)).item(),2)))

预训练完毕后,把相应的权重进行保存

正式训练的时候可以直接调用预训练的权重

Autoencoder正式训练的代码

模型初始化

初始化五个全连接层,这一个也没什么特别的,这里可以调用之前预训练好的权重

class Autoencoder(nn.Module): #输入是已知的时间序列,输出是预测时间序列
    def __init__(self,args,hidden_size=300):
        '''
        Arg:
        seq_len=96代表输入数据的第二个维度(时间维度:已知多长的时间序列)
        pred_len=96代表预测时间有多长,hidden_size=300是隐藏层的神经元数量
        '''
        super(Autoencoder, self).__init__()
        seq_len=args.seq_len
        pred_len=args.pred_len
        # 输入是(batch_size,dim,seq_len)-->输出为(batch_size,dim,hidden_size)
        self.fc1=nn.Linear(seq_len,hidden_size)
        # 输入是(batch_size,dim,hidden_size)-->输出为(batch_size,dim,hidden_size)
        self.fc2=nn.Linear(hidden_size,hidden_size)
        # 输入是(batch_size,dim,hidden_size)-->输出为(batch_size,dim,hidden_size)
        self.fc3=nn.Linear(hidden_size,hidden_size)
       # 输入是(batch_size,dim,hidden_size)-->输出为(batch_size,dim,hidden_size)
        self.fc4=nn.Linear(hidden_size,hidden_size)
        # 输入是(batch_size,dim,hidden_size)-->输出为(batch_size,dim,pred_len)
        self.fc5=nn.Linear(hidden_size,pred_len)

forward部分的代码,把五个全连接连接在一起,最后输出预测值

def forward(self, enc_x, enc_mark, y, y_mark):
    '''
    Args:
    :param enc_x: 已知的时间序列 (batch_size,seq_len,dim)
    以下的 param本 model未使用,不做过多介绍
    :param enc_mark: 已知的时序序列的时间对应的时间矩阵,
    :param y:
    :param y_mark:
    :return:  x_cat_pred[:,-self.pred_len:,:] 将预测的时间序列的部分返回回去 (batch_size,pred)len,dim)
    '''
    enc_x=enc_x.permute(0,2,1) # 输入为(batch_size,seq_len,dim)--》输出为(batch_size,dim,seq_len)
    x=torch.sigmoid(self.fc1(enc_x)) # 输入是(batch_size,dim,seq_len)-->输出为(batch_size,dim,hidden_size)
    x=torch.sigmoid(self.fc2(x)) # 输入是(batch_size,dim,hidden_size)-->输出为(batch_size,dim,hidden_size)
    x=torch.sigmoid(self.fc3(x)) # 输入是(batch_size,dim,hidden_size)-->输出为(batch_size,dim,hidden_size)
    x=torch.sigmoid(self.fc4(x)) # 输入是(batch_size,dim,hidden_size)-->输出为(batch_size,dim,hidden_size)
    x=self.fc5(x)  # 输入是(batch_size,dim,hidden_size)-->输出为(batch_size,dim,pred_len)
    x=x.permute(0,2,1)#输入是(batch_size,dim,pred_len)输出是(batch_size,pred_len,dim)
    return x #返回值的shape是(batch_size,pred_len,dim)

数据压缩网络

根据Autoencoder的特性,另一个思路就是根据Auencoder进行数据压缩,压缩过后的数据在送入LSTM中进行预测任务

class SAE(nn.Module):
    def __init__(self,arg):
        super(SAE, self).__init__()
        self.arg=arg
        # 先使用SAE_encoder把数据的时间维度进行压缩,压缩为hidden_size2,即从seq_len-->hidden_size2
        self.SAE_encoder=SAE_encoder(seq_len=self.arg.seq_len,hidden_size1=72,hidden_size2=48)
        # 将经过SAE_encoder压缩过后的数据,放入LSTM中进行预测任务
        self.LSTM=LSTM(seq_len=48,pred_len=self.arg.pred_len,dim=self.arg.d_feature,hidden_size=128,num_layers=1,batch_size=self.arg.batch_size)

    def forward(self,enc_x, enc_mark, y, y_mark):
        '''
        :param enc_x: 已知的时间序列 (batch_size,seq_len,dim)
        以下的 param本 model未使用,不做过多介绍
        :param enc_mark: 已知的时序序列的时间对应的时间矩阵,
        :param y:
        :param y_mark:
        :return:  x 将预测的时间序列的部分返回回去 (batch_size,pred_len,dim)
        '''
        # 其中预训练是使用自编码器的方法进行预训练
        self.SAE_encoder.load_state_dict(torch.load('./checkpoint/SAE/SAE_encoder')) # 使用预训练的SAE_encoder的权重
        x=self.SAE_encoder(enc_x) # x shape(batch_size,dim,hidden_size2),seq_len被压缩为hidden_size2, 为了方便后面的seq_len都是表示hidden_size2
        x=x.permute(2,0,1)#输出为(seq_len,batch_size,dim)
        x=self.LSTM(x)#输出是(pred_len,batch_size,dim)
        x=x.permute(1,0,2)#输出是(batch_size,pred_len,dim)
        return x

首先数据压缩网络也没有什么特别的,就是几个全连接神经网络将数据的时间维度进行了压缩

class SAE_encoder(nn.Module):
    def __init__(self,seq_len=96,hidden_size1=72,hidden_size2=48):
        '''
        Arg:
        seq_len=96代表输入数据的第二个维度(时间维度:已知多长的时间序列)
        hidden_size1=72是隐藏层1的神经元数量,hidden_size2=48是隐藏层2的神经元数量
        其中hidden_size2就是经过SAE_encoder压缩过后的数据的时间维度
        '''
        super(SAE_encoder, self).__init__()
        # 输入是(batch_size,in_channels,seq_len)-->输出为(batch_size,in_channels,hidden_size1)
        # 其中in_channels表示的是数据的特征维度,本任务中为7
        self.hidden1=nn.Linear(seq_len,hidden_size1)
        # 输入是(batch_size,in_channels,hidden_size1)-->输出为(batch_size,in_channels,hidden_size2)
        self.hidden2=nn.Linear(hidden_size1,hidden_size2)
    def forward(self,x):
        x=x.permute(0,2,1) # 对输入数据x进行转置,处理的是seq_len维度
        x=torch.relu(self.hidden1(x)) # 输出为(batch_size,in_channels,hidden_size1)
        x=self.hidden2(x) # 输出为(batch_size,in_channels,hidden_size2)
        return x

数据压缩过后送入LSTM网络中

初始化LSTM网络

class LSTM(nn.Module):#输入的数据维度(seq_len,batch_size,dim),因为使用了SAE进行数据压缩,把时间维度从seq_len变成hidden_size2

    def __init__(self,seq_len=48,pred_len=96,dim=7,hidden_size=128,num_layers=1,batch_size=8):
        '''
        Args:
        :param seq_len: 表示已知的时间维度,这里应该要和SAE_encoder的hidden_size2
        :param pred_len: 表示预测的时间序列的长度
        :param dim: 表示数据的特征维度
        :param hidden_size:表示隐藏层的单元数
        :param num_layers:使用多少层lstm网络
        :param batch_size:时间序列的batch_size
        '''
        super(LSTM, self).__init__()
        self.seq_len=seq_len # seq_len是经过数据压缩后的长度
        self.dim=dim
        self.pred_len=pred_len
        self.num_layers=num_layers
        self.hidden_sie=hidden_size
        self.batch_size=batch_size
        self.total_len=seq_len+pred_len # 一次任务总共的时间序列
        # 输入是(seq_len,batch_size,dim)-->输出是(seq_len,batch_size,hidden_size)
        # 其中seq_len应该为SAE_encoder的hidden_size2
        self.lstm=nn.LSTM(input_size=dim,hidden_size=hidden_size,
                          num_layers=num_layers,batch_first=False) # 初始化一个LSTM网络
        # 输入为(seq_len,batch_size,hidden_size)-->输出为(seq_len,batch_size,dim)
        self.fc_dim=nn.Linear(hidden_size,dim)#压缩数据特征维度
        #  输入为(dim,batch_size,seq_len)-->(dim,batch_size,1)
        self.fc_time=nn.Linear(seq_len,1)#压缩时间维度

forward部分的代码

每一次获得seq_len长度的时间数据,得到下一天的时间数据,实现的code为pred_onestep,也是forward部分的主体功能实现代码,其余forward部分的代码时获得已知的时间数据,得到预测的数据,把数据拼接回已知的时间数据的序列中,然后继续滑动窗口得到下一个的输入数据

def forward(self,x):
    hidden=torch.zeros(self.num_layers,self.batch_size,self.hidden_sie).to(x.device) # hidden_size=(num_layers,batch_size,hidden_size)
    cell=torch.zeros(self.num_layers,self.batch_size,self.hidden_sie).to(x.device) # 初始化hidden和cell
    # 初始化x_cat_pred 用来装已知的时间序列和预测的时间序列
    x_cat_pred=torch.zeros(self.total_len,self.batch_size,self.dim).to(x.device) # 把预测部分也放在一起 x_cat_pred=(seq_len+pred_len,batch_size,dim)
    # 把已知的时间序列放入x_cat_pred中
    x_cat_pred[:self.seq_len,:,:]=x_cat_pred[:self.seq_len,:,:].clone()+x # 把已知的序列数据也放入到x_cat_pred中

    for i in range(self.pred_len): # 滑动多少次,预测多少长的时间序列就滑动多少次
        lstm_input=x_cat_pred[i:i+self.seq_len,:,:].clone()#获取每一次的输入lstm_input (seq_len,batch_size,dim)
        pred=self.pred_onestep(lstm_input,hidden,cell)#输出是pred (1,batch_szie,dim)
        x_cat_pred[i+self.seq_len,:,:]=x_cat_pred[i+self.seq_len,:,:].clone().unsqueeze(0)+pred#把预测值也拼接回去
    return x_cat_pred[-self.pred_len:,:,:]#把预测的在返回回去,输出是(pred_len,batch_size,dim)

pred_onestep

一次只预测一天的时间数据

输入数据(seq_len,batch_size,dim)–>(1,batch_size,dim)

# 每一次只预测下一天的时间序列
def pred_onestep(self,x,hidden,cell): # 输入为(seq_len,batch_size,dim)
    output,(hidden,cell)=self.lstm(x,(hidden,cell))#output是(seq_len,batch_size,hidden_size)
    output=self.fc_dim(output)#输出是(seq_len,batch_size,dim)
    output=output.permute(2,1,0)#变为(dim,batch_size,seq_len),为了对时间维度进行操作
    output=self.fc_time(output)#输出为(dim,batch_size,1)
    output=output.permute(2,1,0)#变回(1,batch_size,dim),1表示一个时间点
    return output # (1,batch_size,dim)

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>