矩阵运算的几何意义:变换、旋转与神经网络中的线性层

矩阵是"变换机器" 标量乘法 3 x 5 = 15 只是把一个数放大 3 倍。要对一个二维点进行缩放、旋转、镜像,一个数字不够用,需要矩阵。 矩阵乘以向量 = 对该点执行一次变换: import numpy as npA = np.array([[2, 0], [0, 2]]) # 缩放矩阵x = np.array([2, 3]) # 原始点 (2, 3)print(A @ x) # [4, 6] -> 放大 2 倍常见变换矩阵 缩放(Scaling): [sx 0 ] 把 x 缩放 sx 倍 [0 sy] 把 y 缩放 sy 倍旋转 angle(Rotation): [cos a -sin a] [sin a cos a]逆时针旋转 90 度: import math theta = math.pi / 2R = np.array([[math.cos(theta), -math.sin(theta)], [math.sin(theta), math.cos(theta)]])x = np.array([1, 0]) print(R @ x) # [0, 1] — (1,0) 逆时针旋转到 (0,1)镜像(y 轴翻转): [-1 0] [ 0 1]为什么矩阵乘法是那个规则 [1 2] [5] = [1x5 + 2x6] = [17] [3 4] x [6] [3x5 + 4x6] [39]不是人为规定,而是"两次变换合并"的自然结果:矩阵的每一行定义输出的一个维度,该维度由输入向量的线性组合得出。 矩阵乘法 = 变换的复合 两个矩阵连乘等于先做 B 变换再做 A 变换: A = np.array([[2, 0], [0, 2]]) # 放大 2 倍 B = np.array([[0, -1], [1, 0]]) # 旋转 90 度C = A @ B # 先旋转再缩放 x = np.array([1, 0]) print(C @ x) # [0, 2]神经网络中的矩阵 全连接层(Linear 层)的本质就是矩阵乘法加偏置: output = W x input + b一个隐藏层有 128 个神经元、输入维度为 64: import torch import torch.nn as nnlinear = nn.Linear(64, 128) # 内部持有 weight: Tensor (128, 64) 和 bias: Tensor (128,)x = torch.randn(32, 64) # batch_size=32, input_dim=64 y = linear(x) # y.shape = (32, 128)每一层做的都是:把输入向量从一个向量空间线性映射到另一个向量空间。激活函数(ReLU、Sigmoid 等)引入非线性,才让网络能拟合复杂函数。 PyTorch 矩阵运算 import torchA = torch.tensor([[1., 2.], [3., 4.]]) B = torch.tensor([[5., 6.], [7., 8.]])# 矩阵乘法 print(A @ B) # torch.matmul print(torch.mm(A, B)) # 二维矩阵专用# 转置 print(A.T)# 行列式 print(torch.det(A)) # -2.0# 逆矩阵 print(torch.inverse(A))高维 Tensor = 批量矩阵运算 深度学习中输入通常是 3D 以上的 Tensor(batch x dim): # batch matmul:对每个样本独立做矩阵乘法 A = torch.randn(32, 4, 4) # 32 个 4x4 矩阵 B = torch.randn(32, 4, 1) # 32 个列向量 C = torch.bmm(A, B) # (32, 4, 1)torch.bmm 对 batch 中每个矩阵独立相乘,是卷积、Attention 等操作的底层基础。

PyTorch 学习路线:从 Tensor 到训练完整模型

