PyTorch核心基石:张量、自动求导与训练流水线实战

如果你正打算进入深度学习这个领域,或者已经在某份岗位清单里反复看到它,那你大概率绕不开PyTorch。这个以动态计算图和Pythonic风格著称的开源深度学习框架,把科研人员的想法到代码之间的路径压缩到了极短,也让工业落地时的调试、部署、迭代过程变得相对可控。我见过不少从零开始学框架的人,一上来就刷模型结构,反而忽略了PyTorch最核心的三块基石:张量、自动求导、训练流程的组织方式。这篇文章就想把这些东西拆开讲清楚,从设计思路到实操代码,再到我踩过的坑,尽量一次说透。

这个框架能做什么,不需要我多啰嗦。图像分类、目标检测、文本生成、语音识别、强化学习,几乎每个热门方向都能看到它的身影。适合谁来读?如果你刚接触深度学习,想把理论基础落到代码上,这篇文章给你一条完整的上手路径;如果你已经写过一些模型但总觉得哪里没理顺,后半部分的排查思路和训练细节应该能帮上忙。

1. 先搞清楚PyTorch解决什么问题

1.1 动态图:调试体验碾压静态图

很多新手会忽略一个关键点:PyTorch和早期一些框架最本质的区别,在于计算图的构建方式。像TensorFlow 1.x时期那种先定义好完整计算图、再喂数据执行的模式,是静态图。静态图有它的性能优势,但调试起来非常痛苦——你没法在中间某个节点打印一个变量看看值到底是什么,因为整个图要先编译再运行。PyTorch选择的是动态图,也就是边运行边建图。你写一行代码,它就执行一行,同时把运算关系记录到一张图上,等到需要反向传播的时候再沿着这张图把梯度送回去。

这个差异用装修房子来类比就很好懂。静态图像你先把整套设计图纸画好,施工队严格按照图纸走,中间想改个插座位置,得改图纸重新来;动态图则是你站在毛坯房里,让工人先砌这面墙,你觉得不满意当场推倒重砌,每一块砖都是你看着放上去的。对于科研和快速验证来说,动态图的灵活性和可干预性是压倒性的优势,这也是PyTorch能在学术界快速铺开的核心原因。

动态图带来的直接好处是Debug特别方便。你可以在模型forward函数的任意一行加print,或者在训练循环里用断点查看某一层输出的shape和数值分布,所见即所得。我在排查模型问题时,最常用的手段就是在loss计算之后、backward之前插入一个断点,直接把logits和target的shape打出来核对,很多维度对不上的问题五分钟内就能定位。这在静态图时代基本不敢想。

1.2 Pythonic接口:让框架贴近你的思考方式

PyTorch的另一个核心竞争力,是它的接口设计非常“Pythonic”。用起来就是Python原生的感觉,而不是在一个拧巴的包装器里写代码。模型继承自nn.Module,像写普通Python类一样定义__init__和forward就行;数据集继承自Dataset,实现__len__和__getitem__两个方法就可以被DataLoader加载。不需要学习一套新语法,不需要理解复杂的配置字符串,你对Python越熟,用PyTorch就越顺畅。

这种设计降低了入门门槛,也提高了长期维护的效率。我在指导新人上手时,通常让他们先忘掉模型结构,专注把一个nn.Module子类跑通,跑通之后再往里填卷积、全连接之类的层次。因为PyTorch的组织方式足够直观,整个思考链条是连贯的:数据先变成Tensor,Tensor在Module里前向传播,得到输出后和标签算出loss,反向传播更新参数。每一步都像在读一篇结构清晰的Python代码,而不是在操作某个黑盒系统。

2. 从张量开始:数据运算的基础设施

2.1 Tensor和NumPy到底差在哪

如果你写过NumPy,那你对Tensor上手会非常快。torch.Tensor本质上就是一个支持GPU加速、能自动记录运算历史的n维数组。两者在形状、索引、切片、广播这些操作上的规则几乎一致,但它们有两点核心差异。

