12.SparkMLlib神经网络算法
12.1 人工神经网络算法
(1)人工神经网络
人工神经网络(Artificial Neural Networks——ANNs)提供了一种普遍而且实用的方法,来从样本中学习值为实数、离散或向量的函数。用反向传播(BackPropagation)这样的算法使用梯度下降来调节网络参数以最佳拟合由输入-输出对组成的训练集合。ANN是基于神经元模型构建的,是一种由许多神经元组成的信息处理网络,具有并行分布结构。每一个神经元具有单一输出,且与其他神经元相连接,有多种输出连接方式,每种方式对应一个连接权重系数。
人工神经网络的研究在一定程度上受到了生物学的启发,因为生物的学习系统是由相互连接的神经元(neuron)组成的异常复杂的网络。而人工神经网络与此大体相似,它是由一系列简单单元相互密集连接构成,其中每一个单元有一定数量的实值输入(可能是其他单元的输出),并产生单一的实数值输出(可能成为其他很多单元的输入)。下图就是一个最简单的神经元的示意图,图右是由神经元前后连接构成的一个三层前馈神经网络,分为输入层,中间层也即隐藏层,输出层。从图中可以看到,输入为
x
1
x_1
x1、
x
2
x_2
x2,输入权重分别为
w
1
w_1
w1、
w
2
w_2
w2,代表输入对输出的影响程度。
y
y
y为神经元的处理单元,加入激活函数,完成从输入到输出的映射。
常用的激活函数有 s i g m o i d sigmoid sigmoid函数和双曲正切函数即 t a n h tanh tanh函数,若采用 s i g m o i d sigmoid sigmoid函数则神经元的输入输出映射关系是一个逻辑回归。注意,这里是有一次见到这个神奇的函数了。
(2)误差逆传播算法BP
上面介绍了人工神经网络的基本原理和示意图,我们可以知道如果设定多个输入,增加层数和中间的隐藏层数,就可以构建一个复杂的从输入到输出的映射拓扑结构。其中最关键的部分就是层与层之间的权重系数。训练数据的输入,不断地去迭代就是去确定这些权重系数的方法,也就是神经网络的自我学习。神经网络从数据中进行学习,由数据自动决定网络参数的值。这里给出了一个寻找最优权重参数的指标——损失函数,常用均方误差来表示。神经网络对数据进行学习的目的就是不断调整参数,降低这个损失函数:
E = 1 2 ∑ k ( y k − t k ) 2 E = \frac { 1 } { 2 } \sum _ { k } ( y _ { k } - t _ { k } ) ^ { 2 } E=21k∑(yk−tk)2
y
k
y_k
yk是预测值,
t
k
t_k
tk是真实值,
k
k
k为输出值个数
均方误差会计算神经网络输出与数据真实值之差的平方和,每一次训练数据的输入到输出,都可以计算出一个均方误差,神经网络学习的目标就是使得这个均方误差的值越来越小,达到一定范围,即完成训练。神经网络中使用梯度下降的思想来寻找损失函数最小值进行最优化。
误差逆传播算法是最常用的ANN 学习技术,它是从神经网络正向传播的反向来计算输出关于输入和权重的梯度值,从而找到如何调整权重来使得误差函数减小。误差逆传播法可高效计算权重参数的梯度,它基于链式法则,将局部导数进行反向传播, 每经过一个结点,把次节点的局部导数乘以上游传过来的值,再传递给前面的节点,最后传递到整个网络的输入端。将逆传播法应用到神经网络中最小损失函数的求解中,即可得到损失函数对输入层每个输入的敏感程度,同时也可以得到网络中权重参数对损失函数的影响程度。下图即为用计算图来表示一个反向传播计算导数的过程。
而我们这里说的误差逆传播,就是指的损失函数关于网络权重的逆传播。
(3)基于误差逆传播神经网络的实现
基于误差逆传播算法我们就可以完成一个神经网络的实现,如图是一个误差逆传播神经网络。其训练过程如下:
准备:在神经网络中设置好层数,输入神经元个数和输出神经元个数,并设置好合适的权重和偏置;
第一步:从训练数据中随机选择一部分数据;
第二步:使用误差逆传播法计算损失函数关于各个权重参数的梯度;
第三步:将权重参数沿梯度方向进行微小的更新;
第四步:对上述第一、二、三步进行重复
当损失函数减小到一定程度时停止迭代
输出:参数确定的逆差误传播人工神经网络
12.2 算法源码分析
MLlib对神经网络部分的支持较为充分,源码部分分析如下:
(1)class NeuralNet:ANN神经网络类,需要设置的参数:
- size——数据格式:Array[Int],神经网络每层的节点个数;
- layer——神经网络层数;
- activation_function:隐含层函数,包括sigmoid和tanh;
- learnRate——学习率;
- momentum——权重更新参数;
- inputZeroMaskedFraction——训练数据中加入噪声;
- scaling_learningRate——学习缩放因子;
- dropoutFraction——隐含层节点加入噪声;
- testing——内部变量,用于测试;
- output_fraction——输出函数支持sigmoid,softmax,linear;
- initW——初始化权重
(2)Nntrain:训练模型的方法,基于梯度下降法进行权重优化计算:
- InitialWeight:初始化权重;
- NNtrain方法调用的方法:
- NNff:前向传播算法实现;
- NNbp:后向传播算法实现;
- Nnapplygrads:权重更新;
(3)NeuralNetModel:神经网络模型类:
- Predict:预测计算;
- Loss:计算输出平均误差;
包含参数:权重、配置参数。
12.3 应用实战
12.3.1 数据说明
测试数据使用的是优化算法中的经典测试函数:
1.Sphere Model
函数表达式如下:
搜索范围:-100<=x_i<=100
全局最优值:min(f_1)=f_1(0,…,0)=0
此函数是非线性对称单峰函数主要测试算法的寻优精度。
2.Generalized Rosenbrock
函数表达式如下:
搜索范围:-30<=x_i<=30
全局最优值:min(f_2)=f_2(0,…,0)=0
此函数是难于进行极小优化的病态二次函数,在其函数图像中,全局最优值与可达局部最优值之间有一道山谷,曲面山谷的点的最大下降方向与函数最小值的最好方向垂直。所以该函数在搜索过程中的优化方向难以确定,会使算法难以辩解正确的搜索方向。
3.Generalized Rastrigin
函数表达式如下:
搜索范围:-5,12<=x_i<=5.12
全局最优值:min(f_3)=f_3(0,…,0)=0
此函数基于Sphere函数,通过余弦函数产生大量局部最小值,通过此函数的图像可以发现这是一个典型的具有大量局部最优点的复杂多峰函数,优化算法很容易陷入局部最优值从而得不到全局最优解。
12.3.2 测试函数代码
import java.util.Random
import breeze.linalg.{
Matrix => BM,
CSCMatrix => BSM,
DenseMatrix => BDM,
Vector => BV,
DenseVector => BDV,
SparseVector => BSV,
axpy => brzAxpy,
svd => brzSvd
}
import breeze.numerics.{
exp => Bexp,
cos => Bcos,
tanh => Btanh
}
import scala.math.Pi
object RandSampleData extends Serializable {
// Rosenbrock:
//∑(100*(x(i+1)-x(i) 2) 2 + (x(i)-1) 2)
// Rastrigin:
//∑(x(i) 2 -10*cos(2*3.14*x(i))+10)
// Sphere :
//∑(x(i) 2)
/**
* 测试函数: Rosenbrock, Rastrigin
* 随机生成n2维数据,并根据测试函数计算Y
* n1 行,n2 列,b1 上限,b2 下限,function 计算函数
*/
def RandM(
n1: Int,
n2: Int,
b1: Double,
b2: Double,
function: String): BDM[Double] = {
// val n1 = 2
// val n2 = 3
// val b1 = -30
// val b2 = 30
val bdm1 = BDM.rand(n1, n2) * (b2 - b1).toDouble + b1.toDouble
val bdm_y = function match {
case "rosenbrock" =>
val xi0 = bdm1(::, 0 to (bdm1.cols - 2))
val xi1 = bdm1(::, 1 to (bdm1.cols - 1))
val xi2 = (xi0 :* xi0)
val m1 = ((xi1 - xi2) :* (xi1 - xi2)) * 100.0 + ((xi0 - 1.0) :* (xi0 - 1.0))
val m2 = m1 * BDM.ones[Double](m1.cols, 1)
m2
case "rastrigin" =>
val xi0 = bdm1
val xi2 = (xi0 :* xi0)
val sicos = Bcos(xi0 * 2.0 * Pi) * 10.0
val m1 = xi2 - sicos + 10.0
val m2 = m1 * BDM.ones[Double](m1.cols, 1)
m2
case "sphere" =>
val xi0 = bdm1
val xi2 = (xi0 :* xi0)
val m1 = xi2
val m2 = m1 * BDM.ones[Double](m1.cols, 1)
m2
}
val randm = BDM.horzcat(bdm_y, bdm1)
randm
}
}
12.3.3 代码详解
基于上述的优化算法测试函数随机生成样本,使用神经网络寻找最优值,观察输出结果:
//导入所需的机器学习包和Spark基础的支持库
import org.apache.log4j.{ Level, Logger }
import org.apache.spark.{ SparkConf, SparkContext }
import org.apache.spark.storage.StorageLevel
import org.apache.spark.mllib.util.MLUtils
import org.apache.spark.mllib.linalg.{ Vector, Vectors }
import org.apache.spark.mllib.linalg.distributed.RowMatrix
import org.apache.spark.mllib.regression.LabeledPoint
import breeze.linalg.{
Matrix => BM,
CSCMatrix => BSM,
DenseMatrix => BDM,
Vector => BV,
DenseVector => BDV,
SparseVector => BSV,
axpy => brzAxpy,
svd => brzSvd,
max => Bmax,
min => Bmin,
sum => Bsum
}
import scala.collection.mutable.ArrayBuffer
import NN.NeuralNet
import util.RandSampleData
object Test_example_NN {
def main(args: Array[String]) {
//1 构建Spark对象
val conf = new SparkConf().setAppName("NNtest")
val sc = new SparkContext(conf)
//基于经典优化算法测试函数随机生成样本
//2 随机生成测试数据
// 随机数生成
Logger.getRootLogger.setLevel(Level.WARN)
val sample_n1 = 1000
val sample_n2 = 5
//在此处选择所需的随机数生成函数,从前述的函数中
val randsamp1 = RandSampleData.RandM(sample_n1, sample_n2, -10, 10, "sphere")
// 归一化[0 1]
val normmax = Bmax(randsamp1(::, breeze.linalg.*))
val normmin = Bmin(randsamp1(::, breeze.linalg.*))
val norm1 = randsamp1 - (BDM.ones[Double](randsamp1.rows, 1)) * normmin
val norm2 = norm1 :/ ((BDM.ones[Double](norm1.rows, 1)) * (normmax - normmin))
// 转换样本train_d
val randsamp2 = ArrayBuffer[BDM[Double]]()
for (i <- 0 to sample_n1 - 1) {
val mi = norm2(i, ::)
val mi1 = mi.inner
val mi2 = mi1.toArray
val mi3 = new BDM(1, mi2.length, mi2)
randsamp2 += mi3
}
val randsamp3 = sc.parallelize(randsamp2, 10)
sc.setCheckpointDir("/user/local/checkpoint")
randsamp3.checkpoint()
val train_d = randsamp3.map(f => (new BDM(1, 1, f(::, 0).data), f(::, 1 to -1)))
//3 设置训练参数,建立模型
// opts:迭代步长,迭代次数,交叉验证比例
val opts = Array(100.0, 50.0, 0.0)
train_d.cache
val numExamples = train_d.count()
println(s"numExamples = $numExamples.")
val NNmodel = new NeuralNet().
setSize(Array(5, 7, 1)).
setLayer(3).
setActivation_function("tanh_opt").
setLearningRate(2.0).
setScaling_learningRate(1.0).
setWeightPenaltyL2(0.0).
setNonSparsityPenalty(0.0).
setSparsityTarget(0.05).
setInputZeroMaskedFraction(0.0).
setDropoutFraction(0.0).
setOutput_function("sigm").
NNtrain(train_d, opts)
//4 模型测试
val NNforecast = NNmodel.predict(train_d)
val NNerror = NNmodel.Loss(NNforecast)
println(s"NNerror = $NNerror.")
val printf1 = NNforecast.map(f => (f.label.data(0), f.predict_label.data(0))).take(20)
println("预测结果——实际值:预测值:误差")
for (i <- 0 until printf1.length)
println(printf1(i)._1 + "\t" + printf1(i)._2 + "\t" + (printf1(i)._2 - printf1(i)._1))
println("权重W{1}")
val tmpw0 = NNmodel.weights(0)
for (i <- 0 to tmpw0.rows - 1) {
for (j <- 0 to tmpw0.cols - 1) {
print(tmpw0(i, j) + "\t")
}
println()
}
println("权重W{2}")
val tmpw1 = NNmodel.weights(1)
for (i <- 0 to tmpw1.rows - 1) {
for (j <- 0 to tmpw1.cols - 1) {
print(tmpw1(i, j) + "\t")
}
println()
}
}
}