文章目录
- 零、Spark基本原理
- 0.1 pyspark.sql 核心类
- 0.2 spark的基本概念
- 0.3 spark部署方式
- 0.4 RDD数据结构
- (1)创建RDD的2种方式
- (2)RDD操作的2种类型
- (3)RDD之间的依赖关系
- 一、Pyspark.SQL部分
- 1.窗口函数
- 2.更换列名:
- 3.sql将一个字段根据某个字符拆分成多个字段显示
- 4.pd和spark的dataframe进行转换:
- 5.报错ValueError: Some of types cannot be determined after inferring
- 6.DF按行打乱
- 7.表格的联结
- 8.dataframe的操作
- 9.createDataFrame的几种方法
- 10.pd dataframe与spark dataframe转换,通过sql语句间接对pandas的dataframe进行处理
- 11.filter筛选
- 12. 新增或者修改spark.sql中dataframe的某列
- 13.将dataframe保存为csv文件
- 14. 取出对应表项内容
- 15.agg和groupby结合使用
- 二、Spark Core模块
- 2.1 udf函数的传参:
- 2.2 pandas core dataframe
- 2.3 rdd操作
- 2.4 filter操作
- 2.5 flatMap
- 2.6 take
- 三、MLlib模块
- 3.1 kmeans聚类分析
- 3.2 gbdt分类和回归
- 3.3 tf-idf英文关键词确定
- 四、推荐算法
- 4.1 达观数据竞赛:3种改进DL算法
- Reference
零、Spark基本原理
- 不同于MapReduce将中间计算结果放入磁盘中,Spark采用内存存储中间计算结果,减少了迭代运算的磁盘IO,并通过并行计算DAG图的优化,减少了不同任务之间的依赖,降低了延迟等待时间。内存计算下,Spark 比 MapReduce 快100倍。
- Spark可以用于批处理、交互式查询(Spark SQL)、实时流处理(Spark Streaming)、机器学习(Spark MLlib)和图计算(GraphX)。
如果是pyspark,为了不破坏Spark已有的运行时架构,Spark在外围包装一层Python API。在Driver端,借助Py4j实现Python和Java的交互,进而实现通过Python编写Spark应用程序。在Executor端,则不需要借助Py4j,因为Executor端运行的Task逻辑是由Driver发过来的,那是序列化后的字节码。
0.1 pyspark.sql 核心类
- pyspark.SparkContext: Spark 库的主要入口点,它表示与Spark集群的一个连接,其他重要的对象都要依赖它.SparkContext存在于Driver中,是Spark功能的主要入口。代表着与Spark集群的连接,可以在集群上创建RDD,accumulators和广播变量
- pyspark.RDD: 是Spark的主要数据抽象概念,是Spark库中定义的一个抽象类。
- pyspark.streaming.StreamingContext 一个定义在Spark Streaming库中定义的类, 每一个Spark Streaming 应用都必须创建这个类
- pyspark.streaming.DStrem:离散数据流,是Spark Streaming处理数据流的主要对象
- pyspark.sql.SparkSession: 是DataFrame和SQL函数的主要入口点。
- pyspark.sql.DataFrame: 是Spark SQL的主要抽象对象,若干行的分布式数据,每一行都要若干个有名字的列。 跟R/Python中的DataFrame 相像,有着更丰富的优化。DataFrame可以有很多种方式进行构造,例如: 结构化数据文件,Hive的table, 外部数据库,RDD。
- pyspark.sql.Column DataFrame 的列表达.
- pyspark.sql.Row DataFrame的行数据
0.2 spark的基本概念
RDD:是弹性分布式数据集(Resilient Distributed Dataset)的简称,是分布式内存的一个抽象概念,提供了一种高度受限的共享内存模型。
DAG:是Directed Acyclic Graph(有向无环图)的简称,反映RDD之间的依赖关系。
Driver Program:控制程序,负责为Application构建DAG图。
Cluster Manager:集群资源管理中心,负责分配计算资源。
Worker Node:工作节点,负责完成具体计算。
Executor:是运行在工作节点(Worker Node)上的一个进程,负责运行Task,并为应用程序存储数据。
Application:用户编写的Spark应用程序,一个Application包含多个Job。
Job:作业,一个Job包含多个RDD及作用于相应RDD上的各种操作。
Stage:阶段,是作业的基本调度单位,一个作业会分为多组任务,每组任务被称为“阶段”。
Task:任务,运行在Executor上的工作单元,是Executor中的一个线程。
总结:Application由多个Job组成,Job由多个Stage组成,Stage由多个Task组成。Stage是作业调度的基本单位。
0.3 spark部署方式
Local显然就是本地运行模式,非分布式。
- Standalone:使用Spark自带集群管理器,部署后只能运行Spark任务,与MapReduce 1.0框架类似。
- Mesos:是目前spark官方推荐的模式,目前也很多公司在实际应用中使用该模式,与Yarn最大的不同是Mesos 的资源分配是二次的,Mesos负责分配一次,计算框架可以选择接受或者拒绝。
- Yarn:Haoop集群管理器,部署后可以同时运行MapReduce,Spark,Storm,Hbase等各种任务,其架构如下图所指示,资源管理和调度依赖YARN,分布式存储依赖于HDFS:
0.4 RDD数据结构
RDD全称Resilient Distributed Dataset,弹性分布式数据集,它是记录的只读分区集合,是Spark的基本数据结构。RDD代表一个不可变、可分区、里面的元素可并行计算的集合。这里也列出RDD最常用的函数:
- map
- flatMap
- mapPartitions
- filter
- count
- reduce
- take
- saveAsTextFile
- collect
- join
- union
- persist
- repartition
- reduceByKey
- aggregateByKey
(1)创建RDD的2种方式
一般有两种方式创建RDD,第一种是读取文件中的数据生成RDD,第二种则是通过将内存中的对象并行化得到RDD。
#通过读取文件生成RDD
rdd = sc.textFile("hdfs://hans/data_warehouse/test/data")
#通过将内存中的对象并行化得到RDD
arr = [1,2,3,4,5]
rdd = sc.parallelize(arr)
创建RDD之后,可以使用各种操作对RDD进行编程。
(2)RDD操作的2种类型
RDD的操作有两种类型,即Transformation操作和Action操作。转换操作是从已经存在的RDD创建一个新的RDD,而行动操作是在RDD上进行计算后返回结果到 Driver。
Transformation操作都具有 Lazy 特性,即 Spark 不会立刻进行实际的计算,只会记录执行的轨迹,只有触发Action操作的时候,它才会根据 DAG 图真正执行。
(3)RDD之间的依赖关系
RDD之间的依赖关系有两种类型,即窄依赖和宽依赖:
- 窄依赖时,父RDD的分区和子RDD的分区的关系是一对一或者多对一的关系。而宽依赖时,父RDD的分区和自RDD的分区是一对多或者多对多的关系。
- 宽依赖关系相关的操作一般具有shuffle过程,即通过一个Patitioner函数将父RDD中每个分区上key不同的记录分发到不同的子RDD分区。
依赖关系确定了DAG切分成Stage的方式。
切割规则:从后往前,遇到宽依赖就切割Stage。
RDD之间的依赖关系形成一个DAG有向无环图,DAG会提交给DAGScheduler,DAGScheduler会把DAG划分成相互依赖的多个stage,划分stage的依据就是RDD之间的宽窄依赖。遇到宽依赖就划分stage,每个stage包含一个或多个task任务。然后将这些task以taskSet的形式提交给TaskScheduler运行。
其中RDD的迭代过程也可以参考下图:
WordCount范例
import findspark
#指定spark_home为刚才的解压路径,指定python路径
spark_home = "/Users/liangyun/ProgramFiles/spark-3.0.1-bin-hadoop3.2"
python_path = "/Users/liangyun/anaconda3/bin/python"
findspark.init(spark_home,python_path)
import pyspark
from pyspark import SparkContext, SparkConf
conf = SparkConf().setAppName("test").setMaster("local[4]")
sc = SparkContext(conf=conf)
只需要5行代码就可以完成WordCount词频统计。
rdd_line = sc.textFile("./data/hello.txt")
rdd_word = rdd_line.flatMap(lambda x:x.split(" "))
rdd_one = rdd_word.map(lambda t:(t,1))
rdd_count = rdd_one.reduceByKey(lambda x,y:x+y)
rdd_count.collect()
[('world', 1),
('love', 3),
('jupyter', 1),
('pandas', 1),
('hello', 2),
('spark', 4),
('sql', 1)]
一、Pyspark.SQL部分
- SparkSQL提供方便的api让我们和hive、HDFS、mysql、Cassandra、Hbase等存储媒介进行数据交换,但其默认只是的数据类型只有Int,Long,Float,Double,String,Boolean等;
- 如果是SQL中不直接支持的功能,可以通过用户自定义函数udf来实现;如果功能更加复杂,可以转为RDD数据结构来实现。
1.窗口函数
# 数据的分组聚合,找到每个用户最近的3次收藏beat(用window开窗函数)
from pyspark.sql.window import Window
import pyspark.sql.functions as F
window_out = Window.partitionBy("user_id") \
.orderBy(F.desc("collect_time"))
# user_feed.withColumn("rank", F.rank().over(window_out)).show(truncate = False)
# user_feed.withColumn("rank", F.rank().over(window_out)).show(40)
user_feed_test = user_feed.withColumn("rank", F.row_number().over(window_out)) \
.where(F.col('rank') <= 3)
user_feed_test.show(30)
结果如下,和mysql的窗口函数类似的,以每个user_id
分组,然后组内排序,这里我只获取排序后collect_time
前3的数据,即最近3次的用户收藏数据:
+--------+-------+------------+--------------------+----+
| user_id|beat_id|collect_type| collect_time|rank|
+--------+-------+------------+--------------------+----+
|10065188| 827272| 4|2021-08-22 04:54:...| 1|
|10065188| 885812| 5|2020-10-23 18:53:...| 2|
|10068979|1069390| 5|2021-06-20 07:44:...| 1|
|10074915| -2| 4|2021-11-27 13:42:...| 1|
|10074915|1122682| 4|2021-09-07 14:26:...| 2|
|10075397| 947751| 4|2022-01-30 07:30:...| 1|
|10075397| 336641| 5|2022-01-30 07:23:...| 2|
|10075397| 886179| 4|2022-01-05 10:35:...| 3|
|10104842| 886462| 1|2021-02-28 17:04:...| 1|
|10122654|1531961| 4|2022-03-16 11:09:...| 1|
|10122654| 893655| 4|2022-03-15 04:32:...| 2|
|10122654| 303121| 4|2022-03-14 05:59:...| 3|
|10134095| 0| 3|2021-07-24 13:02:...| 1|
|10134095|1023250| 4|2021-07-22 00:31:...| 2|
|10139927| 0| 5|2020-09-05 19:14:...| 1|
|10139927| 0| 5|2020-09-03 17:51:...| 2|
|10245428| 889915| 5|2020-05-18 14:41:...| 1|
|10245428|1073074| 5|2020-05-18 14:07:...| 2|
+--------+-------+------------+--------------------+----+
2.更换列名:
如现在有个人员信息表,新加上一列coun try
字段信息:
# 修改列名
from pyspark.sql.functions import col
# df2 = df1.withColumn("avg_resp_rate", col("sum_def_imps")/col("sum_count")).withColumn("avg_ctr", col("sum_clicks")/col("sum_imps"))
# another example
from pyspark.sql.functions import col, lit
from pyspark.sql.types import StructType, StructField, StringType,IntegerType
data = [('James','','Smith','1991-04-01','M',3000),
('Michael','Rose','','2000-05-19','M',4000),
('Robert','','Williams','1978-09-05','M',4000),
('Maria','Anne','Jones','1967-12-01','F',4000),
('Jen','Mary','Brown','1980-02-17','F',-1)
]
print(data)
"""
[('James', '', 'Smith', '1991-04-01', 'M', 3000),
('Michael', 'Rose', '', '2000-05-19', 'M', 4000),
('Robert', '', 'Williams', '1978-09-05', 'M', 4000),
('Maria', 'Anne', 'Jones', '1967-12-01', 'F', 4000),
('Jen', 'Mary', 'Brown', '1980-02-17', 'F', -1)]
"""
先给出对应的字段,创建我们的DataFrame
格式,然后通过withColumn
新加上一列,其中lit("ABC")
是指整列的数据都是对应的ABC
字符串:
# schema只需要给出列名即可
columns = ["firstname","middlename","lastname","dob","gender","salary"]
# 增加
df = spark.createDataFrame(data=data, schema = columns)
df.show()
# 增加or修改列
df2 = df.withColumn("salary",col("salary").cast("Integer"))
df2.show()
df3 = df.withColumn("salary",col("salary")*100)
df3.show()
# lit默认均是USA
df5 = df.withColumn("Coun try", lit("ABC"))
df5.show()
结果如下:
+---------+----------+--------+----------+------+------+
|firstname|middlename|lastname| dob|gender|salary|
+---------+----------+--------+----------+------+------+
| James| | Smith|1991-04-01| M| 3000|
| Michael| Rose| |2000-05-19| M| 4000|
| Robert| |Williams|1978-09-05| M| 4000|
| Maria| Anne| Jones|1967-12-01| F| 4000|
| Jen| Mary| Brown|1980-02-17| F| -1|
+---------+----------+--------+----------+------+------+
+---------+----------+--------+----------+------+------+
|firstname|middlename|lastname| dob|gender|salary|
+---------+----------+--------+----------+------+------+
| James| | Smith|1991-04-01| M| 3000|
| Michael| Rose| |2000-05-19| M| 4000|
| Robert| |Williams|1978-09-05| M| 4000|
| Maria| Anne| Jones|1967-12-01| F| 4000|
| Jen| Mary| Brown|1980-02-17| F| -1|
+---------+----------+--------+----------+------+------+
+---------+----------+--------+----------+------+------+
|firstname|middlename|lastname| dob|gender|salary|
+---------+----------+--------+----------+------+------+
| James| | Smith|1991-04-01| M|300000|
| Michael| Rose| |2000-05-19| M|400000|
| Robert| |Williams|1978-09-05| M|400000|
| Maria| Anne| Jones|1967-12-01| F|400000|
| Jen| Mary| Brown|1980-02-17| F| -100|
+---------+----------+--------+----------+------+------+
+---------+----------+--------+----------+------+------+-------+
|firstname|middlename|lastname| dob|gender|salary|Country|
+---------+----------+--------+----------+------+------+-------+
| James| | Smith|1991-04-01| M| 3000| ABC|
| Michael| Rose| |2000-05-19| M| 4000| ABC|
| Robert| |Williams|1978-09-05| M| 4000| ABC|
| Maria| Anne| Jones|1967-12-01| F| 4000| ABC|
| Jen| Mary| Brown|1980-02-17| F| -1| ABC|
+---------+----------+--------+----------+------+------+-------+
3.sql将一个字段根据某个字符拆分成多个字段显示
可以通过withColumn
和split
进行分隔,参考上次写的【Pyspark基础】sql获取user最近3次使用的item。
更多参考: https://www.pythonheidong.com/blog/article/690421/aa556949151c244e81f8/
4.pd和spark的dataframe进行转换:
- 当需要把Spark DataFrame转换成Pandas DataFrame时,可以调用
toPandas()
; - 当需要从Pandas DataFrame创建Spark DataFrame时,可以采用
createDataFrame(pandas_df)
。
更多参考:
厦大数据实验室-借助arrow实现pyspark和pandas之间的数据转换:http://dblab.xmu.edu.cn/blog/2752-2/
5.报错ValueError: Some of types cannot be determined after inferring
是因为有字段类型spark识别不了:
(1)可以提高数据采样率:
sqlContext.createDataFrame(rdd, samplingRatio=0.01)
(2)显式声明要创建的 DataFrame 的数据结构schema
:
from pyspark.sql.types import *
schema = StructType([
StructField("c1", StringType(), True),
StructField("c2", IntegerType(), True)
])
df = sqlContext.createDataFrame(rdd, schema=schema)
# 方法二: 使用toDF
from pyspark.sql.types import *
schema = StructType([
StructField("c1", StringType(), True),
StructField("c2", IntegerType(), True)
])
df = rdd.toDF(schema=schema)
参考:https://www.codeleading.com/article/64083243294/
6.DF按行打乱
每行生成随机数后排序,然后删除这一随机数的列。
import pyspark.sql.functions as F
# 从rdd生成dataframe
schema = StructType(fields)
df_1 = spark.createDataFrame(rdd, schema)
# 乱序: pyspark.sql.functions.rand生成[0.0, 1.0]中double类型的随机数
df_2 = df_1.withColumn('rand', F.rand(seed=42))
# 按随机数排序
df_rnd = df_2.orderBy(df_2.rand)
# 删除随机数的一列
df = df_rnd.drop(df_rnd.rand)
7.表格的联结
Spark DataFrame理解和使用之两个DataFrame的关联操作SQL数据库语言基础之SqlServer多表连接查询与INNER JOIN内连接查询
SQL的表格之间的join连接方式——inner join/left join/right join/full join语法及其用法实例
8.dataframe的操作
9.createDataFrame的几种方法
(1)注:structtype的格式(官方文档):
可以通过StructType
设置schema
,关于其使用:
# 读取beat数据
schema = StructType([StructField("beatid", StringType(), True)\
,StructField("name", StringType(), True)\
,StructField("language", StringType(), True)])
beats = spark.read.csv("filepath", header=False, schema=schema)
# print(beats.show())
beats.show()
(2)从pandas dataframe创建spark dataframe
# 从pandas dataframe创建spark dataframe
colors = ['white','green','yellow','red','brown','pink']
color_df=pd.DataFrame(colors,columns=['color'])
color_df['length']=color_df['color'].apply(len)
color_df=spark.createDataFrame(color_df)
color_df.show()
10.pd dataframe与spark dataframe转换,通过sql语句间接对pandas的dataframe进行处理
pandasDF_out.createOrReplaceTempView("pd_data")
# %%
spark.sql("select * from pd_data").show()
# %%
res = spark.sql("""select * from pd_data
where math>= 90
order by english desc""")
res.show()
# %%
output_DF = res.toPandas()
print(type(output_DF))
11.filter筛选
可以通过filter进行筛选,如找出category_title
为00后
的对应行:
# 方法一:filter
category_beat.filter(" category_title = '00后' ").head(5)
12. 新增或者修改spark.sql中dataframe的某列
官方文档pyspark.sql.DataFrame.withColumn描述:https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.withColumn.html?highlight=withcolumn#pyspark.sql.DataFrame.withColumn
13.将dataframe保存为csv文件
这里的repartition(num)
是一个可选项:
# save positive_res_data
file_location = "file:///home/hadoop/development/RecSys/data/"
positive_res_data.repartition(2).write.csv(file_location + 'positive_res_data.csv')
# 保存成功,有多个文件
也可以使用save:
https://wenku.baidu.com/view/1329194ea75177232f60ddccda38376baf1fe078.html
14. 取出对应表项内容
首先初始表category_beat
是这样的:
+--------------------------+----------------------------------+
| category_title| collect_set(name)|
+--------------------------+----------------------------------+
| 00后|[白月光与朱砂痣, 致姗姗来迟的你...|
| 04年唱的歌| [祝我生日快乐, 旅行的意义, 柠...|
|10年前没有iPhone但有这些歌| [逆光, 改变自己, 达尔文, 最...|
+--------------------------+----------------------------------+
现在要得到第一行的collect_set
表项内容,即对应的集合:
import random
ans = category_beat.where(col("category_title") == '00后').select(col("collect_set(name)"))
# type(ans)
ans.show()
# 取出对应行列里的set,并且转为对应的list
lst = ans.take(1)[0][0]
# take是取出前1行
lst = list(lst)
print(lst, "\n")
print("推荐的歌曲:", lst[random.randint(0, len(lst))])
"""
+----------------------------------+
| collect_set(name)|
+----------------------------------+
|[白月光与朱砂痣, 致姗姗来迟的你...|
+----------------------------------+
['白月光与朱砂痣', '致姗姗来迟的你', '陨落', '踏山河', '花.间.酒', '山海', '
推荐的歌曲: 山海
"""
15.agg和groupby结合使用
二、Spark Core模块
2.2 pandas core dataframe
可以使用rdd的api。
2.3 rdd操作
RDD是spark的重点,包括两个算子:
【transformation变换】map、flatMap、groupByKey、reduceByKey等。
【action动作】action有懒惰执行的特性,出发基于RDD依赖关系的计算,只有当action操作触发到该依赖才会计算,如collect、count、take、top、first等。
2.4 filter操作
rdd = sc.parallelize([1, 2, 3, 4, 5, 6])
rdd = rdd.filter(lambda x: x % 2 == 0)
rdd.collect()
# [2, 4, 6]
2.5 flatMap
对rdd中每个元素按照函数操作,并将结果进行扁平化处理。
rdd = sc.parallelize([3, 4, 5])
fm = rdd.flatMap(lambda x: range(1, x))
fm.collect()
# [1, 2, 1, 2, 3, 1, 2, 3, 4]
2.6 take
take操作将前若干个数据汇集到Driver,相比collect安全
rdd = sc.parallelize(range(10),5)
part_data = rdd.take(4)
part_data
# [0, 1, 2, 3]
三、MLlib模块
MLlib (DataFrame-based)
MLlib (RDD-based)
3.1 kmeans聚类分析
api的使用本身不难,和sklearn的使用差不多:
from pyspark.ml.clustering import KMeans
kMeans = KMeans(k=25, seed=1)
model = kMeans.fit(kmeans_data.select('features'))
model.transform(kmeans_data).show(1000)
# 分析模型训练的结果
# 训练得到的模型是否有summary
print(model.hasSummary, "\n")
# 获得聚类中心
print(model.clusterCenters(), "\n")
# 每个簇的大小(含有的数据点数)
print(model.summary.clusterSizes)
3.2 gbdt分类和回归
这里以GBDT分类举例,使用libsvm
数据集:
# test libsvm dataset
#!usr/bin/env python
#encoding:utf-8
from __future__ import division
import findspark
findspark.init()
import pyspark
from pyspark import SparkConf
from pyspark.ml import Pipeline
from pyspark.context import SparkContext
from pyspark.sql.session import SparkSession
from pyspark.ml.classification import DecisionTreeClassifier
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
from pyspark.ml.feature import StringIndexer, VectorIndexer,IndexToString
"""
conf=SparkConf().setAppName('MLDemo')
sc = SparkContext('local')
spark = SparkSession(sc)
"""
def gradientBoostedTreeClassifier(data="data/sample_libsvm_data.txt"):
'''
GBDT分类器
'''
#加载LIBSVM格式的数据集
data = spark.read.format("libsvm").load(data)
print("data数据集前5行:\n", data.show(n = 3))
labelIndexer = StringIndexer(inputCol="label", outputCol="indexedLabel").fit(data)
#自动识别类别特征并构建索引,指定maxCategories,因此具有> 4个不同值的特征被视为连续的
featureIndexer=VectorIndexer(inputCol="features", outputCol="indexedFeatures", maxCategories=4).fit(data)
#训练集、测试集划分
(trainingData, testData) = data.randomSplit([0.7, 0.3])
# 构建10个基分类器 maxIter = 10
gbt = GBTClassifier(labelCol="indexedLabel", featuresCol="indexedFeatures", maxIter=10)
pipeline = Pipeline(stages=[labelIndexer, featureIndexer, gbt])
model = pipeline.fit(trainingData)
# transformer是一个pipelineStage,将一个df转为另一个df,对testData进行
predictions = model.transform(testData)
#展示前5行数据,或者show进行打印
predictions.select("prediction", "indexedLabel", "features").show(5)
#展示预测标签与真实标签,计算测试误差
evaluator = MulticlassClassificationEvaluator(
labelCol="indexedLabel", predictionCol="prediction", metricName="accuracy")
# 评估model
accuracy = evaluator.evaluate(predictions)
print("Test Error = %g" % (1.0 - accuracy))
gbtModel = model.stages[2]
print('gbtModelSummary: ',gbtModel) #模型摘要
if __name__=='__main__':
filepath = "file:///home/hadoop/development/RecSys/"
gradientBoostedTreeClassifier(filepath + "data/sample_libsvm_data.txt")
这里的结果如下,可以发现accuary
的值可以达到97%。
+-----+--------------------+
|label| features|
+-----+--------------------+
| 0.0|(692,[127,128,129...|
| 1.0|(692,[158,159,160...|
| 1.0|(692,[124,125,126...|
+-----+--------------------+
only showing top 3 rows
data数据集前5行:
None
+----------+------------+--------------------+
|prediction|indexedLabel| features|
+----------+------------+--------------------+
| 1.0| 1.0|(692,[95,96,97,12...|
| 1.0| 1.0|(692,[122,123,124...|
| 1.0| 1.0|(692,[124,125,126...|
| 1.0| 1.0|(692,[124,125,126...|
| 1.0| 1.0|(692,[125,126,127...|
+----------+------------+--------------------+
only showing top 5 rows
Test Error = 0.03125
gbtModelSummary: GBTClassificationModel: uid = GBTClassifier_d0de0e9263e7, numTrees=10, numClasses=2, numFeatures=692
3.3 tf-idf英文关键词确定
TF-IDF(Term Frequency/Inverse Document Frequency,词频-逆文档频率)算法,可以找出文档中的关键词,
顾名思义,TF-IDF 分数由两部分组成:
第一部分是TF词语频率(Term Frequency),
第二部分是IDF逆文档频率(Inverse Document Frequency)。
其中计算语料库中文档总数除以含有该词语的文档数量,然后再取对数就是逆文档频率。
TF(t)= 该词语在当前文档出现的次数 / 当前文档中词语的总数
IDF(t)= log_e(文档总数 / 出现该词语的文档总数)即:
IDF反应了一个词在所有文本中出现的频率,如果一个词在很多的文本中出现,那么它的IDF值应该低,比如
I come to China to travel
中的“to
”。而反过来如果一个词在比较少的文本中出现,那么它的IDF值应该高。一个极端的情况,如果一个词在所有的文本中都出现,那么它的IDF值应该为0。
如果之前跑过数据,需要删除缓存中的rdd数据再跑:normalized_document_tfidf_rdd.unpersist()
。具体代码如下:
from pyspark import SparkConf, SparkContext
import math
#以下为计算过程中需要用到的几个函数
# 该函数主要是统计一个文档中包含哪些单词
def word_contains(words_list):
words_set=set(words_list)#将列表转为set,去除重复的单词
return list(words_set)#再将set转为列表返回
# 计算每个单词的逆文档频率idf
def computeIDF(word_df_tuple,num_document):
word=word_df_tuple[0]
df=word_df_tuple[1]
#根据逆文档频率计算公式计算idf值
word_idf = math.log(float(num_document+1) / float(df+1), 2)
return (word, word_idf)#以一个元组tuple的形式返回一个单词的dif值
# 计算每个文档中单词的tf值,并将文档转成向量
def computeTF(words_list, all_words_list):
words_num=len(words_list)#获取文档中出现的单词的个数
words_dic={}
for word in words_list:#统计文档中每个单词出现的次数
if word in words_dic.keys():
words_dic[word]+=1
else:
words_dic[word]=1
tf_vector=[]
for word in all_words_list:#将文档转为一个tf值向量并返回
if word in words_dic.keys():
tf=float(words_dic[word])/words_num
tf_vector.append(tf)
else:
tf_vector.append(0)
return tf_vector
# 计算每个文档向量中每个单词的tfidf值
def computeTFIDF(tf_vector, words_idf_dic,all_words_list):
i=0
tfidf_vector=[]
for word in all_words_list:#将每个单词的tf值和idf值相乘
tfidf=tf_vector[i]*words_idf_dic[word]
tfidf_vector.append(tfidf)
i+=1
return tfidf_vector
# 对每个tfidf向量进行归一化
def nomoralize(tfidf_vector):
new_vector=[]
sum=0
for item in tfidf_vector:
sum+=math.pow(item,2)
sqrt_sum=math.sqrt(sum)
for item in tfidf_vector:
new_item=item/sqrt_sum
new_vector.append(new_item)
return new_vector
#主程序
if __name__ == "__main__":
#conf = SparkConf().setAppName("tfidf")
#sc = SparkContext(conf=conf)
# 删除缓存中的rdd数据
# normalized_document_tfidf_rdd.unpersist()
#示例文档数据,每个文档是一个单词列表
documents_list=[["hello","world","china","good","spark","good"],
["hello","china","china","great","love","china"],
["love","spark","spark","good","hello","spark"]]
#documents_list=[["hello","friends","today","is","my","holiday"],
# ["hello","china","china","great","love","china"],
# ["love","spark","spark","good","hello","spark"]]
#创建RDD并进行缓存
tokenized_document_rdd=sc.parallelize(documents_list).cache()
print ("*************************** compute idf************************************")
#这个阶段的主要操作是计算单词的idf值
#获取文档的个数用来计算逆文档频率
num_document=tokenized_document_rdd.count()
#计算每个单词的文档支持度
#实现思路是,针对每个文本文档,通过将单词列表转成set来获取每个文档中出现的单词,然后
#通过flatMap操作,将每个文档出现的单词合并成一个新的集合。在新的集合中,一个单词出现
#的次数即是其文档支持度。因此,我们可以在flatMap操作之后应用map和reducebykey操作来统
#计每个单词的文档支持度。
words_df_rdd=tokenized_document_rdd.flatMap(lambda words_list:word_contains(words_list)) \
.map(lambda word:(word,1)) \
.reduceByKey(lambda a,b:a+b)
#根据单词的文档频率和文档的总数计算每个单词的idf
# computeIDF函数实现的是具体计算idf的值
words_idf_rdd=words_df_rdd.map(lambda word_df_tuple:
computeIDF(word_df_tuple, num_document))
print ("*********************************** compute tf *******************************")
#计算每个文本中每个单词出现的频次,进而计算tf值
#返回包含所有单词的列表
#flatMap是将所有文档中的单词合并成一个大的列表,distinct是将列表中重复的单词去除
all_words_list= tokenized_document_rdd.flatMap(lambda words_list:words_list) \
.distinct() \
.collect()
#考虑到单词可能很多,我们将包含所有单词的all_words_list变量做出广播变量,使得一个executor
#上的多个Task可以共享该变量
all_words_broadcast=sc.broadcast(all_words_list)
#计算单词的tf,得到文档的tf向量
document_tf_rdd= tokenized_document_rdd.map(lambda words_list:
computeTF(words_list, all_words_broadcast.value))
print ("******************************* compute tfidf*********************************")
#提取从rdd中提取每个单词的idf值,并将提取的列表变量转成字典变量,进而转成广播变量,以
#供发送给各个executor计算每个文档中每个单词的tfidf值
words_idf_list= words_idf_rdd.collect()
words_idf_dic={}
for item in words_idf_list:#将单词的idf值列表转为字典易于获取每个单词的idf值
words_idf_dic[item[0]]=item[1]
words_idf_broadcast=sc.broadcast(words_idf_dic)
#计算每个文本中每个单词的tfidf值
document_tfidf_rdd= document_tf_rdd.map(lambda words_tf_list:computeTFIDF(words_tf_list,
words_idf_broadcast.value,all_words_broadcast.value))
#将每个文本对应的列表向量进行归一化
normalized_document_tfidf_rdd= document_tfidf_rdd.map(lambda tfidf_vector:
nomoralize(tfidf_vector))
print ("************************** print tfidf vectors*********************************")
#打印输出每个tfidf向量
tfidf_vectors= normalized_document_tfidf_rdd.collect()
num = 0
for item in tfidf_vectors:
print (item)
num = num + 1
print("第%d条文本:" % num)
print("当前文本的tfidf向量: \n", item)
print(documents_list[num - 1])
print("最大值是:", p.max(item), "所在的下标是:", item.index(p.max(item)))
# tf-idf值最大的单词
print("tfidf值最大的单词", documents_list[num - 1][ item.index(p.max(item)) ], "\n")
结果分析:每条句子(文档)的tf-idf最大的单词也打印出来了
*************************** compute idf************************************
*********************************** compute tf *******************************
******************************* compute tfidf*********************************
************************** print tfidf vectors*********************************
[0.5820915838854853, 0.0, 0.29104579194274266, 0.0, 0.29104579194274266, 0.7012517964002163, 0.0]
第1条文本:
当前文本的tfidf向量:
[0.5820915838854853, 0.0, 0.29104579194274266, 0.0, 0.29104579194274266, 0.7012517964002163, 0.0]
['hello', 'china', 'china', 'great', 'love', 'china']
最大值是: 0.7012517964002163 所在的下标是: 5
tfidf值最大的单词 china
[0.0, 0.0, 0.0, 0.6060537877905645, 0.7546051455392007, 0.0, 0.2515350485130669]
第2条文本:
当前文本的tfidf向量:
[0.0, 0.0, 0.0, 0.6060537877905645, 0.7546051455392007, 0.0, 0.2515350485130669]
['hello', 'china', 'china', 'great', 'love', 'china']
最大值是: 0.7546051455392007 所在的下标是: 4
tfidf值最大的单词 love
[0.30151134457776363, 0.0, 0.9045340337332908, 0.0, 0.0, 0.0, 0.30151134457776363]
第3条文本:
当前文本的tfidf向量:
[0.30151134457776363, 0.0, 0.9045340337332908, 0.0, 0.0, 0.0, 0.30151134457776363]
['hello', 'china', 'china', 'great', 'love', 'china']
最大值是: 0.9045340337332908 所在的下标是: 2
tfidf值最大的单词 china
四、推荐算法
4.1 达观数据竞赛:3种改进DL算法
http://www.360doc.com/content/17/0615/21/16619343_663476259.shtml
【安装pyspark】
在Ubuntu上安装pyspark:https://zhuanlan.zhihu.com/p/34635519
apache官网上的安装包:https://www.apache.org/dyn/closer.lua/spark/spark-3.2.1/spark-3.2.1-bin-hadoop3.2.tgz
Reference
[1] https://spark.apache.org/docs/latest/api/python/reference/index.html
[2] 算法工程师的数据分析:https://zhuanlan.zhihu.com/p/343375787
[3] 用createDataFrame创建表的几种方法 [4] spark dataframe按行随机打乱
[5] dataframe的常用操作:
2)https://www.jianshu.com/p/acd96549ee15
3)https://www.dandelioncloud.cn/article/details/1441272697576869890
[6] 厦门大学数据库实验室:Spark部署模式 [7] Spark-RDD宽窄依赖及Stage划分
[8] pyspark分类算法之梯度提升决策树分类器模型GBDT实践【gradientBoostedTreeClassifier】
[9] PySpark之RDD入门最全攻略