Kaggle-练习赛-Titanic -机器学习 - 决策树 和 随机森林
目录
在 上一节中,使用经典的逻辑回归模型完成了Titanic的预测。https://blog.csdn.net/qq_38770806/article/details/151714012?fromshare=blogdetail&sharetype=blogdetail&sharerId=151714012&sharerefer=PC&sharesource=qq_38770806&sharefrom=from_link
在本文中,将使用另外经典的机器学习算法-决策树和随机森林解决Titanic问题。
决策树的基本概念
决策树是一种监督学习算法,用于分类和回归任务。它通过递归地划分数据集,构建树状结构,每个内部节点表示一个特征判断,每个分支代表判断结果,而叶节点代表最终的预测结果。
决策树的构建方法
ID3算法
基于信息增益选择特征,信息增益表示特征对数据集纯度的提升程度。计算公式为:
信息增益 = 原始信息熵 - 按特征划分后的加权信息熵
C4.5算法
改进ID3,引入信息增益比(信息增益 / 特征固有值)避免偏向取值多的特征。
CART算法
支持分类和回归任务,分类树使用基尼系数(Gini Index)作为分裂标准,回归树使用均方误差(MSE)。基尼系数公式为:
Gini = 1 - Σ(p_i)^2
决策树的剪枝
预剪枝
在建树过程中提前停止分裂,通过设定最大深度、最小样本数等参数防止过拟合。
后剪枝
先构建完整树,再自底向上剪枝,通过验证集评估剪枝前后的性能差异。
决策树的优缺点
优点
- 可解释性强,结果直观。
- 无需数据标准化,支持混合类型数据。
缺点
- 容易过拟合,需依赖剪枝。
- 对样本敏感,微小变化可能导致树结构剧变。
随机森林的基本概念
随机森林(Random Forest)是一种集成学习方法,通过构建多个决策树并结合它们的预测结果来提高模型的准确性和鲁棒性。它属于Bagging类算法,适用于分类和回归任务。
随机森林的工作原理
- Bootstrap抽样:从原始数据集中有放回地随机抽取样本,形成多个子数据集。每个子数据集用于训练一棵决策树。
- 随机特征选择:在每棵决策树的节点分裂时,随机选择一部分特征(而非全部特征)进行最佳分裂。通常选择特征数为总特征数的平方根。
- 投票或平均:对于分类任务,每棵树的预测结果通过投票决定最终结果;对于回归任务,取所有树的预测平均值。
随机森林的优势
- 抗过拟合:通过多棵树的集成和随机特征选择,降低过拟合风险。
- 处理高维数据:对特征数量不敏感,适合高维数据集。
- 鲁棒性强:对缺失值和噪声数据有较好的容忍度。
- 可解释性:提供特征重要性评估,帮助理解数据。
随机森林的缺点
- 训练时间较长:树的数量较多时,训练和预测速度较慢。
- 内存消耗大:需要存储多棵树的模型参数。
- 黑盒性:相比单棵决策树,可解释性稍弱。
关键参数调优
- n_estimators:森林中树的数量,通常越大越好,但需平衡计算成本。
- max_depth:单棵树的最大深度,控制模型复杂度。
- min_samples_split:节点分裂所需的最小样本数,防止过拟合。
- max_features:节点分裂时考虑的最大特征数,常用
sqrt或log2。
以下是在kaggle notebook上的代码。
决策树:
首先,使用决策树对模型进行训练,为了方便运行,先将上一节完成的一些代码打包。
加载库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import SelectFromModel
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, roc_auc_score
from scipy.stats import randint, uniform
定义函数1-数据加载与合并:
def load_and_merge_data(train_path, test_path):
"""加载训练集和测试集并合并"""
train_df = pd.read_csv(train_path)
test_df = pd.read_csv(test_path)
test_df["Survived"] = np.nan # 测试集标签占位
combined_df = pd.concat([train_df, test_df], ignore_index=True)
print(f"✅ 数据加载完成,合并后数据形状:{combined_df.shape}")
return combined_df, test_df
定义函数2-缺失值处理:
def handle_missing_values(combined_df):
# 提取头衔用于Age填充
combined_df["Title"] = combined_df["Name"].str.extract(r"([A-Za-z]+)\.")
rare_titles = ["Dona", "Lady", "Countess", "Capt", "Col", "Don", "Dr", "Major", "Rev", "Sir", "Jonkheer"]
combined_df["Title"] = combined_df["Title"].replace(rare_titles, "Rare")
combined_df["Title"] = combined_df["Title"].replace({"Mlle": "Miss", "Ms": "Miss", "Mme": "Mrs"})
# 按头衔填充Age(无inplace,直接赋值)
age_median = combined_df.groupby("Title")["Age"].median()
for title in combined_df["Title"].unique():
mask = (combined_df["Age"].isnull()) & (combined_df["Title"] == title)
combined_df.loc[mask, "Age"] = age_median[title]
# 按Pclass填充Fare(修复警告:去掉inplace=True,用赋值替代)
fare_median = combined_df.groupby("Pclass")["Fare"].median()
combined_df["Fare"] = combined_df["Fare"].fillna(fare_median[3]) # 关键修改:删除inplace=True
# 填充Embarked(修复警告:去掉inplace=True,用赋值替代)
embarked_mode = combined_df["Embarked"].mode()[0]
combined_df["Embarked"] = combined_df["Embarked"].fillna(embarked_mode) # 关键修改:删除inplace=True
print(f"✅ 缺失值处理完成,剩余缺失值数量:{combined_df.isnull().sum().sum()}")
return combined_df
定义函数3-特征衍生与数据清洗:
def create_features_and_clean(combined_df):
"""衍生家庭规模、是否独自旅行特征,删除无用列"""
# 衍生新特征
combined_df["FamilySize"] = combined_df["SibSp"] + combined_df["Parch"] + 1
combined_df["IsAlone"] = 1
combined_df.loc[combined_df["FamilySize"] > 1, "IsAlone"] = 0
# 删除无用特征
drop_cols = ["Name", "Ticket", "Cabin", "Title"]
combined_df = combined_df.drop(columns=drop_cols)
print(f"✅ 特征衍生完成,当前特征列表:{combined_df.columns.tolist()}")
return combined_df
定义函数4-训练集与测试集拆分:
def split_train_test(combined_df):
"""拆分训练集(含标签)和测试集(无标签)"""
train_processed = combined_df[combined_df["Survived"].notna()].copy()
test_processed = combined_df[combined_df["Survived"].isna()].copy()
X = train_processed.drop(columns=["Survived", "PassengerId"])
y = train_processed["Survived"].astype(int)
test_X = test_processed.drop(columns=["Survived", "PassengerId"])
test_passenger_ids = test_processed["PassengerId"]
print(f"✅ 数据拆分完成:训练集特征形状{X.shape},测试集特征形状{test_X.shape}")
return X, y, test_X, test_passenger_ids
定义函数5-预处理管道构建
def build_preprocessor(numeric_features, categorical_features):
"""构建特征预处理管道(数值特征直接传递,分类特征独热编码)"""
preprocessor = ColumnTransformer(
transformers=[
("num", "passthrough", numeric_features),
("cat", OneHotEncoder(drop="first", sparse=False), categorical_features)
]
)
print(f"✅ 预处理管道构建完成:数值特征{numeric_features},分类特征{categorical_features}")
return preprocessor
定义函数6-训练集与测试集拆分:
def build_base_dt_pipeline(preprocessor):
"""构建可调优的基础决策树模型管道(超参数留空,后续GridSearchCV填充)"""
base_pipeline = Pipeline(steps=[
("preprocessor", preprocessor),
("classifier", DecisionTreeClassifier(random_state=42)) # 超参数不固定,后续调优
])
print("✅ 基础决策树模型管道构建完成(超参数待调优)")
return base_pipeline
定义函数7 - GridSearchCV——决策树超参数调优:
def tune_model_with_gridsearch(base_pipeline, X_train, y_train):
# 超参数网格(控制决策树复杂度,避免过拟合)
param_grid = {
"classifier__max_depth": [5, 8, 10, 12, 15],
"classifier__min_samples_split": [5, 8, 10, 12],
"classifier__min_samples_leaf": [1, 2, 3, 4],
"classifier__max_features": ["sqrt", "log2", None]
}
# 初始化GridSearchCV(5折交叉验证,多核加速)
grid_search = GridSearchCV(
estimator=base_pipeline,
param_grid=param_grid,
cv=5,
scoring="accuracy",
n_jobs=-1,
verbose=1
)
# 执行超参数搜索
print("\n=== 开始超参数调优(GridSearchCV)===")
grid_search.fit(X_train, y_train)
# 提取最优结果
best_model = grid_search.best_estimator_
best_params = grid_search.best_params_
best_cv_score = grid_search.best_score_
# 打印调优结果
print(f"\n✅ 超参数调优完成!")
print(f"最优超参数组合:{best_params}")
print(f"最优模型交叉验证平均准确率:{best_cv_score:.4f}")
return best_model, best_params, best_cv_score
定义函数8-模型训练与评估函数:
def train_and_evaluate_model(model, X, y, test_size=0.2):
# 拆分训练集与验证集(保持标签分布一致)
X_train, X_val, y_train, y_val = train_test_split(
X, y, test_size=test_size, random_state=42, stratify=y
)
# 超参数调优
best_model, best_params, best_cv_score = tune_model_with_gridsearch(model, X_train, y_train)
# 验证集评估
y_val_pred = best_model.predict(X_val)
y_val_proba = best_model.predict_proba(X_val)[:, 1]
val_accuracy = accuracy_score(y_val, y_val_pred)
val_roc_auc = roc_auc_score(y_val, y_val_proba)
# 打印验证集结果
print("\n=== 最优模型验证集评估结果 ===")
print(f"验证集准确率:{val_accuracy:.4f}")
print(f"验证集ROC-AUC:{val_roc_auc:.4f}")
print("\n混淆矩阵:")
print(confusion_matrix(y_val, y_val_pred))
print("\n分类报告:")
print(classification_report(y_val, y_val_pred))
# 全量数据5折交叉验证(验证泛化能力)
full_cv_scores = cross_val_score(best_model, X, y, cv=5, scoring="accuracy")
print(f"\n=== 最优模型全量数据5折交叉验证 ===")
print(f"平均准确率:{full_cv_scores.mean():.4f}(标准差:{full_cv_scores.std():.4f})")
return best_model, val_accuracy, full_cv_scores.mean()
定义函数9-决策树特征可视化:
def plot_feature_importance(model, numeric_features, categorical_features):
# 获取预处理后的特征名称
preprocessor = model.named_steps["preprocessor"]
cat_feature_names = preprocessor.named_transformers_["cat"].get_feature_names_out(categorical_features)
all_feature_names = numeric_features + cat_feature_names.tolist()
# 计算特征重要性
feature_importance = pd.DataFrame({
"Feature": all_feature_names,
"Importance": model.named_steps["classifier"].feature_importances_
}).sort_values(by="Importance", ascending=False)
# 打印并可视化
print("\n=== 最优决策树Top 10特征重要性 ===")
print(feature_importance.head(10))
plt.figure(figsize=(10, 6))
sns.barplot(x="Importance", y="Feature", data=feature_importance.head(10))
plt.title("Tuned Decision Tree - Top 10 Feature Importance")
plt.tight_layout()
plt.show()
定义函数10-提交文件生成 :
def generate_submission(model, X_full, y_full, test_X, test_passenger_ids, output_path):
# 用全量数据训练最优模型
print(f"\n=== 用全量训练数据训练最优模型 ===")
model.fit(X_full, y_full)
# 测试集预测
test_y_pred = model.predict(test_X)
# 生成提交文件
submission = pd.DataFrame({
"PassengerId": test_passenger_ids.astype(int),
"Survived": test_y_pred.astype(int)
})
submission.to_csv(output_path, index=False)
print(f"✅ 提交文件已生成!路径:{output_path}")
print("提交文件前5行:")
print(submission.head())
定义 main()函数:
def main():
# 配置参数(Kaggle路径)
TRAIN_PATH = "/kaggle/input/titanic/train.csv"
TEST_PATH = "/kaggle/input/titanic/test.csv"
OUTPUT_PATH = "titanic_tuned_dt_submission.csv"
# 定义特征类型
NUMERIC_FEATURES = ["Age", "Fare", "SibSp", "Parch", "FamilySize"]
CATEGORICAL_FEATURES = ["Sex", "Pclass", "Embarked", "IsAlone"]
# 数据预处理流程
combined_df, test_df = load_and_merge_data(TRAIN_PATH, TEST_PATH)
combined_df = handle_missing_values(combined_df)
combined_df = create_features_and_clean(combined_df)
X_full, y_full, test_X, test_passenger_ids = split_train_test(combined_df)
# 模型构建与超参数调优流程
preprocessor = build_preprocessor(NUMERIC_FEATURES, CATEGORICAL_FEATURES)
base_dt_pipeline = build_base_dt_pipeline(preprocessor)
tuned_dt_model, val_accuracy, full_cv_score = train_and_evaluate_model(base_dt_pipeline, X_full, y_full)
# 特征重要性可视化
plot_feature_importance(tuned_dt_model, NUMERIC_FEATURES, CATEGORICAL_FEATURES)
# 生成提交文件
generate_submission(tuned_dt_model, X_full, y_full, test_X, test_passenger_ids, OUTPUT_PATH)
代码运行:
if __name__ == "__main__":
main()
运行结果:

