datawhale2023第三期夏令营机器学习笔记

本篇内容是datawhale2023第三期夏令营机器学习笔记-第一篇

一 baseline流程跑通

1 加载必要的库

import pandas as pd 
import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import f1_score

2 读取数据

train_data = pd.read_csv('data/train.csv')
test_data = pd.read_csv('data/test.csv')

3 特征工程

3.1 变换时间戳

这里将以毫秒表示的时间转为常见的时间格式。

train_data['common_ts'] = pd.to_datetime(train_data['common_ts'], unit='ms')
test_data['common_ts'] = pd.to_datetime(test_data['common_ts'], unit='ms')

例如:将1689673468244转为时间2023-07-18 09:44:28.244000

3.2 udmap特征处理

由于原始的udmap特征是非结构化的形式存储,机器学习不支持该类数据的输入,因此需要转化为结构化的特征。

def udmap_onethot(d):
    v = np.zeros(9)
    if d == 'unknown':
        return v   
    d = eval(d) #将字符形式的数据解析为字典
    for i in range(1, 10):
        if 'key' + str(i) in d:
            v[i-1] = d['key' + str(i)]       
    return v
train_udmap_df = pd.DataFrame(np.vstack(train_data['udmap'].apply(udmap_onethot)))
test_udmap_df = pd.DataFrame(np.vstack(test_data['udmap'].apply(udmap_onethot)))

在 python 中, 形如 {'key1': 3, 'key2': 2} 格式的为字典类型对象, 通过key-value键值对的方式存储。而在本数据集中, udmap实际是以字符的形式存储, 所以处理时需要先用eval 函数将udmap 解析为字典。

将经过处理的udmap特征重命名,并与原始的数据进行拼接。

train_udmap_df.columns = ['key' + str(i) for i in range(1, 10)]
test_udmap_df.columns = ['key' + str(i) for i in range(1, 10)]

train_data = pd.concat([train_data, train_udmap_df], axis=1)
test_data = pd.concat([test_data, test_udmap_df], axis=1)

3.3 构建访问频率特征

根据不同的访问行为,得到各自的频率。

train_data['eid_freq'] = train_data['eid'].map(train_data['eid'].value_counts())
test_data['eid_freq'] = test_data['eid'].map(train_data['eid'].value_counts())

3.4 不同访问行为目标值的均值特征

根据不同的访问行为进行数据分组,并将同一组的目标值计算均值。

train_data['eid_mean'] = train_data['eid'].map(train_data.groupby('eid')['target'].mean())
test_data['eid_mean'] = test_data['eid'].map(train_data.groupby('eid')['target'].mean())

3.5 行为属性为unknown的记为1

原始的unknown不好表达,因此转为数值型的数据。

train_data['udmap_isunknown'] = (train_data['udmap'] == 'unknown').astype(int)
test_data['udmap_isunknown'] = (test_data['udmap'] == 'unknown').astype(int)

3.6 提取小时特征

train_data['common_ts_hour'] = train_data['common_ts'].dt.hour
test_data['common_ts_hour'] = test_data['common_ts'].dt.hour

4 模型训练

clf = DecisionTreeClassifier()
clf.fit(
    train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1),
    train_data['target']
)

5 结果预测

predict = pd.DataFrame({
    'uuid': test_data['uuid'],
    'target': model.predict(test_data.drop(['udmap', 'common_ts', 'uuid'], axis=1))
})
predict.to_csv('predict/submit_20230813_01.csv', index=None)

6 本地结果验证

6.1 所有数据本地验证

根据4的模型训练,计算在本地训练的情况下,f1_score的得分。

pred = clf.predict(train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1))
accuracy = f1_score(train_data['target'],pred, average='macro')
print('在训练集上的精确度: %.4f'%accuracy)

#结果为:0.9521

6.2 切分数据为训练集与验证集

这里只用训练集进行数据的训练,查看训练集与验证集的差距。

#random_state:保证结果复现
train, val = train_test_split(train_data, test_size=0.2, random_state=2023, shuffle=True)

clf1 = DecisionTreeClassifier()
clf1.fit(
    train.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1),
    train['target']
)
pred1 = clf1.predict(train.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1))
accuracy1 = f1_score(train['target'],pred1, average='macro')
print('在训练集上的精确度: %.4f'%accuracy1)

pred2 = clf1.predict(val.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1))
accuracy2 = f1_score(val['target'],pred2, average='macro')
print('在测试集上的精确度: %.4f'%accuracy2)

