Kaggle竞赛系列_SpaceshipTitanic比赛

【前言】

Spaceship Titanic比赛,类似Titanic比赛,只是增加了更多的属性以及更大的数据量,仍是一个二分类问题。我使用的是基于LightGBM,结合交叉验证进行预测的单模方案,最终得分为0.79,有很多地方还可以改进,如果您愿意分享您宝贵的经验和知识,我将不胜感激。

【比赛简介】

Spaceship Titanic比赛是一个在Kaggle上举办的机器学习挑战,参赛者的任务是预测Spaceship Titanic在与时空异常碰撞时,哪些乘客被传送到了另一个维度。这个比赛提供了从飞船损坏的计算机系统中恢复的一组个人记录,参赛者需要使用这些数据来进行预测。

【正文】

(一)数据获取

参考之前的Titanic比赛的数据获取操作 Kaggle_Titanic比赛

(二)数据分析

1. 初步分析

导入必要的包

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

读取文件

train = pd.read_csv('/kaggle/input/spaceship-titanic/train.csv')
test = pd.read_csv('/kaggle/input/spaceship-titanic/test.csv')

对照官方的字段说明

image-20240125094535642

train.csv - 用于训练数据的大约三分之二(约8700名)乘客的个人记录。
PassengerId - 每位乘客的唯一标识。每个标识的格式为gggg_pp,其中gggg表示乘客所在的团体,pp是他们在团体中的编号。一个团体中的人通常是家庭成员,但并不总是如此。
HomePlanet - 乘客出发的星球,通常是他们的永久居住星球。
CryoSleep - 表示乘客是否选择在航程期间被置于悬浮动状态。处于冷冻睡眠状态的乘客被限制在他们的舱房内。
Cabin - 乘客所住的舱房号码。格式为deck/num/side,其中side可以是P(港口)或S(舷侧)。
Destination - 乘客将要下船的星球。
Age - 乘客的年龄。
VIP - 乘客是否支付额外费用获得特殊的VIP服务。
RoomService, FoodCourt, ShoppingMall, Spa, VRDeck - 乘客在太空巨轮泰坦尼克号的许多豪华设施上的费用金额。
Name - 乘客的名字和姓氏。
Transported - 乘客是否被传送到另一个维度。这是目标,也就是您要预测的列。
test.csv - 剩余三分之一(约4300名)乘客的个人记录,用作测试数据。您的任务是预测这一集合中乘客的Transported值。
sample_submission.csv - 一个以正确格式的提交文件。
PassengerId - 测试集中每位乘客的标识。
Transported - 目标。对于每位乘客,预测True或False。

查看前五行

train.head(5)

image-20240125094152130

查看形状(数据量、属性个数)

(train.shape, test.shape)
# ((8693, 14), (4277, 13))

2. 数据质量分析
(1)判断唯一索引是否有重复值
train['Name'].nunique() == train.shape[0],test['Name'].nunique() == test.shape[0]

Out:

(True, True)
(2)缺失值检验
train.isnull().sum()

image-20240125094257510

test.isnull().sum()

image-20240125094323210

根据官方给出的字段解释,其中需要我们填充缺失值的属性只有Age,其他诸如RoomService、FoodCourt等属性并不是缺失值,而是本身的数值就为0,而Name属性的缺失值对我们判断并没有影响,可以忽略。

(3)异常值检验
cols = train.columns.tolist()
cols.remove('Name')
cols.remove('PassengerId')
cols.remove('Transported')

循环打印各个属性的直方图

sns.set()
for col in cols:
    statistic = train[col].describe()
    print(statistic)
    plt.figure(figsize=(6,6))
    sns.histplot(train[col], kde=True)
    plt.title(f'Histogram of {col}')
    plt.show()

image-20240125120917673

经过对直方图的观察后,初步判断没有将对实验产生影响的异常值

(4)规律一致性检测

判断test表与train表是否出自同一分布

train_count = train.shape[0]
test_count = test.shape[0]
features = cols
i = 1
plt.figure(figsize=(15, 15))
for feature in features:
    plt.subplot(4, 3, i)
    (train[feature].value_counts().sort_index()/train_count).plot()
    (test[feature].value_counts().sort_index()/test_count).plot()
    plt.legend(['train', 'test'])
    plt.xlabel(feature)
    plt.ylabel('radio')
    plt.show
    i+=1

