读书笔记:Spark上数据的获取,处理与准备 上

来源:互联网 发布:java反序列化代码 编辑:程序博客网 时间:2024/05/29 12:32

感想

这是文章的第三章,讲得还挺详细的,其中还掺杂着代码,以后我决定在说理论的时候不贴代码了,严重耽误我的时间了,我花了差不多一天半的时间读了这一张,这张讲了怎么处理数据,对数据进行预处理,去掉一些不完整的数据,针对不同类型的数据都将其向量化,规范化,正则化,理论很简单,需要有具体应用场景的工程实践。

介绍

机器学习是一个极为广泛的领域,其应用范围已包括Web和移动应用,物联网,传感网络,金融服务,医疗健康和其他科研领域,而这些还只是其中一小部分。

本书将重点关注在其商业领域的应用,这类领域中可用的数据通常由组织的内部数据(比如金融公司的交易数据)以及外部数据(比如该金融公司下的金融资产价格数据)。

一般来说,获取世纪公司或机构的内部数据十分困难,因为这些信息很敏感(尤其是记录,用户或客户行为以及公司财务),也关系组织的潜在利益。这也是对这类数据应用机器学习建模的实用之处:一个预测精准的好模型有着极高的商业价值。

1. 获取公开数据集

商业敏感数据虽然难以获取,但好在仍有相当多有用数据可公开访问。它们中的不少常用来作为特定机器学习问题的基准测试数据。常见的有以下几个。

·            UCL机器学习知识库:包括近300个不同大小和类型的数据集,可用于分类,回归,聚类和推荐系统任务。数据集列表位于:http://archive.ics.uci.edu/ml/index.php

·            Amazon AWS公开数据集:包含的通常是大型数据集,可通过Amazon S3访问。这些数据包括人类基因组项目,Common Crawl网页语料库,维基百科数据和GoogleBooks Ngrams.  网址为:https://aws.amazon.com/public-datasets/

·            Kaggle:这里集合了Kaggle举行的各种机器学习竞赛所用的数据集。它们覆盖分类,回归,排名,推荐系统以及图像分析领域,可从Competitions区域下载:https://www.kaggle.com

·            KDnuggets:这里包含一个详细的公开数据集列表。网址为:http://www.kdnuggets.com/datasets/index.html

MovieLens 100K数据集

书上给了一个方法,我这里给出另一种方法,也可以获取该数据集,在终端运行下面的命令,大约250MB,结束后,里面就有该数据集:
git clone https://github.com/tophua/spark-machine-learning-book.git

2. 探索与可视化数据

PySpark支持运行Python时可指定的参数。在启动PySpark终端时,我们可以使用IPython而非标准的Python shell。启动时也可以向IPython传入其他参数,包括让它在启动时也启用pylab功能。

PYSPARK_DRIVER_PYTHON=ipython PYSPARK_DRIVER_PYTHON_OPTS='notebook' ./bin/pyspark

打开notebook后,就可以在你下载的spark-machine-learning-book找相应的源码,可以直接在notebook上面运行,下面的源码都来自git下来的示例代码,读者可以自己去试试。

2.1 搜索用户数据

首先分析MovieLens用户的特征。在你的终端里输入如下代码:

user_data=sc.textFile("/Users/eric/Documents/Spark/spark-machine-learning-book/ml-100k/u.user")user_data.first()
读者需要换到自己电脑的路径哈

输出为:

u'1|24|M|technician|85711'

这是用户数据文件的首行,从中可以看出,它是由“|”字符分隔

 first 函数与collect 函数类似,单前者只向驱动程序返回RDD的首个元素。也可使用take(k)函数来只返回RDD的前k个元素到驱动程序。

下面用“|”字符来分隔各行数据。这将生成一个RDD,其中每一个记录对应一个Python列表,各列表用户ID(user ID)、年龄(age)、性别(gender)、职业(occupation)和邮编(ZIPcode) 五个属性构成。

之后再统计用户,性别,职业和邮编的数目。代码如下,该数据集不大,故这里并未缓存它。

user_fields = user_data.map(lambda line: line.split("|"))num_users = user_fields.map(lambda fields: fields[0]).count()num_genders = user_fields.map(lambda fields: fields[2]).distinct().count()num_occupations = user_fields.map(lambda fields:fields[3]).distinct().count()num_zipcodes = user_fields.map(lambda fields:fields[4]).distinct().count()print "Users: %d, genders: %d, occupations: %d, ZIP codes: %d" % (num_users, num_genders, num_occupations, num_zipcodes)

输出为:
Users: 943, genders: 2, occupations: 21, ZIP codes: 795

接着用matplotlib的hist函数来创建一个直方图,以分析用户年龄的分布状况:

from matplotlib.pylab import *ages = user_fields.map(lambda x: int(x[1])).collect()hist(ages, bins=20, color='lightblue', normed=True)fig = matplotlib.pyplot.gcf()fig.set_size_inches(16, 10)

这里hist函数的输入参数有ages数组,直方图bins数目(即区间数,这里是20)。同时还使用了normed=True参数来正则化直方图,即让每个方条表示年龄在该区间内的数据量占总数据量的比。

从下图可以看出MoviesLens的用户偏年轻。大量用户处于15岁到35岁之间。



若想了解用户的职业分布情况,代码如下,首先利用之前用到的MapReduce方法来计算数据集中各种职业的出现次数,然后matplotlib下的bar函数来绘制一个不同的职业的数量的条形图。

数据中对职业的描述用的是文本,所以需要对其稍作处理以便bar函数使用:

count_by_occupation = user_fields.map(lambda fields: (fields[3], 1)). reduceByKey(lambda x, y: x + y).collect()x_axis1 = np.array([c[0] for c in count_by_occupation])y_axis1 = np.array([c[1] for c in count_by_occupation])

在得到各职业所占数量的RDD后,需将其转为两个数组才能用来做条形图。它们分别对应x轴(职业标签)与y轴(数量).collect函数返回数量数据时并不排序。我们需要对该数据进行排序,从而在条形图中以从少到多的顺序来显示各个职业。

为此刻先创建两个numpy数组。之后调用argsort函数来以数量升序从数组中选取元素。注意这里会对x轴和y轴的数组都以y轴值排序(即以数量排序)。

x_axis = x_axis1[np.argsort(y_axis1)]y_axis = y_axis1[np.argsort(y_axis1)]

有了条形图两轴所需的数据后便可创建条形图。创建时,会以职业作为x轴上的分类标签,以数量作为y轴的值。

pos = np.arange(len(x_axis))width = 1.0ax = plt.axes()ax.set_xticks(pos + (width / 2))ax.set_xticklabels(x_axis)plt.bar(pos, y_axis, width, color='lightblue')plt.xticks(rotation=30)fig = matplotlib.pyplot.gcf()fig.set_size_inches(16, 10)

生成的图形如下。从中可以看出,数量最多的职业是student。

Spark对RDD提供了一个名为countByValue的便捷函数。它会计算RDD里各不同值所分别出现的次数,并将其以Python dict函数的形式返回给驱动程序:

count_by_occupation2 = user_fields.map(lambda fields: fields[3]).countByValue()print "Map-reduce approach:"print dict(count_by_occupation2)print ""print "countByValue approach:"print dict(count_by_occupation)

输出为:

Map-reduce approach:{u'administrator': 79, u'retired': 14, u'lawyer': 12, u'healthcare': 16, u'marketing': 26, u'executive': 32, u'scientist': 31, u'student': 196, u'technician': 27, u'librarian': 51, u'programmer': 66, u'salesman': 12, u'homemaker': 7, u'engineer': 67, u'none': 9, u'doctor': 7, u'writer': 45, u'entertainment': 18, u'other': 105, u'educator': 95, u'artist': 28}countByValue approach:{u'administrator': 79, u'writer': 45, u'retired': 14, u'lawyer': 12, u'doctor': 7, u'marketing': 26, u'executive': 32, u'none': 9, u'entertainment': 18, u'healthcare': 16, u'scientist': 31, u'student': 196, u'educator': 95, u'technician': 27, u'librarian': 51, u'programmer': 66, u'artist': 28, u'salesman': 12, u'other': 105, u'homemaker': 7, u'engineer': 67}


2.2 探索电影数据

接下来了解下电影分类数据的特征。如之前那样,我们可以先简单看一下某行记录,然后再统计电影总数。

movie_data = sc.textFile("%s/ml-100k/u.item" % PATH)print movie_data.first()num_movies = movie_data.count()print "Movies: %d" % num_movies

终端输出为:

1|Toy Story (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Toy%20Story%20(1995)|0|0|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0Movies: 1682

绘制电影年龄的分布图的方法和之前对用户年龄和职业分布的处理类似。电影年龄即其发行年份相对于现在过了多少年(本数据中现在是1998年)。

从下面的代码可以看到,电影数据中有些数据不规整,故需要一个函数来处理解析releasedate时可能的解析错误。函数名为convert_year:

def convert_year(x):    try:        return int(x[-4:])    except:        return 1900 # there is a 'bad' data point with a blank year, which we set to 1900 and will filter out later

有了以上函数来解析发行年份后,便可在调用电影数据进行map转换时应用该函数,并取回结果:

movie_fields = movie_data.map(lambda lines: lines.split("|"))years = movie_fields.map(lambda fields: fields[2]).map(lambda x: convert_year(x))

解析出错的数据的年份已设为1900.要过滤掉这些数据可以使用Spark的filter转换操作:

# we filter out any 'bad' data points hereyears_filtered = years.filter(lambda x: x != 1900)

现实的数据经常会有不规整的情况,对其解析时就需要进一步的处理。事实上,这也表明了数据探索的重要性所在,即它有助于发现数据再完整性和质量上的问题。

过滤掉问题数据后,我们用当前年份减去发行年份,从而将电影发行年份列表转换为电影年龄。接着countByValue来计算不同年龄电影的数目。最后绘制电影年龄直方图(使用hist函数,其values变量的值来自countByValue的结果,主键为bins变量):

# plot the movie ages histogrammovie_ages = years_filtered.map(lambda yr: 1998-yr).countByValue()values = movie_ages.values()bins = movie_ages.keys()hist(values, bins=bins, color='lightblue', normed=True)fig = matplotlib.pyplot.gcf()fig.set_size_inches(16,10)

这里解释一下bins:一个histogram,通常可以用一个列向量表示(例子中的a,b),列向量里面的每一个值就是一个bin(a,b),比如说列向量有个50个元素,那么就代表有50个bin。


你会看到上图所示的结果。它表明大部分电影发行于1998年的前几年

2.3    探索评级数据

现在看一些评级数据:

rating_data_raw = sc.textFile("%s/ml-100k/u.data" % PATH)print rating_data_raw.first()num_ratings = rating_data_raw.count()print "Ratings: %d" % num_ratings

输出为:

1962423881250949Ratings: 100000

可以看到评级次数共有10万。另外和用户数据与电影数据不同,评级记录用“\t”分隔。你可能也已想到,我们会想做些基本的统计,以及绘制评级值分布的直方图。

rating_data = rating_data_raw.map(lambda line: line.split("\t"))ratings = rating_data.map(lambda fields: int(fields[2]))max_rating = ratings.reduce(lambda x, y: max(x, y))min_rating = ratings.reduce(lambda x, y: min(x, y))mean_rating = ratings.reduce(lambda x, y: x + y) / float(num_ratings)median_rating = np.median(ratings.collect())ratings_per_user = num_ratings / num_usersratings_per_movie = num_ratings / num_moviesprint "Min rating: %d" % min_ratingprint "Max rating: %d" % max_ratingprint "Average rating: %2.2f" % mean_ratingprint "Median rating: %d" % median_ratingprint "Average # of ratings per user: %2.2f" % ratings_per_userprint "Average # of ratings per movie: %2.2f" % ratings_per_movie
输出为:
Min rating: 1Max rating: 5Average rating: 3.53Median rating: 4Average # of ratings per user: 106.00Average # of ratings per movie: 59.00

从中可以看到,最低的评级为1,最大的评级为5.这并不意外,因为评级的范围为1到5.

Spark对RDD也提供了一个名为states的函数。该函数包含一个数值变量用于做类似的统计:

# we can also use the stats function to get some similar information to the aboveratings.stats()
输出:

(count: 100000, mean: 3.52986, stdev: 1.12566797076, max: 5.0, min: 1.0)

可以看出,用户对电影的平均评级是3.5足有,而评级的中位数为4.这就能期待说评级的分布稍倾向高点的得分。要验证这点,可以创建一个评级值分布的条形图。

# create plot of counts by rating valuecount_by_rating = ratings.countByValue()x_axis = np.array(count_by_rating.keys())y_axis = np.array([float(c) for c in count_by_rating.values()])# we normalize the y-axis here to percentagesy_axis_normed = y_axis / y_axis.sum()pos = np.arange(len(x_axis))width = 1.0ax = plt.axes()ax.set_xticks(pos + (width / 2))ax.set_xticklabels(x_axis)plt.bar(pos, y_axis_normed, width, color='lightblue')plt.xticks(rotation=30)fig = matplotlib.pyplot.gcf()fig.set_size_inches(16, 10)


其特征和我们之前所期待的相同,即评级的分布的确偏向中等以上。

同样也可以求各个用户评级次数的分布情况。记得之前我们已对评级数据用制表符分隔,从而生成过rating_data RDD。

计算各用户的评级次数的分布时,我们先从rating_data RDD里提取出以用户ID为主键,评级为值的键值对。之后调用Spark的groupByKey函数,来对评级以用户ID为主键进行分组:

# to compute the distribution of ratings per user, we first group the ratings by user iduser_ratings_grouped = rating_data.map(lambda fields: (int(fields[0]), int(fields[2]))).\    groupByKey() # then, for each key (user id), we find the size of the set of ratings, which gives us the # ratings for that user user_ratings_byuser = user_ratings_grouped.map(lambda (k, v): (k, len(v)))user_ratings_byuser.take(5)

要检查结果RDD,可从中选出少数记录。这应该会返回一个(用户ID,评级次数)键值对类型的RDD:

[(2, 62), (4, 24), (6, 211), (8, 59), (10, 184)]

最后,用我们所熟悉的hist函数来绘制各用户评级分布的直方图。

# and finally plot the histogramuser_ratings_byuser_local = user_ratings_byuser.map(lambda (k, v): v).collect()hist(user_ratings_byuser_local, bins=200, color='lightblue', normed=True)fig = matplotlib.pyplot.gcf()fig.set_size_inches(16,10)


结果如图,可以看出,大部分用户的评级次数少于100.但该分布也表明仍然有较多用户做出过上百次的评级。


阅读全文
0 0
原创粉丝点击