2019年华为算法精英大赛--用户人口属性预测组比赛覆盘

距离比赛时间已经过去了将近一年。作为第一次参加的算法类数据挖掘比赛,最终名次37,获得了前Top3%的成绩,算是不枉费研一暑假两个月的努力吧,作为一个渣渣,自然比不上排名靠前的很多大神,也比不上和我同时参加比赛得奖,直博交大的的师兄。近期由于面临实习的原因,所以想好好整理一下当时比赛的思绪,希望为自己之后的比赛可以有些借鉴,如果可以让各位读者有豁然开朗的地方,那也算是一点小小的贡献吧。
在这里插入图片描述

比赛介绍

官网链接:算法精英大赛
比赛有两个赛题,赛事题目如下:

赛题一:账号用户人口属性预测

  • 尝试解决的问题:对于手机设备厂商,获取当前手机用户的人口属性信息(demographics)非常困难,当前华为手机3.5亿用户中,大概只有5000万用户的性别和年龄信息,如何基于用户的手机及使用偏好准确地预测其人口属性信息是提升个性化体验、构建精准用户画像的基础

赛题二:CTR预测

  • 任务描述:基于用户对广告任务的历史行为和广告任务属性,选择合适的算法预测用户在特定上下文下对某个广告任务的点击概率。
  • 尝试解决的问题:提高广告点击转化率预估的准确性
  • 难点:广告任务相对可推用户数量非常少;有行为的广告任务较少,数据非常稀疏;广告任务在投放周期的不同阶段转化率差异较大;存在误点击噪音数据;有效特征识别困难

因为当初是第一次参加此类的算法比赛,在咨询导师的前提下,我们决定选择难度稍小的人口属性预测比赛。所以我们的赛题就这么确定了。

初次探索

我们确定好了题目之后,就先来看了数据集。
主要分为5个数据集。分别是

  • User_basic_info.csv:包含了用户的性别、常住地址、手机型号、手机ram容量、ram剩余容量比、rom容量、rom剩余容量比、手机颜色、字体大小、上网类型、移动运营商和手机系统版本13个字段.
  • User_behavior_info.csv:包含了用户标识、手机A特征使用次数、手机B特征使用次数、手机C特征使用次数、手机D特征使用次数、手机E特征使用次数、手机F特征使用次数、手机G特征使用情况共9个字段。
  • User_app_actived.csv:包含了用户标识、应用标识共2个字段。
  • User_app_usage.csv:包含了用户标识、应用标识、使用时长、打开次数、使用日期共5个字段。
  • app_info.csv:包含了应用标识、应用类型共2个字段。
  • age_train.csv:训练集中的年龄分布。

训练集和预测集的数据就是上述所说的内容。

首先我们对训练集和测试集进行划分。

age_test_UID_list=[]
for i in age_test['UID']:
    age_test_UID_list.append(i)
#age_test_UID_list

train_user_basic_info=user_basic_info[~user_basic_info['UID'].isin(age_test_UID_list)]
train_user_basic_info.sort_values('UID',inplace=True) 
train_user_basic_info.to_csv('./train/train_user_basic_info.csv',index=False)
train_user_basic_info=pd.read_csv('./train/train_user_basic_info.csv')
train_user_basic_info   #2010000 

进过上述代码对几个数据集进行划分,发现训练集一共包含2010000条数据,测试集包含502500条数据。这么一看,数据量是非常大的,仅仅是读进内存也是非常困难的。尤其是我们经过探索发现其中的一个表User_app_usage.csv中,因为每一个APP在不同时间段的使用时长是不一样的,也就是说每个APP需要30行才可以囊括一个APP在30天内的使用时间。这样一算,就包含两个多亿的数据量,是非常可以的。所以经过和队员的讨论,我们暂时摒弃User_app_usage.csv这个表格的数据(后来表明,这部分数据是非常重要的)。

但是这时候数据量依旧是非常大的。如何解决这个问题呢?一般的话有三种解决方法:1.租一台云服务器,当然需要非常高,花费也不少。2.使用Google colab等类似的云平台,但是我们发现他们对数据内存的读取也存在严格的制约。3.自费配置一台服务器。当然我们选择了第3种,这里需要感谢我们的导师,他非常支持我们的竞赛。自掏腰包帮我们配置了128G的内存,GTX1080ti显卡。这样我们的配置问题就这样迎刃而解了。

缺失值填充

回到数据本身。发现数据存在大量的缺失值。

print('city缺失值个数:',len(train_user_basic_info[train_user_basic_info.city.isnull()]))
print('ramcapacity缺失值个数:',len(train_user_basic_info[train_user_basic_info.ramcapacity.isnull()]))
print('ramleftration缺失值个数:',len(train_user_basic_info[train_user_basic_info.ramleftration.isnull()]))
print('romcapacity缺失值个数:',len(train_user_basic_info[train_user_basic_info.romcapacity.isnull()]))
print('romleftration缺失值个数:',len(train_user_basic_info[train_user_basic_info.romleftration.isnull()]))
print('fontsize缺失值个数:',len(train_user_basic_info[train_user_basic_info.fontsize.isnull()]))
print('ct缺失值个数:',len(train_user_basic_info[train_user_basic_info.ct.isnull()]))
print('os缺失值个数:',len(train_user_basic_info[train_user_basic_info.os.isnull()]))

city缺失值个数: 8317
ramcapacity缺失值个数: 24757
ramleftration缺失值个数: 202200
romcapacity缺失值个数: 24757
romleftration缺失值个数: 173039
fontsize缺失值个数: 492558
ct缺失值个数: 113443
os缺失值个数: 984

可以看出,在User_basic_info.csv中,city属性缺少8317个数据,fontsize有将近50万的缺失值。经过讨论,我们最终采用的是用众数进行填充。采用的是手机的众数对各个属性的缺失值进行填充。至于为什么采用众数进行填充,而不是随便按照均值或者最大/最小值对每列进行填充,可能当时觉得缺少这么多的数据如果太随便填充的话,会对结果产生不好的影响,影响数据的分布。

train_user_basic_info[train_user_basic_info.prodname=='p00195'].os.value_counts()#求众数

这样我们用手机同型号的众数填充完毕,算是填充了这部分的缺失值。然而在比赛中结束后,经过和师兄的聊天,发现其实并不需要这么复杂的填充,而且也并没有什么实际的提升效果。师兄就是简单的用平均值填充,简单来说就是数据量太大,计算填充的数据不是那么精确,也不会对最终的数据分布产生太大的偏差。

解决完这个表,我们再来看下表User_behavior_info.csv。这个表没有缺失值,但是存在异常值。比如手机特性使用次数存在负值,我们最终取绝对值来解决这个问题;还有的数本应该是整数,却有小数点存在,我们也是给他直接四舍五入取整。

到目前为止,我们暂时先完成了数据预处理部分。

再次探索

这时候我们把目光转移到了表app_info.csv中。我们先来看下这个表的内容。
在这里插入图片描述
从图可以看出一共有167622个APP,且APP一共被分成40个类别。
我们是这样想的,直接利用这个表提供的数据,把40个类别作为属性,然后统计每个用户所使用的APP的分类。如果用户的APP在这40个类别中,那么数字变为1,如果不在这些类别中,那么就为0.后期我们发现,用户中有些APP所属类别并不在app_info.csv所提供的4个类别中,因为我们更改为41个属性,另一个属性就是防止没有类别进行归属。经过划分,我们得到结果如下。
在这里插入图片描述
可以看到,用户的APP如果在类别中出现则为1,否则为0.

这样我们预期就先利用三个表User_basic_info和User_behavior和app_info所提供的41个类别,把他们作为特征对年龄进行预测。

