数据挖掘工具---spark使用练习---ml(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qingqing7/article/details/80935303

Spark中ml和mllib的区别

来源:
Spark中ml和mllib的主要区别和联系如下:

  • ml和mllib都是Spark中的机器学习库,目前常用的机器学习功能2个库都能满足需求。
  • spark官方推荐使用ml, 因为ml功能更全面更灵活,未来会主要支持ml,mllib很有可能会被废弃(据说可能是在spark3.0中deprecated)。
  • ml主要操作的是DataFrame, 而mllib操作的是RDD,也就是说二者面向的数据集不一样。相比于mllib在RDD提供的基础操作,ml在DataFrame上的抽象级别更高,数据和操作耦合度更低。
  • DataFrame和RDD什么关系?DataFrame是Dataset的子集,也就是Dataset[Row], 而DataSet是对RDD的封装,对SQL之类的操作做了很多优化。
  • 相比于mllib在RDD提供的基础操作,ml在DataFrame上的抽象级别更高,数据和操作耦合度更低。
  • ml中的操作可以使用pipeline, 跟sklearn一样,可以把很多操作(算法/特征提取/特征转换)以管道的形式串起来,然后让数据在这个管道中流动。大家可以脑补一下Linux管道在做任务组合时有多么方便。
  • ml中无论是什么模型,都提供了统一的算法操作接口,比如模型训练都是fit;不像mllib中不同模型会有各种各样的trainXXX。
  • mllib在spark2.0之后进入维护状态, 这个状态通常只修复BUG不增加新功能。

以上就是ml和mllib的主要异同点。下面是ml和mllib逻辑回归的例子,可以对比看一下, 虽然都是模型训练和预测,但是画风很不一样。

Spark ML中的机器学习

PySpark.ML模块介绍

来源:2018.01.13 13:59
参考地址:spark.apache.org/docs/latest/api/python/pyspark.ml.html
pyspark.ml package

ML模块包括机器学习三个核心功能:

(1)数据准备: 特征提取、变换、选择、分类特征的散列和自然语言处理等等;
(2)机器学习方法: 实现了一些流行和高级的回归,分类和聚类算法;
(3)实用程序: 统计方法,如描述性统计、卡方检验、线性代数(稀疏稠密矩阵和向量)和模型评估方法。

目前ML模块还处于不断发展中,但是已经可以满足我们的基础数据科学任务。
注意:官网上,标注“E”为测试阶段,不稳定,可能会产生错误失败

ML模块三个抽象类:

转换器(Transformer)、评估器(Estimator)和管道(Pipeline)

数据准备

数据查看

df.describe().show()
数据的查看和转换操作很多都可以利用pyspark.sql下的api接口来实现,比如一般数据预处理时候,我们会用pyspark.ml.feature module中的接口来实现,但如果不能满足,其实pyspark.sql.functions接口可能实现更多的变换。我们只要先对某一列变换,然后再加回到df.就好比在sklearn中我们会优先使用sklearn.preprocessing来完成预处理,但如果不能够满足自己的要求,我个人会使用map函数来做变换。

数据类型的转换

首先是在读入数据时,可以根据实际情形,设置inferschema=True,如

df = spark.read.csv(path='/tmp/test/hour.csv', header=True, sep=',',inferSchema=True)

这样会自动判断类型,否则会部为string类型。
但是自动识别类型,有时还是不能满足需求,就需要人为转换,目前还没找到方法,不过应该是可以的。

缺失值处理

缺失值处理方法

或者参考
查看各列有无缺失值
在sklearn中查看缺失值可以利用pandas.DataFrame的一些特性,比如df[‘XX’].value_counts()函数来查看各个值的情况,对于离散型数据较好用,会详细列出有哪些值,每个值的个数,因为数据来源我们是不知道,不同的数据来源对缺失值的表示方法可能不同,比如用’'表示,用null表示,用NaN表示;或者利用df.describe()或df.info()来查看。但是df.describe()只适用于缺失值用标准的NaN表示的时候,df.info()在缺失值用标准的NaN表示时会将缺失值排除在外进行基本的统计,因为我们也可以发现缺失值的情况,但如果存在一些其他方法的表示,就会造成不同类型数据混合而无法得到统计结果,我们就需要进一步的查看了。如下所示

a={'a':[1,'',2],'b':[3,4,float('NaN')],'c':[5,6,'Null']}
c=pd.DataFrame(a)
# print(c)
"""
    a    b     c
0  1  3.0     5
1     4.0     6
2  2  NaN  Null
"""
# print(c['a'].value_counts())
"""
2    1
1    1
     1
Name: a, dtype: int64
"""
# print(c['b'].value_counts())
"""
4.0    1
3.0    1
Name: b, dtype: int64
"""
# print(c['c'].value_counts())
"""
Null    1
6       1
5       1
Name: c, dtype: int64
"""
# print(c.info())
"""
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 3 columns):
a    3 non-null object
b    2 non-null float64
c    3 non-null object
dtypes: float64(1), object(2)
memory usage: 152.0+ bytes
None
"""
# print(c.describe())
"""
             b
count  2.000000
mean   3.500000
std    0.707107
min    3.000000
25%    3.250000
50%    3.500000
75%    3.750000
max    4.000000
"""

但pyspark中查看缺失值并没有太好的方法,

for i in range(len(fieldIndex)):
    df.filter('%s is null'%(fieldIndex[i])).select(fieldIndex[i]).limit(10).show()

利用pyspark.sql.DataFrame的接口
利用类似下面的语句进行缺少值填充

df.na.fill({'age': 50, 'name': 'unknown'})

具体填充的值,可用类似下面的语句获取。

#mean方法只接受数值类型的参数,Int, Long等,如果是String, Date, Timestamp 类型的话要用agg(mean(“b”))
meanTemp=df.agg({'temp': 'avg'}).collect()[0][0]
print(meanTemp)
for i in range(len(fieldIndex)):
    meanTemp = df.agg({fieldIndex[i]: 'avg'}).collect()[0][0]
    df.na.fill({fieldIndex[i]:meanTemp})

也可以用pyspark.ml.feature.Imputer(*args, **kwargs)来实现缺失值的填充
类似于

imputer=Imputer(strategy='mean',inputCols=['atemp'],outputCols=['out_atemp'])
model=imputer.fit(df)
df=model.transform(df)

转换器

pyspark.ml.Transformer
通常通过将一个新列附加到DataFrame来转换数据。
当从转换器的抽象类派生时,每个新的转换器类需要实现.transform()方法,该方法要求传递一个要被转换的DataFrame,该参数通常是第一个也是唯一的一个强制性参数。

transform(dataset, params=None)
          Transforms the input dataset with optional parameters(使用可选参数转换输入数据集。).      
Parameters: 
**dataset** – input dataset, which is an instance of pyspark.sql.DataFrame
**params** – an optional param map that overrides embedded params.
Returns:    transformed dataset

pyspark.ml.feature模块提供了许多的转换器:

  • pyspark.ml.feature.Binarizer(self, threshold=0.0, inputCol=None, outputCol=None)
    根据指定的阈值将连续变量转换为对应的二进制
#-*-coding:utf-8-*-

from pyspark import SparkContext
from pyspark.sql import SparkSession
from pyspark.ml.feature import Binarizer


if __name__=="__main__":
    sc=SparkContext(appName="myApp")
    spark=SparkSession.builder.enableHiveSupport().getOrCreate()
    df=spark.createDataFrame([(0.5,)],["values"])#注意0.5后面有个逗号,不加会出错,不知道为什么要这样;
    """
    df.show()
    +------+
    | values |
    +------+
    | 0.5 |
    +------+
    """
    binarizer=Binarizer(threshold=1.0,inputCol="values",outputCol="features")
    bd=binarizer.transform(df)
    """
    bd.show()
    +------+-------+
    | values | features |
    +------+-------+
    | 0.5 | 0.0 |
    +------+-------+
    print(bd.head().features)
    0.0
    """
    binarizer.setParams(outputCol="freqs")
    bdf=binarizer.transform(df)
    """
    bdf.show()
    +------+-----+
    | values | freqs |
    +------+-----+
    | 0.5 | 0.0 |
    +------+-----+
    """
    params={binarizer.threshold:-0.5,binarizer.outputCol:"vectors"}
    bdf=binarizer.transform(df,params=params)
    """
    bdf.show()
    +------+-------+
    | values | vectors |
    +------+-------+
    | 0.5 | 1.0 |
    +------+-------+
    """
    temp_path="/tmp/test/"
    binarizerPath ="%sbinarizer"%(temp_path)
    binarizer.save(binarizerPath)
    loadBinarizer=Binarizer.load(binarizerPath)
    # print(loadBinarizer.getThreshold()==binarizer.getThreshold())
    # True
  • pyspark.ml.feature.Bucketizer(self, splits=None, inputCol=None, outputCol=None, handleInvalid="error")
    与Binarizer类似,该方法根据阈值列表(分割的参数),将连续变量转换为多项值(连续变量离散化到指定的范围区间)
#-*-coding:utf-8-*-

from pyspark import SparkContext
from pyspark.sql import SparkSession
from pyspark.ml.feature import Bucketizer
import numpy as np

if __name__=="__main__":
    sc=SparkContext(appName="myApp")
    spark=SparkSession.builder.getOrCreate()
    values=[(0.1,),(0.4,),(1.2,),(1.5,),(np.nan,),(np.nan,)]
    # values = [(0.1,), (0.4,), (1.2,), (1.5,), (float("nan"),), (float("nan"),)]
    df=spark.createDataFrame(values,["values"])
    """
    df.show()
    +------+
    |values|
    +------+
    |   0.1|
    |   0.4|
    |   1.2|
    |   1.5|
    |   NaN|
    |   NaN|
    +------+
    """
    bucketizer=Bucketizer(splits=[-float("inf"),0.5,1.4,float("inf")],inputCol="values",outputCol="buckets",handleInvalid="keep")
    bdf=bucketizer.transform(df)
    """
    bdf.show()
    +------+-------+
    | values | buckets |
    +------+-------+
    | 0.1 | 0.0 |
    | 0.4 | 0.0 |
    | 1.2 | 1.0 |
    | 1.5 | 2.0 |
    | NaN | 3.0 |
    | NaN | 3.0 |
    print(bdf.head().buckets)
    0.0
    print(bdf.collect()[0].buckets)
    0.0
    print(bdf.collect()[3].buckets)
    2.0
    """
    bucketizer.setParams(outputCol="b")
    bdf=bucketizer.transform(df)
    """
    bdf.show()
    +------+---+
    | values | b |
    +------+---+
    | 0.1 | 0.0 |
    | 0.4 | 0.0 |
    | 1.2 | 1.0 |
    | 1.5 | 2.0 |
    | NaN | 3.0 |
    | NaN | 3.0 |
    +------+---+
    """
    temp_path = "/tmp/test/"
    bucketizerPath ="%sbucketizer"%(temp_path)
    bucketizer.save(bucketizerPath)
    loadBucketizer=Bucketizer.load(bucketizerPath)
    # print(loadBucketizer.getSplits()==bucketizer.getSplits() )
    # True
  • pyspark.ml.feature.ChiSqSelector(self, numTopFeatures=50, featuresCol="features", outputCol=None, labelCol="label", selectorType="numTopFeatures", percentile=0.1, fpr=0.05, fdr=0.05, fwe=0.05)
    对于分类目标变量(思考分类模型),此功能允许你选择预定义数量的特征(由numTopFeatures参数进行参数化),以便最好地说明目标的变化。该方法需要两部:需要.fit()——可以计算卡方检验,调用.fit()方法,将DataFrame作为参数传入返回一个ChiSqSelectorModel对象,然后可以使用该对象的.transform()方法来转换DataFrame。默认情况下,选择方法是numTopFeatures,默认顶级要素数设置为50。
if __name__=="__main__":
    sc=SparkContext(appName="myApp")
    spark=SparkSession.builder.getOrCreate()
    df=spark.createDataFrame([(Vectors.dense([0.0, 0.0, 18.0, 1.0]),1.0),(Vectors.dense([0.0, 1.0, 12.0, 0.0]),0.0),(Vectors.dense([1.0, 0.0, 15.0, 0.1]),0.0)],["features","label"])
    """
    df.show()
    +------------------+-----+
    | features | label |
    +------------------+-----+
    | [0.0, 0.0, 18.0, 1.0] | 1.0 |
    | [0.0, 1.0, 12.0, 0.0] | 0.0 |
    | [1.0, 0.0, 15.0, 0.1] | 0.0 |
    +------------------+-----+
    """
    chiSqSelector=ChiSqSelector(numTopFeatures=1,outputCol="selectedFeatures")
    model=chiSqSelector.fit(df)
    # print(model.selectedFeatures)
    # [2]
    cdf=model.transform(df)
    """
    cdf.show()
    +------------------+-----+----------------+
    | features | label | selectedFeatures |
    +------------------+-----+----------------+
    | [0.0, 0.0, 18.0, 1.0] | 1.0 | [18.0] |
    | [0.0, 1.0, 12.0, 0.0] | 0.0 | [12.0] |
    | [1.0, 0.0, 15.0, 0.1] | 0.0 | [15.0] |
    +------------------+-----+----------------+
    """
  • pyspark.ml.feature.CountVectorizer(self, minTF=1.0, minDF=1.0, vocabSize=1 << 18, binary=False, inputCol=None, outputCol=None)
    用于标记文本
>>> df = spark.createDataFrame(
...    [(0, ["a", "b", "c"]), (1, ["a", "b", "b", "c", "a"])],
...    ["label", "raw"])
>>> cv = CountVectorizer(inputCol="raw", outputCol="vectors")
>>> model = cv.fit(df)
>>> model.transform(df).show(truncate=False)
+-----+---------------+-------------------------+
|label|raw            |vectors                  |
+-----+---------------+-------------------------+
|0    |[a, b, c]      |(3,[0,1,2],[1.0,1.0,1.0])|
|1    |[a, b, b, c, a]|(3,[0,1,2],[2.0,2.0,1.0])|
+-----+---------------+-------------------------+
...
>>> sorted(model.vocabulary) == ['a', 'b', 'c']
True
>>> countVectorizerPath = temp_path + "/count-vectorizer"
>>> cv.save(countVectorizerPath)
>>> loadedCv = CountVectorizer.load(countVectorizerPath)
>>> loadedCv.getMinDF() == cv.getMinDF()
True
>>> loadedCv.getMinTF() == cv.getMinTF()
True
>>> loadedCv.getVocabSize() == cv.getVocabSize()
True
>>> modelPath = temp_path + "/count-vectorizer-model"
>>> model.save(modelPath)
>>> loadedModel = CountVectorizerModel.load(modelPath)
>>> loadedModel.vocabulary == model.vocabulary
True
  • pyspark.ml.feature.Imputer(*args, **kwargs)
    用于完成缺失值的插补估计器,使用缺失值所在列的平均值或中值。 输入列应该是DoubleType或FloatType。 目前的Imputer不支持分类特征,可能会为分类特征创建不正确的值。
    请注意,平均值/中值是在过滤出缺失值之后计算的。 输入列中的所有Null值都被视为缺失,所以也被归类。 为了计算中位数,使用pyspark.sql.DataFrame.approxQuantile(),相对误差为0.001。
#-*-coding:utf-8-*-

from pyspark import SparkContext
from pyspark.sql import SparkSession
from pyspark.ml.feature import Imputer
from pyspark.ml.feature import ImputerModel

if __name__=="__main__":
    sc=SparkContext(appName="myApp")
    spark=SparkSession.builder.getOrCreate()
    df=spark.createDataFrame([(1.0,float("nan")),(2.0,float("nan")),(float("nan"),3.0),(4.0,4.0),(5.0,5.0)],["a","b"])
    """
    df.show()
    +---+---+
    | a | b |
    +---+---+
    | 1.0 | NaN |
    | 2.0 | NaN |
    | NaN | 3.0 |
    | 4.0 | 4.0 |
    | 5.0 | 5.0 |
    +---+---+
    """
    imputer=Imputer(inputCols=["a","b"],outputCols=["out_a","out_b"])
    model=imputer.fit(df)
    """
    model.surrogateDF.show()
    #默认情况下,就是取出了ab两列的均值
    +---+---+
    | a | b |
    +---+---+
    | 3.0 | 4.0 |
    +---+---+
    """
    idf=model.transform(df)
    """
    idf.show()
    +---+---+-----+-----+
    | a | b | out_a | out_b |
    +---+---+-----+-----+
    | 1.0 | NaN | 1.0 | 4.0 |
    | 2.0 | NaN | 2.0 | 4.0 |
    | NaN | 3.0 | 3.0 | 3.0 |
    | 4.0 | 4.0 | 4.0 | 4.0 |
    | 5.0 | 5.0 | 5.0 | 5.0 |
    +---+---+-----+-----+
    """
    imputer.setStrategy("median").setMissingValue(1.0)
    model=imputer.fit(df)
    idf=model.transform(df)
    """
    idf.show()
    +---+---+-----+-----+
    | a | b | out_a | out_b |
    +---+---+-----+-----+
    | 1.0 | NaN | 4.0 | NaN |
    | 2.0 | NaN | 2.0 | NaN |
    | NaN | 3.0 | NaN | 3.0 |
    | 4.0 | 4.0 | 4.0 | 4.0 |
    | 5.0 | 5.0 | 5.0 | 5.0 |
    +---+---+-----+-----+
    """
    temp_path="/tmp/test/"
    imputer_path="%simputer"%(temp_path)
    imputer.save(imputer_path)
    loadImputer=Imputer.load(imputer_path)
    # print(loadImputer.getStrategy()==imputer.getStrategy())
    # True
    model_path="%simputermodel"%(temp_path)
    model.save(model_path)
    loadModel=ImputerModel.load(model_path)
    # print(loadModel.transform(df).head().out_a==model.transform(df).head().out_a)
    # True
  • pyspark.ml.feature.MaxAbsScaler(self, inputCol=None, outputCol=None)
    通过分割每个特征中的最大绝对值来单独重新缩放每个特征以范围[-1,1]。 它不会移动/居中数据,因此不会破坏任何稀疏性。
>>> from pyspark.ml.linalg import Vectors
>>> df = spark.createDataFrame([(Vectors.dense([1.0]),), (Vectors.dense([2.0]),)], ["a"])
>>> maScaler = MaxAbsScaler(inputCol="a", outputCol="scaled")
>>> model = maScaler.fit(df)
>>> model.transform(df).show()
+-----+------+
|    a|scaled|
+-----+------+
|[1.0]| [0.5]|
|[2.0]| [1.0]|
+-----+------+
...
>>> scalerPath = temp_path + "/max-abs-scaler"
>>> maScaler.save(scalerPath)
>>> loadedMAScaler = MaxAbsScaler.load(scalerPath)
>>> loadedMAScaler.getInputCol() == maScaler.getInputCol()
True
>>> loadedMAScaler.getOutputCol() == maScaler.getOutputCol()
True
>>> modelPath = temp_path + "/max-abs-scaler-model"
>>> model.save(modelPath)
>>> loadedModel = MaxAbsScalerModel.load(modelPath)
>>> loadedModel.maxAbs == model.maxAbs
True
  • pyspark.ml.feature.MinMaxScaler(self, min=0.0, max=1.0, inputCol=None, outputCol=None)
    使用列汇总统计信息,将每个特征单独重新标定为一个常用范围[min,max],这也称为最小 - 最大标准化或重新标定(注意由于零值可能会被转换为非零值,因此即使对于稀疏输入,转换器的输出也将是DenseVector)。 特征E的重新缩放的值被计算为,数据将被缩放到[0.0,1.0]范围内。
    For the case E_max == E_min, Rescaled(e_i) = 0.5 * (max + min)```
    
>>> from pyspark.ml.linalg import Vectors
>>> df = spark.createDataFrame([(Vectors.dense([0.0]),), (Vectors.dense([2.0]),)], ["a"])
>>> mmScaler = MinMaxScaler(inputCol="a", outputCol="scaled")
>>> model = mmScaler.fit(df)
>>> model.originalMin
DenseVector([0.0])
>>> model.originalMax
DenseVector([2.0])
>>> model.transform(df).show()
+-----+------+
|    a|scaled|
+-----+------+
|[0.0]| [0.0]|
|[2.0]| [1.0]|
+-----+------+
...
>>> minMaxScalerPath = temp_path + "/min-max-scaler"
>>> mmScaler.save(minMaxScalerPath)
>>> loadedMMScaler = MinMaxScaler.load(minMaxScalerPath)
>>> loadedMMScaler.getMin() == mmScaler.getMin()
True
>>> loadedMMScaler.getMax() == mmScaler.getMax()
True
>>> modelPath = temp_path + "/min-max-scaler-model"
>>> model.save(modelPath)
>>> loadedModel = MinMaxScalerModel.load(modelPath)
>>> loadedModel.originalMin == model.originalMin
True
>>> loadedModel.originalMax == model.originalMax
True
  • pyspark.ml.feature.Normalizer(self, p=2.0, inputCol=None, outputCol=None)
    使用给定的p范数标准化矢量以得到单位范数(默认为L2)。
>>> from pyspark.ml.linalg import Vectors
>>> svec = Vectors.sparse(4, {1: 4.0, 3: 3.0})
>>> df = spark.createDataFrame([(Vectors.dense([3.0, -4.0]), svec)], ["dense", "sparse"])
>>> normalizer = Normalizer(p=2.0, inputCol="dense", outputCol="features")
>>> normalizer.transform(df).head().features
DenseVector([0.6, -0.8])
>>> normalizer.setParams(inputCol="sparse", outputCol="freqs").transform(df).head().freqs
SparseVector(4, {1: 0.8, 3: 0.6})
>>> params = {normalizer.p: 1.0, normalizer.inputCol: "dense", normalizer.outputCol: "vector"}
>>> normalizer.transform(df, params).head().vector
DenseVector([0.4286, -0.5714])
>>> normalizerPath = temp_path + "/normalizer"
>>> normalizer.save(normalizerPath)
>>> loadedNormalizer = Normalizer.load(normalizerPath)
>>> loadedNormalizer.getP() == normalizer.getP()
True
  • pyspark.ml.feature.OneHotEncoder(self, dropLast=True, inputCol=None, outputCol=None)
    (分类列编码为二进制向量列)
    一个热门的编码器,将一列类别索引映射到一列二进制向量,每行至多有一个单值,表示输入类别索引。 例如,对于5个类别,输入值2.0将映射到[0.0,0.0,1.0,0.0]的输出向量。 最后一个类别默认不包含(可通过dropLast进行配置),因为它使向量条目总和为1,因此线性相关。 所以一个4.0的输入值映射到[0.0,0.0,0.0,0.0]。这与scikit-learn的OneHotEncoder不同,后者保留所有类别。 输出向量是稀疏的。
    用于将分类值转换为分类索引的StringIndexer.
>>> stringIndexer = StringIndexer(inputCol="label", outputCol="indexed")
>>> model = stringIndexer.fit(stringIndDf)
>>> td = model.transform(stringIndDf)
>>> encoder = OneHotEncoder(inputCol="indexed", outputCol="features")
>>> encoder.transform(td).head().features
SparseVector(2, {0: 1.0})
>>> encoder.setParams(outputCol="freqs").transform(td).head().freqs
SparseVector(2, {0: 1.0})
>>> params = {encoder.dropLast: False, encoder.outputCol: "test"}
>>> encoder.transform(td, params).head().test
SparseVector(3, {0: 1.0})
>>> onehotEncoderPath = temp_path + "/onehot-encoder"
>>> encoder.save(onehotEncoderPath)
>>> loadedEncoder = OneHotEncoder.load(onehotEncoderPath)
>>> loadedEncoder.getDropLast() == encoder.getDropLast()
True

OneHotEncoder可以实现对变量的哑变量化
参考

  • pyspark.ml.feature.PCA(self, k=None, inputCol=None, outputCol=None)
    PCA训练一个模型将向量投影到前k个主成分的较低维空间。
#-*-coding:utf-8-*-

from pyspark import SparkContext
from pyspark.sql import SparkSession
from pyspark.ml.feature import PCA
from pyspark.ml.feature import PCAModel
from pyspark.ml.linalg import Vectors

if __name__=="__main__":
    sc=SparkContext(appName="myApp")
    spark=SparkSession.builder.getOrCreate()
    data=[(Vectors.sparse(5,[(1,1.0),(3,7.0)]),),
          (Vectors.dense([2.0, 0.0, 3.0, 4.0, 5.0]),),
          (Vectors.dense([4.0, 0.0, 0.0, 6.0, 7.0]),)]
    df=spark.createDataFrame(data,["features"])
    """
    df.show()
    +--------------------+
    | features |
    +--------------------+
    | (5, [1, 3], [1.0, 7.0]) |
    | [2.0, 0.0, 3.0, 4.0, ... |
    | [4.0, 0.0, 0.0, 6.0, ... |
    +--------------------+
    """
    pca=PCA(k=2,inputCol="features",outputCol="pcaFeatures")
    model=pca.fit(df)
    # print(model.explainedVariance)
    # [0.794393253223, 0.205606746777]
    pcadf=model.transform(df)
    """
    pcadf.show()
    +--------------------+--------------------+
    | features | pcaFeatures |
    +--------------------+--------------------+
    | (5, [1, 3], [1.0, 7.0]) | [1.64857282308838... |
    | [2.0, 0.0, 3.0, 4.0, ... | [-4.6451043317815... |
    | [4.0, 0.0, 0.0, 6.0, ... | [-6.4288805356764... |
    +--------------------+--------------------+
    """
    temp_path="/tmp/test/"
    pca_path="%spca"%(temp_path)
    pca.save(pca_path)
    pcaload=PCA.load(pca_path)
    # print(pcaload.getK()==pca.getK())
    # True
    pcaModel_path="%spcaModel"%(temp_path)
    model.save(pcaModel_path)
    modelLoad=PCAModel.load(pcaModel_path)
    # print(modelLoad.pc==model.pc)
    # True
    # print(modelLoad.explainedVariance==model.explainedVariance)
    # True
  • pyspark.ml.feature.QuantileDiscretizer(self, numBuckets=2, inputCol=None, outputCol=None, relativeError=0.001, handleInvalid="error")
    与Bucketizer方法类似,但不是传递分隔参数,而是传递一个numBuckets参数,然后该方法通过计算数据的近似分位数来决定分隔应该是什么。
>>> values = [(0.1,), (0.4,), (1.2,), (1.5,), (float("nan"),), (float("nan"),)]
>>> df = spark.createDataFrame(values, ["values"])
>>> qds = QuantileDiscretizer(numBuckets=2,
...     inputCol="values", outputCol="buckets", relativeError=0.01, handleInvalid="error")
>>> qds.getRelativeError()
0.01
>>> bucketizer = qds.fit(df)
>>> qds.setHandleInvalid("keep").fit(df).transform(df).count()
6
>>> qds.setHandleInvalid("skip").fit(df).transform(df).count()
4
>>> splits = bucketizer.getSplits()
>>> splits[0]
-inf
>>> print("%2.1f" % round(splits[1], 1))
0.4
>>> bucketed = bucketizer.transform(df).head()
>>> bucketed.buckets
0.0
>>> quantileDiscretizerPath = temp_path + "/quantile-discretizer"
>>> qds.save(quantileDiscretizerPath)
>>> loadedQds = QuantileDiscretizer.load(quantileDiscretizerPath)
>>> loadedQds.getNumBuckets() == qds.getNumBuckets()
True
  • pyspark.ml.feature.StandardScaler(self, withMean=False, withStd=True, inputCol=None, outputCol=None)
    (标准化列,使其拥有零均值和等于1的标准差)
    通过使用训练集中样本的列汇总统计消除平均值和缩放到单位方差来标准化特征。使用校正后的样本标准偏差计算“单位标准差”,该标准偏差计算为无偏样本方差的平方根。
>>> from pyspark.ml.linalg import Vectors
>>> df = spark.createDataFrame([(Vectors.dense([0.0]),), (Vectors.dense([2.0]),)], ["a"])
>>> standardScaler = StandardScaler(inputCol="a", outputCol="scaled")
>>> model = standardScaler.fit(df)
>>> model.mean
DenseVector([1.0])
>>> model.std
DenseVector([1.4142])
>>> model.transform(df).collect()[1].scaled
DenseVector([1.4142])
>>> standardScalerPath = temp_path + "/standard-scaler"
>>> standardScaler.save(standardScalerPath)
>>> loadedStandardScaler = StandardScaler.load(standardScalerPath)
>>> loadedStandardScaler.getWithMean() == standardScaler.getWithMean()
True
>>> loadedStandardScaler.getWithStd() == standardScaler.getWithStd()
True
>>> modelPath = temp_path + "/standard-scaler-model"
>>> model.save(modelPath)
>>> loadedModel = StandardScalerModel.load(modelPath)
>>> loadedModel.std == model.std
True
>>> loadedModel.mean == model.mean
True

数据规范化、归一化、Z-Score

  • pyspark.ml.feature.VectorAssembler(self, inputCols=None, outputCol=None)
    非常有用,将多个数字(包括向量)列合并为一列向量
#-*-coding:utf-8-*-

from pyspark import SparkContext
from pyspark.sql import SparkSession
from pyspark.ml.feature import VectorAssembler

if __name__=="__main__":
    sc=SparkContext(appName="myApp")
    spark=SparkSession.builder.getOrCreate()
    df=spark.createDataFrame([(12, 10, 3), (1, 4, 2)],["a","b","c"])
    """
    df.show()
    +---+---+---+
    | a | b | c |
    +---+---+---+
    | 12 | 10 | 3 |
    | 1 | 4 | 2 |
    +---+---+---+
    """
    vectorAssembler=VectorAssembler(inputCols=["a","b","c"],outputCol="features")
    vdf=vectorAssembler.transform(df)
    """
    vdf.show()
    +---+---+---+---------------+
    | a | b | c | features |
    +---+---+---+---------------+
    | 12 | 10 | 3 | [12.0, 10.0, 3.0] |
    | 1 | 4 | 2 | [1.0, 4.0, 2.0] |
    +---+---+---+---------------+
    """
    temp_path="/tmp/test/"
    vectorAssembler_path="%svectorAssembler"
    vectorAssembler.save(vectorAssembler_path)
    vectorAssemblerLoad=VectorAssembler.load(vectorAssembler_path)
    # print(vectorAssemblerLoad.transform(df).head().features==vectorAssembler.transform(df).head().features)
    # True
  • pyspark.ml.feature.VectorIndexer(self, maxCategories=20, inputCol=None, outputCol=None)类别列生成索引向量

  • pyspark.ml.feature.VectorSlicer(self, inputCol=None, outputCol=None, indices=None, names=None)作用于特征向量,给定一个索引列表,从特征向量中提取值。

特征变换–标签和索引的转化

来源:阮榕城 2017年12月18日
在机器学习处理过程中,为了方便相关算法的实现,经常需要把标签数据(一般是字符串)转化成整数索引,或是在计算结束后将整数索引还原为相应的标签。

Spark ML包中提供了几个相关的转换器,例如:StringIndexer,IndexToString,OneHotEncoder,VectorIndexer,它们提供了十分方便的特征转换功能,这些转换器类都位于pyspark.ml.feature包下。
值得注意的是,用于特征转换的转换器和其他的机器学习算法一样,也属于ML Pipeline模型的一部分,可以用来构成机器学习流水线,以StringIndexer为例,其存储着进行标签数值化过程的相关 超参数,是一个Estimator,对其调用fit(…)方法即可生成相应的模型StringIndexerModel类,很显然,它存储了用于DataFrame进行相关处理的 参数,是一个Transformer(其他转换器也是同一原理)
下面对几个常用的转换器依次进行介绍。

StringIndexer

​StringIndexer转换器可以把一列类别型的特征(或标签)进行编码,使其数值化,索引的范围从0开始,该过程可以使得相应的特征索引化,使得某些无法接受类别型特征的算法可以使用,并提高诸如决策树等机器学习算法的效率。

索引构建的顺序为标签的频率,优先编码频率较大的标签,所以出现频率最高的标签为0号。
如果输入的是数值型的,我们会把它转化成字符型,然后再对其进行编码。

首先,引入必要的包,并创建一个简单的DataFrame,它只包含一个id列和一个标签列category:

from pyspark.ml.feature import StringIndexer

df = spark.createDataFrame([(0, "a"), (1, "b"), (2, "c"), (3, "a"), (4, "a"), (5, "c")],["id", "category"])

随后,我们创建一个StringIndexer对象,设定输入输出列名,其余参数采用默认值,并对这个DataFrame进行训练,产生StringIndexerModel对象:

indexer = StringIndexer(inputCol="category", outputCol="categoryIndex")
model = indexer.fit(df)

随后即可利用该对象对DataFrame进行转换操作,可以看到,StringIndexerModel依次按照出现频率的高低,把字符标签进行了排序,即出现最多的“a”被编号成0,“c”为1,出现最少的“b”为0。

indexed = model.transform(df)
indexed.show()
 
+---+--------+-------------+
| id|category|categoryIndex|
+---+--------+-------------+
|  0|       a|          0.0|
|  1|       b|          2.0|
|  2|       c|          1.0|
|  3|       a|          0.0|
|  4|       a|          0.0|
|  5|       c|          1.0|
+---+--------+-------------+

IndexToString

与StringIndexer相对应,IndexToString的作用是把标签索引的一列重新映射回原有的字符型标签。

其主要使用场景一般都是和StringIndexer配合,先用StringIndexer将标签转化成标签索引,进行模型训练,然后在预测标签的时候再把标签索引转化成原有的字符标签。当然,你也可以另外定义其他的标签。

首先,和StringIndexer的实验相同,我们用StringIndexer读取数据集中的“category”列,把字符型标签转化成标签索引,然后输出到“categoryIndex”列上,构建出新的DataFrame。

from pyspark.ml.feature import IndexToString, StringIndexer

df = spark.createDataFrame([(0, "a"), (1, "b"), (2, "c"), (3, "a"), (4, "a"), (5, "c")],["id", "category"])

indexer = StringIndexer(inputCol="category", outputCol="categoryIndex")
model = indexer.fit(df)
indexed = model.transform(df)

随后,创建IndexToString对象,读取“categoryIndex”上的标签索引,获得原有数据集的字符型标签,然后再输出到“originalCategory”列上。最后,通过输出“originalCategory”列,可以看到数据集中原有的字符标签。

converter = IndexToString(inputCol="categoryIndex", outputCol="originalCategory")
converted = converter.transform(indexed)
converted.select("id", "categoryIndex", "originalCategory").show()
 
+---+-------------+----------------+
| id|categoryIndex|originalCategory|
+---+-------------+----------------+
|  0|          0.0|               a|
|  1|          2.0|               b|
|  2|          1.0|               c|
|  3|          0.0|               a|
|  4|          0.0|               a|
|  5|          1.0|               c|
+---+-------------+----------------+

OneHotEncoder

独热编码(One-Hot Encoding) 是指把一列类别性特征(或称名词性特征,nominal/categorical features)映射成一系列的二元连续特征的过程,原有的类别性特征有几种可能取值,这一特征就会被映射成几个二元连续特征,每一个特征代表一种取值,若该样本表现出该特征,则取1,否则取0。

One-Hot编码适合一些期望类别特征为连续特征的算法,比如说逻辑斯蒂回归等。

首先创建一个DataFrame,其包含一列类别性特征,需要注意的是,在使用OneHotEncoder进行转换前,DataFrame需要先使用StringIndexer将原始标签数值化:

from pyspark.ml.feature import OneHotEncoder, StringIndexer
 
df = spark.createDataFrame([
    (0, "a"),
    (1, "b"),
    (2, "c"),
    (3, "a"),
    (4, "a"),
    (5, "c")
], ["id", "category"])
 
stringIndexer = StringIndexer(inputCol="category", outputCol="categoryIndex")
model = stringIndexer.fit(df)
indexed = model.transform(df)

随后,我们创建OneHotEncoder对象对处理后的DataFrame进行编码,可以看见,编码后的二进制特征呈稀疏向量形式,与StringIndexer编码的顺序相同,需注意的是最后一个Category(”b”)被编码为全0向量,若希望”b”也占有一个二进制特征,则可在创建OneHotEncoder时指定setDropLast(false)。

encoder = OneHotEncoder(inputCol="categoryIndex", outputCol="categoryVec")
encoded = encoder.transform(indexed)
encoded.show()
 
+---+--------+-------------+-------------+
| id|category|categoryIndex|  categoryVec|
+---+--------+-------------+-------------+
|  0|       a|          0.0|(2,[0],[1.0])|
|  1|       b|          2.0|    (2,[],[])|
|  2|       c|          1.0|(2,[1],[1.0])|
|  3|       a|          0.0|(2,[0],[1.0])|
|  4|       a|          0.0|(2,[0],[1.0])|
|  5|       c|          1.0|(2,[1],[1.0])|
+---+--------+-------------+-------------+

VectorIndexer

之前介绍的StringIndexer是针对单个类别型特征进行转换,倘若所有特征都已经被组织在一个向量中,又想对其中某些单个分量进行处理时,Spark ML提供了VectorIndexer类来解决向量数据集中的类别性特征转换。

通过为其提供maxCategories超参数,它可以自动识别哪些特征是类别型的,并且将原始值转换为类别索引。它基于不同特征值的数量来识别哪些特征需要被类别化,那些取值可能性最多不超过maxCategories的特征需要会被认为是类别型的。

在下面的例子中,我们读入一个数据集,然后使用VectorIndexer训练出模型,来决定哪些特征需要被作为类别特征,将类别特征转换为索引,这里设置maxCategories为10,即只有种类小10的特征才被认为是类别型特征,否则被认为是连续型特征:

from pyspark.ml.feature import VectorIndexer
data = spark.read.format('libsvm').load('file:///usr/local/spark/data/mllib/sample_libsvm_data.txt')
indexer = VectorIndexer(inputCol="features", outputCol="indexed", maxCategories=10)
indexerModel = indexer.fit(data)
categoricalFeatures = indexerModel.categoryMaps
indexedData = indexerModel.transform(data)
indexedData.show()


+-----+--------------------+--------------------+
|label|            features|             indexed|
+-----+--------------------+--------------------+
|  0.0|(692,[127,128,129...|(692,[127,128,129...|
|  1.0|(692,[158,159,160...|(692,[158,159,160...|
|  1.0|(692,[124,125,126...|(692,[124,125,126...|
|  1.0|(692,[152,153,154...|(692,[152,153,154...|
|  1.0|(692,[151,152,153...|(692,[151,152,153...|
|  0.0|(692,[129,130,131...|(692,[129,130,131...|
|  1.0|(692,[158,159,160...|(692,[158,159,160...|
|  1.0|(692,[99,100,101,...|(692,[99,100,101,...|
|  0.0|(692,[154,155,156...|(692,[154,155,156...|
|  0.0|(692,[127,128,129...|(692,[127,128,129...|
|  1.0|(692,[154,155,156...|(692,[154,155,156...|
|  0.0|(692,[153,154,155...|(692,[153,154,155...|
|  0.0|(692,[151,152,153...|(692,[151,152,153...|
|  1.0|(692,[129,130,131...|(692,[129,130,131...|
|  0.0|(692,[154,155,156...|(692,[154,155,156...|
|  1.0|(692,[150,151,152...|(692,[150,151,152...|
|  0.0|(692,[124,125,126...|(692,[124,125,126...|
|  0.0|(692,[152,153,154...|(692,[152,153,154...|
|  1.0|(692,[97,98,99,12...|(692,[97,98,99,12...|
|  1.0|(692,[124,125,126...|(692,[124,125,126...|
+-----+--------------------+--------------------+

Spark ML 几种 归一化(规范化)方法总结

4种不同的归一化方法:

  • Normalizer
  • StandardScaler
  • MinMaxScaler
  • MaxAbsScaler

Normalizer

Normalizer的作用范围是每一行,使每一个行向量的范数变换为一个单位范数,下面的示例代码都来自spark官方文档加上少量改写和注释。

// L^1 norm将每一行的规整为1阶范数为1的向量,1阶范数即所有值绝对值之和。
+---+--------------+------------------+
| id| features| normFeatures|
+---+--------------+------------------+
| 0|[1.0,0.5,-1.0]| [0.4,0.2,-0.4]|
| 1| [2.0,1.0,1.0]| [0.5,0.25,0.25]|
| 2|[4.0,10.0,2.0]|[0.25,0.625,0.125]|
+---+--------------+------------------+

// L^inf norm向量的无穷阶范数即向量中所有值中的最大值
+---+--------------+--------------+
| id| features| normFeatures|
+---+--------------+--------------+
| 0|[1.0,0.5,-1.0]|[1.0,0.5,-1.0]|
| 1| [2.0,1.0,1.0]| [1.0,0.5,0.5]|
| 2|[4.0,10.0,2.0]| [0.4,1.0,0.2]|
+---+--------------+--------------+

StandardScaler

StandardScaler处理的对象是每一列,也就是每一维特征,将特征标准化为单位标准差或是0均值,或是0均值单位标准差。
主要有两个参数可以设置:

  • withStd: 默认为真。将数据标准化到单位标准差。
  • withMean: 默认为假。是否变换为0均值。 (此种方法将产出一个稠密输出,所以不适用于稀疏输入。)

StandardScaler需要fit数据,获取每一维的均值和标准差,来缩放每一维特征。

StandardScaler是一个Estimator,它可以fit数据集产生一个StandardScalerModel,用来计算汇总统计。
然后产生的模可以用来转换向量至统一的标准差以及(或者)零均值特征。
注意如果特征的标准差为零,则该特征在向量中返回的默认值为0.0。

// 将每一列的标准差缩放到1。
+---+--------------+------------------------------------------------------------+
|id |features |scaledFeatures |
+---+--------------+------------------------------------------------------------+
|0 |[1.0,0.5,-1.0]|[0.6546536707079772,0.09352195295828244,-0.6546536707079771]|
|1 |[2.0,1.0,1.0] |[1.3093073414159544,0.1870439059165649,0.6546536707079771] |
|2 |[4.0,10.0,2.0]|[2.618614682831909,1.870439059165649,1.3093073414159542] |
+---+--------------+------------------------------------------------------------+

MinMaxScaler

MinMaxScaler作用同样是每一列,即每一维特征。将每一维特征线性地映射到指定的区间,通常是[0, 1]。

MinMaxScaler计算数据集的汇总统计量,并产生一个MinMaxScalerModel。

注意因为零值转换后可能变为非零值,所以即便为稀疏输入,输出也可能为稠密向量。

该模型可以将独立的特征的值转换到指定的范围内。
它也有两个参数可以设置:

  • min: 默认为0。指定区间的下限。
  • max: 默认为1。指定区间的上限。
// 每维特征线性地映射,最小值映射到0,最大值映射到1。
+--------------+-----------------------------------------------------------+
|features |scaledFeatures |
+--------------+-----------------------------------------------------------+
|[1.0,0.5,-1.0]|[0.0,0.0,0.0] |
|[2.0,1.0,1.0] |[0.3333333333333333,0.05263157894736842,0.6666666666666666]|
|[4.0,10.0,2.0]|[1.0,1.0,1.0] |
+--------------+-----------------------------------------------------------+

MaxAbsScaler

MaxAbsScaler将每一维的特征变换到[-1, 1]闭区间上,通过除以每一维特征上的最大的绝对值,它不会平移整个分布,也不会破坏原来每一个特征向量的稀疏性。

因为它不会转移/集中数据,所以不会破坏数据的稀疏性。

// 每一维的绝对值的最大值为[4, 10, 2]
+--------------+----------------+
| features| scaledFeatures|
+--------------+----------------+
|[1.0,0.5,-1.0]|[0.25,0.05,-0.5]|
| [2.0,1.0,1.0]| [0.5,0.1,0.5]|
|[4.0,10.0,2.0]| [1.0,1.0,1.0]|
+--------------+----------------+

ml中的数据统计特性

描述性统计

mllib中有count()、max()、min()、mean()、 normL1()、normL2() 、numNonzeros() 、 variance()等基本的统计功能,不知道为什么在ml中没有了。
但是可以利用pysparl.sql.DataFrame来处理相关的统计

  • df.agg(*exprs)计算均值、最大值等
  • df.approxQuantile(col, probabilities, relativeError)计算分位数
  • df.corr(col1, col2, method=None)计算相关系数
  • df.describe(*cols)计算统计特性
>>> df.describe().show()
+-------+------------------+-----+
|summary|               age| name|
+-------+------------------+-----+
|  count|                 2|    2|
|   mean|               3.5| null|
| stddev|2.1213203435596424| null|
|    min|                 2|Alice|
|    max|                 5|  Bob|
+-------+------------------+-----+
  • df.summary(*statistics)
>>> df.summary().show()
+-------+------------------+-----+
|summary|               age| name|
+-------+------------------+-----+
|  count|                 2|    2|
|   mean|               3.5| null|
| stddev|2.1213203435596424| null|
|    min|                 2|Alice|
|    25%|                 2| null|
|    50%|                 2| null|
|    75%|                 5| null|
|    max|                 5|  Bob|
+-------+------------------+-----+
>>> df.summary("count", "min", "25%", "75%", "max").show()
+-------+---+-----+
|summary|age| name|
+-------+---+-----+
|  count|  2|    2|
|    min|  2|Alice|
|    25%|  2| null|
|    75%|  5| null|
|    max|  5|  Bob|
+-------+---+-----+
  • df.dropna(how='any', thresh=None, subset=None)
  • df.fillna(value, subset=None)
  • df.avg(*cols)
  • count()
  • max(*cols)
  • mean(*cols)
  • min(*cols)
  • pivot(pivot_col, values=None)
  • sum(*cols)

相关性

class pyspark.ml.stat.Correlation

#-*-coding:utf-8-*-

from pyspark import SparkContext
from pyspark.sql import SparkSession
from pyspark.ml.linalg import Vectors
from pyspark.ml.stat import Correlation

if __name__=="__main__":
    sc=SparkContext(appName="myApp")
    spark=SparkSession.builder.getOrCreate()
    dataset=[
        [Vectors.dense([1, 0, 0, -2])],
        [Vectors.dense([4, 5, 0, 3])],
        [Vectors.dense([6, 7, 0, 8])],
        [Vectors.dense([9, 0, 0, 1])]
    ]
    df=spark.createDataFrame(dataset,["features"])
    dfCorr=Correlation.corr(dataset=df,column="features",method="pearson")
    """
    dfCorr.show()
    +--------------------+
    |   pearson(features)|
    +--------------------+
    |1.0              ...|
    +--------------------+
    print(dfCorr.collect())
    [Row(pearson(features) = DenseMatrix(4, 4, [1.0, 0.0556, nan, 0.4005, 0.0556, 1.0, nan, 0.9136, nan, nan, 1.0, nan,
                                                0.4005, 0.9136, nan, 1.0], False))]
    """
    dfCorrelation=dfCorr.collect()[0][0]
    """
    print(print(str(dfCorrelation).replace('nan', 'NaN')))
    DenseMatrix([[ 1.        ,  0.05564149,         NaN,  0.40047142],
                 [ 0.05564149,  1.        ,         NaN,  0.91359586],
                 [        NaN,         NaN,  1.        ,         NaN],
                 [ 0.40047142,  0.91359586,         NaN,  1.        ]])
    """

统计检验

class pyspark.ml.stat.ChiSquareTest

#-*-coding:utf-8-*-

from pyspark import SparkContext
from pyspark.sql import SparkSession
from pyspark.ml.linalg import Vectors
from pyspark.ml.stat import ChiSquareTest

if __name__=="__main__":
    sc=SparkContext(appName="myApp")
    spark=SparkSession.builder.getOrCreate()
    dataset=[
        [0,Vectors.dense([0, 0, 1])],
        [0,Vectors.dense([1, 0, 1])],
        [1,Vectors.dense([2, 1, 1])],
        [1,Vectors.dense([3, 1, 1])]
    ]
    df=spark.createDataFrame(dataset,["label","features"])
    dfChiSq=ChiSquareTest.test(df,featuresCol="features",labelCol="label")
    """
    dfChiSq.show()
    +--------------------+----------------+-------------+                           
    |             pValues|degreesOfFreedom|   statistics|
    +--------------------+----------------+-------------+
    |[0.26146412994911...|       [3, 1, 0]|[4.0,4.0,0.0]|
    +--------------------+----------------+-------------+
    """

猜你喜欢

转载自blog.csdn.net/qingqing7/article/details/80935303