新闻详情

新闻详情

首页 / 资讯中心 / 详情

DQN深度Q网络入门:目标网络与经验回放实战

发布时间:2026/10/1 4:39:33来源:尧图网络
DQN深度Q网络入门:目标网络与经验回放实战
1. DQN到底在解决什么问题从查字典到猜答案的转变DQN全称Deep Q-Network中文一般叫深度Q网络。如果你之前接触过强化学习大概率是从Q-Learning入的门那套东西简单说就是在内存里维护一张大表行是状态列是动作格子里填的是在某个状态下做某个动作长期看能拿多少分。这套玩法在小场景里非常好使比如走迷宫、玩格子游戏状态数撑死几千个表格填满就完事了。但一旦场景稍微真实一点这张表就会瞬间爆炸我说的不是夸张是字面意义上的爆炸。举个最直观的例子Atari游戏里的画面如果按210×160的分辨率、每个像素128级灰度来算理论状态数是128的33600次方。这个数字大到什么程度宇宙中的原子总数大概也就10的80次方左右而这个状态数把10的80次方远远甩在后面。你想用表格把这些状态全存下来别说内存不够你就是把全世界的硬盘都堆起来都装不下。所以问题的核心就变成了当状态空间大到无法枚举时我们怎么估计每个动作的价值DQN给出的答案很朴素但极其有效——别查表了用神经网络去猜。你把状态喂进去网络输出每个动作对应的Q值它不需要见过这个状态只要这个状态和它训练时见过的状态在特征上相似它就能给出一个合理的估计。这叫泛化能力是表格法完全不具备的本事。表格法对于没见过的状态就是一片空白而神经网络能根据相邻、相似的状态推断出一个值来。这篇文章我打算从零把DQN讲透不是那种照着论文念一遍的讲法而是按我自己踩坑的顺序来先讲清楚为什么这么设计再讲每个零件背后的动机然后手把手带你搭一个能跑起来的版本最后把那些文档里不会写、但实际调试时天天遇到的问题一条条列出来。不管你是刚看完强化学习入门课的学生还是想把自己项目里的控制逻辑换成学习型方案的老哥看完应该都能直接上手改。适合谁看如果你会一点Python懂神经网络的前向传播和反向传播大概在干嘛那就够了。数学部分我会尽量用生活化的类比带过去不会堆公式吓人。2. 拆开DQN的四个核心零件每个零件为什么必须存在很多人第一次看DQN的论文会觉得这些设计有点为了复杂而复杂凭什么要搞两个网络、搞个池子、还要用个贝尔曼方程绕来绕去。我当初也这么想直到自己写了个只用单个网络的版本训练曲线跟心电图一样乱跳才明白每个设计都是被逼出来的。这一章我把这四个零件逐个拆开讲。2.1 Q网络与目标网络为什么要同时养两个网络先讲最容易被忽视的那个设计目标网络。DQN里其实有两个结构完全一样的网络一个叫在线网络也叫评估网络参数记作θ另一个叫目标网络参数记作θ⁻。前向算Q值、反向更新梯度的只有在线网络目标网络只负责一件事——提供训练目标里的那个未来最大Q值。为什么非要这么干因为如果你只用一个网络训练目标就变成了r γ * max Q(s, a; θ)注意这个目标里带着θ也就是说你在用当前网络的输出当作当前网络的训练目标。这就像什么像一个人照着镜子里的自己学写字镜子里的字会随着你的变化而变化你就永远追不上它。学术点说这叫移动的目标会导致训练极不稳定Q值可能一路飙高到爆炸也可能直接崩到负无穷。目标网络的做法是把θ⁻冻结一段时间比如每1000步把θ的参数直接复制过去这叫硬更新或者每一步都让θ⁻慢慢往θ靠这叫软更新θ⁻ ← τθ (1-τ)θ⁻τ一般取0.005。这样一来在两次更新之间训练目标是相对固定的网络就有了一个稳定的追赶对象。注意硬更新和软更新没有绝对优劣。硬更新的间隔C是个敏感参数C太小起不到稳定作用C太大目标又跟不上实际变化。我自己的经验是简单任务用硬更新、C取1000到10000连续控制类任务用软更新、τ取0.005更稳。2.2 经验回放池把经历存起来反复咀嚼第二个零件是经验回放池英文叫Replay Buffer。它的作用非常直白智能体每走一步就把这条经验(状态, 动作, 奖励, 下一个状态, 是否结束)存进一个队列里训练的时候从这个队列里随机抽一批出来算梯度。不存行不行行就是效果差。原因有两个。第一强化学习的数据是强相关的时序数据你连着走十步这十条数据之间有极强的关联性拿它们去训练神经网络等价于给网络喂了一批高度相似的样本梯度方向会剧烈偏向最近的经历网络就忘记了更早学到的东西。这在深度学习里叫灾难性遗忘。随机采样打破了这种相关性让每批数据尽量独立同分布。第二数据利用率。真实环境里采样是有成本的尤其是机器人这种走一步可能要等电机响应。经验回放让一条数据可以被反复使用几十次相当于变相提高了样本效率。这也是为什么off-policy的方法DQN就是能用回放池而on-policy的方法比如策略梯度那一类用不了——后者只能拿当前策略产生的数据来更新。回放池的大小是个实打实的调参点。太小比如只存1000条那数据的多样性不够还是容易过拟合到最近的经历太大比如存100万条老掉牙的经验还被反复抽到而策略早就变了那些经验的价值估计已经不准了。一般从1万到10万起步简单的CartPole用1万就够复杂点的用10万。2.3 贝尔曼方程整个训练目标的来源DQN的训练目标不是拍脑袋想出来的它来自贝尔曼最优方程。这个方程说的是一个状态动作对的最优价值等于你立刻拿到的奖励加上打折后的下一状态最优价值。写成公式就是Q*(s, a) r γ * max Q*(s, a)γ是折扣因子取值0到1之间控制你多看重未来的奖励。γ0就是纯短视只看眼前这一步的奖励γ0.99就是相当有远见愿意为了100步之后的收益放弃眼前的小利。实际用的时候一般取0.95到0.99。你可以把γ理解成一个人的耐心程度。γ越小越急功近利γ越大越愿意放长线。有意思的是γ太大也有问题因为未来奖励被打了折之后还是要参与求max如果网络对未来的估计有偏差这个偏差会被反复放大训练就容易发散。所以并不是γ越大越好。贝尔曼方程给我们的启发是训练目标可以用当前网络自己算出来这就是所谓的自举bootstrapping。我们不需要知道真实的Q值只需要知道下一步的Q值估计加上实际拿到的奖励就能构造出一个比当前估计更准一点的目标。整个DQN的训练就是让Q(s,a)不断逼近r γ max Q(s,a)的过程。2.4 损失函数与梯度更新怎么把误差反向传回去有了目标剩下的就是标准的监督学习流程了。定义损失函数L(θ) E[ (r γ * max_a Q(s, a; θ⁻) - Q(s, a; θ))^2 ]括号里那一坨叫TD误差时序差分误差本质上是我原本以为这个动作值多少分和实际发生之后我觉得应该值多少分之间的差距。用均方误差去衡量这个差距然后对θ求梯度、反向传播。这里有两个细节值得单独拎出来说。第一计算目标的时候要切断梯度。也就是说r γ max Q(s,a; θ⁻)这一项只当作常数不能让梯度从这里流回去。PyTorch里最省事的写法是with torch.no_grad():包住目标计算那段或者用.detach()。我见过有人忘了这一步结果两个网络的梯度混在一起训练直接崩。第二只有实际执行的那个动作的Q值参与损失计算其他动作的输出不管。这也是DQN区别于普通多分类任务的地方——我们不是要预测所有动作的对错只要修正被选中的那一个。实现上用gather按动作索引把对应那一列取出来就行。3. 手把手搭建一个能跑的DQN从环境到训练循环理论讲完了接下来上硬货。我以CartPole倒立摆为例这几乎是所有人的第一个DQN实验场状态是4维小车位置、速度、杆子角度、角速度动作2个左推、右推目标是让杆子尽量久地不倒。跑通了它你再去换LunarLander或者Atari代码骨架基本不用大改。3.1 选环境与设计状态表达环境选择的原则很简单先用最简的验证代码对不对再往上加复杂度。CartPole的好处是状态维度低、回合短、反馈明确每撑一步给1分倒了就结束。状态表达这一块很多人直接拿原始观测就喂进去了这没问题但有两个坑要提前知道。第一个是数值范围差异CartPole里位置的范围大概是±4.8角速度范围能到±3.4但如果你换成别的环境有的维度是0到1的小数有的维度是几百上千的整数直接混在一起喂给网络大数会把小数淹没掉。我习惯的做法是先做归一化把每个维度按经验范围缩放到大致[-1, 1]区间。第二个坑更隐蔽有些状态量是应该做变换的。比如角度这种周期性变量359度和1度其实只差2度但数值上差了358网络会被这个假距离骗到。这时候用sin和cos两个分量来表示角度会好很多。这个技巧在机器人控制里特别常用。import numpy as np class NormalizeWrapper: # 把观测按给定的高低范围缩放到 [-1, 1] def __init__(self, env, low, high): self.env env self.low np.array(low, dtypenp.float32) self.high np.array(high, dtypenp.float32) def step(self, action): obs, reward, done, info self.env.step(action) return self._norm(obs), reward, done, info def reset(self): return self._norm(self.env.reset()) def _norm(self, obs): # 先裁剪再缩放防止越界观测把归一化搞崩 obs np.clip(obs, self.low, self.high) return 2.0 * (obs - self.low) / (self.high - self.low 1e-8) - 1.0提示归一化的范围最好按环境的理论边界来定而不是按你跑出来的经验边界因为后者会随策略变化而漂移导致网络学到的东西前后不一致。3.2 网络结构怎么定别一上来就上大模型DQN的网络没有想象中那么讲究。CartPole这种4维输入、2个动作输出的任务两层全连接、每层64个单元就足够了。Atari那种从像素输入的任务才需要上卷积一般三层卷积加两层全连接。结构设计的核心考量是输出维度和动作数对齐。网络的输出是一个长度为动作数的向量第i个元素就代表第i个动作的Q值。这种一次前向输出所有动作的设计比每个动作单独跑一次网络效率高太多也方便算max。import torch import torch.nn as nn class QNet(nn.Module): def __init__(self, obs_dim, act_dim, hidden64): super().__init__() self.net nn.Sequential( nn.Linear(obs_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, act_dim) ) def forward(self, x): return self.net(x)别小看这个简单结构我一开始觉得简单任务嘛搞个三层128总更强结果训练速度慢了一倍效果还更差。原因是网络容量超过任务复杂度之后容易过拟合到最近的少数经历上。网络容量和任务复杂度匹配这句话在DQN里比在普通监督学习里更成立。3.3 训练主循环一行一行讲清楚在干嘛训练循环是整个DQN的心脏我把它拆成四段采样、存池、抽批、更新。def train(env, q_net, target_net, episodes500, gamma0.99, lr1e-3, batch_size64, buffer_size10000, eps_start1.0, eps_end0.05, eps_decay500, target_update200): optimizer torch.optim.Adam(q_net.parameters(), lrlr) loss_fn nn.MSELoss() buffer [] # 这里用列表简化工业级请换 deque 或 ring buffer eps eps_start step_count 0 for ep in range(episodes): state env.reset() done False while not done: # 1) epsilon-贪心采样动作 if np.random.rand() eps: action env.action_space.sample() else: with torch.no_grad(): q q_net(torch.tensor(state, dtypetorch.float32)) action int(q.argmax().item()) # 2) 执行并存储经验 next_state, reward, done, _ env.step(action) buffer.append((state, action, reward, next_state, float(done))) if len(buffer) buffer_size: buffer.pop(0) state next_state step_count 1 # 3) 池子够大了才开始学 if len(buffer) batch_size: continue batch random.sample(buffer, batch_size) s, a, r, s2, d zip(*batch) s torch.tensor(np.array(s), dtypetorch.float32) a torch.tensor(a, dtypetorch.int64).unsqueeze(1) r torch.tensor(r, dtypetorch.float32).unsqueeze(1) s2 torch.tensor(np.array(s2), dtypetorch.float32) d torch.tensor(d, dtypetorch.float32).unsqueeze(1) # 4) 算目标注意切断梯度 with torch.no_grad(): next_q target_net(s2).max(dim1, keepdimTrue)[0] y r gamma * next_q * (1 - d) # 终止状态的未来价值为0 q_vals q_net(s).gather(1, a) loss loss_fn(q_vals, y) optimizer.zero_grad() loss.backward() # 梯度裁剪防止偶发的爆炸 nn.utils.clip_grad_norm_(q_net.parameters(), 10.0) optimizer.step() # 5) 目标网络硬更新 if step_count % target_update 0: target_net.load_state_dict(q_net.state_dict()) eps max(eps_end, eps_start - step_count / eps_decay * (eps_start - eps_end))这段代码里有两个地方特别容易写错我当年都翻过车。第一个是(1 - d)这一项。终止状态的下一状态其实是不存在的如果你不给它乘个0网络就会把倒下的那一刻后面的虚无状态也估个分结果就是Q值在临近终止时虚高。第二个是gather的维度问题q_net(s)输出是[batch, act_dim]a必须是[batch, 1]才能对上写成[batch]会静默广播出错。3.4 关键超参数取值的依据不是玄学超参数看着一堆但真正敏感的只有几个。我把它们和我常用的取值整理成表顺便说说为什么这么取。参数常用取值作用与取值依据学习率 lr1e-4 ~ 1e-3太大Q值震荡太小收敛慢如蜗牛Adam比SGD更宽容折扣因子 γ0.95 ~ 0.99看任务的时间跨度回合越长γ越接近1回放池大小1e4 ~ 1e5简单任务1万够复杂任务建议10万起步batch size32 ~ 64再大收益递减还会拖慢每步更新目标更新间隔200 ~ 10000硬更新用软更新则改τ0.005epsilon 起止1.0 → 0.05前期必须充分探索后期保留一点随机防僵死epsilon 衰减步数总步数的30%~50%衰减太快学不到东西太慢又收敛不了这里重点说说epsilon的衰减节奏。很多教程直接写个eps * 0.995每步衰减实际上这样衰减到0.05需要大概600步太快了智能体还没来得及充分探索就变得贪婪学出来的策略往往只覆盖了状态空间的一小角。我的做法是按总训练步数的比例来衰减让探索期占到总时长的三分之一到一半。4. Double DQN一个几乎零成本的改进DQN本身有个被诟病很久的毛病——Q值高估。这个问题不是bug是数学上的必然而且它会导致策略偏向那些被高估得最多的动作而不是真正最好的动作。Double DQN就是冲着这个问题来的改动小到只有一行但效果往往立竿见影。4.1 Q值高估是怎么来的max操作的双重误差叠加我们回顾一下目标的计算y r γ * max_a Q_target(s, a)问题出在max上。假设真实的最优动作价值是10但由于网络估计有噪声四个动作分别被估成了9、10、11、10.5。max一下你取的是11比真实值高了1。这个高估本身没什么但糟糕的是它会进入下一次训练目标下一次的估计又会基于这个偏高值再偏一次误差就这么一层层累积放大了。一句话总结用同一个网络既选动作又评估动作噪声会互相叠加。选动作时倾向于挑噪声偏正的那一个评估时又把这个偏正的值当真于是高估就产生了。4.2 拆开选和评Double DQN的一行改动Double DQN的思路非常优雅让在线网络选动作让目标网络评估这个动作。因为两个网络的参数不完全一致噪声就不容易同时偏正。with torch.no_grad(): # 在线网络决定选哪个动作 next_actions q_net(s2).argmax(dim1, keepdimTrue) # 目标网络给出这个动作的价值 next_q target_net(s2).gather(1, next_actions) y r gamma * next_q * (1 - d)对比一下原版就是先对q_net(s2)求argmax拿到动作索引再用这个索引去target_net(s2)里取值。改动确实就这一处计算开销几乎没增加。4.3 实测对比高估被压下去了多少我在CartPole上做了个粗略对照同一套超参数、同一个随机种子跑了三次取平均。原版DQN的Q值在训练后期稳定在30到40之间而CartPole的单回合最高奖励其实是500左右每步1分最多500步按理说Q值应该能到几百可见原版倾向于低估而不是高估这里要注意Q值的高低和γ、奖励缩放都强相关直接看绝对值没意义要看同一个动作的估计值和它真实回报的差距。更实用的观察方式是看训练曲线的稳定性。实测下来Double DQN的奖励曲线抖动明显更小尤其是训练前期原版那种猛冲一波然后崩下去的情况少了很多。LunarLander上差距更明显原版有时候会卡在负一两百的奖励上反复挣扎Double版本往往能更快摸到200分这个通关线。提示还有一个常被一起用的技巧叫Dueling DQN它把Q值拆成状态价值V(s)和优势A(s,a)两部分再组合。它和Double DQN不冲突可以叠加使用很多实际项目就是这么干的。5. 训练不收敛怎么办常见问题排查实录这可能是全文最实用的部分。DQN训练不起来是常态能一次跑通才是意外。我把这些年遇到的高频问题按症状整理了一遍你能对照着快速定位。5.1 损失不降反升别急着骂代码首先要破除一个误解强化学习的损失和supervised learning的loss不是一回事。监督学习里loss下降代表学得更好但DQN的loss是TD误差它反映的是当前估计和目标之间的差距。就算策略在变好因为目标r γ max Q自己也在动loss完全可能一直在一个不小的数值上震荡甚至上升。那什么时候该警惕如果loss一路飙到1e6、1e8这种量级那肯定是出问题了常见原因有三个学习率太大梯度把参数推飞了。直接降一个数量级试试。忘了切断目标端的梯度。检查torch.no_grad()有没有生效。奖励没有做缩放某些环境下单步奖励是几百乘上γ之后累加目标值直接进入很大数量级网络根本拟合不动。关于奖励缩放我多说一句这是最容易被忽略的一环。我一般会把奖励按经验范围缩到大概[-1, 1]或者[0, 1]之间这样所有超参数的学习率都能通用换个环境不用重新调。5.2 奖励曲线像心电图三类典型形状对应的病因看训练曲线是门手艺形状本身就是诊断信息。第一类是一直贴着地板不动。大概率是探索不足epsilon衰减太快了智能体还没摸到任何有奖励的路径就开始贪婪。解决方法是把epsilon的衰减步数拉长或者干脆前期固定在1.0探够再说。也有可能是奖励太稀疏走一百步才给一次分这时候就得考虑奖励塑形reward shaping或者上HER这类技巧。第二类是冲上去又掉下来反复横跳。这通常是目标网络更新太频繁或者回放池太小导致数据分布剧烈变化。我遇到最夸张的一次是自己写的软更新系数写成了0.5本来想写0.005相当于目标网络每步都在大改结果就是永远在追一个跑得飞快的目标。改回0.005立刻稳了。第三类是缓慢上升但一直达不到预期。这种情况往往是网络容量不够或者gamma设得太小导致看不远。先加大网络宽度试试如果没改善再动gamma。5.3 问题速查表照着查别瞎试症状可能原因处理方向loss 爆炸到百万级学习率过大 / 奖励未缩放降 lr缩放奖励奖励一直贴地板探索不足 / 奖励稀疏延长 epsilon 衰减加奖励塑形奖励反复冲高又崩目标网络更新太频繁增大更新间隔或降低软更新 τ收敛慢、曲线平滑但上不去网络太小 / γ 太小加宽网络调大 γQ 值普遍虚高max 操作高估换 Double DQN训练末期性能倒退回放池过小旧策略数据污染增大池子或按时间加权采样换个环境超参数全失效奖励尺度差异统一做奖励归一化这里面我最想提醒的是最后一行。很多人调好一个环境之后把超参数原样搬到新环境发现完全不行就开始怀疑人生。其实差异往往不在超参数本身而在奖励的尺度。先把奖励归一化做掉你那一套参数的可迁移性会好很多。6. 我踩过的坑那些文档里不会写的细节前面讲的是方法论这一章讲的是真实的动手感受。有些东西写不进教科书但少了它们你的实验就是跑不顺。6.1 关于评估别被训练奖励骗了训练奖励是在epsilon策略下跑出来的里面有随机探索的成分。你拿这个数去看策略好坏会低估真实性能因为随机动作拉低了分数。所以我一直坚持训练时每过N个回合跑一次纯贪心评估eps设为0连跑5到10个回合取平均。这个评估分数才是你判断到底学没学会的依据。我早期犯过一个错训练奖励冲到400多就兴冲冲去部署结果一到真实场景就拉胯。后来加了评估环节才发现那个400分里有一大块是运气——环境恰好给了个容易的初始状态。评估必须多回合取平均而且要尽量和训练用的初始状态分布一致。6.2 关于随机种子一次跑通不算数强化学习的方差大到超乎想象。同一个算法、同一套参数换三个随机种子出来的结果能差好几倍。这是常态别因此怀疑自己的实现。我的习惯是任何对比实验至少跑三个种子取均值和标准差一起画出来。如果两组配置的均值差异还没有种子间的波动大那这个改进就是噪声不值得高兴。6.3 关于调试技巧把网络变笨再看看有个特别管用的调试方法如果训练不收敛先把网络改得极简单比如只有一层线性、几个隐藏单元先让它能过拟合一个极小的任务。如果连过拟合都做不到说明问题出在数据流或者损失计算本身而不是超参数。这招有点像传统编程里的最小可复现示例把变量降到最少问题自己就暴露出来了。另一个习惯是打日志。我一般会记录每步的TD误差、Q值的均值最大值、epsilon的当前值、回放池的占用率每1000步打印一次。问题往往不是突然出现的而是某个指标先悄悄漂了等你发现奖励掉下来的时候已经晚了。还有个我个人觉得挺香的小扩展如果单机训练太慢可以考虑把环境采样和网络更新拆开用一个进程专门跑环境往池子里灌数据另一个进程专门做梯度更新两者通过共享内存通信。这个套路在Ape-X、R2D2那些论文里都用过实现起来无非是多进程加一个带优先级的队列的事对训练速度的提升在复杂环境上相当明显。至于优先经验回放Prioritized Experience Replay它按TD误差的大小给样本排优先级误差大说明这条数据对网络还有信息量被抽中的概率就高一点。思路很简单但实现上要维护一个带权重的抽样结构还牵扯到重要性采样权重的修正属于能提效果但增加复杂度的选项。你要是刚入门我建议先老老实实把均匀采样跑稳再考虑上这些进阶玩意儿不然调试起来会同时面对两个变量非常折磨。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Java多人联机飞机游戏源码解析:Socket通信与多线程实战 2026/10/1 5:42:28

