目前是小型比赛的public lb第四名,自己对此也比较满意了,从中也学到不少知识。发个图纪念一下,从一开始的0.76,到0.85,再到现在0.86139,每一次的进步都来之不易。之后private lb选了一个比较低的提交,因为是校外的,就不影响他们的成绩了。
这个比赛的目标是给出一些市场的环境,预测消费者会不会在该环境进行消费。接下来简单的复盘一下:
首先是data的overview
检查代码是否有空值
print(data.isnull().sum())
visits 0
total_sales 0
credit_card 0
sales_product_category_1 0
sales_product_category_2 0
......
returns 0
conversion 0
dtype: int64
按数据类型初步划分连续还是离散数据,float类型当成是连续的
print(data.info())
RangeIndex: 4122 entries, 0 to 4121
Data columns (total 44 columns):
visits 4122 non-null int64
total_sales 4122 non-null float64
credit_card 4122 non-null int64
sales_product_category_1 4122 non-null float64
sales_product_category_2 4122 non-null float64
......
returns 4122 non-null float64
conversion 4122 non-null int64
dtypes: float64(30), int64(13), object(1)
这里看到object类型,我们要把它转化成数值型
print(data['phone_on_file'].value_counts())
这里把y变成1,n变成0,画出图,可以看到区分度不大,可以删去。代码略过。
打开csv文件,可以发现total_sales是一列汇总数,等于sales_stores_n几列相加,n为1到4。我们可以直接把sales_stores给drop了,减少共线性。
然后查看连续变量的热力图,可以继续删除相关程度高的变量。代码略过。
以下就是variables.csv的分类结果。
drop代表删掉,因为相关度高或者对模型影响不大。
binary是bool型数据,dicrete是离散型,continuous是连续型,
categorical是个人认为的类型,和离散型差不多。response是结果,记录是否消费。(提交要预测概率)
variable | type |
visits | discrete |
total_sales | continuous |
credit_card | binary |
sales_product_category_1 | continuous |
sales_product_category_2 | continuous |
...... | ...... |
sales_product_category_14 | continuous |
sales_product_category_15 | continuous |
sales_store_1 | drop |
sales_store_2 | drop |
...... | ...... |
sales_last_year | drop |
margin | continuous |
promos | discrete |
days_on_file | discrete |
days_between_purchases | continuous |
markdown | continuous |
crossbuy | discrete |
...... | ...... |
stores_visited | discrete |
phone_on_file | drop |
online_shopper | binary |
attempts | categorical |
conversions | categorical |
product_uniformity | continuous |
days_between_visits | continuous |
customer_type | drop |
returns | continuous |
conversion | response |
接下来画出连续型变量的分布
对这些靠近0分布比较多的变量可以考虑用log1p,这里加了之后分数一下子上去了。引用别人的话
原文链接:https://blog.csdn.net/qq_36523839/article/details/82422865
在讲数据预处理前,先给出要用到库,以sklearn为主
import time
import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectKBest
from sklearn.preprocessing import StandardScaler, PowerTransformer, OneHotEncoder
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegressionCV
import warnings
warnings.filterwarnings('ignore')
首先是要读取
data = pd.read_csv('train.csv')
test = pd.read_csv('test.csv', index_col='Id')
variables = pd.read_csv('./variables.csv', index_col='variable')
variables = variables['type']
# 这样有助于变量分类处理
continuous = variables[variables == 'continuous'].index.tolist()
discrete = variables[variables == 'discrete'].index.tolist()
binary = variables[variables == 'binary'].index.tolist()
categorical = variables[variables == 'categorical'].index.tolist()
response = variables[variables == 'response'].index.tolist()
predictors = continuous + discrete + binary + categorical
接着是对数据进行平滑(十分重要)
# log1p,这个labels是通过数据探索和个人分析得到,分数从200名一下子进到前4
log_labels = ['coupons', 'stores_visited', 'visits', 'crossbuy', 'individual_items', 'total_sales',
'sales_product_category_1', 'sales_product_category_2', 'sales_product_category_3',
'sales_product_category_4', 'sales_product_category_5', 'sales_product_category_6',
'sales_product_category_7', 'sales_product_category_8', 'sales_product_category_9', 'markdown',
'sales_product_category_10', 'sales_product_category_11', 'sales_product_category_12',
'sales_product_category_13', 'sales_product_category_14', 'sales_product_category_15', 'returns']
data[log_labels] = np.log1p(data[log_labels])
test[log_labels] = np.log1p(test[log_labels])
然后是划分数据,此时还没划分训练集和验证集,可以留意到X_data和test的数据结构是一样的,结果单独拿出来了(y_data)
X_data, y_data, test = data[predictors], data[response], test[predictors]
y_data = np.ravel(y_data)
接下来我们对离散型和类别数据进行dummy(one hot encoder),我把要dummy的列给删掉,拼接上dummy后的列。这里不用get_dummies是因为我发现对binary无序变量是没作处理,这样模型会认为1的权重更大,而0的权重很少。举个例子,性别如果男的标为1,女的标为0,我们需要把男的变为10,女的变为01,这样才能有效学习无序的变量。
重点:我们先要把训练集和测试集拼接起来,这样dummy就能对齐。防止有些变量在训练集里有而测试集没有,或者相反情况
# onehot
dum_label = binary + categorical
dum = pd.concat((X_data[dum_label], test[dum_label]))
oh = OneHotEncoder(sparse=False)
dum = oh.fit_transform(dum)
X_data = X_data.drop(columns=dum_label)
test = test.drop(columns=dum_label)
X_data = X_data.join(pd.DataFrame(dum[:len(X_data)]))
test = test.join(pd.DataFrame(dum[len(X_data):]))
然后我们进行归一化处理,这样模型能更好提取特征
重点:我们scaler不能对上面dummy后的变量进行处理,要另外处理
# yeo-j
yeoj = PowerTransformer(method='yeo-johnson')
yeojtransf = ['total_sales', 'margin', 'days_between_purchases', 'product_uniformity', 'days_between_visits']
X_data[yeojtransf] = yeoj.fit_transform(X_data[yeojtransf])
test[yeojtransf] = yeoj.transform(test[yeojtransf])
# scaler
ss = StandardScaler()
sstransf = continuous + discrete
X_data[sstransf] = ss.fit_transform(X_data[sstransf])
test[sstransf] = ss.transform(test[sstransf])
接下来可以划分训练集和验证集,这里我选择略过验证集,用更大量的数据去训练 。
X_train, y_train = X_data, y_data
# 想要校验集的话可以用下面代码,还可以对结果进行分层,可以设置stratify参数
# X_train, X_val, y_train, y_val = train_test_split(X_data, y_data, test_size=0.2,
# random_state=5)
模型我选择了逻辑回归,当然也可以尝试其他的分类器,比如svm,lgb等等,我利用了gridsearchCV来寻找最佳超参,利用pipeline构建模型。大家有时间可以尝试不同的组合,pca是用来降维,SelectKbest是用来选择最好的变量。当然各种归一化,平滑也可以放到里面。但由于我是按类型进行处理,之前已经处理了,这里就没放了。
param_grid = {
"logit__Cs": [10, 15, 20, 50],
"logit__solver": ['liblinear'],
"logit__penalty": ['l1', 'l2'],
"logit__cv": [3, 4, 5],
"logit__max_iter": [100, 200, 300],
"logit__scoring": ["neg_log_loss"]
}
pipe = Pipeline([
# ('pca', PCA(0.996)),
('select', SelectKBest(k=25)),
('logit', LogisticRegressionCV())])
grid = GridSearchCV(pipe, n_jobs=-1, param_grid=param_grid, cv=5, scoring='roc_auc', verbose=1)
grid.fit(X_train, y_train)
print(grid.best_score_)
print(grid.best_estimator_)
最后是输出
# output
test_pred = grid.predict_proba(test)[:, 1]
output_name = time.strftime("%y%m%d") + '_log_submission.csv'
output = pd.DataFrame(data=test_pred, columns=response)
output.to_csv(output_name, index_label='Id')
特征工程比优化模型重要,值得花更多的时间去探索