今天是tutorial4,简单看了一下,结论是不会考,但是我们热爱学习,嘿!
注意,coursework就是这个代码,学会这个代码你就会写了
tutorial4的目标:
通过过去30个交易日的股票信息判断股价在某天会涨还是跌,当然我们的数据集不是30天哈。
数据是从课程页面上下载的,是Amazons Stock price(代码里面的AMZN.csv就是数据所在的表格,这个表格包含了从2015-02-13到2020-02-12(Date)亚马逊股票的日开盘价(Open)、收盘价(Close)、最高价(High)、最低价(Low)、调整后的收盘价(Adj Close)(咱也不懂是怎么调整的,反正也没用到)以及交易量(Volume)
数据来源咱也不知道,反正是老师给的
首先是我们用到的包
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 3 12:26:10 2022
@author: Pamplemousse
"""
# In[1]:
import os
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib import pyplot as plt
sns.set()
from tensorflow.keras.preprocessing.sequence import TimeseriesGenerator
import tensorflow as tf
除了os好像都用过,那么简单说说os
os就是涉及操作系统的一个库,这里我们主要是一个路径操作(就是文件路径)
想弄清楚的建议百度
然后,人为地定义了训练集、校验集和测试集的起止日期
在tutorial3里面我们曾经使用train_test_split来做训练集测试集分离,但是那个方法分到的测试集和训练集是随机的,是没有顺序的,这里因为是时间序列,而且要用过去30天的数据来做,所以不能随机分
# In[2]:
train_start_date = "2015-02-13"
train_end_date = "2017-12-31"
val_start_date = "2018-01-01"
val_end_date = "2018-12-31"
test_start_date = "2019-01-01"
test_end_date = "2020-01-01" #不要在意这个为什么不是2020-02-12,它也没用到
然后是读取数据、生成数据集之类的操作
# In[3]:
os.chdir('C:/Users/Pamplemousse/Desktop/King/815')
#理论上来说下面这个是可以得到代码保存的路径的,但是我输出的是"C:\\Users\\Pamplemousse
#所以我加了上面这句手动设置路径,如果同学们下一句输出无误就不需要手动设置了。
os.getcwd()
#得到当前工作目录
# In[4]:
amzn = pd.read_csv('AMZN.csv', header = 0, parse_dates = True, index_col = 0)
#读取AMZN.csv文件,一般来说pd.read_csv读取的时候,如果文件和代码在同一个文件夹内,就不需要写完整路径,否则需要写完整路径,这条语句也可以写成
#amzn = pd.read_csv('C:/Users/Pamplemousse/Desktop/King's/815/AMZN.csv', header = 0, parse_dates = True, index_col = 0)
#上面这个只是我的存储路径哈,你们如果要试要用自己的路径
# In[5]
amzn['return'] = amzn['Close'] / amzn['Close'].shift() - 1
#在amzn中新增一项'return',它的值是本期收盘价除以上期收盘价-1,即本期收益率
# In[6]:
amzn['label'] = np.where(amzn['return'] > 0, 1, 0)
#在amzn中新增一项'label',用来标记收益率,若收益率为正,则label为1,反之为0
# In[7]:
amzn["std_return"] = (amzn["return"] - amzn["return"][:val_start_date].mean()) / amzn["return"][:val_start_date].std()
'''
在amzn中新增一项'std_return',其值为标准化后的amzn,说是标准化,但是它用的均值和方差都是训练
集数据的均值和方差,也就是说校验集和测试集的数据都是使用训练集的均值和方差标准化的,OK,fine,也
行,毕竟按理来说我们是不能知道校验集和测试集的均值和方差的,但训练集却是可以的。
'''
# tips:新的注释方式,你学会了吗,开始和结束的时候三个英文的单引号
amzn["std_volume"]=(amzn["Volume"] - amzn["Volume"].rolling(50).mean()) / amzn["Volume"].rolling(50).std()
#这里是对交易量进行标准化,我输出了一下amzn["std_volume"]
#认为它是利用每一天成交量的前50个数据的均值和方差进行的标准化
#所以std_volume一开始的数据为空
# In[8]:
amzn.dropna(inplace = True)
#去除amzn里面的NAN,即去掉空缺值所在行
amzn.head()
#head()输出amzn的前n行,默认值为5,如果想要输出,例如前三行,可以:
#amzn.head(n = 3)
#这里有输出,在文章中贴上
# In[9]:
val_start_iloc = amzn.index.get_loc(val_start_date, method = 'bfill')
test_start_iloc = amzn.index.get_loc(test_start_date, method = 'bfill')
#定位校验集、测试起始的位置,有:
#amzn['Date'][val_start_iloc] = val_start_date
#这是个等式,不是赋值!
# In[10]:
test_start_iloc
#输出一下测试集的定位:928
# In[11]:
#注意写coursewrok的时候这里的length要修改
train_generator = TimeseriesGenerator(amzn[["std_return","std_volume"]].values, amzn[["label"]].values, length = 30, batch_size = 64, end_index = val_start_iloc-1)
val_generator = TimeseriesGenerator(amzn[["std_reutrn","std_volume"]].values, amzn[["label"]].values, length = 30, batch_size = 64, start_index = val_start_iloc, end_index = test_start_iloc-1)
test_generator = TimeseriesGenerator(amzn[["std_return","std_volume"]].values, amzn[["label"]].values, length =30, batch_size=64, start_index = test_start_iloc)
#生成训练集、校验集、测试集
#其中std_return和std_volume作为x值,label作为y值,窗口大小为30,(因为要用前30天数据预测)
#batch_size是指每次训练从训练集中采取的数据量,具体如何操作咱不是很明白。为什么这里是64咱也不知道
我们看到第一行已经是从2015-04-27开始的,因为std_volume前49行都是空所以amzn的前49行都被删除了。数据有10列哈,因为显示不全所以自动省略了。
然后是两个函数,一般来说我们是把函数写在前面,不会写着写着在中间加函数
首先是一个通用的做模型拟合的函数
def model_fn(params):
#常见的定义模型结构的语句:
model = tf.keras.Sequential()
model.add(tf.keras.layers.LSTM(params["lstm_size"], input_shape = (30,2)))
#注意写coursework的时候这里的input_shape要修改
#其中input_shape的由来是两个自变量,30天的历史数据,所以是30*2的输入
model.add(tf.keras.layers.Dropout(params["dropout"]))
#这是一种正则化的方式
#lstm_size 和 dropout 是params当中的值
model.add(tf.keras.layers.Dense(1, activation = "sigmoid"))
#输出层
model.compile(optimizer = tf.keras.optimizers.Adam(params["learning_rate"]), loss = "binary_crossentropy", metrics = ["accuracy"])
#损失函数使用binary_crossentropy,文章里面会给大家贴个该函数的链接
#拟合优度矩阵使用准确度,准确度为 判断正确的个数/用于判断的样本大小
callbacks = [tf.keras.callbacks.EarlyStopping(monitor = "val_acc", patience = 5, restore_best_weights = True)]
#这是一个可以提前结束模型迭代的监控器
#如果在5(patience)次迭代之内 校验集的准确度(val_acc) 没有改善就不再迭代,直接得到模型
history = model.fit_generator(train_generator, validation_data = val_generator, callbacks = callbacks, epochs = 100, verbose = 0).history
#拟合模型
return (history, model)#返回模型拟合结果和训练后的模型
随后是调用上面这个函数的一个函数,该函数旨在找到最好的模型
def random_search(model_fn, search_space, n_iter, search_dir):
results = []
#创建一个空集用于保存结果,注意这里是有s的
os.mkdir(search_dir)
#创建一个目录,看看后文我们知道是"search_new"
#跑完所有代码,打开代码所在目录你就找到它了
best_model_path = os.path.join(search_dir, "best_model.h5")
results_path = os.path.join(search_dir, "results.csv")
#在该目录下加入这两个文件
for i in range(n_iter):#迭代n_iter次,看看后文我们知道是10次
params = {k:v[np.random.randint(len(v))] for k, v in search_space.items()}#随机抽取获得用于构建模型的参数
history, model = model_fn(params)#构建模型
epochs = np.argmax(history["val_acc"] + 1
#因为可能提前终止了,所有我们要定位模型迭代次数
result = {k:v[epochs-1] for k, v in history.items()}
#记录模型迭代停止时的校验数据
params["epochs"]=epochs#这里之前不小心多了个+1,感谢热心网友的指正
if i==0:#在最初的时候
best_val_acc = result["val_acc"]
#因为是第一个模型,使用就是最好的验证准确率
model.save(best_model_path)
#将第一个模型保存到该路径下
if result["val_acc"] > best_val_acc:
#如果出现一个模型的校验准确率高于当前保存的最好校验准确率
best_val_acc = result["val_acc"]
#对准确率进行替换
model.save(best_model_path)
#对模型进行替换
result ={**params, **result}
#将每一次迭代的参数和校验结果取出
results.append(result)
#保存每一次的结果
tf.keras.backend.clear_session()
print(f"iteration {i + 1} - {', '.join(f'{k}:{v:.4g}' for k, v in result.items())}")
#输出结果
best_model = tf.keras.models.load_model(best_model_path)
#取出最好的模型
results = pd.DataFrame(results)
#将n_iter次迭代的结果转换成DataFrame类型
results.to_csv(results_path)
#将结果输出到csv文件中
return (results, best_model)#返回n_iter次结果和最好的模型
这里的print需要在后面调用的时候输出,所以在后面贴结果
为执行函数定义一个search_space:
search_space = {"lstm_size": np.linspace(50, 200,3, dtype = int),
#把(50,200)三等分,得到50,125,200
"dropout": np.linspace(0, 0.4, 2),
#把(0,0.4)二等分,得到0,0.4
"learning_rate": np.linspace(0.004, 0.01, 5)
#把(0.004,0.01)五等分,得到0.004,0.0055,0.007,0.0085,0.01}
params的内容就是从这里来的
然后我们调用函数
# In[14]:
iterations = 10
#迭代次数为10,即前面说的n_iter是10
results, best_model = random_search(model_fn, search_space, iterations, "search_new")
#得到10次迭代的结果和最好的模型
注意如果不删除search_new文件夹,这个代码只能跑一次,否则会报错说search_new已存在无法创建
跑出来的结果有WARNING,但是老师也有,他还贴到给上课的内容上,所以我们也不管了
就说这个多么扯吧,判断股票会涨还是跌,假设涨跌的概率是50%,那我们猜对的概率是50%,但是这个的正确率也是五十几,就扯!
然后以校验准确率降序排序,输出准确率最高的前五个
# In[15]:
results.sort_values("val_acc", ascending = False).head()
#ascending = True则为升序排序
得到的结果是
最后对测试集进行测试
best_model.evaluate_generator(test_generator)
得到的结果为loss和accuracy。这准确率,和随机也没多大差别了
不过本期的代码没有什么错,真是让人欣慰,可喜可贺
下班!