基于用户画像的精准营销

1. 问题背景

所要解决的问题是找到目标人群,精准投放营销广告。
用户画像,即用户信息标签化,就是企业通过收集与分析消费者社会属性、生活习惯、消费行为等主要信息的数据之后,完美地抽象出一个用户的商业全貌。所以基于用户画像建立分类模型来预测短信投放的目标用户。

2. 构造训练数据

有三部分数据:用户画像数据(199737765条)、营销汇总数据(472616840条)、登录数据(495517967条),最后要筛选出选定的一次短信营销活动中的用户,用该用户的用户画像数据作为训练集,目标变量为观察期内是否登录,登录为1,未登录为0。

2.1 用户画像数据

先来看看用户画像数据,存放在hive中dim_customer_marking表内。用户画像表共有368个字段,剔除已下线和停止刷新项目如大学生项目,并选择用户的固有属性,如:是否购买基金、是否是陆金所用户、是否是平安内勤员工,剔除金融产品中的细分类别,并根据业务逻辑选择重要特征。因为要将静默用户和近期活跃用户按一定比例选择作为目标投放用户,并且响应指标是看用户在观察期内是否登录,所以剔除用户画像表中登录信息。对于生活类消费和购买的理财产品有很多细分类别,也先剔除,只保留对这些大类是否交易过,最后筛选出认为比较重要的特征56个。

from pyspark import SparkConf, SparkContext
from pyspark.sql import HiveContext,Row
hiveContext = HiveContext(sc)
dim_data=sc.sequenceFile('/user/hive/warehouse/i8ji.db/dim_customer_marking')  
dim_data=dim_data.map(lambda x:x[1].split("\x01")).map(lambda x:(x[0],x[4],x[7],x[14],x[23],x[24],x[26],x[29],x[31],x[33],x[46],x[47],x[48],x[49],x[50],x[51],x[52],x[53],x[54],x[55],x[56],x[57],x[66],x[89],x[94],x[96],x[117],x[118],x[122],x[127],x[141],x[142],x[143],x[144],x[146],x[148],x[149],x[180],x[181],x[261],x[262],x[263],x[275],x[276],x[277],x[278],x[279],x[280],x[289],x[306],x[335],x[336],x[337],x[340],x[341],x[342],x[343]))
dim_data1=hiveContext.createDataFrame(dim_data)

部分特征如下:
这里写图片描述

2.2 营销汇总数据

然后在营销数据汇总表dim_yx_customer_base_new中,选择短信营销,即令channel=’S’,并选择一次营销活动——【822砍价——短信营销第二波】,活动id为39010,即令campaign_id=39010。
营销数据汇总表为orc格式,用hiveContext.read.format(“orc”).load(path)来读写。

data=hiveContext.read.format("orc").load("/user/hive/warehouse/i8ji.db/dim_yx_customer_base_new")
train_data=data.filter("channel='S' and campaign_id=39010").select("customer_id")

2.3 登录数据

在用户登录表mid_dim_cust_ex_login中取出在2017年8月22日至9月5日(观察期)内登录的用户,去重,并赋值为1(为后面构造目标变量做准备)。

login_data=sc.textFile("/user/hive/warehouse/i8ji.db/mid_dim_cust_ex_login")
login_data=login_data.map(lambda x:x.split("\x01"))
login_user=login_data.map(lambda x:Row(customer_id=x[0],login_time=x[1]))
login_user1=hiveContext.createDataFrame(login_user)
login_user2=login_user1.filter("login_time>'2017-08-22' and login_time<'2017-09-05'").select("customer_id")
login_user3=login_user2.distinct()
login_user4=login_user3.rdd.map(lambda x:Row(customer_id=x[0],y=1))
login_user5=hiveContext.createDataFrame(login_user4)

2.4 构造训练集

先取出该次营销活动用户的用户画像数据,即用户两个数据表按customer_id做“inner” join。

train=dim_data1.join(train_data,train_data.customer_id==dim_data1._1,'inner')
train1=train.drop('customer_id')

然后构造目标变量,将上述数据与登录数据按customer_id做left_join,空值填充为0。

train2=train1.join(login_user5,train1._1==login_user5.customer_id,'left_outer')
train3=train2.na.fill({'y':0})
train4=train3.drop('customer_id')

最后将训练集保存到hive。

train4.saveAsTable('trainmm',mode='overwrite')

3. 数据处理

先来看下训练集大小和正负样本数量。得到训练集有284681条

df=hiveContext.read.load('/user/hive/warehouse/trainmm')
df.count()
df.groupBy('y‘).count().show()

如下表所示,正负样本分布比较均衡:

ycount
0170103
1114578

静默用户分布:

_40count
0245213
139468

将训练集中56个特征按照离散变量和连续变量分类处理。基本方法是做缺失值、异常值处理、离散化、数据变换(标准化、归一化、对数变换)等。

3.1 离散变量

首先看各变量的缺失情况(注意,实际上不是空值,而是空字符串“)。

缺失变量count
_255020
_355022
_1039645
_24117465
_2913
_3013
_31258194
_32258194
_33258217
_34258217
_35258217
_36284681
_37284681
_4374610
_4474610
_4574610
_4674610
_4774610
_4874610
_49281167
_519180
_529180
_539180
_5412650
_5584406
_5684406
_5784406

_31、_32、_33、_34、_35、_36、_37、_49,这8个变量的缺失数量在90%以上,所以删除这些变量,其他有缺失值的变量中一般将缺失值填充为-1,还有下面的特殊情况。
将生日所在年份、注册时间、首次登陆时间转变为年龄或距营销日期天数。并且,将定性的特征变成数值型,定性特征如下:

_2count
F124404
M105257
55020
_44count
00.积分段0元80185
09.积分段[50,100)元7309
13.积分段大于等于1000元以上3729
06.积分段[10,20)元8870
07.积分段[20,30)元5335
08.积分段[30,50)元6148
05.积分段[5,10)元11462
11.积分段[200,500)元6563
01.积分段(0,0.2)元27229
12.积分段[500,1000)元3457
10.积分段[100,200)元6700
04.积分段[2,5)元12442
02.积分段[0.2,1)元21785
74610
03.积分段[1,2)元8857
_48count
MOB_m0502
MOB_m131166
MOB_m218811
old159592
74610

连续变量

所有连续变量的描述统计信息,可以看到有几个特征的最大值非常大,该特征的含义均为多天的交易金额或理财账户余额,所以比较合理,不认为是异常值。

summary_8_9_11_12_13_14_15_16_17_18
count284681284681284681284681284681284681284681284681284681284681
mean4.880.180.652840.11884.990.462.6312087.801852.431.84
stddev7.360.745.1329635.179656.474.9415.6981661.2515482.6114.99
min0000.00.0000.00.00
max999999999.919999.66999999999999993.7499
summary_19_20_21_22_25_26_27_28_50
count284681284681284681284681284681284681284681284681284681
mean8.0736595.323059.085.9038021.4669.242913.355.91
stddev40.34188195.6922527.6537.97123498.0986.6629701.63422.50766.76
min00.00.00
max99399998.09999.8799399999999967.03996.08027.38

然后查看缺失值,只有5个特征有缺失值,缺失数量如下,删除_25、_26和_50特征。_27和_28因为是余额,所以缺失可能是因为用户没有办理这项业务,所以将缺失值填充为0。

缺失变量count
_25233374
_26271181
_2761383
_2839645
_50260242

数据处理如下:

df1=df.drop('_25')
df1=df1.drop('_26')
df1=df1.drop('_31')
df1=df1.drop('_32')
df1=df1.drop('_33')
df1=df1.drop('_34')
df1=df1.drop('_35') 
df1=df1.drop('_36')
df1=df1.drop('_37')
df1=df1.drop('_49')
df1=df1.drop('_50')
from datetime import datetime
d=df1.rdd.map(lambda x:[x[0],x[1],x[2],x[3],x[4],x[5],x[6],x[7],x[8],x[9],x[10],x[11],x[12],x[13],x[14],x[15],x[16],x[17],x[18],x[19],x[20],x[21],x[22],x[23],x[24],x[25],x[26],x[27],x[28],x[29],x[30],x[31],x[32],x[33],x[34],x[35],x[36],x[37],x[38],x[39],x[40],x[41],x[42],x[43],x[44],x[45],x[46]])
mm={u'MOB_m0':0,u'MOB_m1':1,u'MOB_m2':2,u'old':3}
m={u'00.积分段0元':0,u'01.积分段(0,0.2)元':1,u'02.积分段[0.2,1)元':2,u'03.积分段[1,2)元':3,u'04.积分段[2,5)元':4,u'05.积分段[5,10)元':5,u'06.积分段[10,20)元':6,u'07.积分段[20,30)元':7,u'08.积分段[30,50)元':8,u'09.积分段[50,100)元':9,u'10.积分段[100,200)元':10,u'11.积分段[200,500)元':11,u'12.积分段[500,1000)元':12,u'13.积分段大于等于1000元以上':13}
def f1(x):
        if [1]=='M':
            x[1]=0
        elif x[1]=='F':
            x[1]=1
        else:
            x[1]=-1
        if x[2]=='':
                x[2]=1980
        if x[9]=='':
                x[9]='2017-08-22 00:00:00'         
        x[2]=2017-int(x[2])
        x[3]=(datetime.strptime('2017-08-22','%Y-%m-%d')-datetime.strptime(x[3],'%Y-%m-%d %H:%M:%S')).days
        x[9]=(datetime.strptime('2017-08-22','%Y-%m-%d')-datetime.strptime(x[9],'%Y-%m-%d %H:%M:%S')).days
        if x[23]=='':
            x[23]=-1
        for j in range(24,26):
            if x[j]=='':
                x[j]=0
        for j in range(26,46):
            if x[j]=='':
                x[j]=-1
        for k,v in mm.items():
            if x[38]==k:
                x[38]=v
        for k,v in m.items():
            if x[34]==k:
                x[34]=v
        x=list(map(float,x))
        return x
data=d.map(lambda x:f1(x))

算法实现

先比较一下后面要用到的两种算法,只看性能方面,不讲具体原理(参考了MLlib文档)

Gradient-Boosted Trees vs. Random Forests:

GDBT和RF都是基于树的集成学习的算法,但训练过程不同,下面是几个它们的不同点:

  • GDBT一次训练一棵树,所以训练时间比RF要长。RF可以并行训练多棵树。另外,训练GDBT比RF时使用更少的树是有意义的,并且树越少花费时间越短。
  • RF更不易于过拟合。训练越多的树时RF会减少过拟合的可能性,但GDBT会增加过拟合。(换做统计语言就是,RF通过使用更多的树减少方差,而GBDT通过使用更多的树减少偏差。
  • RF更容易调参,因为模型性能随树的个数单调递增(但是如果树的个数变得过大,性能可能会开始变差)。
    总之,两个算法都很有效,如何选择要基于实际的数据集。

Random Forests

不需要对特征标准化
训练过程引入两个随机性:

  • 每次迭代对原始训练集子采样,每次得到不同的训练集(a.k.a. bootstrapping)。
  • 每棵树划分节点时随机选择的特征的子集作为候选划分特征

Usage tips

首先看最重要的两个参数,调节它们通常会提高性能:

  • numTrees: 树的个数。增加树的个数会减少预测的方差,提高模型的准确率。训练时间呈线性增加。
  • maxDepth: 每棵树的最大深度。增加树深度会使模型更美好和强大,但是会花费更多时间去训练,并且容易过拟合。通常,可以接受训练RF时比训练单颗决策树使用更大深度的树。一棵树比RF更易过拟合(因为平均多棵树会减少方差)。
    还有两个参数通常不需要调节。但是,可以调节它们来提高训练的速度。
  • subsamplingRate: 训练每棵树的训练集大小,是原始训练集的一部分。推荐使用默认值(1.0),减少这个比例可以提高训练的速度。
  • featureSubsetStrategy: 每棵树划分节点时使用的候选特征的个数。减少这个数量可以提高训练的速度,但如果值太低有时会影响效果。

Gradient-Boosted Trees

同样不需要对特征标准化
Losses
每个损失函数的适用情况,注意:每个损失函数只适应于分类或回归中的一种。

LossTaskDescription
Log LossClassificationTwice binomial negative log likelihood.
Squared ErrorRegressionAlso called L2 loss. Default loss for regression tasks.
Absolute ErrorRegressionAlso called L1 loss. Can be more robust to outliers than Squared Error.

Usage tips:

  • loss: 不同的损失函数会得到不同的结果,具体用哪个取决于数据集。
  • numIterations: 设置集成中树的个数。每次迭代会产生一棵树。增加这个值使模型性能更优,提高训练集准确率。但如果值太大,测试集准确率降低。
  • learningRate: 学习速率,即步长。不该调节该参数。如果算法表现不稳定,减小这个值可能会提高稳定性。

模型评估及选择

最后就到建模这一步了,要做交叉验证来看模型效果和模型调优,一般是将训练集分成两部分,一部分用于训练我们需要的模型,另外一部分数据上看我们预测算法的效果。

from pyspark.mllib.regression import LabeledPoint
train=data.map(lambda x:LabeledPoint(x[-1],x[1:46]))
(trainingData, testData) = train.randomSplit([0.7, 0.3])

然后训练模型,这里分别用了随机森林和GDBT,看一下在训练集上预测的准确率。
maxDepth均设为10,RF调节numTrees参数,GBDT调节numIterations参数

from pyspark.mllib.tree import RandomForest, RandomForestModel
from pyspark.mllib.tree import GradientBoostedTrees, GradientBoostedTreesModel
from pyspark.mllib.util import MLUtils
r1=[]
for i in [3,10,40,60,80]:
    model = GradientBoostedTrees.trainClassifier(trainingData,categoricalFeaturesInfo={},numIterations=i,maxDepth=10)
    predictions = model.predict(testData.map(lambda x: x.features))
    labelsAndPredictions = testData.map(lambda lp: lp.label).zip(predictions)
    accuracy = 1-labelsAndPredictions.filter(lambda (v, p): v != p).count() / float(l)
    r1.append(accuracy)

r2=[]
for i in [50,100,150,200]:
    model = RandomForest.trainClassifier(trainingData, numClasses=2, categoricalFeaturesInfo={}, numTrees=i, featureSubsetStrategy="auto",impurity='gini', maxDepth=10, maxBins=32)
    predictions = model.predict(testData.map(lambda x: x.features))
    labelsAndPredictions = testData.map(lambda lp: lp.label).zip(predictions)
    accuracy = 1-labelsAndPredictions.filter(lambda (v, p): v != p).count() / float(l)
    r2.append(accuracy)

随机森林调参:

numTrees50100150200
accuracy0.84434737682588890.846531398243389190.843830726598093110.84445305528157433

GBDT调参:

numIterations31040
accuracy0.847482504344558740.85094640928091680.85233197125546001

GDBT以时间换性能。

总结

  • 活跃用户和静默用户按一定比例作为目标用户,所以分别训练模型
  • 按不同类别的营销活动,分别筛选特征、建模
  • 重新筛选特征、构造特征、交叉特征
  • 不盲目调参,理解参数对预测的影响,深入理解算法
近几年中国在淘宝、京东、天猫等巨头电商公司带动下迅猛发展,电商在中国显示出了强大的生命力,每家电商公司的商品更是以指数级的数量增长,可是在商品增长的同时,也无形中增加了用户寻找商品的困难程度。这个问题在小型智能移动终端尤其明显,用户在小型智能移动终端浏览大量的商品不仅会占用客户的时间还会消耗大量的流量,这种欠佳的浏览体验是导致用户转移购物平台的一个主要因素。本文设计并实现基于“用户画像”的商品推送系统正是在上述问题的基础之上立项的,将用户画像与主动推送相结合,避免了用户在海量商品中苦苦寻求自己感兴趣的商品,不仅解决了商品过载的问题而且实现了对用户的精准营销。主要研究内容如下:首先介绍了基于“用户画像”的商品推送系统的立题意义以及相关的理论基础,对国内外推送系统的发展状况进行了深入调研,详细的阐述了所需要使用的技术。其次对基于“用户画像”的商品推送系统从需求、设计和实现的三个方面进行详细的说明,本系统主要构成为以下两个部分:(1)用户画像系统,首先以用户的个人历史行为为基础,通过评分矩阵模型构建用户兴趣模型,然后基于标签规则将用户兴趣模型转换为用户标签模型,用户画像系统则是以用户标签模型为基础生成的,并通过Echarts图表将用户画像进行展示。与传统推送系统相比,本系统将用户置于最重要的部分,对每个用户都实现精准营销。(2)商品个性化推送平台,调用本接口可以返回商品列表,返回的商品列表是在用户兴趣模型的基础上混合多种规则并加以过滤得到的最符合用户偏好的推送商品集合,以接口的形式给不同类型的小型智能移动终端提供数据。本接口应用Thrift框架编写,通过该框架进行系统之间的交互具有高性能、低延迟、支持同步和异步通信等优点。最后,为推送效果提供测试方案,商品推送系统的参数调优通过NDCG算法,NDCG表示归一化折损累积增益,该算法是当下比较流行的推荐系统评测指标之一,通过用户对推送商品的操作行为量化出用户对商品列表的满意程度,根据用户满意度进而对系统参数进行调整
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值