image-20240125121302594

  • 发现在Cabin属性上,train表与test表的分布存在较大差异,但每个值的分布占比又特别小,数值范围特别大,并且峰值出现的规律也很相近(应该是在某个船舱住了较多人)——>也许要对此处进行单独分析(尚未做/(ㄒoㄒ)/~~)
  • 其次,RoomService、FoodCourt等其他属性的0值占比较大,其他数值也基本上也只出现一次,各自占比较小(接近0)
  • 最后,在剩余的属性上train表与test表的分布基本一致

(三)数据预处理

1. 缺失值填充

仅填充Age属性

train['Age'] = train['Age'].fillna(train['Age'].mean())
test['Age'] = test['Age'].fillna(test['Age'].mean())

2. 将object数据类型进行字典编码
train.info()

image-20240125122347162

发现’HomePlanet’, ‘CryoSleep’, ‘Cabin’, ‘Destination’,‘VIP’,'Name’字段为object类型,因为我们不使用Name作为判断的条件,因此将除了Name以外的字段进行字典编码。

为了后续建模的数据格式一致,也将Transported字段从True、False转为1、0。

def change_object_cols(se):
    value = se.unique().tolist()
    value.sort()
    return se.map(pd.Series(range(len(value)), index=value)).values
change_cols = ['HomePlanet', 'CryoSleep', 
               'Cabin', 'Destination','VIP', 'Transported']
for col in change_cols:
    train[col] = change_object_cols(train[col])
    test[col] = change_object_cols(test[col])

再次查看数据类型

train.info()

image-20240125122553799

归纳总结:数据分析与预处理常用方法:

思维导图


(四)保存数据

train.to_csv('/kaggle/working/train_pre.csv', index=False)
test.to_csv('/kaggle/working/test_pre.csv', index=False)

(五)建模过程

1. 导入必要的包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import lightgbm as lgb
from hyperopt import hp,fmin,tpe
from sklearn.model_selection import KFold
from numpy.random import RandomState
import time
from sklearn.metrics import accuracy_score

2. 读取已经处理好的数据

因为kaggle平台一次性最多只能运行12h,而且中途会检测你人是否还在,因此最好将数据预处理、数据清晰与建模分析的过程分开来,防止因中断内核导致的内存释放。

train = pd.read_csv('/kaggle/input/space-titanic/train_pre.csv')
test = pd.read_csv('/kaggle/input/space-titanic/test_pre.csv')
train.info()

image-20240125122936029

可以看到经过预处理后,原先的object字段已经全部转换为字典编码,可以被模型读入。


3. 特征工程

通用组合特征,统计不同离散特征在不同取值水平下、不同连续特征取值之和创建的特征,并根据PassengerId进行分组求和。通过该方法创建的数据集,不仅能够尽可能从更多维度表示每个Passenger的情况,同时也能够顺利和训练集/测试集完成拼接,从而带入模型进行建模。

(1)选择离散、连续字段
numeric_cols = ['Age', 'Cabin']
category_cols = []
for col in train.columns:
    if col not in numeric_cols:
        category_cols.append(col)
        
category_cols.remove('PassengerId')
category_cols.remove('Name')
category_cols.remove('Transported')

因为不使用PassengerId、Name进行建模,因此我最终选定的离散字段是’HomePlanet’, ‘CryoSleep’, ‘Destination’, ‘VIP’, ‘Ticket’, ‘RoomService’,‘FoodCourt’,‘ShoppingMall’,‘Spa’,‘VRDeck’,连续字段是’Age’,‘Cabin’。

(2)特征拼接
features = {}
people_all = pd.concat([train['PassengerId'], test['PassengerId']]).values.tolist()
for people in people_all:
    features[people] = {}
    
columns = train.columns.tolist()
idx = columns.index('PassengerId')

category_cols_index = [columns.index(col) for col in category_cols]
numeric_cols_index = [columns.index(col) for col in numeric_cols]

s = time.time()
num = 0
for i in range(train.shape[0]):
    va = train.loc[i].values
    people = va[idx]
    for cate_ind in category_cols_index:
        for num_ind in numeric_cols_index:
            col_name = '&'.join([columns[cate_ind], 
                                 str(va[cate_ind]), 
                                 columns[num_ind]])
            features[people][col_name] = features[people].get(col_name, 0) + va[num_ind]
            num += 1
            if num % 100 == 0:
                print(time.time() - s, 's')
(3)格式转换
df = pd.DataFrame(features).T.reset_index()
cols = df.columns.tolist()
df.columns = ['PassengerId'] + cols[1:]
df.isnull().sum()

image-20240125123306881