算法一开始采用的是比赛大杀器XGBoost,但是发现效果并不好,原因是矩阵比较稀疏,使用树模型达不到良好的效果。我们转而使用BP神经网络算法,经过不断调参和优化,准确率逐渐稳定在0.43左右。

当然,这时候排名提升了100多名。说明对数据进行分析还是有用的。

提取新特征

再尽力过上述结果之后,我们几天一直停滞不前,原因是没有想到好的特征。这时候不断翻看相关的文献和资料,终于在论文《Mining User Attributes Using Large-Scale APP Lists of Smartphones》中得到了思考。

我们看下User_app_actived表的内容。
在这里插入图片描述
可以看出,每个用户下载了多个APP,最多的下载了100多个,最少的也有十几个。
上面我们也说过,一共存在16万个app,如果把这些app全部作为属性列出来,属性爆炸,是无法想象的。

因此我们对这个表进行了统计,提取了用户使用最多的前500,1000,1500,2000,2500,3000个app。

def get_data(data):
    appnames = {}
    print('开始计算', datetime.datetime.now())
    for i, rows in data.iterrows():
        if rows['appid'] in appnames:
            appnames[rows['appid']] += 1
        else:
            appnames[rows['appid']] = 1
        if i % 100000 == 0:
        print('进度', (i / len(data))*100)
        
    # 无序的dict 共有9401个app
    print('无序的字典', datetime.datetime.now())
    f = open('./temp/appid_dict.txt', 'w')
    f.write(str(appnames))
    f.close()
    
    # 有序的dict
    list_sort = sorted(appnames.items(), key=lambda x: x[1], reverse=True)
    print('有序的字典', datetime.datetime.now())
    f = open('./temp/sort_appid_dict.txt', 'w')
    f.write(str(list_sort))
    f.close()
    
    #取出前1000个app
    count = 0
    ten_hundred_nums = []
    for app in list_sort:
        count += 1
        # 取出的app是tuple类型
        ten_hundred_nums.append(app[0])
        if count == 1000:
            break
    f = open('./temp/ten_number_sort_appid_dict.txt', 'w')
    f.write(str(ten_hundred_nums))
    f.close()


print('开始寻找1000个高频app', datetime.datetime.now())
get_data(user_app_usage)
print('高频app已经寻找完毕', datetime.datetime.now())

然后我们结合之前的特征,再次跑一下结果。发现当APP数量是2500的时候,效果最佳,达到了59%。相比较之前有了十几个百分点的提升,效果算是不错了,我们又提升了很多名次。

但是遗憾的是,通过其他几名获奖的同学演讲时,我们发现,他们提取的APP数量有的甚至达到了几万个,我当时就特么几乎吐血了。本来数据量就大,你倒是怎么跑得起来呀。后来,我发现这对人家不是问题,毕竟南大周志华老师那组的学生人家不用PC机,也不用传统的服务器,人家有集群,集群,集群!终究输在了硬件上,我认输。

再次提取新特征

这时候,我们再回过头来,发现只有user_app_usage.csv这个表我们没有用到了。

我们先看下表的内容
在这里插入图片描述
UID代表用户标识,appid就是APP名称,duration表示1天内用户对某APP的使用时长,times表示1天内用户对某APP的累积打开次数,date表示用户对某app的使用日期。

经过分析,我们提取了新的特征。
在这里插入图片描述
通过可视化,可以很清晰发现这个规律。
在这里插入图片描述
加上这部分特征之后,我们的精度达到了0.61.也是有了小幅度的提升。

后记

后来的将近半个月时间。别人都已经离校,我们还在实验室不断提着特征。大概前前后后相处了将近10中的新特征,但是不幸,最终都被PASS掉了,因为性能不升反降。这样,临近比赛结束的时候,我们就不再提取新的特征了,就进行特征融合了。最终对XGBoost+Bp进行融合,不断调参,到了0.63最高的精度。

前面说的比较口语化,我们最后公布一下我们的文档,感兴趣的同学可以看一下。

极客算法精英大赛算法实现说明
1 算法实现思路说明
1.1算法整体框架

本题是一个多分类问题,需要我们从混合复杂的信息中提取户的特征,通过已有标签训练,并预测未知标签(此处标签为6个不同年龄段)。首先对数据集进行的笥单的统计,用户id为1000001、32512500,共2512500个,其中201 佣00个有标签,5025佣个无标签。共有488124个app,40个app类别。我们从已给文件中提取了5个子特征矩阵,并合并成了一个总的特征矩阵。最后,我们通过双层神经网络进行K折交叉得到最终的预测结果。下图为算法哐架图.
在这里插入图片描述
其中step step5为将征提取步骤,step6描述了如何使用神经网络训练与预

1.2 特征提取

为了节省内存空间,我们提取的特征都以俑at16的形式存储,特征矩阵的每一行代表一个户的特征向量。例如特征矩阵第一行就表示用户1000001的特征向量。

Stepl:从User_Behavior_info中构建F-Table1
数据形式.
在这里插入图片描述
该数据表述的是用户对8个特性的使用次数,为连续特征,0表示缺失值 其中出现的小于零的数我们把它当作异常值,赋值为0。我们把每个用户对应的特性使用次数投射到他id对应的那一行去,形成大小为2512500*8的将征矩阵Behavior,接苕按图1变换生成F一Table1
在这里插入图片描述
其中作表示Log_Behavior=log2(Behavior+1), Norma肠e表示对矩阵的每一列进行规范化,即对每一列的元素减去均值处以方差。
Step2:从User_Basic_info中构建F-TabIe2
在这里插入图片描述
数据形式:
在这里插入图片描述
该数据表述的是厍户的离散属性,如性别,居住地,手机型号等等。此文件中缺失值为NULL,我们使用0填充缺失值。接着,我们把离散字符属性数值化,对于每一列,我们按属性出现的顺序给它赋值。例如,居住城市这一属性出现顺序为C00145’,C001 7乙C00435’,则’ m0145 '变为1,C00177’变为2。得到大小为251250 1 2的特征矩阵Basic,再通过One-Hot将Basic转换为F_Table2。

Step3:从User-App-Actived中提取F-Table3 数据形式:
数据形式:
在这里插入图片描述
该数据表述的是用户所激活的所有app的标识。我们首先遍历了数据,统计发现该文件中共有94佣个app,接着我们对这9400个app的激活人次进行统计,我们从中选取激活用户最多的前60佣个app,每个用户对这6000个app进行0/ 1 编码形成用户对应的特征向量。例如,若用户使用了激活人数最多的app,则他对应的特征向量的第一维为1。如此我们得到大小为251250伊6佣0的矩阵AP P-Actived0接着,我们考虑提取app之间的相互关系,此处我们只考虑前1000 个appo首先我们通过所有用户的激活记录,得到一个app间的联通矩阵s。再通过该联通矩阵求每个厍户对应的PageRank向量 组合成特征矩阵App_PageRank
假设有几条用户激活记录如下:
user1:appl ,app2,app3
user2: app2,app3
user3: app3,app4

如果两个app在同一个用户的激活记录里出现,则认为它们有一次连接,我们可以很容易的得到它的联通图和联通矩阵s。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在求得转换矩阵Transform_s后,我们将它与APP_Actived相乘得到App_PageRank,最后我们将App_Actived与App_PageRank拼接得到F_Table3.

Step4:从User_app_usage文件生成F_Table4
在这里插入图片描述
数据形式:
在这里插入图片描述
该文件表述了户在一个月内每一天的使厍app的次数和时间。与似我们选取使用户最多的前600个app构建特征矩阵。我们使原始文件第三维的D酊ati。n和第维的Time属性各构建一个601维的特征矩阵。例如,对于用户丿有和,表示用户使用挑选出的第一位的app的总次数,代表户使用所有未被挑选的app的总次数,同理。再拼接为一个2512500 ×1202的矩阵,最后对整体数据进行Log化并按列Max_Min归一化,得到2512500 ×1202的矩阵F_Table4.

