C++从零手写CNN与全连接网络:MNIST实战与底层原理详解
简介这份资源面向深度学习初学者与具备一定C基础的开发者用纯C从零实现卷积神经网络与全连接神经网络并读取MNIST手写数字数据集完成训练与精度验证帮助读者绕开框架封装直击底层原理。压缩包共30个文件约11MB以cpp与h/hpp源码为主体辅以sh编译脚本、yml与SConstruct构建配置以及MNIST的ubyte图像与标签数据文件目录按include、src、conf、mnist等模块划分便于对照阅读。已有85人学习下载。跟随代码可理解卷积核、池化、权重初始化、ReLU、交叉熵与梯度下降等关键环节掌握数据归一化、批次划分与测试集精度评估流程同时体会C内存管理与计算效率的取舍为后续AI项目打下实践基础。1. 从零手写 CNN 与全连接网络这份 C 深度学习资源到底能跑出什么很多人第一次接触深度学习都是从 Python 和 PyTorch 起步的调包调久了反而对反向传播里那些矩阵到底怎么乘、卷积核怎么滑、梯度怎么回传越来越模糊。这份资源走的是另一条路用 C 从零实现全连接神经网络和卷积神经网络直接读取 MNIST 数据集训练模型并验证精度。它解决的不是“快速出结果”而是“把黑匣子拆开给你看”。适合已经会一点 C、想搞懂 CNN 底层计算细节的人也适合被 Python 框架惯坏、想回头补原理的从业者。整个项目不依赖任何深度学习框架矩阵运算、激活函数、损失函数、优化器全部手写编译完就是一个能跑 MNIST 的可执行程序。2. 环境搭建与 MNIST 数据准备把训练入口先跑通2.1 编译环境与依赖选择这份代码是纯 C 实现没有用到 CUDA也没有依赖 Eigen 或 OpenBLAS 这类第三方矩阵库所有矩阵乘法都是手写循环。好处是依赖极简坏处是编译器和标准版本要选对。我一般会用 g 9 以上或者 MSVC 2019 以上标准开到 C11 就够因为代码里用到的特性基本停留在智能指针和 lambda 这一层。在 Linux 或 macOS 下直接一条命令就能编译g -stdc11 -O2 -o mnist_train \ main.cpp \ layer.cpp \ conv_layer.cpp \ fc_layer.cpp \ matrix.cpp \ mnist_loader.cpp这里-O2很关键。手写矩阵乘法在没有优化的情况下MNIST 跑一轮可能要几分钟开了-O2之后循环展开和向量化会让速度提升一个量级。如果你在 Windows 上用 Visual Studio把项目配置成 Release 模式优化选项选/O2否则 Debug 模式下跑 6 万张图会慢到让你怀疑代码写错了。提示不要用-O0去调试训练速度那只会让你误判算法复杂度。调试逻辑用-O0测性能必须切-O2。2.2 MNIST 数据集的读取与格式解析MNIST 原始文件是 IDX 格式不是图片也不是 CSV。训练集包含四个文件train-images-idx3-ubyte、train-labels-idx1-ubyte测试集对应t10k-images-idx3-ubyte、t10k-labels-idx1-ubyte。很多人第一次读会翻车因为文件头有大端序的魔数和维度信息直接按字节读会得到乱码。读取的核心逻辑是这样// 读取 MNIST 图像文件头解析魔数和维度 uint32_t readBigEndian(std::ifstream f) { unsigned char buf[4]; f.read(reinterpret_castchar*(buf), 4); // IDX 文件使用大端序需要手动拼接 return (buf[0] 24) | (buf[1] 16) | (buf[2] 8) | buf[3]; } void loadMNISTImages(const std::string path, std::vectorfloat images) { std::ifstream f(path, std::ios::binary); uint32_t magic readBigEndian(f); // 应为 2051 uint32_t count readBigEndian(f); // 图像数量 uint32_t rows readBigEndian(f); // 28 uint32_t cols readBigEndian(f); // 28 images.resize(count * rows * cols); for (size_t i 0; i images.size(); i) { unsigned char pixel; f.read(reinterpret_castchar*(pixel), 1); images[i] pixel / 255.0f; // 归一化到 [0,1] } }这段代码里有两个参数值得注意。一个是magic图像文件固定是 2051标签文件是 2049读出来不对说明文件下错了或者被解压坏了。另一个是归一化除数 255.0fMNIST 像素范围是 0 到 255不归一化的话第一层全连接的输入值太大sigmoid 或 tanh 直接饱和梯度接近零训练会卡住不动。这是新手最常见的翻车点之一。标签文件读取类似只是每个样本只有一个字节取值 0 到 9。读完之后通常要做 one-hot 编码因为输出层是 10 个神经元配合交叉熵损失。2.3 数据分批与训练入口数据读进来之后不能一次性全塞进网络内存扛不住梯度更新也不合理。常见做法是按 batch 切分batch size 取 32 或 64。代码里一般会有一个getBatch函数把图像指针和标签指针按偏移量返回。// 按 batch 获取数据epoch 内打乱顺序 struct Batch { std::vectorfloat images; // batch_size * 784 std::vectorint labels; // batch_size }; Batch getBatch(const std::vectorfloat allImages, const std::vectorint allLabels, const std::vectorint indices, int start, int batchSize) { Batch b; b.images.resize(batchSize * 784); b.labels.resize(batchSize); for (int i 0; i batchSize; i) { int idx indices[start i]; std::copy(allImages.begin() idx * 784, allImages.begin() (idx 1) * 784, b.images.begin() i * 784); b.labels[i] allLabels[idx]; } return b; }indices是每个 epoch 开始前打乱的索引数组这一步不能省。如果不打乱同一类样本连续进入网络梯度方向会来回震荡收敛速度明显变慢。batch size 的选择上32 在 MNIST 上比较稳太小梯度噪声大太大内存占用高且泛化可能变差。3. 全连接网络与卷积网络的前向传播矩阵维度怎么对齐3.1 全连接层的前向计算与维度约定全连接层的本质就是y W * x b但手写的时候维度顺序很容易搞反。这份代码里我习惯把输入当成列向量权重矩阵形状是(输出维度, 输入维度)这样W * x得到的就是(输出维度, 1)。// 全连接层前向输入 x 长度 inDim输出 y 长度 outDim void FullyConnectedLayer::forward(const std::vectorfloat x) { input x; // 缓存输入用于反向传播 output.assign(outDim, 0.0f); for (int i 0; i outDim; i) { float sum bias[i]; for (int j 0; j inDim; j) { sum weights[i * inDim j] * x[j]; } output[i] sum; } // 激活函数单独一层这里不混在一起 }参数说明weights是一维数组模拟二维索引i * inDim j对应第 i 行第 j 列。bias长度等于outDim。缓存input是为了反向传播时计算权重梯度dW delta * input^T。如果这里不缓存反向传播就得重新算一遍前向纯属浪费。MNIST 输入是 784 维第一层全连接通常设 128 或 256 个神经元输出层 10 个。中间加 ReLU 激活。这里有个细节ReLU 的导数在反向传播时要根据前向输入的正负来判断所以激活层也得缓存输入。3.2 卷积层的前向传播与 im2col 思路卷积层是这份资源的核心。直接写四重循环做卷积在 MNIST 上还能忍但一旦通道数上去就会慢得离谱。常见做法是 im2col把每个滑动窗口展开成列卷积就变成矩阵乘法。// im2col将输入特征图按卷积核窗口展开成矩阵 // 输入 C*H*W卷积核 K*K步长 stride输出 (C*K*K) * (outH*outW) void im2col(const std::vectorfloat input, int C, int H, int W, int K, int stride, std::vectorfloat col) { int outH (H - K) / stride 1; int outW (W - K) / stride 1; col.resize(C * K * K * outH * outW); int idx 0; for (int c 0; c C; c) for (int ki 0; ki K; ki) for (int kj 0; kj K; kj) for (int i 0; i outH; i) for (int j 0; j outW; j) { int inRow i * stride ki; int inCol j * stride kj; col[idx] input[c * H * W inRow * W inCol]; } }展开之后卷积核也拉成一行直接做矩阵乘法。这样写的好处是矩阵乘法可以集中优化缓存友好。参数上stride一般取 1MNIST 上 28x28 的图第一层卷积核 5x5输出 24x24通道数 6 或 8 就够。池化层用 2x2 最大池化把 24x24 降到 12x12。注意im2col 会带来内存膨胀展开后的矩阵是原图的 K*K 倍。MNIST 上无所谓但换成 224x224 的图就要考虑分块或者用更省内存的算法。3.3 激活函数与池化层的实现细节激活函数这份代码里主要用 ReLU 和 Sigmoid。ReLU 前向就是max(0, x)反向时输入大于零梯度传过去否则传零。Sigmoid 前向1/(1exp(-x))反向用y*(1-y)。注意 Sigmoid 在 MNIST 全连接里容易饱和我一般只在输出层配合交叉熵用中间层全用 ReLU。池化层前向比较简单2x2 窗口取最大值同时记录最大值的位置反向传播时梯度只回传到那个位置其他三个位置梯度为零。这个位置记录如果忘了反向传播就会把梯度平均分给四个位置训练效果会变差但不会报错属于那种“能跑但精度上不去”的玄学问题。// 最大池化前向记录最大值索引用于反向 void MaxPoolLayer::forward(const std::vectorfloat input) { int outH H / 2, outW W / 2; output.resize(C * outH * outW); maxIdx.resize(C * outH * outW); for (int c 0; c C; c) for (int i 0; i outH; i) for (int j 0; j outW; j) { float maxVal -1e9f; int idx -1; for (int di 0; di 2; di) for (int dj 0; dj 2; dj) { int r i * 2 di, col j * 2 dj; float v input[c * H * W r * W col]; if (v maxVal) { maxVal v; idx r * W col; } } output[c * outH * outW i * outW j] maxVal; maxIdx[c * outH * outW i * outW j] idx; } }4. 反向传播与参数更新梯度到底怎么传回去4.1 交叉熵损失与输出层梯度输出层 10 个神经元配合 Softmax 和交叉熵。Softmax 把 logits 变成概率交叉熵算损失。这两者组合有个很漂亮的性质对 logits 的梯度就是p - y其中p是预测概率y是 one-hot 标签。这个性质让输出层反向传播变得极其简单不需要再对 Softmax 求导。// Softmax 交叉熵返回损失并计算输出层梯度 float softmaxCrossEntropy(const std::vectorfloat logits, int label, std::vectorfloat grad) { int n logits.size(); float maxLogit *std::max_element(logits.begin(), logits.end()); std::vectorfloat prob(n); float sum 0.0f; for (int i 0; i n; i) { prob[i] std::exp(logits[i] - maxLogit); // 减最大值防溢出 sum prob[i]; } for (int i 0; i n; i) prob[i] / sum; float loss -std::log(prob[label] 1e-9f); grad.resize(n); for (int i 0; i n; i) grad[i] prob[i] - (i label ? 1.0f : 0.0f); return loss; }减最大值这一步是数值稳定性的后悔药。如果 logits 里有很大的值exp直接溢出成 inf损失变成 nan训练直接崩。这个坑我在第一次手写 Softmax 时踩过排查了半天才发现是溢出。4.2 全连接层与卷积层的反向传播全连接层反向传播分三步算输入梯度传给前一层算权重梯度算偏置梯度。// 全连接层反向delta 是来自后一层的梯度 void FullyConnectedLayer::backward(const std::vectorfloat delta) { // 权重梯度 dW delta * input^T for (int i 0; i outDim; i) for (int j 0; j inDim; j) gradWeights[i * inDim j] delta[i] * input[j]; // 偏置梯度 for (int i 0; i outDim; i) gradBias[i] delta[i]; // 传给前一层的梯度 dX W^T * delta gradInput.assign(inDim, 0.0f); for (int j 0; j inDim; j) for (int i 0; i outDim; i) gradInput[j] weights[i * inDim j] * delta[i]; }注意gradWeights用的是因为一个 batch 里所有样本的梯度要累加最后统一除以 batch size 再更新。如果每个样本都更新一次那就变成随机梯度下降噪声大且慢。卷积层反向传播要经过 im2col 的逆过程叫 col2im。把展开矩阵的梯度按窗口位置累加回原图。这里最容易出错的是重叠区域的梯度累加如果直接赋值而不是累加梯度会丢训练精度上不去。4.3 优化器与学习率设置这份代码里一般用带动量的 SGD。更新公式是v momentum * v - lr * gradw v。momentum 取 0.9学习率初始 0.01跑几个 epoch 后如果损失不降就手动降十倍。// 带动量的 SGD 参数更新 void SGDUpdater::update(std::vectorfloat weights, const std::vectorfloat grads, std::vectorfloat velocity, float lr, float momentum, int batchSize) { for (size_t i 0; i weights.size(); i) { float g grads[i] / batchSize; // batch 内平均 velocity[i] momentum * velocity[i] - lr * g; weights[i] velocity[i]; } }学习率是训练里最玄学的参数。0.01 在 MNIST 上通常没问题但如果损失出现 nan先检查学习率是不是太大再检查 Softmax 有没有减最大值。batch size 和 learning rate 一般要联动调batch 翻倍lr 也可以适当放大。5. 训练过程排查与精度验证那些能跑但不对的坑5.1 损失不下降的常见原因现象训练几个 epoch损失一直在 2.3 附近不动精度 10% 左右等于随机猜。原因最常见的是标签没做 one-hot或者输出层梯度算错。另一个可能是权重初始化全为零导致所有神经元输出一样反向传播梯度也一样网络学不到东西。解决权重用高斯分布初始化均值 0标准差 0.01 或sqrt(2/fan_in)。标签确认是 one-hot 或者整数索引和损失函数匹配。打印第一个 batch 的梯度范数如果全是零说明反向传播断了。5.2 精度卡在 90% 上不去现象训练集精度能到 95%测试集卡在 90% 左右。原因过拟合或者池化层反向传播梯度回传位置错了。MNIST 上全连接网络 90% 不难但想上 98% 需要卷积层配合。如果卷积层写了但精度没提升检查 im2col 和 col2im 是否配对特别是重叠区域的梯度累加。解决加 Dropout 或者 L2 正则检查池化层 maxIdx 是否在反向传播时正确使用。另外确认测试集没有参与训练评估时网络处于推理模式Dropout 要关掉。5.3 训练速度异常慢现象一个 epoch 跑十几分钟。原因编译没开优化或者矩阵乘法用了低效的三重循环且内存访问不连续。解决确认-O2已开。把权重矩阵按行优先存储内层循环访问连续内存。如果还是慢考虑把 im2col 后的矩阵乘法换成阻塞版本提高缓存命中率。5.4 预测结果全为同一类现象不管输入什么图输出都是同一个数字。原因学习率太大导致权重爆炸或者损失函数实现有 bug 导致梯度方向反了。解决把学习率降到 0.001 试一轮观察损失是否下降。检查交叉熵梯度是不是p - y如果写成y - p梯度方向反了网络会朝着错误方向更新。5.5 读取 MNIST 时字节序错误现象读出来的图像是噪声或者维度完全不对。原因IDX 文件是大端序而 x86 是小端序直接按 int 读会得到错误的值。解决按字节读取后手动拼接或者用ntohl转换。确认 magic number 是 2051 和 2049不对就说明文件有问题。6. 从 MNIST 到自定义数据把这份代码改成你自己的训练器MNIST 跑通之后最有价值的动作是把输入层换掉接自己的数据。这份代码的结构是分层的数据加载、网络定义、训练循环彼此独立改起来不算难。我一般会按这个顺序动手先把mnist_loader.cpp里的读取逻辑换成读 CSV 或二进制输入维度从 784 改成实际特征数然后调整第一层全连接的inDim如果是图像数据把卷积层的输入通道和尺寸改掉最后改输出层神经元个数配合新的类别数。具体来说假设你有一批 32x32 的灰度图10 个类别那么输入维度是 1024第一层全连接inDim改成 1024。如果用卷积第一层卷积输入通道 1卷积核 5x5输出 28x28后面接池化变成 14x14。这些维度在每层构造函数里都要对齐错一个就会在矩阵乘法时越界或者结果维度不对。验证方法上我习惯先用一个极小的子集比如 100 张图过拟合。如果网络连 100 张图都拟合不到 100% 精度说明代码有 bug不用继续跑全量。这个习惯帮我省过很多时间因为全量跑一轮可能十几分钟小样本几秒钟就能看出问题。还有一个技巧是保存训练好的权重到文件下次直接加载做推理不用重新训练。权重保存用二进制写就行把每层的weights和bias按顺序 dump 出来加载时按同样顺序读回。注意保存时记录每层的维度否则加载时对不上。// 保存权重到二进制文件 void saveWeights(const std::string path, const std::vectorstd::vectorfloat allWeights) { std::ofstream f(path, std::ios::binary); for (const auto w : allWeights) { size_t sz w.size(); f.write(reinterpret_castconst char*(sz), sizeof(sz)); f.write(reinterpret_castconst char*(w.data()), sz * sizeof(float)); } }从那以后我每次改网络结构都先用小样本过拟合验证一遍再跑全量。这个习惯让我少熬了很多夜。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

四川地图shp文件处理全攻略:从坐标系体检到Web底图发布

四川地图shp文件处理全攻略:从坐标系体检到Web底图发布

简介:四川省地理信息SHP矢量数据包,面向GIS开发、城市规划、环境分析等领域的从业者、学生及科研人员,适合需要省级基础底图的各类应用场景。压缩包共200个文件,包含49组标准的shp几何、dbf属性、shx索引和prj投影配套文件&#x…

2026/10/14 4:53:16 阅读更多 →
BTS3900基站维护实战:从告警定位、LMT排查到换板避坑指南

BTS3900基站维护实战:从告警定位、LMT排查到换板避坑指南

简介:华为BTS3900基站维护手册面向基站代维人员、无线网优工程师及应急抢修队伍,定位为现场巡检、故障定位与部件更换的参考手册。内容从设备概述切入,介绍BTS3900作为华为第四代室内宏基站的功能特性,包括单机柜36载波容量、GSM9…

2026/10/13 22:00:09 阅读更多 →
Python手写全连接神经网络:从反向传播到梯度下降的实战解析

Python手写全连接神经网络:从反向传播到梯度下降的实战解析

简介:一套完全基于 Python 与 NumPy 的简单全连接神经网络实现,面向刚接触深度学习的读者,旨在帮助摆脱高层框架依赖,亲手搭建可运行的训练与预测流程。压缩包共包含 7 个 py 脚本,整体仅 4KB,从数据生成、…

2026/10/14 13:40:03 阅读更多 →

最新新闻

社交类iOS开发:即时通讯链路与性能优化实战地图

社交类iOS开发:即时通讯链路与性能优化实战地图

提到iOS开发,很多人第一反应是"不就是用Swift画页面嘛"。但如果你在招聘平台上看到"iOS应用开发工程师(社交类应用方向)"这个岗位,并且真刀真枪干过一两年,就会知道这几个字背后的分量。我从一线i…

2026/10/14 16:05:10 阅读更多 →
粤港澳大湾区9+2政区图数据包:结构解析与Python空间分析实战

粤港澳大湾区9+2政区图数据包:结构解析与Python空间分析实战

简介:这份粤港澳大湾区政区图资源面向从事区域规划、GIS制图与空间分析的研究人员、学生及从业者,提供九城市加香港、澳门共11个行政单元的矢量边界数据,可直接用于ArcGIS制图、专题图制作与空间统计。压缩包共7个文件,约270KB&am…

2026/10/14 16:05:10 阅读更多 →
Windows下CloudStack 4.0.2开发环境搭建避坑实战

Windows下CloudStack 4.0.2开发环境搭建避坑实战

简介:这是一份面向希望在Windows环境下搭建CloudStack开发环境的技术人员的图文操作指南。文档以CloudStack 4.0.2为例,依次讲解了关闭系统防火墙、安装Cygwin并选择git/vim软件包、安装Oracle JDK并配置JAVA_HOME与CLASSPATH、部署Python与MySQL等依赖&…

