Spark机器学习6·聚类模型

美团推荐算法实践:机器学习重排序模型成亮点
发表于 17:50|
来源美团技术博客|
作者beanjoy_786
摘要:本文介绍了美团网推荐系统的构建和优化过程中的一些做法,包括数据层、触发层、融合过滤层和排序层五个层次,采用了HBase、Hive、storm、Spark和机器学习等技术。两个优化亮点是将候选集进行融合与引入重排序模型。
编者按:在用户意图明确时,我们通常用搜索引擎来解决互联网时代的信息过载问题,但当用户的意图不明确或者很难用清晰的语义表达,搜索引擎就无能为力。此时,借助推荐系统通过用户行为的分析理解其意图,为其推送个性化的结果,便成为一种更好的选择。美团作为国内发展较快的O2O网站,有着大量的用户和丰富的用户行为,这些为推荐系统的应用和优化提供了很好的条件。本文由美团技术团队成员撰写,介绍其推荐系统的构建和优化过程中的一些做法。框架从框架的角度看,推荐系统基本可以分为数据层、触发层、融合过滤层和排序层。数据层包括数据生成和数据存储,主要是利用各种数据处理工具对原始日志进行清洗,处理成格式化的数据,落地到不同类型的存储系统中,供下游的算法和模型使用。候选集触发层主要是从用户的历史行为、实时行为、地理位置等角度利用各种触发策略产生推荐的候选集。候选集融合和过滤层有两个功能,一是对出发层产生的不同候选集进行融合,提高推荐策略的覆盖度和精度;另外还要承担一定的过滤职责,从产品、运营的角度确定一些人工规则,过滤掉不符合条件的item。排序层主要是利用机器学习的模型对触发层筛选出来的候选集进行重排序。同时,对与候选集触发和重排序两层而言,为了效果迭代是需要频繁修改的两层,因此需要支持ABtest。为了支持高效率的迭代,我们对候选集触发和重排序两层进行了解耦,这两层的结果是正交的,因此可以分别进行对比试验,不会相互影响。同时在每一层的内部,我们会根据用户将流量划分为多份,支持多个策略同时在线对比。数据应用数据乃算法、模型之本。美团作为一个交易平台,同时具有快速增长的用户量,因此产生了海量丰富的用户行为数据。当然,不同类型的数据的价值和反映的用户意图的强弱也有所不同。用户主动行为数据记录了用户在美团平台上不同的环节的各种行为,这些行为一方面用于候选集触发算法(在下一部分介绍)中的离线计算(主要是浏览、下单),另外一方面,这些行为代表的意图的强弱不同,因此在训练重排序模型时可以针对不同的行为设定不同的回归目标值,以更细地刻画用户的行为强弱程度。此外,用户对deal的这些行为还可以作为重排序模型的交叉特征,用于模型的离线训练和在线预测。负反馈数据反映了当前的结果可能在某些方面不能满足用户的需求,因此在后续的候选集触发过程中需要考虑对特定的因素进行过滤或者降权,降低负面因素再次出现的几率,提高用户体验;同时在重排序的模型训练中,负反馈数据可以作为不可多得的负例参与模型训练,这些负例要比那些展示后未点击、未下单的样本显著的多。用户画像是刻画用户属性的基础数据,其中有些是直接获取的原始数据,有些是经过挖掘的二次加工数据,这些属性一方面可以用于候选集触发过程中对deal进行加权或降权,另外一方面可以作为重排序模型中的用户维度特征。通过对UGC数据的挖掘可以提取出一些关键词,然后使用这些关键词给deal打标签,用于deal的个性化展示。策略触发上文中我们提到了数据的重要性,但是数据的落脚点还是算法和模型。单纯的数据只是一些字节的堆积,我们必须通过对数据的清洗去除数据中的噪声,然后通过算法和模型学习其中的规律,才能将数据的价值最大化。在本节中,将介绍推荐候选集触发过程中用到的相关算法。1. 协同过滤提到推荐,就不得不说协同过滤,它几乎在每一个推荐系统中都会用到。基本的算法非常简单,但是要获得更好的效果,往往需要根据具体的业务做一些差异化的处理。清除作弊、刷单、代购等噪声数据。这些数据的存在会严重影响算法的效果,因此要在第一步的数据清洗中就将这些数据剔除。合理选取训练数据。选取的训练数据的时间窗口不宜过长,当然也不能过短。具体的窗口期数值需要经过多次的实验来确定。同时可以考虑引入时间衰减,因为近期的用户行为更能反映用户接下来的行为动作。user-based与item-based相结合。尝试不同的相似度计算方法。在实践中,我们采用了一种称作loglikelihood ratio[1]的相似度计算方法。在mahout中,loglikelihood ratio也作为一种相似度计算方法被采用。下表表示了Event A和Event B之间的相互关系,其中:k11 :Event A和Event B共现的次数k12 :Event B发生,Event A未发生的次数k21 :Event A发生,Event B未发生的次数k22 :Event A和Event B都不发生的次数则logLikelihoodRatio=2 * (matrixEntropy - rowEntropy - columnEntropy)其中rowEntropy = entropy(k11, k12) + entropy(k21, k22)columnEntropy = entropy(k11, k21) + entropy(k12, k22)matrixEntropy = entropy(k11, k12, k21, k22)(entropy为几个元素组成的系统的香农熵)2. location-based对于移动设备而言,与PC端最大的区别之一是移动设备的位置是经常发生变化的。不同的地理位置反映了不同的用户场景,在具体的业务中可以充分利用用户所处的地理位置。在推荐的候选集触发中,我们也会根据用户的实时地理位置、工作地、居住地等地理位置触发相应的策略。根据用户的历史消费、历史浏览等,挖掘出某一粒度的区域(比如商圈)内的区域消费热单和区域购买热单区域消费热单区域购买热单当新的线上用户请求到达时,根据用户的几个地理位置对相应地理位置的区域消费热单和区域购买热单进行加权,最终得到一个推荐列表。此外,还可以根据用户出现的地理位置,采用协同过滤的方式计算用户的相似度。3. query-based搜索是一种强用户意图,比较明确的反应了用户的意愿,但是在很多情况下,因为各种各样的原因,没有形成最终的转换。尽管如此,我们认为,这种情景还是代表了一定的用户意愿,可以加以利用。具体做法如下:对用户过去一段时间的搜索无转换行为进行挖掘,计算每一个用户对不同query的权重。计算每个query下不同deal的权重。当用户再次请求时,根据用户对不同query的权重及query下不同deal的权重进行加权,取出权重最大的TopN进行推荐。4. graph-based对于协同过滤而言,user之间或者deal之间的图距离是两跳,对于更远距离的关系则不能考虑在内。而图算法可以打破这一限制,将user与deal的关系视作一个二部图,相互间的关系可以在图上传播。Simrank[2]是一种衡量对等实体相似度的图算法。它的基本思想是,如果两个实体与另外的相似实体有相关关系,那它们也是相似的,即相似性是可以传播的。5. 实时用户行为目前我们的业务会产生包括搜索、筛选、收藏、浏览、下单等丰富的用户行为,这些是我们进行效果优化的重要基础。我们当然希望每一个用户行为流都能到达转化的环节,但是事实上远非这样。当用户产生了下单行为上游的某些行为时,会有相当一部分因为各种原因使行为流没有形成转化。但是,用户的这些上游行为对我们而言是非常重要的先验知识。很多情况下,用户当时没有转化并不代表用户对当前的item不感兴趣。当用户再次到达我们的推荐展位时,我们根据用户之前产生的先验行为理解并识别用户的真正意图,将符合用户意图的相关deal再次展现给用户,引导用户沿着行为流向下游行进,最终达到下单这个终极目标。目前引入的实时用户行为包括:实时浏览、实时收藏。6. 替补策略虽然我们有一系列基于用户历史行为的候选集触发算法,但对于部分新用户或者历史行为不太丰富的用户,上述算法触发的候选集太小,因此需要使用一些替补策略进行填充。热销单:在一定时间内销量最多的item,可以考虑时间衰减的影响等。好评单:用户产生的评价中,评分较高的item。城市单:满足基本的限定条件,在用户的请求城市内的。子策略融合为了结合不同触发算法的优点,同时提高候选集的多样性和覆盖率,需要将不同的触发算法融合在一起。常见的融合的方法有以下几种:加权型:最简单的融合方法就是根据经验值对不同算法赋给不同的权重,对各个算法产生的候选集按照给定的权重进行加权,然后再按照权重排序。分级型:优先采用效果好的算法,当产生的候选集大小不足以满足目标值时,再使用效果次好的算法,依此类推。调制型:不同的算法按照不同的比例产生一定量的候选集,然后叠加产生最终总的候选集。过滤型:当前的算法对前一级算法产生的候选集进行过滤,依此类推,候选集被逐级过滤,最终产生一个小而精的候选集合。目前我们使用的方法集成了调制和分级两种融合方法,不同的算法根据历史效果表现给定不同的候选集构成比例,同时优先采用效果好的算法触发,如果候选集不够大,再采用效果次之的算法触发,依此类推。候选集重排序如上所述,对于不同算法触发出来的候选集,只是根据算法的历史效果决定算法产生的item的位置显得有些简单粗暴,同时,在每个算法的内部,不同item的顺序也只是简单的由一个或者几个因素决定,这些排序的方法只能用于第一步的初选过程,最终的排序结果需要借助机器学习的方法,使用相关的排序模型,综合多方面的因素来确定。1. 模型非线性模型能较好的捕捉特征中的非线性关系,但训练和预测的代价相对线性模型要高一些,这也导致了非线性模型的更新周期相对要长。反之,线性模型对特征的处理要求比较高,需要凭借领域知识和经验人工对特征做一些先期处理,但因为线性模型简单,在训练和预测时效率较高。因此在更新周期上也可以做的更短,还可以结合业务做一些在线学习的尝试。在我们的实践中,非线性模型和线性模型都有应用。非线性模型目前我们主要采用了非线性的树模型Additive Groves[4](简称AG),相对于线性模型,非线性模型可以更好的处理特征中的非线性关系,不必像线性模型那样在特征处理和特征组合上花费比较大的精力。AG是一个加性模型,由很多个Grove组成,不同的Grove之间进行bagging得出最后的预测结果,由此可以减小过拟合的影响。每一个Grove有多棵树组成,在训练时每棵树的拟合目标为真实值与其他树预测结果之和之间的残差。当达到给定数目的树时,重新训练的树会逐棵替代以前的树。经过多次迭代后,达到收敛。线性模型目前应用比较多的线性模型非Logistic Regression莫属了。为了能实时捕捉数据分布的变化,我们引入了online learning,接入实时数据流,使用google提出的FTRL[5]方法对模型进行在线更新。主要的步骤如下:在线写特征向量到HBaseStorm解析实时点击和下单日志流,改写HBase中对应特征向量的label通过FTRL更新模型权重将新的模型参数应用于线上2. 数据采样:对于点击率预估而言,正负样本严重不均衡,所以需要对负例做一些采样。负例:正例一般是用户产生点击、下单等转换行为的样本,但是用户没有转换行为的样本是否就一定是负例呢?其实不然,很多展现其实用户根本没有看到,所以把这样样本视为负例是不合理的,也会影响模型的效果。比较常用的方法是skip-above,即用户点击的item位置以上的展现才可能视作负例。当然,上面的负例都是隐式的负反馈数据,除此之外,我们还有用户主动删除的显示负反馈数据,这些数据是高质量的负例。去噪:对于数据中混杂的刷单等类作弊行为的数据,要将其排除出训练数据,否则会直接影响模型的效果。3. 特征在我们目前的重排序模型中,大概分为以下几类特征:deal(即团购单,下同)维度的特征:主要是deal本身的一些属性,包括价格、折扣、销量、评分、类别、点击率等user维度的特征:包括用户等级、用户的人口属性、用户的客户端类型等user、deal的交叉特征:包括用户对deal的点击、收藏、购买等距离特征:包括用户的实时地理位置、常去地理位置、工作地、居住地等与poi的距离对于非线性模型,上述特征可以直接使用;而对于线性模型,则需要对特征值做一些分桶、归一化等处理,使特征值成为0~1之间的连续值或01二值。总结以数据为基础,用算法去雕琢,只有将二者有机结合,才会带来效果的提升。对我们而言,以下两个节点是我们优化过程中的里程碑:将候选集进行融合:提高了推荐的覆盖度、多样性和精度引入重排序模型:解决了候选集增加以后deal之间排列顺序的问题原文链接:
&(责编/周建丁)&
推荐阅读相关主题:
CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
相关热门文章spark机器学习笔记:(七)用Spark Python构建聚类模型
声明:版权所有,转载请联系作者并注明出处&&
博主简介:风雪夜归子(英文名:Allen),机器学习算法攻城狮,喜爱钻研Meachine Learning的黑科技,对Deep Learning和Artificial Intelligence充满兴趣,经常关注Kaggle数据挖掘竞赛平台,对数据、Machine Learning和Artificial Intelligence有兴趣的童鞋可以一起探讨哦,个人CSDN博客:
1 从数据中提取正确的特征
类似大多数机器学习模型,K-均值聚类需要数值向量作为输入,于是用于分类和回归的特征提取和变换方法也适用于聚类。
K-均值和最小方差回归一样使用方差函数作为优化目标,因此容易受到离群值(outlier)和较大方差的特征影响。对于回归和分类问题来说,上述问题可以通过特征的归一化和标准化来解决,同时可能有助于提升性能。但是某些情况我们可能不希望数据被标准化,比如根据某个特定的特征找到对应的类簇。
从MovieLens数据集提取特征
本文中,我们继续使用博文 http://blog.csdn.net/u/article/details/ 中的电影打分数据集,这个数据集主要分为三个部分:第一个是电影打分的数据集(在u.data文件中), 第二个是用户数据(u.user),第三个是电影数据(u.item)。除此之外,我们从题材文件中获取了每个电影的题材(u.genre)。
查看电影数据集
movies = sc.textFile('/Users/youwei.tan/ml-100k/u.item')
print movies.take(1)
输出结果:
[u'1|Toy Story (1995)|01-Jan-1995||/M/title-exact?Toy%20Story%20(|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0']
到目前为止,我们既知道电影的名称,也将电影按题材分类。那为什么还需要对电影数据进行聚类呢?具体原因有两个。
 第一,因为我们知道每部电影的题材标签,所以可以用这些标签评估聚类模型的性能。
 第二,我们希望基于其他属性或特征对电影进行分类,而不单单是题材。
本例中,除了题材和标题,我们还有打分数据用于聚类。之前,我们已经根据打分数据建立了一个矩阵分解模型,这个模型由一系列用户和电影因素向量组成。
我们可以思考怎样在一个新的隐式特征空间中用电影相关的因素表示一部电影,反过来说就是用隐式特征表示打分矩阵中一些特定形式的结构。每个隐式特征无法直接解释,因为它们表示一些可以影响用户对电影打分行为的隐式结构。可用的因素有用户对题材的偏好、演员和导演或者电影的主题等。因此,如果将电影的相关因素向量表示作为聚类模型的输入,我们可以得到基于用户实际打分行为的分类而不是人工的题材分类。同样,我们可以在打分行为的隐式特征空间中用用户相关因素表示一个用户,因此对用户向量进行聚类,就得到了基于用户打分行为的聚类结果。
1.1 提取电影的题材标签
在进一步处理之前,我们先从u.genre文件中提取题材的映射关系。根据之前对数据集的输出结果来看,需要将题材的数字编号映射到可读的文字版本。查看u.genre开始几行数据:
genres = sc.textFile('/Users/youwei.tan/ml-100k/u.genre')
print genres.take(5)
for line in genres.take(5):
print line
输出结果:
[u'unknown|0', u'Action|1', u'Adventure|2', u'Animation|3', u&Children's|4&]
Adventure|2
Animation|3
Children's|4
上面输出的数字表示相关题材的索引,比如0是unknown的索引。索引对应了每部电影关于题材的特征二值子向量(即前面数据中的0和1)。
为了提取题材的映射关系,我们对每一行数据进行分割,得到具体的&题材,索引&键值对。注意处理过程中需要处理最后的空行,不然会抛出异常(见代码中高亮部分):
#为电影题材编码
genre_map = genres.filter(lambda x: len(x) & 0).\
map(lambda line : line.split('|')).\
map(lambda x:(x[1],x[0])).collectAsMap()
print '构造出的电影题材的编码字典:',genre_map
输出结果:
构造出的电影题材的编码字典: {u'11': u'Horror', u'10': u'Film-Noir', u'13': u'Mystery', u'12': u'Musical', u'15': u'Sci-Fi', u'14': u'Romance', u'17': u'War', u'16': u'Thriller', u'18': u'Western', u'1': u'Action', u'0': u'unknown', u'3': u'Animation', u'2': u'Adventure',
u'5': u'Comedy', u'4': u&Children's&, u'7': u'Documentary', u'6': u'Crime', u'9': u'Fantasy', u'8': u'Drama'}
接下来,我们需要为电影数据和题材映射关系创建新的RDD,其中包含电影ID、标题和题材。当我们用聚类模型评估每个电影的类别时,可以用生成的RDD得到可读的输出。
接下来的代码中,我们对每部电影提取相应的题材(是Strings形式而不是Int索引)。
提取电影的title和genres:
movies=sc.textFile('/Users/youwei.tan/ml-100k/u.item')
print '电影数据集的第一条数据:',movies.first()
#查看电影的标题
movies_title
= movies.map(lambda x: x.split('|')).map(lambda x: x[1])
print '电影标题:',movies_title.take(5)
#查看电影的题材, 0表示不属于该题材, 1表示属于该题材
movies_genre = movies.map(lambda x: x.split('|')).map(lambda x: x[5:])
print '电影的题材:'
print movies_genre.take(5)
输出结果:
电影数据集的第一条数据: 1|Toy Story (1995)|01-Jan-1995||/M/title-exact?Toy%20Story%20(|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0
电影标题: [u'Toy Story (1995)', u'GoldenEye (1995)', u'Four Rooms (1995)', u'Get Shorty (1995)', u'Copycat (1995)']
电影的题材:
[[u'0', u'0', u'0', u'1', u'1', u'1', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0'], [u'0', u'1', u'1', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'1', u'0', u'0'], [u'0', u'0', u'0', u'0',
u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'1', u'0', u'0'], [u'0', u'1', u'0', u'0', u'0', u'1', u'0', u'0', u'1', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'0'], [u'0', u'0', u'0', u'0', u'0', u'0', u'1', u'0',
u'1', u'0', u'0', u'0', u'0', u'0', u'0', u'0', u'1', u'0', u'0']]
根据电影的题材编码字典genre_map,从上述结果可以知道,第一部电影属于Animation,Children's,Comedy题材.
def func(rdd):
genres = rdd[5:]
#提取题材特征
genres_assigned = zip(genres, range(len(genres)))
index = []
#存储题材特征数值为1的特征索引号
for genre,idx in genres_assigned:
if genre=='1':
index.append(idx)
index_val = [genre_map[str(i)] for i in index]
#根据编码字典找出索引的相应题材名
index_val_str = ','.join(index_val)
return (int(rdd[0]),rdd[1]+','+index_val_str)
titles_and_genres = movies.map(lambda x: x.split('|')).map(lambda x:func(x))
print '前5部电影的标题和相应的题材类型:',titles_and_genres.take(5)
输出结果:
前5部电影的标题和相应的题材类型: [(1, u&Toy Story (1995),Animation,Children's,Comedy&), (2, u'GoldenEye (1995),Action,Adventure,Thriller'), (3, u'Four Rooms (1995),Thriller'), (4, u'Get Shorty (1995),Action,Comedy,Drama'), (5, u'Copycat (1995),Crime,Drama,Thriller')]
1.2 训练推荐模型
要获取用户和电影的因素向量,首先需要训练一个新的推荐模型。我们在博文 & &中做过类似的事情,因此接下来使用相同的步骤:
from pyspark.mllib.recommendation import ALS
from pyspark.mllib.recommendation import Rating
raw_data = sc.textFile(&/Users/youwei.tan/ml-100k/u.data&)
#数据集u.data中四个字段分别表示用户ID, 电影ID, 评分, 时间戳
print 'raw data sample:', raw_data.map(lambda x : x.split('\t')).take(3)
raw_ratings = raw_data.map(lambda x:x.split('\t')[:3])
ratings = raw_ratings.map(lambda x: Rating(x[0], x[1], x[2]))
ratings.cache()
print 'rating data sample:',ratings.take(3)
#训练推荐模型
als_model = ALS.train(ratings,50,5,0.1)
raw data sample: [[u'196', u'242', u'3', u''], [u'186', u'302', u'3', u''], [u'22', u'377', u'1', u'']]
rating data sample: [Rating(user=196, product=242, rating=3.0), Rating(user=186, product=302, rating=3.0), Rating(user=22, product=377, rating=1.0)]
from pyspark.mllib.linalg import Vectors
print 'productFeatures的第一条数据:',als_model.productFeatures().take(1)
movie_factors = als_model.productFeatures().map(lambda (id,factor): (id,Vectors.dense(factor)))
print 'movie_factors的第一条数据:',movie_factors.first()
movie_vectors = movie_factors.map(lambda (id,vec):vec)
user_factors = als_model.userFeatures().map(lambda (id,factor):(id,Vectors.dense(factor)))
print 'user_factors的第一条数据:',user_factors.first()
user_vectors = user_factors.map(lambda (id, vec):vec)
输出结果:
productFeatures的第一条数据: [(4, array('d', [-0.08, -0..172, -0.072, 0.6, 0.288, -0.67438,
0.00903, -0.966, -0.803, 0.1395, -0.51794, -0.93677, -0.74045, -0.08859, -0.785866, -0.22266,
0.19525, -0.072, -0.8291, -0.12, -0.45, -0.04468, -0.28412, -0.02951, -0.59451,
-0.54846, -0.053, -0.821, -0.70856, -0.19672, -0.65754, -0.22, 0.8,
-0.23602, -0.207]))]
movie_factors的第一条数据: (4, DenseVector([-0.6, -0.3, 0.1906, -0.4, 0.245, 0.8, -0.2, -0.1869, -0.9, 0.4476, -0.0626, -0.3309, -0.6, -0.0959, -0.0208, -0.1, -0.305, 0.0172,
-0.2867, -0., -0., -0.1621, -0.1276, -0.1064, -0.1153, -0.086, -0.2955, -0.9, -0.2294, -0.0911, -0.5, -0.376, 0.3, 0.5568, -0.1411, -0.9]))
user_factors的第一条数据: (4, DenseVector([-0.9, -0.3194, -0.4114, -0.0362, -0.8, 0.8531, -0.9, 0.0442, -0.8, -0.8, 0.7738, -0.2748, -0.4775, -0.4801, -0.2, -0.5152, -0.47, 0.7984, -0.158, 0.0298,
0.2694, -0.3, -0.1, -0.9, 0.3014, -0.0691, -0.0043, -1.2, -0.9, -0.3126, -0.4778, -0.2319, -0.1191, -0., 0.341, -0.7563, -0.1962, -0.3569]))
1.3 归一化
在训练聚类模型之前,有必要观察一下输入数据的相关因素特征向量的分布,这可以告诉我们是否需要对训练数据进行归一化。具体做法和第5章一样,我们使用MLlib中的RowMatrix进行各种统计,代码实现如下:
from pyspark.mllib.linalg.distributed import RowMatrix
moive_matrix = RowMatrix(movie_vectors)
user_matrix = RowMatrix(user_vectors)
from pyspark.mllib.stat import MultivariateStatisticalSummary
desc_moive_matrix = MultivariateStatisticalSummary(moive_matrix.rows)
desc_user_matrix = MultivariateStatisticalSummary(user_matrix.rows)
print 'Movie factors mean:',desc_moive_matrix.mean()
print 'Movie factors variance:',desc_user_matrix.mean()
print 'User factors mean:',desc_moive_matrix.variance()
print 'User factors variance:',desc_user_matrix.variance()
输出结果:
Movie factors mean: [ -2.&& 1.& -1.&& 6.
& -4.& -3.&& 1.&& 3.
&& 7.&& 6.& -1.&& 1.
& -3.&& 6.&& 1.&& 2.
& -1.& -1.& -1.& -1.
& -8.& -1.& -2.&& 3.
& -2.& -7.&& 2.& -3.
&& 1.& -3.&& 1.& -1.
&& 7.& -3.& -7.& -2.
& -4.& -1.&& 1.& -4.
& -1.& -8.& -5.& -1.
&& 9.&& 1.&& 3.& -1.
& -1.& -1.]
Movie factors variance: [-0...1727202&& 0...
&-0.1042916&& 0.....
&-0..0220296&& 0.0087616& -0.0980786& -0..
&-0....0606273&& 0..
&-0.1358626& -0.2620565 ]
User factors mean: [ 0......
& 0...0229145&& 0...
& 0......0260555
& 0.....0325904&& 0.
User factors variance: [ 0..0540869&& 0....
& 0....0367134&& 0..
& 0......036413
从结果来看,没有发现特别的离群点会影响聚类结果,因此本例中没有必要进行归一化。
2 训练聚类模型
在MLlib中训练K-均值的方法和其他模型类似,只要把包含训练数据的RDD传入KMeans对象的train方法即可。注意,因为聚类不需要标签,所以不用LabeledPoint实例,而是使用特征向量接口,即RDD的Vector数组即可。
用MovieLens数据集训练聚类模型
MLlib的K-均值提供了随机和K-means||两种初始化方法,后者是默认初始化。因为两种方法都是随机选择,所以每次模型训练的结果都不一样。
K-均值通常不能收敛到全局最优解,所以实际应用中需要多次训练并选择最优的模型。MLlib提供了完成多次模型训练的方法。经过损失函数的评估,将性能最好的一次训练选定为最终的模型。
from pyspark.mllib.clustering import KMeans
num_clusters = 5
num_iterations = 20
num_runs =3
movie_cluster_model = KMeans.train(movie_vectors,num_clusters, num_iterations, num_runs)
movie_cluster_model_coverged = KMeans.train(movie_vectors,num_clusters,100)
user_cluster_model = KMeans.train(user_vectors,num_clusters,num_iterations, num_runs)
predictions = movie_cluster_model.predict(movie_vectors)
print '对前十个样本的预测标签为:'+&,&.join([str(i) for i in predictions.take(10)])
输出结果:
对前十个样本的预测标签为:4,4,4,3,4,3,1,3,0,2
print 'movie_factors的第一条数据:',movie_factors.first()
print '========================'
print 'titles_and_genres的第一条数据:',titles_and_genres.first()
titles_factors = titles_and_genres.join(movie_factors)
print '========================'
print 'titles_factors的第一条数据:',titles_factors.first()
输出结果:
movie_factors的第一条数据: (4, DenseVector([-0.6, -0.3, 0.1906, -0.4, 0.245, 0.8, -0.2, -0.1869, -0.9, 0.4476, -0.0626, -0.3309, -0.6, -0.0959, -0.0208, -0.1, -0.305, 0.0172,
-0.2867, -0., -0., -0.1621, -0.1276, -0.1064, -0.1153, -0.086, -0.2955, -0.9, -0.2294, -0.0911, -0.5, -0.376, 0.3, 0.5568, -0.1411, -0.9]))
========================
titles_and_genres的第一条数据: (1, u&Toy Story (1995),Animation,Children's,Comedy&)
========================
titles_factors的第一条数据: (1536, (u'Aiqing wansui (1994),Drama', DenseVector([-0.2953, -0.2653, -0.2, -0.0061, -0.2, 0.4489, -0.5, -0.086, 0.2009, -0.1806, -0.9, 0.4077, -0.1397, -0.4622, -0.2987, -0.4978, -0.0521, -0.3738,
-0.7, -0.5649, -0.4, -0.2, -0.3913, -0.3867, -0.0565, -0.131, -0.1507, -0.5, -0.9, 0.2411, -0.3772, -0.2846, -0.1636, -0.3747, -0.0247, -0.1281, -0.9, -0.1743, -0.6])))
#对每个电影计算其特征向量与类簇中心向量的距离
def func2(rdd):
id,(name_genres,vec) = rdd
pred = movie_cluster_model.predict(vec)
cluster_center = movie_cluster_model.clusterCenters[pred]
cluster_center_vec = Vectors.dense(cluster_center)
dist = vec.squared_distance(cluster_center_vec)
return u'电影' + str(id) + u'的题材类型是' + name_genres + ',' + u'聚类模型预测的标签是' + str(pred)+ ',' + \
u'与聚类所属类别中心的距离是' + str(dist)
movies_assigned = titles_factors.map(lambda x:func2(x))
for i in movies_assigned.take(5):
输出结果:
电影1536的题材类型是Aiqing wansui (1994),Drama,聚类模型预测的标签是4,与聚类所属类别中心的距离是1.
电影1026的题材类型是Lay of the Land, The (1997),Comedy,Drama,聚类模型预测的标签是2,与聚类所属类别中心的距离是1.
电影516的题材类型是Local Hero (1983),Comedy,聚类模型预测的标签是4,与聚类所属类别中心的距离是1.
电影6的题材类型是Shanghai Triad (Yao a yao yao dao waipo qiao) (1995),Drama,聚类模型预测的标签是4,与聚类所属类别中心的距离是2.
电影1032的题材类型是Little Big League (1994),Children's,Comedy,聚类模型预测的标签是2,与聚类所属类别中心的距离是1.
3 评估聚类模型的性能
3.1 内部评价指标
通用的内部评价指标包括WCSS(我们之前提过的K-元件的目标函数)、Davies-Bouldin指数、Dunn指数和轮廓系数(silhouette coefficient)。所有这些度量指标都是使类簇内部的样本距离尽可能接近,不同类簇的样本相对较远。
3.2 外部评价指标
因为聚类被认为是无监督分类,如果有一些带标注的数据,便可以用这些标签来评估聚类模型。可以使用聚类模型预测类簇(类标签),使用分类模型中类似的方法评估预测值和真实标签的误差(即真假阳性率和真假阴性率)。
具体方法包括Rand measure、F-measure、雅卡尔系数(Jaccard index)等。
3.3 在MovieLens数据集计算性能
movie_cost = movie_cluster_model.computeCost(movie_vectors)
user_cost = user_cluster_model.computeCost(user_vectors)
print &WCSS for movies: %f&%movie_cost
print &WCSS for users: %f&%user_cost
输出结果:
WCSS for movies:
WCSS for users:
4 聚类模型参数调优
不同于以往的模型,K-均值模型只有一个可以调的参数,就是K,即类中心数目。通过交叉验证选择K
类似分类和回归模型,我们可以应用交叉验证来选择模型最优的类中心数目。这和监督学习的过程一样。需要将数据集分割为训练集和测试集,然后在训练集上训练模型,在测试集上评估感兴趣的指标的性能。如下代码用60/40划分得到训练集和测试集,并使用MLlib内置的WCSS类方法评估聚类模型的性能:
train_test_split_movies = movie_vectors.randomSplit([0.6,0.4],123)
train_movies = train_test_split_movies[0]
test_movies = train_test_split_movies[1]
for k in [2,3,4,5,10,20]:
k_model = KMeans.train(train_movies, num_iterations, k, num_runs)
cost = k_model.computeCost(test_movies)
print 'WCSS for k=%d : %f'%(k,cost)
输出结果:
WCSS for k=2 : 766.646663
WCSS for k=3 : 769.816755
WCSS for k=4 : 770.081729
WCSS for k=5 : 763.681320
WCSS for k=10 : 761.307839
WCSS for k=20 : 760.734158
从结果可以看出,随着类中心数目增加,WCSS值会出现下降,然后又开始增大。另外一个现象,K-均值在交叉验证的情况,WCSS随着K的增大持续减小,但是达到某个值后,下降的速率突然会变得很平缓。这时的K通常为最优的K值(这称为拐点)。根据预测结果,我们选择最优的K=10。需要说明是,模型计算的类簇需要人工解释(比如前面提到的电影或者顾客聚类的例子),并且会影响K的选择。尽管较大的K值从数学的角度可以得到更优的解,但是类簇太多就会变得难以理解和解释。为了实验的完整性,我们还计算了用户聚类在交叉验证下的性能:
train_test_split_movies = user_vectors.randomSplit([0.6,0.4],123)
train_users = train_test_split_movies[0]
test_users = train_test_split_movies[1]
for k in [2,3,4,5,10,20]:
k_model = KMeans.train(train_users,num_iterations,k,num_runs)
cost = k_model.computeCost(test_users)
print 'WCSS for k=%d : %f'%(k,cost)
输出结果:
WCSS for k=2 : 569.221509
WCSS for k=3 : 569.650019
WCSS for k=4 : 558.117543
WCSS for k=5 : 568.013497
WCSS for k=10 : 560.014328
WCSS for k=20 : 567.601320
本文中,我们研究了一种新的模型, 它可以在无标注数据中进行学习,即无监督学习。我们学习了如何处理需要的输入数据、特征提取,以及如何将一个模型(我们用的是推荐模型)的输出作为另外一个模型(K-均值聚类模型)的输入。最后,我们评估聚类模型的性能时,不仅进行了类簇人工解释,也使用具体的数学方法进行性能度量。
下一篇博文,我们将讨论其他类型的无监督学习,在数据中选择保留最重要的特征或者应用其他降维模型。
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?

我要回帖

 

随机推荐