转自http://blog.youkuaiyun.com/cnweike/article/details/47167411
从本质上来说,这是一个分类问题,类似于通过邮件内容来推测垃圾邮件,通过用户的相关信息来推测用户是否会拖欠贷款,而通过用户的APP安装列表来推测用户的性别也是一个类似的问题。
对于贝叶斯算法来说,我们首先需要一个训练集数据,这个训练集是一个已经打好标签的数据。而要对一堆的数据打标签,人工来做是不太合适的,在这里需要结合人类与计算机各自的优势,来半自动化的识别出比较明显的有性别倾向的APP的列表,再通过这些(有性别标识的)APP在用户的安装列表中所占的比例来判定出性别特征比较明显的用户,并为这些用户打上性别的标签,而这些用户以及其安装的APP列表就成为我们最终使用的贝叶斯算法的训练数据。
1. 首先要筛选出男女各自的代表性APP列表集合
首先,受益于某些APP的命名以及其功能明显的性别倾向性,或者说得益于男女之间的差异,我们能够分别为男女各自定义一组APP名的关键词用来区别男女的用户。比如,对于女性来说,类似于“美丽说”,“美颜相机”,“蘑菇街”,“可爱”之类的关键词就比较能代表其女性的性别倾向性;而类似于“美女”,“体育”,“足球篮球”,“汽车”,“赛车”等关键字又是男性比较有代表性APP关键字。所以最终,我们形成了类似于下面的一组数据,这组数据由人工来完成:
- 性感美女 男性应用
- Sexy 男性应用
- 汽车 男性应用
- 体育足球篮球 男性应用
- 足球 男性应用
- 篮球 男性应用
- 男人必玩 男性应用
- 战斗 男性应用
- 赛车 男性应用
- 快播 男性应用
- 配件 男性应用
- 暴力 男性应用
- 坦克 男性应用
- 美图秀秀 女性应用
- 大姨妈经期 女性应用
- 化妆美容 女性应用
- 美颜 女性应用
- 宝宝 女性应用
- 儿童 女性应用
- 美丽说 女性应用
- 美柚 女性应用
- 蘑菇街 女性应用
- 可爱 女性应用
通过,这组人工整理的数据,我们要将其与APP名称的库中的所有的APP名进行模糊匹配,继而找出所有的能够打上性别标签的APP。为此,我们可以采用ES+IK中文分词插件的方式,将我们的关键词以及其类型录入其中,其中的关键词会被IK插件分词,而类型信息将不会被处理,为此我们需要为ES的索引做以下的设定:
- curl -XPUT localhost:9200/appgender -d '{
- "settings" : {
- "analysis" : {
- "analyzer" : {
- "ik" : {
- "tokenizer" : "ik"
- }
- }
- }
- },
- "mappings" : {
- "appgender" : {
- "dynamic" : true,
- "properties" : {
- "appname" : {
- "type" : "string",
- "analyzer" : "ik"
- },
- "genre": {
- "type": "string",
- "index": "not_analyzed"
- }
- }
- }
- }
- }'
但是,通过以上的工作之后,还需要根据需要来为IK插件的字典添加自己定义的词,这样用来优化最终的匹配效果,比如“美图秀秀”这个关键词,如果IK字典中没有“美图”这样的词汇,那么其就可能被分词成“美”和“图”两个字,这样在匹配的时候就会扩大匹配的范围,导致匹配结果失真;但是同时也要注意相反的方向,使用ES的目的就是模糊匹配,也就是要扩大匹配范围,但是正如我们前面说的,要掌握好度,既要保证有足够数量的APP被打上性别标签,也要注意避免太多的APP被打上标签(以致正确率下降)。我们可以通过Python ES API来将这些人工组织的数据写入ES的appgender索引,代码非常简单:
- #coding=utf-8
- from elasticsearch import Elasticsearch
- if __name__ == '__main__':
- es = Elasticsearch()
- with open('data/gender_app_feature.tsv') as appgenre:
- for line in appgenre:
- appname, genre = line.strip().split('\t')
- es.index(index="appgender", doc_type="appgender", body={"appname": appname, "genre": genre})
2. 就是通过以上的ES中的appgender索引中的数据,来匹配APP库中的APP名,并将其结果保存下来,APP库的形式类似于:
- GO锁屏 1975242
- 360省电王 2142434
- 录音机 2169688
- 天天爱消除 2450275
- 系统更新 2517301
- Messaging 2533345
- 今日头条 2625085
- 墨迹天气 2762610
- 同花顺 3076707
- QQ空间 3131907
- 漏洞修复 3255717
- 拨号盘 3609291
- 开心消消乐 3638686
第一列是APP名,第二列是APP的用户数,或者出现次数。而匹配工作也非常简单:
- #coding=utf-8
- import sys
- reload(sys)
- sys.setdefaultencoding('utf-8')
- from elasticsearch import Elasticsearch
- if __name__ == '__main__':
- es = Elasticsearch()
- with open('data/applist.log') as f:
- for line in f:
- line = line.replace('?', '')
- if line.strip():
- t = line.strip().split(' ')
- if len(t) == 2:
- appname, num = t
- try:
- num = int(num)
- if num < 5: continue
- # print 'Processing: [%s], and its type is [%s]' % (appname, type(appname))
- r = es.search(index='appgender', doc_type='appgender', body={'query': {'match': {'appname': appname}}})
- hits = r['hits']['hits']
- if hits:
- hit = hits[0]
- matched_app = hit['_source']['appname']
- matched_genre = hit['_source']['genre']
- hit_score = hit['_score']
- record = u'%s\t%s\t%s\t%d\t%s' % (appname, matched_app, matched_genre, num, hit_score)
- print record.encode('utf-8')
- except Exception, e:
- sys.stderr.write(str(e) + '\n')
通过以上的简单程序,我们就得到类似以下的结果:
- 玩库赚流量 男人必玩 男性应用 14423 0.17464119
- 儿童拖拖乐游戏 儿童 女性应用 14699 0.34928238
- 4D极速沙滩赛车 赛车 男性应用 15583 0.34928238
- 宝宝树时光 宝宝 女性应用 16620 0.5457385
- PPTV第1体育 体育足球篮球 男性应用 25234 0.17464119
- 汽车报价-汽车之家出品 汽车 男性应用 51951 0.34928238
- 汽车在线 汽车 男性应用 68215 1.03399
- 魔秀桌面 美图秀秀 女性应用 88965 0.34108657
- 球探体育比分 体育足球篮球 男性应用 90670 0.17464119
- 新浪体育 体育足球篮球 男性应用 102179 0.516995
- 美丽说 美丽说 女性应用 108899 2.873763
- ......
一共有5列,第一列是APP库中的某个APP,第二列是匹配到的ES中的关键词,第三列是匹配到的ES关键词所对应的分类(这里就两类,男性应用和女性应用),第四列是APP库中所标记的APP的用户数,可以用这个数来调整结果,第五列是匹配度得分,当然也可以通过这个得分来调整结果。
3. 通过以上的数据,我们可以看到某个用户的APP列表与上面结果的一个交集,有了个这个交集,我们就可以看出这个用户的“男女比重”,你可以通过这个指标定一个阀值(比如说,男性应用的比重在50%以上的定性为男性,否则为女性)来将其标定为男性或女性用户;如果交集为空,那么我们就不能确定这个用户的性别,那我们就不处理它。通过本步的处理,我们能够确定一部分的用户的性别特征,再将这些用户的APP列表展开,我们就能够得到如下的形式的数据:
- 0052c56f854f363e72a66b4c174e4050 女性应用 搜索
- 0052c56f854f363e72a66b4c174e4050 女性应用 美图秀秀
- 0056c01c656c00093a39be1ea783d060 女性应用 腾讯新闻
- 0056c01c656c00093a39be1ea783d060 女性应用 微信
- 0056c01c656c00093a39be1ea783d060 女性应用 快乐炸金花
- 0056c01c656c00093a39be1ea783d060 女性应用 欢乐斗地主
- 0056c01c656c00093a39be1ea783d060 女性应用 欢乐斗牛
- 0056c01c656c00093a39be1ea783d060 女性应用 百度
- 0056c01c656c00093a39be1ea783d060 女性应用 蘑菇街
- 0056c01c656c00093a39be1ea783d060 女性应用 金山电池医生
- 005cdf6d9f672ba5cbec4574586a942d 男性应用 快播
- 0088d4af42a65f864fc356405c73d39c 男性应用 牛仔美女
- 0088d4af42a65f864fc356405c73d39c 男性应用 美女刮刮乐
- 008a882683313fc6f64429a6396fed9c 女性应用 爱游戏
- 008a882683313fc6f64429a6396fed9c 女性应用 蘑菇街
- 00981a6f972fca3d1eccc942015054d2 女性应用 美图秀秀
- 00a17e719a86d8f11d534de346e0fcce 女性应用 微信
- 00a17e719a86d8f11d534de346e0fcce 女性应用 美图秀秀
- 00a953e662ef896ecde30a9a76e57e96 男性应用 天天基金网
第一列是用户的标识,第二列是根据“男女应用比重”所确定出的本用户的性别,而第三列就是这个用户展开后的全部应用。
4. 最后,就是通过第三步的数据,进行一些简单的统计,以满足贝叶斯模型的需求:
- #coding=utf-8
- from collections import defaultdict
- TRAIN_DATA_FILE = 'data/genderapplist.log'
- if __name__ == '__main__':
- gender_dist = defaultdict(set)
- app_dist = defaultdict(lambda: defaultdict(int))
- app_cond_male_prob = defaultdict(float)
- app_cond_female_prob = defaultdict(float)
- with open(TRAIN_DATA_FILE) as train_data:
- for line in train_data:
- line = line.strip()
- cells = line.split('\t')
- if len(cells) == 3:
- imei, gender, appname = cells
- gender = 'female' if gender == '女性应用' else 'male'
- gender_dist[gender].add(imei)
- app_dist[gender][appname] += 1
- # calculate P(male)
- pmale = float(len(gender_dist['male'])) / (len(gender_dist['male']) + len(gender_dist['female']))
- for appname in app_dist['male']:
- app_cond_male_prob[appname] = float(app_dist['male'].get(appname)) / len(gender_dist['male'])
- for appname in app_dist['female']:
- app_cond_female_prob[appname] = float(app_dist['female'].get(appname)) / len(gender_dist['female'])
- def gender_prob(applist):
- male_prob = pmale
- female_prob = (1 - pmale)
- for appname in applist:
- mp = app_cond_male_prob.get(appname, 0)
- fp = app_cond_female_prob.get(appname, 0)
- if mp == 0:
- mp = (0 + 1.) / (len(gender_dist['male']) + len(gender_dist['male']))
- if fp == 0:
- fp = (0 + 1.) / (len(gender_dist['female']) + len(gender_dist['female']))
- male_prob = male_prob * mp
- female_prob = female_prob * fp
- #print '-----------', male_prob, female_prob
- print 'user male probability %f' % (male_prob / (male_prob + female_prob))
- print male_prob, female_prob
- appsamples = ["Polaris Viewer 4.1",'QQ', 'UC浏览器', '住这儿', '唱吧', '喜马拉雅', '天天爱消除', '安卓授权管理', '家长定位小孩', '微信', '快播', '我查查', '手机淘宝', '手机管家', '招商银行', '搜索', '旅游攻略', '时光网', '智行火车票', '百度', '网易新闻', '腾讯微博', '航班管家', '视频盒子', '记事本']
- gender_prob(appsamples)
在这里需要注意以下几个事实:
1. 我们的输入是一个用户的APP列表,输出是这个用户为男性的可能性
2. 输入类似于X = ("美图秀秀",'搜索', '腾讯新闻', '微信', '快乐炸金花', '欢乐斗地主', '欢乐斗牛', '百度', '蘑菇街', '金山电池医生'),而我们输出就是P(male|X)这样的条件概率。
3. 完整的贝叶斯公式为 P(male|X) = P(male) * P(X|male) / P(X),其中P(male)是男性的概率;P(male|X)是安装了这一堆APP的情况下此用户是男性的概率;P(X)是着堆APP被安装的概率。 但是对于一组固定的X来说,P(X)是个常量,我们可以不计算它。
4. 由于P(X)不用计算,我们必须分别计算出P(male) * P(X|male)和P(female) * P(X|female)
5. 由于训练数据集中P(appx|male)=0的情况,可以使用贝叶斯m估计的方法处理
通过以上的方法,最终就可以通过一个用户的APP的安装列表信息,估计这个用户的性别可能性。
如有表述疏漏,欢迎批评指正。