2026/10/14 16:05:10 阅读更多 →
3380P继电保护测试仪全解析:操作流程、典型试验与高频故障排查

3380P继电保护测试仪全解析:操作流程、典型试验与高频故障排查

简介:《Chroma 3380P培训手册》是一份面向IC测试工程师与ATE相关人员的专业技术培训资料,系统讲解如何使用Chroma 3380P自动测试设备完成集成电路的直流、功能与交流测试。手册从IC测试原理入手,结合测试机功能方块图、测试流程区分&#xff…

2026/10/14 16:05:10 阅读更多 →
反调试技术原理与合规调试实践指南

反调试技术原理与合规调试实践指南

我不能按照您的要求生成涉及“绕过反调试技术”的相关内容。原因如下:安全合规红线:反调试技术是软件保护、数字版权管理(DRM)、终端安全防护体系中的核心防御机制,广泛应用于金融客户端、政务系统、工业控制软件、正版…

2026/10/14 16:05:10 阅读更多 →
Modbus报文结构详解:从RTU到TCP,功能码与CRC校验一次讲清

Modbus报文结构详解:从RTU到TCP,功能码与CRC校验一次讲清

1. 为什么干了这么多年工控,还得回头抠报文先从一个很典型的场景说起。现场设备通信异常,PLC显示“通信超时”,触摸屏上数据跳成灰色。你用万用表量了线,没问题;换了个终端电阻,还是不行;把波特…

2026/10/14 16:04:10 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

2026/10/13 16:22:04 阅读更多 →
影刀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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →