本片博文对应的代码在github:https://github.com/kunguang/SelectFeature/tree/develop ,develop分支下
转载请注明:http://blog.csdn.net/zkwdn/article/details/53390923
背景
在ctr训练模型之前,我们需要特征交叉,特征选择,特征编号,单特征auc和logloss(这3步是一个重复的循环,得到每个特征的auc和logloss后,选择一些有用的特征重新开始编号),映射成训练样本(libsvm格式或者其他格式)这5个过程。但是,其中特征交叉和特征编号这2个过程有点复杂,当我们实验一个新的特征的时候,要改写大量的代码。我们熟知的特征交叉形式有如下3种形式:单特征和单特征的交叉,比如性别和年龄的交叉;单特征和多特征的交叉,比如年龄和用户的兴趣;多特征和多特征的交叉,比如用户的兴趣和当前搜索的query包含的关键词。我们最后选取的特征既有连续特征,又有ID特征,其中连续特征需要等频离散或者其他离散方式进行编号,将他们统一编号有些复杂。所以,本代码的目的就是为了能够在不改变代码的前提下,紧紧通过配置文件(xml格式)便可以完成上述5个过程,最终的到训练集和测试集。
模块描述
本小节按照上面的5个过程描述一个真正的实例,
假设现在输入的数据格式如下,
label |
uid |
sex(性别) |
age(年龄) |
location(地域) |
custuid(广告主ID) |
inters(用户兴趣) |
搜索query包含关键词 |
日期 |
1 |
1 |
女 |
18 |
北京 |
1 |
足球|篮球 |
体育|足球 |
20161102 |
0 |
2 |
男 |
19 |
上海 |
2 |
军事|娱乐 |
飞机|大炮 |
20161102 |
0 |
3 |
女 |
18 |
北京 |
1 |
体育|娱乐 |
范冰冰|电影 |
20161103 |
1 |
4 |
男 |
19 |
上海 |
2 |
足球|篮球 |
足球|奥运 |
20161103 |
1.特征交叉和特征选择
特征交叉的形式如背景中描述,有3种形式:
1.单特征和单特征的交叉:比如性别和年龄,广告主三者的交叉,相当于在本来的6个特征中额外又加入了一列sex_age_custuid,
sex_age_custuid |
女_18_1 |
男_19_2 |
女_18_1 |
男_19_2 |
2.单特征和多特征的交叉,比如性别和用户兴趣的2者交叉,相当于在本来的6个特征中额外加入了一列,sex_inters
sex_inters |
女_体育|女_足球 |
男_飞机|男_大炮 |
女_体育|女_娱乐 |
男_足球|男_篮球 |
3.多特征和多特征的交叉,比如兴趣和当前搜索词的交叉,相当于在本来的6个特征中额外加入了一列,inters_query
inters_query |
足球_体育|足球_足球|篮球_体育|篮球_足球 |
军事_飞机|军事_大炮|娱乐_飞机|娱乐_大炮 |
体育_范冰冰|体育_电影|娱乐_范冰冰|娱乐_电影 |
足球_足球|足球_奥运|篮球_足球|篮球_奥运 |
配置文件如下:
每个特征用
1.index:
用来描述输入数据中该特征在第几列,从0开始.比如性别这个特征,index就是1.对于交叉特征,即输入数据中没有的这个特征,这个属性不应该存在。
2.name : 用来描述该特征的名字,比如性别这个特征的名字叫sex
3.category:有2个取值,singlefeature,crossfeature。对于输入数据中所有的特征都取值single feature,对于所有输入数据中没有需要交叉才能得到的叫做cross feature。
4.selectindex:用来描述输入数据中,该特征应该在第几列。比如想让性别这个特征在输出中是第3列,那么数值就是3。如果不想让性别这个特征在输出数据中存在,就不要这个属性或者值默认为-1。用作特征选择
这样来看,其实category这个属性和index冗余了,即当Index属性不存在时,category的值应该是cross feature,表明这个特征在输入数据中不存在,需要交叉获取。但是为了以后理解起来方便,还是加上吧。
<allitem>
<item>
<index>0index>
<name>labelname>
<category>singlefeaturecategory>
<selectindex>0selectindex>
item>
<item>
<index>1index>
<name>uidname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>2index>
<name>sexname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>3index>
<name>agename>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>4index>
<name>locationname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>5index>
<name>custuidname>
<category>singlefeaturecategory>
<selectindex>1selectindex>
item>
<item>
<index>5index>
<name>intersname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>6index>
<name>queryname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>7index>
<name>datetimename>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<name>sex_age_custuidname>
<category>crossfeaturecategory>
<selectindex>2selectindex>
item>
<item>
<name>sex_intersname>
<category>crossfeaturecategory>
<selectindex>3selectindex>
item>
<item>
<name>inters_queryname>
<category>crossfeaturecategory>
<selectindex>4selectindex>
item>
allitem>
这个配置文件,说明我们最终的输出会有5列,顺序如下,label,custuid,sex_age_custuid,sex_inters,inters_query。输出的样本如下
label |
custuid |
sex_age_custuid |
sex_inters |
inters_query |
1 |
1 |
女_18_1 |
女_体育|女_足球 |
足球_体育|足球_足球|篮球_体育|篮球_足球 |
0 |
2 |
男_19_2 |
男_飞机|男_大炮 |
军事_飞机|军事_大炮|娱乐_飞机|娱乐_大炮 |
0 |
1 |
女_18_1 |
女_体育|女_娱乐 |
体育_范冰冰|体育_电影|娱乐_范冰冰|娱乐_电影 |
1 |
2 |
男_19_2 |
男_足球|男_篮球 |
足球_足球|足球_奥运|篮球_足球|篮球_奥运 |
OK!!!到此为止,我们介绍完了特征交叉的主要部分。接下来介绍一下次要部分,可以看到,上面所有的交叉特征都可以用输入数据中的特征进行组合得到,即交叉完都是ID类特征。但是假如我们想做进一步的特征处理,比如我想将特征sex_age_custuid这个特征变成一个连续特征,该特征的取值是该特征的历史ctr,假设我们统计得到如下特征的历史点击率:
特征名字 |
点击率 |
曝光次数 |
点击次数 |
sex_age_custuid:女_18_1 |
0.002 |
1000 |
2 |
sex_age_custuid:男_19_2 |
0.001 |
1000 |
1 |
那么上面的输出数据中sex_age_custuid这一列的数据就变为 |
label |
sex_age_custuid |
1 |
0.002 |
0 |
0.001 |
0 |
0.002 |
1 |
0.001 |
问题1来了,我们需要一个特征历史文件有3列数据;特征名字,曝光次数,点击次数,即上上个图表,点击率=点击次数/曝光次数,所以点击率这列可以忽略掉。
在讲我们如何使用配置文件获取这个特征历史文件之前,我们先来理解下历史ctr这种类型的特征在模型中是什么意义?假设现在使用sex_age_custuid,location_custuid这2个特征的历史ctr预测当前样本的ctr。即训练集大概是这样子的,
label |
sex_age_custuid_historyctr |
location_custuid_historyctr |
1 |
0.002 |
0.003 |
0 |
0.001 |
0.0005 |
为了形象的理解历史ctr特征的含义,
我们现在假设损失函数是线性回归函数,y=a *sex_age_custuid_historyctr+b *location_custuid_historyctr
需要求解的参数有2个,a和b。
sex_age_custuid_historyctr:表示只使用特征sex_age_custuid训练得到模型1,来预测当前样本的ctr
location_custuid_historyctr:表示只使用特征location_custuid训练得到模型2,来预测当前样本的ctr
所以上面的回归函数就等价于混合模型预估,每个模型都占一个相应的权重。如果权重接近于0, 表示这个模型没用。
a参数:表示sex_age_custuid_historyctr这个模型的重要性。
b参数:表示location_custuid_historyctr这个模型的重要性.
当然在实际操作的时候,我们会采用如下2种改进方法:
1.损失函数用lr回归
2.每个连续特征分别进行等频离散。比如sex_age_custuid_historyctr被等频分成200快,location_custuid_historyctr等频分成300块,这样就会有500个参数需要训练。
OK!!!我们知道了历史ctr这个特征的物理含义,继续我们上面的问题,如何获取这个历史文件?
1.输入文件还是样例的输入数据格式,
2.需求:计算sex_age_custuid,location_custuid这2个特征的历史ctr ,.这儿会有两种不同的形式,不分日期计算特征的ctr 和分日期计算,分日期计算的意义在于时间衰减性,比如我们只需要这个特征最近7天的行为。以 女_18_1这个特征为例,
不分日期计算,就会得到一条数据结果
特征名字 |
曝光次数 |
点击次数 |
sex_age_custuid:女_18_1 |
2 |
1 |
分日期计算,就会得到两条结果
特征名字 |
曝光次数 |
点击次数 |
日期 |
sex_age_custuid:女_18_1 |
1 |
1 |
20161102 |
sex_age_custuid:女_18_1 |
1 |
0 |
20161103 |
假设输入数据还是最最上面最原始的输入数据,现在我们通过配置文件来得到,sex_age_custuidsex_inters,inters_query这3个特征的历史ctr .sex_inters是单特征和多特征的交叉,inters_query是多特征和多特征的交叉,对于这种特征,最终得到的是每个子项(按“|”符号切分)特征的曝光次数和点击次数,比如sex_inters这个特征,假设全局不考虑日期。会分解为: |
特征名字 |
曝光次数 |
点击次数 |
sex_inters:女_体育 |
2 |
1 |
sex_inters:女_足球 |
1 |
0 |
sex_inters:男_飞机 |
1 |
0 |
sex_inters:男_大炮 |
1 |
0 |
sex_inters:女_娱乐 |
1 |
0 |
sex_inters:男_足球 |
1 |
1 |
sex_inters:男_篮球 |
1 |
1 |
可以看到和上面交叉特征唯一的区别在于,将custuid这个特征的selectindex属性值改为-1,添加了一个全局变量partitionname(这个名字和第七列的名字一样即可),用于分日期计算,如果不分日期就去掉这个属性.
<allitem>
<partitionname>datetimepartitionname>
<item>
<index>0index>
<name>labelname>
<category>singlefeaturecategory>
<selectindex>0selectindex>
item>
<item>
<index>1index>
<name>uidname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>2index>
<name>sexname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>3index>
<name>agename>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>4index>
<name>locationname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>5index>
<name>custuidname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>5index>
<name>intersname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>6index>
<name>queryname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>7index>
<name>datetimename>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<name>sex_age_custuidname>
<category>crossfeaturecategory>
<selectindex>2selectindex>
item>
<item>
<name>sex_intersname>
<category>crossfeaturecategory>
<selectindex>3selectindex>
item>
<item>
<name>inters_queryname>
<category>crossfeaturecategory>
<selectindex>4selectindex>
item>
allitem>
OK!!!到此为止,我们得到了每个特征的历史ctr文件。
回到中间的那个问题:但是假如我们想做进一步的特征处理,比如我想将特征sex_age_custuid,sex_inters这2个特征变成连续特征(历史ctr),但是Inters_query这个交叉特征继续保持ID类数值特征。可以换个方式描述,一次性将ID类和交叉特征和映射历史ctr完成。
我们现在有2个输入文件:一个是最最上面的那个原始的输入文件,还有一个是我们上一步得到的历史ctr文件,有3列数据:特征,曝光次数,点击次数(记住把日期那一列去掉).我们该用什么样的配置文件解决我们上面的问题呢.和最上面的那个配置文件相比,又以下几点不同:
1.我们给每个特征又添加了一个属性叫做”needmap”,这个属性用来描述当前特征是否需要映射成历史ctr,如果需要则是
2.除这个属性为,对需要映射成历史ctr的特征来讲,又额外加了一个属性叫做”replace",这个属性用来说明当该特征在历史ctr文件中查不到时,需要其他特征来替换,取值格式为name,name,name, 可以由多个特征替换,先查找第一个,如果第一个也不存在则查找第2个。其中的name值只需满足以下2个条件中的任意一个即可。
1).这个name有selectindex值,即在输出数据中。下面的例子说明,当sex_age_custuid的历史ctr不存在时,用sex_inters替换,
2). 这个name有needmap这个属性,但是可以没有selectindex属性。比如下面的custuid这个特征。
除了上述2个(二选一条件外),有一个必选条件:
这个name只能针对单特征对单特征的替换,比如只能用custuid的ctr替换sex_age_custuid,而不能替换sex_inters或者inters_query。
事实上,如果你强硬这样替换的,比如用custuid的ctr替换sex_inters。这个action触发的前提是sex_inters下所有特征的历史ctr都没在历史ctr文件中找到;如果触发了这个行动,那就是整个sex_inters的历史ctr只有一个custuid的ctr。
如果都查找不到,则丢弃这条样本。假如这个特征就算查不到历史ctr,但也不需要其他特征替换时,则该属性不存在。
3.多了一个全局变量,feature threshold,也是针对需要映射成历史ctr的特征来讲,当这个特征的历史曝光次数低于这个阈值,认为这个特征的点击率不置信,丢弃掉这个特征。这样也就有了上面replace这个属性的用武之地。
<allitem>
<featurethreshold>300
<item>
<index>0index>
<name>labelname>
<category>singlefeaturecategory>
<selectindex>0selectindex>
item>
<item>
<index>1index>
<name>uidname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>2index>
<name>sexname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>3index>
<name>agename>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>4index>
<name>locationname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>5index>
<name>custuidname>
<category>singlefeaturecategory>
item>
<item>
<index>5index>
<name>intersname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>6index>
<name>queryname>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<index>7index>
<name>datetimename>
<category>singlefeaturecategory>
<selectindex>-1selectindex>
item>
<item>
<name>sex_age_custuidname>
<category>crossfeaturecategory>
<selectindex>1selectindex>
item>
<item>
<name>sex_intersname>
<category>crossfeaturecategory>
<selectindex>2selectindex>
item>
<item>
<name>inters_queryname>
<category>crossfeaturecategory>
<selectindex>3selectindex>
item>
allitem>
OK,到此为止,所有关于特征交叉的代码和配置文件都已经交代清楚。
2.特征编号和单特征指标
由上面第一步我们已经产生了新的输入数据,即共有5列数据,假设如下(和上面的数据没有直接对应关系,为了少写点。。)
label |
custuid |
sex_age_custuid_historyctr |
sex_inters_historyctr |
inters_query |
1 |
1 |
0.002 |
0.003 |
足球_奥运|足球_篮球 |
1 |
2 |
0.003 |
0.001 |
娱乐_军事 |
0 |
3 |
0.001 |
0.01 |
娱乐_范冰冰|娱乐_电影 |
0 |
2 |
0.01 |
0.005 |
军事_飞机 |
1 |
2 |
0.003 |
0.001 |
娱乐_军事 |
可以看到这4个特征中,其中custuid和inters_query是单特征,其它两个是连续特征,我们对连续特征采用简单的等频离散方式,如果想二次开发的话,也很容易。
第一步:连续特征等频离散。将sex_age_custuid_historyctr等频离散成3份,假设区间是[0,0.001],[0.001,0.01],[0.01,无穷大],sex_inters_historyctr离散成2份,[0,0.003],[0.003,0.01],[0.01,无穷大]。
任何一个无穷大的区间编号最后都不会映射到真实样本中,因为任何一个数据跟无穷大的距离都小于正常点数值。
此时就会形成5个编号,其实真正的编号,应该是这样的,比如sex_inters_historyctr值是0.005,真是区间是落在区间4内,因为和0.003的距离小于和0.01的距离,代码中是这么实现的。
离散区间 |
编号 |
sex_age_custuid_historyctr:[0,0.001] |
1 |
sex_age_custuid_historyctr:(0.001,0.01] |
2 |
sex_age_custuid_historyctr:(0.01,0.无穷大] |
3 |
sex_inters_historyctr:(0,0.003] |
4 |
sex_inters_historyctr:(0.003,0.01] |
5 |
sex_inters_historyctr:(0.01,无穷大] |
6 |
第二步:将ID类特征继续编号。
特征名字 |
编号 |
custuid:1 |
7 |
custuid:2 |
8 |
custuid:3 |
9 |
inters_query:足球_奥运 |
10 |
inters_query:足球_篮球 |
11 |
inters_query:娱乐_军事 |
12 |
inters_query:娱乐_范冰冰 |
13 |
inters_query:娱乐_电影 |
14 |
inters_query:军事_飞机 |
15 |
第三步:映射回原来的样本,
label |
custuid |
sex_age_custuid_historyctr |
sex_inters_historyctr |
inters_query |
1 |
7:1 |
2:1 |
4:1 |
10:0.5|11:0.5 |
1 |
8:1 |
2:1 |
4:1 |
12:1 |
0 |
9:1 |
1:1 |
5:1 |
13:0.5|14:0.5 |
0 |
8:1 |
2:1 |
4:1 |
15:1 |
1 |
8:1 |
2:1 |
4:1 |
12:1 |
冒号后的值等于1.0/这个特征下有几个特征值,比如第一行inters_query有2个特征,那么后面的值是1.0/2。
这样做的目的来源于libfm的那篇论文,最原始的来源是SVD++,算是一个小trick,如果不这么做,都设为1也可以。目的其实就是为了表明整个者特征对样本的数值贡献初始为1.0.
第四步:计算每个特征的logloss和auc。
这边其实对于这种多交叉特征计算的结果是无效的,比如计算的时候,是将10:0.5|11:0.5整个作为一个整体,但是在训练模型时,其实是2个特征,10:0.5 11:0.5.
单特征的logloss是很好计算的,我们采用归纳法来理解。
以上述custuid这个特征为例,共有3个不同的特征,7,8,9,同样我们采用lr损失函数的话,最后会得到3个参数,每个特征对应一个参数,假设7对应w0,8对应w1,9对应w2。仔细看上面的例子,
w0参数的求解只受第一条样本影响,只有w0这个参数能使得这条样本贡献的Logloss最小即可
w1这个参数受第二条和第四条样本,第5条样本影响,只要w1这个参数能使得这3条样本贡献的Logloss最小
w2受第三条样本影响,只要w2这个参数能使得这条样本贡献的logloss最小即可。
假如上面每个参数都能使得自己所分割的样本贡献的logloss最小,全局的logloss最小,结论一就是,这些参数之间是独立的,进而可以得到每个参数都是可以独立求解的。
我们正常的求解方法是,是使用各种迭代算法求解,无限逼近最小的logloss。但是本文直接给出参数的解析式,即使得logloss最小的最优值求解算法。
以w1为例,现在w1对应的
第二条样本贡献的logloss是,-log(1.0/(1.0+exp(-w1)))
第四条样本贡献的Logloss是,-log(1.0-1.0/(1.0+exp(-w1)))
第五条样本贡献的Logloss是,-log(1.0/(1.0+exp(-w1)))
现在我们假设f(w1) = .0/(1.0+exp(-w1)).即当样本中有这个特征时,被预测为1的概率。
这样上面两条logloss转化为:
第二条样本: -log(f(w1))
第四条样本: -log(1.0-f(w1))
第5条样本:-log(f(w1))
两条样本的Logloss相加为: -2.0 *log(f(w1)) - log(1.0-f(w1)),我们只要求得w1使得这个logloss最小即可。
我们进一步假设f(w1) =x,将f(w1)整体看做一个变量。现在logloss变为
F(x) = -2.0 * log(x)-log(1.0-x).现在我们的目的是要求解x使得F(x)最小。
d(Fx)/dx = 0 时的x 可以使得F(x)最小,
F(x)关于x的导数是,-2.0 *(1.0/x) + 1.0/(1.0-x) = (3x-2)/(x-x^2)
另上式等于0,只用分子=0即可,即3x-2=0,得到x = 2/3。
结论,即f(w1)=x = 2/3,可以看到这个数值的分子就是w1这个这个参数对应特征8:1的点击次数,分母是曝光次数。通过x就可以直接得到这3条样本的logloss就是-2.0*log(2.0/3) - 1.0 * log(1.0/3),不用再求解w1。而x的值可以通过统计得到,即只用统计8:1这个特征的曝光次数和点击次数即可。
归纳一下,上面w1这个特征对应2条正样本,一条负样本,假设对应n条正样本,m条负样本,那么这个特征对应的(m+n)条样本对应的logloss就是
-n*log(f(w1))- m*log(1.0-f(w1))
进一步假设,f(w1) = x,将f(w1)看做一个变量。现在logloss变为
F(x) = -n*log(x) - m *log(1-x),目标转化为求解x使得F(x)最小
d(Fx)/dx = 0 时的x 可以使得F(x)最小,
F(x)关于x的导数是,-n *(1.0/x) + m/(1.0-x) = ((n+m)x-n)/(x-x^2)
另上式等于0,只用分子=0即可,即(n+m)x-n=0,得到x = n/(n+m)。
进而求解这m+n条样本贡献的logloss就是 -n * log(n/n+m) - m * log(m/(n+m))
结论同上,所以如果我们要就某个单特征的logloss,
第一步就是统计这个特征下每个特征值对应的曝光次数和点击次数,3列数据 特征名字,曝光次数,点击次数。
第二步:求解每个特征贡献的logloss。
第三步:将每个特征贡献的logloss相加。
OK,到此为止,单特征的Logloss怎么求解的就说明完了。
单特征auc
参考这篇博文http://www.cnblogs.com/lixiaolun/p/4053499.html。代码里既有分布式的实现方式,也有单机的实现方式。logloss和auc的代码都独立出来了,方便调用。
配置代码如下,针对
label |
custuid |
sex_age_custuid_historyctr |
sex_inters_historyctr |
inters_query |
其中custuid和inters_query是单特征。另外2个是连续特征。
2个全局变量:
1.featurethreshold: 阈值,默认0。再对特征进行编号的时候,我们要去掉一些置信度低(曝光次数少的特征),这个属性就表示当某个特征的曝光次数小于这个值的时候,就丢掉这个特征。
2.discardsample:丢弃样本,默认不丢弃,当取值为1的时候丢弃。即因为我们上面的阈值变量,肯定会过滤掉一批ID类型的特征,那么在映射到样本的时候,这些被丢弃的特征就找不到编号,此时我们用一个默认编号代替(即最大的编号),一般来讲我们可以丢弃掉这些样本。
每个特征有4个属性:
index:说明当前这个特征是第几列。
name:这个特征的名字
category:有2个取值,single feature:表示这是ID类特征。crossfeature表示这是连续特征,需要离散,
block size:这个数值只有当category时crossfeature时才会生效,即这个连续特征被离散成多少个区间,默认100.
select:表示这个特征是否在此次编号时使用,取值有2种:1(使用),-1,默认为-1(不使用)。因为我们往往在测试某个特征的auc的时候,先去掉这个特征,看样本的auc,然后再加上这个特征,看样本的auc是否提高。
<allitem>
<featurethreshold>300featurethreshold>
<discardsample>1discardsample>
<item>
<index>0index>
<name>labelname>
<category>singlefeaturecategory>
<select>1select>
item>
<item>
<index>1index>
<name>custuidname>
<category>singlefeaturecategory>
<select>1select>
item>
<item>
<index>2index>
<name>sex_age_custuid_historyctrname>
<category>continuefeaturecategory>
<blocksize>100blocksize>
<select>1select>
item>
<item>
<index>3index>
<name>sex_inters_historyctrname>
<category>continuefeaturecategory>
<blocksize>100blocksize>
<select>1select>
item>
<item>
<index>4index>
<name>inters_queryname>
<category>singlefeaturecategory>
<select>1select>
item>
allitem>
最后,以上阐述了代码整体的设计思想和思考逻辑,这些逻辑和设计肯定还是有一些缺陷的,甚至代码里或许还会有一些bug,我工程能力毕竟很弱。只能说随着以后对特征工程的认知提高,逐渐提高这些代码的通用型。作为一个算法同学,最好所有的代码逻辑都不是黑盒,毕竟要做各种尝试。举个简单的例子,还是以上面的输入数据为例,我现在想加入一个新的特征,用户的兴趣关键词是否包含了当前搜索的关键词,那该怎么处理呢?肯定得要自己在代码中修改了,这些接口都留好了~~。
StartFragment EndFragment