前言

在对数据进行了初步探索后,想必读者对MovieLens数据集有了感性认识。而在数据挖掘/推荐引擎运行前,往往需要对数据预处理。预处理的重要性不言而喻,甚至比数据挖掘/推荐系统本身还重要。

然而完整的数据预处理工作会涉及到:缺失值,异常值,口径统一,去重,特征提取等等等等,可以单写一本书了,本文无法一一介绍。

本文仅就特征提取这一话题进行粗略讨论并展示。

类别特征提取

在很多场景下,数据集的很多特征是类型变量,比如MovieLens里面的职业类型。这样的变量无法作为很多算法的输入,因为这类变量无法作用于样本间距离的计算。

可参考的方法是 1 of k 编码,就是将某种类型的特征打平,将其转化为具有n列的向量。具体的做法是先为特征列创建字典,然后将各具体特征值映射到 1 of k 编码。

下面以MoveiLens中的职业类型特征为例,演示特征值为programmer的特征提取:

 # 载入数据集
user_data = sc.textFile("/home/kylin/ml-100k/u.user")
# 以' | '切分每列,返回新的用户RDD
user_fields = user_data.map(lambda line: line.split("|"))
# 获取职业RDD并落地
all_occupations = user_fields.map(lambda fields: fields[3]).distinct().collect()
# 对各职业进行排序
all_occupations.sort() # 构建字典
idx = 0
all_occupations_dict = {}
for o in all_occupations:
all_occupations_dict[o] = idx
idx +=1 # 生成并打印职业为程序员(programmer)的1 of k编码
K = len(all_occupations_dict)
binary_x = np.zeros(K)
k_programmer = all_occupations_dict['programmer']
binary_x[k_programmer] = 1
print "程序员的1 of k编码为: %s" % binary_x

结果为:

派生特征提取

并非所有的特征均可直接拿来学习。比如电影发行日期特征,它显然无法拿来进行学习。但正如上一节所做的一个工作,将它转化为电影年龄,这就可以在很多场景下进行学习了。

再比如时间戳属性,可参考将他们转为为:早/中/晚这样的分类变量:

 # 载入数据集
rating_data_raw = sc.textFile("/home/kylin/ml-100k/u.data")
# 获取评分RDD
rating_data = rating_data_raw.map(lambda line: line.split("\t"))
ratings = rating_data.map(lambda fields: int(fields[2])) # 函数: 将时间戳格式转换为datetime格式
def extract_datetime(ts):
import datetime
return datetime.datetime.fromtimestamp(ts) # 获取小时RDD
timestamps = rating_data.map(lambda fields: int(fields[3]))
hour_of_day = timestamps.map(lambda ts: extract_datetime(ts).hour) # 函数: 将小时映射为分类变量并展示
def assign_tod(hr):
times_of_day = {
'morning' : range(7, 12),
'lunch' : range(12, 14),
'afternoon' : range(14, 18),
'evening' : range(18, 23),
'night' : range(23, 7)
}
for k, v in times_of_day.iteritems():
if hr in v:
return k # 获取新的分类变量RDD
time_of_day = hour_of_day.map(lambda hr: assign_tod(hr))
time_of_day.take(5)

结果为:

若要使用这个特征,大部分机器学习算法可以考虑将其1 of k编码。部分支持分类型变量的算法除外。

PS:有两个None是因为代码中night:range(23,7)这么写是不对的。算了不纠结,意思懂就好 :)

文本特征提取

关于文本特征提取方法有很多,本文仅介绍一个简单而又经典的提取方法 - 词袋法。
       其基本步骤如下:

1. 分词 - 将文本分割为由词组成的集合。可根据空格符,标点进行分割;
2. 删除停用词 - the and 这类词无学习的价值意义,删除之;
3. 提取词干 - 将各个词转化为其基本形式,如men -> man;
4. 向量化 - 从根本上来说和1 of k相同。不过由于词往往很多,所以稀疏矩阵技术很重要;

下面将MovieLens数据集中的影片标题进行特征提取:

 # 载入数据集
movie_data = sc.textFile("/home/kylin/ml-100k/u.item")
# 以' | '切分每列,返回影片RDD
movie_fields = movie_data.map(lambda lines: lines.split("|")) # 函数: 剔除掉标题中的(年份)部分
def extract_title(raw):
import re
grps = re.search("\((\w+)\)", raw)
if grps:
return raw[:grps.start()].strip()
else:
return raw # 获取影片名RDD
raw_titles = movie_fields.map(lambda fields: fields[1]) # 剔除影片名中的(年份)
movie_titles = raw_titles.map(lambda m: extract_title(m)) # 由于仅仅是个展示的例子,简简单单用空格分割
title_terms = movie_titles.map(lambda t: t.split(" ")) # 搜集所有的词
all_terms = title_terms.flatMap(lambda x: x).distinct().collect()
# 创建字典
idx = 0
all_terms_dict = {}
for term in all_terms:
all_terms_dict[term] = idx
idx +=1
num_terms = len(all_terms_dict) # 函数: 采用稀疏向量格式保存编码后的特征并返回
def create_vector(terms, term_dict):
from scipy import sparse as sp
x = sp.csc_matrix((1, num_terms))
for t in terms:
if t in term_dict:
idx = term_dict[t]
x[0, idx] = 1
return x # 将字典保存为广播数据格式类型。因为各个worker都要用
all_terms_bcast = sc.broadcast(all_terms_dict)
# 采用稀疏矩阵格式保存影片名特征
term_vectors = title_terms.map(lambda terms: create_vector(terms, all_terms_bcast.value))
# 展示提取结果
term_vectors.take(5)

其中,字典的创建过程也可以使用Spark提供的便捷函数zipWithIndex,这个函数可以将原RDD中的值作为主键,而新的值为主键在原RDD中的位置:

 all_terms_dict2 = title_terms.flatMap(lambda x: x).distinct().zipWithIndex().collectAsMap()

collectAsMap则是将结果落地为Python的dict格式。

结果为:

归一化特征

归一化最经典的做法就是所有特征值-最小值/特征区间。但对于一般特征的归一化网上很多介绍,请读者自行学习。本文仅对特征向量的归一化做介绍。

一般来说,我们是先计算向量的二阶范数,然后让向量的所有元素去除以这个范数。

下面演示对某随机向量进行归一化:

 # 设置随机数种子
np.random.seed(42)
# 生成随机向量
x = np.random.randn(10)
# 产生二阶范数
norm_x_2 = np.linalg.norm(x)
# 归一化
normalized_x = x / norm_x_2 # 结果展示
print "向量x:\n%s" % x
print "向量x的2阶范数: %2.4f" % norm_x_2
print "归一化后的向量x:\n%s" % normalized_x
print "归一化后向量x的2阶范数:\n%2.4f" % np.linalg.norm(normalized_x)

结果为:

Spark的MLlib库提供了专门的正则化函数,它们执行起来的效率显然远远高于我们自己写的:

 # 导入Spark库中的正则化类
from pyspark.mllib.feature import Normalizer
# 初始化正则化对象
normalizer = Normalizer()
# 创建测试向量(RDD)
vector = sc.parallelize([x])
# 对向量进行归一化并返回结果
normalized_x_mllib = normalizer.transform(vector).first().toArray() # 结果展示
print "向量x:\n%s" % x
print "向量x的二阶范数: %2.4f" % norm_x_2
print "被MLlib归一化后的向量x:\n%s" % normalized_x_mllib
print "被MLlib归一化后的向量x的二阶范数: %2.4f" % np.linalg.norm(normalized_x_mllib)

结果请读者自行对比。

第二篇:使用Spark对MovieLens的特征进行提取的更多相关文章

  1. 第二篇:Spark SQL Catalyst源码分析之SqlParser

    /** Spark SQL源码分析系列文章*/ Spark SQL的核心执行流程我们已经分析完毕,可以参见Spark SQL核心执行流程,下面我们来分析执行流程中各个核心组件的工作职责. 本文先从入口 ...

  2. 第二篇 特征点匹配以及openvslam中的相关实现详解

    配置文件 在进入正题之前先做一些铺垫,在openvslam中,配置文件是必须要正确的以.yaml格式提供,通常需要指明使用的相机模型,ORB特征检测参数,跟踪参数等. #==============# ...

  3. 大数据篇:Spark

    大数据篇:Spark Spark是什么 Spark是一个快速(基于内存),通用,可扩展的计算引擎,采用Scala语言编写.2009年诞生于UC Berkeley(加州大学伯克利分校,CAL的AMP实验 ...

  4. 深入理解javascript作用域系列第二篇——词法作用域和动态作用域

    × 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极 ...

  5. 第二篇 Integration Services:SSIS数据泵

    本篇文章是Integration Services系列的第二篇,详细内容请参考原文. 简介SSIS用于移动数据.数据流任务提供此功能.因为这个原因,当介绍SSIS时我喜欢从数据流任务开始.数据流任务的 ...

  6. 第二篇 界面开发 (Android学习笔记)

    第二篇 界面开发 第5章 探索界面UI元素 ●The Android View Class     ●△Widget设计步骤 需要修改三个XML,以及一个class: 1)第一个xml是布局XML文件 ...

  7. 【译】第二篇 Integration Services:SSIS数据泵

    本篇文章是Integration Services系列的第二篇,详细内容请参考原文. 简介SSIS用于移动数据.数据流任务提供此功能.因为这个原因,当介绍SSIS时我喜欢从数据流任务开始.数据流任务的 ...

  8. 深入理解javascript作用域系列第二篇

    前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作 ...

  9. 第七篇:Spark SQL 源码分析之Physical Plan 到 RDD的具体实现

    /** Spark SQL源码分析系列文章*/ 接上一篇文章Spark SQL Catalyst源码分析之Physical Plan,本文将介绍Physical Plan的toRDD的具体实现细节: ...

随机推荐

  1. C++数据个数未知情况下的输入方法

    我们经常需要输入一串数,而数据个数未知.这时候就不能以数据个数作为输入是否结束的判断标准了. 这种情况下,我们可以用以下两种方法输入数据. 方法一:判断回车键(用getchar()=='\n'即可判断 ...

  2. WinSock IOCP 模型总结(附一个带缓存池的IOCP类)

    前言 本文配套代码:https://github.com/TTGuoying/IOCPServer 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了 ...

  3. 【Tools】ubuntu虚拟机Vmware Tools 安装记录

    1.下载VmwareTools 链接: https://pan.baidu.com/s/1c4baji4 密码: h3si 2.终端打开下载文件位置 cd /home/ubuntu/Downloads ...

  4. SSE图像算法优化系列十七:一些图像处理中常用小过程的SSE实现。

    在做图像处理的SSE优化时,也会经常遇到一些小的过程.数值优化等代码,本文分享一些个人收藏或实现的代码片段给大家. 一.快速求对数运算 对数运算在图像处理中也是个经常会遇到的过程,特备是在一些数据压缩 ...

  5. map,vector 等容器内容的循环删除问题(C++)

    map,vector 等容器内容的循环删除问题(C++) map,vector等容器的循环删除不能用普通的方法删除: for(auto p=list.begin();p!=list.end();p++ ...

  6. zip-gzip-bzip2_压缩文件

    问:为什么要压缩文件? 答:方便传输,因为压缩的文件容量会比较小        存储所使用的空间也会比较小 ---> 备份   Windows里的压缩软件:WinRAR.Zip.好压.2345 ...

  7. Java经典编程题50道之十三

    一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少? public class Example13 {    public static void main(St ...

  8. UVALive - 4329 Ping pong 树状数组

    这题不是一眼题,值得做. 思路: 假设第个选手作为裁判,定义表示在裁判左边的中的能力值小于他的人数,表示裁判右边的中的能力值小于他的人数,那么可以组织场比赛. 那么现在考虑如何求得和数组.根据的定义知 ...

  9. 使用flask_sqlalchemy

    首先引用一下廖雪峰Python教程里关于sqlalchemy的话, 这里我们要讲的是flask_sqlalchemy的用法. 1. 安装 用pip安装即可, 进入cmd控制台输入 pip instal ...

  10. yaf框架学习文件配置

    文件配置: 在配置php支持yaf的时候,可以设置一个参数yaf.environ:把本地开发设置成develop.测试环境配置成test.生产环境配置成product. [yaf] extension ...