记录一下一次小型kaggle比赛 QBUS6810 Classification Challenge,逻辑回归,处理连续型和离散型数据

 

前言

目前是小型比赛的public lb第四名,自己对此也比较满意了,从中也学到不少知识。发个图纪念一下,从一开始的0.76,到0.85,再到现在0.86139,每一次的进步都来之不易。之后private lb选了一个比较低的提交,因为是校外的,就不影响他们的成绩了。
 

记录一下一次小型kaggle比赛 QBUS6810 Classification Challenge,逻辑回归,处理连续型和离散型数据_第1张图片

这个比赛的目标是给出一些市场的环境,预测消费者会不会在该环境进行消费。接下来简单的复盘一下:

 

1. EDA(数据探索):

首先是data的overview

记录一下一次小型kaggle比赛 QBUS6810 Classification Challenge,逻辑回归,处理连续型和离散型数据_第2张图片

检查代码是否有空值 

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,画出图,可以看到区分度不大,可以删去。代码略过。

记录一下一次小型kaggle比赛 QBUS6810 Classification Challenge,逻辑回归,处理连续型和离散型数据_第3张图片

 

打开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

接下来画出连续型变量的分布 

记录一下一次小型kaggle比赛 QBUS6810 Classification Challenge,逻辑回归,处理连续型和离散型数据_第4张图片

 对这些靠近0分布比较多的变量可以考虑用log1p,这里加了之后分数一下子上去了。引用别人的话

  1. 在数据预处理时首先可以对偏度比较大的数据用log1p函数进行转化,使其更加服从高斯分布,此步处理可能会使我们后续的分类结果得到一个更好的结果;
  2. 平滑处理很容易被忽略掉,导致模型的结果总是达不到一定的标准,同样使用逼格更高的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')

 

2. 数据预处理

首先是要读取

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)

3.建立模型和输出

模型我选择了逻辑回归,当然也可以尝试其他的分类器,比如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')

总结

特征工程比优化模型重要,值得花更多的时间去探索

你可能感兴趣的:(机器学习,Python)