机器学习(一)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 否则为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()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FAREWELL00075

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值