基于Spark的SVM模型手写数字识别
数据集简介
MNIST手写数字数据集官网:
或者数据集下载网址:http://yann.lecun.com/exdb/mnist/
共有4个数据集,下载之后是4个gz压缩包,把它们保存在磁盘中: train-images-idx3-ubyte.gz: training set images (9912422 bytes) train-labels-idx1-ubyte.gz: training set labels (28881 bytes) t10k-images-idx3-ubyte.gz: test set images (1648877 bytes) t10k-labels-idx1-ubyte.gz: test set labels (4542 bytes)
解压之后得到的是一个二进制文件。由60000个训练样本集和10000个测试样本集构成,每个样本的尺寸为28x28,以二进制格式存储。标签是0到9的数字,图像则是0到255的像素值。
文件的格式如下:训练集和测试集格式一样,这里以训练集为例:
train-labels-idx1-ubyte
| [offset] | [type] | [value] | [description] |
|---|---|---|---|
| 0000 | 32 bit integer | 0x00000801(2049) | magic number (MSB first) (文件头魔数) |
| 0004 | 32 bit integer | 60000 | number of items (标签个数) |
| 0008 | unsigned byte | ?? | label (图像标签) |
| 0009 | unsigned byte | ?? | label |
| …….. | unsigned byte | ?? | label |
The labels values are 0 to 9.
train-images-idx3-ubyte
| [offset] | [type] | [value] | [description] |
|---|---|---|---|
| 0000 | 32 bit integer | 0x00000803(2051) | magic number(文件头魔数) |
| 0004 | 32 bit integer | 60000 | number of images(图像个数) |
| 0008 | 32 bit integer | 28 | number of rows(图像宽度) |
| 0012 | 32 bit integer | 28 | number of columns(图像高度) |
| 0016 | unsigned byte | ?? | pixel (图像像素值) |
| 0017 | unsigned byte | ?? | pixel |
| …….. | unsigned byte | ?? | pixel |
这里的行和列,其实就是一张图片的像素矩阵,也就是每个图片都有28×28=784个像素。从第16个字节开始,就是图片的每一个像素点的值了。
SVM模型简介
svm通常用于解决监督式机器学习中的二元分类问题,其基本模型定义为特征空间上间隔最大的线性分类器,即支持向量机的学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。如图示

Spark MLlib通过SVMWithSGD类和其伴生对象实现了线性SVM二元分类模型,并采用随机梯度下降算法来优化目标函数
数据集处理
数据集读取
spark MLlib的数据格式是 LabeledPoint 标签向量数据格式,所以要先对数据进行读取和处理。
/**
* 安全打开文件流方法
*/
def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B =
try {
f(resource)
} finally {
resource.close()
}
读取图片信息如下:返回有每一张图片像素数据组成的数组
/**
* 读取图像文件
*
* @param imagesPath
* @return Array[Array[Byte]]
* TRAINING SET IMAGE FILE (train-images-idx3-ubyte):
* [offset] [type] [value] [description]
* 0000 32 bit integer 0x00000803(2051) magic number
* 0004 32 bit integer 60000 number of images
* 0008 32 bit integer 28 number of rows
* 0012 32 bit integer 28 number of columns
* 0016 unsigned byte ?? pixel
* 0017 unsigned byte ?? pixel
* ........
* xxxx unsigned byte ?? pixel
*/
def loadImages(imagesPath: String): Array[Array[Byte]] ={
val file = new File(imagesPath)
val in = new FileInputStream(file)
var trainingDS = new Array[Byte](file.length.toInt)
using(new FileInputStream(file)) { source =>
{
in.read(trainingDS)
}
}
//32 bit integer 0x00000803(2051) magic number
val magicNum = ByteBuffer.wrap(trainingDS.take(4)).getInt
println(s"magicNum=$magicNum")
//32 bit integer 60000 number of items
val numOfItems = ByteBuffer.wrap(trainingDS.slice(4, 8)).getInt
println(s"numOfItems=$numOfItems")
//32 bit integer 28 number of rows
val numOfRows = ByteBuffer.wrap(trainingDS.slice(8, 12)).getInt
println(s"numOfRows=$numOfRows")
//32 bit integer 28 number of columns
val numOfCols = ByteBuffer.wrap(trainingDS.slice(12, 16)).getInt
println(s"numOfCols=$numOfCols")
trainingDS = trainingDS.drop(16)
val itemsBuffer = new ArrayBuffer[Array[Byte]]
for(i <- 0 until numOfItems){
//使用slice方法从规定的索引处提取数组中的元素
itemsBuffer += trainingDS.slice( i * numOfCols * numOfRows , (i+1) * numOfCols * numOfRows)
}
itemsBuffer.toArray
}
读取标签代码如下:
/**
* 读取标签
*
* @param labelPath
* @return Array[Byte]
* TRAINING SET LABEL FILE (train-labels-idx1-ubyte):
* [offset] [type] [value] [description]
* 0000 32 bit integer 0x00000801(2049) magic number (MSB first)
* 0004 32 bit integer 60000 number of items
* 0008 unsigned byte ?? label
* 0009 unsigned byte ?? label
* ........
* xxxx unsigned byte ?? label
* The labels values are 0 to 9.
*/
def loadLabel(labelPath: String): Array[Byte] ={
val file = new File(labelPath)
//根据文件路径获取读文件的对象in
val in = new FileInputStream(file)
var labelDS = new Array[Byte](file.length.toInt)
//定义一个Array[Byte]类型的labelDS,把读取到的文件放进去
using(new FileInputStream(file)) { source =>
{
in.read(labelDS)
}
}
/**
* Wraps a byte array into a buffer.(将字节数组包装到缓冲区中。)
*
* 输入参数:array
* The array that will back this buffer
*
* @return The new byte buffer
*/
//32 bit integer 0x00000801(2049) magic number (MSB first--high endian)
val magicLabelNum = ByteBuffer.wrap(labelDS.take(4)).getInt
println(s"magicLabelNum=$magicLabelNum")
//32 bit integer 60000 number of items
val numOfLabelItems = ByteBuffer.wrap(labelDS.slice(4, 8)).getInt
println(s"numOfLabelItems=$numOfLabelItems")
//删掉前面的文件描述
labelDS = labelDS.drop(8)
labelDS
}
-
添加依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-core_2.11</artifactId> <version>2.4.7</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-mllib_2.11</artifactId> <version>2.4.7</version> </dependency>
//处理成mlLib能用的基本类型 LabeledPoint
if(trainLabel.length == trainImages.length) {
/**
* zip函数将传进来的两个参数中相应位置上的元素组成一个pair数组。如果其中一个参数元素比较长,那么多余的参数会被删掉。
* 标签数量和图像数量能对上则合并数组 Array[(label,images)]
* 把像素值由二进制byte类型转换成double类型 使用p & 0xFF
* 只能对0/1进行识别
* data: Array[LabeledPoint]
* LabeledPoint:[label:0/1 features:各个图像的像素值]
*/
val data = trainLabel.zip(trainImages).filter(d => d._1.toInt == 0 || d._1.toInt == 1).map( d =>
LabeledPoint(d._1.toInt, Vectors.dense(d._2.map(p => (p & 0xFF).toDouble))))
//创建data的RDD
//trainRdd: RDD[LabeledPoint]
val trainRdd = sc.makeRDD(data)
3.调用MLlib的api,输入数据进行训练,得到分类模型
/**
* 同样使用zip函数将将test数据集中的testLabel,testImages一一对应组成Array[(labe,images)]
* testData: Array[(Int:0/1, Vector:各个图像的像素值)]
*/
val testData = testLabel.zip(testImages).filter(d => d._1.toInt == 0 || d._1.toInt == 1)
.map(d =>(d._1.toInt,Vectors.dense(d._2.map(p => (p & 0xFF).toDouble )) ))
//testRDD: RDD[Vector] 其中的元素都是各个元素的像素值
val testRDD = sc.makeRDD(testData.map(_._2))
// res:Array[Int] 其中的元素都是使用model模型预测的测试集数据的标签
val res = model.predict(testRDD).map(l => l.toInt).collect()
//res.foreach(println(_))
//把测试的结果label和数据集本身的label组成一个pair数组
val tr = res.zip(testData.map(_._1))
//统计测试结果和数据集本身的label一样的个数
val sum = tr.map( f =>{
if(f._1 == f._2.toInt) 1 else 0
}).sum
println("准确率为:"+ sum.toDouble /tr.length)



浙公网安备 33010602011771号