pyspark 数据写入hive_用Pyspark进行机器学习

本文介绍如何利用Pyspark进行机器学习,通过宝石数据集展示数据读取、预处理、特征工程和随机森林模型训练。Pyspark提供大数据处理功能,支持数据清洗和转换操作,类似pandas但适用于大规模数据。文中详细讲解了数据转换、特征处理、模型训练和超参数调优,展示了随机森林模型的特征重要性。
摘要由CSDN通过智能技术生成

永远扯紧欢跳的琴弦,不必去看那无字的白纸。

-- 史铁生《命若琴弦》

刚开始用Python进行机器学习时,一般会在自己的小电脑上进行一些小项目的训练,此时的方案一般是pandas+sklearn,其中pandas主要做来前期的数据清洗和处理,sklearn主要用来进行特征工程和建模,当然有的一些模型sklearn中并没有封装,如xgboostlightgbm,此时需要单独安装进行使用。总的来说,当数据量比较小时,在本地依靠pandas+sklearn+lightgbm就可以完成建模需求。

但是,当数据比较大时,自己的小本本放不下时,就很难在本地做了,此时有两个选择,第一是借助于深度学习的模型和框架如Tensorflow/Pytorch等,并且借助GPU加速等分布式方法进行大规模数据的建模和处理,另外一种方式是借助一些大数据框架,如spark进行数据挖掘,spark中封装了ml库用来进行大数据量的机器学习建模,其风格和sklearn很相似,而且spark本身对数据框类型的数据dataframe提供了非常丰富且灵活的操作函数,可以完成对大数据的清洗和处理。

本文就以spark为例,整理一下用spark进行机器学习的代码,spark本身支持JavaPythonScalaR四种语言,这里就用Python,也即Pyspark

这里使用的数据是一份宝石数据,大约5万多行,提供了关于宝石的一些特征,如价格,颜色,透明度和品质等。其基本情况如下图。

379dcfcdc4bfad657c14da6acb43c5e0.png

我们就以预测cut这一列为目标进行这个小项目的展示吧。

首先导入相关的库,并进行数据读取

 1from pyspark.sql import SparkSession
2from pyspark.sql import SQLContext, HiveContext
3from pyspark import SparkContext
4from pyspark.sql.functions import *
5from pyspark.sql.types import *
6import pyspark.sql.functions as F
7
8from pyspark.ml.feature import ChiSqSelector
9from pyspark.ml.linalg import Vectors
10from pyspark.ml import Pipeline
11from pyspark.ml.classification import GBTClassifier
12from pyspark.ml.classification import RandomForestClassifier
13from pyspark.ml.feature import MinMaxScaler,MaxAbsScaler,StringIndexer, VectorIndexer,VectorAssembler
14from pyspark.ml.evaluation import MulticlassClassificationEvaluator
15from pyspark.ml.evaluation import BinaryClassificationEvaluator
16import pyspark.ml.feature as ft
17import pyspark.ml.evaluation as ev
18import pyspark.ml.tuning as tune
19
20import numpy as np
21import pandas as pd
22import time
23import seaborn as sns
24import matplotlib.pyplot as plt
25
26
27
28spark = SparkSession.builder.appName("pyspark_test").enableHiveSupport().getOrCreate()
29
30
31## 读取数据,既可以读取本地的csv等数据,也可以读取HDFS上的Hive表
32
33spark_df = spark.read.csv('./data/diamonds.csv',inferSchema=True,header=True)
34# 只取一部分数据来进行展示,否则小本本扛不住~
35spark_df,_ =spark_df.randomSplit([0.05,0.95])
36
37
38"""39# 还可以使用pandas方式读取本地csv,转换pandas dataframe为spark dataframe。40import pandas as pd41from pyspark import SparkContext42from pyspark.sql import SQLContext43pandas_df = pd.read_csv('./data/diamonds.csv')44sc = SparkContext()45sqlContest = SQLContext(sc)46df = sqlContest.createDataFrame(pandas_df)4748# hive数据库读取49spark.sql('select * from hive_table')5051"""

可以对数据进行一些基本的描述和做一些简单的统计操作:

 1# **基础描述**
