news 2026/10/10 22:19:52

从零实现全连接神经网络:Python手写代码与核心原理解读

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零实现全连接神经网络:Python手写代码与核心原理解读

简介:这份资源是一份使用 Python 与 NumPy 从零搭建全连接神经网络的入门实现,面向想理解深度学习底层原理的初学者与进阶开发者,解决“只会调用框架、不懂内部机制”的问题。资源共含 7 个 py 文件,压缩包仅 4KB,代码轻量却覆盖完整流程:从数据生成、网络初始化、前向传播与反向传播,到损失计算、训练与预测均有独立脚本,便于按模块对照学习。已有 4211 人学习下载,社区认可度较高。读者拿到后可直接运行并修改参数,通过逐行阅读和调试,直观掌握权重更新、梯度计算等核心概念,为后续学习 TensorFlow、PyTorch 等框架打下扎实基础。整体体积小巧,特别适合作为神经网络课程实验或自学练习的参考模板。

1. 全连接神经网络是什么:一个Python实现就能讲清的最小分类器

当你想判断“这个用户会不会流失”“这台设备要不要停机检修”,又不想一上来就抱TensorFlow、PyTorch大腿时,最该先动手写的,就是一个简单的全连接神经网络。全连接神经网络是所有深度学习框架里最底层的积木:一个输入层、若干隐藏层、一个输出层,层与层之间每个神经元都两两相连。用Python动手实现一遍,你才能真正看懂后面那些框架里Linear、Dense到底替你做了什么,也才知道报错时该往哪个方向查。这篇文章面向两类人:刚入门Python、想弄懂神经网络内部机制的新手;以及熟手想找一个可复现的最小基线,做算法对比或者调试参照。我会直接给出能跑的代码,再有针对性地讲参数怎么调、坑在哪里。

2. 全连接网络的核心机制:从矩阵乘法到链式法则的四块基石

很多人写全连接网络,把代码从开源仓库复制下来能跑通,但换一个数据集就崩。原因不是代码问题,是没吃透层与层之间到底在算什么。这一章把四个最关键的概念拆开讲,并且每一节都会落到你能在Python里亲手验证的细节上。

2.1 全连接层的数学表达:权重矩阵与偏置到底在做什么

一个全连接层做的事可以浓缩成一行式子:Z = XW + b。这里的X是形状为(m, n)的输入矩阵,m是样本数,n是特征数;W是形状为(n, h)的权重矩阵,h是本层神经元的个数;b是形状为(1, h)的偏置。矩阵乘法XW的本质,是把每个样本的n维特征向量分别与W的每一列做点积,得到一个h维的新向量。

你可以用Python做一个最小实验,体会“全连接”这三个字。假设有3个样本、4个特征,隐藏层设5个神经元,权重形状就是(4, 5)。样本i在隐藏层第j个神经元的输入值,等于该样本的4个特征分别乘以W[j]这4个权重再求和,最后加上b[j]。这就是“全连接”的含义:上一层的每个输出都参与了下一层每个神经元的计算,没有哪个连接被省略。

import numpy as np m, n, h = 3, 4, 5 X = np.random.randn(m, n) # 3个样本,每个4个特征 W = np.random.randn(n, h) # 权重矩阵:4x5 b = np.zeros((1, h)) # 偏置:1x5 Z = X @ W + b # 前向计算的线性部分 print(Z.shape) # 输出 (3, 5)

这段代码里@是NumPy的矩阵乘法运算符。b的形状是(1, h),它和(m, h)的X @ W相加时靠广播机制自动扩展到每一行。你唯一需要死记的,就是矩阵乘法要求左边矩阵的列数等于右边矩阵的行数,也就是特征数必须等于权重矩阵的行数。很多人写代码报维度错误的根因,就是把X @ W写成了W @ X。

2.2 激活函数:为什么非线性是神经网络的灵魂

