函数极小值

## 微小趋势判断 - 效率稍微高一些,但取的是极小值 - x = x 加/减 一个微小变化 - $x -= \Delta x$ - 重点是让x加上一个微小的值,这个微小的值可以变化,也可以是定值,在一定范围内的一个比较小的值就可以 ``` import numpy as np def fn(x): return x ** 2 def dfn(x): return 2 * x # 随机选取一个出生点 x0 = np.random.uniform(low=-1000, high=1000) print(x0) print(fn(x0)) for _ in range(5000): x0 -= 0.001 * dfn(x0) print(fn(x0)) ``` 580.7493541721117 337269.8123713248 0.0006813815105911381
 
- 现多使用梯度下降法求函数极小值,来训练模型。 

- 梯度,导数,更确切的说是偏导数/微分,描述了函数在某点的变化率和变化方向。
- 用更自然的语言来说,是变化趋势与变化的快慢(大小)。
  - 一个代表方向,最重要 
  - 一个代表大小,在远离极小值的地方,函数值变化快,梯度大;在接近极小值的地方,函数值变化慢,梯度小。
  - 这使得开始时变化幅度大,接近极小值时变化幅度小,比较合理。
- dl09-1.py.ipynb
``` import numpy as np x = np.linspace(start=-10,stop=10,num=50) from matplotlib import pyplot as plt plt.plot(x,x**2) ``` matplotlib plot
``` import numpy as np x = np.random.uniform(low=-10, high=10,) x # -8.248295885583863 ``` ``` """ 1,随机生成一个数组 2,求最小值 """ import numpy as np x = np.random.uniform(low=-10, high=10, size=100000) def fn(x): return x ** 2 # x0存放最小值点对应的x x0 = x[0] # y_min 存放最小值 y_min = fn(x[0]) for ele in x: y = fn(ele) if y < y_min: y_min = y x0 = ele print(x0, y_min) ```

 


 


pytorch
``` import torch w1 = torch.tensor([1.0,1.0],requires_grad=True) b = torch.tensor(1.0,requires_grad=True) y = 2*w1 + b # tensor([2., 2.], grad_fn=) loss = y.sum() #转为标量 loss.backward() ``` - 从import torch开始,就开始构建计算图 - 从loss.backward()开始,反向传播,计算梯度 ``` w1.grad #tensor([2., 2.]) w1.ndim,b.ndim,y.ndim,loss.ndim #(1, 0, 1, 0) ```

 
- 一个变量往往是由一组变量组合表示,即一个变量是与多个变量的组合呈线性关系 
- 组合方式,即变量的系数,就是权重 
- 每个变量都有一个梯度
    
