【深度学习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
    )
相关推荐
NAGNIP3 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab4 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab4 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP8 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年8 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼8 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS8 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区9 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈9 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang10 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx