什么是TCN
TCN全稱Temporal Convolutional Network,時序卷積網絡,是在2018年提出的一個卷積模型,但是可以用來處理時間序列。
卷積如何處理時間序列
時間序列預測,最容易想到的就是那個馬爾可夫模型:
就是計算某一個時刻的輸出值,已知條件就是這個時刻之前的所有特征值。上面公式中,P表示概率,可以不用管這個,\(y_k\)表示k時刻的輸出值(標簽),\(x_k\)表示k時刻的特征值。
如果使用LSTM或者是GRU這樣的RNN模型,自然是可以處理這樣的時間序列模型的,畢竟RNN生來就是為了這個的。
但是這個時間序列模型,宏觀上思考的話,其實就是對這個這個時刻之前的數據做某個操作,然后生成一個標簽,回想一下在卷積在圖像中的操作,其實有異曲同工。(這里不理解也無妨,因為我之前搞了一段時間圖像處理,所以對卷積相對熟悉一點)。
一維卷積
假設有一個時間序列,總共有五個時間點,比方說股市,有一個股票的價格波動:[10,13,12,14,15]:
TCN中,或者說因果卷積中,使用的卷積核大小都是2,我也不知道為啥不用更大的卷積核,看論文中好像沒有說明這個,如果有小伙伴知道原因或者有猜想,可以下方評論處一起討論討論。
卷積核是2,那么可想而知,對上面5個數據做一個卷積核為2的卷積是什么樣子的:
五個數據經過一次卷積,可以變成四個數據,但是每一個卷積后的數據都是基於兩個原始數據得到的,所以說,目前卷積的視野域是2。
可以看到是輸入是5個數據,但是經過卷積,變成4個數據了,在圖像中有一個概念是通過padding來保證卷積前后特征圖尺寸不變,所以在時間序列中,依然使用padding來保證尺寸不變:
padding是左右兩頭都增加0,如果padding是1的話,就是上圖的效果,其實會產生6個新數據,但是秉着:“輸入輸出尺寸相同”和“我們不能知道未來的數據”,所以最后邊那個未來的padding,就省略掉了,之后再代碼中會體現出來。
總之,現在我們大概能理解,對時間序列卷積的大致流程了,也就是對一維數據卷積的過程(圖像卷積算是二維)。
下面看如何使用Pytorch來實現一維卷積:
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就這么多,謝謝大家。
喜歡的話加個微信公眾號支持一下吧~目前主要再整理針對機器學習算法崗位的面試可能遇到的知識點。
公眾號回復【下載】有精選的免費機器學習學習資料。 公眾號每天會更新一個機器學習、深度學習的小知識,都是面試官會問的知識點哦~
- 【機器學習的基礎數學(PDF)】
- 【競賽中的大數據處理流程(PDF)】
- 【如何做大數據的基礎特征工程(PDF)】
- 【自然語言處理NLP的應用實踐大合集(PDF)】
- 【python入門級教材(400頁PDF)】
公眾號每天會更新一個機器學習、深度學習的小知識,都是面試官會問的知識點哦~