随机森林:
随机森林在决策树的代码上进行部分修改:
# 决策树原函数
'''def build_base_dt_pipeline(preprocessor):
base_pipeline = Pipeline(steps=[
("preprocessor", preprocessor),
("classifier", DecisionTreeClassifier(random_state=42)) # 决策树
])
return base_pipeline'''
# 改为随机森林
def build_base_rf_pipeline(preprocessor):
base_pipeline = Pipeline(steps=[
("preprocessor", preprocessor),
("classifier", RandomForestClassifier(random_state=42)) # 替换为随机森林
])
print("✅ 基础随机森林模型管道构建完成(超参数待调优)")
return base_pipeline
'''# 决策树原超参数网格
param_grid = {
"classifier__max_depth": [5, 8, 10, 12, 15],
"classifier__min_samples_split": [5, 8, 10, 12],
"classifier__min_samples_leaf": [1, 2, 3, 4],
"classifier__max_features": ["sqrt", "log2", None]
}'''
# 改为随机森林超参数网格(新增n_estimators等关键参数)
param_grid = {
"classifier__n_estimators": [100, 200, 300, 400], # 随机森林特有:树的数量(核心参数)
"classifier__max_depth": [None, 10, 20, 30], # 单棵树深度(None表示不限制)
"classifier__min_samples_split": [2, 5, 10],
"classifier__min_samples_leaf": [1, 2, 4],
"classifier__max_features": ["sqrt", "log2"], # 每棵树使用的特征比例
"classifier__bootstrap": [True, False] # 随机森林特有:是否bootstrap抽样
}
'''# 决策树原预处理(数值特征直接传递)
preprocessor = ColumnTransformer(
transformers=[
("num", "passthrough", numeric_features), # 无标准化
("cat", OneHotEncoder(drop="first", sparse=False), categorical_features)
])
# 随机森林无需标准化,但可增加特征选择步骤(推荐)'''
def build_preprocessor(numeric_features, categorical_features):
preprocessor = ColumnTransformer(
transformers=[
("num", "passthrough", numeric_features),
("cat", OneHotEncoder(drop="first", sparse=False), categorical_features)
])
# 新增特征选择器(随机森林对特征冗余更稳健,但筛选后更高效)
from sklearn.feature_selection import SelectFromModel
full_preprocessor = Pipeline(steps=[
("col_transform", preprocessor),
("feature_selector", SelectFromModel(RandomForestClassifier(n_estimators=100), threshold="mean"))
])
return full_preprocessor
决策树和随机森林的结果并未如所想一样比逻辑回归高出很多。
下一篇或许我们可以尝试一下集成学习。
更多推荐



所有评论(0)