基于VGG16的股市预测
上一篇整了下VGG16识别狗猫,这回来个乱入的,预测股市:)
# 纯属恶搞
我想干啥
股市的预测被人憧憬了很久了。想想,要是真搞成了,不用多,每天来个3%,就足够打败巴菲特了。。。
当然,虽说是恶搞,但也要按科学来,总不能没一点逻辑啊。
既然要基于VGG16,总要有图形数据吧?股市有K线图,难道就要基于K线图?给电脑看K线图,然后AI就可以告诉我们下一步能不能涨?涨多少?
咳咳。。。这样也不是不行,不过,这不是我想干的。
我的思路是这样,所谓的图像,不就是一个二维数组吗?值就是每个像素的RGB值,所以,一般是3维,比如这样:
(224,224,3)
其中(224,224)是像素坐标,3,则是RGB值。
股市里我们也找个二维数组,好找,比如这个:
ts_code trade_date open high low close pre_close change pct_chg vol amount
0 600928.SH 20220617 3.89 3.97 3.88 3.96 3.91 0.05 1.2788 291159.15 114358.081
1 600928.SH 20220616 4.02 4.03 3.91 3.91 4.06 -0.15 -3.6946 477974.67 188631.013
2 600928.SH 20220615 3.96 4.21 3.93 4.06 3.99 0.07 1.7544 870343.32 353336.822
3 600928.SH 20220614 3.84 4.02 3.79 3.99 3.88 0.11 2.8351 576001.07 223688.091
4 600928.SH 20220613 4.00 4.04 3.84 3.88 3.96 -0.08 -2.0202 566726.51 223235.386
这里面真正有意义的是后面9列,开盘价、收盘价、涨跌幅什么的。如果我们取相邻9天的,不就是9 * 9的二维数组了吗?
当然,RGB值是3个,上面这个9*9怎么搞成3个,我还没有很好的思路,目前只是简单的复制了3份。大家有好的其他想法可以告诉我。
造出来这个(9,9,3)的数据,后面就好办了,套用上一篇的分析就好了。是不是很天才?
:)
开干
说干就干。股市数据用的是Tushare,知道的应该不少,不知道的百度一下就好。现在数据还免费,真好。
第一步:加载股市数据
import tushare as ts
# ts.get_hist_data('600928') #一次性获取全部日k线数据
import pandas as pd
import numpy as np
#这里要用自己的tushare token
pro = ts.pro_api('XXXX')
data = pro.daily(ts_code='600928.SH', start_date='20190101', end_date='20220618')
data
这样就可以加载出最近三年的某股票日k线数据,结果就是上面所示的那样。
第二步:数据整理:逆序
# data.loc[:,'open':'amount']
#只取后面最重要的9列,前面的去掉
price = data.loc[:,'open':'amount']
#逆序。最早的时间在最前面
price = price.iloc[::-1]
price = price.reset_index(drop=True)
# price = price.loc[:,'close']
price.head()
'''
open high low close pre_close change pct_chg vol amount
0 5.62 6.74 5.62 6.74 4.68 2.06 44.0171 4414.27 2967.378
1 7.41 7.41 7.41 7.41 6.74 0.67 9.9407 4651.68 3446.895
2 8.15 8.15 8.15 8.15 7.41 0.74 9.9865 10350.85 8435.943
3 8.97 8.97 8.97 8.97 8.15 0.82 10.0613 18318.15 16431.381
4 9.87 9.87 9.87 9.87 8.97 0.90 10.0334 19916.95 19658.030
'''
这一步是做一下基本的数据整理。
首先,只取后面最重要的9列,前面的去掉。用的pandas dataframe的loc函数来切片。
其次,tushare的数据是最新的在最前面,要逆序一下,让最早的数据在最前面。iloc函数。
同时原来的索引值要去掉,重新索引一下。
最后的结果就变成了我们想要的样子。
第三步:可视化(可选)
%matplotlib inline
from matplotlib import pyplot as plt
fig1 = plt.figure(figsize=(18,15))
plt.plot(price.loc[:,'close'])
plt.title('close price')
plt.xlabel('datetime')
plt.ylabel('price')
plt.show()
这里仅仅是拿每天的收盘价做了个曲线,看一下,省的寂寞。
第四步,数据整理:消除负数和大数
minpricechange =min(price['change'])
minpricechange
price['change'] = price['change'] - minpricechange
min(price['change'])
minpctchange =min(price['pct_chg'])
minpctchange
price['pct_chg'] = price['pct_chg'] - minpctchange
min(price['pct_chg'])
maxvol =max(price['vol'])
maxvol
price['vol'] = (price['vol']/maxvol)*255
maxamount =max(price['amount'])
maxamount
price['amount'] = (price['amount']/maxamount)*255
print(price.loc[0:8,:])
''' 这是整理完的数据
open high low close pre_close change pct_chg vol \
0 5.62 6.74 5.62 6.74 4.68 3.11 54.0608 0.362225
1 7.41 7.41 7.41 7.41 6.74 1.72 19.9844 0.381706
2 8.15 8.15 8.15 8.15 7.41 1.79 20.0302 0.849368
3 8.97 8.97 8.97 8.97 8.15 1.87 20.1050 1.503147
4 9.87 9.87 9.87 9.87 8.97 1.95 20.0771 1.634340
5 10.86 10.86 10.86 10.86 9.87 2.04 20.0741 19.165173
6 10.91 11.95 10.23 11.95 10.86 2.14 20.0805 255.000000
7 11.35 12.79 11.20 12.08 11.95 1.18 11.1316 204.793995
8 11.85 12.26 11.35 11.45 12.08 0.42 4.8285 129.462168
amount
0 0.221281
1 0.257040
2 0.629080
3 1.225310
4 1.465926
5 18.914500
6 255.000000
7 222.530055
8 137.054107
'''
涨跌幅和涨跌比例,两个数据里面是有负数的。VGG16是对图像的处理,里面没有负数,而且数据都是0-255之间。交易量Vol和交易金额都是超过255的数据,所以对于大数也要做额外的处理。如果选的是茅台,可能对于交易价格等还要做相应处理。
消除负数的思路也很简单,把最小的数设为0,其他的数据都同等加一个数。
消除大数的思路也简单,同比例缩放到0-255即可。
第五步:数据整理:改造成VGG16所需的(224,224,3)
首先准备一个改造的函数:
from skimage.transform import resize
#define x and y
#define method to extract x and y
def extract_stock_data(price,time_step):
xx = np.zeros([len(price)-time_step,224,224,3])
xy = np.zeros([len(price)-time_step])
for i in range(len(price)-time_step):
#print('第行',aa)
aa = np.array(price.loc[i:i+time_step,:])
#aa = aa.reshape(aa.shape[0],aa.shape[1],1)
img99 = np.zeros([9,9,3])
img99[:,:,0] = aa
img99[:,:,1] = aa
img99[:,:,2] = aa
xx[i] = resize(img99, (224,224,3),mode ='constant', preserve_range = True)
bb = 0.
if i<(len(price)-time_step-1):
bb = price.loc[i+time_step+1,'pct_chg'] + minpctchange
else:
bb = price.loc[i+time_step,'pct_chg'] + minpctchange
if bb > -1 and bb < 1:
bb = 0
elif bb >= 1 and bb < 3:
bb = 2
elif bb >= 3 and bb < 5:
bb = 4
elif bb >= 5:
bb = 6
elif bb > -3 and bb <= -1:
bb = -2
elif bb > -5 and bb <= -3:
bb = -4
else:
bb = -6
bb = bb + 6.0
xy[i] = bb
xx = np.array(xx)
xy = np.array(xy)
return xx,xy
这里调用了skimage.transform里面的resize函数,把9 X 9的数据扩展为224 X 224,主要是为了满足VGG16数据的要求。
同时按照涨跌幅的数据,把结果分为7类:
-1 - 1 :0
1 - 3 :2
3 - 5 :4
5 以上 :6
-1 - -3 :-2
-3 - -5 :-4
-5 以下 :-6
最后,又统一加上6,变成了0-12直接的值。
接下来,真实调用函数,做数据准备:
time_step = 8
x,y = extract_stock_data(price,time_step)
print(x.shape,y.shape)
# (793, 224, 224, 3) (793,)
这一步时间稍长,用了16.8s。
第六步:调用VGG16模型,预处理数据,做特征提取
from keras.applications.vgg16 import VGG16
from keras.applications.vgg16 import preprocess_input
from keras import backend as K
model_vgg = VGG16(weights='imagenet',include_top=False)
def modelProcess(x,i):
x1 = x[i]
x1 = np.expand_dims(x1,axis=0)
x1 = preprocess_input(x1)
x_vgg = model_vgg.predict(x1)
x_vgg = x_vgg.reshape(1,25088)
return x_vgg
#preprocess multiple images
features1 = np.zeros([len(x),25088])
for i in range(len(x)):
feature_i = modelProcess(x,i)
print('preprocessed:',i)
features1[i] = feature_i
和上一篇的思路一样,引入VGG16模型,对前面处理好的793张“图片”,进行特征提取。提取的结果保存在features1里。
这步最耗时。在我的电脑上用时6m37S。
第七步:配置后续MLP模型
#set up the mlp model
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(units=10,activation="relu",input_dim=25088))
model.add(Dense(units=10,activation="relu"))
model.add(Dense(units=10,activation="relu"))
model.add(Dense(units=7,activation="softmax"))
model.summary()
'''
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 10) 250890
_________________________________________________________________
dense_1 (Dense) (None, 10) 110
_________________________________________________________________
dense_2 (Dense) (None, 10) 110
_________________________________________________________________
dense_3 (Dense) (None, 7) 77
=================================================================
Total params: 251,187
Trainable params: 251,187
Non-trainable params: 0
'''
原本MLP就只配置了两层,一层Relu,10个神经元,一层softmax,7个神经元。
Sigmoid在二分类场景中比较适用,比如上个案例是做猫狗区分,正好合适。这个股市预测我们把涨跌幅分成了7类,就不再适用Sigmoid了,这里选用了softmax。
还有两层Dense是后来加上的,也可以去掉,看效果来判断吧。
第八步:MLP前的数据整理
在交给MLP之前,VGG16的数据还要进行最后一次改造:
y = y.reshape(-1,1)
y_train = y.astype(int)
x_train = features1
print(x_train.shape,x_train.shape,x.shape)
print(y_train.shape,y_train.shape)
y_train
y11 = []
for i in y_train:
y11.append(i[0])
#print(i[0])
y11 = pd.get_dummies(y11).values
y11
y_train = y11
'''
array([[1, 0, 0, ..., 0, 0, 0],
[1, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 1],
...,
[1, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 1],
[0, 0, 0, ..., 0, 0, 1]], dtype=uint8)
'''
改造,是要把y reshape成1列的整数,同时还要进行one-hot变形。写完上面的程序,后来才发现有个to_categorical函数好像是专门做这个的。
第九步:编译模型和训练模型
#configure the model
#model.compile(optimizer='adam',loss = 'binary_crossentropy',metrics=['accurary'])
#configure the model
from tensorflow import optimizers
# model.compile(loss='binary_crossentropy',
# optimizer=optimizers.RMSprop(learning_rate=1e-4),
# metrics=['acc'])
# model.compile(loss='sparse_categorical_crossentropy',
# optimizer=optimizers.Adam(),
# metrics=['accuracy'])
# model.compile(loss='categorical_crossentropy',
# optimizer=optimizers.Adam(),
# metrics=['accuracy'])
model.compile(loss='categorical_crossentropy',
optimizer=optimizers.Adam(),
metrics=['accuracy'])
model.fit(x_train,y_train,batch_size = 30,epochs=500)
'''
Epoch 1/500
27/27 [==============================] - 1s 5ms/step - loss: 1.6683 - accuracy: 0.5624
Epoch 2/500
27/27 [==============================] - 0s 5ms/step - loss: 1.4042 - accuracy: 0.5914
...
Epoch 499/500
27/27 [==============================] - 0s 5ms/step - loss: 0.3681 - accuracy: 0.8840
Epoch 500/500
27/27 [==============================] - 0s 5ms/step - loss: 0.3465 - accuracy: 0.8840
'''
这步耗时1m13s。
这里要注意,损失函数是categorical_crossentropy,是进行多分类的,一般选这个。binary_crossentropy,只能用于2分类。
循环500次,准确率就到了88%左右。还是相当。。。。。。可以的。:)
不知道循环5000次会不会好点?测了下1500次,大概能到93%。
'''
Epoch 998/1000
27/27 [==============================] - 0s 5ms/step - loss: 0.1484 - accuracy: 0.9571: 0s - loss: 0.1321 - accuracy: 0.
Epoch 999/1000
27/27 [==============================] - 0s 5ms/step - loss: 0.1869 - accuracy: 0.9357
Epoch 1000/1000
27/27 [==============================] - 0s 5ms/step - loss: 0.1675 - accuracy: 0.9508
'''
又加了1000步,接近97%:
'''
Epoch 998/1000
27/27 [==============================] - 0s 5ms/step - loss: 0.0921 - accuracy: 0.9723
Epoch 999/1000
27/27 [==============================] - 0s 5ms/step - loss: 0.0990 - accuracy: 0.9697
Epoch 1000/1000
27/27 [==============================] - 0s 5ms/step - loss: 0.1107 - accuracy: 0.9609
'''
又加了1000步,貌似基本稳定在97%:
'''
Epoch 997/1000
27/27 [==============================] - 0s 6ms/step - loss: 0.0730 - accuracy: 0.9723
Epoch 998/1000
27/27 [==============================] - 0s 5ms/step - loss: 0.0670 - accuracy: 0.9786
Epoch 999/1000
27/27 [==============================] - 0s 6ms/step - loss: 0.0698 - accuracy: 0.9760
Epoch 1000/1000
27/27 [==============================] - 0s 5ms/step - loss: 0.0713 - accuracy: 0.9773
'''
总结
- 结果应该说很不错了,我们将结果分成了7类,如果只留下涨跌2分法,有可能能更好,准确率接近100%还是有可能。
- 结果也足以说明,VGG16的适用性还是很广的
- 数据采用了9X9,也就是基于前面9天的数据来预测第10天,如果时间更久些,比如30天,有可能能更好
- 以前试过RNN模型下的收盘价预测,拟合程度和准确率虽然很高,但是因为滞后效应,所以意义不大。根本原因在于RNN只抓取了前面几天的收盘价,所参考的信息量太少,所以导致计算结果没有多大价值。而我们的思路,却是参考了前面几天的收盘价、涨跌幅、交易量等更多数据,理论上抓取的信息量更多,所以理论上的价值更大
- 利用同样的思路,也许有很多问题可以参考。以后的问题解决中可以注意