Step5:结åApp」nfo,User_App_Actived User_App_Usage生成F-Table5
数据形式
在这里插入图片描述
该文件描述了每个app对应的类别,类别总数为40。对应的此处可以构建CatA CT,CatTIME,CatDURA三个子矩阵。构建方式类似与Step3、Step4,不同的是此处我们按照类别索引进行投射,而不是appid使人次的顺序索引。最后我们把三个子矩阵拼接在一起,并进行L。g化和按行规范化生成F_Table5。
总共有40类,采类似Step4的操作,利用User-app-actived,和User-app_usage产生三个251250041的特征矩阵,拼接为2512500 123的矩阵并进行log化,最终得到F_Table5。
在这里插入图片描述
最终拼接所有子特征矩阵F_Tablel~F_Table5为总特征矩阵F.

1.3 模型拟合

step6: K折交叉裤经网络
由于设备计算能力有限,我们本次竞赛没有使用较流行的LightGBM、XGBoost等。我们只使用了简单的双隐层裤经网络这一种分类器,进行了10折交叉平均。我们在喼层使用u为激活函数,输出层使用softmax为激活函数,对输入label做one-hot处理,顸测值得到样本对应类别概率向量,选取概率最大的作为预测类别。在模型集成方面,我们采取10折交叉的方法,每一折训练5个神经网络,最后通过50个裤经网络预测类别概率向量的简单平均求得模型最终的顸测值。
模型效率:
模型训练耗时.单个裤经网络18分钟达到收敛,50个裤经网络模型训练耗时9 佣分钟。
模型预测耗时 50个模型求和平均耗时不到50分钟。

2 算法实现说明
2.1算法运行环境

操作系统:Window10
编译软件:Anaconda3 Spyder编译器Python3.7
第三方库:keras tensorflow, numpy, skleam
CPU: i7一9700K
内存:128G
显卡、ATX1080TI

2.2实验过程

我们单个裤经网络就能达到线上0.618的成绩集成后能到0.6386。下面陈列出我们的部分尝试与发现。
1.经过大量尝试,我们发现在数据规范化过程中对于那些连续的,跨度很大的,具有长尾分布的数据,先采取g化会使得属性对于裤经网络很友好。
2.在处理离散属性时,one-hot的方法会比我们转换为概率的方法好那么一点 但是它会带来更多的维度。
3.在处理User-App-Usage数据时,我们尝试过采用分时间段提取信息,如节假日和非节假日,星期一、星期日,这些对算法并没有什么帮助。
4.实验结果显示,加人越多的属性,NN预测就越精准,采ÅRLDA、PCAß#维方法或其他Embedding方法后精度都会下降,此外,防止过拟合的方法Ll、L2或 Dr叩。ut都会使得精度下降。
5、在模型构建方面,我们发现基于树的算法实在是太慢了。只有
法效率还尚可,但是它单模的精度也就和NN差不多,我们考虑最后进行模型融
6.考虑改变损失函数,改为pair-wise,初步尝试和现在精度变化不大,但觉得可以用作最后模型融合

7.Bagging集成的方法实验结果不如简单的K折平均法。
8.我们把预训练的50个裤经网络模型训练产生的结果作为一个新的学习器的输入,可能是由于预训练的裤经网络存在过拟合的原因,新学习器的拟合结果并不理想。
9.尝试对子分类器进行Stacking集成,效果不如普通平均。

总结

这一次参赛收获还是蛮多的,之前仅仅是从书上对算法进行单方面的了解,并没有深入进行探索。在比赛中,也发现了很多有趣的小伙伴,也有第一次参见数据挖掘类比赛就拿到了季军。后面还有一个比赛,有时间还是会和大家分享。如果有小伙伴对这次比赛的数据集感兴趣,想要自己跑跑,也可以在直接私信我,我会把当时的代码和思路、文档一起发给你。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章