如果每一层都只做Z = XW + b,那么无论叠多少层,整个网络最终等价于一个线性变换,分类能力不会比逻辑回归强。激活函数的作用,就是给网络注入非线性。最常见的两个起点是Sigmoid和ReLU。Sigmoid把任意实数压缩到0到1之间,适合二分类输出层,但饱和区的梯度几乎为0,深层网络里容易让梯度消失。ReLU是max(0, z),计算极快,正区间梯度恒为1,是目前隐藏层的默认选择。

def sigmoid(z): return 1 / (1 + np.exp(-z)) def relu(z): return np.maximum(0, z) # 数值稳定性更好的sigmoid def sigmoid_stable(z): return np.where(z >= 0, 1 / (1 + np.exp(-z)), np.exp(z) / (1 + np.exp(z)))

sigmoid_stable里用了分段计算,避免z为很大的负数时np.exp(-z)溢出。这个细节新手很容易忽略,但实际训练中一旦特征没归一化,z的绝对值可能冲到几十甚至上百,普通sigmoid会直接算出NaN或0。选择激活函数时有个经验:隐藏层首选ReLU,输出层做二分类用Sigmoid,多分类用Softmax,回归问题输出层可以不用激活函数。

2.3 反向传播与链式法则:从损失函数一路回传的梯度

反向传播是整个网络的“学习”机制。前向传播算出的预测值和真实标签之间有误差,反向传播要做的事,就是把这个误差对每一个权重和偏置求偏导,然后沿梯度反方向更新参数。求偏导的理论基础是链式法则:损失对某一层权重的梯度,等于“损失对输出的梯度”乘以“输出对该层线性输出的梯度”乘以“线性输出对权重的梯度”。

以二分类为例,输出层用Sigmoid,损失用均方误差MSE。设a为预测输出,y为真实标签,z为输出层线性输出,a = sigmoid(z)。损失对z的梯度是:

dL/dz = (a - y) * a * (1 - a)

这个式子在代码里常写为d_z = (y_pred - y) * y_pred * (1 - y_pred)。注意必须在除以样本数m之后再更新权重,否则梯度大小随批量样本数膨胀,学习率就很难调。

# 假设 y_pred 和 y 都是形状 (m, 1) 的列向量 m = y.shape[0] d_z = (y_pred - y) * y_pred * (1 - y_pred) / m d_W2 = hidden_output.T @ d_z # hidden_output 是上一层的激活值 d_b2 = np.sum(d_z, axis=0, keepdims=True)

这里hidden_output.T @ d_z又是矩阵乘法:上一层的激活值形状是(m, h),转置后是(h, m),与(m, 1)的d_z相乘,得到(h, 1),正好和W2形状一致。我一般会每一步都打印中间结果的形状,这是排查反向传播错误最有效的习惯。

2.4 参数初始化与学习率:影响收敛的两个隐性变量

权重初始化决定了训练起点。如果把所有权重初始化为0,那么同一层所有神经元会收到完全相同的梯度,无论训练多久都学不到差异化特征。常见做法是用随机初始化:ReLU配合He初始化,Sigmoid配合Xavier初始化。He初始化是np.random.randn(shape) * np.sqrt(2.0 / n_in),其中n_in是上一层的神经元数。

学习率则是每次参数更新的步长。学习率太大,损失函数会在最优值附近来回震荡甚至发散;太小则训练慢到你失去耐心。一个参考值:SGD优化器下,ReLU网络的学习率从0.01起步;Adam优化器下可以放宽到0.001。学习率不是越准越好,而是要配合损失曲线观察。

n_input, n_hidden, n_output = 64, 32, 1 W1 = np.random.randn(n_input, n_hidden) * np.sqrt(2.0 / n_input) W2 = np.random.randn(n_hidden, n_output) * np.sqrt(2.0 / n_hidden) b1 = np.zeros((1, n_hidden)) b2 = np.zeros((1, n_output))

偏置初始化为0是没有问题的,因为梯度不会因此对称。权重初始化这一环,很多人觉得是玄学,其实它和激活函数是配套的:你选了ReLU却用Xavier,初始时负区间会有大量神经元死亡;你选了Sigmoid却用He初始化,初始输出饱和概率升高,梯度直接消失。

3. 用NumPy从零实现简单全连接网络:可运行的最小代码

这一章直接给出完整的NumPy实现,目标是在不依赖深度学习框架的前提下,训练一个能区分手写数字0和1的全连接神经网络。数据用sklearn自带的digits数据集子集,不需要额外下载文件,方便你快速复现。

3.1 环境准备与数据准备:先检验Python和NumPy环境

动手前先确认环境是完好的。Python环境变量配置有问题,或者NumPy没装好,后面所有代码都会卡在import这一步。常见做法是在终端里分别运行下面两条命令,确认输出正常。

python --version python -c "import numpy; print(numpy.__version__)"

如果你还没有安装numpy库,最简单的安装方法是用pip安装:

pip install numpy scikit-learn

提示:如果你在VS Code里配置了多个Python解释器,务必确认终端里的python命令和你当前选择的是同一个解释器,否则会出现“明明装了numpy却import不到”的典型问题。

数据准备这部分,我一般会直接复用sklearn自带的手写数字集,但只保留标签为0和1的样本,变成二分类问题,这样网络结构可以做得最简单,训练时间也短。

import numpy as np from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split digits = load_digits() mask = (digits.target == 0) | (digits.target == 1) X = digits.data[mask] # 形状 (360, 64) y = digits.target[mask].reshape(-1, 1) # 形状 (360, 1) X = X / 16.0 # 像素范围0-16,归一化到0-1 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42 ) print(X_train.shape, X_val.shape)

这里除以16.0是关键一步。sklearn的digits数据每个像素取值0到16,如果不归一化,特征量级偏大,会让Sigmoid输出进入饱和区,梯度直接消失。train_test_split里的random_state=42固定了划分方式,保证你每次运行结果一致。

3.2 前向传播代码:从输入到预测的完整流程

网络结构设计为:输入层64个神经元,隐藏层32个,输出层1个。隐藏层用ReLU,输出层用Sigmoid。代码里我会把前向传播写成类方法,缓存中间结果,方便反向传播复用。

class SimpleFCN: def __init__(self, n_input, n_hidden, n_output, lr=0.1): # He初始化,适配ReLU self.W1 = np.random.randn(n_input, n_hidden) * np.sqrt(2.0 / n_input) self.b1 = np.zeros((1, n_hidden)) self.W2 = np.random.randn(n_hidden, n_output) * np.sqrt(2.0 / n_hidden) self.b2 = np.zeros((1, n_output)) self.lr = lr def forward(self, X): # 隐藏层:线性变换 + ReLU self.z1 = X @ self.W1 + self.b1 self.a1 = np.maximum(0, self.z1) # 输出层:线性变换 + Sigmoid self.z2 = self.a1 @ self.W2 + self.b2 self.a2 = 1 / (1 + np.exp(-self.z2)) return self.a2

self.z1的维度是(m, 32),self.a1与它同形状。self.a2的形状是(m, 1),代表每个样本预测为正类的概率。逻辑说明:forward里保存的z1、a1、z2都是中间变量,反向传播需要它们计算梯度。如果把中间结果写成局部变量而不存到self上,反向传播时就得重新算一遍,代码没问题,但多花一倍时间。

参数说明:lr=0.1是学习率,针对NumPy手写版本的SGD,这个数值在这个小数据集上是安全的。隐藏层神经元数量32是经验值,太大容易过拟合,太小拟合能力不足。换数据集时可以先固定这个值跑通流程,再回头调。

3.3 反向传播与参数更新:核心代码与形状对照

反向传播的关键,是每一步梯度矩阵的形状都要和前向传播里对应参数的形状一致。我写代码时有一个习惯:先写出所有梯度的形状注释,再写计算逻辑。

