目前是小型比赛的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()) <class 'pandas.core.frame.DataFrame'> 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是结果,记录是否消费。(提交要预测概率)
variabletypevisitsdiscretetotal_salescontinuouscredit_cardbinarysales_product_category_1continuoussales_product_category_2continuous............sales_product_category_14continuoussales_product_category_15continuoussales_store_1dropsales_store_2drop............sales_last_yeardropmargincontinuouspromosdiscretedays_on_filediscretedays_between_purchasescontinuousmarkdowncontinuouscrossbuydiscrete............stores_visiteddiscretephone_on_filedroponline_shopperbinaryattemptscategoricalconversionscategoricalproduct_uniformitycontinuousdays_between_visitscontinuouscustomer_typedropreturnscontinuousconversionresponse接下来画出连续型变量的分布
对这些靠近0分布比较多的变量可以考虑用log1p,这里加了之后分数一下子上去了。引用别人的话
在数据预处理时首先可以对偏度比较大的数据用log1p函数进行转化,使其更加服从高斯分布,此步处理可能会使我们后续的分类结果得到一个更好的结果;平滑处理很容易被忽略掉,导致模型的结果总是达不到一定的标准,同样使用逼格更高的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')特征工程比优化模型重要,值得花更多的时间去探索