'''  结果如下:
在训练集上的精确度: 0.9555
在测试集上的精确度: 0.7715
'''

这里可以看出来在训练集表现良好,验证集的结果很差,说明模型过拟合了。

6.3 采用交叉验证查看结果

'''***---   增加交叉验证在默认参数情况下   ---***'''
# 原始训练集
scores = cross_val_score(clf,train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1),
                          train_data['target'],cv=5, scoring='f1_macro')
print("训练集上的精确度: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))

# 切分后的训练集
scores1 = cross_val_score(clf1,train.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1),
                          train['target'],cv=5, scoring='f1_macro')
print("训练集上的精确度: %0.2f (+/- %0.2f)" % (scores1.mean(), scores1.std() * 2))

scores2 = cross_val_score(clf1,val.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1),
                          val['target'],cv=5, scoring='f1_macro')
print("测试集上的平均精确度: %0.2f (+/- %0.2f)" % (scores2.mean(), scores2.std() * 2))

'''
		结果为:
	训练集上的精确度: 0.77 (+/- 0.00)
	训练集上的精确度: 0.76 (+/- 0.00)
	测试集上的平均精确度: 0.68 (+/- 0.01)
'''

6.4 线上提交结果

线上结果为:0.62687。

7 结论

以上是baseline的整个流程,由最后的本地结果验证可以知道,模型存在过拟合的情况。

以下是采用lightgbm模型进行训练,如果采用默认参数,效果很差,线上分数大概为0.18左右(我已经尝试过了,小伙伴们可以不用尝试了),因此这里采用了optuna进行了模型的粗调。

lightgbm + optuna

1 将baseline的1-4步骤封装至Load_data.py

# -*- coding: utf-8 -*-
"""
将baseline提供的特征工程封装为一个函数,并返回训练集与测试集
"""
import pandas as pd
import numpy as np

def load_data():
    # 1.加载数据
    train_data = pd.read_csv('data/train.csv')
    test_data = pd.read_csv('data/test.csv')
    
    # 2.将时间戳转为常用的时间格式
    train_data['common_ts'] = pd.to_datetime(train_data['common_ts'], unit='ms')
    test_data['common_ts'] = pd.to_datetime(test_data['common_ts'], unit='ms')
    
    # 3.对其中一列变量进行one-hot编码
    def udmap_onethot(d):
        v = np.zeros(9)
        if d == 'unknown':
            return v
        
        d = eval(d)
        for i in range(1, 10):
            if 'key' + str(i) in d:
                v[i-1] = d['key' + str(i)]
                
        return v
    
    train_udmap_df = pd.DataFrame(np.vstack(train_data['udmap'].apply(udmap_onethot)))
    test_udmap_df = pd.DataFrame(np.vstack(test_data['udmap'].apply(udmap_onethot)))
    
    train_udmap_df.columns = ['key' + str(i) for i in range(1, 10)]
    test_udmap_df.columns = ['key' + str(i) for i in range(1, 10)]
    
    train_data = pd.concat([train_data, train_udmap_df], axis=1)
    test_data = pd.concat([test_data, test_udmap_df], axis=1)
    
    # 4.访问行为特征得到访问频率
    train_data['eid_freq'] = train_data['eid'].map(train_data['eid'].value_counts())
    test_data['eid_freq'] = test_data['eid'].map(train_data['eid'].value_counts())
    
    # 5.不同访问行为目标值的均值
    train_data['eid_mean'] = train_data['eid'].map(train_data.groupby('eid')['target'].mean())
    test_data['eid_mean'] = test_data['eid'].map(train_data.groupby('eid')['target'].mean())
    
    # 6.行为属性为unknown的记为1
    train_data['udmap_isunknown'] = (train_data['udmap'] == 'unknown').astype(int)
    test_data['udmap_isunknown'] = (test_data['udmap'] == 'unknown').astype(int)
    
    # 7.提取小时特征
    train_data['common_ts_hour'] = train_data['common_ts'].dt.hour
    test_data['common_ts_hour'] = test_data['common_ts'].dt.hour
    
    return train_data,test_data

2 加载必要的库

import numpy as np
from Load_data import load_data #加载1中的数据。
import optuna
import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from optuna.visualization import plot_optimization_history
from optuna.visualization import plot_param_importances
import time

3 读取数据

