前言
ROCKET(RandOm Concolution KErnel Transform)是一种利用随机卷积核来实现时间序列分类方法的算法,其核心思想就是随机卷积核的一切:尺寸、偏置量、是否填充、膨胀系数等等,通过大量的随机卷积核从数据中提取特征,利用类似池化的操作提取最大值和正例比例得到特征向量,结合线性分类器完成数据的分类任务。
1.ROCKET简介
ROCKET是RandOm Concolution KErnel Transform的缩写,不得不说名字取得很好,随机卷积核变换=火箭。
ROCKET不同于其他的分类算法,它不需要一些先验知识,不是从形状、频谱等等一些“显式特征”来进行分类的,提取的特征是什么样完全取决于这些随机卷积核的卷积结果,这样的好处是对于未知的数据也会有比较好的效果,而且卷积核不需要设计,全都是随机产生的,整个算法运行速度也挺快的。
文章后续会根据算法流程展开,其中代码的编写是从论文提供的源代码、pyts提供的代码中学习的,并且修改了一些地方使它的速度比原来的更快一点(完整的从训练到测试分类速度快一点,但快的不是很多)。
2.卷积核生成
ROCKET的随机卷积核的权值、偏置、是否填充、膨胀系数都是随机选择的,其中:
- 尺寸是从[7,9,11]中随机抽取
- 权值从正态分布中随机抽取;
- 偏置量在[-1,1]均匀分布中抽取;
- 是否填充各50%概率选择;
- 膨胀系数与输入的时间序列长度有关:
d = 2 x , x ∼ U ( 0 , A ) , A = l o g 2 l i n p u t − 1 l k e r n e l − 1 d=2^x,x\sim U(0,A),A=log_2\frac{l_{input}-1}{l_{kernel}-1} d=2x,x∼U(0,A),A=log2lkernel−1linput−1
这样的作用是保证单个卷积核可以将感受野扩充至整个时间序列长度中。
这些概念和卷积神经网络的内容很像,但是我还没学过神经网络所以从个人角度来谈一谈理解:
- 权值不必多说,卷积核每一个点的数值;
- 偏置即在卷积运算结束后额外附加一个值;
- 填充可以理解为是否在序列前后补零,保证卷积后的序列长度与原始序列相同;
- 膨胀系数的话,可以理解为在卷积核的每个权值之间增添指定数量的0,如
假设原始时间序列为 T = [ 1 , 3 , 5 , 2 , 1 , 3 , 5 , 4 ] T=[1,3,5,2,1,3,5,4] T=[1,3,5,2,1,3,5,4],卷积核 [ 1 / 3 , 1 / 3 , 1 / 3 ] [1/3,1/3,1/3] [1/3,1/3,1/3],即进行一个均值滤波计算(不填补)
原始计算结果: [ 3 , 10 / 3 , 8 / 3 , 2 , 3 , 4 ] [3,10/3,8/3,2,3,4] [3,10/3,8/3,2,3,4]
膨胀系数为1时,卷积核变为 [ 1 / 3 , 0 , 1 / 3 , 0 , 1 / 3 ] [1/3,0,1/3,0,1/3] [1/3,0,1/3,0,1/3]
此时计算结果为: [ 7 / 3 , 8 / 3 , 11 / 3 , 3 ] [7/3,8/3,11/3,3] [7/3,8/3,11/3,3]
将均值滤波的核的尺寸从3扩充到了5,这样做均值滤波计算的时候就是第1,3,5点进行加权平均,扩大了计算范围(感受野),使卷积能捕捉到更广泛的特征。
实战代码
@njit()
def kernel_generate(length_list, l_input, num):
"""
用于生成指定数量的随机卷积核
:param length_list:提供的卷积核尺寸列表
:param l_input: 输入的时间序列的长度
:param num: 指定的卷积核数量
:return: 权重、偏置、膨胀系数、填充
"""
length_choice = np.random.choice(np.array(length_list), num)
weights = [np.zeros(length, dtype=np.float64) for length in length_choice]
dilations = np.zeros(num)
biases = np.zeros(num)
paddings = np.zeros(num)
# 其实最好都加一个dtype=np.int32
for i in range(num):
weight = np.random.normal(0, 1, length_choice[i])
weights[i] = weight - np.mean(weight) # 去均值化
dilations[i] = np.int32(2 ** np.random.uniform(0, np.log2((l_input - 1) / (length_choice[i] - 1) - 1)))
# 注意这里减了个1,是因为个人觉得扩充卷积核尺寸至原始序列长度没啥必要,而且之前在写代码时候出现了尺寸设置的一些问题,
# -1解决了一切问题咳咳
biases[i] = np.random.uniform(-1, 1)
paddings[i] = np.random.randint(2)
return weights, dilations, paddings, biases
这里用到了
@
n
j
i
t
(
)
可以大大大大提高循环这些计算的速度,但是需要注意函数中变量类别规范、且用的
一些别的库必须是
n
u
m
b
a
所支持的
\textcolor{red}{这里用到了@njit()可以大大大大提高循环这些计算的速度,但是需要注意函数中变量类别规范、且用的\\一些别的库必须是numba所支持的}
这里用到了@njit()可以大大大大提高循环这些计算的速度,但是需要注意函数中变量类别规范、且用的一些别的库必须是numba所支持的
注意到这里的输入是卷积核尺寸列表,虽然原文献说的是[7,9,11]中选取,但是我还是喜欢自由一点的选择。
2.卷积运算
其实这里没有太多要说的,主要是为了提升计算的速度的一些技巧,我之前看过几篇文章,说的是在运行速度上,C/C++>numba>numpy>matlab>原生python,这里面用到了@njit(),如果还是用np.convolve(),相信我,真是慢了很多,因此直接使用循环来计算卷积:
实战代码
@njit()
def conv_diy(data, weight, dilation, biase, padding):
"""
将时间序列与卷积核进行卷积运算
:param data:单个数据
:param weight:权重
:param dilation:膨胀系数
:param biase:偏置项
:param padding:是否填充
:return:卷积结果
"""
n_stamp = len(data)
kernel_len = len(weight)
zero_len = (kernel_len + (kernel_len - 1) * dilation) // 2 if padding == 1 else 0
zero_padding = np.zeros(zero_len)
new_data = np.concatenate((zero_padding, data, zero_padding))
conv_num = n_stamp - kernel_len - (kernel_len-1)*dilation + 1 if padding == 0 else n_stamp
ppv = 0 # 正例比例,即结果为正的所占比例
max_val = -np.inf
for i in prange(conv_num):
temp = 0
for j in prange(kernel_len):
temp += weight[j] * new_data[i+j*dilation]
temp += biase
max_val = max(temp,max_val)
ppv = ppv+1 if temp > 0 else ppv
return max_val, ppv/conv_num
可以注意这里的:
for j in prange(kernel_len):
temp += weight[j] * new_data[i+j*dilation]
这里的i+j*dilation其实就是实现了膨胀的效果,我之前也尝试了先将卷积核填充0后再计算,但是运行速度上慢了不是一点。
3.分类训练
文章说的是可以与任何线性分类器结合,不挑,原文章使用的是岭回归和逻辑回归,我试了一下线性SVM,效果会更好一些。这里就没必要贴代码啦。
4.小总结+MINI ROCKET
可以看到,ROCKET算法不是很复杂,但的的确确是一个很有效的分类算法,用这些随机的卷积核尽量提取特征,通过池化操作得到最大值和正例比例来构成特征向量,即尽量从潜在特征中抽取出有用的特征来进行分类,即便我们根本不知道这些潜在特征是什么,从这个角度来看,对于那些未知的数据直接上ROCKET提取一波还是很合理的。
当然看到这里朋友们也能想到这个算法的一些问题,比如什么都是随机的,导致每次分类的结果不会是完全一致的,但是有一说一,测试下来差的不会很多,因为卷积核的数量几百上千上万的,量很大,最终结果的波动也不是特别大。
改进方法也有一些,我提一下比较好的一个:MINI ROCKET,这个算法把很多随机的过程都取消了,而换成了固定的,先看一下下面这张图:
迷你火箭固定了核的尺寸为9,使用的卷积核保证权值之和为0来确保常量不偏移,然后提出了用加法来代替卷积的乘法运算,速度非常之快,论文中写的速度是原来的二十多倍,至少我测试卷积的运算确实很快,而且很有趣,然后就是取消了池化提取最大值,因为没啥用,正例比例就够了,感兴趣的朋友可以读一读原文,我就不详细说了。
参考文献
Dempster A, Petitjean F, Webb G I. ROCKET: exceptionally fast and accurate time series classification using random convolutional kernels[J]. Data Mining and Knowledge Discovery, 2020, 34(5): 1454-1495.
Dempster A, Schmidt D F, Webb G I. Minirocket: A very fast (almost) deterministic transform for time series classification[C]//Proceedings of the 27th ACM SIGKDD conference on knowledge discovery & data mining. 2021: 248-257.