|
|
@@ -0,0 +1,166 @@
|
|
|
+package com.example.data
|
|
|
+
|
|
|
+import com.example.SparkConnect
|
|
|
+import com.example.dao.{FileInfoDao, HdfsDao}
|
|
|
+import org.apache.spark.ml.{Pipeline, PipelineModel}
|
|
|
+import org.apache.spark.ml.feature.{StringIndexer, VectorAssembler}
|
|
|
+import org.apache.spark.sql.types.StringType
|
|
|
+import org.apache.spark.sql.{Column, DataFrame, SaveMode, SparkSession}
|
|
|
+import org.springframework.beans.factory.annotation.Autowired
|
|
|
+import org.springframework.stereotype.Component
|
|
|
+
|
|
|
+import javax.annotation.PostConstruct
|
|
|
+
|
|
|
+
|
|
|
+@Component
|
|
|
+class DataSetHelper extends SparkConnect{
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ val fileLocDao: FileInfoDao = null
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ val hdfsDao: HdfsDao = null
|
|
|
+
|
|
|
+
|
|
|
+ def fillNull(dataFrame: DataFrame): DataFrame ={
|
|
|
+
|
|
|
+ val stringTypeFeatures = dataFrame.schema.filter(_.dataType.equals(StringType)).map(_.name)
|
|
|
+ return dataFrame.na.fill("NA",stringTypeFeatures)
|
|
|
+ }
|
|
|
+
|
|
|
+ def getStringTypeFeatureTransformerUri(modelId:Int): String ={
|
|
|
+ hdfsUri+"pipeline_StringTypeFeatureTransformer_modelId-"+modelId
|
|
|
+ }
|
|
|
+
|
|
|
+ def getFileLibsvmUri(fileFlag: Int): String = {
|
|
|
+ hdfsUri + fileFlag + "-2023.libsvm"
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ def getLabelStringIndexerUri(modelId: Int):String = {
|
|
|
+ hdfsUri+"LabelStringIndexer_modelId-"+modelId
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @throws(classOf[Exception])
|
|
|
+ def processTrainDataSet(modelId:Int,
|
|
|
+ fileId: Int,
|
|
|
+ label: String,
|
|
|
+ features: Array[String]): String ={
|
|
|
+
|
|
|
+ val spark = sparkSession
|
|
|
+ val trainDataFile = fileLocDao.findById(fileId)
|
|
|
+ val trainDataFileUri = trainDataFile.getLocation
|
|
|
+ log.info("trainDataFileUri = "+trainDataFileUri)
|
|
|
+
|
|
|
+ //训练集读取
|
|
|
+ var trainSet:DataFrame = spark.read.parquet(trainDataFileUri)
|
|
|
+
|
|
|
+ //保留标签|特征
|
|
|
+ trainSet = trainSet.select(label,features:_*).persist()
|
|
|
+
|
|
|
+ //去除标签列为空的数据(无法进行学习)
|
|
|
+ trainSet = trainSet.filter("'"+ label+" ' is not null")
|
|
|
+
|
|
|
+ //填充特征空值
|
|
|
+ trainSet = fillNull(trainSet)
|
|
|
+ log.info("训练集-已填充空值")
|
|
|
+ trainSet.show(5)
|
|
|
+
|
|
|
+ //取出类型为string的特征
|
|
|
+ val stringTypeFeatures = trainSet.schema
|
|
|
+ .filter(_.dataType.equals(StringType)) //是string类型
|
|
|
+ .filter(!_.name.equals(label)) //且不是label列
|
|
|
+ .map(_.name)
|
|
|
+
|
|
|
+ //字符类型特征 -> 数值类型特征
|
|
|
+ val stringTypeFeatureIndexers = stringTypeFeatures.map(stringTypeFeature => {
|
|
|
+ new StringIndexer()
|
|
|
+ .setInputCol(stringTypeFeature)
|
|
|
+ .setOutputCol(stringTypeFeature + "_index")
|
|
|
+ .fit(trainSet)
|
|
|
+ })
|
|
|
+
|
|
|
+ if(stringTypeFeatures.nonEmpty){ //若存在字符类型特征
|
|
|
+ log.info("需要进行字符型类型转换")
|
|
|
+ log.info("待转换字符型特征有"+stringTypeFeatures.length+"个:")
|
|
|
+ log.info(stringTypeFeatures.toString())
|
|
|
+
|
|
|
+ log.info("构建pipeline进行转换")
|
|
|
+ val pipeline = new Pipeline().setStages(stringTypeFeatureIndexers.toArray)
|
|
|
+ val pipelineModel : PipelineModel = pipeline.fit(trainSet)
|
|
|
+
|
|
|
+ val pipelineUri = getStringTypeFeatureTransformerUri(modelId)
|
|
|
+ log.info("转换模型适配完成,正在保存到服务器,保存地址:"+pipelineUri)
|
|
|
+ pipelineModel.write.overwrite().save(pipelineUri)
|
|
|
+
|
|
|
+ trainSet = pipelineModel.transform(trainSet)
|
|
|
+ }
|
|
|
+
|
|
|
+ //训练集-去除非数值的特征
|
|
|
+ trainSet = trainSet.drop(stringTypeFeatures:_*)
|
|
|
+
|
|
|
+ log.info("训练集-去除非数值的特征:")
|
|
|
+ trainSet.show(5)
|
|
|
+
|
|
|
+
|
|
|
+ //找到为string类型的标签(0个或者1个)
|
|
|
+ val stringLabel = trainSet.schema.filter(_.name.equals(label)).filter(_.dataType.equals(StringType)).map(_.name)
|
|
|
+ if(stringLabel.nonEmpty){ //需要转换,将label的转换方法持久化
|
|
|
+ val labelStringIndexer = new StringIndexer().setInputCol(label).setOutputCol("label").fit(trainSet)
|
|
|
+ val labelStringIndexerUri = getLabelStringIndexerUri(modelId)
|
|
|
+ labelStringIndexer.write.overwrite().save(labelStringIndexerUri)
|
|
|
+ trainSet = labelStringIndexer.transform(trainSet)
|
|
|
+ log.info("label转换模型已经存储到远端,uri:"+labelStringIndexerUri)
|
|
|
+ trainSet.show(5)
|
|
|
+ }
|
|
|
+
|
|
|
+ val columns = trainSet.columns
|
|
|
+
|
|
|
+ //过滤标签,取出特征列表
|
|
|
+ val featureList:Array[String] = columns.filter(columnName => (!columnName.equals(label) && !columnName.equals("label")))
|
|
|
+
|
|
|
+ //将特征列表合成向量
|
|
|
+ val assembler = new VectorAssembler()
|
|
|
+ .setInputCols(featureList)
|
|
|
+ .setOutputCol("features")
|
|
|
+
|
|
|
+ // 若label是字符串类型,那么将选择新的数值型的label列
|
|
|
+ var selectedLabel= label
|
|
|
+ if(stringLabel.nonEmpty){
|
|
|
+ selectedLabel = "label"
|
|
|
+ }
|
|
|
+
|
|
|
+ val assembledData = assembler.transform(trainSet)
|
|
|
+ .select(selectedLabel,"features")
|
|
|
+ .toDF("label","features") //重命名
|
|
|
+
|
|
|
+ log.info("写入libsvm的数据格式如下:")
|
|
|
+ assembledData.show(5)
|
|
|
+
|
|
|
+ val libsvmFileUri = getFileLibsvmUri(fileId)
|
|
|
+
|
|
|
+
|
|
|
+// implicit val encoder = org.apache.spark.sql.Encoders.STRING
|
|
|
+
|
|
|
+
|
|
|
+ assembledData.write.mode(SaveMode.Overwrite) //没有上面那句声明,无法写入成功
|
|
|
+ .format("libsvm")
|
|
|
+ .save(libsvmFileUri)
|
|
|
+
|
|
|
+ log.info("libsvm写入,uri: "+libsvmFileUri)
|
|
|
+
|
|
|
+ return libsvmFileUri
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ def processTestDataSet(): String = {
|
|
|
+
|
|
|
+
|
|
|
+ "libsvmFileUri"
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|