Java多人联机飞机游戏源码解析:Socket通信与多线程实战

简介:本资源为基于Java语言开发的多人联机飞机游戏完整源码,包含客户端与服务器端两大部分,面向具备一定Java基础、希望深入理解网络编程与游戏架构的开发者及计算机专业学生。项目采用客户端/服务器分离设计,客户端负责界面渲染、…

阅读更多 →
设备AI接管自查清单:从数据到决策的六维评估指南 2026/10/1 5:42:28

设备AI接管自查清单:从数据到决策的六维评估指南

您的设备,AI能接管吗?给老板的一张自查清单凌晨三点,值班电话响了:产线三号线停了。你在电话里听完工程师的通报,脑子里的第一反应是——传感器数据显示恒温区温度漂了0.8度,但PLC没报警,机械臂…

阅读更多 →
TongWeb 7.0.4.9企业版安装部署全流程指南 2026/10/1 5:42:28

TongWeb 7.0.4.9企业版安装部署全流程指南

1. 先摸清TongWeb的定位:这个中间件适合哪些场景、7.0.4.9值不值得装如果你接到过“在Linux上装一套TongWeb 7.0.4.9企业版”这种任务,多半是项目里要用国产中间件跑Java Web应用。TongWeb是东方通的产品,和Tomcat、WebLogic、WebSphere算同类…