def backward(self, X, y): m = X.shape[0] # 输出层梯度 d_z2 = (self.a2 - y) * self.a2 * (1 - self.a2) / m # (m, 1) d_W2 = self.a1.T @ d_z2 # (32, 1) d_b2 = np.sum(d_z2, axis=0, keepdims=True) # (1, 1) # 隐藏层梯度 d_a1 = d_z2 @ self.W2.T # (m, 32) d_z1 = d_a1 * (self.z1 > 0) # ReLU导数 d_W1 = X.T @ d_z1 # (64, 32) d_b1 = np.sum(d_z1, axis=0, keepdims=True) # (1, 32) # 参数更新 self.W2 -= self.lr * d_W2 self.b2 -= self.lr * d_b2 self.W1 -= self.lr * d_W1 self.b1 -= self.lr * d_b1 def train(self, X, y, epochs=200): for epoch in range(epochs): y_pred = self.forward(X) self.backward(X, y) if epoch % 50 == 0: loss = np.mean((y_pred - y) ** 2) print(f"epoch {epoch}, loss {loss:.4f}")

代码逻辑说明:d_z2公式里的self.a2 * (1 - self.a2)是Sigmoid函数的导数。/m这个除法决定了梯度是“平均梯度”,这样学习率不会因为batch变大而需要等比缩小。d_z1用(self.z1 > 0)实现ReLU的导数:z1大于0的位置导数为1,否则为0。注意这里直接用布尔矩阵和d_a1相乘,不需要转成float,NumPy会正确处理。

这个训练方式是全批量梯度下降:每次迭代用全部训练样本计算梯度并更新一次参数。样本数只有288,全批量完全跑得动。如果你的数据集超过几万条,就要改成小批量或者随机梯度下降,否则单次迭代计算太慢。

3.4 训练主循环与损失观察:怎样才算训练成功

训练一个模型,不要只看最终准确率,要观察损失曲线的下降过程。理想情况是损失单调下降,前几十个epoch下降快,后面趋于平缓。

model = SimpleFCN(n_input=64, n_hidden=32, n_output=1, lr=0.1) model.train(X_train, y_train, epochs=200) y_pred_val = model.forward(X_val) val_acc = ((y_pred_val >= 0.5).astype(int) == y_val).mean() print(f"验证集准确率: {val_acc:.4f}")

这段代码里,forward(X_val)只做前向计算,不会修改模型参数。预测概率大于等于0.5判为正类,其余判为负类。验证集准确率在这个二分类任务上,跑通代码后应该能得到98%以上,如果低于95%,优先检查归一化有没有做、学习率是否合适。

训练结束后,模型会存在model.W1、model.b1、model.W2、model.b2这四个数组里。这就是“训练好的模型”——它没有任何特殊结构,就是一组浮点数。你想把这个模型保存下来,用np.savez把这四个数组打包即可,下次预测时重新加载再调用forward。

4. 用PyTorch实现同一个网络:两版代码的取舍与迁移时机

NumPy版能帮你建立底层直觉,但真实项目里我们通常直接用框架。这一章用PyTorch实现完全相同的三层全连接网络,并把两版代码的关键差异摊开来讲。

4.1 PyTorch模型定义:三层结构的代码形态

PyTorch里定义一个全连接网络,通常继承torch.nn.Module。网络结构和NumPy版完全一致:64输入、32隐藏、1输出,ReLU加Sigmoid。

import torch import torch.nn as nn class SimpleFNN(nn.Module): def __init__(self, n_input=64, n_hidden=32, n_output=1): super().__init__() self.fc1 = nn.Linear(n_input, n_hidden) self.relu = nn.ReLU() self.fc2 = nn.Linear(n_hidden, n_output) self.sigmoid = nn.Sigmoid() def forward(self, x): x = self.fc1(x) x = self.relu(x) x = self.fc2(x) x = self.sigmoid(x) return x

nn.Linear(n_input, n_hidden)内部做的事情和NumPy里的X @ W + b完全一样,但权重初始化策略由PyTorch自动选择,默认是Kaiming均匀初始化,对ReLU是安全的。相比NumPy版,你不需要自己操心初始化公式,框架已经把最优实践封装进去了。

模型实例化和推理代码如下:

model = SimpleFNN() criterion = nn.BCELoss() # 二分类交叉熵损失 optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

nn.BCELoss是二分类交叉熵损失,它在数学上比MSE更适合分类任务:交叉熵在预测错误且置信度高时给出更大梯度,训练效率更高。这体现了框架版的第一个优势:你不需要手写损失函数的导数,criterion已经内置了前向和反向的全部逻辑。

4.2 训练循环与数据集装载:DataLoader的正确用法

PyTorch训练循环的标准写法是:清空梯度、前向计算、反向传播、更新参数、记录损失。

from torch.utils.data import TensorDataset, DataLoader X_train_t = torch.tensor(X_train, dtype=torch.float32) y_train_t = torch.tensor(y_train, dtype=torch.float32) train_dataset = TensorDataset(X_train_t, y_train_t) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) for epoch in range(200): for x_batch, y_batch in train_loader: optimizer.zero_grad() pred = model(x_batch) loss = criterion(pred, y_batch) loss.backward() optimizer.step() if epoch % 50 == 0: print(f"epoch {epoch}, loss {loss.item():.4f}")

逻辑说明:optimizer.zero_grad()每轮迭代必须调用,否则梯度会累积在tensor.grad里,导致参数更新步长被成倍放大。loss.backward()自动计算整个计算图的梯度,optimizer.step()用优化器把梯度应用到参数上。

DataLoader里的batch_size=32就是小批量梯度下降。和NumPy版的全批量不同,每个batch只包含32个样本,因此每轮epoch会更新多次参数。batch_size是训练中最重要的参数之一:太小梯度噪声大、收敛不稳;太大则每次更新方向过于平均,训练慢。32是一个久经考验的起点。

4.3 迁移到PyTorch的时机:什么场景值得换

如果你只是学习原理、写个几百行数据的演示,NumPy版完全够用。但遇到下面几种情况,我会立刻切到PyTorch:第一,数据量超过十万条,纯NumPy矩阵运算的内存和速度都吃紧;第二,需要GPU训练,PyTorch只需把模型和数据移动到GPU设备;第三,网络结构要加深或改造成卷积网络,手写反向传播的复杂度会指数上升。

PyTorch还有一个隐藏优势:自动求导。NumPy版里你得手写d_z1 = d_a1 * (self.z1 > 0),一旦网络多几层,这种推导非常容易出错。框架的loss.backward()会自动从损失回传到所有参数,并且支持任意复杂的计算图。你只需要关心网络结构和数据流动,不用碰梯度公式。

切换时的代价也很明确:需要理解tensor和numpy.ndarray的区别,以及设备位置问题。常见报错“Expected all tensors to be on the same device”,就是模型在CPU而数据在GPU,或者反过来。排查方式是在模型和数据创建时都指定device='cuda'或device='cpu',保持两边一致即可。

5. 避坑:全连接网络训练中常见的5个问题排查

这一章是我在实际项目里反复踩过的坑,按“现象、原因、解决”的排查思路整理。如果你训练时遇到类似状况,可以直接对照定位。

5.1 损失一直停在0.69附近不下降

现象:训练几十个epoch,损失始终在0.69附近波动,准确率只有50%。

原因:0.69是二分类交叉熵损失在随机猜测时的理论值。损失卡在这里,意味着模型根本没有学到任何模式。最常见的原因是输入特征没有归一化,数值量级差异大,导致Sigmoid饱和。其次是标签的取值范围有问题,比如把二分类标签写成了0和1之外的数值。

解决:先检查数据预处理,把每个特征的取值范围压缩到0到1之间。再检查y是不是只有0和1两种取值。如果都正常,把学习率从0.1降到0.01再试,排除学习率过大导致损失震荡的假象。

5.2 训练震荡剧烈:损失曲线像锯齿一样

现象:损失整体在下降,但每几个epoch就出现一次明显反弹,曲线不平滑。

原因:最常见是学习率偏高。每次参数更新的步长太大,跨过了最优区域,下一次迭代又折返回来。另一种可能是batch_size设得太小,比如在小型数据集上用了batch_size=1,每个样本的梯度方向不一致,造成更新方向来回摇摆。

解决:先把学习率降一个数量级,看曲线是否变平滑。如果有效,再进一步用学习率衰减策略,常见做法是每20个epoch把学习率乘以0.9。如果数据量不大,直接改用全批量训练或batch_size设为64以上,能显著降低震荡。

5.3 加深网络后训练集损失都降不下去

现象:把网络从一层隐藏层加到三层,理论上拟合能力更强,但训练集损失反而比浅层网络还高。

原因:梯度消失。隐藏层中使用Sigmoid激活时,每经过一层,梯度要乘一次a(1-a),而a(1-a)最大值只有0.25。三层之后梯度已经接近0,前几层的权重几乎不再更新。

解决:隐藏层的激活函数全部换成ReLU,它正区间的导数是1,梯度可以顺畅地穿透多层。如果仍不行,检查每一层的权重标准差,初始值不宜太小。更深层的网络还要配Batch Normalization,但那是后话,全连接网络优先保证激活函数选型正确。

5.4 训练集表现很好、验证集差一大截

现象:训练集准确率99%,验证集只有80%,差距逐年增加。

原因:过拟合。全连接网络的参数量通常很大,这个例子中64输入、32隐藏的参数是64*32 + 32 = 2080个,而训练样本只有288个,参数数量远超样本量,模型完全有能力“背下”训练集。

解决:第一优先级是加正则化。常见做法是在损失函数里增加L2正则项,PyTorch里设置optimizer的weight_decay=0.001即可;NumPy版则需要在更新权重时额外减去lr * weight_decay * W。第二优先级是早停:每训练一轮看验证集loss,连续几个epoch不下降就停止训练。第三优先级是加大数据量或数据增强,二分类数字识别可以通过旋转、平移扩充样本。

5.5 全连接层输出了NaN,概率变成None

现象:训练到某个epoch,损失输出nan,预测结果全是nan。

原因:数值上溢。最常见是Sigmoid的np.exp(-z)在z为很负的数时溢出,或者学习率过大导致权重更新后误差爆炸,中间激活值变得极大。另一个容易被忽视的原因是没有做数值稳定的Sigmoid计算,直接在代码里写了1 / (1 + np.exp(-z))。

解决:把Sigmoid换成前面提过的np.where(z >= 0, ...)分段实现。然后检查学习率,nan出现通常意味着学习率超过安全上界一个数量级以上。最后检查标签和特征里有没有inf或nan混入,数据清洗时用np.isnan(X).any()排查。

6. 验证你的实现对不对:梯度检查、损失曲线判读与下一步

模型写完不是结束,要验证它真的“学对了”。这一章给你三个实用的自检手段,全部不依赖额外库。

6.1 数值梯度检查:最直接的反向传播自检

反向传播只要有一处推导错误,模型也能训,只是训得差且难以定位。标准的做法是用数值梯度做差分验证:对某个参数w,按下式近似计算梯度,和反向传播算出的梯度对比:

def numerical_gradient(model, X, y, param_name, eps=1e-5): param = getattr(model, param_name) grad = np.zeros_like(param) for i in range(param.shape[0]): for j in range(param.shape[1]): old = param[i, j] param[i, j] = old + eps loss_plus = np.mean((model.forward(X) - y) ** 2) param[i, j] = old - eps loss_minus = np.mean((model.forward(X) - y) ** 2) param[i, j] = old grad[i, j] = (loss_plus - loss_minus) / (2 * eps) return grad

