特征工程

本片博文对应的代码在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

足球_体育|足球_足球|篮球_体育|篮球_足球

军事_飞机|军事_大炮|娱乐_飞机|娱乐_大炮

体育_范冰冰|体育_电影|娱乐_范冰冰|娱乐_电影

足球_足球|足球_奥运|篮球_足球|篮球_奥运


配置文件如下:

每个特征用包括,包含4个属性:

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,如果需要则是1,如果不需要就去掉。

     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>

1

    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>

1

custuid

    item>

    <item>

        <name>sex_intersname>

        <category>crossfeaturecategory>

        <selectindex>2selectindex>

1

    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









你可能感兴趣的:(特征工程)