- 兼容写法 ``` USE_CUDA = torch.cuda.is_available() device = torch.device("cuda" if USE_CUDA else "cpu") device = "cuda:0" if torch.cuda.is_available() else "cpu" X = X.to(device=device) y = y.to(device=device) ``` ### 两种device写法的区别 #### 写法1:torch.device对象 ```python device = torch.device("cuda" if USE_CUDA else "cpu") X = X.to(device) ``` **特点:** - 返回一个`torch.device`对象,类型明确 - 可以指定更详细的设备信息,如`torch.device("cuda:0")` - 代码可读性更好,语义更清晰 - 适合在代码中多次传递device参数 #### 写法2:字符串 ```python device = "cuda:0" if torch.cuda.is_available() else "cpu" X = X.to(device) ``` **特点:** - 直接使用字符串,简洁但类型不够明确 - `"cuda:0"`明确指定使用第0号GPU - 代码更紧凑,适合简单场景 - 字符串会被PyTorch自动转换为device对象 **推荐:** 在生产代码中使用`torch.device`对象,类型更安全,可维护性更好。 ### 单GPU与多GPU #### 单GPU使用 ```python # 默认使用cuda:0(第一块GPU) device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") model = model.to(device) data = data.to(device) # 或者简写为 device = "cuda" # 默认使用cuda:0 ``` #### 多GPU使用场景 ##### 1. 查看可用GPU ```python import torch # 查看GPU数量 num_gpus = torch.cuda.device_count() print(f"可用GPU数量: {num_gpus}") # 查看每个GPU的名称 for i in range(num_gpus): print(f"GPU {i}: {torch.cuda.get_device_name(i)}") # 查看当前GPU print(f"当前GPU: {torch.cuda.current_device()}") ``` ##### 2. 指定特定GPU ```python # 使用第1块GPU(索引从0开始) device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu") model = model.to(device) data = data.to(device) ``` ##### 3. 数据并行(DataParallel)- 最常用 ```python import torch import torch.nn as nn # 定义模型 model = MyModel() # 检查是否有多个GPU if torch.cuda.device_count() > 1: print(f"使用 {torch.cuda.device_count()} 个GPU") # 将模型包装为DataParallel model = nn.DataParallel(model) # 将模型移至GPU(默认移至所有可用GPU) model = model.cuda() # 数据只需要移至默认GPU(cuda:0),DataParallel会自动分发 data, target = data.cuda(), target.cuda() # 前向传播 output = model(data) ``` **DataParallel工作原理:** - **模型复制:** 将完整的模型复制到每个GPU上(每个GPU都有完整的模型权重参数副本) - **数据拆分:** 自动将输入数据的batch拆分到多个GPU - **并行计算:** 每个GPU使用自己那份模型副本,独立计算前向传播 - **结果汇总:** 收集所有GPU的结果并汇总到主GPU(默认cuda:0) - **梯度同步:** 反向传播时自动同步所有GPU的梯度,并更新模型参数 **核心概念:数据并行 vs 模型并行** ``` ┌─────────────────────────────────────────────────────────────┐ │ 数据并行 (DataParallel) │ ├─────────────────────────────────────────────────────────────┤ │ GPU 0: [模型完整副本] + [数据批次 1/3] → [输出1/3] │ │ GPU 1: [模型完整副本] + [数据批次 2/3] → [输出2/3] │ │ GPU 2: [模型完整副本] + [数据批次 3/3] → [输出3/3] │ │ │ │ 每个GPU都有完整的模型,处理不同的数据,最后汇总结果 │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ 模型并行 (Model Parallel) │ ├─────────────────────────────────────────────────────────────┤ │ GPU 0: [模型第1层] → [中间结果] ──┐ │ │ GPU 1: [模型第2层] → [中间结果] ←──┤ │ │ GPU 2: [模型第3层] → [最终输出] ←──┘ │ │ │ │ 模型分散在多个GPU上,数据按顺序流经各个GPU │ └─────────────────────────────────────────────────────────────┘ ``` **注意事项:** - 数据batch size应该是GPU数量的整数倍 - 主GPU(cuda:0)需要额外存储汇总结果,显存占用更大 - 适合单机多卡场景 - **每个GPU都需要能够完整容纳模型**(这是关键限制) ##### 4. 分布式数据并行(DistributedDataParallel)- 推荐 ```python import torch import torch.distributed as dist import torch.multiprocessing as mp from torch.nn.parallel import DistributedDataParallel as DDP def setup(rank, world_size): # 初始化进程组 dist.init_process_group( backend='nccl', # NVIDIA GPU推荐使用nccl init_method='tcp://localhost:12345', rank=rank, world_size=world_size ) def cleanup(): dist.destroy_process_group() def train(rank, world_size): setup(rank, world_size) # 创建模型并移至当前GPU model = MyModel().to(rank) # 包装为DDP模型 model = DDP(model, device_ids=[rank]) # 创建数据加载器(每个进程获取不同数据) dataset = MyDataset() sampler = torch.utils.data.distributed.DistributedSampler( dataset, num_replicas=world_size, rank=rank ) dataloader = DataLoader(dataset, batch_size=32, sampler=sampler) # 训练循环 for data, target in dataloader: data, target = data.to(rank), target.to(rank) output = model(data) # ... 训练逻辑 cleanup() if __name__ == "__main__": world_size = 2 # 使用2个GPU mp.spawn(train, args=(world_size,), nprocs=world_size, join=True) ``` **DDP优势:** - 每个GPU独立运行一个进程,性能更好 - 避免DataParallel的GIL限制 - 支持多机多卡训练 - 生产环境推荐方案 ##### 5. 手动分配数据到不同GPU(模型并行) ```python # 场景:模型的不同部分在不同GPU上计算 # 当模型太大无法放入单个GPU时使用 class MultiGPUModel(nn.Module): def __init__(self): super().__init__() # 模型的不同部分放在不同GPU上 self.part1 = nn.Linear(1000, 500).to('cuda:0') self.part2 = nn.Linear(500, 100).to('cuda:1') def forward(self, x): # 数据需要在GPU间传输 x = x.to('cuda:0') x = self.part1(x) x = x.to('cuda:1') # 中间结果传输到GPU 1 x = self.part2(x) return x model = MultiGPUModel() data = data.to('cuda:0') output = model(data) ``` **注意事项:** - GPU间数据传输有性能开销(这是主要瓶颈) - 仅适用于特殊场景(模型太大单个GPU放不下) - 需要手动管理数据流,编程复杂度高 - **这是模型并行的实现方式,与DataParallel的数据并行不同** #### 数据并行 vs 模型并行对比 | 特性 | 数据并行 (DataParallel) | 模型并行 (Model Parallel) | |------|------------------------|---------------------------| | **模型分布** | 每个GPU有完整模型副本 | 模型分散在多个GPU上 | | **数据分布** | 数据batch拆分到各GPU | 所有GPU处理相同数据 | | **适用场景** | 模型能放入单个GPU | 模型太大,单个GPU放不下 | | **通信开销** | 梯度同步(相对较小) | GPU间数据传输(可能较大) | | **实现复杂度** | 低(一行代码) | 高(需要手动设计) | | **训练速度** | 接近线性加速 | 受限于GPU间传输速度 | | **显存需求** | 每个GPU需容纳完整模型 | 每个GPU只需容纳模型一部分 | **实际应用建议:** - **绝大多数情况使用数据并行**(DataParallel/DDP) - 只有当模型单个GPU放不下时才考虑模型并行 - 可以结合使用:先用模型并行将大模型拆分,再对每个部分用数据并行 ```python # 示例:结合数据并行和模型并行 class HybridParallelModel(nn.Module): def __init__(self): super().__init__() # 第一层在GPU 0,1上数据并行 self.part1 = nn.DataParallel(nn.Linear(1000, 500).cuda(0)) # 第二层在GPU 2,3上数据并行 self.part2 = nn.DataParallel(nn.Linear(500, 100).cuda(2)) def forward(self, x): x = self.part1(x) x = x.cuda(2) # 在GPU组间传输 x = self.part2(x) return x ``` ### GPU内存管理 ```python # 查看GPU内存使用 print(torch.cuda.memory_summary(device=None, abbreviated=False)) # 查看当前GPU显存分配 print(f"已分配: {torch.cuda.memory_allocated()} / {torch.cuda.get_device_properties(0).total_memory}") # 清空缓存 torch.cuda.empty_cache() # 设置GPU内存增长策略 import os os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'max_split_size_mb:128' ``` ### 最佳实践 1. **单GPU场景:** 使用`device = torch.device("cuda")`即可 2. **多GPU训练:** 优先使用`DataParallel`(简单)或`DistributedDataParallel`(高效) 3. **指定GPU:** 使用环境变量`CUDA_VISIBLE_DEVICES=0,1`限制可见GPU 4. **batch size:** 设置为GPU数量的整数倍以保证负载均衡 ```bash # 在命令行指定使用哪些GPU CUDA_VISIBLE_DEVICES=0,1,2,3 python train.py ``` - 模型从CPU迁移至GPU ``` import torch use_gpu = torch.cuda.is_available() if use_gpu: model.cuda() ```
### 全局控制 #### set_num_threads ``` import torch # 设置全局线程数 torch.set_num_threads(4) ``` - 注意,torch.set_num_threads 设置的是全局线程数, - 它将影响所有的 PyTorch 操作,包括数据加载、模型前向传播等。 #### 环境变量 ``` import os 设置环境变量以限制线程数 os.environ['OMP_NUM_THREADS'] = '4' os.environ['MKL_NUM_THREADS'] = '4' 现在导入 PyTorch 和你的模型 import torch import torch.nn as nn 你的模型定义和加载代码... ``` ### 数据加载时 - 如果你的瓶颈在于数据加载而不是模型推理, - 你可以通过调整 DataLoader 的 num_workers 参数来控制用于数据加载的线程数。 ``` from torch.utils.data import DataLoader, Dataset 假设你有一个自定义的 Dataset 类 dataset = MyDataset() 创建 DataLoader 并设置 num_workers dataloader = DataLoader(dataset, batch_size=32, num_workers=4) ```

 


 


 


 


线性表示
- 一个变量往往是由一组变量的线性组合所表示的 - 这种组合方式就是线性变换的规则T - $\Alpha = T(\Beta)$
平均绝对误差(Mean Absolute Error, MAE)的数学公式如下: ## MAE 公式 $$MAE = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i|$$ ## 公式说明 ### 参数含义 - **$n$**:样本总数 - **$y_i$**:第 $i$ 个样本的真实值(实际值) - **$\hat{y}_i$**:第 $i$ 个样本的预测值 - **$|y_i - \hat{y}_i|$**:第 $i$ 个样本的绝对误差 ### 公式解读 1. 先计算每个样本的**绝对误差**:$|y_i - \hat{y}_i|$ 2. 将所有样本的绝对误差**求和**:$\sum_{i=1}^{n} |y_i - \hat{y}_i|$ 3. 最后**求平均**:除以样本总数 $n$ ## 其他常用形式 ### 加权平均绝对误差 当样本具有不同权重时: $$WMAE = \frac{\sum_{i=1}^{n} w_i |y_i - \hat{y}_i|}{\sum_{i=1}^{n} w_i}$$ 其中 $w_i$ 是第 $i$ 个样本的权重。 ### 样本外MAE(预测用) 对于时间序列预测: $$MAE_{out} = \frac{1}{T} \sum_{t=1}^{T} |y_t - \hat{y}_t|$$ 其中 $T$ 是预测期的长度。 ## 代码实现 ### Python 实现 ```python import numpy as np from sklearn.metrics import mean_absolute_error # 方法1:手动计算 def mae_manual(y_true, y_pred): """ 手动计算MAE """ n = len(y_true) absolute_errors = np.abs(np.array(y_true) - np.array(y_pred)) mae = np.sum(absolute_errors) / n return mae # 方法2:使用sklearn def mae_sklearn(y_true, y_pred): """ 使用sklearn计算MAE """ return mean_absolute_error(y_true, y_pred) # 示例 y_true = [3, -0.5, 2, 7] y_pred = [2.5, 0.0, 2, 8] print(f"手动计算 MAE: {mae_manual(y_true, y_pred):.4f}") print(f"sklearn MAE: {mae_sklearn(y_true, y_pred):.4f}") ``` ## MAE 的特点 ### 优点 1. **直观易懂**:表示平均预测误差的绝对值 2. **单位一致**:与原始数据单位相同 3. **鲁棒性**:对异常值不如MSE敏感 4. **线性惩罚**:所有误差权重相同 ### 缺点 1. **不可微分**:在0点不可导(优化困难) 2. **不放大误差**:无法突出大误差的影响 3. **缺乏方向信息**:无法知道误差是正偏还是负偏 ## 与其他指标的对比 | 指标 | 公式 | 特点 | |------|------|------| | **MAE** | $\frac{1}{n}\sum\|y_i-\hat{y}_i\|$ | 平均绝对误差,线性惩罚 | | **MSE** | $\frac{1}{n}\sum(y_i-\hat{y}_i)^2$ | 均方误差,放大误差 | | **RMSE** | $\sqrt{\frac{1}{n}\sum(y_i-\hat{y}_i)^2}$ | 均方根误差,与原始单位一致 | ## 实际应用场景 MAE 常用于: - **回归问题评估**:房价预测、销量预测 - **时间序列预测**:股票价格、气象数据 - **机器学习模型比较**:评估模型预测精度 - **异常检测**:识别预测偏差大的样本 ### 解释示例 ```python # 实际应用示例 house_prices_true = [300, 350, 280, 400, 320] # 万元 house_prices_pred = [310, 340, 290, 380, 330] # 万元 mae = mean_absolute_error(house_prices_true, house_prices_pred) print(f"房价预测 MAE: {mae:.2f} 万元") print(f"解释:平均每个房子的预测价格与实际价格相差 {mae:.2f} 万元") ```
- 量化之后需要一个衡量的标准,之后才能优化 - 模型输出,与真实标签之间 - 如果使用想减再相加,那么会有正负中和的问题, - 内部本有较大差异,但中和后可能整体差异不明显 - 这实际上涉及三步 - 差异计算 :行为,目标 - 衡量方法 :如何比较,比较的方法 - 微分优化 :一次优化一小步

 


 


 


 


 


