机器学习的流程

适合回归的数据集 适合做回归的数据集_sed

公开的数据集
1. UCL机器学习知识库:包括近300个不同大小和类型的数据集,可用于分类、回归、聚类和推荐系统任务。数据集列表位于:http://archive.ics.uci.edu/ml/
2. Amazon AWS公开数据集:包含的通常是大型数据集,可通过Amazon S3访问。这些数据集包括人类基因组项目、Common Crawl网页语料库、维基百科数据和Google BooksNgrams。相关信息可参见:http://aws.amazon.com/publicdatasets/
3. Kaggle:这里集合了Kaggle举行的各种机器学习竞赛所用的数据集。它们覆盖分类、回归、排名、推荐系统以及图像分析领域,可从Competitions区域下载:http://www.kaggle.com/competitions
4.KDnuggets:这里包含一个详细的公开数据集列表,其中一些上面提到过的。该列表位于:http://www.kdnuggets.com/datasets/index.html

MovieLens 100k数据集
MovieLens 100k数据集包含表示多个用户对多部电影的10万次评级数据,也包含电影元数据
和用户属性信息。该数据集不大,方便下载和用Spark程序快速处理,故适合做讲解示例。
可从http://files.grouplens.org/datasets/movielens/ml-100k.zip下载这个数据集。

数据探索及可视化
1.探索用户数据
首先来分析MovieLens用户的特征。在你的终端里输入如下代码(其中的PATH是指用unzip
命令来解压MovieLens 100k数据集时所生成的主目录):

user_data = sc.textFile('/Users/youwei.tan/ml-100k/u.user')
user_data.first()

输出结果:u’1|24|M|technician|85711’
下面用“|”字符来分隔各行数据。这将生成一个RDD,其中每一个记录对应一个Python列表,各列表由用户ID(user ID)、年龄(age)、性别(gender)、职业(occupation)和邮编(ZIP code)五个属性构成。4之后再统计用户、性别、职业和邮编的数目。这可通过如下代码实现。该数据集不大,故这里并未缓存它。

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函数来创建一个直方图,以分析用户年龄的分布情况:

import matplotlib.pyplot as plt
from matplotlib.pyplot import hist
ages = user_fields.map(lambda x: int(x[1])).collect()
hist(ages, bins=20, color='lightblue',normed=True)
fig = plt.gcf()
fig.set_size_inches(12,6)
plt.show()

适合回归的数据集 适合做回归的数据集_数据_02


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

画出用户的职业的分布图:

import numpy as np
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])
x_axis = x_axis1[np.argsort(y_axis1)]
y_axis = y_axis1[np.argsort(y_axis1)]
pos = np.arange(len(x_axis))
width = 1.0
ax = 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 = plt.gcf()
fig.set_size_inches(12,6)
plt.show()

适合回归的数据集 适合做回归的数据集_sed_03


从中可看出,数量最多的职业是student、other、educator、administrator、engineer和programmer。

Spark对RDD提供了一个名为countByValue的便捷函数。它会计算RDD里各不同值所分别出现的次数,并将其以Pythondict函数的形式(或是Scala、Java下的Map函数)返回给驱动程序:

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.探索电影数据

movie_data = sc.textFile("/Users/youwei.tan/ml-100k//u.item")
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|0
Movies: 1682

绘制电影的age分布图:
绘制电影年龄的分布图的方法和之前对用户年龄和职业分布的处理类似。电影年龄即其发行年份相对于现在过了多少年(在本数据中现在是1998年)。从下面的代码可以看到,电影数据中有些数据不规整,故需要一个函数来处理解析releasedate时可能的解析错误。这里命名该函数为convert_year。

def convert_year(x):
    try:
        return int(x[-4:])
    except:
        return 1900

movie_fields = movie_data.map(lambda lines:lines.split('|'))
years = movie_fields.map(lambda fields: fields[2]).map(lambda x: convert_year(x))
years_filtered = years.filter(lambda x: x!=1900)
print years_filtered.count()
movie_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 = plt.gcf()
fig.set_size_inches(12,6)
plt.show()