学习阶段规划阶段 时间 目标Python 基础 3~5 天 能写循环、函数、类NumPy 基础 2 天 理解矩阵运算PyTorch Tensor 3 天 会张量计算和 GPU 迁移Autograd 自动求导 2 天 理解梯度和 backwardnn.Module 基础 3 天 会定义 MLP训练流程 3 天 会完整训练和评估CNN 5 天 图片分类Transformer 基础 7 天 理解 Attention项目实战 长期 YOLO、PointNet 等约一个月可以达到能读懂主流论文代码的水平。 为什么从线性回归开始 # 目标:给出面积和房间数,预测房价 area = [80, 100, 120, 150] rooms = [2, 3, 3, 4] price = [120, 180, 220, 300]神经网络就是找一个函数 f,使得 f(area, rooms) ≈ price。PyTorch 的作用是:定义 f 的结构(model) 自动计算 f 的梯度(autograd) 按梯度更新参数,让预测更准确(optimizer)核心训练循环 import torch import torch.nn as nn# 1. 数据 x = torch.tensor([[80, 2], [100, 3], [120, 3], [150, 4]], dtype=torch.float32) y = torch.tensor([120, 180, 220, 300], dtype=torch.float32)# 2. 定义模型 model = nn.Sequential( nn.Linear(2, 16), nn.ReLU(), nn.Linear(16, 1) )# 3. 定义 loss 和 optimizer criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)# 4. 训练循环 for epoch in range(200): pred = model(x).squeeze() # 前向传播 loss = criterion(pred, y) # 计算 loss optimizer.zero_grad() # 清空上一步的梯度 loss.backward() # 反向传播,计算梯度 optimizer.step() # 按梯度更新参数 if epoch % 50 == 0: print(f"Epoch {epoch}: loss = {loss.item():.2f}")四步缺一不可:zero_grad() → 清除旧梯度(PyTorch 默认累积梯度) backward() → 计算所有参数对 loss 的偏导数 step() → 把参数沿梯度方向更新一步Tensor 基础操作 import torcha = torch.tensor([1.0, 2.0, 3.0]) b = torch.zeros(3, 4) c = torch.randn(2, 3) # 标准正态分布# 形状操作 c.shape # torch.Size([2, 3]) c.reshape(3, 2) c.unsqueeze(0) # 增加维度: [1, 2, 3] c.squeeze() # 去掉维度为 1 的维# GPU device = "cuda" if torch.cuda.is_available() else "cpu" c = c.to(device)常用 Loss 函数任务 Loss 调用回归 MSELoss nn.MSELoss()二分类 BCELoss nn.BCEWithLogitsLoss()多分类 CrossEntropy nn.CrossEntropyLoss()常用 Optimizer # Adam:自适应学习率,大多数情况首选 optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)# AdamW:Adam + 权重衰减,Transformer 常用 optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)# SGD:经典,配合 momentum optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)保存和加载模型 # 保存权重 torch.save(model.state_dict(), "model.pth")# 加载权重 model.load_state_dict(torch.load("model.pth")) model.eval() # 推理时关闭 dropout 和 BatchNorm 的训练行为入门建议用 model.state_dict() 而不是直接 torch.save(model, ...),后者会把整个模型类序列化,跨环境加载容易出问题。

PyTorch 点云分割:PointNet 到 Point Transformer 的模型选型

三种任务类型任务 输入 输出 典型场景语义分割 点云 每个点的类别 地面/树木/汽车/行人实例分割 点云 每个点的实例 ID 区分两辆不同车辆部件分割 单个物体点云 零件归属 椅子的腿/靠背/坐垫主流模型 PointNet(最经典) 输入 N×3,直接用 MLP 逐点提取特征,全局池化后再输出分割结果: import torch import torch.nn as nnclass PointNet(nn.Module): def __init__(self, num_classes): super().__init__() self.mlp = nn.Sequential( nn.Linear(3, 64), nn.ReLU(), nn.Linear(64, 128), nn.ReLU(), nn.Linear(128, 256) ) self.cls = nn.Linear(256, num_classes) def forward(self, x): # x: [B, N, 3] feature = self.mlp(x) # [B, N, 256] out = self.cls(feature) # [B, N, num_classes] return out预测时取 argmax 得到每个点的标签: pred = out.argmax(dim=-1) # [B, N]其他模型选型模型 核心 适合场景PointNet++ 局部邻域分层学习 室内、激光雷达、工业检测DGCNN KNN 建图 + EdgeConv 精度要求高的分类/分割Point Transformer Transformer Attention SOTA,大型场景MinkowskiNet 稀疏体素 + Sparse CNN 超密点云,速度快数据格式 点云数据通常有三种格式: xyz # 坐标 [x, y, z] xyzrgb # 坐标 + 颜色 [x, y, z, r, g, b] xyz+intensity # 激光雷达:坐标 + 反射强度读入后转 Tensor: import numpy as np import torchpoints = np.load("scan.npy") # (N, 3) x = torch.tensor(points, dtype=torch.float32) # [N, 3] x = x.unsqueeze(0) # [1, N, 3] add batch dimLoss 函数 点云分割本质是逐点分类,直接用 CrossEntropyLoss: criterion = nn.CrossEntropyLoss()# pred: [B, num_classes, N] or reshape to [B*N, num_classes] # label: [B, N]pred_flat = pred.view(-1, num_classes) # [B*N, num_classes] label_flat = label.view(-1) # [B*N]loss = criterion(pred_flat, label_flat)常用公开数据集数据集 场景 用途ShapeNet Part 单物体 部件分割S3DIS 室内 语义分割SemanticKITTI 自动驾驶 激光雷达语义分割ScanNet RGB-D 室内 场景理解工程推荐流程 点云采集 ↓ 预处理(去噪、下采样、法向量估计) ↓ PointNet++ / Point Transformer 训练 ↓ 输出分割结果 ↓ 后处理(聚类、过滤小区域)入门推荐从 PointNet 开始,结构最简单,容易在本地小数据上跑通。确认流程后再换 PointNet++ 提升精度。

