前言: 模拟题2是一道2分类预测类建模,需要预测利润而不是传统的准确率或召回率等,这就代表用一个模型是没有办法获得最高分的,必须根据不同的情况进行调参。
虽然没有答案,但是好在有一个类似的练习赛
地址是: http://jingsai.cda.cn/info/id/6.html
需要注意的是:
1、练习赛的数据集小于模拟题的数据集,不要搞混了;
2、练习赛的评判标准是accuracy
接下去的代码实现都是基于练习赛的数据集,以线上的accuracy作为评判标准。同时会写利润预测的metrics以供参考(目前还没有写出来)。如果要做到最优化,还是要使用代价敏感算法,但这已经超纲了。
重要提示:
1、从练习赛分数就可以看出,大家的得分都是比较接近的,而且不管是训练集还是测试集的数据质量都很差,很难做特征工程;
2、虽然练习赛分数是按accuracy,但是已经结束的B榜还是按照混淆矩阵模拟利润的,所以还是有必要自己自定义一个metrics以便模型调参。
A公司希望发掘用户购买产品的行为习惯,建立产品精准营销模型,对有意向的客户进行精准营销,增加收入,减少开支。
参赛选手将取得训练数df_training.csv及测试数据df_test.csv。
训练数据包含6000笔客户资料;每笔客户资料包含12个字段(1个客户ID字段、10个输入字段及1个目标字段-Purchase or not购买与否(0代表未购买,1代表购买)。
测试数据包含2000笔客户资料;字段个数与训练数据相同,唯目标字段的值全部填“Withheld”。
我们通过混淆矩阵(Confusion matrix)来评价分类模型的准确率。准确率越高,说明正确预测出响应营销效果越好,混淆矩阵如下图所示:
准确率公式:
其中,acc是准确率,是正确预测购买产品和不买产品的数目与总数目(也就是2000)的比率;TP表示正确预测购买产品的客户数目,FN表示实际上购买但预测为不买的客户数目,FP是实际上不买但预测为购买的客户数目,TN是正确预测不购买产品的客户数目,TP+FP+FN+TN也就是测试数据里客户的总数2000人。
实务应用中,老板是否采用你的营销方案往往不是以预测结果的正确率做为考虑‚而是以模型能带给公司多少的利润最为最终的考虑。
参与比赛的成绩以公司获得的利润结果排序,模型越赚钱者越好。为此‚我们做了以下两个假设,选手需分别将预测结果存于results_A.csv及results_B.csv。
两个假设如下:
(A) P产品价格$2,800元,成本$800元,营销文宣成本$500元/每位顾客,价格减成本后,每个产品若有卖出公司则获利$1,500元。根据上述数据‚以下是这个产品的成本矩阵:
因此‚若模型对测试数据预测的混淆矩阵如下:
则此模型的得分为1411500-76500=173,500分,分数越高越好。
(B) P产品价格$2,000元,成本$800元,营销文宣成本$500元/每位顾客,价格减成本后,每个产品若有卖出公司则获利$700元。根据上述数据‚以下是这个产品的成本矩阵:
因此‚若模型对测试数据预测的混淆矩阵如下:
则此模型的得分为83700-170500= 58,100-85,000=-26,900 分,分数越高越好。
以下是正文了,教你如何达到baseline(也仅仅只是baseline)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
df_test = pd.read_csv('df_test.csv')
df_training = pd.read_csv('df_training.csv')
df_training.info()
分析:很多应该是数值型的都是object,后面需要转变形式。
object_features = list(set(df_training.columns) - set(['ID', "'Purchase or not'"]))
for feature in object_features:
print(feature)
why_temp = df_training[df_training[feature]=='?']
why_temp_per = why_temp["'Purchase or not'"].value_counts()[1]/why_temp.shape[0]
print('空值的百分比:%.2f'% why_temp_per)
df_train = df_training.copy()
df_train[feature] = df_train[feature].replace('?', np.nan).dropna()
if feature not in ["'User area'", 'gender']:
df_train[feature] = pd.to_numeric(df_train[feature])
if len(df_train[feature].unique())>=20:
df_train['cut'] = pd.cut(df_train[feature],bins=20, include_lowest=True, right=False, precision=0)
temp_pivot = df_train.pivot_table(index="cut", values='ID', columns="'Purchase or not'", aggfunc='count', fill_value=0)
else:
temp_pivot = df_train.pivot_table(index=feature, values='ID', columns="'Purchase or not'", aggfunc='count', fill_value=0)
temp_pivot['percent'] = temp_pivot[1] / (temp_pivot[0]+temp_pivot[1])
temp_pivot['percent'].plot.bar()
plt.show()
说明:上面的代码主要是用来数据清洗和画图的,也就是传统的EDA
根据上面的数据分析,可以得出几个结论:
1、每个特征值的缺失值数量都差不多,且缺失值的’Purchase or not’比例基本接近,都是20%左右。所以针对这一点,不建议做缺失值填充;
2、对于gender来说,Female的比例(指’Purchase or not’中1的占比,下同)高于Male一大截;
3、对于Active User来说,0的用户要高于1;
4、对于age来说,总体呈近正态分布,48-62岁明显高于其它年龄段,72岁以后接近于0,但84岁有一个异常值,建议删除;
5、对于User area(用户地区)来说,Taichung的比例远高于Tainan和Taipei;
6、对于Product using score(产品使用分数)来说,410分以下的购买可能基本为0,其它年龄段则分布不均、时高时低;
7、对于Estimated salary(估计薪资)来说,除了少数的几个区间外,大部分都在20%左右;
8、对于Point balance(点数余额)来说,有2个区间特别高,其它则忽高忽低;
9、对于Cumulative using time(使用累计时间)来说,除0特别高以外,8特别低以外,其它都差不多;
10、对于Product service usage(产品服务使用量)来说,3和4特别高,1在30%,2则仅10%左右;
11、对于Pay a monthly fee by credit card(是否使用信用卡付月费)来说,两者差不多。
df_total = pd.concat([df_training, df_test], axis=0)
area_map = {
'Taichung': 0, 'Tainan': 1, 'Taipei': 2, '?': 3}
gender_map = {
'Female': 0, 'Male': 1, '?': 3}
df_total["'User area'"] = df_total["'User area'"].map(area_map)
df_total["gender"] = df_total["gender"].map(gender_map)
df_total = df_total.replace('?', '-1')
for feature in object_features:
df_total[feature] = pd.to_numeric(df_total[feature])
new_train_data = df_total[df_total["'Purchase or not'"]!='Withheld']
new_train_data["'Purchase or not'"] = pd.to_numeric(new_train_data["'Purchase or not'"])
new_test_data = df_total[df_total["'Purchase or not'"]=='Withheld']
new_train_data.info()
#简单预测
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import lightgbm as lgb
predictors = ["'Product using score'", "'User area'", "gender", "age", "'Cumulative using time'", "'Point balance'",
"'Product service usage'", "'Pay a monthly fee by credit card'", "'Active user'", "' Estimated salary'"]
# cate_feature_en = ["'User area'", "gender", 'Pay a monthly fee by credit card', 'Active user']
params = {
'num_leaves': 30, #结果对最终效果影响较大,越大值越好,太大会出现过拟合
'min_data_in_leaf': 30,
'objective': 'binary', #定义的目标函数
'max_depth': -1,
'learning_rate': 0.01,
"min_sum_hessian_in_leaf": 6,
"boosting": "gbdt",
"feature_fraction": 0.8, #提取的特征比率
"bagging_freq": 1,
"bagging_fraction": 0.8,
"bagging_seed": 11,
"lambda_l1": 0.1, #l1正则
# 'lambda_l2': 0.001, #l2正则
"verbosity": -1,
"nthread": -1, #线程数量,-1表示全部线程,线程越多,运行的速度越快
'metric': {
'binary_logloss'}, ##评价函数选择
"random_state": 2019, #随机数种子,可以防止每次运行的结果不一致
# 'device': 'gpu' ##如果安装的事gpu版本的lightgbm,可以加快运算
}
X_train, X_val, y_train, y_val = train_test_split(new_train_data[predictors], new_train_data["'Purchase or not'"],
test_size=0.2, random_state=2019)
train_data = lgb.Dataset(X_train, label=y_train)
val_data = lgb.Dataset(X_val, label=y_val, reference=train_data)
evals_result = {
} #记录训练结果所用
model = lgb.train(params,
train_data,
num_boost_round=20000,
valid_sets=val_data,
early_stopping_rounds=100,
# categorical_feature = cate_feature_en,
evals_result = evals_result,
verbose_eval=500)
pred1 = model.predict(new_test_data[predictors])
pred = np.where(pred1>=0.5, 1, 0)
#计算最终利润
con_matrix = confusion_matrix(y_val, pred)
print(con_matrix)
last_profit1 = con_matrix[0][0] * 1500 - con_matrix[0][1] * 500
print(last_profit1)
last_profit2 = con_matrix[0][0] * 700 - con_matrix[0][1] * 500
print(last_profit2)
df_test['Predicted_Results'] = pred
df_test[['ID', 'Predicted_Results']].to_csv('test_pred.csv', index=False)
有些人没有LightGBM,用RandomForest模型也是一样的
#使用RF进行简单预测
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import KFold
X_train, X_val, y_train, y_val = train_test_split(new_train_data[object_features], new_train_data["'Purchase or not'"],
test_size=0.2, random_state=2019, stratify=new_train_data["'Purchase or not'"])
rf = RandomForestClassifier(n_estimators=50, min_samples_split=5, min_samples_leaf=3)
rf.fit(X_train, y_train)
print(accuracy_score(rf.predict(X_val), y_val))
pred = rf.predict(new_test_data[object_features])
new_test_data['Predicted_Results'] = pred
new_test_data[['ID', 'Predicted_Results']].to_csv('up_answer.csv', index=False)
后续优化:
1、自定义损失函数;
2、自定义metrics