STATWORX团队最近从GoogleFinanceAPI中选取了S&P500数据,其中包含了S&P500指数和股价信息。有了这些数据,他们希望使用深度学习模型和500种成分股的价格来预测标准普尔500指数。STATWORX团队的数据集很新颖,但只用了一个四隐藏层的全连接网络来实现预测。读者也可以下载资料试用更优秀的递归神经网络。这篇文章非常适合初学者了解如何使用TensorFlow构建基本的神经网络。它充分展示了构建TensorFlow模型所涉及的概念和模块。本文使用的数据集可以直接下载,所以有一定基础的读者也可以尝试使用更强的循环神经网络来处理这类时序数据。数据集地址:http://files.statworx.com/sp500.zip导入和预处理数据STATWORX团队从服务器上爬取股票数据,并保存为csv格式文件。该数据集包含500只股票和标准普尔500指数从2017年4月到2017年8月的n=41266分钟记录,包含范围广泛的股票和指数。#Importdatadata=pd.read_csv('data_stocks.csv')#Dimensionsofdatasetn=data.shape[0]p=data.shape[1]这个数据集已经过清洗和预处理,即丢失的股票和股指已经过LOCF处理(下一个观察重复前一个),因此数据集没有任何缺失值。我们可以使用pyplot.plot('SP500')语句绘制S&P时间序列数据。标准普尔500指数时间序列图准备训练和测试数据数据集需要分为训练数据和测试数据,训练数据包含总数据集80%的记录。数据集不需要洗牌,只需要顺序切片。训练数据可以选择2017年4月到2017年7月下旬,测试数据可以选择到2017年8月的剩余数据。#Trainingandtestdatatrain_start=0train_end=int(np.floor(0.8*n))test_start=train_end+1test_end=ndata_train=data[np.arange(train_start,train_end),:]data_test=data[np.arange(test_start,test_end),:]时间序列的交叉验证有很多种不同的方式,比如进行带或不带refitting的滚动预测,或者更详细的策略如时间序列bootstrap重采样等。后者涉及时间序列周期性分解的重复样本为了模拟与原始时间序列具有相同周期模式的样本,但这并不是简单地复制它们的值。数据归一化大多数神经网络架构都需要归一化数据,因为大多数神经元(如tanh和sigmoid)都具有在[-1,1]或[0,1]区间中定义的激活函数。目前最常用的是线性修正单元ReLU激活函数,但其??取值范围有下界,没有上界。但是,无论如何我们都应该重新缩放输入值和目标值的范围,这也有助于我们使用梯度下降算法。使用sklearn的MinMaxScaler可以轻松实现缩放值。#Scaledatafromsklearn.preprocessingimportMinMaxScalerscaler=MinMaxScaler()scaler.fit(data_train)scaler.transform(data_train)scaler.transform(data_test)#BuildXandyX_train=data_train[:,1:]y_train=data_train[:,0]X_test=data_test[:,1:]y_test=data_test[:,0]pycharm注意一定要慎重判断什么时候对数据的哪一部分进行缩放。一个常见的错误是在拆分测试和训练数据集之前缩放整个数据集。因为我们在进行缩放的时候,会涉及到计算统计量,比如一个变量的最小值和最小值。但在现实世界中,我们没有未来的观察结果,因此统计计算必须扩展到训练数据并应用于测试数据。否则我们使用未来的时间序列预测信息,这通常会使预测指标向正方向倾斜。TensorFlow简介TensorFlow是一个优秀的框架,是目前深度学习和神经网络应用最广泛的框架。它基于C++底层后端,但通常通过Python进行控制。TensorFlow使用强大的静态图来表示我们需要设计的算法和操作。这种方法允许用户将操作指定为图中的节点,并以张量的形式传输数据,以实现高效的算法设计。由于神经网络实际上是数据和数学运算的计算图,因此TensorFlow对神经网络和深度学习的支持很好。一般来说,TensorFlow是一个使用数据流图进行数值计算的开源软件库。其中Tensor表示传递的数据是张量(多维数组),Flow表示使用计算图进行计算。数据流图使用“节点”和“边”的有向图来描述数学运算。“节点”通常用于表示要应用的数学运算,但也可以表示数据输入的开始和输出的结束,或者读/写持久变量的结束。边表示节点之间的输入/输出关系。这些数据边缘可以传输具有动态可调维度的多维数据阵列,称为张量。执行加法的简单计算图在上图中,两个零维张量(标量)将执行加法任务,这两个张量存储在两个变量a和b中。这两个值在图中流过,到达正方形节点时相加,相加的结果保存在变量c中。其实a、b、c可以看成是占位符,在a和b中输入的任何值都会加到c中。这就是TensorFlow的基本原理。用户可以通过占位符和变量来定义模型的抽象表示,然后用实际数据填充占位符以生成实际操作。下面的代码实现了上面的简单计算图:#ImportTensorFlowimporttensorflowastf#Defineaandbasplaceholdersa=tf.placeholder(dtype=tf.int8)b=tf.placeholder(dtype=tf.int8)#Definetheadditionc=tf.add(a,b)#Initializethegraphgraph=tf.Session()#Runthegraphgraph.run(c,feed_dict{a:5,b:4})如上,导入TensorFlow库后,使用tf.placeholder()定义两个占位符,预存张量a和b.定义好操作后,可以执行操作图得到结果。占位符如前所述,神经网络最初是从占位符衍生而来的。所以现在我们首先定义两个占位符来拟合模型,X包含神经网络的输入(所有S&P500股票在时间T=t的价格),Y包含神经网络的输出(S&P500在时间T=t+1的指数值)。因此,输入数据占位符的维度可以定义为[None,n_stocks],输出占位符的维度为[None],分别代表二维张量和一维张量。了解输入和输出张量的维度对于构建整体神经网络非常重要。#PlaceholderX=tf.placeholder(dtype=tf.float32,shape=[None,n_stocks])Y=tf.placeholder(dtype=tf.float32,shape=[None])上面代码中的None表示我们不知道yet每批传递给神经网络的数量,因此使用None以保持灵活性。稍后我们将定义batch_size来控制每次训练使用的批量大小。变量除了占位符,变量是TensorFlow的另一个重要元素,用来表示数据和操作。虽然占位符通常用于存储计算图中的输入和输出数据,但变量是计算图中非常灵活的容器,可以在执行过程中修改和传递。神经网络的权值和偏置项一般由变量定义,以便在训练过程中很容易调整。变量需要初始化,后面会详细说明。该模型由四个隐藏层组成,第一层包含1024个神经元,接下来的三层依次减少2倍,即512、256和128个神经元。后一层神经元的连续减少压缩了前一层提取的特征。当然,我们也可以使用其他神经网络架构和神经元配置来更好地处理数据。比如卷积神经网络架构适用于处理图像数据,循环神经网络适用于处理时间序列数据,但本文只是对初学者的简单介绍。时间序列数据是使用全连接网络处理的,所以本文不会讨论那些复杂的架构。#Modelarchitectureparametersn_stocks=500n_neurons_1=1024n_neurons_2=512n_neurons_3=256n_neurons_4=128n_target=1#Layer1:VariablesforhiddenweightsandbiasesW_hidden_??1=tf.Variable(weight_initializer([n_stocks,n_neurons_1]))bias_hidden_??1=tf.Variable(bias_initializer([n_neurons_1]))#Layer2:VariablesforhiddenweightsandbiasesW_hidden_??2=tf.Variable(weight_initializer([n_neurons_1,n_neurons_2]))bias_hidden_??2=tf.Variable(bias_initializer([n_neurons_2]))#Layer3:VariablesforhiddenweightsandbiasesW_hidden_??3=tf.Variable(weight_initializer([n_neurons_2,n_neurons_2])Varisons_3bias_initializer([N_NEURONS_3]))#layer4:variablesforHiddenDeweightsandBiasesw_hiddend_4=tf.variable(stoge_initializer([n_neurons_3,n_neurons_3,n_neurons_4]))n_neurons_4,n_target]))bias_out=tf.Variable(bias_initializer([n_target]))解释输入层、隐藏层和输出层之间变化量的维度转换对于理解整个网络非常重要。作为多层感知器的经验法则,后一层的第一个维度对应于前一层权重变量的第二个维度。这听起来可能很复杂,但它实际上只是将每一层的输出作为输入传递给下一层。偏置项的维度等于当前层权重的第二个维度,也等于该层神经元的数量。设计神经网络的架构在定义了神经网络所需的权重矩阵和偏置项向量之后,我们需要指定神经网络的拓扑或网络架构。所以占位符(数据)和变量(权重和偏差项)需要组合成一个连续的矩阵乘法系统。此外,网络隐藏层的每个神经元还需要有一个激活函数来进行非线性变换。激活函数是网络架构中非常重要的一部分,因为它们将非线性引入系统。目前有很多激活函数,其中最常见的是线性校正单元ReLU激活函数,在本模型中也会用到。#Hiddenlayerhidden_??1=tf.nn.relu(tf.add(tf.matmul(X,W_hidden_??1),bias_hidden_??1))hidden_??2=tf.nn.relu(tf.add(tf.matmul(hidden_??1,W_hidden_??2),bias_hidden_??2))hidden_??3=tf.nn.relu(tf.add(tf.matmul(hidden_??2,W_hidden_??3),bias_hidden_??3))hidden_??4=tf.nn.relu(tf.add(tf.matmul(hidden_??3,W_hidden_??4),bias_hidden_??4))#Outputlayer(mustbetransposed)out=tf.transpose(tf.add(tf.matmul(hidden_??4,W_out),bias_out))下图将展示本文构建的神经网络架构。该模型主要由三个构建块组成,即输入层、隐藏层和输出层。这种架构称为前馈网络或全连接网络。前馈意味着输入的批处理数据只会从左向右流动。循环神经网络等其他架构也允许数据向后流动。前馈网络的核心架构损失函数网络的损失函数主要用于生成网络预测与实际观察到的训练目标之间的偏差值。对于回归问题,最常用的是均方误差(MSE)函数。MSE计算预测值和目标值之间的均方误差。#Costfunctionmse=tf.reduce_mean(tf.squared_difference(out,Y))但是MSE的特性在常见的优化问题中是非常有优势的。优化器优化器在训练期间处理必要的计算以适应网络的权重和偏差变量。这些计算调用梯度计算,指示权重和偏差在训练期间需要改变的方向,以最小化网络的成本函数。开发稳定快速的优化器一直是神经网络和深度学习领域的重要研究课题。#Optimizeropt=tf.train.AdamOptimizer().minimize(mse)上面用到了Adam优化器,是深度学习中默认的优化器。Adam代表AdaptiveMomentEstimation,可以被认为是两个优化器AdaGrad和RMSProp的组合。初始化器用于在训练之前初始化网络变量。因为神经网络是使用数值优化技术训练的,所以优化问题的起点是找到一个好的解决方案的重点。TensorFlow中有不同的初始化器,每个初始化器都有不同的初始化方法。在本文中,我使用了tf.variance_scaling_initializer(),这是一种默认的初始化策略。#Initializerssigma=weight_initializer=tf.variance_scaling_initializer(mode="fan_avg",distribution="uniform",scale=sigma)bias_initializer=tf.zeros_initializer()注意在TensorFlow的计算图中可以为不同的变量定义多个初始化函数。然而,在大多数情况下,一个统一的初始化函数就足够了。拟合神经网络一旦为网络定义了占位符、变量、初始化器、成本函数和优化器,就可以开始训练模型,通常使用小批量训练方法。在mini-batch训练过程中,从训练数据中随机抽取若干n=batch_size的数据样本送入网络。训练数据集将按顺序以n/batch_size的批次馈入网络。这时占位符X和Y就派上用场了,它们分别保存输入数据和目标数据,并在网络中分别表示为输入和目标。一批X通过网络向前流动,直到到达输出层。在输出层,TensorFlow会将当前批次的模型预测与实际观察到的目标Y进行比较。TensorFlow然后进行优化,使用所选的学习方案更新网络参数。更新权重和偏差后,对下一批进行采样并重复该过程。这个过程将一直持续到所有批次都被馈送到网络,即一个epoch完成。当训练达到epoch的最大值或其他用户定义的停止标准时,网络的训练将停止。#Runinitializernet.run(tf.global_variables_initializer())#Setupinteractiveplotplt.ion()fig=plt.figure()ax1=fig.add_subplot(111)line1,=ax1.plot(y_test)line2,=ax1.plot(y_test*0.5)plt.show()#Numberofepochsandbatchsizeepochs=10batch_size=256foreinrange(epochs):#Shuffletrainingdatashuffle_indices=np.random.permutation(np.arange(len(y_train)))X_train=X_train[shuffle_indices]le_train=y_bindices[fortraining(0,len(y_train)//batch_size):start=i*batch_sizebatch_x=X_train[start:start+batch_size]batch_y=y_train[start:start+batch_size]#Runoptimizerwithbatchnet.run(opt,feed_dict={X:batch_x,Y:batch_y})#Showprogressifnp.mod(i,5)==0:#Predictionpred=net.run(out,feed_dict={X:X_test})line2.set_ydata(pred)plt.title('Epoch'+str(e)+',Batch'+str(i))file_name='img/epoch_'+str(e)+'_batch_'+str(i)+'.jpg'plt.savefig(file_name)plt.pause(0.01)期间在训练过程中,我们评估了网络在测试集(网络未学习的数据)上的预测能力,每5批训练,以及sh欠的结果。此外,这些图像将被导出到磁盘并组合成训练过程的视频动画。该模型可以快速学习测试数据中时间序列的位置和形状,并在经过几个时期的训练后产生准确的预测。奇妙!可以看出,网络很快适应了时间序列的基本形状,并继续学习数据的更精细模式。这是由于Adam学习方案,它降低了模型训练期间的学习率以避免丢失最小值。10个epoch之后,我们完美地拟合了测试数据!最佳测试MSE等于0.00078,该值非常低,因为目标已缩放。测试集预测的平均百分比误差为5.31%,这是一个非常好的结果。预测和实际标准普尔价格的散点图(按比例)。请注意,有许多方法可以进一步优化此结果:层和神经元设计、不同初始化和激活方案的选择、引入神经元的dropout层、earlystopping方法的应用等。此外,其他不同类型的深度学习模型,比如递归神经网络,或许能在这个任务上取得更好的效果。不过,这不在我们的讨论范围之内。结论与展望TensorFlow的发布是深度学习研究的里程碑事件。其高度的灵活性和强大的性能使研究人员能够开发各种复杂的神经网络架构和其他机器学习算法。但是,与使用Keras或MxNet等高级API相比,灵活性的代价是建模时间更长。尽管如此,我相信TensorFlow将继续发展并成为神经网络和深度学习开发的研究和实际应用的事实标准。我们的许多客户已经在使用TensorFlow,或者正在开发应用TensorFlow模型的项目。我们的STATWORX数据科学顾问(https://www.statworx.com/de/data-science/)基本上使用TensorFlow研究课程来开发深度学习和神经网络。Google对TensorFlow的未来计划是什么?TensorFlow缺乏用于在TensorFlow后端设计和开发神经网络架构的简洁GUI,至少在我看来是这样。也许这就是谷歌未来的目标之一
