基于VGG16的股市预测

AI人工智能之乱入

Posted by 就是我啦 on June 30, 2022

基于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
'''

总结

  1. 结果应该说很不错了,我们将结果分成了7类,如果只留下涨跌2分法,有可能能更好,准确率接近100%还是有可能。
  2. 结果也足以说明,VGG16的适用性还是很广的
  3. 数据采用了9X9,也就是基于前面9天的数据来预测第10天,如果时间更久些,比如30天,有可能能更好
  4. 以前试过RNN模型下的收盘价预测,拟合程度和准确率虽然很高,但是因为滞后效应,所以意义不大。根本原因在于RNN只抓取了前面几天的收盘价,所参考的信息量太少,所以导致计算结果没有多大价值。而我们的思路,却是参考了前面几天的收盘价、涨跌幅、交易量等更多数据,理论上抓取的信息量更多,所以理论上的价值更大
  5. 利用同样的思路,也许有很多问题可以参考。以后的问题解决中可以注意