PyTorch 训练基础:Tensor、自动求导、训练循环与模型保存

Tensor Tensor 是 PyTorch 的核心数据结构,类似 NumPy 数组但支持 GPU 运算: import torch# 0 维标量 t0 = torch.tensor(3.14)# 1 维向量 t1 = torch.tensor([1.0, 2.0, 3.0])# 2 维矩阵 t2 = torch.zeros(3, 4)# 移到 GPU if torch.cuda.is_available(): t2 = t2.cuda()Dynamic Graph 与 Autograd PyTorch 使用动态计算图(Define-by-Run),每次前向传播都重新构建计算图,便于调试和条件分支。 开启梯度追踪: x = torch.tensor(2.0, requires_grad=True) y = x ** 2 + 3 * xy.backward() # 反向传播 print(x.grad) # dy/dx = 2x + 3 = 7.0标准训练循环 model = MyModel() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) loss_fn = torch.nn.CrossEntropyLoss()for epoch in range(100): for x_batch, y_batch in dataloader: pred = model(x_batch) # 1. 前向传播 loss = loss_fn(pred, y_batch) # 2. 计算损失 optimizer.zero_grad() # 3. 清空上一步梯度 loss.backward() # 4. 反向传播 optimizer.step() # 5. 更新参数 print(f"Epoch {epoch}, Loss: {loss.item():.4f}")zero_grad() 必须在 backward() 前调用,否则梯度会累加。 损失函数选择任务 损失函数 激活函数二分类 BCELoss Sigmoid多分类 CrossEntropyLoss 无(内置 Softmax)回归 MSELoss 无# 二分类:Logistic Regression model = torch.nn.Sequential( torch.nn.Linear(10, 1), torch.nn.Sigmoid() ) loss_fn = torch.nn.BCELoss()# 多分类 model = torch.nn.Sequential( torch.nn.Linear(10, 5) # 5 个类别 ) loss_fn = torch.nn.CrossEntropyLoss() # 内部含 Softmax# 回归 model = torch.nn.Linear(10, 1) loss_fn = torch.nn.MSELoss()优化器 # SGD(基础,需手动调 lr) optimizer = torch.optim.SGD(model.parameters(), lr=0.01)# SGD + Momentum(更稳定) optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)# Adam(最常用,lr=0.001 通常无需调整) optimizer = torch.optim.Adam(model.parameters(), lr=0.001)# AdamW(Transformer 推荐,带权重衰减修正) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.01)大多数情况从 Adam + lr=0.001 开始,效果不好再调。 保存与加载模型 推荐:只保存参数(state_dict) # 保存 torch.save(model.state_dict(), "model.pth")# 加载 model = MyModel() model.load_state_dict(torch.load("model.pth")) model.eval() # 切换到推理模式保存完整模型(不推荐,依赖类定义路径): torch.save(model, "model_full.pth") model = torch.load("model_full.pth")导出 ONNX ONNX 格式可在 TensorFlow、ONNX Runtime、TensorRT 等框架中使用: dummy_input = torch.randn(1, 3, 224, 224) # 与实际输入形状一致torch.onnx.export( model, dummy_input, "model.onnx", input_names=["input"], output_names=["output"], opset_version=17 )导出后可用 onnxruntime 推理,速度通常比 PyTorch 原生快。 推理模式 model.eval()with torch.no_grad(): # 禁用梯度计算,节省内存 output = model(input_tensor)推理时必须调 model.eval() 和 torch.no_grad(),否则 BatchNorm 和 Dropout 行为与训练时不同。

