机器学习(一)Kaggle泰坦尼克乘客生存预测之线性模型
预测提交分数,可参考
# =============================================================================
# 泰坦尼克号生存预测 - 逻辑回归算法
# 作者:[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 否则为0data['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'] + 1data['IsAlone'] = np.where(data['FamilySize'] == 1, 1, 0)#是否独自一人 是则为1 否则为0# 乘客类型特征data['IsChild'] = np.where(data['Age'] < 16, 1, 0)#是否为儿童 是则为1 否则为0data['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 女性为1data['Sex'] = data['Sex'].map({'male': 0, 'female': 1})# Embarked编码(独热) 用来表示乘客的登船港口 embarked_dummies = pd.get_dummies(data['Embarked'], prefix='Embarked')# Pclass编码(独热) 用来表示乘客的船票等级 1等舱为1 2等舱为2 3等舱为3pclass_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)# 填充可能的缺失值 将缺失值填充为0all_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*xnw0是偏置项,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 - ygradient = 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_weightsweights = 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 否则预测为0binary_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 否则为0data['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'] + 1data['IsAlone'] = np.where(data['FamilySize'] == 1, 1, 0)#是否独自一人 是则为1 否则为0# 乘客类型特征data['IsChild'] = np.where(data['Age'] < 16, 1, 0)#是否为儿童 是则为1 否则为0data['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 女性为1data['Sex'] = data['Sex'].map({'male': 0, 'female': 1})# Embarked编码(独热) 用来表示乘客的登船港口 embarked_dummies = pd.get_dummies(data['Embarked'], prefix='Embarked')# Pclass编码(独热) 用来表示乘客的船票等级 1等舱为1 2等舱为2 3等舱为3pclass_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)# 填充可能的缺失值 将缺失值填充为0all_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*xnw0是偏置项,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 - ygradient = 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_weightsweights = 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 否则预测为0binary_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()