第一是设备感知。Tensor可以存放在显存里,通过tensor.cuda()或tensor.to('cuda')完成从内存到显存的搬运,后续的运算就直接在GPU上执行。NumPy数组只能躺在内存里,想用GPU得先转成Tensor,这步转换在框架里是无感的,因为DataLoader自动帮你处理。第二是自动求导。Tensor带有requires_grad属性,一旦开启,框架就会为它记录所有涉及到的运算,并构建出一张计算图,供后续反向传播使用。NumPy没有这套机制,它只负责数值计算本身。

这也引出一个重要认知:Tensor是模型的“血液”,模型不敢接受NumPy数组直接做前向计算(至少不推荐),必须通过torch.tensor()或torch.from_numpy()做转换。转换过程中要注意torch.from_numpy()返回的Tensor和原NumPy数组共享底层内存,修改其中一个会影响另一个;如果你需要独立的副本,务必要`.

2.2 新建Tensor的常用姿势

日常写代码时,创建Tensor的方式五花八门,我整理了一份常用清单:

创建方式说明常见使用场景
torch.tensor([1.0, 2.0])从Python列表或NumPy数组直接创建快速构造小规模数据
torch.zeros(3, 4)/torch.ones(3, 4)全0 / 全1张量初始化掩码、占位
torch.randn(3, 4)标准正态分布随机张量初始化权重、噪声
torch.rand(3, 4)均匀分布 [0, 1) 随机张量生成随机比例参数
torch.arange(0, 10)等距序列,类似range生成索引序列
torch.eye(3)单位矩阵构造one-hot编码
torch.full((2, 3), 7)填充固定值的张量构造常量张量

创建时最容易犯的错,是没注意dtype。默认情况下,torch.tensor([1, 2])是int64,torch.tensor([1.0, 2.0])是float32,而模型权重默认是float32。如果输入数据和模型参数的类型不一致,轻则报类型错误,重则精度被悄悄降低。我建议在创建数据或标签时,显式加上dtype=torch.float32或dtype=torch.long,一次到位,省得后面反复排查。

2.3 设备与精度:最容易翻车的两个细节

设备不一致的报错,大概是PyTorch初段最常见的问题。错误信息通常长这样:Expected all tensors to be on the same device。原因很简单,模型在GPU上,但你喂进去的Tensor还在CPU上,反过来也一样。这里我有一套固定的排查流程:先检查模型是在哪个设备上,再检查输入Tensor的.device属性,最后检查loss计算时是否混用了不同设备的张量。训练代码里我习惯在开头就定义device = torch.device('cuda' if torch.cuda.is_available() else 'cpu'),然后所有显式创建的Tensor和模型都通过.to(device)切过去,能少掉九成设备问题的烦恼。

精度问题则更隐蔽。PyTorch的默认浮点类型是float32,对于绝大多数深度学习任务够用。但如果你从某些数据集里读进来的是float64的NumPy数组,直接转Tensor后精度会保留float64,导致模型训练变慢,显存占用暴涨。我踩过这个坑,一个看似不大的数据集,因为精度没归一,训练速度下降了三成。干脆统一在数据加载时转成float32,模型里该用float32的地方就都用float32,不要混搭。

3. autograd:免费午餐背后的工作原理

3.1 计算图与反向传播的直观理解

绝大多数新手用PyTorch训练模型时,只知道要调loss.backward()和optimizer.step(),但不清楚这两步之间发生了什么。这里我把autograd的机制拆开。

你每做一次张量运算,PyTorch就在后台悄悄构建一个“计算图”的节点。这个图记录了三件事:参与运算的输入张量、输出张量、以及这个运算的求导规则。比如你写y = w * x + b,那么框架会记住y是由w、x、b通过乘法和加法得到的。当你调用loss.backward()时,PyTorch会从loss这个张量出发,沿着这张图反向遍历,利用链式法则依次计算每个叶子节点(也就是requires_grad=True的Tensor)的梯度,并把梯度值累加到这些张量的.grad属性里。

所以“自动求导”的本质是:前向传播时“记账”,反向传播时“算账”。requires_grad=True的张量就是你需要计算梯度的“账本”。模型权重默认开启requires_grad,而输入数据、标签通常不需要梯度,所以你在构造Tensor时很少手动设置这个属性,因为nn.Parameter已经帮你处理好了。

3.2 梯度累积和清零:一个反复出现的坑

初学训练循环时,我对optimizer.zero_grad()的存在理由感到困惑:既然backward()里已经把梯度算好了,为什么每次还要清空一遍?原因是backward()的默认行为是“累积”梯度,而不是“覆盖”梯度。也就是说,如果你不清零,第二次调用backward()时,新算出来的梯度会叠加到旧梯度上,而不是替换掉旧值。

这个设计在某些场景有用,比如你想用更大的有效batch size,但显存放不下,就可以拆成多次小batch前向和反向,让梯度累积起来再统一更新。但在普通的训练循环里,忘记zero_grad()的后果就是梯度越积越大,loss曲线上下乱跳,最终模型完全无法收敛。我建议把这三行代码看作一组固定套餐,顺序不要乱:

optimizer.zero_grad() loss.backward() optimizer.step()

没有任何例外。如果你想手动梯度累积,那就不要在每个batch后step(),而是每隔N个batch才调用一次step(),中间用zero_grad()清一次即可。理解“累积”这个语义之后,就不会再把zero_grad当作可有可无的仪式了。

3.3 in-place操作和detach的边界

在用PyTorch时,你可能偶尔会看到报错提示:one of the variables needed for gradient computation has been modified by an inplace operation。这是因为你对某个参与计算图运算的张量做了就地修改,比如x += 1或x.data.fill_(0)。一旦发生in-place修改,框架之前记录的计算图就失效了,反向传播时拿到的数据对不上号,自然没法算梯度。

我对新人的建议是:只要某个Tensor参与反向传播,就别对它的数据做in-place操作。如果你确实想修改一个叶子节点的值,比如处理网络中间某个特征,应该先做x_cloned = x.detach().clone(),再对克隆体操作。detach()的作用更广一点,它能把一个Tensor从计算图中“摘出来”,切断梯度的回传路径。比如你只想用生成模型产出的特征做后续计算,但又不想让梯度流回生成器,就可以在中间给feature.detach()一下。这属于比较进阶的操作,先了解它的含义,用到时自然就知道怎么用。

4. 一条完整的训练流水线长什么样

4.1 数据读取:Dataset与DataLoader的配合

训练任何模型,第一步永远是搞定数据的读取方式。PyTorch对数据读取的抽象非常清晰:Dataset负责定义“怎么从一堆原始数据里取出一条样本和它的标签”,DataLoader负责定义“怎么把这些样本打包成一个batch并且打乱顺序、并行加载”。

写一个自定义Dataset其实很简单,核心就是三个方法。__init__里准备好文件路径列表或预处理后的数据;__len__返回样本总数,供DataLoader知道有多少条数据;__getitem__接收一个索引,返回一个(input, label)的元组。举个例子:

from torch.utils.data import Dataset, DataLoader import torch class CustomDataset(Dataset): def __init__(self, file_list): self.file_list = file_list self.labels = [int(path.split('_')[1].split('.')[0]) for path in file_list] def __len__(self): return len(self.file_list) def __getitem__(self, idx): path = self.file_list[idx] data = torch.randn(3, 32, 32) # 以随机数据代替真实读取逻辑 label = torch.tensor(self.labels[idx], dtype=torch.long) return data, label dataset = CustomDataset(['sample_0.jpg', 'sample_1.jpg']) loader = DataLoader(dataset, batch_size=16, shuffle=True, num_workers=4)

注意两点:第一,__getitem__里最好做完整的变换和类型转换,让返回的已经是Tensor;第二,DataLoader的shuffle=True在训练集上几乎必须开启,否则模型每次看到的样本顺序固定,会影响优化过程的稳定性。num_workers则表示用几个子进程加载数据,在Linux和macOS下可以设置成4或8来提速,Windows下如果多进程报错,先调回0。

4.2 模型定义与参数管理

模型定义听起来吓人,但本质上就是往nn.Module里塞参数,再规定这些参数怎么把输入变成输出。我写模型时遵循一个习惯:在__init__里只搭建层的结构,不做任何实际计算;实际计算全部放到forward里,这样数据和结构分离,每层输入输出的shape一目了然。

以最简单的多层感知机为例:

import torch.nn as nn class MLP(nn.Module): def __init__(self, input_dim=784, hidden_dim=256, num_classes=10): super().__init__() self.net = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, num_classes) ) def forward(self, x): return self.net(x)

nn.Linear代表全连接层,起作用的参数是权重和偏置,都通过nn.Parameter封装。你不需要手动初始化这些参数,因为框架在构建层的时候已经用合理的分布初始化好了。训练过程中如果需要查看或修改某个参数,可以用model.state_dict()拿全部参数的字典,或者用named_parameters()遍历每个参数的名字和数值。这对调试很有用,我可以随时把某一层权重打印出来,确认更新是否正常。

4.3 训练循环:这五步记住就不会乱

训练循环是所有模型训练的骨架,我把它的标准写法提炼成五个固定步骤:取数据、前向计算、算损失、反向传播、更新参数。任何模型,不管结构多复杂,循环体内部的逻辑都逃不出这五步。

model.train() for epoch in range(10): running_loss = 0.0 for batch_idx, (inputs, labels) in enumerate(loader): inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() running_loss += loss.item()

这段代码看起来简单,但每一步背后都有讲究。model.train()把模型切到训练模式,这个模式会影响Dropout和BatchNorm的行为,训练和评估阶段必须切换。criterion通常是nn.CrossEntropyLoss(),我要多提醒一句:这个损失函数自带Softmax操作,所以模型最后一层别额外接Softmax,直接输出logits就行。loss.item()把loss从Tensor里取出来变成普通Python数值,是为了不保留计算图,避免显存不断累积。

训练时最常用到的两个超参数也在这里落地:学习率和batch size。学习率决定每一步参数更新的幅度,初学者我建议从1e-3起步,如果loss不降再往1e-4调;batch size受显存限制,一般从16到64之间选,选太大不一定每次都有收益,选太小则梯度噪声大。学习率、batch size和迭代轮数三者要一起考虑,不是孤立调整的。

4.4 校验、保存与迁移到显卡

模型训练起来之后,下一步是定期做验证。验证阶段唯一的区别是:不要反向传播、不要更新参数,并且要把模型切到model.eval()模式。为了避免验证时意外计算出梯度,我习惯在验证上下文里用with torch.no_grad():包住前向计算逻辑。这两者配合,确保验证阶段的内存占用和计算量都降到最低。

model.eval() correct = 0 total = 0 with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print(f'Accuracy: {100 * correct / total:.2f}%')

保存模型也有两个流派,我推荐用torch.save(model.state_dict(), 'model.pth')的方式,只保存参数,不保存整个模型对象。这样加载时你需要重新构建一次模型结构,再把参数灌进去,格式轻便而且不会绑定Python版本和类定义位置。很多人图省事直接torch.save(model, ...),一年后再回来加载时往往因为类的路径变了而崩溃,教训深刻。

至于迁移到显卡,核心就两行:定义device,然后把模型和所有输入数据都.to(device)。迁移之后你可能会发现显存还是不够,那后续可以考虑梯度累积、混合精度、或者模型简化,这些属于性能调优的话题,先把基础跑通再从长计议。

5. 高频报错与排查思路,照着抄就行

5.1 五个高频问题的症状与解法

我在教学和开发过程中,把新手最容易遇到、也最典型的问题总结成了下面这张速查表:

症状可能原因解决方法
Expected all tensors to be on the same device模型或数据不在同一个计算设备上统一用device = torch.device('cuda'),所有Tensor和模型都.to(device)
维度不匹配(mat1 and mat2 shapes cannot be multiplied)Linear层输入维度与数据实际维度不一致打印中间层输出的shape,沿前向传播逐层核对
Loss输出为nan或inf学习率过大、数据里有异常值、梯度爆炸降低学习率,检查数据是否归一化,或尝试梯度裁剪
Loss在训练早期就卡住不下降学习率太小、特征没有归一化、模型结构有问题先用几十条数据过拟合测试,确认模型能学才上全量数据
保存后在加载时提示ModuleAttributeError保存的是整个模型对象而非state_dict换成torch.save(model.state_dict(), ...)并在加载前构建好新模型

这五类问题占了深度学习初段报错的七成以上。每一次报错具体原因可能略有差异,但排查的路径基本一样:先缩小问题的范围,再看报错的完整堆栈,最后在关键位置插入打印语句。

5.2 万能排查顺序:从前向到梯度

遇到模型不收敛或者报错,我有一套固定的排查顺序,分享出来供你参考。第一步,先检查数据。打印inputs和labels的shape、dtype,确认数据本身没有NaN或无穷值,确认标签的类别数量跟模型输出维度对得上。第二步,检查前向传播。扔一个单batch数据进模型,打印每一层的输出shape,这能快速定位结构性的不匹配。第三步,检查loss计算。确认loss函数接受的输入输出顺序没有搞反,尤其是CrossEntropyLoss期望的第一个参数是模型输出logits,第二个参数是类别索引。第四步,检查梯度。在backward()之后打印几个参数的.grad,如果梯度全是None或者0,说明计算图断了;如果梯度中出现NaN,则往下降学习率或加梯度裁剪。第五步,检查参数更新。打印参数更新前后的数值差值,确认optimizer.step()确实在生效。

这套顺序的核心思想是“从前到后、逐段验证”。每步都确认之后,问题范围就一步步缩小,大多数问题都能在第三步前定位。如果还不行,就打印更多中间变量,或者用torch.autograd.set_detect_anomaly(True)来让框架在反传异常时给出更详细的提示。虽然开启后训练会变慢,但用来排查问题非常高效。

5.3 复现性设置和几个提升效率的小习惯

最后分享几个我长期维护的小习惯。第一,训练脚本开头固定设置随机种子,保证结果可复现:

import random import numpy as np import torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)

第二,训练循环里定期打印loss和当前学习率,不要等到训练完才发现曲线崩了。第三,日志里除了loss,尽量记录验证集准确率或误差,单独追踪训练loss很容易被过拟合的假象误导。第四,在模型定义里主动注明每一层的输入输出维度,这种注释在两周后回看代码时价值巨大。第五,尽量把关键配置项集中放到脚本头部或配置文件中,不要散落在代码各处。

我在实际操作中的体会是,这份框架最强大的地方不在于某个单一功能,而在于它把“研究思路到代码落地”这个过程中的摩擦降到了极低。很多人纠结于要不要把某个中间变量detach出来,要不要手动控制梯度流动,这些问题多跑几次实验、多报几次错,自然就有了手感。最后再分享一个小技巧:当你遇到任何原理上讲不通的报错时,先检查版本一致性——PyTorch版本、Python版本、依赖库版本,有时候问题不是你的代码逻辑错了,而是版本环境不匹配。先把环境锁定,再谈调参,这是我从多次无意义的排查里总结出来的前三条经验之一。

相关新闻

React数据管理平台实战:从技术选型到性能优化

React数据管理平台实战:从技术选型到性能优化

刚拿到“rea”这个项目代号的时候,说实话我愣了一下。没有现成的需求文档,也没有产品背景说明,就这三个字母。按我多年的习惯,遇到这种极简信息的任务,第一件事不是急着写代码,而是先把这个名字拆开&#x…

2026/10/20 13:17:09 阅读更多 →
ComfyUI工作流合集避坑指南:环境对齐、节点检测与批量导入

ComfyUI工作流合集避坑指南:环境对齐、节点检测与批量导入

简介:这份资源是面向AI绘画与自动化流程爱好者的ComfyUI工作流合集,适合希望借助可视化拖拽界面快速搭建图像生成、风格迁移、内容创作等AI应用的用户,无论是否具备编程基础都能上手。压缩包共收录1310个文件,约113.09MB&#xff…

2026/10/20 13:16:08 阅读更多 →
算法基础课学什么?从零搭建算法知识体系与实战指南

算法基础课学什么?从零搭建算法知识体系与实战指南

1. 算法基础课到底在讲什么:从零到一搭建算法知识体系很多人第一次接触算法,脑子里冒出来的第一个念头就是“我是不是得先把数学分析、离散数学、概率论全部啃一遍才能开始”。这个想法不能说错,但绝对会让你在起步阶段就卡死。算法基础课的核…

2026/10/20 13:16:08 阅读更多 →

最新新闻

SecureCRT免安装版配置指南:离线串口调试与SSH便携部署

SecureCRT免安装版配置指南:离线串口调试与SSH便携部署

简介:本资源为SecureCRT绿色便携版免安装包,面向系统管理员、运维工程师及开发人员,解决多平台快速部署安全终端连接工具的需求,尤其适用于需在不同电脑间频繁切换、无管理员权限或追求轻量使用的场景。压缩包共137个文件&#xf…

2026/10/20 14:00:00 阅读更多 →
防火墙规则集时序配置:顺序、生效时间与安全空窗排查

防火墙规则集时序配置:顺序、生效时间与安全空窗排查

防火墙规则集里的时序配置,是我这几年排查安全事件时最常踩的隐形坑。规则明明写对了,但因为顺序放错、时间对象失效、批量下发存在中间态,导致规则的实际生效时间和预期完全对不上,轻则策略空转半个月没人发现,重则直…

2026/10/20 14:00:00 阅读更多 →
Python Pygame核心机制与实战:从零构建接苹果小游戏

Python Pygame核心机制与实战:从零构建接苹果小游戏

很多人在命令行里写过一阵Python脚本之后,总想搞点“能动的、看得见的东西”。Python Pygame模块就是你会碰到的第一块敲门砖:一个基于SDL封装、专门用来做2D图形和游戏的原生库。我第一次用它做窗口程序的时候,印象最深的不是那种“我能控制…

2026/10/20 14:00:00 阅读更多 →
100套可配置ECharts大数据大屏模板工程包

100套可配置ECharts大数据大屏模板工程包

简介:本资源是一套面向数据可视化工程师、前端开发人员及政企数字化项目实施者的专业级大屏模板集合,覆盖社区治理、智慧物业、数字政务、智能交通、金融银行等十余个高需求行业场景,有效解决业务数据实时监控与决策看板快速搭建难题。压缩包…

2026/10/20 14:00:00 阅读更多 →
AI加速剪辑工作流:字幕、粗剪与卡点自动化实战

AI加速剪辑工作流:字幕、粗剪与卡点自动化实战

我见过太多人剪视频,剪到怀疑人生。明明素材拍得挺好,脚本也没什么大问题,但就是一剪一整夜,导出前还在逐帧删吸气声、给字幕换行、把片尾黑场拖到精确位置。你以为是效率问题,其实是你把80%的时间花在了AI 10秒就能搞…

2026/10/20 14:00:00 阅读更多 →
深度学习训练调参实战:梯度、激活函数、学习率与优化器的链路

深度学习训练调参实战:梯度、激活函数、学习率与优化器的链路

入门深度学习时,很多人会被一连串名词劝退:激活函数、优化器、学习率、梯度。我第一次跑模型的时候也觉得它们是四个独立的调参项,今天调一下学习率,明天换一个激活函数,最后模型loss曲线像心电图一样乱跳,…

2026/10/20 13:59:00 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →