PyTorch深度学习实战:张量、自动微分、多卡训练与部署避坑指南
如果你和我一样最早是从 Numpy 和手写梯度下降开始接触深度学习的那第一次真正上手 PyTorch多半会有一种“好像也没那么难”的感觉。张量、自动微分、模型模块化这套设计把深度学习最日常的三件事——处理数据、搭网络、调参数——都变成了可读性很高的 Python 代码。这篇内容不是官方教程的复读而是我在多个落地项目里反复用到、也被反复坑过的 PyTorch 实践经验总结。主要适合两类人一类是刚把 Python 和基础机器学习概念学完、准备开始写第一个深度模型的学习者另一类是已经能跑通 demo但遇到显存溢出、NaN loss、加载模型报错、多卡训练起不来这类工程问题时会懵的开发者。我不想做概念陈列直接按“从数据到训练、从单卡到多卡、从训练到部署”这条主线把真正值得留意的细节和排查思路写清楚。1. 从 Numpy 思维切换到 PyTorch 思维最重要的一步是理解张量1.1 张量不只是多维数组它自带设备与梯度信息很多从 Numpy 过来的人会用“多维数组”去理解 Tensor这个方向没问题但不够。PyTorch 的张量比 Numpy 数组多了两个非常关键的信息它知道自己现在在哪块设备上也知道自己是否需要计算梯度。前者决定了你的数据是在 CPU 内存还是 GPU 显存里后者决定了 autograd 这张计算图要不要为它记录操作历史。我见过不少人在这一步吃了亏。比如在某次训练里我习惯性地用torch.as_tensor(data)把一批数据处理成张量然后直接丢给模型结果报错说“CPU tensor cannot be moved”。你不是非得每一步都手动.to(device)但你必须对张量的 device 有掌控感。设备不一致的问题在训练后期排查起来很麻烦与其报错了再猜不如从一开始就养成一个习惯device torch.device(cuda if torch.cuda.is_available() else cpu) data torch.as_tensor(raw_data).to(device)另一个容易忽略的点是requires_grad。默认情况下从数据转换来的张量是requires_gradFalse的只有当你用torch.randn(..., requires_gradTrue)或者通过可求导运算得到某个张量时它才会被放进计算图里。手动实现线性回归时这个区别尤其明显。1.2 用最简单的线性回归把 autograd 跑通与其背概念不如直接看一段小代码。假设我们要拟合y 2x 0.3加了一点噪声。用 PyTorch 手动实现参数更新不引入nn.Module整个流程是这样的import torch x torch.linspace(-1, 1, 200).reshape(-1, 1) y 2 * x 0.3 torch.randn_like(x) * 0.02 w torch.randn(1, 1, requires_gradTrue) b torch.zeros(1, requires_gradTrue) lr 0.1 for step in range(1000): pred x w b loss ((pred - y) ** 2).mean() loss.backward() with torch.no_grad(): w - lr * w.grad b - lr * b.grad w.grad.zero_() b.grad.zero_()这段代码最值得琢磨的不是数学而是loss.backward()之后发生了什么。backward()会从 loss 出发沿着计算图反向传播梯度把每个需要梯度的叶节点张量的.grad填上数值。然后我们用with torch.no_grad():来更新参数这等于告诉 PyTorch这一段不要继续追踪计算图的构建因为参数更新不应该被记录进下一次的梯度计算里。最后必须w.grad.zero_()和b.grad.zero_()。否则梯度会累加到下一次反向传播中。这也是新手最容易犯的错误第一次 loss 正常下降第二次开始乱跳大概率就是忘记清零梯度。zero_()里的下划线表示原地操作直接修改张量本身这也是 PyTorch 风格里一个常见记号。1.3 为什么说动态图让调试回归了 Python 直觉PyTorch 的计算图是动态构建的也就是说你每跑一次 forward计算图就新生成一次。这对于调试来说是巨大的利好你能在loss.backward()之前随意打印中间张量的形状和数值可以用普通的 Pythonif控制流来改变模型结构不需要预先定义一个静态图。对比“先构图后执行”的思路PyTorch 的设计更贴近程序员的直觉。但这种自由也有代价。因为图是动态的某些错误只在特定输入形状或特定分支下才会暴露。如果模型里有复杂的if分支或for循环你要格外留意每个分支的张量形状是否一致。我自己的习惯是数据形状如果不是特别复杂就在 forward 的开头加几行注释把期望的输入和输出形状写清楚。模型跑久了这种文档能省掉大量回忆成本。2. 搭起一套能用的训练循环Dataset、Model、Optimizer 的协作关系2.1 DataLoader 的 num_workers 和 pin_memory 不是摆设单机上跑深度学习最容易被低估的是数据管线。很多人写一个 Dataset 子类然后直接DataLoader(dataset, batch_size32)就开训等到 GPU 利用率上不去才回来调参。其实 DataLoader 的几个参数对训练吞吐量有很大影响。num_workers控制的是数据加载时使用多少个 worker 进程。如果你的__getitem__里有大量图像解码、缩放、数据增强把它设成 0 意味着主进程边加载边训练GPU 很容易饿着。一般我会从num_workers2或4开始试同时观察 CPU 和 GPU 利用率。注意 worker 数量不是越大越好因为每个 worker 会持有数据副本内存占用会上升另外在多卡训练时worker 数量还和整体 CPU 资源相关设得过高反而可能拖慢。pin_memoryTrue是一个不太起眼的开关但如果你在 GPU 上训练推荐打开。它的作用是让 DataLoader 把数据放进锁页内存后续从 CPU 拷贝到 GPU 时可以用异步传输减少阻塞时间。有些开源代码默认不写这个参数我通常会在自己的训练脚本里加上。还有一个常被忽略的参数是drop_last。如果数据集大小不能被 batch size 整除最后一个 batch 会偏小。对 BatchNorm 这类依赖 batch 统计量的层来说最后一个小 batch 会让均值和方差估计抖动。训练时我会设置drop_lastTrue保证每个 batch 大小一致验证时用drop_lastFalse把全部数据都评估完。2.2 nn.Module 里最常见的两个结构误区搭模型时nn.Module看似只是把层放在__init__里、在forward里调用但有两个结构性问题我见得最多。第一个问题是把子模块放进普通的 Python 列表里。比如class BadModel(nn.Module): def __init__(self): super().__init__() self.layers [nn.Linear(64, 64) for _ in range(3)] def forward(self, x): for layer in self.layers: x torch.relu(layer(x)) return x这段代码不会直接报错但self.layers里的 Linear 层不会被model.parameters()识别因为nn.Module只自动注册nn.ModuleList、nn.Sequential这类“被明确声明”的子模块。结果就是 optimizer 拿不到这些层的参数训练时权重完全不更新。解决办法是把列表改成nn.ModuleList或者直接用nn.Sequential组织。第二个问题是把forward写得太复杂所有 Python 逻辑都堆在里面。forward当然可以有控制流但如果你发现一个 forward 里有五六个if、两个for还夹杂着各种中间张量的原地修改那就要警惕了。调试成本会随着分支数量指数级上升。更好的做法是尽量把稳定不变的部分用nn.Sequential组织把灵活变化的部分单独抽成方法保持 forward 的可读性。2.3 训练循环里一步都不能乱的三个动作标准训练循环看起来很简单但每个动作的顺序有讲究。我的习惯是optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step()先清空上一步的梯度再反向传播算梯度最后用优化器更新参数。这三个动作的顺序不能乱。清空梯度一定要放在backward之前否则会把这次刚算出来的梯度清掉。梯度裁剪放在backward之后、optimizer.step之前它防的是梯度爆炸而不是直接改 loss。很多人会在一个循环里手动调用loss.backward()多次。如果是为了梯度累积比如每 4 个小 batch 累积更新一次那要记得在optimizer.step()之后再zero_grad()同时注意 loss 除以累积步数的方式。如果只是误操作把 backward 放在一个不需要梯度的验证阶段里那会白费很多显存。验证阶段一定要包在torch.no_grad()或torch.inference_mode()里面否则计算图会在推理时被保留显存占用会异常高。3. 训练期“炸炉”现场显存、NaN 和设备混用的完整排查链路3.1 从一个 OOM 报错回溯到数据管线的 batch size“CUDA out of memory”应该是训练时最常见的崩溃之一。很多人的第一反应是调小 batch size这当然没错但如果你不做定位只是盲目调小可能反复试很多次才能跑起来。我自己的排查思路是先算一次理论显存占用再决定改哪里。一个简单公式只考虑张量本身参数量乘以每个参数的字节数再考虑中间激活值。最典型的是卷积层输出特征图如果输入是[batch_size, 3, 224, 224]float32 类型那么一个 batch 的输入张量本身占用的显存大约是32 * 3 * 224 * 224 * 4字节约 18 MB。听着很小但每一层卷积输出的特征图、反向传播需要的中间张量都会各占一份一个有几十层的网络累积下来就是几个 GB。你不用手动算得特别精确只要大致估算当前 batch size 对应的规模再对比可用显存就能确定是不是 batch size 的问题。另一个容易造成“假 OOM”的地方是计算图没有被及时释放。如果在验证阶段忘了包torch.no_grad()或者在一个 Python list 里保存了每个 iteration 的 loss那么这些张量和图会一直留在显存里。遇到这种情况torch.cuda.empty_cache()只能清掉缓存不能释放被强引用持有的内存关键还是找到谁在持有张量。你可以用torch.cuda.max_memory_allocated()对比前后两次内存峰值快速判断是模型独占还是数据管线的多余占用。3.2 loss 变成 NaN 时按“数据→模型→数值”的顺序找根因NaN loss 是个让人头大的问题因为表面看起来一切正常loss 却突然变成nan。我一般按“数据→模型→数值”三层顺序排查。先看数据。训练样本里是否存在无穷值或缺失值如果输入里有 NaN 或 inf反向传播算出的梯度几乎必然会被污染。在处理文本和表格数据时尤其容易发生某列有空值没处理分词结果里有空 tensor数值特征归一化时除数为零。先把数据里的异常值扫一遍很多时候问题就没了。再看模型。输出层后接的 loss 函数是否稳定比如手动实现交叉熵时如果先对 logits 做softmax再用log当 softmax 输出接近 0 时log(0)会得到负无穷loss 就是 NaN。更稳妥的做法是直接用torch.nn.functional.cross_entropy它在内部做了数值稳定的 log_softmax。同理如果是自己写损失函数要检查有没有潜在的对数运算或除法运算。最后看数值稳定性。网络层数深、梯度累乘后可能爆炸梯度裁剪是一种常见解法。我还会检查学习率是不是太大尤其是使用 Adam 时初始学习率设成0.1这种“看起来不太大”的值实际很容易把 loss 打飞。还有一个容易忽略的细节某些激活函数在输入极大时梯度消失或爆炸比如 sigmoid如果网络初始化的权重范围不对深层的激活值可能迅速饱和。3.3 device 不一致引发的报错为什么不看堆栈也能猜出来PyTorch 最经典的报错之一是一长串“Expected all tensors to be on the same device”。这个报错出现时第一反应不是去读堆栈而是检查三类对象的 device模型参数、输入数据、目标标签。很多人在把模型搬到 GPU 后忘了把每个 batch 的输入也搬到 GPU。DataLoader 返回的是 CPU 张量需要手动执行batch.to(device)。这个问题在 CPU 单机上永远不会暴露一旦切到 GPU第一次迭代就会报。还有一个相对隐蔽的点模型内部如果有“不是通过nn.Module注册的缓冲区”比如自定的register_buffer在保存加载时也要确保它被正确搬到目标设备。我习惯在一开始就定义一个统一的 device 变量然后让模型和数据都通过它来搬运device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) for batch in dataloader: xb, yb batch[0].to(device), batch[1].to(device)如果你经常在几种设备之间切换还可以在测试时打印几个关键张量的device属性。这个习惯可以帮你把 device 相关的问题扼杀在摇篮里而不是等训练跑到一半才爆出来。4. 让训练可以打断重来模型保存与断点续跑的工程细节4.1 只保存 state_dict 还是连优化器一起保存保存模型时最稳妥的做法是torch.save(model.state_dict(), model.pt)而不是直接把整个model对象保存起来。直接保存模型对象虽然代码少但要求加载环境的代码结构完全一致类定义稍有改动就可能加载失败。state_dict只保存参数和缓冲区本质上是一个从名字到张量的映射加载时用load_state_dict塞回模型灵活得多。那什么时候才需要连优化器一起保存答案是断点续跑。如果你只保存了model.state_dict()重新训练时优化器里的动量、Adam 的矩估计全部丢失学习率调度器也要从头开始。短期训练无所谓但训练一个跑一两天的大模型时这些附加状态很关键。优化器状态里往往包含跟每个参数对应的额外张量文件体积比模型参数还大但这是必要的。4.2 一份真正可靠的 checkpoint 至少包含四样东西我一般把 checkpoint 设计成一个字典包含当前训练进度和所有关键状态。除了模型参数、优化器参数还要记录 epoch、全局 step、当前最佳评估指标以及必要的随机种子状态。这样即使断电中断也能恢复到上一次保存的精确状态。checkpoint { model: model.state_dict(), optimizer: optimizer.state_dict(), scheduler: scheduler.state_dict(), epoch: epoch, global_step: global_step, best_score: best_score, rng_state: torch.random.get_rng_state(), } torch.save(checkpoint, checkpoint_last.pt)加载时注意用map_location参数把张量映射到正确设备。比如你之前用多卡训练checkpoint 里的张量可能在cuda:0现在只有一块卡直接加载可能报错或占用不该占的卡。我常用的写法是ckpt torch.load(checkpoint_last.pt, map_locationdevice) model.load_state_dict(ckpt[model]) optimizer.load_state_dict(ckpt[optimizer]) epoch ckpt[epoch] global_step ckpt[global_step]从 checkpoint 恢复后第一件事就是跑一次验证集确认 loss 和评估指标的数值和中断前的记录对得上。如果差得很远说明恢复过程中漏了某些状态比如随机种子没有恢复、数据集的 shuffle 顺序发生了变化。4.3 加载预训练权重时遇到键名不匹配的通用解法加载别人训练好的权重时最常见的报错是Missing key(s) and unexpected key(s)。背后的原因通常是两类一是模型结构不完全一样比如输出类别数不同二是键名前缀不一致比如原模型用DataParallel包装过state_dict 的每个键前面都多了一个module.。面对这种情况我很少用strictFalse一关了之而是做一个“按形状过滤”的加载逻辑pretrained torch.load(pretrained.pt, map_locationcpu) model_state model.state_dict() for k, v in model_state.items(): if k in pretrained and pretrained[k].shape v.shape: model_state[k] pretrained[k] model.load_state_dict(model_state)这段代码的意义在于名字匹配且形状匹配的层就加载其他层保持随机初始化。如果你的模型只是在最后一层改了分类头这个逻辑可以自动把前面所有兼容层的权重迁过来。如果键名只是差一个module.前缀可以先把 pretrained 的键名做一次去前缀处理再执行同样的过滤流程。5. 当单卡不够用时从单机多卡到分布式训练的平滑过渡5.1 torch.nn.DataParallel 能跑但为什么不推荐单卡显存不够时很多人第一反应是torch.nn.DataParallel(model)。它确实能让多卡跑起来代码改动也很少但它的工作方式是单进程多线程前向传播时把 batch 切到多张卡再把结果汇总回主卡。主卡既要负责梯度归约又要负责输出汇总通信很快成为瓶颈多卡加速比很难看尤其是小 batch 的场景。更关键的是DataParallel 对模型内部状态的处理比较粗比如某些自定义操作在device_ids上的处理容易出问题。当你真的需要稳定训练、单机多卡甚至多机多卡时应该切换到DistributedDataParallel也就是 DDP。它的设计是每个进程一张卡进程之间通过通信库同步梯度扩展性和性能都好得多。5.2 用 torchrun 启动 DDP 的三个小时踩坑记录我第一次用 DDP 时花了不少时间在处理初始化上。后来发现最省事的启动方式是torchrun它负责设置RANK、LOCAL_RANK、WORLD_SIZE这些环境变量你只需要在脚本里读取并初始化进程组。一个最小可运行的 DDP 训练脚本大概长这样import os import torch import torch.distributed as dist import torch.multiprocessing as mp from torch.nn.parallel import DistributedDataParallel as DDP def main(): dist.init_process_group(backendnccl) local_rank int(os.environ[LOCAL_RANK]) torch.cuda.set_device(local_rank) model MyModel().to(local_rank) ddp_model DDP(model, device_ids[local_rank]) train_dataset MyDataset() train_sampler torch.utils.data.distributed.DistributedSampler(train_dataset) dataloader torch.utils.data.DataLoader( train_dataset, batch_size32, samplertrain_sampler, num_workers4, pin_memoryTrue, ) optimizer torch.optim.Adam(ddp_model.parameters(), lr1e-3) for epoch in range(10): train_sampler.set_epoch(epoch) for xb, yb in dataloader: xb, yb xb.to(local_rank), yb.to(local_rank) pred ddp_model(xb) loss torch.nn.functional.cross_entropy(pred, yb) optimizer.zero_grad() loss.backward() optimizer.step() dist.destroy_process_group() if __name__ __main__: main()启动命令是torchrun --nproc_per_node4 train_ddp.py这里的--nproc_per_node表示每台机器上用几张卡。最容易踩的坑有三个。第一忘记train_sampler.set_epoch(epoch)导致每个 epoch 的数据划分顺序相同不仅影响随机性还可能降低训练效果。第二多个进程同时往同一个路径写 checkpoint文件会互相覆盖。要约定只有rank 0的进程才执行保存逻辑。第三dist.init_process_group的 backend 在多机环境下通常用nccl它要求所有进程的网络能互通真实的多机环境里还要留意端口和网卡设置。5.3 多卡训练里随机种子和 batch size 之间容易忽略的关系多卡训练时数据会被DistributedSampler自动切成多份每个进程只处理自己的子集。正因为如此很多人会忽略一个细节如果你在 Dataset 里做随机数据增强不同进程的随机状态如果完全相同那么相同索引的样本经过的增强操作可能完全一样等于变相缩小了每个 epoch 看到的数据多样性。我常用的做法是给每个进程设置不同的随机种子偏移base_seed 42 local_rank int(os.environ[LOCAL_RANK]) torch.manual_seed(base_seed local_rank)这样既能让整体实验可复现又能保证每个卡上的随机增强过程不完全一致。另外要注意的是DDP 的总 batch size 是“每卡 batch size × 卡数”。如果你原来是单卡 batch 128改成 4 卡后想让总体 batch 保持 128每卡 batch 就是 32反之每卡 128 的话总 batch 就是 512。总 batch 变大后学习率是否需要调整取决于具体任务但至少要在日志里记清楚当前实际总 batch否则后续调参容易误判。6. 训完只是开始推理提速与导出部署的实用心得6.1 半精度推理的收益与注意点训练收敛之后往推理阶段优化是另一个常见需求。最直接的一步就是把模型和数据切到半精度。对大多数深度学习模型来说float16 推理能让显存占用减半同时在某些 GPU 上显著提速。但半精度不是无脑切换。使用model.half()后模型内部所有参数都会变成 float16输入数据也要转成 float16否则前向传播会报类型不匹配。更稳妥的方式是用 PyTorch 提供的自动混合精度上下文model model.eval().to(device) with torch.inference_mode(): with torch.autocast(device_typecuda, dtypetorch.float16): pred model(input_tensor)autocast会智能地把部分运算保持高精度把部分运算切到半精度比手动全部转 float16 更稳妥。如果你是 CPU 推理半精度可能不会快甚至更慢需要先确认当前推理设备和算子库对半精度的支持情况。6.2 torch.compile 不是银弹但值得先试一下PyTorch 2.x 之后最值得关注的新特性就是torch.compile。你只需要把原来的模型包一层model torch.compile(model)在其他代码不变的情况下编译后的模型在部分网络结构上能获得明显加速尤其是层结构规整、没有太多动态控制流的模型。我第一次试用时大概只改了这一行推理延迟就降了 20% 左右。但torch.compile不是银弹。它第一次调用时需要花时间做编译和图优化而且有些模型结构会导致编译时间很长甚至因为动态 shape 而触发不必要的重新编译。最简单的策略是先保留原始模型单独写一个基准测试脚本对比model.eval()和torch.compile(model)在不同 batch size 下的延迟再决定是否在正式环境里启用。如果你用的是比较新的 PyTorch 版本遇到编译相关的报错也比较常见这时可以先升级到稳定版或者尝试设置不同的mode比如torch.compile(model, modereduce-overhead)。6.3 ONNX 导出时最常遇到的动态维度问题把 PyTorch 模型部署到服务端或移动端导出 ONNX 是常见路径。torch.onnx.export用起来不复杂dummy_input torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy_input, model.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, )这里最容易出问题的是动态维度。如果你不设置dynamic_axes导出模型会完全绑定 dummy input 的形状比如 batch 只能是 1一旦线上请求的 batch size 变成 4推理框架就会报 shape 不匹配。设置dynamic_axes可以解决这个问题代价是某些优化器可能无法对动态 shape 做深度优化。所以我的建议是如果业务里 batch size 固定就不要动态化如果一定需要变长 batch再开动态维度。另一个常见问题是模型里有 Python 控制流比如if x.shape[0] 10:或者for循环依赖张量值。这类逻辑在导出时无法被完整表达ONNX 导出器通常会直接报错或导出成不正确的图。如果遇到这种问题优先把控制流从模型主体中拆出来放到调用侧去处理让模型的输入输出保持简单。最后补充一个我自己的使用习惯导出后不要直接部署先用onnxruntime加载导出文件跑一遍和 PyTorch 原模型相同的输入对比输出差异。两次结果会有数值偏差但如果平均绝对误差大得不正常大概率是某个算子导出的行为不一致比如归一化层的位置、常量折叠方式等。这个验证步骤通常只需要几分钟却可以避免把坏模型直接带到线上。关于 PyTorch 我能分享的实操经验还有很多很多但最核心的还是那句话框架只是工具真正决定项目质量的是你在训练循环前对数据、模型、设备状态有没有掌控感。每次遇到问题先冷静拆解是数据、模型、数值还是通信层面的原因再动手改比盲目调参有效得多。以后如果再碰到显存溢出或加载 checkpoint 的诡异报错不妨回想一下这篇里的排查顺序你会发现大部分问题都是那几个典型原因在反复变着花样出现。

相关新闻

context-mode实战:大模型对话上下文管理的五种模式与优化策略

context-mode实战:大模型对话上下文管理的五种模式与优化策略

1. context-mode是什么:先搞懂问题,再谈方案1.1 我一开始理解的“上下文”其实全是错的我最早做对话类应用时,“上下文”在我脑子里就是“把聊天记录拼起来,一起发给模型”。于是代码里写了一个简单的列表,每轮聊完就往…

2026/10/15 6:22:44 阅读更多 →
跨端迁移到OpenHarmony:Redux Toolkit选择器避坑与性能优化

跨端迁移到OpenHarmony:Redux Toolkit选择器避坑与性能优化

最近我把一个基于 React Native 的跨端应用往 OpenHarmony 平台上迁移,整个过程中踩坑最多的并不是 UI 组件适配,也不是原生模块桥接,反而是状态管理里一个看似不起眼的环节——Redux Toolkit 选择器的使用。今天就把这块的实践经验完整梳理一…

2026/10/15 6:22:44 阅读更多 →
Transformer论文精读:自注意力机制与训练实战全解析

Transformer论文精读:自注意力机制与训练实战全解析

简介:这是一份围绕《Attention Is All You Need》论文制作的Transformer与Attention机制讲解PPT,适合NLP初学者、算法工程师及组会汇报场景。内容系统梳理Transformer的提出背景,针对RNN并行困难与CNN局部感受野问题,逐层拆解Self…

2026/10/15 6:22:44 阅读更多 →

最新新闻

C++多态原理:编译时与运行时多态及虚函数机制详解

C++多态原理:编译时与运行时多态及虚函数机制详解

1. 从形状计算看多态的本质:为什么我们需要它前两天整理 C 多态笔记时,我想起了自己在某个图形项目里写过的一段“黑历史”:刚开始接触面向对象时,我以为多态就是“给函数加个 virtual”,结果写出来的形状面积计算全是…

2026/10/15 7:01:52 阅读更多 →
express校园通全栈项目:从需求拆解到部署上线

express校园通全栈项目:从需求拆解到部署上线

做校园类项目的人不少,但能把“校园生活助手”做到真正能上线、能过答辩、能作为后面求职作品展示的,往往不是功能堆得最多的那个,而是把后端接口、前端交互、部署运维这条链路都理清楚的那个。express“校园通”校园生活助手APP这个题目&…

2026/10/15 7:01:52 阅读更多 →
TensorFlow实战指南:从核心设计到生产部署的完整解析

TensorFlow实战指南:从核心设计到生产部署的完整解析

1. 从一个“老古董”框架说起:为什么今天还要聊TensorFlow前几天有个刚入行的朋友问我,现在新项目到底该选TensorFlow还是PyTorch。这问题要是放在五年前,答案可能一边倒;但放到今天,情况变得微妙起来。我自己从2016年…

2026/10/15 7:01:52 阅读更多 →
第2章:Linux 基础,clear 命令

第2章:Linux 基础,clear 命令

专栏导航 上一篇:第2章:Linux 基础,whoami 命令 回到目录 下一篇:无 本节前言 对于本节所讲解的知识,有可能,你会需要时不时地参考本专栏的其它文章。真的遇到了需要参考之前的文章的知识点&#xff0c…

2026/10/15 7:01:52 阅读更多 →
RocketMQ消费组内Tag不一致的坑:消息丢失与进度跳跃的底层原理

RocketMQ消费组内Tag不一致的坑:消息丢失与进度跳跃的底层原理

1. 先复盘:同一个消费组设不同Tag的迷惑现场1.1 你会遇到的那些"奇怪现象"做消息中间件的时间久了,碰到"同一个消费组设置不同tag出现奇怪现象"这类问题频率其实很高。前前后后我至少见过五种典型表现:同一个消费组里&am…

2026/10/15 7:01:52 阅读更多 →
医疗健康大数据平台实战:从数据治理到智能应用

医疗健康大数据平台实战:从数据治理到智能应用

做了几年“基于大数据的医疗健康智能管理”方向的东西,最大的感触是:这个领域最难的从来不是算法,而是把数据从各个角落里捞出来、洗干净、排整齐。跟很多同行聊下来,大家都遇到过类似的尴尬——领导看完大屏演示,问了…

2026/10/15 7:00:51 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →