预测提交分数,可参考
# =============================================================================
# 泰坦尼克号生存预测 - 逻辑回归算法
# 作者:[FAREWELL00075]
# 日期:[2025.6.29]
# 功能:使用逻辑回归算法预测泰坦尼克号乘客生存情况
# =============================================================================
代码整体分为7个模块:
模块1:导入必要的库和设置
- 导入numpy、pandas、sklearn等库
- 设置matplotlib中文显示
# =============================================================================
# 模块1:导入必要的库和设置
# =============================================================================
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
# 设置matplotlib中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
模块2:文件路径配置
- 定义训练集、测试集和提交文件的路径
# =============================================================================
# 模块2:文件路径配置
# =============================================================================
TRAIN_PATH = './titanic/train.csv'
TEST_PATH = './titanic/test.csv'
SUBMISSION_PATH = './submission.csv'
模块3:数据预处理函数
包含5个子模块:
- 3.1 缺失值处理 - 填充年龄、票价、登船港口的缺失值
- 3.2 新特征提取 - 从姓名提取称呼、创建家庭特征、乘客类型特征
- 3.3 类别特征编码 - 性别编码、独热编码
- 3.4 特征选择 - 选择最终使用的特征
- 3.5 特征标准化 - 使用StandardScaler标准化特征
数据预处理就像是给机器准备"食物"的过程,如果数据乱七八糟,机器就学不好。我们的设计很简单:首先确保训练数据和测试数据用同样的方法处理,这样才公平;然后从原始数据中找出有用的信息,比如从姓名中提取称呼;接着把数据整理成机器能理解的格式;最后处理一下缺失的数据,让数据更完整。这样处理后的数据就像是一桌整齐的饭菜,机器吃起来更容易消化。
设计原理:
- 中位数填充:适用于数值型特征(年龄、票价),中位数对异常值不敏感,能保持数据的分布特征
- 众数填充:适用于类别型特征(登船港口),选择最常见的类别作为默认值
- 训练集统计:只在训练集上计算统计量,测试集使用训练集的统计值,避免数据泄露
各种特征设计的目的:
称呼特征(Title):
目的:反映乘客的社会地位和身份,不同称呼的乘客生存概率差异很大。Mr(先生)代表成年男性,生存率较低;Miss/Mrs(女士)代表女性,生存率较高;Master(少爷)代表未成年男性,生存率中等;Dr/Rev等代表高社会地位,可能有特殊待遇。
家庭特征(FamilySize/IsAlone)
目的:家庭规模反映乘客是否有家人陪伴,影响救援优先级;独自旅行标识反映独自一人的乘客可能缺乏帮助,生存率较低。
乘客类型特征
目的:儿童标识反映儿童通常优先救援,生存率较高;母亲标识反映有孩子的成年女性,可能享受"女士和儿童优先"的待遇。
票价特征
目的:消除家庭规模对票价的影响,反映个人实际支付能力,间接反映社会地位。
客舱特征
目的:是否有客舱信息可能反映乘客的船票等级和记录完整性。
为什么要进行编码?为什么选择这种编码?
机器就像是一个只会数数的孩子,它看不懂"男"、"女"这样的文字,只能理解数字。所以我们要把文字转换成数字,这个过程就叫编码。对于性别这种简单的二选一问题,我们用0代表男性,1代表女性,这样机器就能理解了。但是对于登船港口这种有多种选择的情况,我们不能简单地用1、2、3来表示,因为机器会以为3比1"更好",但实际上它们只是不同的选择而已。所以我们用独热编码,比如有三个港口,就用三个数字来表示,每个港口对应一个数字,这样机器就不会搞混了。最后我们还要把所有的数字都调整到差不多的范围,就像把不同大小的苹果都切成一样大的块,这样机器学习起来更公平,不会因为某个数字特别大就特别重视它。
代码如下:
# =============================================================================
# 模块3:数据预处理函数
# 功能:对原始数据进行清洗、特征工程和标准化处理
# =============================================================================
def preprocess_data(df, train_stats=None, is_train=True):
"""
数据预处理函数,对训练集和测试集进行相同的特征工程处理
参数:
df - 原始数据框
train_stats - 训练集统计信息
is_train - 是否为训练数据
返回: 处理后的特征矩阵
"""
# 副本操作,避免修改原始数据
data = df.copy()
# 3.1 缺失值处理 -------------------------------------------------
if is_train:
# 训练集:计算参考值
age_median = data['Age'].median()#年龄中位数
fare_median = data['Fare'].median()#票价中位数
embarked_mode = data['Embarked'].mode()[0]#登船港口众数
else:
# 测试集:使用训练集的参考值
age_median = train_stats['median']['Age']
fare_median = train_stats['median']['Fare']
embarked_mode = train_stats['mode']['Embarked']
# 应用填充 - 不使用inplace避免警告
data['Age'] = data['Age'].fillna(age_median)#年龄填充中位数
data['Fare'] = data['Fare'].fillna(fare_median)#票价填充中位数
data['Embarked'] = data['Embarked'].fillna(embarked_mode)#登船港口填充众数
# 特殊处理:客舱特征 是否有客舱 有则为1 否则为0
data['HasCabin'] = data['Cabin'].apply(lambda x: 0 if pd.isna(x) else 1)
# 3.2 新特征提取 -------------------------------------------------
# 从姓名中提取称呼(使用原始字符串而不是转义序列)
data['Title'] = data['Name'].str.extract(r' ([A-Za-z]+)\.', expand=False)
title_mapping = {
'Mr': 1, 'Miss': 2, 'Mrs': 3, 'Master': 4,#先生
'Dr': 5, 'Rev': 5, 'Col': 5, 'Major': 5, #博士
'Mlle': 2, 'Countess': 3, 'Ms': 2, 'Lady': 3,#女士
'Jonkheer': 5, 'Sir': 5, 'Capt': 5, 'Don': 5,#男士
'Mme': 3, 'Dona': 3#女士
}
data['Title'] = data['Title'].map(title_mapping).fillna(5) # 未知称呼映射为5
# 家庭相关特征
data['FamilySize'] = data['SibSp'] + data['Parch'] + 1
data['IsAlone'] = np.where(data['FamilySize'] == 1, 1, 0)#是否独自一人 是则为1 否则为0
# 乘客类型特征
data['IsChild'] = np.where(data['Age'] < 16, 1, 0)#是否为儿童 是则为1 否则为0
data['IsMother'] = np.where(
(data['Sex'] == 'female') &
(data['Parch'] > 0) &
(data['Age'] > 18) &
(data['Title'] != 2), 1, 0)
# 票价相关特征
data['FarePerPerson'] = data['Fare'] / data['FamilySize']
# 3.3 类别特征编码 -------------------------------------------------
# 性别编码 用来表示乘客的性别 男性为0 女性为1
data['Sex'] = data['Sex'].map({'male': 0, 'female': 1})
# Embarked编码(独热) 用来表示乘客的登船港口
embarked_dummies = pd.get_dummies(data['Embarked'], prefix='Embarked')
# Pclass编码(独热) 用来表示乘客的船票等级 1等舱为1 2等舱为2 3等舱为3
pclass_dummies = pd.get_dummies(data['Pclass'], prefix='Pclass')
# 3.4 特征选择 -------------------------------------------------
# 原始特征 用来表示乘客的性别 年龄 票价 称呼 家庭大小 是否独自一人
# 是否为儿童 是否为母亲 是否有客舱信息 登船港口 船票等级
base_features = ['Sex', 'Age', 'FarePerPerson', 'Title',
'FamilySize', 'IsAlone', 'IsChild', 'IsMother', 'HasCabin']
# 组合所有特征 将所有特征组合成一个特征矩阵
all_features = pd.concat([
data[base_features],
embarked_dummies,
pclass_dummies
], axis=1)
# 填充可能的缺失值 将缺失值填充为0
all_features = all_features.fillna(0)
# 3.5 特征标准化 -------------------------------------------------
if is_train:
# 训练集:创建并拟合标准化器 标准化器是用来将特征缩放到0-1之间
scaler = StandardScaler()
scaled_features = scaler.fit_transform(all_features)
# 返回特征和统计信息 用来存储训练集的统计信息
return scaled_features, {
'mean': scaler.mean_,#均值
'scale': scaler.scale_,#标准差
'median': {
'Age': age_median,#年龄中位数
'Fare': fare_median#票价中位数
},
'mode': {
'Embarked': embarked_mode#众数
}
}
else:
# 测试集:使用训练集的标准化器
scaler = StandardScaler()
scaler.mean_ = train_stats['mean']#均值
scaler.scale_ = train_stats['scale']#标准差
scaled_features = scaler.transform(all_features)#将特征缩放到0-1之间
return scaled_features
模块4:逻辑回归模型训练
包含3个子模块:
- 4.1 添加偏置项 - 为特征矩阵添加常数项
- 4.2 初始化权重 - 权重初始化为0
- 4.3 训练循环 - 梯度下降算法实现
算法原理与实现思路
逻辑回归是一个经典的二分类算法,它的核心思想是通过sigmoid函数将线性组合转换为0-1之间的概率值,然后根据概率大小进行分类。我们的实现完全手写了梯度下降算法,没有使用任何现成的机器学习库,这样能够深入理解算法的本质。
数学基础与核心公式
逻辑回归的预测公式是P(y=1|x) = 1 / (1 + exp(-z)),其中z = w0 + w1x1 + w2x2 + ... + wnxn。这里的w0是偏置项,相当于线性方程中的常数项,让决策边界可以不在原点;x1到xn是输入特征;w1到wn是对应的权重,决定了每个特征对预测结果的重要程度。sigmoid函数的作用是将任意实数转换为0-1之间的概率值,当z很大时概率接近1,当z很小时概率接近0。
训练过程详解
我们的训练过程采用梯度下降算法,就像是在山上找最低点一样。首先初始化所有权重为0,意味着开始时所有特征的重要性相同。然后进入训练循环,每次迭代包含六个步骤:第一步是前向传播,计算线性组合z和预测概率;第二步是计算误差,即预测概率与真实标签的差距;第三步是计算梯度,梯度告诉我们权重应该往哪个方向调整,梯度越大说明这个权重对损失的影响越大;第四步是更新权重,沿着梯度的反方向调整权重,学习率控制每次调整的步长;第五步是检查收敛,如果权重变化很小说明已经接近最优解;第六步是打印训练进度,每500次迭代显示一次损失值。
损失函数与优化目标
我们使用交叉熵损失函数来衡量模型预测的准确性。交叉熵损失的计算公式是L = -1/n * (y * log(p) + (1-y) * log(1-p)),其中y是真实标签,p是预测概率。当预测完全正确时损失为0,当预测完全错误时损失很大。这个损失函数的特点是能够很好地衡量概率预测的准确性,特别适合二分类问题。
关键参数设置与调优
学习率是梯度下降算法中最重要的超参数,它控制每次权重更新的步长。学习率太大可能导致算法震荡甚至发散,学习率太小则收敛速度很慢。我们设置的学习率是0.1,这是一个相对较大的值,适合我们的数据规模。最大迭代次数设置为3000次,防止算法无限循环。收敛阈值设置为1e-4,当权重变化小于这个值时认为算法已经收敛。
代码如下:
# =============================================================================
# 模块4:逻辑回归模型训练
# 功能:使用梯度下降法训练逻辑回归模型
# =============================================================================
def train_logistic_regression(X, y, learning_rate=0.01, max_iter=3000, tol=1e-4):
"""
逻辑回归训练函数
预测公式:P(y=1|x) = 1 / (1 + exp(-z))
其中z = w0 + w1*x1 + w2*x2 + ... + wn*xn
w0是偏置项,x1,x2,...,xn是特征,w1,w2,...,wn是权重
"""
# 4.1 添加偏置项 偏置项是常数项,用于调整模型的截距
# 允许决策边界不经过原点,使模型能够更好地拟合数据
X_bias = np.c_[X, np.ones(X.shape[0])]#创建一个长度为X行数的全1数组 是numpy的列连接函数
# 将两个数组按列方向连接
# 4.2 初始化权重
weights = np.zeros(X_bias.shape[1])#初始化权重为0
# 4.3 训练循环
#z = w^T * x + b(线性组合)
#p = 1 / (1 + e^(-z))(sigmoid函数)
#L = -1/n * (y * log(p) + (1-y) * log(1-p))(交叉熵损失函数)
#w = w - learning_rate * gradient(梯度下降法)
#gradient = 1/n * (p - y) * x(梯度计算)
for i in range(max_iter):
# 计算预测概率
z = np.dot(X_bias, weights)
# 应用sigmoid函数得到概率
predictions = 1 / (1 + np.exp(-z))
# 计算梯度 梯度是损失函数对权重的偏导数 预测值与实际值的误差
error = predictions - y
gradient = np.dot(X_bias.T, error) / len(y) # 梯度是误差与特征的乘积的平均值
# 更新权重 使用梯度下降法更新权重
new_weights = weights - learning_rate * gradient
# 检查收敛性 如果新权重与旧权重之间的差异小于阈值,则认为模型已经收敛
if np.linalg.norm(new_weights - weights) < tol:
print(f"收敛于迭代 {i}/{max_iter}")
return new_weights
weights = new_weights
# 打印进度 每500次迭代打印一次损失值
if i % 500 == 0:
loss = np.sum(-y * np.log(predictions+1e-10) - (1-y) * np.log(1-predictions+1e-10)) / len(y)
print(f"Iteration {i}/{max_iter} - Loss: {loss:.4f}")
print(f"达到最大迭代次数 {max_iter}")
return weights
模块5:预测函数
包含3个子模块:
- 5.1 添加偏置项 - 为测试数据添加常数项
- 5.2 计算概率 - 使用sigmoid函数计算生存概率
- 5.3 转换为二元结果 - 根据阈值0.5进行二分类
# =============================================================================
# 模块5:预测函数
# 功能:使用训练好的权重进行预测
# =============================================================================
def make_prediction(X, weights):
"""使用训练好的权重进行预测"""
# 5.1 添加偏置项 偏置项是常数项,用于调整模型的截距
# 允许决策边界不经过原点,使模型能够更好地拟合数据
X_bias = np.c_[X, np.ones(X.shape[0])]#创建一个长度为X行数的全1数组 是numpy的列连接函数
# 将两个数组按列方向连接
# 5.2 计算概率
z = np.dot(X_bias, weights)#计算线性组合
predictions = 1 / (1 + np.exp(-z))#计算概率
# 5.3 转换为二元结果 如果概率大于0.5 则预测为1 否则预测为0
binary_predictions = np.where(predictions >= 0.5, 1, 0)
return binary_predictions#返回二元结果
模块6:可视化函数
- 生成预测结果对比图
这里感觉怪怪的,就没用这个函数,感兴趣的可以看
# 模块6:可视化函数
# 功能:生成预测结果的可视化图表
# =============================================================================
'''
#绘制权重分布图
def plot_weights(weights, feature_names):
"""
绘制权重柱状图
参数:
weights - 模型权重
feature_names - 特征名称列表
"""
plt.figure(figsize=(12, 6))
plt.bar(range(len(weights)), weights)
plt.xticks(range(len(weights)), feature_names, rotation=45, ha='right')
plt.title('特征权重分布')
plt.xlabel('特征')
plt.ylabel('权重值')
plt.tight_layout()
plt.savefig('weights_distribution.png')
plt.close()
'''
def plot_prediction_comparison(y_true, y_pred):
"""
绘制预测结果对比图
参数:
y_true - 实际标签
y_pred - 预测标签
"""
# 6.1 计算正确和错误的预测数
correct = np.sum(y_true == y_pred)
incorrect = len(y_true) - correct
# 6.2 计算实际存活和死亡数
survived = np.sum(y_true == 1)
died = np.sum(y_true == 0)
# 6.3 创建对比图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# 预测结果图
ax1.bar(['正确预测', '错误预测'], [correct, incorrect])
ax1.set_title('预测结果统计')
ax1.set_ylabel('数量')
# 实际结果图
ax2.bar(['存活', '死亡'], [survived, died])
ax2.set_title('实际结果统计')
ax2.set_ylabel('数量')
plt.tight_layout()
plt.savefig('prediction_comparison.png')
plt.close()
模块7:主函数
包含8个子模块:
- 7.1 读取数据 - 加载训练集和测试集
- 7.2 训练集预处理 - 调用模块3处理训练数据
- 7.3 训练模型 - 调用模块4训练逻辑回归
- 7.4 测试集预处理 - 调用模块3处理测试数据
- 7.5 预测测试集 - 调用模块5进行预测
- 7.6 可视化结果 - 调用模块6生成图表
- 7.7 生成提交文件 - 保存预测结果
- 7.8 项目总结 - 输出项目信息和提交说明
# =============================================================================
# 模块7:主函数
# 功能:整合所有模块,完成完整的机器学习流程
# =============================================================================
def main():
# 7.1 读取数据
print("=== 开始泰坦尼克号生存预测项目 ===")
train_df = pd.read_csv(TRAIN_PATH)
test_df = pd.read_csv(TEST_PATH)
# 7.2 训练集预处理
print("\n=== 模块3:数据预处理 ===")
print("处理训练数据...")
X_train, train_stats = preprocess_data(train_df, is_train=True)
y_train = train_df['Survived'].values
# 7.3 训练模型
print("\n=== 模块4:模型训练 ===")
print("训练逻辑回归模型...")
weights = train_logistic_regression(X_train, y_train, learning_rate=0.1, max_iter=3000)
'''
# 绘制权重分布图
feature_names = ['Sex', 'Age', 'FarePerPerson', 'Title', 'FamilySize',
'IsAlone', 'IsChild', 'IsMother', 'HasCabin',
'Embarked_C', 'Embarked_Q', 'Embarked_S',
'Pclass_1', 'Pclass_2', 'Pclass_3', 'Bias']
plot_weights(weights, feature_names)
'''
# 7.4 测试集预处理
print("\n=== 模块5:预测 ===")
print("处理测试数据...")
X_test = preprocess_data(test_df, train_stats=train_stats, is_train=False)
# 7.5 预测测试集
print("生成预测结果...")
predictions = make_prediction(X_test, weights)
# 7.6 可视化结果
print("\n=== 模块6:结果可视化 ===")
plot_prediction_comparison(y_train, make_prediction(X_train, weights))
# 7.7 生成提交文件
print("\n=== 生成提交文件 ===")
submission = pd.DataFrame({
'PassengerId': test_df['PassengerId'],
'Survived': predictions
})
submission.to_csv(SUBMISSION_PATH, index=False)
print(f"提交文件已保存至: {SUBMISSION_PATH}")
print(f"生成 {len(submission)} 条预测记录")
# 7.8 项目总结
print("\n=== 项目总结 ===")
print("Kaggle提交说明:")
print("1. 访问 https://www.kaggle.com/c/titanic/submit")
print("2. 点击'选择文件'按钮")
print("3. 上传生成的", SUBMISSION_PATH)
print("4. 在描述中注明'逻辑回归改进版'")
print("5. 点击'提交'查看您的分数")
print("\n可视化结果已保存:")
#print("- weights_distribution.png:特征权重分布图")
print("- prediction_comparison.png:预测结果对比图")
print("\n=== 项目完成 ===")
# =============================================================================
# 程序入口
# =============================================================================
if __name__ == '__main__':
main()
附完整码:
# =============================================================================
# 泰坦尼克号生存预测 - 逻辑回归算法
# 作者:[FAREWELL00075]
# 日期:[2025.6.29]
# 功能:使用逻辑回归算法预测泰坦尼克号乘客生存情况
# =============================================================================
# =============================================================================
# 模块1:导入必要的库和设置
# =============================================================================
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
# 设置matplotlib中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# =============================================================================
# 模块2:文件路径配置
# =============================================================================
TRAIN_PATH = './titanic/train.csv'
TEST_PATH = './titanic/test.csv'
SUBMISSION_PATH = './submission.csv'
# =============================================================================
# 模块3:数据预处理函数
# 功能:对原始数据进行清洗、特征工程和标准化处理
# =============================================================================
def preprocess_data(df, train_stats=None, is_train=True):
"""
数据预处理函数,对训练集和测试集进行相同的特征工程处理
参数:
df - 原始数据框
train_stats - 训练集统计信息
is_train - 是否为训练数据
返回: 处理后的特征矩阵
"""
# 副本操作,避免修改原始数据
data = df.copy()
# 3.1 缺失值处理 -------------------------------------------------
if is_train:
# 训练集:计算参考值
age_median = data['Age'].median()#年龄中位数
fare_median = data['Fare'].median()#票价中位数
embarked_mode = data['Embarked'].mode()[0]#登船港口众数
else:
# 测试集:使用训练集的参考值
age_median = train_stats['median']['Age']
fare_median = train_stats['median']['Fare']
embarked_mode = train_stats['mode']['Embarked']
# 应用填充 - 不使用inplace避免警告
data['Age'] = data['Age'].fillna(age_median)#年龄填充中位数
data['Fare'] = data['Fare'].fillna(fare_median)#票价填充中位数
data['Embarked'] = data['Embarked'].fillna(embarked_mode)#登船港口填充众数
# 特殊处理:客舱特征 是否有客舱 有则为1 否则为0
data['HasCabin'] = data['Cabin'].apply(lambda x: 0 if pd.isna(x) else 1)
# 3.2 新特征提取 -------------------------------------------------
# 从姓名中提取称呼(使用原始字符串而不是转义序列)
data['Title'] = data['Name'].str.extract(r' ([A-Za-z]+)\.', expand=False)
title_mapping = {
'Mr': 1, 'Miss': 2, 'Mrs': 3, 'Master': 4,#先生
'Dr': 5, 'Rev': 5, 'Col': 5, 'Major': 5, #博士
'Mlle': 2, 'Countess': 3, 'Ms': 2, 'Lady': 3,#女士
'Jonkheer': 5, 'Sir': 5, 'Capt': 5, 'Don': 5,#男士
'Mme': 3, 'Dona': 3#女士
}
data['Title'] = data['Title'].map(title_mapping).fillna(5) # 未知称呼映射为5
# 家庭相关特征
data['FamilySize'] = data['SibSp'] + data['Parch'] + 1
data['IsAlone'] = np.where(data['FamilySize'] == 1, 1, 0)#是否独自一人 是则为1 否则为0
# 乘客类型特征
data['IsChild'] = np.where(data['Age'] < 16, 1, 0)#是否为儿童 是则为1 否则为0
data['IsMother'] = np.where(
(data['Sex'] == 'female') &
(data['Parch'] > 0) &
(data['Age'] > 18) &
(data['Title'] != 2), 1, 0)
# 票价相关特征
data['FarePerPerson'] = data['Fare'] / data['FamilySize']
# 3.3 类别特征编码 -------------------------------------------------
# 性别编码 用来表示乘客的性别 男性为0 女性为1
data['Sex'] = data['Sex'].map({'male': 0, 'female': 1})
# Embarked编码(独热) 用来表示乘客的登船港口
embarked_dummies = pd.get_dummies(data['Embarked'], prefix='Embarked')
# Pclass编码(独热) 用来表示乘客的船票等级 1等舱为1 2等舱为2 3等舱为3
pclass_dummies = pd.get_dummies(data['Pclass'], prefix='Pclass')
# 3.4 特征选择 -------------------------------------------------
# 原始特征 用来表示乘客的性别 年龄 票价 称呼 家庭大小 是否独自一人
# 是否为儿童 是否为母亲 是否有客舱信息 登船港口 船票等级
base_features = ['Sex', 'Age', 'FarePerPerson', 'Title',
'FamilySize', 'IsAlone', 'IsChild', 'IsMother', 'HasCabin']
# 组合所有特征 将所有特征组合成一个特征矩阵
all_features = pd.concat([
data[base_features],
embarked_dummies,
pclass_dummies
], axis=1)
# 填充可能的缺失值 将缺失值填充为0
all_features = all_features.fillna(0)
# 3.5 特征标准化 -------------------------------------------------
if is_train:
# 训练集:创建并拟合标准化器 标准化器是用来将特征缩放到0-1之间
scaler = StandardScaler()
scaled_features = scaler.fit_transform(all_features)
# 返回特征和统计信息 用来存储训练集的统计信息
return scaled_features, {
'mean': scaler.mean_,#均值
'scale': scaler.scale_,#标准差
'median': {
'Age': age_median,#年龄中位数
'Fare': fare_median#票价中位数
},
'mode': {
'Embarked': embarked_mode#众数
}
}
else:
# 测试集:使用训练集的标准化器
scaler = StandardScaler()
scaler.mean_ = train_stats['mean']#均值
scaler.scale_ = train_stats['scale']#标准差
scaled_features = scaler.transform(all_features)#将特征缩放到0-1之间
return scaled_features
# =============================================================================
# 模块4:逻辑回归模型训练
# 功能:使用梯度下降法训练逻辑回归模型
# =============================================================================
def train_logistic_regression(X, y, learning_rate=0.01, max_iter=3000, tol=1e-4):
"""
逻辑回归训练函数
预测公式:P(y=1|x) = 1 / (1 + exp(-z))
其中z = w0 + w1*x1 + w2*x2 + ... + wn*xn
w0是偏置项,x1,x2,...,xn是特征,w1,w2,...,wn是权重
"""
# 4.1 添加偏置项 偏置项是常数项,用于调整模型的截距
# 允许决策边界不经过原点,使模型能够更好地拟合数据
X_bias = np.c_[X, np.ones(X.shape[0])]#创建一个长度为X行数的全1数组 是numpy的列连接函数
# 将两个数组按列方向连接
# 4.2 初始化权重
weights = np.zeros(X_bias.shape[1])#初始化权重为0
# 4.3 训练循环
#z = w^T * x + b(线性组合)
#p = 1 / (1 + e^(-z))(sigmoid函数)
#L = -1/n * (y * log(p) + (1-y) * log(1-p))(交叉熵损失函数)
#w = w - learning_rate * gradient(梯度下降法)
#gradient = 1/n * (p - y) * x(梯度计算)
for i in range(max_iter):
# 计算预测概率
z = np.dot(X_bias, weights)
# 应用sigmoid函数得到概率
predictions = 1 / (1 + np.exp(-z))
# 计算梯度 梯度是损失函数对权重的偏导数 预测值与实际值的误差
error = predictions - y
gradient = np.dot(X_bias.T, error) / len(y) # 梯度是误差与特征的乘积的平均值
# 更新权重 使用梯度下降法更新权重
new_weights = weights - learning_rate * gradient
# 检查收敛性 如果新权重与旧权重之间的差异小于阈值,则认为模型已经收敛
if np.linalg.norm(new_weights - weights) < tol:
print(f"收敛于迭代 {i}/{max_iter}")
return new_weights
weights = new_weights
# 打印进度 每500次迭代打印一次损失值
if i % 500 == 0:
loss = np.sum(-y * np.log(predictions+1e-10) - (1-y) * np.log(1-predictions+1e-10)) / len(y)
print(f"Iteration {i}/{max_iter} - Loss: {loss:.4f}")
print(f"达到最大迭代次数 {max_iter}")
return weights
# =============================================================================
# 模块5:预测函数
# 功能:使用训练好的权重进行预测
# =============================================================================
def make_prediction(X, weights):
"""使用训练好的权重进行预测"""
# 5.1 添加偏置项 偏置项是常数项,用于调整模型的截距
# 允许决策边界不经过原点,使模型能够更好地拟合数据
X_bias = np.c_[X, np.ones(X.shape[0])]#创建一个长度为X行数的全1数组 是numpy的列连接函数
# 将两个数组按列方向连接
# 5.2 计算概率
z = np.dot(X_bias, weights)#计算线性组合
predictions = 1 / (1 + np.exp(-z))#计算概率
# 5.3 转换为二元结果 如果概率大于0.5 则预测为1 否则预测为0
binary_predictions = np.where(predictions >= 0.5, 1, 0)
return binary_predictions#返回二元结果
# =============================================================================
# 模块6:可视化函数
# 功能:生成预测结果的可视化图表
# =============================================================================
'''
#绘制权重分布图
def plot_weights(weights, feature_names):
"""
绘制权重柱状图
参数:
weights - 模型权重
feature_names - 特征名称列表
"""
plt.figure(figsize=(12, 6))
plt.bar(range(len(weights)), weights)
plt.xticks(range(len(weights)), feature_names, rotation=45, ha='right')
plt.title('特征权重分布')
plt.xlabel('特征')
plt.ylabel('权重值')
plt.tight_layout()
plt.savefig('weights_distribution.png')
plt.close()
'''
def plot_prediction_comparison(y_true, y_pred):
"""
绘制预测结果对比图
参数:
y_true - 实际标签
y_pred - 预测标签
"""
# 6.1 计算正确和错误的预测数
correct = np.sum(y_true == y_pred)
incorrect = len(y_true) - correct
# 6.2 计算实际存活和死亡数
survived = np.sum(y_true == 1)
died = np.sum(y_true == 0)
# 6.3 创建对比图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# 预测结果图
ax1.bar(['正确预测', '错误预测'], [correct, incorrect])
ax1.set_title('预测结果统计')
ax1.set_ylabel('数量')
# 实际结果图
ax2.bar(['存活', '死亡'], [survived, died])
ax2.set_title('实际结果统计')
ax2.set_ylabel('数量')
plt.tight_layout()
plt.savefig('prediction_comparison.png')
plt.close()
# =============================================================================
# 模块7:主函数
# 功能:整合所有模块,完成完整的机器学习流程
# =============================================================================
def main():
# 7.1 读取数据
print("=== 开始泰坦尼克号生存预测项目 ===")
train_df = pd.read_csv(TRAIN_PATH)
test_df = pd.read_csv(TEST_PATH)
# 7.2 训练集预处理
print("\n=== 模块3:数据预处理 ===")
print("处理训练数据...")
X_train, train_stats = preprocess_data(train_df, is_train=True)
y_train = train_df['Survived'].values
# 7.3 训练模型
print("\n=== 模块4:模型训练 ===")
print("训练逻辑回归模型...")
weights = train_logistic_regression(X_train, y_train, learning_rate=0.1, max_iter=3000)
'''
# 绘制权重分布图
feature_names = ['Sex', 'Age', 'FarePerPerson', 'Title', 'FamilySize',
'IsAlone', 'IsChild', 'IsMother', 'HasCabin',
'Embarked_C', 'Embarked_Q', 'Embarked_S',
'Pclass_1', 'Pclass_2', 'Pclass_3', 'Bias']
plot_weights(weights, feature_names)
'''
# 7.4 测试集预处理
print("\n=== 模块5:预测 ===")
print("处理测试数据...")
X_test = preprocess_data(test_df, train_stats=train_stats, is_train=False)
# 7.5 预测测试集
print("生成预测结果...")
predictions = make_prediction(X_test, weights)
# 7.6 可视化结果
print("\n=== 模块6:结果可视化 ===")
plot_prediction_comparison(y_train, make_prediction(X_train, weights))
# 7.7 生成提交文件
print("\n=== 生成提交文件 ===")
submission = pd.DataFrame({
'PassengerId': test_df['PassengerId'],
'Survived': predictions
})
submission.to_csv(SUBMISSION_PATH, index=False)
print(f"提交文件已保存至: {SUBMISSION_PATH}")
print(f"生成 {len(submission)} 条预测记录")
# 7.8 项目总结
print("\n=== 项目总结 ===")
print("Kaggle提交说明:")
print("1. 访问 https://www.kaggle.com/c/titanic/submit")
print("2. 点击'选择文件'按钮")
print("3. 上传生成的", SUBMISSION_PATH)
print("4. 在描述中注明'逻辑回归改进版'")
print("5. 点击'提交'查看您的分数")
print("\n可视化结果已保存:")
#print("- weights_distribution.png:特征权重分布图")
print("- prediction_comparison.png:预测结果对比图")
print("\n=== 项目完成 ===")
# =============================================================================
# 程序入口
# =============================================================================
if __name__ == '__main__':
main()