第10章:神经网络——层、激活函数与前向传播
10.1 线性模型的致命短板
第 8 章的线性回归:y = w*x + b。它学到的是一条直线。
现实世界几乎没有"直线问题"。你让一个线性回归判断一张图片里有没有猫——给定 32×32×3 = 3072 个像素值,输出"猫/非猫"——y = w·x + b 无论怎么训练都搞不定。不是参数不够多,是结构本身处理不了非线性边界。
需要神经网络,原因就在这里。
10.2 层的直觉:空间扭曲
一个神经网络层做的事情可以这样理解:
输入的原始数据 → [线性变换] → [非线性扭曲] → 输出
线性变换(矩阵乘法):W·x + b——把数据从一个空间投影到另一个空间。
非线性激活函数:ReLU(W·x + b)——把投影后的空间"弯折"一下。
一层 = 一次投影 + 一次弯折。多层 = 多次投影 + 多次弯折。原本线性不可分的数据,在新的空间里变得线性可分了。
关键证明:如果没有非线性,多层等于单层。两层线性变换的复合仍然是线性变换:W₂(W₁·x + b₁) + b₂ = (W₂·W₁)·x + (W₂·b₁ + b₂)——等效于一层。非线性才是让"深度"有意义的东西。
10.3 GoMLX 的层体系
GoMLX 的 layers 包提供了一组建筑积木:
| 层 | 函数 | 干什么 |
|---|---|---|
| Dense | layers.Dense(ctx, input, useBias, units) |
全连接层 |
| DenseWithBias | layers.DenseWithBias(ctx, input, units) |
带偏置的全连接层 |
| Dropout | layers.DropoutStatic(ctx, input, rate) |
训练时随机丢弃神经元 |
| BatchNorm | batchnorm.New(ctx, input, axis).Done() |
批归一化 |
| LayerNorm | layers.LayerNormalization(ctx, input, axis).Done() |
层归一化——Transformer 标配 |
每个层函数的第一个参数都是 ctx *context.Context。
10.4 激活函数:非线性的来源
GoMLX 内置的激活函数在 activations 子包里:
import "github.com/gomlx/gomlx/pkg/ml/layers/activations"
| 激活函数 | 函数 | 特点 | 什么时候用 |
|---|---|---|---|
| ReLU | activations.Relu(x) |
max(0, x) |
隐藏层默认选择 |
| Sigmoid | activations.Sigmoid(x) |
压到 (0,1) | 二分类输出层 |
| Tanh | activations.Tanh(x) |
压到 (-1,1) | RNN 隐层 |
| GeLU | activations.Gelu(x) |
光滑版 ReLU | Attention 模型 |
| Swish | activations.Swish(x) |
x·Sigmoid(x) |
EfficientNet |
选型建议:隐藏层用 ReLU。输出层根据任务选——二分类用 Sigmoid,多分类用 Softmax(activations.Softmax)。
10.5 搭一个两层 MLP(MNIST 风格)
import (
"github.com/gomlx/gomlx/pkg/ml/context"
"github.com/gomlx/gomlx/pkg/core/graph"
"github.com/gomlx/gomlx/pkg/ml/layers"
"github.com/gomlx/gomlx/pkg/ml/layers/activations"
)
func MNISTModel(ctx *context.Context, input *graph.Node) *graph.Node {
flat := graph.Reshape(input, -1, 784)
hidden := layers.DenseWithBias(ctx.In("hidden"), flat, 128)
hidden = activations.Relu(hidden)
output := layers.DenseWithBias(ctx.In("output"), hidden, 10)
return output
}
MNIST:输入 [batch, 28, 28, 1] → Flatten → [batch, 784] → Dense 128 + ReLU → Dense 10。10 个输出对应 0-9 十个数字类别。
10.6 Dropout:别让神经元偷懒
Dropout 在训练时随机把一部分神经元的输出置零。每步训练随机选不同的神经元休眠——相当于每步在训练一个"子网络"。
为什么有效:防止神经元之间形成"共谋"——比如神经元 A 只负责猫的左耳,B 只负责猫的右耳,两个永远同时激活。一旦测试时耳朵被遮挡,两个同时废。Dropout 强制每个神经元必须独立学习有用特征。
// 静态 dropout(rate 为常量)
hidden = layers.DropoutStatic(ctx.In("dropout"), hidden, 0.5)
Dropout 只在训练时生效。测试时自动关闭,所有权重会按比例缩放以保证输出幅度一致。rate=0.5 意味着每步随机丢掉一半神经元。
10.7 BatchNorm:让训练稳如老狗
BatchNorm 在一个 batch 内对每个特征做标准化:减均值、除标准差,再乘 γ 加 β。
效果:
- 让每一层的输入分布稳定——无论前面层的参数怎么变
- 可以用更高的学习率——加速收敛 5-10 倍
- 自带轻微正则化——有时可以省掉 Dropout
import "github.com/gomlx/gomlx/pkg/ml/layers/batchnorm"
hidden = batchnorm.New(ctx.In("bn"), hidden, -1).Done()
hidden = activations.Relu(hidden)
特征轴 -1:对最后一个维度做标准化。注意 batchnorm.New 返回一个 Builder,必须调 .Done() 才得到 *graph.Node。
10.8 收束
你现在有了搭神经网络的积木——层、激活函数、Dropout、BatchNorm。第 11 章教你用更高级的积木:CNN(图像专用层)和 Attention(序列专用层)。
作者:步子哥,资深AI研究员 | 连载中:《GoMLX从入门到精通》
上一章:第9章 训练管线
下一章预告:第11章 CNN与Attention——参数共享与动态加权
讨论回复
加载中...正在加载回复...
推荐
智谱 GLM-5 已上线
我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。