模型开发
--- ## 📘 深度学习要点总结 ### 📌 摘要 本总结涵盖了 PyTorch 中张量的基本概念与维度、深度学习编程中的常见问题与封装思想、PyTorch 模型定义的核心方法(`__init__` 与 `forward`),以及深度学习开发中需要注意的形状优先原则和模型保存习惯。 --- ### 📊 要点表格 | 类别 | 要点 | 说明 | |------|------|------| | **张量维度** | 标量 (scalar) | 维度为 0,单个数值 | | | 向量 (vector) | 维度为 1 或 2,一行或一列数值 | | | 矩阵 (matrix) | 二维结构,成行成列 | | | 张量 (tensor) | 高维数据容器,是深度学习的基本数据结构 | | **编程问题与封装** | 数据批量处理 | 数据加载器确保数据以批次形式输入 | | | 层封装 | 将变量定义与处理逻辑封装为“层” | | | 损失函数封装 | 常用损失函数可直接调用,减少重复代码 | | | 优化器封装 | 封装参数更新与梯度清零操作 | | **PyTorch 编程结构** | `__init__` 函数 | 定义超参数和网络层 | | | 类与对象 | 定义模型类,实例化为对象 | | | `forward` 函数 | 定义前向传播逻辑,对象可被调用执行 | | **开发注意事项** | 形状优先 | 张量的形状是调试和建模的第一步 | | | 结果次之 | 模型输出结果是第二步关注点 | | **模型保存习惯** | 结构单独保存 | 网络结构可保存为脚本或配置文件 | | | 参数单独保存 | 模型权重参数通常保存为 `.pth` 文件 | ---
``` class Model: def __init__(self,params): """机器学习框架""" self.params = params def fit(self,X,y): pas def predict(self,X): pass ```

 


 


 


 


参考