train_data, test_data = load_data()
# 在训练集将原始的udmap、时间戳、uuid、target四列数据去掉,得到特征X与标签y
X = train_data.drop(['udmap', 'common_ts', 'uuid', 'target'], axis=1)
y = train_data['target']
# 将训练集分为训练集与验证集,验证集用于模型的评估
train_x, test_x, train_y, test_y = train_test_split(X, y, test_size=0.2,random_state=2023)
# 将训练集与验证集转换为lightgbm可以加载的数据格式
train_data = lgb.Dataset(data = train_x, label=train_y)
val_data = lgb.Dataset(data = test_x, label=test_y)

4 参数调整

4.1 自定义评估函数f1_score

由于lightgbm没有内置该函数,因此自定义该评估函数

'''
	自定义的评估函数要求返回三个参数
        name:评估函数名字,字符串格式
        score: 计算出的分数
        is_higher: 分数是否越高越好(True / False)
'''
def f1_eval(preds, true):
    true = true.get_field('label')
    preds = np.where(preds >= 0.5, 1, 0)
    f_score = f1_score(true , preds,  average = 'macro')
    return 'f1_score', f_score, True

4.2 定义优化目标

def objective(trial): 
    
    params_naive={
        "learning_rate":trial.suggest_categorical('learning_rate', [0.01, 0.02, 0.05, 0.005, 0.1]),
        'max_bin':63,
        'num_leaves':trial.suggest_int('num_leaves', 11, 333),
        "max_depth":trial.suggest_int('max_depth', 5, 64),
        'num_boost_round': trial.suggest_int('num_boost_round', 2000, 8000),
        "lambda_l1":trial.suggest_float('lambda_l1', 0.001, 10.0),
        "lambda_l2":trial.suggest_float('lambda_l2', 0.001, 10.0),
        'random_state': 42,
        'boosting_type': 'gbdt',
        'metric': 'f1_score',
        'device': 'cpu'
        # 安装了gpu版本的可以采用gpu训练
        #'gpu_platform_id': 0,
        #'gpu_device_id': 0
    }

    bst = lgb.train(params_naive, train_data, valid_sets=[val_data], callbacks=[lgb.early_stopping(stopping_rounds=300)]
                    ,verbose_eval = 50, feval=f1_eval)
    
    preds = bst.predict(test_x) 
    preds = np.where(preds >= 0.5, 1, 0)
    f1 = f1_score(test_y, preds,  average = 'macro')    
    trial.set_user_attr("f1_score_val", f1)
    
    return f1

4.3 开始优化

study = optuna.create_study(direction='maximize', sampler=optuna.samplers.TPESampler())
study.optimize(objective, n_trials=20)

5 保存最好的模型

这里训练集可以采用全部训练集,也可以采用切分后的训练集。

result = lgb.train(params=study.best_params, train_set=train_data)
result.save_model("model/light_20230816_01.txt")

6 加载模型并查看特征重要性

gbm = lgb.Booster(model_file='model/light_20230816_01.txt')
df_feature_importance = pd.DataFrame({
    'importance':gbm.feature_importance(),
    'name':gbm.feature_name()
})
df_feature_importance.sort_values(by='importance',ascending=False, inplace = True,ignore_index=True)
plt.barh(df_feature_importance['name'], df_feature_importance['importance'])

在这里插入图片描述

由上图可知,小时特征、x5、x4、key3、x7这5个特征占比比较重要,可用于后续的特征构建。

7 本地最好的结果

7.1 得到optuna调整的最好得分

df = study.trials_dataframe()
df.to_csv('optuna/optuna_light_20230816_01.csv',index = None)
study.best_score

在测试集上的f1得分为:0.8087764989879106。

其中的csv文件包含训练的参数,这里就不展示了。

7.2 线上提交结果

线上结果为:0.68696。

总结

第一步是baseline的跑通,采用了决策树的算法,第二步是采用了lightgbm+optuna进行粗略的模型参数调整。最终只比baseline的模型提高了一点点的分数,因此下一步注重对特征的构建。

7 本地最好的结果

7.1 得到optuna调整的最好得分

df = study.trials_dataframe()
df.to_csv('optuna/optuna_light_20230816_01.csv',index = None)
study.best_score

在测试集上的f1得分为:0.8087764989879106。

其中的csv文件包含训练的参数,这里就不展示了。

7.2 线上提交结果

线上结果为:0.68696。

总结

第一步是baseline的跑通,采用了决策树的算法,第二步是采用了lightgbm+optuna进行粗略的模型参数调整。最终只比baseline的模型提高了一点点的分数,因此下一步注重对特征的构建。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值