【深度学习6】多层感知机2

  1. 范数
    范数是对向量大小的度量 ,可以反映权重的总影响力度,范数越大说明权重的累计影响越大,函数对于输入特征更敏感,可以拟合更复杂的模式。
    L1范数:侧重非零权重的数量 。范数大可能意味着更多特征被激活,函数更 "繁琐"。限制L1范数,会让部分权重变为 0,实现 "特征选择",简化函数(只保留关键特征)。
    L2范数:侧重权重的绝对大小 。范数大可能意味着部分权重数值极大,函数对个别特征过度依赖,容易 "过拟合"(即复杂性超出数据真实规律)。限制L2范数,会让权重数值整体变小,避免个别特征主导预测,让函数更 "稳健"。
    权重衰减 (weight decay)是最广泛使用的正则化的技术之一, 它通常也被称为

    正则化。 这项技术通过函数与零的距离来衡量函数的复杂度。核心目的是通过对权重施加惩罚,使权重数值减小,减少对个别特征的过度依赖,控制函数复杂度,避免过拟合,同时提升模型的泛化能力和稳健性。

    权重衰减代码实现:

    python 复制代码
    import matplotlib
    matplotlib.use('TkAgg')  # ✅ 让 PyCharm 可以显示图像窗口
    import torch
    from torch import nn
    from d2l import torch as d2l
    
    n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
    true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
    train_data = d2l.synthetic_data(true_w, true_b, n_train)
    train_iter = d2l.load_array(train_data, batch_size)
    test_data = d2l.synthetic_data(true_w, true_b, n_test)
    test_iter = d2l.load_array(test_data, batch_size, is_train=False)
    
    def init_params():
        w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)
        b = torch.zeros(1, requires_grad=True)
        return [w, b]
    
    def l2_penalty(w):
        return torch.sum(w.pow(2)) / 2
    
    def train(lambd):
        w, b = init_params()
        net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
        num_epochs, lr = 100, 0.003
        animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
                                xlim=[5, num_epochs], legend=['train', 'test'])
        for epoch in range(num_epochs):
            for X, y in train_iter:
                # 增加了L2范数惩罚项,
                # 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
                l = loss(net(X), y) + lambd * l2_penalty(w)
                l.sum().backward()
                d2l.sgd([w, b], lr, batch_size)
            if (epoch + 1) % 5 == 0:
                animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
                                         d2l.evaluate_loss(net, test_iter, loss)))
        print('w的L2范数是:', torch.norm(w).item())
    
    train(lambd=0)
    
    train(lambd=3)
  2. 暂退法:"暂退" 体现在训练时随机 "暂时退出" 部分神经元(注入噪声的本质),目的是通过打破神经元间的固定依赖,提升模型泛化能力、防止过拟合。在前向传播中,给每一层的神经元加入噪声。

  3. 经典泛化理论认为,为了缩小训练和测试性能之间的差距,应该以简单的模型为目标 。 "简单模型",本质是 "复杂度匹配数据规律" 的模型,而非 "表面简单"------ 它能避免过拟合,自然缩小训练与测试差距,而 "复杂模型" 反而因拟合噪声导致差距扩大。简单性的另一个角度是平滑性,即函数不应该对其输入的微小变化敏感。注入噪声(如暂退法随机置零神经元、高斯噪声添加到激活值)的核心是 "迫使模型适应不确定性",最终抵消噪声的 "破坏性",形成平滑映射:

  4. 如何注入这种噪声 ? 一种想法是以一种无偏向 (unbiased)的方式注入噪声。 这样在固定住其他层时,每一层的期望值等于没有噪音时的值。噪声注入的 "无偏向(unbiased)",指训练时注入的噪声不会改变该层输出的 "数学期望" ------ 固定其他层时,含噪声的输出均值和无噪声时完全一致,避免给模型训练引入系统性偏差。

  5. 前向传播(forward propagation或forward pass) 指的是:按顺序(从输入层到输出层)计算和存储神经网络中每层的结果。

  6. 反向传播(backward propagation或backpropagation)指的是计算神经网络参数梯度的方法。言之,该方法根据微积分中的链式规则,按相反的顺序从输出层到输入层遍历网络。

  7. 隐藏变量是网络中间层的输出(连接输入和输出的 "过渡特征"),变换 f 是每一层对输入(前一层输出)的计算规则(含权重、激活函数等参数化操作),二者共同构成深层网络的 "分层特征传递" 逻辑。

  8. 梯度爆炸(gradient exploding)问题: 参数更新过大,破坏了模型的稳定收敛; 梯度消失(gradient vanishing)问题: 参数更新过小,在每次更新时几乎不会移动,导致模型无法学习。

  9. 协变量(Covariate):就是我们模型的输入特征(比如预测疾病时的 "年龄、血压、血糖",预测销量时的 "价格、推广费用"),是用来解释或预测标签的变量。

    标签函数(Label Function):本质是 "特征→标签" 的条件分布 , 它代表了特征和标签之间的核心关联规律(比如 "血压≥180→高血压风险 80%" 这个规律)。

    分布偏移(Distribution Shift):训练数据和测试数据的分布不一致(比如训练时用的是年轻人数据,测试时用的是老年人数据)。

    协变量偏移:特征(血压值)的分布变了(年轻人→老年人),但特征到标签的规律(血压阈值判断高血压)没变。

    1. Kaggle预测房价
    python 复制代码
    # =========================================================
    # Kaggle House Prices - Pure PyTorch Version
    # 每一行都有详细中文解释,适合学习 / 实验报告
    # =========================================================
    
    # ---------- Matplotlib 设置 ----------
    import matplotlib
    matplotlib.use('TkAgg')  # 使用 TkAgg 后端,保证 PyCharm 本地可以弹出图像窗口
    
    # ---------- 常用科学计算库 ----------
    import pandas as pd           # 用于读取 CSV、表格数据处理
    import numpy as np            # 数值计算(主要辅助 pandas)
    
    # ---------- PyTorch 核心 ----------
    import torch                  # PyTorch 主库
    import torch.nn as nn         # 神经网络模块(Linear、Loss 等)
    from torch.utils.data import TensorDataset, DataLoader  # 数据集与批量加载器
    
    # ---------- 其他工具 ----------
    import hashlib                # 用于校验文件完整性(SHA1)
    import os                     # 文件路径操作
    import requests               # 网络下载
    import matplotlib.pyplot as plt  # 绘图
    
    # =========================================================
    # 一、数据下载工具(来自 D2L 思想)
    # =========================================================
    
    # DATA_HUB 用于存储:数据名称 -> (下载链接, SHA1 校验值)
    DATA_HUB = dict()
    
    # D2L 官方数据服务器地址
    DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'
    
    def download(name, cache_dir=os.path.join('..', 'data')):
        """
        根据数据名称下载文件,并缓存到本地。
        如果文件已存在且校验正确,则直接使用缓存。
        """
        # 确保数据名称存在
        assert name in DATA_HUB, f"{name} 不存在于 DATA_HUB 中"
    
        # 解包下载地址和校验码
        url, sha1_hash = DATA_HUB[name]
    
        # 创建缓存目录
        os.makedirs(cache_dir, exist_ok=True)
    
        # 本地文件路径
        fname = os.path.join(cache_dir, url.split('/')[-1])
    
        # 如果文件已经存在,则进行 SHA1 校验
        if os.path.exists(fname):
            sha1 = hashlib.sha1()
            with open(fname, 'rb') as f:
                while True:
                    data = f.read(1048576)  # 每次读取 1MB
                    if not data:
                        break
                    sha1.update(data)
            # 如果校验通过,直接返回文件路径
            if sha1.hexdigest() == sha1_hash:
                return fname
    
        # 否则重新下载
        print(f'正在从 {url} 下载 {fname}...')
        r = requests.get(url, stream=True)
        with open(fname, 'wb') as f:
            f.write(r.content)
    
        return fname
    
    # =========================================================
    # 二、下载并读取 Kaggle 房价数据
    # =========================================================
    
    # 训练集
    DATA_HUB['kaggle_house_train'] = (
        DATA_URL + 'kaggle_house_pred_train.csv',
        '585e9cc93e70b39160e7921475f9bcd7d31219ce'
    )
    
    # 测试集
    DATA_HUB['kaggle_house_test'] = (
        DATA_URL + 'kaggle_house_pred_test.csv',
        'fa19780a7b011d9b009e8bff8e99922a8ee2eb90'
    )
    
    # 读取 CSV 文件
    train_data = pd.read_csv(download('kaggle_house_train'))
    test_data = pd.read_csv(download('kaggle_house_test'))
    
    # 打印数据规模,确认读取正确
    print(train_data.shape, test_data.shape)
    
    # =========================================================
    # 三、特征工程(核心步骤)
    # =========================================================
    
    # 合并训练集和测试集的特征(不包含 Id 和 SalePrice)
    # 目的:保证 one-hot 编码后维度完全一致
    all_features = pd.concat(
        (train_data.iloc[:, 1:-1], test_data.iloc[:, 1:])
    )
    
    # 找出所有数值型特征(排除字符串)
    numeric_features = all_features.dtypes[
        all_features.dtypes != 'object'
    ].index
    
    # 对数值特征进行标准化处理(均值 0,方差 1)
    all_features[numeric_features] = all_features[numeric_features].apply(
        lambda x: (x - x.mean()) / x.std()
    )
    
    # 标准化后,缺失值用 0 填充(合理且稳定)
    all_features[numeric_features] = all_features[numeric_features].fillna(0)
    
    # 对类别型特征进行 One-Hot 编码
    # dummy_na=True 表示将缺失值也作为一个类别
    all_features = pd.get_dummies(all_features, dummy_na=True)
    
    # =========================================================
    # 四、转换为 PyTorch Tensor
    # =========================================================
    
    # 训练集样本数
    n_train = train_data.shape[0]
    
    # 训练特征
    train_features = torch.tensor(
        all_features[:n_train].values,
        dtype=torch.float32
    )
    
    # 测试特征
    test_features = torch.tensor(
        all_features[n_train:].values,
        dtype=torch.float32
    )
    
    # 训练标签(房价)
    # reshape(-1, 1):转换为二维,适配 Linear 输出
    train_labels = torch.tensor(
        train_data.SalePrice.values.reshape(-1, 1),
        dtype=torch.float32
    )
    
    # =========================================================
    # 五、模型定义
    # =========================================================
    
    # 输入特征维度
    in_features = train_features.shape[1]
    
    def get_net():
        """
        定义一个线性回归模型:
        y = Wx + b
        """
        return nn.Sequential(
            nn.Linear(in_features, 1)
        )
    
    # 均方误差损失函数
    loss = nn.MSELoss()
    
    # =========================================================
    # 六、评价指标:对数 RMSE
    # =========================================================
    
    def log_rmse(net, features, labels):
        """
        计算对数 RMSE,用于 Kaggle 官方评测
        """
        with torch.no_grad():  # 不计算梯度
            preds = torch.clamp(net(features), 1, float('inf'))
            rmse = torch.sqrt(
                loss(torch.log(preds), torch.log(labels))
            )
        return rmse.item()
    
    # =========================================================
    # 七、模型训练函数
    # =========================================================
    
    def train(net, train_features, train_labels,
              test_features, test_labels,
              num_epochs, lr, weight_decay, batch_size):
    
        train_ls, test_ls = [], []
    
        # 构建 PyTorch 数据加载器
        dataset = TensorDataset(train_features, train_labels)
        train_iter = DataLoader(
            dataset, batch_size=batch_size, shuffle=True
        )
    
        # Adam 优化器
        optimizer = torch.optim.Adam(
            net.parameters(), lr=lr, weight_decay=weight_decay
        )
    
        # 训练循环
        for epoch in range(num_epochs):
            for X, y in train_iter:
                optimizer.zero_grad()   # 清空梯度
                l = loss(net(X), y)     # 前向传播 + 计算损失
                l.backward()            # 反向传播
                optimizer.step()        # 更新参数
    
            # 记录训练误差
            train_ls.append(log_rmse(net, train_features, train_labels))
    
            # 如果有验证集,计算验证误差
            if test_labels is not None:
                test_ls.append(log_rmse(net, test_features, test_labels))
    
        return train_ls, test_ls
    
    # =========================================================
    # 八、K 折交叉验证
    # =========================================================
    
    def get_k_fold_data(k, i, X, y):
        fold_size = X.shape[0] // k
        X_train, y_train = None, None
    
        for j in range(k):
            idx = slice(j * fold_size, (j + 1) * fold_size)
            X_part, y_part = X[idx], y[idx]
    
            if j == i:
                X_valid, y_valid = X_part, y_part
            elif X_train is None:
                X_train, y_train = X_part, y_part
            else:
                X_train = torch.cat([X_train, X_part], dim=0)
                y_train = torch.cat([y_train, y_part], dim=0)
    
        return X_train, y_train, X_valid, y_valid
    
    def k_fold(k, X_train, y_train,
               num_epochs, lr, weight_decay, batch_size):
    
        train_l_sum, valid_l_sum = 0, 0
    
        for i in range(k):
            data = get_k_fold_data(k, i, X_train, y_train)
            net = get_net()
    
            train_ls, valid_ls = train(
                net, *data, num_epochs, lr, weight_decay, batch_size
            )
    
            train_l_sum += train_ls[-1]
            valid_l_sum += valid_ls[-1]
    
            print(f'折 {i+1}:训练 log RMSE={train_ls[-1]:.4f},'
                  f'验证 log RMSE={valid_ls[-1]:.4f}')
    
        return train_l_sum / k, valid_l_sum / k
    
    # =========================================================
    # 九、执行训练
    # =========================================================
    
    k, num_epochs, lr, weight_decay, batch_size = 5, 100, 5, 0, 64
    
    train_l, valid_l = k_fold(
        k, train_features, train_labels,
        num_epochs, lr, weight_decay, batch_size
    )
    
    print(f'\n{k}-折交叉验证结果:')
    print(f'平均训练 log RMSE: {train_l:.4f}')
    print(f'平均验证 log RMSE: {valid_l:.4f}')
    
    # =========================================================
    # 十、最终训练并生成提交文件
    # =========================================================
    
    def train_and_pred(train_features, test_features, train_labels,
                       test_data, num_epochs, lr, weight_decay, batch_size):
    
        net = get_net()
    
        train_ls, _ = train(
            net, train_features, train_labels,
            None, None, num_epochs, lr, weight_decay, batch_size
        )
    
        # 绘制训练曲线
        plt.plot(range(1, num_epochs + 1), train_ls)
        plt.xlabel('epoch')
        plt.ylabel('log rmse')
        plt.yscale('log')
        plt.show()
    
        print(f'最终训练 log RMSE: {train_ls[-1]:.4f}')
    
        # 对测试集进行预测
        preds = net(test_features).detach().numpy()
    
        # 生成提交文件
        test_data['SalePrice'] = preds.reshape(-1)
        submission = pd.concat(
            [test_data['Id'], test_data['SalePrice']], axis=1
        )
        submission.to_csv('submission.csv', index=False)
    
        print('✅ submission.csv 已成功生成')
    
    train_and_pred(
        train_features, test_features, train_labels,
        test_data, num_epochs, lr, weight_decay, batch_size
    )
相关推荐
啊巴矲6 小时前
小白从零开始勇闯人工智能:机器学习初级篇(KNN算法)
人工智能
FL16238631296 小时前
[C#][winform]基于yolov11的水下目标检测系统C#源码+onnx模型+评估指标曲线+精美GUI界面
人工智能·yolo·目标检测
todoitbo6 小时前
从零搭建 Dify AI 平台:一次跌宕起伏的部署之旅
人工智能·ai·大模型·dify·流处理·工具流
SCBAiotAigc6 小时前
一个github的proxy url
人工智能·python
serve the people6 小时前
tensorflow 零基础吃透:TensorFlow 稀疏张量(SparseTensor)的核心用法
人工智能·tensorflow·neo4j
jinxinyuuuus6 小时前
GTA 风格 AI 生成器:提示词工程、LLM创造性联想与模因的自动化生成
运维·人工智能·自动化
free-elcmacom6 小时前
机器学习高阶教程<1>优化理论:破解优化器的底层密码
人工智能·python·机器学习·优化理论
Angelina_Jolie6 小时前
ICCV 2025 | 去模糊新范式!残差引导 + 图像金字塔,强噪声下核估计精度提升 77%,SOTA 到手
图像处理·人工智能·计算机视觉
瀚岳-诸葛弩6 小时前
对比tensorflow,从0开始学pytorch(五)--CBAM
人工智能·pytorch·python