LSTM的備胎,用卷積處理時間序列——TCN與因果卷積(理論+Python實踐)

什麼是TCN

TCN全稱Temporal Convolutional Network,時序卷積網絡,是在2018年提出的一個卷積模型,可是能夠用來處理時間序列。python

卷積如何處理時間序列

時間序列預測,最容易想到的就是那個馬爾可夫模型:網絡

\[P(y_k|x_k,x_{k-1},...,x_1) \]

就是計算某一個時刻的輸出值,已知條件就是這個時刻以前的全部特徵值。上面公式中,P表示機率,能夠不用管這個,\(y_k\)表示k時刻的輸出值(標籤),\(x_k\)表示k時刻的特徵值。ide

若是使用LSTM或者是GRU這樣的RNN模型,天然是能夠處理這樣的時間序列模型的,畢竟RNN生來就是爲了這個的。函數

可是這個時間序列模型,宏觀上思考的話,其實就是對這個這個時刻以前的數據作某個操做,而後生成一個標籤,回想一下在卷積在圖像中的操做,其實有殊途同歸。(這裏不理解也無妨,由於我以前搞了一段時間圖像處理,因此對卷積相對熟悉一點)。學習

一維卷積

假設有一個時間序列,總共有五個時間點,比方說股市,有一個股票的價格波動:[10,13,12,14,15]:
在這裏插入圖片描述
TCN中,或者說因果卷積中,使用的卷積核大小都是2,我也不知道爲啥不用更大的卷積核,看論文中好像沒有說明這個,若是有小夥伴知道緣由或者有猜測,能夠下方評論處一塊兒討論討論。spa

卷積核是2,那麼可想而知,對上面5個數據作一個卷積核爲2的卷積是什麼樣子的:
在這裏插入圖片描述
五個數據通過一次卷積,能夠變成四個數據,可是每個卷積後的數據都是基於兩個原始數據獲得的,因此說,目前卷積的視野域是2。.net

能夠看到是輸入是5個數據,可是通過卷積,變成4個數據了,在圖像中有一個概念是經過padding來保證卷積先後特徵圖尺寸不變,因此在時間序列中,依然使用padding來保證尺寸不變:
在這裏插入圖片描述
padding是左右兩頭都增長0,若是padding是1的話,就是上圖的效果,其實會產生6個新數據,可是秉着:「輸入輸出尺寸相同」和「咱們不能知道將來的數據」,因此最後邊那個將來的padding,就省略掉了,以後再代碼中會體現出來。3d

總之,如今咱們大概能理解,對時間序列卷積的大體流程了,也就是對一維數據卷積的過程(圖像卷積算是二維)。code

下面看如何使用Pytorch來實現一維卷積:orm

net = nn.Conv1d(in_channels=1,out_channels=1,kernel_size=2,stride=1,padding=1,dilation=1)

其中的參數跟二維卷積很是相似,也是有通道的概念的。這個好好品一下,一維數據的通道跟圖像的通道同樣,是根據不一樣的卷積核從相同的輸入中抽取出來不一樣的特徵。kernel_size=2以前也說過了,padding=1也沒問題,不過這個公式中假如輸入5個數據+padding=1,會獲得6個數據,最後一個數據被捨棄掉。dilation是膨脹係數,下面的下面會講。

因果卷積

  • 因果卷積是在wavenet這個網絡中提出的,以後被用在了TCN中。
    TCN的論文連接
  • 因果卷積應爲就是:Causal Convolutions。

以前已經講了一維卷積的過程了,那麼因果卷積,其實就是一維卷積的一種應用吧算是。

假設想用上面講到的概念,作一個股票的預測決策模型,而後但願決策模型能夠考慮到這個時間點以前的4個時間點的股票價格進行決策,總共有3種決策:

  • 0:不操做,1:買入,2:賣出

因此其實就是一個分類問題。由於要求視野域是4,因此按照上面的設想,要堆積3個卷積核爲2的1維卷積層:
在這裏插入圖片描述
三次卷積,可讓最後的輸出,擁有4個視野域。就像是上圖中紅色的部分,就是作出一個決策的過程。

股票數據,每每是按照分鐘記錄的,那少說也是十萬、百萬的數據量,咱們決策,想要考慮以前1000個時間點呢?視野域要是1000,那意味着要999層卷積?啥計算機吃得消這樣的計算。因此引入了膨脹因果卷積。

膨脹因果卷積

  • 英文是Dilated Causal Convolution。這個其實就是空洞卷積啦,不肯定在以前的博文中有沒有講過這個概念(最近別人要求在寫一個很是長的教程,和博客中的博文可能會有記憶混亂的狀況2333)
  • 反正就是,這個空洞卷積、或者叫擴張卷積、或者叫膨脹卷積就是操做dilation這個參數。
    在這裏插入圖片描述
    如圖,這個就是dilation=2的時候的狀況,與以前的區別有兩個:
  • 看紅色區域:能夠看到卷積核大小依然是2,可是卷積核之間變得空洞了,隔過去了一個數據;若是dilation=3的話,那麼能夠想而知,這個卷積核中間會空的更大,會隔過去兩個數據。
  • 看淡綠色數據:由於dilation變大了,因此相應的padding的數量從1變成了2,因此爲了保證輸入輸出的特徵維度相同,padding的數值等於dalition的數值(在卷積核是2的狀況下,嚴格說說:padding=(kernel_size-1)*dilation)

而後咱們依然實現上面那個例子,每次決策想要視野域爲4:
在這裏插入圖片描述
能夠看到,第一次卷積使用dilation=1的卷積,而後第二次使用dilation=2的卷積,這樣經過兩次卷積就能夠實現視野域是4.

那麼假設事業域要是8呢?那就再加一個dilation=4的卷積。dilation的值是2的次方,而後視野域也是2的次方的增加,那麼就算是要1000視野域,那十層大概就好了。

這裏有一個動圖,挺好看的:

TCN結構

TCN基本就是一個膨脹因果卷積的過程,只是上面咱們實現因果卷積就只有一個卷積層。而TCN的稍微複雜一點(可是不難!)

  • 卷積結束後會由於padding致使卷積以後的新數據的尺寸B>輸入數據的尺寸A,因此只保留輸出數據中前面A個數據;
  • 卷積以後加上個ReLU和Dropout層,不過度吧這要求。
  • 而後TCN中並非每一次卷積都會擴大一倍的dilation,而是每兩次擴大一倍的dilation
  • 總之TCN中的基本組件:TemporalBlock()是兩個dilation相同的卷積層,卷積+修改數據尺寸+relu+dropout+卷積+修改數據尺寸+relu+dropout
  • 以後弄一個Resnet殘差鏈接來避免梯度消失,結束!
    關於Resnet的內容:【從零學習PyTorch】 如何殘差網絡resnet做爲pre-model +代碼講解+殘差網絡resnet是個啥其實不看也行,不妨礙理解TCN

模型的PyTorch實現(最好了解一點PyTorch)

若是不瞭解的話,emm,我要安利個人博文了2333:
從零學習pytorch 第5課 PyTorch模型搭建三要素

# 導入庫
import torch
import torch.nn as nn
from torch.nn.utils import weight_norm
# 這個函數是用來修剪卷積以後的數據的尺寸,讓其與輸入數據尺寸相同。
class Chomp1d(nn.Module):
    def __init__(self, chomp_size):
        super(Chomp1d, self).__init__()
        self.chomp_size = chomp_size

    def forward(self, x):
        return x[:, :, :-self.chomp_size].contiguous()

能夠看出來,這個函數就是第一個數據到倒數第chomp_size的數據,這個chomp_size就是padding的值。比方說輸入數據是5,padding是1,那麼會產生6個數據沒錯吧,那麼就是保留前5個數字。

# 這個就是TCN的基本模塊,包含8個部分,兩個(卷積+修剪+relu+dropout)
# 裏面提到的downsample就是下采樣,其實就是實現殘差連接的部分。不理解的能夠無視這個
class TemporalBlock(nn.Module):
    def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation, padding, dropout=0.2):
        super(TemporalBlock, self).__init__()
        self.conv1 = weight_norm(nn.Conv1d(n_inputs, n_outputs, kernel_size,
                                           stride=stride, padding=padding, dilation=dilation))
        self.chomp1 = Chomp1d(padding)
        self.relu1 = nn.ReLU()
        self.dropout1 = nn.Dropout(dropout)

        self.conv2 = weight_norm(nn.Conv1d(n_outputs, n_outputs, kernel_size,
                                           stride=stride, padding=padding, dilation=dilation))
        self.chomp2 = Chomp1d(padding)
        self.relu2 = nn.ReLU()
        self.dropout2 = nn.Dropout(dropout)

        self.net = nn.Sequential(self.conv1, self.chomp1, self.relu1, self.dropout1,
                                 self.conv2, self.chomp2, self.relu2, self.dropout2)
        self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None
        self.relu = nn.ReLU()
        self.init_weights()

    def init_weights(self):
        self.conv1.weight.data.normal_(0, 0.01)
        self.conv2.weight.data.normal_(0, 0.01)
        if self.downsample is not None:
            self.downsample.weight.data.normal_(0, 0.01)

    def forward(self, x):
        out = self.net(x)
        res = x if self.downsample is None else self.downsample(x)
        return self.relu(out + res)

最後就是TCN的主網絡了:

class TemporalConvNet(nn.Module):
    def __init__(self, num_inputs, num_channels, kernel_size=2, dropout=0.2):
        super(TemporalConvNet, self).__init__()
        layers = []
        num_levels = len(num_channels)
        for i in range(num_levels):
            dilation_size = 2 ** i
            in_channels = num_inputs if i == 0 else num_channels[i-1]
            out_channels = num_channels[i]
            layers += [TemporalBlock(in_channels, out_channels, kernel_size, stride=1, dilation=dilation_size,
                                     padding=(kernel_size-1) * dilation_size, dropout=dropout)]

        self.network = nn.Sequential(*layers)

    def forward(self, x):
        return self.network(x)

咋用的呢?就是num_inputs就是輸入數據的通道數,通常就是1; num_channels應該是個列表,其餘的np.array也行,比方說是[2,1]。那麼整個TCN模型包含兩個TemporalBlock,整個模型共有4個卷積層,第一個TemporalBlock的兩個卷積層的膨脹係數\(dilation=2^0=1\),第二個TemporalBlock的兩個卷積層的膨脹係數是\(dilation=2^1=2\).

沒了,整個TCN挺簡單的,若是以前學過PyTorch和圖像處理的一些內容,而後用TCN來上手時間序列,效果會和LGM差很少。(根據最近作的一個比賽),沒有跟Wavenet比較過,Wavenet的pytorch資源看起來怪複雜的,由於wavenet是用來處理音頻生成的,會更加複雜一點。

總之TCN就這麼多,謝謝你們。

相關文章
相關標籤/搜索