阅读更多 →
Madeira:Apple平台Windows应用兼容层技术解析 2026/10/1 5:42:28

Madeira:Apple平台Windows应用兼容层技术解析

1. 项目概述:Madeira 不是马德拉酒,而是 Wine 在 macOS/iOS 生态中的深度适配探索 最近在多个技术社区和开发者群聊里,“Madeira”这个词频繁跳出来,和 Wine、FEX-Emu、DXMT、iOS 这几个关键词紧密捆绑。一开始我也以为是葡萄牙那…

阅读更多 →
Codex、Claude Code、OpenCode 接入火山方舟 API 指南 2026/10/1 5:42:28

Codex、Claude Code、OpenCode 接入火山方舟 API 指南

Codex、Claude Code、OpenCode 这三个终端 AI 编程工具最近确实火,但它们默认绑定的模型链路对不少人来说都不太顺——要么受账号环境限制,要么订阅成本高,要么想用 DeepSeek 这类模型却不知道怎么接。我花了一天时间,把这三个工具…

阅读更多 →
串口与TCP相互转发工具:原理、实现与远程调试实践 2026/10/1 5:42:21

串口与TCP相互转发工具:原理、实现与远程调试实践

上周在客户现场调试一台边缘网关,设备只留了一个RS232串口,我人却在几百公里外的办公室。串口调试助手、USB转串口线这种常规武器全派不上用场。后来我把思路改了:在网关边上放一台小主机,写了一个"串口和TCP互相转发工具&qu…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