CTR场景数据分析建模 问题汇总5 - 复盘比赛2
创建特征(续)
简易fm
对部分相关度较高的特征做相乘相加处理,扩展特征数量,发现更好特征。
data_lxy_fa = data_lxy.copy()
float_importfa_feature = [ 'count_Product_id',
'seller_Product_id_count',
'seller_user_id_count',
'Product_id_day_count',
'Product_id_action_type_count',
'Product_id_weekday_count',
'user_id_nunique_under_Product_id',
'day_before_Product_id_count',
'user_id_seller_frequency']
for i in float_importfa_feature:
for j in float_importfa_feature:
if i!=j:
data_lxy_fa[i+'_dot_'+j+'_fa'] = data_lxy_fa[i]*data_lxy_fa[j]
data_lxy_fa[i+'_add_'+j+'_fa'] = data_lxy_fa[i]+data_lxy_fa[j]
#data_lxy_fa[i+'_sub_'+j+'_fa'] = data_lxy_fa[i]-data_lxy_fa[j]
for col1 in combinations(float_importfa_feature, 3):
data_lxy_fa['_'.join(col1)+'_dot_']=data_lxy_fa[col1[0]]*data_lxy_fa[col1[1]]*data_lxy_fa[col1[2]]
data_lxy_fa['_'.join(col1)+'_add_']=data_lxy_fa[col1[0]]+data_lxy_fa[col1[1]]+data_lxy_fa[col1[2]]
归一化
归一化 减去平均值除以方差
#归一化 减去平均值除以方差
for i in float_feature:
data_lxy_fa[i] = (data_lxy_fa[i] - data_lxy_fa[i].mean()) / (data_lxy_fa[i].std())
for i in data_lxy.columns:
if '_dot_' in i:
data_lxy_fa[i] = (data_lxy_fa[i] - data_lxy_fa[i].mean()) / (data_lxy_fa[i].std())
if '_add_' in i:
data_lxy_fa[i] = (data_lxy_fa[i] - data_lxy_fa[i].mean()) / (data_lxy_fa[i].std())
训练集分割验证集
由于本题的day特征对结果影响很大,故对训练集分割时不采取随即分割的方式,而是按照day特征来分割。即将每一天内特征的百分之20化为验证集,用来初步调试模型,找到最优树结构。
train_lxy = data_lxy_fa[data_lxy_fa.purchase.notnull()]
test_lxy = data_lxy_fa[data_lxy_fa.purchase.isnull()]
train_lxy['favorite']=train_lxy['favorite'].astype('category')
train_lxy['purchase']=train_lxy['purchase'].astype('category')
train_feature = [f for f in data_lxy_fa.columns if f not in ['purchase', 'favorite']]
train_lxy_x = train_lxy[train_feature_kendall].reset_index(drop=True)
train_lxy_y_fa = train_lxy['favorite'].reset_index(drop=True)
day_unique = data_lxy.day.unique()
valid_lxy = pd.DataFrame()
train_lxy_without_valid = pd.DataFrame()
for i in day_unique:
temp = train_lxy[train_lxy['day'].isin([i])]
# exec("train_lxy%s = temp" % i)
valid_lxy_temp = temp.sample(frac = 0.05,replace = False)
train_lxy_without_valid_temp = temp.drop(valid_lxy_temp.index)
valid_lxy = pd.concat([valid_lxy, valid_lxy_temp], ignore_index=True)
train_lxy_without_valid = pd.concat([train_lxy_without_valid, train_lxy_without_valid_temp], ignore_index=True)
valid_lxy_x = valid_lxy[train_feature_kendall].reset_index(drop=True)
valid_lxy_y_fa = valid_lxy['favorite'].reset_index(drop=True)
test_lxy_x = test_lxy[train_feature_kendall].reset_index(drop=True)
train_lxy_x_without_valid = train_lxy_without_valid[train_feature_kendall].reset_index(drop=True)
train_lxy_y_without_valid_fa = train_lxy_without_valid['favorite'].reset_index(drop=True)
模型选取
lightgbm
该模型效果最好,速度和性能在本题上都比xgboost快。
后悔应该尝试下catboost
categorical_feats=['seller','Product_id','user_id']
lgb_train_fa = lgb.Dataset(train_lxy_x_without_valid, train_lxy_y_without_valid_fa,categorical_feature=categorical_feats)
lgb_eval_fa = lgb.Dataset(valid_lxy_x, valid_lxy_y_fa,categorical_feature=categorical_feats, reference=lgb_train_fa)
# specify your configurations as a dict
params = {'boosting_type':"gbdt", 'num_leaves':20, 'reg_alpha':0, 'reg_lambda':0.018,
'max_depth':5, 'n_estimators':2000, 'objective':'binary','num_class' : 1,
'subsample':0.8, 'colsample_bytree':0.8, 'subsample_freq':1,'min_child_samples' : 40,
'learning_rate':0.018, 'random_state':2019, 'metric':"auc",'n_jobs':-1
}
print('Starting training...')
# train
gbm_fa = lgb.train(params,
lgb_train_fa,
valid_sets=lgb_eval_fa,
early_stopping_rounds=50,verbose_eval=20)
注:一开始我使用的时lgb的sklearn中的api:lgb.LGBMClassifier。但是后来上网发现lgb可以处理类别型特征,可以大大减少工作量,但是需要使用自己的api。
具体操作就是需要构建lgb.Dataset,在其中表明哪几个特征是类别型的(一定要真的转换为类别型,否则报错)。
再利用lgb.train将参数、lgb.Dataset传入,进行训练。有千分点级别的提升。
lgb叶子节点+LR
这个是facebook提出来的,应用到比赛中效果一般,比直接lgb要差、慢,很可能是我叶子节点的个数选择、及部分调参工作还有待加强。
from sklearn.linear_model import LogisticRegression
y_pred_train = lgb_model_pur.predict(train_lxy_x, pred_leaf=True)#这里是关键,保留叶子节点
#num_leaf=300 原来是300,有问题这里 应该改为20
num_leaf=20 #这里是叶子节点数量
transformed_training_matrix = np.zeros([len(y_pred_train), len(y_pred_train[0]) * num_leaf],
dtype=np.int64) # N * num_tress * num_leafs
for i in range(0, len(y_pred_train)):
temp = np.arange(len(y_pred_train[0])) * num_leaf + np.array(y_pred_train[i])
transformed_training_matrix[i][temp] += 1
##===================== 测试集转换
print('Writing transformed testing data')
y_pred_test = lgb_model_pur.predict(test_lxy_x, pred_leaf=True)
transformed_testing_matrix = np.zeros([len(y_pred_test), len(y_pred_test[0]) * num_leaf], dtype=np.int64)
for i in range(0, len(y_pred_test)):
temp = np.arange(len(y_pred_test[0])) * num_leaf + np.array(y_pred_test[i])
transformed_testing_matrix[i][temp] += 1
##================================= LR ======================================
lm = LogisticRegression(penalty='l2',C=0.05) # logestic model construction
lm.fit(transformed_training_matrix,train_lxy_y_pur) # fitting the data
y_pred_lr_test = lm.predict_proba(transformed_testing_matrix) # Give the probabilty on each label
y_pred_lr_test
!!
复盘时发现问题:
1、num_leaves应该小于等于2^max_depth 之前实验时设置有误
max_depth : 树的深度
num_leaves : 树的最大叶子节点数
num_iterations 迭代次数
2、#num_leaf=300 原来是300,有问题这里 应该改为20
num_leaf=20 #这里是叶子节点数量
libfm
想应用libfm,但是每个fm需要有固定长的onehot样本以供其进行fm操作。
所以自然而然的想到可以使用lgb+libfm进行操作。
libfm下载我是windows,就不用进行编译了。
libfm数据格式:
使用libfm很是耗费了我一番精力,主要是自己犯懒,不想写数字转onehot以及转libfm格式的程序,
妄图使用官网提供的工具。结果实践证明不如自己干。
贴出部分我的血泪史:
最后幡然醒悟,几行代码的事,还是得靠自己
1、onehot化
#训练集gbdt叶子onehot化
y_pred_train = gbm_fa.predict(train_lxy_x, pred_leaf=True)
num_leaf=20
transformed_training_matrix = np.zeros([len(y_pred_train), len(y_pred_train[0]) * num_leaf],dtype=np.int64)
# N * num_tress * num_leafs
for i in range(0, len(y_pred_train)):
temp = np.arange(len(y_pred_train[0])) * num_leaf + np.array(y_pred_train[i])
transformed_training_matrix[i][temp] += 1
#训练集gbdt叶子onehot化
y_pred_valid = gbm_fa.predict(valid_lxy_x, pred_leaf=True)
num_leaf=20
transformed_valid_matrix = np.zeros([len(y_pred_valid), len(y_pred_valid[0]) * num_leaf],dtype=np.int64)
# N * num_tress * num_leafs
for i in range(0, len(y_pred_valid)):
temp = np.arange(len(y_pred_valid[0])) * num_leaf + np.array(y_pred_valid[i])
transformed_valid_matrix[i][temp] += 1
#拼接特征与结果
#train_y_concat=np.concatenate((transformed_training_matrix, train_lxy_y_fa.values.reshape(len(train_lxy_y_fa.values),1)), axis=1)
##===================== 测试集转换
#测试集gbdt叶子onehot化
print('Writing transformed testing data')
y_pred_test = gbm_fa.predict(test_lxy_x, pred_leaf=True)
transformed_testing_matrix = np.zeros([len(y_pred_test), len(y_pred_test[0]) * num_leaf], dtype=np.int64)
for i in range(0, len(y_pred_test)):
temp = np.arange(len(y_pred_test[0])) * num_leaf + np.array(y_pred_test[i])
transformed_testing_matrix[i][temp] += 1
2、转libfm格式(libsvm)
def np_to_libsvm_format(filename,transformed_matrix,y_value):
#filename 转换格式后的输出文件
#transformed_matrix onehot后的待转矩阵
#y_value 对应训练集的y值
output = open(filename, 'w') #中间文件
lenmat=len(transformed_matrix)
linelen = 20000
for i in range(lenmat):
output_line = ''
if np.isnan(transformed_matrix[i,0]):
break
for j in range(linelen):
#the features cols
if j==0:
output_line = output_line + str(int(y_value[i]))
if transformed_matrix[i,j]==1 :
the_text = ' ' + str(j) + ':' + str(1)
output_line = output_line + the_text
output_line = output_line + '\n'
output.write(output_line)
output.close()
libfm的Manual写的太简洁了,加上我理解力不足,搞定这个让我死了不少脑细胞。
以上面的例子为例
训练集是有标签的,但是测试集是否还需要留y值的位置?
我用实验证明,即使你的测试集你不知道y值是多少,你也得强行写上y值(哪怕全0)
测试截图:
3、libfm使用
最后理解了,为了追求简洁。实际上这里的-test 是既可以放验证集也可以放测试集。
如果需要线下验证模型,就在test上放入验证集,可以显示出训练和测试的loss。
如果要预测训练集,就在test放入训练集,如果你设置测试集为全0,就不要去看结果中测试集的loss了,没啥意义。
另外,这里的loss是类mse,且只有一种loss不可更换。这让用auc作为评判标准的题目很是尴尬,但是至少明白loss的意义对调参还是有用的。
命令:
./libFM -task c -train ml1m-train.libfm -test ml1m-test.libfm -dim ’1,1,8’
神奇的是train的loss不停上升。。但是结果还可以上0.6了,有些迷惑,望有经验的同志们解答。
最后调参调了lgb的叶子数、训练轮数、dim of 2-way interactions、分类器(MCMC、SGD、SGDA)MCMC效果最好,也最快。
其他
使用了xgboost、随机森林、LR,效果都不如lgb
相关性选择
选择相关性靠前的x个特征,输入分类器,有略微提高
#相关性判定-favorite
train_lxy['favorite']=train_lxy['favorite'].astype('float')
d={}
c={}
e={}
for i in train_feature:
if (i!='favorite')&(i!='purchase'):
data=pd.concat([train_lxy[i],train_lxy['favorite']],axis=1)
c[i]=data.corr(method='spearman').values[1,0] # Sperman秩相关系数
d[i]=data.corr().values[1,0] # Pearson相关系数 - 算法
e[i]=data.corr(method='kendall').values[1,0]
train_feature_kendall = []
for i in train_feature:
if abs(e[i]) > 0.01:
train_feature_kendall.append(i)
train_feature_kendall.append('seller')
train_feature_kendall.append('Product_id')
train_feature_kendall.append('user_id')
train_feature_kendall