Spark MLlib(现在更常指基于 spark.ml
包的 DataFrame-based API)完美地支持这个流程。下面详细解释每个阶段在 Spark MLlib 中的具体实现和关键组件:
graph TD
A[数据准备] --> B[特征工程]
B --> C[模型训练]
C --> D[模型评估]
D --> E{评估是否通过?}
E -->|否| C
E -->|是| F[模型部署]
subgraph 数据准备
A1[加载原始数据] --> A2[数据清洗]
A2 --> A3[特征提取(结构化)]
A1["DataFrameReader<br>(csv/parquet等)"]
A2["na.fill()/drop()<br>缺失值处理"]
A3["StringIndexer<br>VectorAssembler"]
end
subgraph 特征工程
B1[特征转换] --> B2[特征生成]
B2 --> B3[特征选择]
B1["StandardScaler<br>MinMaxScaler"]
B2["Tokenizer<br>HashingTF<br>IDF"]
B3["ChiSqSelector<br>PCA"]
end
subgraph 模型训练
C1[监督学习] --> C2[无监督学习]
C1["LogisticRegression<br>RandomForest<br>ALS"]
C2["KMeans<br>GaussianMixture"]
C1 & C2 -.-> C4["Estimator.fit()生成Model"]
end
subgraph 模型评估
D1[生成预测结果] --> D2[计算指标]
D1["model.transform(testDF)"]
D2["MulticlassClassificationEvaluator<br>RegressionEvaluator"]
end
subgraph 模型部署
F1[保存模型] --> F2[加载模型]
F2 --> F3[批量/实时预测]
F1["model.save('path')"]
F2["Model.load('path')"]
F3["Spark Streaming/Flink集成"]
end
核心思想:Pipeline API
Spark MLlib 的核心抽象是 Pipeline
。它将数据准备、特征工程、模型训练(甚至评估和预测)的多个步骤串联成一个工作流(Workflow)。每个步骤是一个 Transformer
(转换器)或 Estimator
(估计器)。
-
数据准备
- 目的: 将原始数据加载到 Spark 并转换为适合 MLlib 处理的格式(主要是
DataFrame
),进行初步清洗和探索。 - 关键 Spark 操作:
- 读取数据:
spark.read.format(...).load(...)
(CSV, JSON, Parquet, JDBC, Hive 等)。 - 数据清洗:
- 处理缺失值:
na.fill()
,na.drop()
,Imputer
(估算器)。 - 处理异常值:基于统计方法过滤。
- 数据类型转换:
cast()
。 - 重命名列:
withColumnRenamed()
。 - 选择/过滤列/行:
select()
,filter()
/where()
。
- 处理缺失值:
- 数据探索:
df.describe().show()
,df.groupBy().agg(...)
,df.stat
(统计函数如corr
,cov
,crosstab
). - 创建 DataFrame: 确保数据结构清晰,包含特征列和(监督学习中的)标签列。
- 读取数据:
- 目的: 将原始数据加载到 Spark 并转换为适合 MLlib 处理的格式(主要是
-
特征工程
- 目的: 将原始数据转换为更能有效表示预测问题的特征向量。这是提升模型性能的关键步骤。
- 关键 Spark MLlib 组件 (Transformers & Estimators):
StringIndexer
: 将字符串类型的分类特征(标签列或特征列)编码为数值索引(如 “cat”->0, “dog”->1)。OneHotEncoder
/OneHotEncoderEstimator
: 将StringIndexer
产生的索引列转换为独热编码向量(稀疏向量表示)。VectorAssembler
: 最重要! 将多个数值型、布尔型特征列以及由其他转换器(如 OneHotEncoder)生成的向量列组合成一个特征向量列。这是模型输入所必需的格式(通常命名为 “features”)。StandardScaler
/MinMaxScaler
/MaxAbsScaler
/RobustScaler
: 数值特征标准化/归一化(通常是 Estimator,需要先 fit 数据得到模型(Transformer),再用该模型 transform 数据)。Bucketizer
: 将连续特征离散化为分箱(区间)。QuantileDiscretizer
: 基于分位数的离散化(Estimator)。Tokenizer
/StopWordsRemover
/CountVectorizer
/HashingTF
/IDF
/Word2Vec
: 用于文本特征处理的工具链。PCA
/ChiSqSelector
: 特征降维或选择(通常是 Estimator)。SQLTransformer
: 使用 SQL 语句进行特征转换(非常灵活)。RFormula
: 使用 R 风格的公式指定特征和标签(自动处理 StringIndexer, OneHotEncoder, VectorAssembler)。
- 输出: 一个包含 “features” 向量列(可能还有 “label” 列)的 DataFrame。
-
模型训练
- 目的: 使用准备好的特征数据训练机器学习模型。
- 关键 Spark MLlib 组件 (Estimators):
- 分类:
LogisticRegression
,DecisionTreeClassifier
,RandomForestClassifier
,GBTClassifier
,NaiveBayes
,LinearSVC
,MultilayerPerceptronClassifier
。 - 回归:
LinearRegression
,DecisionTreeRegressor
,RandomForestRegressor
,GBTRegressor
,GeneralizedLinearRegression
,IsotonicRegression
。 - 聚类:
KMeans
,BisectingKMeans
,GaussianMixture
,LDA
(主题模型)。 - 协同过滤:
ALS
。 - 模型都是
Estimator
: 它们需要调用.fit(trainingData)
方法。fit()
方法会:- 分析(
trainingData
)计算模型参数(训练过程)。 - 返回一个
Transformer
(即训练好的模型Model
)。
- 分析(
- 分类:
- 数据分割: 通常使用
trainingData.randomSplit(Array(trainRatio, testRatio))
将数据划分为训练集和测试集(有时还会分验证集)。 - 超参数调优:
ParamGridBuilder
: 构建需要搜索的超参数组合网格。CrossValidator
/TrainValidationSplit
: 评估器,用于在网格上执行交叉验证或训练-验证分割,选择最佳超参数组合。它们内部会多次调用.fit()
。- 输出: 一个
Transformer
(训练好的模型Model
对象)。
-
模型评估
- 目的: 使用独立的测试集评估训练好的模型在未见数据上的性能。
- 关键 Spark MLlib 组件 (Evaluators):
- 分类:
BinaryClassificationEvaluator
(支持 areaUnderROC, areaUnderPR)。MulticlassClassificationEvaluator
(支持 accuracy, weightedPrecision, weightedRecall, f1, etc.)。
- 回归:
RegressionEvaluator
(支持 rmse, mse, mae, r2)。
- 聚类:
ClusteringEvaluator
(基于 Silhouette 轮廓系数)。
- 分类:
- 流程:
- 使用训练好的模型(
Model
,即Transformer
)对 测试集 调用.transform(testData)
。这会生成一个包含原始特征、原始标签、预测标签(prediction
)和可能预测概率(probability
)的新 DataFrame。 - 将上一步得到的预测结果 DataFrame 传递给对应的
Evaluator
的.evaluate(predictions)
方法,得到评估指标值。
- 使用训练好的模型(
- 模型选择:
CrossValidator
/TrainValidationSplit
在训练过程中会自动使用Evaluator
来评估不同参数组合的性能,并选择最优模型。
-
模型部署 / 预测
- 目的: 将训练好的模型用于对新数据进行预测。
- 关键操作:
- 保存模型: 训练好的模型(
Model
)是Transformer
,可以使用.write().overwrite().save(path)
保存到 HDFS、S3、本地文件系统等。PipelineModel
(包含整个 Pipeline 步骤的模型)尤其适合保存和加载,因为它封装了从原始数据到预测的所有预处理和模型逻辑。
- 加载模型:
PipelineModel.load(path)
。 - 进行预测:
- 加载新数据(
newData
)为 DataFrame(格式需要与训练时进入VectorAssembler
之前的结构一致)。 - 使用加载的
Model
或PipelineModel
调用.transform(newData)
。 - 输出 DataFrame 将包含原始特征和预测结果列(通常是
prediction
,可能还有probability
或features
等)。
- 加载新数据(
- 保存模型: 训练好的模型(
- 部署方式:
- 批处理预测: 在 Spark 作业中加载模型,对大量新数据文件或数据库表进行
.transform()
。这是 Spark 最擅长的场景。 - 近实时预测 (API Serving):
- Spark Streaming / Structured Streaming: 处理实时数据流并进行预测。
- 导出模型: 将训练好的 Spark MLlib 模型导出为其他格式(如 PMML, ONNX, MLeap - 注意:支持有限且非官方),然后在专门的模型服务器(如 TensorFlow Serving, TorchServe, Seldon Core, KServe)或 Web 服务框架(如 Flask, FastAPI)中加载进行低延迟预测。
spark-submit
微服务: 将预测逻辑包装在一个常驻的小型 Spark 应用中,通过 REST API 接收请求,在 Spark 集群内进行预测并返回结果(延迟相对较高,适合小批量或对延迟要求不高的场景)。
- 边缘部署: 对于非常小的模型,有时可以提取模型参数(如线性模型的权重)在非 Spark 环境(如手机、IoT 设备)中实现预测逻辑。
- 批处理预测: 在 Spark 作业中加载模型,对大量新数据文件或数据库表进行
Pipeline 示例代码片段 (简化版)
import org.apache.spark.ml.{Pipeline, PipelineModel}
import org.apache.spark.ml.feature.{VectorAssembler, StringIndexer, OneHotEncoder, StandardScaler}
import org.apache.spark.ml.classification.LogisticRegression
import org.apache.spark.ml.evaluation.BinaryClassificationEvaluator
import org.apache.spark.ml.tuning.{ParamGridBuilder, CrossValidator}
// 1. 数据准备
val rawData = spark.read.option("header", "true").option("inferSchema", "true").csv("data.csv")
val cleanedData = rawData.na.drop() // 简单示例:删除缺失值
// 2. 定义特征工程和模型 Estimators & Transformers
val indexer = new StringIndexer()
.setInputCol("categoryCol")
.setOutputCol("categoryIndex")
val encoder = new OneHotEncoder()
.setInputCol("categoryIndex")
.setOutputCol("categoryVec")
val numCols = Array("age", "income") // 数值特征列
val assembler = new VectorAssembler()
.setInputCols(numCols ++ Array("categoryVec")) // 组合数值特征和编码后的分类特征
.setOutputCol("rawFeatures")
val scaler = new StandardScaler()
.setInputCol("rawFeatures")
.setOutputCol("features")
.setWithStd(true)
.setWithMean(true)
val lr = new LogisticRegression()
.setFeaturesCol("features")
.setLabelCol("label")
// 3. 构建 Pipeline (定义工作流)
val pipeline = new Pipeline()
.setStages(Array(indexer, encoder, assembler, scaler, lr))
// (可选) 超参数调优
val paramGrid = new ParamGridBuilder()
.addGrid(lr.regParam, Array(0.01, 0.1, 1.0))
.addGrid(lr.elasticNetParam, Array(0.0, 0.5, 1.0))
.build()
val evaluator = new BinaryClassificationEvaluator()
.setMetricName("areaUnderROC")
val cv = new CrossValidator()
.setEstimator(pipeline) // 整个 Pipeline 作为 Estimator
.setEvaluator(evaluator)
.setEstimatorParamMaps(paramGrid)
.setNumFolds(5) // 5折交叉验证
// 4. 数据分割 & 训练模型
val Array(trainingData, testData) = cleanedData.randomSplit(Array(0.8, 0.2))
// 训练基础 Pipeline 模型
// val model = pipeline.fit(trainingData)
// 或者训练带调优的 CV 模型 (它会返回最佳 PipelineModel)
val cvModel = cv.fit(trainingData)
// 5. 模型评估 (使用最佳模型)
val bestModel = cvModel.bestModel.asInstanceOf[PipelineModel]
val predictions = bestModel.transform(testData)
val auc = evaluator.evaluate(predictions)
println(s"Test Area Under ROC: $auc")
// 6. 部署/预测 - 保存模型
bestModel.write.overwrite().save("path/to/bestModel")
// ... 之后在另一个程序或作业中 ...
// val loadedModel = PipelineModel.load("path/to/bestModel")
// val newData = ... // 加载新数据 (格式需匹配)
// val newPredictions = loadedModel.transform(newData)
// newPredictions.select("features", "prediction", "probability").show()
总结关键点:
DataFrame
是核心: 数据在整个流程中以DataFrame
的形式流动。Transformer
和Estimator
是基础组件:Estimator.fit(DataFrame) => Transformer(Model)
。Transformer.transform(DataFrame) => DataFrame
。
Pipeline
串联流程: 将多个Transformer
和Estimator
组合成一个可训练、可部署的整体工作流。- 特征向量是关键:
VectorAssembler
是将各种特征组合成模型所需输入格式(一个向量列)的必经步骤。 - 评估器 (
Evaluator
) 量化性能: 使用标准化的指标评估模型。 - 模型即
PipelineModel
: 保存和加载通常针对封装了整个预处理和模型逻辑的PipelineModel
。 - 批处理是强项,近实时需额外方案: Spark 天生适合大规模批处理预测,低延迟 API 服务通常需要结合其他技术。
理解这个流程以及 Transformer
/Estimator
/Pipeline
的设计模式,是高效使用 Spark MLlib 的关键。