输出结果:

1681

适合回归的数据集 适合做回归的数据集_数据_04


它表明大部分电影发行于1998年的前几年。

3.探索评分数据

#对数据进行一些基本的统计:

rating_data = rating_data.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)/num_ratings
median_rating = np.median(ratings.collect())
ratings_per_user = num_ratings/num_users;
ratings_per_movie = num_ratings/ num_movies
print 'Min rating: %d' %min_rating
print 'max rating: %d' % max_rating
print 'Average rating: %2.2f' %mean_rating
print 'Median rating: %d '%median_rating
print 'Average # of ratings per user: %2.2f'%ratings_per_user
print 'Average # of ratings per movie: %2.2f' % ratings_per_movie

输出结果:
Min rating: 1
max rating: 5
Average rating: 3.00
Median rating: 4
Average # of ratings per user: 106.00
Average # of ratings per movie: 59.00

从上述结果可以看到,最低的评级为1,而最大的评级为5。这并不意外,因为评级的范围便是从1到5。
Spark对RDD也提供一个名为states的函数。该函数包含一个数值变量用于做类似的统计:

ratings.stats()

输出结果:
(count: 100000, mean: 3.52986, stdev: 1.12566797076, max: 5.0, min: 1.0)

可以看出,用户对电影的平均评级(mean)是3.5左右,而评级中位数(median)为4。这就能期待说评级的分布稍倾向高点的得分。要验证这点,可以创建一个评级值分布的条形图。具体做法和之前的类似:

count_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()])
y_axis_normed = y_axis/y_axis.sum()
pos = np.arange(len(x_axis))
width = 1.0
ax = 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 = plt.gcf()
fig.set_size_inches(12,6)
plt.show()

适合回归的数据集 适合做回归的数据集_sed_05


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

#计算每个用户和其对应的评价次数:
user_ratings_grouped = rating_data.map(lambda fields:(int(fields[0]),int(fields[2]))).groupByKey()
user_rating_byuser = user_ratings_grouped.map(lambda (k,v):(k,len(v)))
user_rating_byuser.take(5)
#绘制每个用户的总共评价次数的分布图:
user_ratings_byuser_local = user_rating_byuser.map(lambda (k,v):v).collect()
hist(user_ratings_byuser_local, bins=200, color = 'lightblue',normed = True)
fig = plt.gcf()
fig.set_size_inches(12,6)
plt.show()

适合回归的数据集 适合做回归的数据集_适合回归的数据集_06


可以看出,大部分用户的评级次数少于100。

数据处理与转换

#用中位值替换bad values和missing values,非规整数据点的序号(之前我们给该数据点分配了1900的值)。
years_pre_processed = movie_fields.map(lambda fields: fields[2]).map(lambda x: convert_year(x)).collect()
years_pre_processed_array = np.array(years_pre_processed)
mean_year = np.mean(years_pre_processed_array[years_pre_processed_array!=1900])
median_year = np.median(years_pre_processed_array[years_pre_processed_array!=1900])
index_bad_data = np.where(years_pre_processed_array==1900)
years_pre_processed_array[index_bad_data] = median_year
print 'Mean year of release: %d' % mean_year
print 'Median year of release: %d ' % median_year
print "Index of '1900' after assigning median: %s"% np.where(years_pre_processed_array==1900)[0]

输出结果:
Mean year of release: 1989
Median year of release: 1995
Index of ‘1900’ after assigning median: []

从数据中提取有用的特征
数据可以概括地氛围如下几种:
 数值特征(numerical feature):这些特征通常为实数或整数,比如之前例子中提到的年龄。
 类别特征(categorical feature):它们的取值只能是可能状态集合中的某一种。我们数据集中的用户性别、职业或电影类别便是这类。
 文本特征(text feature):它们派生自数据中的文本内容,比如电影名、描述或是评论。
 其他特征:大部分其他特征都最终表示为数值。比如图像、视频和音频可被表示为数值数据的集合。地理位置则可由经纬度或地理散列(geohash)表示。