我们发现有很多属性是有很多缺失值的,需要在后续进行处理

(4)与train/test表合并
train_dict = pd.merge(train, df, how='left', on='PassengerId')
test_dict = pd.merge(test, df, how='left', on='PassengerId')

4. 特征筛选

采用的是Wrapper特征筛选方法,使用的机器学习模型是LightGBM模型,更详细内容请参考集成学习之Boosting方法_LightGBM。选择重要性最大的前200个特征进行建模。并输出每个训练验证集上的损失曲线。

def feature_select_wrapper(train, test):
    print('start select feature')
    label = 'Transported'
    features = train.columns.tolist()
    features.remove('PassengerId')
    features.remove('Name')
    features.remove('Transported')
    
    params_initial = {
        'num_leaves': 15,
        'learning_rate': 0.1,
        'boosting': 'gbdt',
        'min_child_samples': 20,
        'bagging_seed': 2025,
        'bagging_fraction': 0.7,
        'bagging_freq': 1,
        'feature_fraction': 0.7,
        'max_depth': -1,
        'metric': 'binary_logloss',
        'objective': 'binary'
    }
    ESR = 20
    NBR = 1000
    VBE = 50
    
    kf = KFold(n_splits=5, random_state=2024, shuffle=True)
    fse = pd.Series(0, index=features)
    all_eval_result = []
    for train_index, eval_index in kf.split(train[features], train[label]):
        train_part = lgb.Dataset(train[features].loc[train_index], train[label].loc[train_index])
        eval_part = lgb.Dataset(train[features].loc[eval_index], train[label].loc[eval_index])
        eval_result = {}
        bst = lgb.train(params_initial,
                       train_part,
                       num_boost_round =NBR,
                       valid_sets = [train_part, eval_part],
                       valid_names= ['train', 'valid'],
                       early_stopping_rounds=ESR,
                       verbose_eval=VBE,
                       evals_result=eval_result)
        all_eval_result.append(eval_result)
        fse += pd.Series(bst.feature_importance(), features)
        
    for eval_result in all_eval_result:
        plt.figure(figsize=(6, 6))
        ax = lgb.plot_metric(eval_result, metric='binary_logloss')
        plt.show()
    feature_select = ['PassengerId'] + fse.sort_values(ascending=False).index.tolist()[: 200]
    print('done')
    return train[feature_select + ['Transported']], test[feature_select]
train_LGBM, test_LGBM = feature_select_wrapper(train_dict, test_dict)

5. 建模过程

使用LightGBM单模方案,结合交叉验证进行模型预测。

与之前Titanic比赛的随机森林预测方案Titanic比赛_随机森林预测类似,都需要在训练模型的过程同时进行超参数的搜索调优。不同的是在超参调优的过程中,使用hyperopt进行超参数搜索而不是之前的网格搜索,考虑使用LightGBM的原生算法库进行建模,并将整个算法建模流程封装在若干个函数内执行。

(1)定义参数回调函数

对于lgb模型来说,并不是所有的超参数都需要进行搜索,为了防止多次实例化模型过程中部分超参数被设置成默认参数,此处我们首先需要创建一个参数回调函数,用于在后续多次实例化模型过程中反复申明这部分参数的固定取值:

def params_append(params):
    params['feature_pre_filter'] = False
    params['objective'] = 'binary'
    params['metric'] = 'binary_logloss'
    params['bagging_seed'] = 2025
    return params
(2)模型训练与参数优化函数

Hyperopt是一个用于优化机器学习模型参数的Python库。提供了一种有效的方式来搜索超参数空间,以找到最佳的参数组合。主要使用以下两种技术:

  1. 贝叶斯优化:这是一种基于概率模型的方法,它假设隐藏在数据中有一个未知的函数,该函数定义了参数配置和性能之间的关系。Hyperopt 使用贝叶斯方法来预测哪些参数配置可能导致更好的性能,这样可以在尝试较少的配置的情况下找到最优配置。
  2. 序列模型优化(SMBO):这是一种在每一步都尽量提高效率的策略。它在每一次迭代中都会根据已经观察到的结果来选择下一个要评估的参数集。这种方法比简单的随机搜索或网格搜索更有效,因为它利用了之前试验的结果。

Hyperopt主要包括三个部分:

  • 搜索空间:定义了超参数可能的值范围。在Hyperopt中,可以定义一个灵活的搜索空间,包括连续的、离散的或条件的参数。
  • 目标函数(Objective):这是一个评估模型性能的函数,取决于超参数的值。Hyperopt的目标是找到最小化或最大化这个函数的超参数值。
  • 优化算法Hyperopt提供了多种算法,如随机搜索、TPE(Tree of Parzen Estimators)等,用于在定义的搜索空间中找到最优的参数组合。
def param_hyperopt(train):
    label = 'Transported'
    features = train.columns.tolist()
    features.remove('Transported')
    features.remove('PassengerId')
    
    train_data = lgb.Dataset(train[features], train[label])
    
    def hyperopt_objective(param):
        params = params_append(param)
        print(params)
        
        # 借助lgb的cv过程,输出某一组超参数下损失值的最小值
        res = lgb.cv(params, 
                     train_data, 
                     500,
                     nfold=5,
                     stratified=False,
                     shuffle=True,
                     metrics='binary_logloss',
                     early_stopping_rounds=30,
                     verbose_eval=False,
                     show_stdv=False,
                     )
        return min(res['binary_logloss-mean'])
    
    params_space = {
        'learning_rate': hp.uniform('learning_rate', 1e-2, 5e-1),
        'bagging_fraction': hp.uniform('bagging_fraction', 0.5, 1),
        'feature_fraction': hp.uniform('feature_fraction', 0.5, 1),
        'num_leaves': hp.choice('num_leaves', list(range(5, 20, 1))),
        'bagging_freq': hp.randint('bagging_freq', 1, 5),
        'min_child_samples': hp.choice('min_child_samples', list(range(1, 10, 1)))
    }
    
    
    params_best = fmin(
        hyperopt_objective,
        space=params_space,
        algo=tpe.suggest,
        max_evals=15,
        rstate=np.random.default_rng(2025)
    )
    
    return params_best
best_clf = param_hyperopt(train_LGBM)
(3)查看最佳参数组合
best_clf

image-20240125125228019

(4)输出结果

结合交叉验证进行模型预测

图片来源:【合集】Kaggle顶级赛事Top 1%方案精讲与实践|数据代码课件齐全|机器学习竞赛上分利器_哔哩哔哩_bilibili

image-20240125125349721

def train_predict(train, test, params):
    label = 'Transported'
    features = train.columns.tolist()
    features.remove('Transported')
    features.remove('PassengerId')
    
    params = params_append(params)
    ESR = 30
    NBR = 10000
    VBE = 50
    
    prediction_test = 0
    cv_score = []
    prediction_train = pd.Series()
    
    kf = KFold(n_splits=5, random_state=2025, shuffle=True)
    for train_index, eval_index in kf.split(train[features], train[label]):
        train_part = lgb.Dataset(train[features].loc[train_index],
                                train[label].loc[train_index])
        eval_part = lgb.Dataset(train[features].loc[eval_index],
                               train[label].loc[eval_index])
        bst = lgb.train(params,
                       train_part,
                       num_boost_round=NBR,
                       valid_sets=[train_part, eval_part],
                        valid_names=['train', 'valid'],
                        early_stopping_rounds=ESR, 
                        verbose_eval=VBE)
        prediction_test += bst.predict(test[features])
        prediction_train = pd.concat([pd.Series(bst.predict(train[features].loc[eval_index],
                                                                        index=eval_index))])
        eval_pre = bst.predict(train[features].loc[eval_index])
        true_labels = train[label].loc[eval_index]
        predicted_labels = np.where(eval_pre > 0.6, 1, 0)
        score = accuracy_score(true_labels, predicted_labels)
        cv_score.append(score)
        
        
    predictions = prediction_test / 5
    threshold = 0.6
    # 应用阈值得到类别标签
    labels = np.where(predictions > threshold, 'True', 'False')
    print(cv_score, sum(cv_score)/ 5)
    test['Transported'] = labels
    test[['PassengerId', 'Transported']].to_csv('/kaggle/working/submission.csv', index=False)
    return 
train_predict(train_LGBM, test_LGBM, best_clf)
(5)提交答案
  • 查看该参数组合在test表上的输出
test_result = pd.read_csv('/kaggle/working/submission.csv')
test_result['Transported']

image-20240125130056158

  • 按照提交格式保存输出文件

提交文件由PassengerId与Transported字段组成

image-20240125130130140

  • 按照提交格式要求保存输出文件

在比赛页面上提交输出文件

image-20240125130254358

  • 查看自己的分数以及排名

image-20240125130325129

至此,基于LightGBM,结合交叉验证进行预测的单模方案基本完成。有什么问题与见解欢迎在评论区留言或私信我o( ̄▽ ̄)ブ

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值