多层感知机(MLP)的训练、应用与评估
1. 训练步骤概述
多层感知机(MLP)的训练过程主要包含五个关键步骤,以下为您详细介绍:
1.1 步骤4 - 突触/权重调整
在这一步骤中,连接权重 $\Delta v$ 和 $\Delta w$ 的调整是通过计算误差的导数之和,并结合学习因子来完成的。之后,权重的梯度会用于计算源层输出的误差。
1.1.1 梯度下降的动量因子
梯度下降是更新权重最简单的算法,但它在收敛到全局最小值的速度上,比共轭梯度法或拟牛顿法要慢。为了加速梯度下降的收敛过程,可以采用动量因子和自适应学习系数等方法。
梯度下降计算神经网络权重的公式如下:
[
w_{ij}^{(t + 1)} = w_{ij}^{(t)} - \eta \frac{\partial \epsilon}{\partial w_{ij}^{(t)}}
]
带有动量系数 $\alpha$ 的梯度下降计算神经网络权重的公式如下:
[
w_{ij}^{(t + 1)} = w_{ij}^{(t)} - \eta \frac{\partial \epsilon}{\partial w_{ij}^{(t)}} + \alpha \Delta w_{ij}^{(t)}
]
当动量因子 $\alpha$ 设为 0 时,即为基本的梯度下降算法。
1.1.2 代码实现
以下是
MLPConnection
类的
connectionUpdate
方法,用于调整每个连接的突触:
def connectionUpdate: Unit =
Range(1, dst.len).foreach(i => {
val delta = dst.delta(i) //1
Range(0, src.len).foreach(j => {
val _output = src.output(j) //2
val oldSynapse = synapses(i)(j)
val grad = config.eta*delta*_output //3
val deltaWeight = grad + config.alpha* oldSynapse._2 //4
synapses(i)(j) = (oldSynapse._1 + deltaWeight, grad) //5
})
})
该方法的具体步骤如下:
1. 计算每个目标神经元的误差(第 1 行)。
2. 获取每个源神经元的输出(第 2 行),用于计算梯度
grad
(第 3 行)。
3. 根据数学公式,为动量调整权重(第 4 行)。
4. 更新源层和目标层的突触(第 5 行)。
1.2 可调整的学习率
通过使学习率可调整,可以进一步优化每个新周期中连接新权重的计算。
1.3 步骤5 - 收敛标准
收敛标准是将平方误差之和与预定的阈值
eps
进行比较。通常,会将平方误差之和除以观测值的数量进行归一化处理。
1.4 配置
MLPConfig
类用于定义多层感知机的配置,包括网络配置(隐藏层)、学习参数、训练参数和激活函数:
Class MLPConfig(val alpha: Double, val eta: Double, val hidLayers:
Array[Int], val numEpochs: Int,val eps: Double,val activation:
Double=>Double) extends Config
配置参数的含义如下:
-
alpha
:动量因子。
-
eta
:学习率(固定或自适应)。
-
hidLayers
:隐藏层大小的数组。
-
numEpochs
:训练神经网络允许的最大周期数。
-
eps
:作为神经网络训练退出条件的收敛标准,即误差 <
eps
。
-
activation
:用于非线性回归的隐藏层激活函数,默认是 sigmoid 函数。
1.5 整合所有步骤
MLP 类定义的算法负责管理训练周期的五个步骤,其代码如下:
class MLP[T <% Double](config: MLPConfig, xt: XTSeries[Array[T]],
labels: DblMatrix)(implicit val mlpObjective: MLP.MLPObjective)
extends PipeOperator[Array[T], DblVector] {
val model: Option[MLPModel]
def |> : PartialFunction[Array[T], DblVector]
}
MLP 算法的参数包括:
-
config
:算法的配置。
-
xt
:用于训练模型的特征时间序列。
-
labels
:用于训练的标记输出值。
-
mlpObjective
:算法的隐式目标(问题类型)。
训练周期的五个步骤可以用以下流程图表示:
graph TD
A[前向传播] --> B[计算平方误差之和]
B --> C{平方误差之和 < eps?}
C -- 是 --> D[退出训练]
C -- 否 --> E[反向传播]
E --> F[权重调整]
F --> A
1.6 训练周期的实现
MLPModel
类的
trainEpoch
方法实现了训练周期的五个步骤:
def trainEpoch(x: DblVector, y: DblVector): Double = {
inLayer.set(x)
connections.foreach( _.connectionForwardPropagation) //1
val _sse = sse(y) //2
val bckIterator = connections.reverseIterator
bckIterator.foreach( _.connectionBackpropagation) //3
connections.foreach( _.connectionUpdate) //4
_sse
}
该方法依次完成了输入的前向传播(第 1 行)、平方误差之和的计算(第 2 行)、误差的反向传播(第 3 行)以及每个突触的权重和权重梯度的重新计算(第 4 行)。
2. 训练策略与分类
2.1 在线训练与批量训练
在训练时间序列数据时,有两种创建 MLP 模型的策略:
-
批量训练
:将整个时间序列作为单个输入一次性处理,在每个周期使用时间序列输出的平方误差之和来更新权重,当平方误差之和满足收敛标准时,训练结束。
-
在线训练
:逐个将观测值输入到神经网络中,处理完整个时间序列后,计算所有观测值的平方误差之和。如果未满足退出条件,则重新处理观测值。
在线训练通常比批量训练更快,因为它需要每个数据点都满足收敛标准,可能会减少训练周期的数量。动量因子或自适应学习方案等技术可以进一步提高在线训练的性能。
2.2 正则化
为了找到适合分类或回归问题的网络架构,可以采用以下两种方法:
-
破坏性调整
:从一个大网络开始,移除对平方误差之和没有影响的节点、突触和隐藏层。
-
建设性调整
:从一个小网络开始,逐步添加能够减少输出误差的节点、突触和隐藏层。
破坏性调整策略通常通过正则化来实现,即通过将突触的权重置为零来移除突触。正则化可以有效解决神经网络的过拟合问题,正则化因子越大,某些权重越有可能被降为零,从而减小网络规模。
2.3 模型实例化
在多层感知机实例化过程中,通过迭代训练周期,直到平方误差之和小于阈值
eps
来创建模型:
var converged = false
val model: Option[MLPModel] = {
val _model = new MLPModel(config, xt(0).size, labels(0).size)(mlpObjective) //1
val errScale = 1.0/(labels(0).size*xt.size) //4
converged = Range(0, config.numEpochs).find( _ => {
xt.toArray.zip(labels)
.foldLeft(0.0)((s, xtlbl) =>
s + _model.trainEpoch(xtlbl._1, xtlbl._2) //2
)*errScale < config.eps //3
}) != None
_model
}
模型首先进行初始化(第 1 行),然后通过
MLPModel.trainEpoch
方法执行训练周期的前四个阶段(第 2 行),该方法返回每个观测值的平方误差之和。接着,将这些误差之和累加,并与收敛标准
eps
进行比较(第 3 行)。为了使误差之和具有可比性,会对其进行归一化处理(第 4 行)。代码中使用 Scala 的
find
方法,在达到最大训练周期数
config.numEpochs
之前,若满足收敛条件则退出迭代。
2.4 预测
MLPModel
类的
getOutput
方法用于进行预测,它接受一个新的观测值(特征向量)作为参数,并通过前向传播算法返回输出:
def getOutput(x: DblVector): DblVector = {
inLayer.set(x)
connections.foreach( _.connectionForwardPropagation)
outLayer.output
}
分类方法通过数据转换
|>
实现,若模型训练成功,它将返回归一化后的预测值作为概率;否则返回
None
:
def |> : PartialFunction[Array[T], DblVector] = {
case x: Array[T] if(model!=None && x.size == dimension(xt)) =>
model.get.getOutput(x)
}
3. 评估
3.1 学习率的影响
为了评估学习率 $\eta$ 对训练周期收敛的影响,我们使用合成生成的观测值 $x$ 和标记输出 $y$ 进行测试。具体代码如下:
val noise = () => NOISE_RATIO*Random.nextDouble
val f1 = (x: Double) => x*(1.0 + noise())
val f2 = (x: Double) => x*x*(1.0 + noise())
def vec1(x: Double): DblVector = Array[Double](f1(x), noise(), f2(x), noise())
def vec2(x: Double): DblVector = Array[Double](noise(), noise())
val x = XTSeries[DblVector](Array.tabulate(TEST_SIZE)(vec1(_,未完待续...
### 3.1 学习率的影响(续)
```scala
val noise = () => NOISE_RATIO*Random.nextDouble
val f1 = (x: Double) => x*(1.0 + noise())
val f2 = (x: Double) => x*x*(1.0 + noise())
def vec1(x: Double): DblVector = Array[Double](f1(x), noise(), f2(x), noise())
def vec2(x: Double): DblVector = Array[Double](noise(), noise())
val x = XTSeries[DblVector](Array.tabulate(TEST_SIZE)(vec1(_)))
val y = XTSeries[DblVector](Array.tabulate(TEST_SIZE)(vec2(_)))
将 $x$ 和 $y$ 的值归一化到 $[0, 1]$ 范围。测试使用大小为
TEST_SIZE
的数据点样本,最多进行 250 个周期的训练,设置一个包含五个神经元的隐藏层,不进行 softmax 变换,并使用以下 MLP 参数:
val NUM_EPOCHS = 250; val EPS = 1.0e-4
val HIDDENLAYER = Array[Int](5)
val ALPHA = 0.9; val TEST_SIZE = 40
val features = XTSeries.normalize(x).get
val labels = XTSeries.normalize(y).get.toArray
val config = MLPConfig(ALPHA, _eta, SIZE_HIDDEN_LAYER, NUM_EPOCHS, EPS)
implicit val mlpObjective = new MLP.MLPBinClassifier
val mlp = MLP[Double](config, features, labels)
测试使用不同的学习率 $\eta$ 进行,为了清晰展示,图表显示前 22 个周期的平方误差之和。结果表明,较大的学习率能使 MLP 模型训练更快收敛。但需要注意的是,过大的学习率可能会使训练过程陷入平方误差的局部最小值,导致生成的权重精度降低。
3.2 动量因子的影响
为了量化动量因子 $\alpha$ 对训练过程收敛到最优模型(突触权重)的影响,我们绘制了前五个周期整个时间序列的平方误差之和的图表。图表显示,随着动量因子的增加,平方误差之和的下降速度加快。这意味着动量因子对梯度下降的收敛有积极但有限的影响。
3.3 测试用例
神经网络在金融领域有广泛应用,如抵押贷款风险管理、商品定价套期保值策略以及金融市场预测建模等。本次测试的目标是理解某些货币汇率、黄金现货价格和标准普尔 500 指数之间的相关因素。
我们使用以下交易型开放式指数基金(ETF)作为货币汇率的代理:
| 符号 | 含义 |
| ---- | ---- |
| FXA | 澳元兑美元汇率 |
| FXB | 英镑兑美元汇率 |
| FXE | 欧元兑美元汇率 |
| FXC | 加元兑美元汇率 |
| FXF | 瑞士法郎兑美元汇率 |
| FXY | 日元兑美元汇率 |
| CYB | 人民币兑美元汇率 |
| SPY | 标准普尔 500 指数 |
| GLD | 黄金美元价格 |
实际要解决的问题是提取一个或多个回归模型,将一个 ETF $y$ 与一组其他 ETF ${x_i}$ 联系起来,即 $y = f(x_i)$。例如,探究日元汇率(FXY)与黄金现货价格(GLD)、欧元兑美元汇率(FXE)和澳元兑美元汇率(FXA)等的组合之间是否存在关系。
3.4 实现步骤
3.4.1 定义配置参数
val path = "resources/data/chap9/"
val ALPHA = 0.5; val ETA = 0.03
val NUM_EPOCHS = 250; val EPS = 1.0e-6
var hidLayers = Array[Int](7, 7)
var config = MLPConfig(ALPHA, ETA, hidLayers, NUM_EPOCHS, EPS)
除了学习参数,网络使用两种配置进行初始化:
- 一个包含四个节点的隐藏层。
- 两个各包含四个神经元的隐藏层。
3.4.2 创建搜索空间
val symbols = Array[String]("FXE", "FXA", "SPY", "GLD", "FXB", "FXF", "FXC", "FXY", "CYB")
val prices = symbols.map(s =>DataSource(s"$path$s.csv",true))
.map( _ |> GoogleFinancials.close)
.map( _.toArray)
从 Google 财经表格中提取所有 ETF 在三年期间的收盘价。
3.4.3 提取目标和特征
val study = Array[String]("FXE", "FXF", "FXB", "CYB")
val obs = study.map(s =>index.get(s).get).map( prices( _ ))
val features = obs.drop(1).transpose
val target = Array[DblVector](obs(0)).transpose
以
study
为例,第一个元素
FXE
作为标记输出,其余三个元素作为观测特征。通过索引构建观测集,将第一个观测作为标签数据,其余作为训练特征。由于观测数据以时间序列数组形式加载,需要对时间特征序列进行转置操作。
3.4.4 构建模型
val THRESHOLD = 0.08
implicit val mlpObjective = new MLP.MLPBinClassifier
val mlp = MLP[Double](config, features, target)
mlp.accuracy(THRESHOLD)
将目标类型
mlpObjective
隐式定义为
MLP.MLPBinClassifier
。计算 MLP 生成的预测输出与目标值之间的平方差之和的平方根,并与预定义的阈值进行比较。准确率计算为预测值在误差范围内与目标值匹配的数据点的百分比。
val nCorrects = xt.toArray.zip(labels).foldLeft(0)((s, xtl) => {
val output = model.get.getOutput(xtl._1)
val _sse = xtl._2.zip(output.drop(1))
.foldLeft(0.0)((err,tp) => {
val diff= tp._1 - tp._2
err + diff*diff
})
val error = Math.sqrt(_sse)/(output.size-1)
if(error < threshold) s + 1
else s
})
nCorrects.toDouble/xt.size
3.5 模型评估
我们评估了六种不同的模型,以确定哪些模型能提供最可靠的相关性。考虑了两种网络架构:
- 两个各包含四个节点的隐藏层。
- 三个分别包含八个、五个和六个节点的隐藏层。
两种架构下最准确的回归模型如下:
- $FXE = f (FXA, SPY, GLD, FXB, FXF, FXD, FXY, CYB)$
- $FXE = g (FXC, GLD, FXA, FXY, FXB)$
- $FXE = h (FXF, FXB, CYB)$
然而,使用日元汇率(FXY)和澳元汇率(FXA)来预测加元兑美元汇率(FXC)的效果在两种配置下都较差。
综上所述,通过对多层感知机的训练步骤、训练策略、模型评估等方面的深入研究和实验,我们可以更好地应用神经网络解决实际问题,如金融市场中的货币汇率预测等。在实际应用中,需要根据具体问题选择合适的网络架构、学习参数和训练策略,以获得更准确的预测结果。
超级会员免费看
1万+

被折叠的 条评论
为什么被折叠?