BIP39 助记词会不会重复:数学上不会,现实中被坑的另有其人

有人问过我一个纠结的问题:用钱包 APP 生成助记词,会不会跟别人重复导致钱包被盗? 数学上算一下就知道,重复的概率低到根本不用担心;真正被盗的原因,几乎都是别的坑。 BIP39 生成流程 12 词助记词的生成,标准流程只有三步: 1. 从系统 CSPRNG 取 128 位熵 import secrets entropy = secrets.token_bytes(16) # 16 字节 = 128 位secrets 底层用的是操作系统提供的密码学安全随机源:Windows:BCryptGenRandom Linux:/dev/urandom macOS:SecRandomCopyBytes2. 拼上 4 位校验和 对 128 位熵做 SHA-256,取前 4 位挂到熵尾部,凑成 132 位: 128 位熵 + 4 位校验和 = 132 位3. 每 11 位查一次词表 BIP39 词表有 2^11 = 2048 个词。132 位 / 11 = 12 个词。 10010101100 → abandon 11100101010 → ability ...碰撞概率有多低 12 词的熵是 128 位,等于 3.4 × 10³⁸ 种可能。用生日悖论算一下极端情况: 假设全球 100 亿人,每人生成 100 万个钱包,一共 10¹⁶ 个: 碰撞概率 ≈ N² / (2 × 2¹²⁸) ≈ 10³² / 6.8 × 10³⁸ ≈ 1.5 × 10⁻⁷约 0.000015%,还是极端假设。真实世界远远达不到这个量级。24 词是 256 位熵,那更是天文数字。 只要用正规钱包 + 系统 CSPRNG 生成,重复被盗几乎不可能。 真被盗的常见姿势 1. 随机数不安全 自己造轮子最容易出问题: import random random.seed(time.time()) # 危险Python 的 random 是伪随机 + 可预测种子。攻击者知道大概时间戳,就能枚举出你所有可能的助记词。 永远用 secrets 或 os.urandom,不用 random。 2. "幸运数字"当种子 有人觉得自己的生日、手机号能当种子更好记: seed = "5201314" entropy = sha256(seed)这种熵严重不足。攻击者跑一遍 0 到 9,999,999,999 就把你的钱包翻出来了。任何"人可以记住"的种子都不够安全。 3. 脑钱包 想一句话当助记词: iloveyou123彩虹表早就把常见短语算光了,这种钱包上链一分钟就被扫走。 4. 假钱包 APP 流程: APP 生成助记词 → 悄悄上传服务器 → 等你存币 → 转走来路不明的浏览器插件、"新币空投工具"是重灾区。装钱包只从官网或 App Store。 5. 助记词泄露截屏保存到手机相册(云同步就完了) 记在便笺、微信自己的对话 输入到"辅助工具"网页正确做法:只离线纸质备份,多份异地存放。 安全生成的一行代码 用 bip_utils: from bip_utils import Bip39MnemonicGenerator, Bip39WordsNummnemonic = Bip39MnemonicGenerator().FromWordsNumber(Bip39WordsNum.WORDS_NUM_12) print(mnemonic)内部就是 CSPRNG + 128 位熵 + SHA-256 校验,标准 BIP39 流程。 一句话总结 担心助记词重复是想多了;担心自己不用 CSPRNG、拿脑钱包和假钱包 APP 是应该的。 只要用正规钱包生成 + 离线纸质备份,安全性远高于随机重复这种理论问题。