深度学习
前期准备
创建文件 PyLog.py
import sysimport threadingfrom datetime import datetime
class PyLog: """极简日志类: 模仿 idea.log 的输出格式""" LEVELS = {"DEBUG": 10, "INFO": 20, "WARN": 30, "ERROR": 40}
def __init__(self, name="root", level="INFO", stream=None): self.name = name # 对应 idea.log 里的 #类别 self.level = self.LEVELS[level.upper()] # 级别过滤阈值 self._stream = stream if stream is not None else sys.stdout self._lock = threading.Lock() # 多线程写日志需要加锁
def _log(self, level_name, message): if self.LEVELS[level_name] < self.level: # 低于阈值 -> 直接丢弃 return now = datetime.now() # 毫秒用逗号分隔, 模仿 log4j/IDEA 风格: 2026-09-13 15:30:01,650 ts = now.strftime("%Y-%m-%d %H:%M:%S.") + f"{now.microsecond // 1000:03d}" line = (f"{ts} " f"[{threading.get_ident():>7}] " # 线程ID, 右对齐定宽 f"{level_name:<5} - " # 级别, 定宽对齐(与ERROR等宽) f"#{self.name} - " f"{message}") with self._lock: # 保证多线程下整行不被打散 self._stream.write(line + "\n") self._stream.flush()
def debug(self, message): self._log("DEBUG", message)
def info(self, message): self._log("INFO", message)
def warn(self, message): self._log("WARN", message)
def error(self, message): self._log("ERROR", message)
# 用法# log = PyLog("com.example.demo", level="INFO")# log.info("服务启动完成")# log.warn("i18n 资源缺失, 使用默认字符串")# log.error("模拟一个异常")
# 写文件: 传 stream 参数即可# f = open("app.log", "w", encoding="utf-8")# file_log = PyLog("com.example.file", level="DEBUG", stream=f)# file_log.debug("DEBUG 级别也能记录")深度学习
是什么? 是机器学习的一个子集,基于人工神经网络特别是包含多个隐藏层的深度神经网络.
作用 深度学习擅长处理非结构化数据,如图像,声音,文本.(NLP,CV,AIGC等)
与机器学习的区别
| 维度 | 传统机器学习 | 深度学习 |
|---|---|---|
| 特征工程 | 依赖人工,需要特征与标签 | 自动学习,不再需要手动提取特征与标签. |
| 数据需求 | 小样本也能工作,数据量增加性能提升有限 | 极度依赖大数据,数据越多效果通常越好 |
| 算力要求 | CPU即可训练 | 通常需要 GPU |
| 可解释性 | 较好,如决策树,线性回归逻辑清晰,数据可溯源 | 较差(常被称为“黑盒”,难以解释内部决策) |
| 适用数据类型 | 结构化表格数据表现优异 | 非结构化数据(图/文/音/视)具有压倒性优势 |
| 典型算法 | SVM, Random Forest, XGBoost, LR | CNN, RNN/LSTM, Transformer, GAN |
PyTorch
深度学习中最常用的框架 PyTorch
安装
# Tencent Mirrorpip install torch torchvision --index-url https://mirrors.cloud.tencent.com/pytorch/cu132主要特征
动态计算图: 代码就是模型,计算图在运行时动态构建.可以通过if/else,for等控制流来定义神经网络,调试时可以随时打断点.( 正向传播,数据从输入到输出)
自动微分模块: PyTorch会自动根据动态生成的计算图,反向推算出每个参数的梯度,从而使得模型参数得到更新.( 反向传播,数据从输出到指定参数的输入)
PyTorch中的张量
是什么? PyTorch中的张量就是元素为同一种类型的多维矩阵.
张量是PyTorch中的核心数据类型,支持各种张量子类型.通常一维张量称为向量/矢量(vector),二维张量称为矩阵(matrix).
张量
声明张量主要有三种方式
方式一: 使用torch.tensor将原有数据转换为张量,支持转换的类型有 标量, 元组,列表,numpy数组.
方式二: 使用torch.Tensor创建指定形状的张量,同时该方法支持方式一的操作.
方式三: 使用torch.IntTensor,torch.FloatTensor等创建指定形状和类型的张量
# 创建张量有三种方式# torch.tensor(): 将指定数据转换为张量# torch.Tensor(): 根据形状创建张量# torch.IntTensor(), torch.FloatTensor(), torch.DoubleTensor(): 根据形状与类型,创建指定形状的张量.
# torch.tensor(<data>): 根据指定数据创建张量,如标量,list,numpy.pylog.info( f"======================================== 创建张量: torch.tensor ========================================")# 将标量转换为张量out = torch.tensor(5, )print_tensor("标量转换为张量", out)
# 将列表转换为张量out = torch.tensor([1, 2, 3])print_tensor("列表转换为张量", out)# 将元组转换为张量out = torch.tensor((1, 2, 3))print_tensor("元组转换为张量", out)
# 将numpy数组转换为张量out = torch.tensor(np.array([1, 2, 3]))print_tensor("numpy 数组转换为张量", out)初始化(创建)常用张量的方法
将已有张量初始化为0或填充指定值的方法
tensor_var.zero_(): 使用0填充已有张量.
tensor_var.fill_(<value>): 使用指定值填充已有张量.
pylog.info( f"======================================== 创建张量: torch.Tensor ========================================")# 根据指定形状创建张量# 使用Tensor()创建张量后,会有残留值(未初始化),需要进行初始化.# 这个方法同样支持.tensor()的格式来创建张量,即将其它类型变量转换为张量.# 这里是先创建,再进行初始化,而不是在创建时同时初始化,如需要在创建时初始化,需要看创建全0或全1张量的方法tensor_var = torch.Tensor(3, 4)print_tensor("创建指定形状的新张量", tensor_var)tensor_var.zero_() # 将未初始化的张量,所有元素初始化为0,原地修改.print_tensor("填充0值的新张量", tensor_var)tensor_var.fill_(19) # 将未初始化的张量,所有元素初始化为指定值,原地修改.print_tensor("填充指定值的新张量", tensor_var)创建线性张量
torch.arange(<start>,<end>,<step>,<dtype>): 创建等差数列张量.左闭右开
torch.linspace(<start>,<end>,<num>,<dtype>): 创建等间隔张量.左闭右闭
pylog.info( f"======================================== 创建线性张量的方法 ========================================")
# 提示: 这里的创建等差,等间隔,随机等张量,与numpy的数组完全相同,没有区别.# 创建等差数列张量,左闭右开,step为步长# torch.arange(<start>,<end>,<step>,<dtype>)out = torch.arange(0, 13, 3, dtype=torch.float32)print_tensor("创建等差数列张量", out)
# 创建等间隔张量,左闭右闭,num为张量中元素个数# torch.linspace(<start>,<end>,<num>,<dtype>)# [ 0.0000, 1.1111, 2.2222, 3.3333, 4.4444, 5.5556, 6.6667, 7.7778, 8.8889, 10.0000]out = torch.linspace(0, 10, 10, dtype=torch.float32)print_tensor("创建等间隔张量", out)创建随机生成的张量
torch.rand(<shape>): 创建均匀分布的张量.
torch.randn(<shape>): 创建标准正态分布的张量.
torch.randint(<low>,<high>,<shape>): 创建随机均匀分布整数的张量.
pylog.info( f"======================================== 创建随机生成数据的张量 ========================================")pylog.info(f"随机数种子: {torch.initial_seed()}")# 指定随机数torch.manual_seed(13)# 创建均匀分布的张量,shape为张量形状,数值范围[0,1)# torch.rand(<shape>)rand_tensor_var = torch.rand(300, 500)print_tensor("创建均匀分布的张量", rand_tensor_var)
# 创建标准正态分布(均值0,方差1)的张量,shape为张量形状# torch.randn(<shape>)randn_tensor_var = torch.randn(300, 500)print_tensor("创建标准正态分布张量", randn_tensor_var)
# 创建指定范围整型张量,shape为张量形状,左闭右开# torch.randint(<low>,<high>,<shape>)randint_tensor_var = torch.randint(0, 100, (300, 500))print_tensor("创建指定范围整型张量", randint_tensor_var)
# 画图,为了运行速度,暂不运行,如有需要可解除注释# rand_tensor_var = rand_tensor_var.flatten()# plt.xlim(-0.1, 1.1)# plt.hist(rand_tensor_var)# plt.show()
# randn_tensor_var = randn_tensor_var.flatten()# 修改随机数分布的均值和标准差# randn_tensor_var = randn_tensor_var * 2 + 10
# plt.xlim(-22, 22)# plt.hist(randn_tensor_var, bins=1000)# plt.show()
# randint_tensor_var = randint_tensor_var.flatten()# plt.xlim(-10, 100)# plt.hist(randint_tensor_var, bins=100)# plt.show()创建全0或全1或指定值填充的张量
torch.zeros(<shape>): 创建指定形状的张量并使用0填充.
torch.ones(<shape>): 创建指定形状的张量并使用1填充.
torch.full(<shape>,<value>): 创建指定形状的张量并使用指定值填充.
pylog.info( f"======================================== 创建全0全1数据的张量 ========================================")# 根据指定形状创建张量int32_tensor_var = torch.IntTensor(2, 3) # int32
# 创建全0张量zero_tensor_var = torch.zeros(3, 5)print_tensor("全0张量", zero_tensor_var)
# 创建全0张量,但大小跟随其它张量zero_tensor_var2 = torch.zeros_like(int32_tensor_var)print_tensor("全0张量2", zero_tensor_var2)# 创建全1张量one_tensor_var = torch.ones(3, 5)print_tensor("全1张量", one_tensor_var)
# 创建全1张量,但大小跟随其它张量one_tensor_var2 = torch.ones_like(int32_tensor_var)print_tensor("全1张量2", one_tensor_var2)
# 创建指定值的张量# 与fill的区别是: full是从0开始创建新张量,然后再填充为指定值,而fill是填充已有的张量.full_tensor_var = torch.full((3, 5), 10)print_tensor("指定值张量", full_tensor_var)
# 创建指定值的张量,但大小跟随其它张量full_tensor_var2 = torch.full_like(int32_tensor_var, 100.3)print_tensor("指定值张量2", full_tensor_var2)本节所有代码整理
import torchimport numpy as npimport matplotlib.pyplot as pltfrom Util.PyLog import PyLog
global pylog
def main(): # 创建张量有三种方式 # torch.tensor(): 将指定数据转换为张量 # torch.Tensor(): 根据形状创建张量 # torch.IntTensor(), torch.FloatTensor(), torch.DoubleTensor(): 根据形状与类型,创建指定形状的张量.
# torch.tensor(<data>): 根据指定数据创建张量,如标量,list,numpy. pylog.info( f"======================================== 创建张量: torch.tensor ========================================") # 将标量转换为张量 out = torch.tensor(5, ) print_tensor("标量转换为张量", out)
# 将列表转换为张量 out = torch.tensor([1, 2, 3]) print_tensor("列表转换为张量", out) # 将元组转换为张量 out = torch.tensor((1, 2, 3)) print_tensor("元组转换为张量", out)
# 将numpy数组转换为张量 out = torch.tensor(np.array([1, 2, 3])) print_tensor("numpy 数组转换为张量", out)
pylog.info( f"======================================== 创建张量: torch.Tensor ========================================") # 根据指定形状创建张量 # 使用Tensor()创建张量后,会有残留值(未初始化),需要进行初始化. # 这个方法同样支持.tensor()的格式来创建张量,即将其它类型变量转换为张量. # 这里是先创建,再进行初始化,而不是在创建时同时初始化,如需要在创建时初始化,需要看创建全0或全1张量的方法 tensor_var = torch.Tensor(3, 4) print_tensor("创建指定形状的新张量", tensor_var) tensor_var.zero_() # 将未初始化的张量,所有元素初始化为0,原地修改. print_tensor("填充0值的新张量", tensor_var) tensor_var.fill_(19) # 将未初始化的张量,所有元素初始化为指定值,原地修改. print_tensor("填充指定值的新张量", tensor_var)
pylog.info( f"======================================== 创建指定元素类型的张量 ========================================") # int16_tensor_var = torch.ShortTensor(2, 3) # int16 int32_tensor_var = torch.IntTensor(2, 3) # int32 int64_tensor_var = torch.LongTensor(2, 3) # int64 # <tensor_var>.shape: 用于查看张量的形状 # <tensor_var>.dtype: 用于查看张量的数据类型 print_tensor("创建int16类型的张量", int16_tensor_var) print_tensor("创建int32类型的张量", int32_tensor_var) print_tensor("创建int64类型的张量", int64_tensor_var)
uint8_tensor_var = torch.ByteTensor(2, 3) # uint8 print_tensor("创建uint8类型的张量", uint8_tensor_var)
float16_tensor_var = torch.HalfTensor(2, 3) # float16 float32_tensor_var = torch.FloatTensor(2, 3) # float32 float64_tensor_var = torch.DoubleTensor(2, 3) # float64 print_tensor("创建float16类型的张量", float16_tensor_var) print_tensor("创建float32类型的张量", float32_tensor_var) print_tensor("创建float64类型的张量", float64_tensor_var) pylog.info( f"======================================== 创建线性张量的方法 ========================================")
# 提示: 这里的创建等差,等间隔,随机等张量,与numpy的数组完全相同,没有区别. # 创建等差数列张量,左闭右开,step为步长 # torch.arange(<start>,<end>,<step>,<dtype>) out = torch.arange(0, 13, 3, dtype=torch.float32) print_tensor("创建等差数列张量", out)
# 创建等间隔张量,左闭右闭,num为张量中元素个数 # torch.linspace(<start>,<end>,<num>,<dtype>) # [ 0.0000, 1.1111, 2.2222, 3.3333, 4.4444, 5.5556, 6.6667, 7.7778, 8.8889, 10.0000] out = torch.linspace(0, 10, 10, dtype=torch.float32) print_tensor("创建等间隔张量", out)
pylog.info( f"======================================== 创建随机生成数据的张量 ========================================") pylog.info(f"随机数种子: {torch.initial_seed()}") # 指定随机数 torch.manual_seed(13) # 创建均匀分布的张量,shape为张量形状,数值范围[0,1) # torch.rand(<shape>) rand_tensor_var = torch.rand(300, 500) print_tensor("创建均匀分布的张量", rand_tensor_var)
# 创建标准正态分布(均值0,方差1)的张量,shape为张量形状 # torch.randn(<shape>) randn_tensor_var = torch.randn(300, 500) print_tensor("创建标准正态分布张量", randn_tensor_var)
# 创建指定范围整型张量,shape为张量形状,左闭右开 # torch.randint(<low>,<high>,<shape>) randint_tensor_var = torch.randint(0, 100, (300, 500)) print_tensor("创建指定范围整型张量", randint_tensor_var)
# 画图,为了运行速度,暂不运行,如有需要可解除注释 # rand_tensor_var = rand_tensor_var.flatten() # plt.xlim(-0.1, 1.1) # plt.hist(rand_tensor_var) # plt.show()
# randn_tensor_var = randn_tensor_var.flatten() # 修改随机数分布的均值和标准差 # randn_tensor_var = randn_tensor_var * 2 + 10
# plt.xlim(-22, 22) # plt.hist(randn_tensor_var, bins=1000) # plt.show()
# randint_tensor_var = randint_tensor_var.flatten() # plt.xlim(-10, 100) # plt.hist(randint_tensor_var, bins=100) # plt.show()
pylog.info( f"======================================== 创建全0全1数据的张量 ========================================") # 根据指定形状创建张量 int32_tensor_var = torch.IntTensor(2, 3) # int32
# 创建全0张量 zero_tensor_var = torch.zeros(3, 5) print_tensor("全0张量", zero_tensor_var)
# 创建全0张量,但大小跟随其它张量 zero_tensor_var2 = torch.zeros_like(int32_tensor_var) print_tensor("全0张量2", zero_tensor_var2) # 创建全1张量 one_tensor_var = torch.ones(3, 5) print_tensor("全1张量", one_tensor_var)
# 创建全1张量,但大小跟随其它张量 one_tensor_var2 = torch.ones_like(int32_tensor_var) print_tensor("全1张量2", one_tensor_var2)
# 创建指定值的张量 # 与fill的区别是: full是从0开始创建新张量,然后再填充为指定值,而fill是填充已有的张量. full_tensor_var = torch.full((3, 5), 10) print_tensor("指定值张量", full_tensor_var)
# 创建指定值的张量,但大小跟随其它张量 full_tensor_var2 = torch.full_like(int32_tensor_var, 100.3) print_tensor("指定值张量2", full_tensor_var2)
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 :param label: 标签,用于显示简短分隔信息 :param tensor: 张量,数据源 :return: None """ # __init__ # __str__ pylog.debug(f"==================== [{label}]\t====================") pylog.debug(f"[{label}] tensor.shape: {tensor.shape}") pylog.debug(f"[{label}] tensor.dtype: {tensor.dtype}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.info("Project Start") main() pylog.info("Project End")类型转换
类型转换会丢失精度或因为符号问题出错,需要注意
类型转换有三种方式:
tensor_var.to(<torch.<数据类型>): 推荐使用
tensor_var.type(<torch.<数据类型>)<旧版api>旧版api>
tensor_var.<数据类型>()<旧版api>旧版api>
float转换为int时,是截断而非四舍五入.
# 支持转换的类型有# <tensor_var>.half()/double()/float()/byte()/short()/int()/long()/char()/bool()/bfloat16()# 注意,float转int时,是截断.# int,float转uint时,如果是负数,会出现问题,谨慎转换.# char类型是有符号的8位整数,而byte类型是无符号的整数,都是1字节长度.# 另外这些方法都是返回一个新的张量而非在原地修改.只有一种例外情况,原张量类型与目标张量类型相同时,将会返回原张量.| 方法 | 等价写法 | 目标 dtype | 说明 |
|---|---|---|---|
.byte() | .to(torch.uint8) | uint8 | 无符号 8 位整数(0~255) |
.char() | .to(torch.int8) | int8 | 有符号 8 位整数(-128~127) |
.short() | .to(torch.int16) | int16 | 有符号 16 位整数 |
.int() | .to(torch.int32) | int32 | 有符号 32 位整数 |
.long() | .to(torch.int64) | int64 | 有符号 64 位整数(PyTorch 默认整数类型) |
.float() | .to(torch.float32) | float32 | 32 位浮点(PyTorch 默认浮点类型) |
.double() | .to(torch.float64) | float64 | 64 位浮点 |
.half() | .to(torch.float16) | float16 | 16 位半精度浮点 |
.bfloat16() | .to(torch.bfloat16) | bfloat16 | Brain Float 16 |
.bool() | .to(torch.bool) | bool | 布尔类型 |
import torch
from Util.PyLog import PyLog
global pylog
def main(): pylog.info( f"======================================== 张量类型转换 ========================================")
float32_tensor_var = torch.randn(2, 2, dtype=torch.float32) # float32
print_tensor("创建float32类型的张量", float32_tensor_var)
int32_tensor_var = float32_tensor_var.int() print_tensor("方式一: 使用tensor_var.<指定类型>()", int32_tensor_var)
int32_tensor_var = float32_tensor_var.type(torch.int32) print_tensor("方式二: 使用tensor_var.type(torch.<指定类型>),此方法为旧式接口,官方推荐使用.to()", int32_tensor_var)
int32_tensor_var = float32_tensor_var.to(torch.int32) print_tensor("方式三: 使用tensor_var.to(torch.<指定类型>),此方法最为推荐使用,可以同时转换类型和送入GPU", int32_tensor_var)
# 支持转换的类型有 # <tensor_var>.half()/double()/float()/byte()/short()/int()/long()/char()/bool()/bfloat16() # 注意,float转int时,是截断. # int,float转uint时,如果是负数,会出现问题,谨慎转换. # char类型是有符号的8位整数,而byte类型是无符号的整数,都是1字节长度. # 另外这些方法都是返回一个新的张量而非在原地修改.只有一种例外情况,原张量类型与目标张量类型相同时,将会返回原张量.
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 :param label: 标签,用于显示简短分隔信息 :param tensor: 张量,数据源 :return: None """ pylog.debug(f"==================== [{label}]\t====================") pylog.debug(f"[{label}] tensor.shape: {tensor.shape}") pylog.debug(f"[{label}] tensor.dtype: {tensor.dtype}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.info("Project Start") main() pylog.info("Project End")张量计算调用GPU
CPU与GPU的对比
从GPU取回数据
| 方法 | 作用 |
|---|---|
torch.tensor(...,device=device) | 将张量送入GPU |
tensor_var.cpu() | 将张量从GPU取回内存. |
tensor_var.to("cuda”)` | 将已有张量送入GPU,并返回一个新的张量对象 |
tensor_var.cuda() | 等价于上面的,但无GPU时会报错. |
import torchfrom Util.PyLog import PyLog
def main(): device = torch.device("cuda" if torch.cuda.is_available() else "cpu") pylog.info(f"当前设备: {device}")
# 1. 送入 GPU a = torch.tensor([[1.0, 2.0], [3.0, 4.0]], device=device) b = torch.tensor([[5.0, 6.0], [7.0, 8.0]], device=device)
# 2. 计算(发生在 GPU 上) c = torch.matmul(a, b)
# 3. 取出结果(搬回 CPU) c_cpu = c.cpu()
# 4. 打印结果 pylog.info(f"计算结果:\n{c_cpu}")
if __name__ == '__main__': pylog = PyLog(level="DEBUG") main()注意
GPU支持全部张量计算
不过所有参与运算的张量必须位于同一个设备,否则会报错.
在GPU上运算的结果会保留在GPU上.
张量与NumPy数组转换
张量 -> NumPy数组
使用tensor_var.numpy()方法可以将张量转换为numpy数组,默认共享内存.
使用tensor_var.numpy().copy()方法可以进行深拷贝,完全脱离关系,不共享内存.
注意:
GPU上的张量,是不能直接转换为NumPy数组的,需要先使用<tensor_var>.cpu()方法复制到内存中再转换.
带有梯度的张量,转换为NumPy数组时会报错,需要先去掉梯度.detach(),使用过该方法后,张量与数组共享内存,只是没有了梯度.
import torch
from Util.PyLog import PyLog
global pylog
def main(): pylog.debug( f"======================================== 张量转换为NumPy数组 ========================================") tensor_var = torch.randint(0, 10, (3, 5)) # print_tensor("创建张量", tensor_var) # 将张量转换为NumPy数组,共享内存 numpy_array = tensor_var.numpy() # print_tensor("张量转换为NumPy数组", numpy_array)
pylog.debug("尝试修改numpy数组的值,tensor_var的值也会被修改") numpy_array[1, 1] = 1000 print_tensor("查看numpy_array", numpy_array) print_tensor("查看tensor_var", tensor_var)
pylog.debug("使用.copy()方法,创建副本,与原张量脱离关系") # 将张量转换为NumPy数组,不共享内存 numpy_array_copy = tensor_var.numpy().copy() numpy_array_copy[1] = 2 print_tensor("查看numpy_array_copy", numpy_array_copy) print_tensor("查看tensor_var", tensor_var)
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 :param label: 标签,用于显示简短分隔信息 :param tensor: 张量,数据源 :return: None """ # __init__ # __str__ pylog.debug(f"==================== [{label}]\t====================") pylog.debug(f"[{label}] type: {type(tensor)}") pylog.debug(f"[{label}] tensor.shape: {tensor.shape}") pylog.debug(f"[{label}] tensor.dtype: {tensor.dtype}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.debug("Project Start") main() pylog.debug("Project End")NumPy数组 -> 张量
使用torch.from_numpy(<numpy数组>),可以将NumPy数组转换为张量,共享内存.
使用torch.from_numpy(<numpy数组>).clone(),可以将NumPy数组转换为张量,不共享内存.
GPU上的tensor_var是不能与numpy共享的.
import torchimport numpy as npfrom Util.PyLog import PyLog
global pylog
def main(): pylog.debug( f"======================================== NumPy数组转换为张量 ========================================" )
# __init__ numpy_array = np.array([1, 3, 5, 7, 9]) print_ndarray("创建 NumPy 数组", numpy_array)
# pylog.debug("共享内存方式将NumPy数组转换为张量") # tensor_var = torch.from_numpy(numpy_array) # print_tensor("NumPy转换为张量,共享内存", tensor_var)
pylog.debug("不共享内存方式将NumPy数组转换为张量") tensor_var = torch.from_numpy(numpy_array).clone() print_tensor("NumPy转换为张量,不共享内存", tensor_var)
pylog.debug("修改数组的值,张量值的变化情况") tensor_var = torch.from_numpy(numpy_array) numpy_array[-1] = 1000 print_ndarray("查看numpy_array", numpy_array) print_tensor("查看tensor_var", tensor_var)
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 """ pylog.debug(f"==================== [{label}] ====================") pylog.debug(f"[{label}] type: {type(tensor)}") pylog.debug(f"[{label}] tensor.shape: {tensor.shape}") pylog.debug(f"[{label}] tensor.dtype: {tensor.dtype}") pylog.debug("[详细数据]") pylog.debug(tensor)
def print_ndarray(label: str, ndarray) -> None: """ 格式化打印 NumPy 数组 """ pylog.debug(f"==================== [{label}] ====================") # pylog.debug(f"[{label}] type: {type(ndarray)}") # pylog.debug(f"[{label}] ndarray.shape: {ndarray.shape}") # pylog.debug(f"[{label}] ndarray.dtype: {ndarray.dtype}") # pylog.debug(f"[{label}] ndarray.data_ptr(): {ndarray.__array_interface__['data'][0]}") pylog.debug("[详细数据]") pylog.debug(ndarray)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.debug("Project Start") main() pylog.debug("Project End")张量转换为Python对象
主要涉及到两个方法
tensor_var.item(): 将一个元素的张量(标量张量)取出,转换为Python对象.
tensor_var.tolist()<将>将>多个元素的张量取出,转换为Python列表.
import torch
from Util.PyLog import PyLog
global pylog
def main(): pylog.debug( f"======================================== 一个和多个张量转换为Python对象 ========================================" ) pylog.debug("一个元素的张量,转换为Python对象,也即取出这个元素") tensor_var = torch.rand(1) print_tensor("一个元素的张量,转换为Python对象", tensor_var) # item() 用于取出单个元素 pylog.debug(tensor_var.item())
pylog.debug("将含有多个元素的张量,转换为Python对象,也即取出所有元素") tensor_var = torch.rand(5) print_tensor("多个元素的张量,转换为Python对象", tensor_var) # tolist() 用于将张量转换为列表 pylog.debug(tensor_var.tolist())
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 """ pylog.debug(f"==================== [{label}] ====================") pylog.debug(f"[{label}] type: {type(tensor)}") pylog.debug(f"[{label}] tensor.shape: {tensor.shape}") pylog.debug(f"[{label}] tensor.dtype: {tensor.dtype}") pylog.debug("[详细数据]") pylog.debug(tensor)
def print_ndarray(label: str, ndarray) -> None: """ 格式化打印 NumPy 数组 """ pylog.debug(f"==================== [{label}] ====================") # pylog.debug(f"[{label}] type: {type(ndarray)}") # pylog.debug(f"[{label}] ndarray.shape: {ndarray.shape}") # pylog.debug(f"[{label}] ndarray.dtype: {ndarray.dtype}") # pylog.debug(f"[{label}] ndarray.data_ptr(): {ndarray.__array_interface__['data'][0]}") pylog.debug("[详细数据]") pylog.debug(ndarray)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.debug("Project Start") main() pylog.debug("Project End")张量的基本运算
张量支持加减乘除四则运算,还支持取负运算.
加法: .add(),.add_(),+
减法: .sub().sub_(),-
乘法: .mul().mul_(),*
除法: .div().div_(),/
取负: .neg().neg_(),-
取余: .remainder(),remainder_(),%
其中,还有下划线的方法,会在原地修改.
import torchfrom Util.PyLog import PyLog
global pylog
def main(): pylog.info( f"======================================== 张量基本运算 ========================================" )
# ===================== 创建两个张量 ===================== a = torch.tensor([1, 2, 3], dtype=torch.float32) b = torch.tensor([4, 5, 6], dtype=torch.float32)
print_tensor("张量 A", a) print_tensor("张量 B", b)
# 方式一: 使用运行符 print_tensor("A+B", a + b) print_tensor("A-B", a - b) print_tensor("A*B", a * b) print_tensor("A/B", a / b) print_tensor("-b", -b)
# 方式二: 使用torch提供的方法 print_tensor("torch.add(a,b)", torch.add(a, b)) print_tensor("torch.sub(a,b)", torch.sub(a, b)) print_tensor("torch.mul(a,b)", torch.mul(a, b)) print_tensor("torch.div(a,b)", torch.div(a, b)) print_tensor("torch.neg(b)", torch.neg(b))
# 方式三: 使用tensor的方法 print_tensor("a.add(b)", a.add(b)) print_tensor("a.sub(b)", a.sub(b)) print_tensor("a.mul(b)", a.mul(b)) print_tensor("a.div(b)", a.div(b)) print_tensor("b.neg()", b.neg())
# 方式四: 使用tensor的方法,原地修改 a.add_(b) print_tensor("a.add_(b)", a) a.sub_(b) print_tensor("a.sub_(b)", a) a.mul_(b) print_tensor("a.mul_(b)", a) a.div_(b) print_tensor("a.div_(b)", a) b.neg_() print_tensor("b.neg_()", b)
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 """ pylog.info(f"==================== [{label}] ====================") pylog.info(f"[{label}] type: {type(tensor)}") pylog.info(f"[{label}] tensor.shape: {tensor.shape}") pylog.info(f"[{label}] tensor.dtype: {tensor.dtype}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.info("Project Start") main() pylog.info("Project End")张量乘法和矩阵乘法
矩阵乘法
加法: .matmul(),.matmul_(),@
import torchfrom Util.PyLog import PyLog
global pylog
def main(): pylog.info( f"======================================== 张量基本运算 ========================================" )
# a = torch.FloatTensor([1, 2, 3, 4]) b = torch.FloatTensor([6, 7, 8, 9])
print_tensor("张量 A", a) print_tensor("张量 B", b)
# 乘法乘法 # 方式一: 使用运算符 print_tensor("a*b", a * b) # 方式二: 使用torch提供的方法 print_tensor("torch.mul(a, b)", torch.mul(a, b)) # 方式三: 使用tensor提供的方法 print_tensor("a.mul(b)", a.mul(b))
# __init__ a = torch.FloatTensor([[1, 2], [3, 4]]) b = torch.FloatTensor([[6, 7], [8, 9]])
# 矩阵乘法 # 方式一: 使用运算符 print_tensor("a@b", a @ b)
# 方式二: 使用torch提供的方法 print_tensor("torch.matmul(a, b)", torch.matmul(a, b))
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 """ pylog.info(f"==================== [{label}] ====================") # pylog.info(f"[{label}] type: {type(tensor)}") # pylog.info(f"[{label}] tensor.shape: {tensor.shape}") # pylog.info(f"[{label}] tensor.dtype: {tensor.dtype}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.info("Project Start") main() pylog.info("Project End")张量的运算方法
逐元素计算
| 方法 | 等价写法 | 作用 | 示例结果 |
|---|---|---|---|
a.floor() | torch.floor(a) | 向下取整(往 −∞) | -1.2→-2.0,0.499→0.0,-0.499→-1.0 |
a.ceil() | torch.ceil(a) | 向上取整(往 +∞) | -1.2→-1.0,0.499→1.0,3.5→4.0 |
a.round() | torch.round(a) | 四舍六入五取偶(银行家舍入) | 3.5→4.0,4.5→4.0,0.499→0.0 |
a.abs() | torch.abs(a) | 取绝对值 | [-1.2,0.499,...]→[1.2,0.499,...] |
a.sqrt() | torch.sqrt(a) | 开平方 | 负数得 nan |
a.pow(2) | a ** 2 | 幂运算 | [-1.2,...]²→[1.44,...] |
a.exp() | torch.exp(a) | 以 e 为底的指数 | — |
a.log() | torch.log(a) | 自然对数 ln | 负数得 nan |
a.log2() | torch.log2(a) | 以 2 为底 | — |
a.log10() | torch.log10(a) | 以 10 为底 | — |
b % 2 | torch.remainder(b, 2) | 对标量取余 | [11,12,13]%2→[1,0,1] |
b % c | torch.remainder(b, c) | 对张量逐元素取余 | [11,12,13]%[5,3,2]→[1,0,1] |
注意:
sqrt / log / log2 / log10 / exp / pow都要求浮点类型,IntTensor上调用会直接报错。%运算符映射到torch.remainder(余数符号跟除数一致),若想让余数符号跟被除数一致,用torch.fmod()——正数时两者相同,有负数时结果不同。
归纳统计运算
以 a.shape == (2, 3) 为例:
| 方法 | 作用 | 结果形状 | 说明 |
|---|---|---|---|
a.mean() | 全局均值 | () 标量 | 全元素平均 |
a.mean(dim=0) | 列均值 | (3,) | 压缩行维度,每列一个均值 |
a.mean(dim=1) | 行均值 | (2,) | 压缩列维度,每行一个均值 |
a.mean(dim=(0,1)) | 多维同时归约 | () 标量 | 多列均值 |
a.sum() | 全局求和 | () 标量 | — |
a.sum(dim=0) | 列求和 | (3,) | — |
a.sum(dim=1) | 行求和 | (2,) | — |
a.max() | 全局最大值 | () 标量 | — |
a.max(dim=0) | 每列最大值 | 命名元组 | 见下节 |
a.max(dim=1) | 每行最大值 | 命名元组 | 见下节 |
记忆口诀:
dim=k就是消掉第 k 维。消掉谁,“沿谁聚合”就成立——dim=0消掉行 → 剩下的是按列统计。
最值索引
以 a.shape == (2, 3) 为例:
| 表达式 | 含义 | 索引指向 |
|---|---|---|
a.max(dim=0).values | 每列最大值 | shape (3,) |
a.max(dim=0).indices | 每列最大值所在的行号 | shape (3,) |
a.max(dim=1).values | 每行最大值 | shape (2,) |
a.max(dim=1).indices | 每行最大值所在的列号 | shape (2,) |
import torchfrom Util.PyLog import PyLog
global pylog
def main(): pylog.info( f"======================================== 张量运算方法 ========================================" )
# a = torch.FloatTensor([-1.2, 0.499, 3.5, 4.5, -0.499])
# 向下取整 result1 = a.floor() print_tensor("向下取整", result1)
# 向上取整 result2 = a.ceil() print_tensor("向上取整", result2)
# 四舍六入五靠偶 result3 = a.round() print_tensor("四舍六入五靠偶", result3)
# 取绝对值 result4 = a.abs() print_tensor("取绝对值", result4)
# 取平方根 result5 = a.sqrt() print_tensor("取平方根", result5)
# 取幂 result6 = a.pow(2) print_tensor("取幂", result6)
# 取对数 result7 = a.log() print_tensor("取对数", result7) # 取余数 b = torch.IntTensor([11, 12, 13]) c = torch.IntTensor([5, 3, 2]) result8 = b % 2 result9 = b % c print_tensor("b%2", result8) print_tensor("b%c", result9)
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 """ pylog.info(f"==================== [{label}] ====================") # pylog.info(f"[{label}] type: {type(tensor)}") # pylog.info(f"[{label}] tensor.shape: {tensor.shape}") # pylog.info(f"[{label}] tensor.dtype: {tensor.dtype}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.info("Project Start") main() pylog.info("Project End")索引操作
索引有多种类型,具体可查表
| 表达式 | 索引类型 | 索引成分 | 结果 shape | 维度变化 |
|---|---|---|---|---|
a[1, :] | Basic | 整数索引 + 切片索引 | (5,) | 维度消除 ×1 |
a[1] | Basic | 整数索引(后续维度由 Ellipsis 补全) | (5,) | 维度消除 ×1 |
a[:, 2] | Basic | 切片索引 + 整数索引 | (5,) | 维度消除 ×1 |
a[:1, :] | Basic | 纯切片索引 | (1, 5) | 维度保持 |
a[:, 1:3] | Basic | 纯切片索引 | (5, 2) | 维度保持 |
a[1, 2] | Basic | 完全整数索引 | () 标量 | 维度消除 ×2 |
a[1:2, 2] | Basic | 切片索引 + 整数索引 | (1,) | 维度消除 ×1 |
a[..., 2] | Basic | 省略号索引 + 整数索引 | (5,) | 维度消除 ×1 |
a[None, :] | Basic | 新增轴索引 + 切片索引 | (1, 5, 5) | 升维 ×1 |
a[[0, 2]] | Advanced | 整数数组索引 | (2, 5) | 维度保持(拷贝) |
a[a > 5] | Advanced | 布尔掩码索引 | (n,) | 展平为 1 维(拷贝) |
有一句话要记一下:
整数索引i与切片索引i+1返回的值相同,唯一区别就是整数索引返回对象的维数减1.
一句话记忆:整数吃维度,None 吐维度,切片只改长度不碰维度
维度计算公式
result_ndim = 原 ndim − 整数索引个数 + None 个数
布尔索引
创建布尔索引:
方式一: 手动创建,直接torch.BoolTensor([Boolean...])
方式二: 自动创建,对tensor_var使用比较运算符
使用布尔索引:
可以使用布尔索引
- 筛选元素(展平为一维)
- 筛选行/列(二维)
- 筛选张量,不降维
import torchfrom Util.PyLog import PyLog
global pylog
def main(): pylog.info( f"======================================== 布尔索引 ========================================" ) # 设置随机数种子 torch.manual_seed(14) a = torch.randint(0, 10, (5, 5), dtype=torch.float32) print_tensor("张量 A", a)
# 基本筛选用法 select_row = torch.BoolTensor([False, False, True, True, False]) print_tensor("筛选行:", a[:, select_row]) select_column = torch.BoolTensor([True, False, True, True, False]) print_tensor("筛选列:", a[:, select_column])
# 1. 构造掩码:逐元素比较,得到与原张量同形状的 bool 张量,然后筛选后只得到一个一维的张量. mask = a > 5 print_tensor("构造布尔索引张量", mask) print_tensor("a[mask]", a[mask])
# 2. 布尔索引取值: 展平成 1 维,只保留 True 位置的元素(返回拷贝) # 有两种写法,一种是直接将布尔索引传入到原张量中,另外一种方式是使用torck.masked_select(<原张量>,<布尔索引张量>) # print_tensor("a[mask] 筛选结果,展平,只有元素", a[mask]) # 下面这个是根据第0列的数据,筛选整行,要求第0列的数据大于5,满足条件的只有第1行.所以只会返回一行. print_tensor("a[:, 0]", a[:, 0] > 5) print_tensor("a[条件, :]", a[a[:, 0] > 5, :])
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 """ pylog.info(f"==================== [{label}] ====================") pylog.info(f"tensor.shape: {tensor.shape}") pylog.info(f"tensor.ndim: {tensor.ndim}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.info("Project Start") main() pylog.info("Project End")多维索引
简单行索引在高维张量中的应用
import torchfrom Util.PyLog import PyLog
global pylog
def main(): pylog.info( f"======================================== 多维索引 ========================================" ) # 设置随机数种子 torch.manual_seed(14) a = torch.randint(0, 10, (3, 4, 5), dtype=torch.float32) print_tensor("张量 A", a)
# 获取第0维的第1个张量数据,是一个二维的张量,因为1是一个整数索引,导致降维了. # print_tensor("a[1,:,:]",a[1,:,:])
# 获取第1维的第3个张量数据 print_tensor("a[:,2,:]", a[:, 2, :])
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 """ pylog.info(f"==================== [{label}] ====================") pylog.info(f"tensor.shape: {tensor.shape}") pylog.info(f"tensor.ndim: {tensor.ndim}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.info("Project Start") main() pylog.info("Project End")维度操作
形状操作主要包括
tensor_var.reshape(<形状>): 改变形状,带自动物理排序功能.
tensor_var.flatten(): 将张量展平为一维.
tensor_var.squeeze(): 清除所有维度大小为1的维度,压缩维度.
tensor_var.unsqueeze(<维度索引>): 在指定维度位置插入一个维度大小为1的维度.扩展维度.
tensor_var.transpos(<维度索引1>,<维度索引2>): 通过维度索引,互换两个维度
permute(<维度索引列表>): 通过维度索引,重新排列维度顺序
view(<维度索引列表>),用于改变张量尺寸,只能用于连续张量.
-
使用
is_contiguous()判断张量是否连续 -
使用
contiguous()将张量转换为连续张量(重新创建新内存地址,按照当前逻辑顺序将元素重新存储.) -
提示:
reshape=is_contiguous+contiguous+view
import torchfrom Util.PyLog import PyLog
global pylog
def main(): pylog.info( f"======================================== 形状操作 ========================================" ) # 设置随机数种子 torch.manual_seed(14) a = torch.randint(0, 10, (2, 3, 4), dtype=torch.int32)
print_tensor("张量 A", a) # reshape允许使用-1来自动计算缺失维度大小 reshaped_tensor = a.reshape(3, -1, 4) print_tensor("reshape 后", reshaped_tensor)
# flatten 展平成一维 flatten_tensor = a.flatten() print_tensor("flatten 后", flatten_tensor)
# ============================ b = torch.randint(0, 10, (2, 3, 1, 5), dtype=torch.int32) print_tensor("张量 B", b) # squeeze 降维,取消张量中所有尺寸为1的维度
squeeze_tensor = b.squeeze() print_tensor("squeeze 后", squeeze_tensor)
# unsqueeze 升维,在指定维度插入尺寸为1的维度 unsqueeze_tensor = b.unsqueeze(dim=1) print_tensor("unsqueeze 后", unsqueeze_tensor)
# transpos(<维度索引1>,<维度索引2>) 通过维度索引,互换两个维度 # 注意,这会导致张量变得不连接,也就是内存访问顺序与实际存储顺序不同,导致访问速度下降. transpose_tensor = b.transpose(dim0=1, dim1=2) print_tensor("transpose 后", transpose_tensor)
# permute(<维度索引列表>) 通过维度索引,重新排列维度顺序 # 注意,这会导致张量变得不连接,也就是内存访问顺序与实际存储顺序不同,导致访问速度下降. permute_tensor = b.permute(dims=(2, 0, 1, 3)) print_tensor("permute 后", permute_tensor)
# view,用于改变张量尺寸,只能用于连续张量. # 使用is_contiguous()判断张量是否连续 # 使用contiguous()将张量转换为连续张量(重新创建新内存地址,按照当前逻辑顺序将元素重新存储.) # 提示: reshape = is_contiguous + contiguous + view
# 3. 连续张量才能 view a_t = a.transpose(dim0=0, dim1=1) # (3, 2, 4),非连续 pylog.info(f"a_t.shape: {a_t.shape}, is_contiguous: {a_t.is_contiguous()}") # False try: a_t.view(-1) # 非连续,报错 except RuntimeError as e: pylog.info(f"非连续张量直接 view 会报错: {str(e)[:60]}...")
# 4. 先 contiguous 再 view a_t_c = a_t.contiguous() pylog.info(f"contiguous 后 is_contiguous: {a_t_c.is_contiguous()}") # True print_tensor("a_t.contiguous().view(-1)", a_t_c.view(2, -1))
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 """ pylog.info(f"==================== [{label}] ====================") pylog.info(f"tensor.shape: {tensor.shape}") pylog.info(f"tensor.ndim: {tensor.ndim}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.info("Project Start") main() pylog.info("Project End")拼接与堆积
拼接: 将两个或多个张量在指定维度上连接起来,但是这几个张量已经变成一个大单体了,所以不需要升维.
堆积: 将两个或多个张量在指定维度上连接起来,但是这几个张量仍然是独立的小个体,所以需要升维并编号.
import torchfrom Util.PyLog import PyLog
global pylog
def main(): pylog.info( f"======================================== 拼接与堆积 ========================================" ) torch.manual_seed(14) a = torch.randint(0, 10, (2, 3, 4), dtype=torch.int32) b = torch.randint(10, 20, (2, 3, 4), dtype=torch.int32) print_tensor("张量 A (2,3,4)", a) print_tensor("张量 B (2,3,4)", b)
# ============ cat 拼接:在已有维度上"接长",维度数不变 ============ pylog.info("---------------- cat 拼接 ----------------") # torch.cat([<张量列表>],dim=<拼接维度>) print_tensor("cat(dim=0) 变 (4,3,4)", torch.cat([a, b], dim=0)) print_tensor("cat(dim=1) 变 (2,6,4)", torch.concat([a, b], dim=1)) print_tensor("cat(dim=2) 变 (2,3,8)", torch.cat([a, b], dim=2))
# ============ stack 堆积:新建维度"摞起来",维度数 +1 ============ pylog.info("---------------- stack 堆积 ----------------") # torch.stack([<张量列表>],dim=<堆积维度(新增的维度,有几个张量,维度大小就是几)>) print_tensor("stack(dim=0) 变 (2,2,3,4)", torch.stack([a, b, a.clone(), b.clone()], dim=0)) print_tensor("stack(dim=1) 变 (2,2,3,4)", torch.stack([a, b], dim=1)) print_tensor("stack(dim=3) 变 (2,3,4,2)", torch.stack([a, b], dim=3))
# ============ 用一维张量看清两者区别 ============ pylog.info("---------------- 一维对比 ----------------") x = torch.tensor([1, 2, 3]) y = torch.tensor([4, 5, 6]) print_tensor("x", x) print_tensor("y", y) print_tensor("cat([x,y]) 首尾相接 (6,)", torch.cat([x, y])) # [1,2,3,4,5,6] print_tensor("stack([x,y],dim=0) 摞成 (2,3)", torch.stack([x, y], dim=0)) print_tensor("stack([x,y],dim=1) 摞成 (3,2)", torch.stack([x, y], dim=1))
# ============ 便捷写法 ============ pylog.info("---------------- 便捷写法 ----------------") print_tensor("vstack 等价 cat(dim=0)", torch.vstack([x, y])) # (2,3) print_tensor("hstack 等价 cat(dim=0) 一维时", torch.hstack([x, y])) # (6,)
# ============ 要求:除拼接维外其余形状必须一致 ============ pylog.info("---------------- 形状要求 ----------------") c = torch.randint(0, 10, (2, 3, 5), dtype=torch.int32) try: torch.cat([a, c], dim=0) # 第 2 维 4 vs 5 不匹配 except RuntimeError as e: pylog.info(f"cat 形状不匹配报错: {str(e)[:70]}...") try: torch.stack([a, c], dim=0) # 形状不完全相同 except RuntimeError as e: pylog.info(f"stack 形状不匹配报错: {str(e)[:70]}...")
def print_tensor(label: str, tensor) -> None: """ 格式化打印张量 """ pylog.info(f"==================== [{label}] ====================") pylog.info(f"tensor.shape: {tensor.shape}") pylog.info(f"tensor.ndim: {tensor.ndim}") pylog.debug("[详细数据]") pylog.debug(tensor)
if __name__ == '__main__': pylog = PyLog(level="DEBUG") pylog.info("Project Start") main() pylog.info("Project End")自动微分模块
// todo
是什么?
神经网络
神经网络的组成
输入层: 接收数据,向隐藏层传输数据. 数据有多少个特征,那么输入层就有多少个神经元.
隐藏层: 对信息进行处理,提取特征,汇总信息,同层神经元没有连接,不允许跨层连接.
输出层: 输出模型预测的结果,需要几个结果,那么输出层的神经元就有多少个.
什么是神经元?
神经元全称是人工神经元.它是对数据进行非线性变换的关键组件.
处理步骤:
第一步: 对输入数据加权求和,得到状态值.(线性变换)
第二步: 将状态值输入到激活函数(对数据进行非线性缩放),神经网络获得强大的数据拟合能力.(非线性变换)
注意,这里的状态值,并非权重,也非偏置,而是一个中间值,需要严格进行区分.
状态值是经过一系列权重加权并加偏置后的临时结果(线性变换),随着给定的数据不同在不同的时刻发生变化.
状态值公式:
神经网络搭建
主要步骤
-
定义模型:
- 继承nn.Module,声明一个新的类.
- 在构造函数中,先初始化,也就是调用父类的构造方法
super().__init(),之后声明每一层的大小,参数,激活函数等.链式地将所有层都串起来 - 定义前向传播函数.
def forward(self,x):
-
准备数据
-
从文件中加载数据,如
pd.read_csv(path="...") -
使用特征工程对数据进行处理,如特征提取,特征预处理,特征降维等.
-
对数据集进行切分,切分为 训练集,验证集,测试集
-
定义数据集对象,至少需要定义两个数据集对象,训练集数据集对象和验证集数据集对象.
有多少样本
单个样本如何处理.
-
定义数据集加载器,至少需要定义两个数据加载器,训练集数据集加载器和验证集数据加载器.
对数据集对象进行批量处理
-
-
定义核心四件套 模型,损失函数,优化器,学习率衰减调度器.
- 模型: 对类进行实例化
model = CusModel() - 损失函数:
loss_fn = nn.<损失函数类型>() - 优化器:
optimizer_var = torch.optim.<梯度下降法>(<要让优化器管理的参数>,<参数列表>) - 学习率衰减调度器: 让学习率随训练进程衰减.
scheduler = torch.optim.lr_scheduler.<学习率衰减调度器名称>(<优化器>,<参数列表>)
- 模型: 对类进行实例化
-
训练循环
-
在训练开始前,需要将模型切换到训练模式
model.train() -
定义两层循环,外层对轮数循环,内层对批次循环
-
在内层循环中需要进行5个步骤
- 前向传播: 生成预测值.
pred=model(X) # 将数据直接传入模型中.无需使用model.forward(),这会让hook全部失效. - 计算损失: 预测值与真实数据之间的差距.
loss_var = loss_fn(pred, y) # 将预测值和真实值同时传入损失函数计算损失值 - 梯度清零: 防止梯度爆炸.
optimizer_var.zero_grad() # 梯度清零 - 反向传播: 计算梯度.
loss_var.backward() # 反向传播,计算梯度 - 更新参数: 更新神经元的参数矩阵.
optimizer.step() # 更新参数
- 前向传播: 生成预测值.
-
在外层循环中,还需要一个额外步骤
- 学习率衰减调度器: 对学习率使用指定调度器进行衰减.
-
神经网络中的组件
激活函数
激活函数是神经网络进行非线性缩放的重要组件,若没有激活函数,神经网络将退化成简单线性模型,学习能力大幅下降.
常见激活函数
sigmoid
公式:
这个激活函数将神经网络中的输出解释为”概率”,最大的特点是将任意实数平滑压缩到(0,1)区间,形状呈S形,整体在X轴之上.
解决的问题: 早期神经网络需要将输出解释为概率,Sigmoid提供了自然的概率主义.
优点:
- 概率解释直观: 输出范围完美对应概率定义域,也就是(0,1).
- 处处可导且平滑: 数学性质优良,便于早期理论分析.
缺点:
- 梯度消失: 这是它最大的问题,当较大时,导数趋近于0,而即使当 较小时,梯度最大也只有0.25,这在多元函数的连乘下,梯度快速逼近0.导致深层网络参数几乎无法更新,这导致使用此激活函数的神经网络只能是浅层的.
- 指数运算昂贵,计算开销大.
现代定位: 已几乎不再应用于隐藏层,仅保留在二分类任务的输出层.
Tanh
公式:
这个激活函数同样将任意实数平滑压缩到(-1,1)区间,形状为S形,但是此函数经过原点,以原点为中心对称.
优点:
- 相比Sigmoid,多了一个零中心化输出.
- 相比Sigmoid,在原点的梯度接近1,有助于模型的稳定和收敛,可减少偏移.
缺点:
- 梯度消失: 同样,没有解决梯度消失问题.
- 指数运算昂贵,计算开销大.
- 初始化敏感性,对于权重的初始化较为敏感,如果权重初始化不当,可能会导致梯度消失或爆炸.
ReLU
公式:
这个激活函数是一个分段线性函数,当z<0时,输出固定为0,当z>0时,输出等于输入,梯度恒为1
解决的问题: Sigmoid/Tanh在深层网络中严重的梯度消失问题,使得训练深层神经网络成为可能.
优点:
- 计算极快: 只需要判断阈值,无需指数/除法运算.
- 稀疏激活: 部分神经元输出为0,带来类似生物神经元的稀疏表示,有助于泛化.
缺点:
- Dead ReLU问题: 若学习率过大导致权重更新后,z永远小于0,该神经元将永久死亡,梯度一直为0,无法更新.
对策: LeakyReLU,PReLU
Softmax
公式:
特点: 将一个K维向量的每个元素映射到(0,1)的区间,所有元素之和严格为1. (将分类得分Logits,转换为归一化的概率)本质是Sigmoid的推广.
优点:
- 概率归一化: 输出天然满足概率公理,可直接用于决策或作为下游任务的输入
- 放大差异: 指数运算使较大的Logit获得更高的概率占比,增强类别区分度.
缺点:
- 数值稳定,直接计算指数易溢出,需要减去最大值
- 梯度饱和,高默念度下梯度极小,不利于持续学习.
温度系数:
: 高温,将分布变得更均匀,缩小项目之间的概率差距.适用于内容行业,希望文本内容生成更为活泼,具有更多的多样性.
: 低温,将分布变得更极端,放大各项之间的概率差距.适用于严谨的文本生成场景,如法律文档,医疗诊断.
参数初始化
参数初始化指的是要打破对称性.
为什么要打破对称性? 假设神经元都被初始化为完全相同的值(权重和偏置),那么
-
前向传播,神经元会接收到完全相同的输入,并产生完全相同的输出,
-
反向传播: 由于输出衰减相同,计算出的梯度也完全相同.
-
参数更新: 梯度下降更新后,神经元的权重依然保持相同.
这导致一层神经网络的多个神经元,与只有一个神经元无异.
主要初始化方法:
-
随机均匀初始化: 从[0-1]均匀随机抽取值,进行初始化.
-
随机标准正态初始化<从均值为0>从均值为0>,标准差为1的正态分布中随机抽取.
-
全0全1或全指定值初始化<未能打破对称性>未能打破对称性>,正常训练不使用,仅限调试模型或对偏置初始化可用.
-
kaiming初始化:深层网络并且使用ReLU激活及其变种函数时采用.(主要采用)
-
考虑了扇入
-
kaiming均匀分布初始化
-
kaiming标准正态初始化
-
-
xavier初始化:深层网络并且使用sigmoid和tanh激活函数时采用.
- 同时考虑了扇入和扇出
- xavier均匀分布初始化
- xavier标准正态初始化
损失函数
损失函数的类型主要对应于两大类任务: 分类任务和回归任务
分类任务
分类任务又分为两种分类任务: 多分类任务和二分类任务
- 多分类任务
适用的损失函数是: 多分类交叉熵损失函数
公式:
是什么? 多分类交叉熵损失函数是尝试学习中分类问题常用的损失函数,适用于多分类问题,通过度量预测分布与真实分布之间的差异,告知模型输出的准确性.
注意: nn.CrossEntropyLoss()方法中,已经自带归一化功能,所以在传入预测值和真实值时,不需要归一化(softmax).
- 二分类任务
适用的损失函数是: 二分类交叉熵损失函数.
公式:
是什么? 适用于二分类问题.同多分类交叉熵损失函数.
注意: nn.BCELoss()方法中,不自带归一化功能,所以在传入预测值和真实值时,需要归一化(sigmoid).
回归任务
MAE平均绝对误差,也叫L1-Loss.
公式:
缺点:
- 0点处不可导
- 梯度无法下降梯度幅值恒为,导致模型训练到最后,参数在最优点震荡,无法准确落入最优点.
MSE均方误差,也叫L2-Loss.
公式:
缺点:
- 对异常值敏感,容易梯度爆炸.
Smooth-L1平滑L1,主流方案,避免了L1-Loss和L2-Loss的缺点.
公式:
本质是将MAE中|x|<1的部分,替换为MSE.
优化器
是什么? 优化器是深度学习中根据梯度和学习率信息更新网络参数的算法,不改变模型结构,只决定”参数向哪个方向走,该走多快”,目标是让损失函数尽快收敛到较低值.
为什么要有? 反向传播计算出梯度后,模型的参数更新需要一个策略.深度学习的损失曲面高维,非凸,充满鞍点和病态曲率,直接沿着负梯度(朴素SGD)走,会遇到震荡,收敛慢,学习率难以调整等一系列问题,所以需要优化器来制定一个统一的策略,聪明地根据梯度去调整参数更新规则.
主流优化器
| 优化器 | 优化项目 | 优化的痛点 | 优点 | 缺点 | 核心流程 |
|---|---|---|---|---|---|
| SGD | 更新参数 | 简单,泛化好 | 对学习率敏感,收敛慢,易震荡. | 沿当前样本的负梯度方向,以固定学习率更新参数. | |
| SGD+Momentum参数 | 更新参数 | 解决了SGD无法冲出鞍点及震荡问题 | 加速收敛速度,抗震荡,能冲出鞍点 | 惯性会导致”过冲”,之后再回头. | 是SGD的改进版,引入加权的历史梯度值作为”动量”,对梯度进行更新. |
| AdaGrad | 学习率 | 可以逐参数自适应学习率 | 学习率衰减过快 | 利用每个参数各自的累计平方梯度对初始学习率进行衰减. | |
| RMSprop | 学习率 | 修复了学习率单调衰减到0的问题. | 缺乏动量项,部分情况下不能让模型跳出局部最优值或鞍点. | 利用累计平方梯度的指数加权移动平均值对学习率进行衰减,避免学习率衰减过快 | |
| Adam | 更新参数和学习率 | 收敛快,对学习率不敏感 | 综合了动量法梯度更新和RMSprop的优点 |
学习率衰减调度器
理想的学习率是在训练的初期,学习率较大,让模型快速收敛,在训练的末期,学习率较小,让模型更精准的探索全局最优参数.
学习率过大: 梯度爆炸
学习率过小: 收敛效率低,训练缓慢
常用的衰减策略
// todo 为这几个常用的衰减策略添加对应的解释
-
等间隔学习率衰减
-
指定间隔学习率衰减
-
指数学习率衰减
-
[了解]余弦退火学习率+预热+重启
模型正则化
常用的正则化方法
随机失活Dropout
-
何时使用训练时开启,测试时关闭
-
原理
- 训练时,每个神经元以一定的概率P失去作用,输出为0.
- 训练时,没有失活的神经元,需要以1/(1-P)的倍率去放大输出. 为了弥补训练阶段由于失活,使得信号强度减弱.
-
作用
- 训练时,部分神经元失活导致模型的可训练参数减少,造成稀疏性,抗过拟合.
训练时,由于每个批次数据失活的神经元是随机决定的,导致每次神经网络的结构都发生变化,从而使得正向传播的数据路径不同,避免学习特征时产生路径依赖( 过分迷信前一层某个特征神经元的输出特征,强迫其从其它未失活神经元学习规律.).
批归一化层
解决神经网络内部协变量偏移问题,每一层的输入数据在每个批次的训练中对应的输出数据的分布会反复变化,使下一层网络的训练一直在尝试” 适应”这些数据,而没有真正的去寻找样本间的规律.
实现的步骤
-
对每一层的输出进行标准化
用于统一每批次输出的数据分布,减少波动幅度.
-
标准化后的数据进行重构
允许标准化后的数据分布一定程度上的还原.
- [了解]数学上的实现
- 做标准化的时候,训练集使用自身的数据计算标准差,完成标准化,对于测试个框,需要利用训练集的均值和标准差,完成测试集的标准化.
- 训练集所使用的均值和标准差,来自于训练集每个批次的均值和标准差的指数加权移动平均值(EMA)来估计的.
CNN
CNN卷积神经网络,是一类专门设计用于处理具有网格状拓扑结构数据的深度前馈人工神经网络.
适用于:
- 计算机视觉(绝对主场): 图像分类,目标检测,图像分割,人脸识别,自动驾驶视觉.
- 自然语言处理: 文本分类,情感分析.
- 语音与音频: 语音识别,音乐流派分类.
组成部分:
| 组成部分 | 核心功能 | 直观理解 |
|---|---|---|
| 输入层(input) | 接收原始的数据张量(H,W,C)形状的图像 | 把照片送进机器 |
| 卷积层(Convolution) | 特征提取. 利用卷积核在输入数据上滑动进行点乘,提取局部特征(边缘,纹理,形状等). | 用不同形状的放大镜在图片上扫过,寻找特定的图案. |
| 激活函数层(Activation) | 引入非线性变换. 将卷积后的线性结果映射为非线性,使网络能学习更复杂的模式,同时缓解梯度消失问题. | 决定找到的哪些特征是有用的,需要留下,哪些特征没用(删除/置零) |
| 池化层(Pooling) | 降维与压缩. 对局部区域取最大值或平均值,减小特征图尺寸,减少计算量,并提供一定的平移不变性. | 把高清图片缩小成缩略图,保留最显著的特征,忽略微小的位置偏移. |
| 批归一化层(Batch Norm) | 稳定训练结果,将每一层的输出标准化,加速网络收敛,允许使用更大的学习率,直到轻微正则化作用 | 防止数据在层层传递中”跑偏”或数值爆炸 |
| 全连接层(Fully Connected) | 特征整合与映射. 将前面提取到的高级抽象特征展平,组合起来,映射到最终的样本标记空间. | 把所有收集到的线索综合起来进行最终判断.(如有耳朵,有尾巴,会喵喵叫) |
| 输出层(Output) | 输出最终预测结果,输出每个类别的概率分布(分类任务)或具体的连续数据(回归任务) | 宣布最终结果:“这是一只猫的概率是95%” |
特性
- 权重共享: 卷积核在全图各个区域提取特征时,其权重值不变.
无法为不同位置学习不同的模式 - 平移不变性: 由于权重共享,所以当图中的重要特征出现在图中的其它位置时,卷积核都能对其保持敏感,提取出相同的特征信息,输出结果不变.
丢失绝对位置信息 - 局部感受野: 卷积后的特征图其每个像素都是输入图像的一个局部区域的信息,因此该局部区域的大小就是该像素的感受野.
长距离依赖必须靠堆叠层数才能建立
正是因为这三个特性,CNN才能精准的采集特征
但也正是因为这三个特性,Vision Transform放弃了CNN,原因是用更大的数据量换取更少的䅩假设,从而获得更高的性能. 在数据量有限的情况下,CNN仍然是不可替代的.
优点:
- 权值共享: 同一个卷积核在整个图像上滑动共享权重,使得参数量呈指数级下降,降低了内存消耗和过拟合风险.
- 局部连接: 每个神经元只与上一层的局部区域连接,完美契合图像等数据的局部相关性(相邻像素关联度高,相距远的关联度低).
- 平移,缩放不变性: 得益于卷积核的权值共享和滑动特性及池化层的降维,CNN对物体在图像中的位置变化具有很强的鲁棒性.
- 自动特征工程: 无需人工设计特征,CNN可以通过反向传播自动学习到最适合当前任务的特征表示.
缺点:
- 缺乏全局上下文感知: 感受野受限,卷积操作本质上是局部的,虽然可以通过堆叠层数护套感受野,但在处理需要长距离依赖或全局上下文关系的任务时,不如Transformer.
- 对空间几何变换敏感: 虽然具有平移不变性,但对图像的旋转,翻转等不具备天然的不变性.
- 数据饥渴: CNN参数量大,需要海量带标签的数据进行训练才能发挥威力,小样本场景下容易过拟合.
- 计算资源要求高.
- 黑盒特性: 难以解释CNN为什么做出某个决策.
RNN
RNN循环神经网络是一种专门处理序列数据的神经网络,核心特征是引入了”循环连接”,即网络在处理当前时刻输入时,会接收上一时刻的输出作为额外输入,从而赋予模型”记忆”能力.
适用于:
- 自然语言处理(传统主场): 语言模型,机器翻译,文本生成,情感分析,序列标注(命名实体识别).
- 时间序列分析(现今主场): 股票/电力/天气/销量预测,传感器异常检测,医疗时序信号分析.
- 语音与音频: 语音识别,语音合成,流式转写.
- 视频理解: 视频动作识别,轨迹预测.
| 组成部分 | 核心功能 | 直观理解 |
|---|---|---|
| 输入层(Input) | 接收当前时间步的输入(如词向量,单帧特征,传感器读数). | 把句子里的”当前这个词”送进机器. |
| 隐藏状态(记忆单元) | 记忆与状态更新.通过 | 边读边记笔记: 新笔记=旧笔记+刚读到的内容 |
| 激活函数 | 引入非线性变换. 使网络能学习非线性的时序模式; tanh同时将移动电源约束在(-1,1)之间,缓解数值发散. | 给笔记内容”设上下限”,防止越记越夸张 |
| 输出层 | 基于记忆的预测. 将当前的隐藏层状态映射为输出(如下一个词的概率分布或回归值) | 看着目前的笔记做出判断:“下一个词大概率是xx"" |
| 循环连接(RNN最大的不同点) | 沿时间传递信息. 作为下一时刻的输出沿时间轴接力传递,使网络在任意长度序列上保持状态; 训练时按时间展开,沿时间反向传播. | 记忆像接力棒,一棒一棒向后传. |
特性:
- 时序记忆: 隐藏状态沿时间轴不断传递,网络对已读内容保持”记忆”.
旧记忆被新输入反复覆盖稀释,距离越远的信息越模糊 - 权重共享: 同一组权重
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!






