2spark_df.count()  # 行数
3spark_df.columns  # 列名称
4spark_df.printSchema()  # 结构及属性显示
5spark_df.show(5)  # 显示前5行
6spark_df.describe().show()  # 均值/最值等描述
7
8# **dataframe操作**
9# 取'age','mobile'两列
10spark_df.select("carat","color").show(5)
11# 新增一列:x+y+z
12spark_df.withColumn("x_y_z",(spark_df["x"]+spark_df["y"]+spark_df["z"])).show(10,False)
13# 新建一列x_double,将x转换为double属性
14spark_df.withColumn("x_double",spark_df["x"].cast(DoubleType())).show(10,False)
15# 筛选
16spark_df.filter(spark_df["depth"]>=60).show()
17spark_df.filter(spark_df["depth"]>=spark_df["table"]).select('x','y','depth','table').show()
18spark_df.filter(spark_df['depth']<58).filter(spark_df['cut'] =="Ideal").show()
19# 去重统计
20spark_df.select('cut').distinct().show()
21# 行去重
22spark_df=spark_df.dropDuplicates()
23# 删除列
24df_new=spark_df.drop('x_y_z')
25# groupby操作
26spark_df.groupBy('color').count().show(5,False)
27spark_df.groupBy('cut').count().orderBy('count',ascending=False).show(5,False)
28spark_df.groupBy('color').agg({'price':'sum'}).show(5,False)  # 根据color分区,计算price的sum

更多的关于dataframe的操作,可以参考:https://blog.csdn.net/sinat_26917383/article/details/80500349,spark基本上就是大数据版的pandas,可以实现丰富而灵活的操作。

下面展示udf的用法,其实udf的功能和map算子基本差不多,都是能对某一列进行某种变换,这里写一下label的转换函数,把原来的cut中的5个品质转化为2个,即最经典的二分类。

 1def label_transform(cut):
2    if cut in ["Good","Very Good"]:
3        return "High"
4    else:
5        return "Low"
6# 用Python函数来创建udf,并且指定输出为string格式,
7# StringType()来自pyspark.sql.types
8myudf=udf(label_transform,StringType())  
9
10# 利用udf进行label转换
11spark_df = spark_df.withColumn("label",myudf(spark_df['cut']))
12spark_df = spark_df.drop('cut')

下面是另外两个udf的例子

1# **another udf的例子**
2# 使用lamba创建udf
3price_udf = udf(lambda price: "high_price" if price >= 330 else "low_price", StringType())  # using lambda function
4# 新建一列price_group
5spark_df = spark_df.withColumn("price_group", price_udf(spark_df["price"]))

一般来说,如果需要对某一列进行某种变换而生成另外新的一列时,就可以使用udf来定制自己的变换函数,而且这种变换都是map类型操作,即不能涉及聚合操作,如果需要涉及聚合操作,则需要定义udaf,而udaf的使用有点麻烦。
关于udfudaf可以参考:
https://www.jianshu.com/p/b1e9d5cc6193
https://www.cnblogs.com/wdmx/p/10156500.html

下面进行特征处理, pyspark.ml.feature提供的特征处理功能,满足了大部分机器学习的特征处理需求。

这里进行两个基本操作,把数值型的特征进行归一化,而对字符型特征进行label编码。

 1# 归一化函数,将列标准化到[0,1]之间
2
3num_cols =["carat","depth","table","price","x","y","z"]
4
5cate_cols = ["color","clarity","label","price_group"]
6
7# 批量地对数值的特征进行归一化
8for num_col in num_cols:
9    # 用于将多个列合并为一个向量列,直接transform即可,经常用的
10    # numsAssembler = VectorAssembler(inputCols=num_cols, outputCol="num_features")
11    # spark_df = numsAssembler.transform(spark_df)
12    # 要用VectorAssembler转成Dense vector
13    numsAssembler = VectorAssembler(inputCols=[num_col], outputCol=num_col+"_ass")
14    spark_df = numsAssembler.transform(spark_df)
15    mmScaler = MinMaxScaler(inputCol=num_col+"_ass", outputCol=num_col+"_scale")
16    scalemodel = mmScaler.fit(spark_df)
17    spark_df = scalemodel.transform(spark_df)
18
19# 这里的代码用到了for循环,似乎有些丑,但是没有想到更好的方法
20# 当然可以像下面一样使用map算子,不过也很麻烦
21# 而udf的方法和map也没有太大差别
22# 借助于map的代码如下,注意使用map时需要把dataframe转为rdd,map之后再转回来
23# 关于rdd和dataframe的相互转换,
24# 可以参考:https://www.jianshu.com/p/70f8c78a3fc9
25"""26# 首先计算出所有数值列的最大值和最小值,待后面使用27cols_max = []28cols_min = []29for col in num_cols:30    cols_max.append(spark_df.agg(F.max(col)).toPandas().iloc[0,0])31    cols_min.append(spark_df.agg(F.min(col)).toPandas().iloc[0,0])3233spark_df_rdd = spark_df.rdd.map(lambda row:(float((row[0]-cols_min[0])/(cols_max[0]-cols_min[0])),34                                            row[1],row[2],35                                            float((row[3] - cols_min[1]) / (cols_max[1] - cols_min[1])),36                                            float((row[4] - cols_min[2]) / (cols_max[2] - cols_min[2])),37                                            float((row[5] - cols_min[3]) / (cols_max[3] - cols_min[3])),38                                            float((row[6] - cols_min[4]) / (cols_max[4] - cols_min[4])),39                                            float((row[7] - cols_min[5]) / (cols_max[5] - cols_min[5])),40                                            float((row[8] - cols_min[6]) / (cols_max[6] - cols_min[6])),41                                            row[9],row[10]42                                             ))43fields = [("carat_scale", FloatType()),  44          ("color", StringType()), 45          ("clarity", StringType()),46          ("depth_scale", FloatType()),47          ("table_scale",FloatType()), 48          ("price_scale", FloatType()),49          ("x_scale", FloatType()),50          ("y_scale", FloatType()), 51          ("z_scale", FloatType()),52          ("label", StringType()),53          ("price_group", StringType())]54schema = StructType([StructField(e[0], e[1], True) for e in fields])55spark_df = spark.createDataFrame(spark_df_rdd,schema)5657spark_df.show()5859# 这种方法虽然不用for,但是也很麻烦,当列数一多尤其麻烦,所以还是用for吧 -_-||,60# 起码for可以提前把需要变换的列拿出来然后一块搞了,而且不用自己写函数61# 这里的map其实和udf一样,所以可以提前定义好每一列的归一化的udf,然后新增归一化的列即可,但是也很麻烦。6263"""
64
65# 批量地,针对单个类别型特征进行转换,把字符串的列按照出现频率进行排序
66for cate_col in cate_cols:
67    stringIndexer = StringIndexer(inputCol=cate_col,outputCol=cate_col+"_index")
68    stringmodel = stringIndexer.fit(spark_df)
69    spark_df = stringmodel.transform(spark_df)
70
71num_cols_out = [x+"_scale" for x in num_cols]
72cate_cols_out = [c+"_index" for c in cate_cols if c != 'label']
73all_cols = num_cols_out+cate_cols_out
74# 把全部特征合并
75featureAssembler = VectorAssembler(inputCols=all_cols, outputCol="features")
76spark_df = featureAssembler.transform(spark_df)

下面进行数据集的划分、建模、和超参数网格寻优,这里就使用随机森林,然后再把随机森林的特征重要性画出来便于我们了解哪些特征比较重要。首先定义好一些需要的函数,分别是获得测试集上的指标,获得网格搜索的参数对应的指标和画特征重要性。

 1def get_test_result(prediction_df):
2    TP = prediction_df.filter('label==1 and prediction==1').count()
3    TPTN = prediction_df.filter('(label==1 and prediction==1) or (label==0 and prediction==0)').count()
4    TPFP = prediction_df.filter('prediction==1').count()
5    TPFN = prediction_df.filter('label==1').count()
6
7    val_precision = TP / TPFP
8    val_recall = TP / TPFN
9    val_accuracy = TPTN / prediction_df.count()
10    val_f1 = 2*val_precision * val_recall / (val_precision + val_recall)
11
12    evaluator = BinaryClassificationEvaluator(metricName="areaUnderPR")
13    val_areaUnderPR = evaluator.evaluate(prediction_df)
14    evaluator = BinaryClassificationEvaluator()
15    val_areaUnderRoc = evaluator.evaluate(prediction_df)
16
17
18    print('val_precision: {} , \n'
19          'val_recall: {} ,\n '
20          'val_total_accuracy: {} ,\n'
21          ' val_f1_score: {} ,\n '
22          'val_areaUnderPR: {} ,\n '
23          'val_AUC: {} \n'.format(val_precision, val_recall,
24                                    val_accuracy, val_f1,
25                                  val_areaUnderPR, val_areaUnderRoc))
26
27
28
29def get_best_cv_par(cvModel):
30    # 查看最佳模型参数
31    param_maps = cvModel.getEstimatorParamMaps()
32    eval_metrics = cvModel.avgMetrics
33
34    param_res = []
35
36    for params, metric in zip(param_maps, eval_metrics):
37        param_metric = {}
38        for key, param_val in zip(params.keys(), params.values()):
39            param_metric[key.name]=param_val
40        param_res.append((param_metric, metric))
41    res = sorted(param_res, key=lambda x:x[1], reverse=True)
42    for r in res:
43        print(r)
44        print("\n")
45    return res
46
47
48def plot_feature_importance(rf_classifier):
49    FI = pd.Series(rf_classifier.featureImportances, index=all_cols)  # pySpark
50    # FI = pd.Series(rf.feature_importances_,index = featureArray) # sklearn
51    FI = FI.sort_values(ascending=False)
52    plt.rcParams['figure.figsize'] = (15.0, 8.0)  # 尺寸
53    plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
54    plt.bar(FI.index, FI.values, color="blue")
55    plt.xlabel('features')
56    plt.xticks(rotation = -30)
57    plt.ylabel('importance')
58    plt.title("features importance")
59    plt.show()

然后定义模型,开始训练,这里把比较重要的两个超参数numTreesmaxDepth进行网格搜索(这里运行时间可能较长)。

 1# 模型构建加网格搜索
2print("开始k-fold网格搜索并且训练=====>")
3rf_classifier=RandomForestClassifier(labelCol='label')
4
5grid = tune.ParamGridBuilder()\
6    .addGrid(rf_classifier.numTrees, [3,10,20])\
7    .addGrid(rf_classifier.maxDepth, [3,10,20])\
8    .build()
9evaluator = BinaryClassificationEvaluator()
10# 使用K-Fold交叉验证评估各种参数的模型
11cv = tune.CrossValidator(
12    estimator=rf_classifier,
13    estimatorParamMaps=grid,
14    evaluator=evaluator,
15    numFolds=2
16)
17
18# 数据集划分
19from pyspark.sql.functions import col
20model_df=spark_df.select(col("features"),col('label_index').alias("label"))
21train_df,test_df=model_df.randomSplit([0.7,0.3])
22
23# cvModel 返回估计的最佳模型
24cvModel = cv.fit(train_df)
25best_cv_par = get_best_cv_par(cvModel)
26
27# 以最优的参数再进行一次训练,否则直接从cvModel里面拿不到特征重要性
28rf_classifier_best=RandomForestClassifier(labelCol='label',
29                                          numTrees = best_cv_par[0][0]["numTrees"],
30                                            maxDepth=best_cv_par[0][0]["maxDepth"]).fit(train_df)
31
32
33# 以最佳模型进行预测
34print("开始预测=====>")
35rf_predictions = rf_classifier_best.transform(test_df)
36rf_predictions.show(5)
37get_test_result(rf_predictions)
38
39# 查看特征重要性
40print("特征重要性===>>>\n",rf_classifier_best.featureImportances)  # 各个特征的权重
41plot_feature_importance(rf_classifier_best)

关于超参数搜索和特征重要性,可参考:
https://www.jianshu.com/p/4d7003182398
https://blog.csdn.net/sinat_36226553/article/details/104129182

可以看到寻参中的每组参数对应的AUC的值。

ea9b3b79d52e3f43889e72ecfb63f14f.png

还可以看到特征重要性的结果:

63c092057c81a4eaf7dc2488a2edd537.png

这说明宝石的depthtableclarity等特征在决定一个宝石的品质时有着很重要的作用。
最后把训练好的模型进行保存即可。

1# 模型保存
2rf_classifier_best.write().overwrite().save("./data/RF_model")
3print("模型保存完毕")

在浏览器中http://localhost:4040/jobs/可以查看sparkUI界面,里面可以看到任务的运行情况,如

a5efe0744422c9fbbbdc1f76ab824643.png

4b7e84771464bde2ab111657f2ae8aa9.png

以上就是用pyspark进行机器学习的一个小例子,
完整的项目在:
https://github.com/ybm1/Pyspark_learn。

如果更多需求可以自行深入学习。

北交大统计学硕士

会点Python/R,cpp在路上

机器学习工程师/数据分析师修炼中

主攻广告和推荐方向

也爱看点文学、心理和宗教的东西

无可无不可,天下皆通途

5bc0e707e20c51b1be6127cf6bde9a51.png

长按扫码关注

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值