eps通常取1e-5到1e-4之间,太小会有浮点误差,太大会偏离真实梯度。对比时用相对误差:norm(analytic - numerical) / norm(analytic),小于1e-6说明实现正确。这个检查只需在小模型、少量样本上做,比如只取10个样本、网络设成2个隐藏神经元,几秒钟就能跑完。

6.2 损失曲线的三种形态:欠拟合、正常收敛与过拟合

损失曲线的形态能告诉你训练该停在哪。第一种形态:损失一直高且平稳,验证集准确率也上不去,这是欠拟合,需要加层或加神经元。第二种形态:训练损失持续下降后走平,验证损失同时走平,这是我们追求的正常收敛,可以放心用。第三种形态:训练损失继续下降但验证损失掉头上升,这是过拟合的典型信号,早停的时机就选在验证损失开始上升的前一个epoch。

6.3 从简单全连接网络出发还能做什么

当你把这一套代码跑通并完成梯度检查,后续的进阶路径会非常清晰:把输出层的Sigmoid换成Softmax,损失改成nn.CrossEntropyLoss,就能直接扩成多分类;把权重更新改成小批量并加入动量,收敛速度会明显加快。我自己每次搭新模型,都会先用这个级别的全连接网络跑通数据和预处理流程,确认损失能降、准确率能看,再上复杂结构。这个习惯帮我省下了很多在debug模型前先debug数据的时间。希望这些过程能帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 22:18:38

微电网仿真必看:三机并联风光储系统Simulink建模全流程

做微电网仿真课题的人,十个里有八个会卡在“风光储怎么并联”这一步。我拿到“三机并联风光混合储能并网系统”这个题目时,起初也以为就是把光伏、风机、储能各搭一个模型,往交流母线上一怼就行。真跑到波形收敛那一关才发现,三台…

作者头像 李华
网站建设 2026/10/10 22:18:14

AnyPS5通用化方案:手柄映射、串流优化与媒体管理实战

1. 从“AnyPS5”这个名字说起:它到底想解决什么问题第一次看到“AnyPS5”这个标题,我脑子里蹦出来的第一个念头是:这大概率是一个围绕 PlayStation 5 生态做“泛化接入”或“能力扩展”的项目。为什么这么说?因为“Any”这个前缀在…

作者头像 李华
网站建设 2026/10/10 22:16:36

Claude BugHunter 技能分析报告:把 Burp MCP 接到 TaoToken 的配置与验证

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

作者头像 李华
网站建设 2026/10/10 22:09:37

无人机数据链加密仿真:AES与ChaCha20实时性能对比

1. 给地面控制链路加加密,不是"多加一个函数"的事无人机项目里,地面控制站与无人机之间的数据传输是飞行安全的主干道。控制指令可能是几字节的航点修正,数据量小但对延迟极其敏感;遥测状态是周期性回传的GPS坐标、姿态…

作者头像 李华
网站建设 2026/10/10 22:05:18

Claude Code、Codex++、OpenCode 三连击:3.0 Flash 接入全家桶最新姿势

Claude Code、Codex、OpenCode 三连击:3.0 Flash 接入全家桶最新姿势 【免费下载链接】Agnes-3.0-Flash 项目地址: https://ai.gitcode.com/hf_mirrors/Agnes-AI/Agnes-3.0-Flash 当大部分开发者还在为 Claude 订阅、OpenAI 套餐和各家 Agent 工具链的额度发…

作者头像 李华
网站建设 2026/10/10 22:02:55

T10-数据增强

● 🍨 本文为🔗365天深度学习训练营中的学习记录博客● 🍖 原作者:K同学啊一、前期准备1.设置GPUimport matplotlib.pyplot as plt import numpy as np import warnings warnings.filterwarnings(ignore) from tensorflow.keras i…

作者头像 李华