|
|
|
@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
## 5.1 Spark机器学习实战
|
|
|
|
|
|
|
|
|
|
坦克图片识别分类
|
|
|
|
|
|
|
|
|
|
### 5.1.1 数据集介绍
|
|
|
|
|
|
|
|
|
|
三种坦克图片数据集,如下图所示:
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
以上有三种数据集,分别是以`bmp-2`开头的`BMP-2`步兵战车的图片、以`btr-70`开头的`BTR-70`装甲输送车的图片、以`t-72`开头的`T-72`主战坦克的图片。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 5.1.2 数据集处理
|
|
|
|
|
|
|
|
|
|
##### 5.1.2.1 加载图片数据集
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
我们可以使用`opencv_python`计算机视觉库来读取一幅图像。
|
|
|
|
|
|
|
|
|
|
在读取图像之前,我们需要对图像的构造有一定的理解,怎么理解呢?
|
|
|
|
|
|
|
|
|
|
简单来说图像就是一个矩阵,在`OpenCV for Python`中,图像就是`NumPy`中的数组!
|
|
|
|
|
|
|
|
|
|
我们知道图像就是一个矩阵了,之后我们通过代码来读取呢?
|
|
|
|
|
|
|
|
|
|
示例:
|
|
|
|
|
```
|
|
|
|
|
# 读入图像文件,返回numpy数组
|
|
|
|
|
img = cv2.imread('/root/1.jpg', cv2.IMREAD_GRAYSCALE)
|
|
|
|
|
rows, columns = img.shape
|
|
|
|
|
# 将二维数组转一维数组
|
|
|
|
|
img = img.reshape(rows*columns)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### 5.1.2.2 将一维数组转换成 Spark 中的向量
|
|
|
|
|
|
|
|
|
|
上一步我们将图片转换成一维的数组之后了,接下来我们将一维数组转换成 Spark 中的向量。
|
|
|
|
|
|
|
|
|
|
可能你会有疑问?为什么要将一维数组转换成 Spark 中的向量呢?
|
|
|
|
|
|
|
|
|
|
简单来说就是类型不匹配了。
|
|
|
|
|
|
|
|
|
|
示例:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
import cv2
|
|
|
|
|
img = cv2.imread("testing2\\bmp-2-1.jpg", cv2.IMREAD_GRAYSCALE)
|
|
|
|
|
rows, columns = img.shape
|
|
|
|
|
img = img.reshape(rows * columns)
|
|
|
|
|
print(type(img))
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
结果如下:
|
|
|
|
|
```
|
|
|
|
|
<class 'numpy.ndarray'>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
我们都知道`numpy.ndarray`是`Python`中的类型,`Spark`是由`Scala`编写的,它怎么可能识别的了`numpy.ndarray`类型呢?
|
|
|
|
|
|
|
|
|
|
那我们怎么将将一维数组转换成 Spark 中的向量呢?
|
|
|
|
|
|
|
|
|
|
很简单,直接使用`from pyspark.ml.linalg import Vectors`中的`Vectors`来进行转换即可!
|
|
|
|
|
|
|
|
|
|
在`pyspark`中,有两种向量,分别是稠密向量和稀疏向量。
|
|
|
|
|
|
|
|
|
|
`DenseVctor` :稠密向量 其创建方式 `Vector.dense(数据)`
|
|
|
|
|
|
|
|
|
|
`SparseVector` :稀疏向量 其创建方式有两种:
|
|
|
|
|
|
|
|
|
|
方法一:`Vector.sparse(向量长度,索引数组,与索引数组所对应的数值数组)`
|
|
|
|
|
|
|
|
|
|
方法二:`Vector.sparse(向量长度,(索引,数值),(索引,数值),(索引,数值),...(索引,数值))`
|
|
|
|
|
|
|
|
|
|
比如向量`(1,0,3,4)`的创建有三种方法:
|
|
|
|
|
|
|
|
|
|
稠密向量:直接`Vectors.dense(1,0,3,4)`
|
|
|
|
|
|
|
|
|
|
稀疏向量:
|
|
|
|
|
|
|
|
|
|
方法一:`Vector.sparse(4,(0,2,3),(1,3,4))`
|
|
|
|
|
|
|
|
|
|
表示该向量的第`0`个,第`2`个,第`3`个位置,`(1,3,4)` 表示`(0,2,3)`位置对应的数值分别为`1,3,4`
|
|
|
|
|
|
|
|
|
|
方法二:`Vector.sparse(4,(0,1),(2,3),(3,4))`
|
|
|
|
|
|
|
|
|
|
`(0,1)`就是`(索引,数值)`的形式。位置`0`的数值为`1`, 位置`2`的数值为`3`,位置`3`的数值为`4`。
|
|
|
|
|
|
|
|
|
|
一般我们会直接将数组转换成稠密向量。
|
|
|
|
|
|
|
|
|
|
##### 5.1.2.3 将向量与标签进行绑定并将其转换成Dataframe
|
|
|
|
|
|
|
|
|
|
上面的两步,我们已经加载了图片数据并将其转换成`Spark`中的向量了,但是这样还远远不够,为什么呢?
|
|
|
|
|
|
|
|
|
|
因为我将图片数据转换成向量之后,这样只能得出该图片的特征,但是我们还没有指定特征的标签。
|
|
|
|
|
|
|
|
|
|
我们回到最初加载图片数据那步,这里我们在读取图片数据的同时获取图片的文件名,我们通过文件名进行打标签,具体要求如下:
|
|
|
|
|
|
|
|
|
|
- 如果文件名包含 `btr-70` ,`label` 设置为 `0` ;
|
|
|
|
|
|
|
|
|
|
- 如果文件名包含 `t-72` ,`label` 设置为 `1`;
|
|
|
|
|
|
|
|
|
|
- 其他的,`label` 设置为 `2`。
|
|
|
|
|
|
|
|
|
|
我们得到标签之后,我们将特征向量与该标签进行绑定,构建一个元组。
|
|
|
|
|
|
|
|
|
|
构建元组之后,我们使用`pandas`将元组数据加载成`Dataframe`并将特征向量列的列名设置为`features`,标签的列的列名设置为`label`。
|
|
|
|
|
|
|
|
|
|
为什么要这样做呢?
|
|
|
|
|
|
|
|
|
|
我们查看下`LogisticRegression`算法的源码
|
|
|
|
|
|
|
|
|
|
构造函数源码如下:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
@keyword_only
|
|
|
|
|
def __init__(self, featuresCol="features", labelCol="label", predictionCol="prediction",
|
|
|
|
|
maxIter=100, regParam=0.0, elasticNetParam=0.0, tol=1e-6, fitIntercept=True,
|
|
|
|
|
threshold=0.5, thresholds=None, probabilityCol="probability",
|
|
|
|
|
rawPredictionCol="rawPrediction", standardization=True, weightCol=None,
|
|
|
|
|
aggregationDepth=2, family="auto",
|
|
|
|
|
lowerBoundsOnCoefficients=None, upperBoundsOnCoefficients=None,
|
|
|
|
|
lowerBoundsOnIntercepts=None, upperBoundsOnIntercepts=None):
|
|
|
|
|
super(LogisticRegression, self).__init__()
|
|
|
|
|
self._java_obj = self._new_java_obj(
|
|
|
|
|
"org.apache.spark.ml.classification.LogisticRegression", self.uid)
|
|
|
|
|
self._setDefault(maxIter=100, regParam=0.0, tol=1E-6, threshold=0.5, family="auto")
|
|
|
|
|
kwargs = self._input_kwargs
|
|
|
|
|
self.setParams(**kwargs)
|
|
|
|
|
self._checkThresholdConsistency()
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
通过`__init__`函数中的参数,我们可以看到`featuresCol`的默认值为`features`,`labelCol`的默认值为`label`。
|
|
|
|
|
|
|
|
|
|
通过`pandas`构建了 `Dataframe`之后指定了列名这样做的话,就可以不用设置`featuresCol`、`labelCol`了,同时增强了代码可读性。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 5.1.3 Spark 加载数据集
|
|
|
|
|
|
|
|
|
|
上面我们已经把数据集处理完毕之后,然后我们可以通过`Spark`来加载数据集。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
spark = SparkSession.builder.master("local[*]").appName("demo").getOrCreate()
|
|
|
|
|
sparkDF = spark.createDataFrame(df) # df是我们通过pandas构建的Dataframe
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 5.1.4 将数据集拆分训练集和测试集
|
|
|
|
|
|
|
|
|
|
上面我们把图片数据集加载到`Spark`中了,之后我们要把数据集划分成两部分,一部分为训练集,另一部分为测试集。
|
|
|
|
|
|
|
|
|
|
简单来说,训练集就是用来训练模型的,测试集就是来评测这样模型的好与坏的。
|
|
|
|
|
|
|
|
|
|
关于训练集与测试集的比例问题,一般来说训练集越多越好,我们一般将训练集与测试集的比例调整为`8:2`或者`7:3`。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
train_df, test_df = sparkDF.randomSplit([0.7, 0.3])
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 5.1.5 创建LR分类器
|
|
|
|
|
|
|
|
|
|
逻辑回归是一种用于预测分类响应的流行方法。这是广义线性模型的一种特殊情况,可以预测结果的可能性。在`spark.ml`逻辑回归中,可以通过使用二项式逻辑回归来预测二进制结果,或者在使用多项式逻辑回归时可以将其预测为多类结果。使用该`family` 参数在这两种算法之间进行选择,或者将其保留为未设置状态,`Spark`会推断出正确的变体。
|
|
|
|
|
|
|
|
|
|
- 通过将`family`参数设置为“多项式”,可以将多项式逻辑回归用于二进制分类。它将产生两组系数和两个截距。
|
|
|
|
|
|
|
|
|
|
- 当对具有恒定非零列的数据集进行`LogisticRegressionModel`拟合而没有截距时,`Spark MLlib`为恒定非零列输出零系数。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
创建`LR`分类器用来训练模型:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
lr = LogisticRegression(family="multinomial")
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 5.1.6 训练模型
|
|
|
|
|
|
|
|
|
|
上一步,我们已经把`LR`分类器构建好了并且我们把数据集划分成训练集与测试集了,接下来我们就是训练模型了。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
model = lr.fit(train_df)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 5.1.7 评估模型
|
|
|
|
|
|
|
|
|
|
我们已经把模型训练好了,接下来就是评估模型了。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
predict_df = model.transform(test_df)
|
|
|
|
|
|
|
|
|
|
# 对测试集做predict, 生成(预测分类, 正确分类)
|
|
|
|
|
def build_predict_target(row):
|
|
|
|
|
return (float(row.prediction), float(row.label))
|
|
|
|
|
|
|
|
|
|
predict_and_target_rdd = predict_df.rdd.map(build_predict_target)
|
|
|
|
|
|
|
|
|
|
# 统计模型效果
|
|
|
|
|
metrics = BinaryClassificationMetrics(predict_and_target_rdd)
|
|
|
|
|
print(metrics.areaUnderROC)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
以上代码就是统计模型效果了,由于我们使用的是逻辑回归,我们只要获取`AUC`的值了,`AUC`越大,模型的准确度越高。
|