介绍
nn.Linear 类(线性层/全连接层),是深度学习中最基础也最核心的层,用于实现线性变换 y = x * W^T + b,广泛应用于分类、回归、特征映射等场景。
作用
是 PyTorch 封装的线性层,核心作用是对输入张量执行线性变换:
y = x \cdot W^T + b
x:输入张量(形状[..., in_features],...代表批量/序列等任意前置维度);W:权重矩阵(形状[out_features, in_features],可训练参数);b:偏置向量(形状[out_features],可训练参数,可选);y:输出张量(形状[..., out_features])。
关键特征
- 输入要求:最后一维必须等于
in_features(线性层的输入特征数); - 输出特征:最后一维变为
out_features(线性层的输出特征数); - 参数自动初始化:权重默认用 Kaiming 均匀分布初始化,偏置默认初始化为 0。
语法
nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
参数
| 参数 | 作用 | 注意事项 |
|---|---|---|
in_features |
输入特征数(输入张量最后一维的维度) | 必传,需与输入数据的最后一维匹配 |
out_features |
输出特征数(输出张量最后一维的维度) | 必传,决定线性层的输出维度 |
bias |
是否启用偏置(默认 True) | 设为 False 时,线性变换为 $y = xW^T$,无偏置项 b |
device |
指定层参数的存储设备(cpu/cuda) | 可省略,后续用 model.to(device) 统一设置更方便 |
dtype |
指定参数的数据类型(float32/float64) | 可省略,默认与输入数据类型一致 |
本质
PyTorch 中 nn.Linear 层的底层计算逻辑是线性变换 y = kx + b(矩阵形式为 y = xW + b)
参数解释:
x:输入张量(形状为[batch_size, in_features]);W:权重矩阵(nn.Linear的weight参数,形状为[out_features, in_features]);b:偏置向量(nn.Linear的bias参数,形状为[out_features]);y:输出张量(形状为[batch_size, out_features])。
提示:标量的 y = kx + b 是一维特例,nn.Linear 是高维通用形式,计算时会做矩阵乘法 + 向量加法(广播)。
nn.Linear 的参数维度
以 nn.Linear(in_features=2, out_features=3) 为例:
in_features=2:输入是2维向量;out_features=3:输出是3维向量;- 权重
weight:形状为[3, 2](输出维度 × 输入维度); - 偏置
bias:形状为[3](输出维度); - 计算逻辑:
y = x @ W.T + b(@是矩阵乘法,W.T是权重矩阵的转置,PyTorch 内部会自动处理转置,无需手动操作)。
代码验证
手动复现 nn.Linear 的计算
步骤1:定义 nn.Linear 层并固定参数
为了结果可复现,我们手动设置 weight 和 bias 的值,避免随机初始化:
import torch
import torch.nn as nn
# 1. 定义Linear层:输入2维,输出3维
linear_layer = nn.Linear(in_features=2, out_features=3)
# 2. 手动固定weight和bias(方便验证)
# weight形状:[3, 2],对应3个输出维度×2个输入维度
linear_layer.weight = nn.Parameter(torch.tensor([
[1.0, 2.0], # 第1个输出维度的权重
[3.0, 4.0], # 第2个输出维度的权重
[5.0, 6.0] # 第3个输出维度的权重
]))
# bias形状:[3],对应3个输出维度的偏置
linear_layer.bias = nn.Parameter(torch.tensor([0.1, 0.2, 0.3]))
# 3. 定义输入x:batch_size=1,输入2维(简化为单个样本)
x = torch.tensor([[10.0, 20.0]]) # 形状:[1, 2]
步骤2:用 nn.Linear 计算输出
# Linear层的输出
y_linear = linear_layer(x)
print("nn.Linear 输出:", y_linear)
# 输出:tensor([[ 50.1000, 110.2000, 170.3000]], grad_fn=<AddmmBackward0>)
计算过程:
[[1.0, 2.0],
[[10.0, 20.0]] * [3.0, 4.0], + [0.1, 0.2, 0.3]
[5.0, 6.0]]
执行结果:
10.0 * 1.0 + 20.0 * 2.0 + 0.1 = 50.1
10.0 * 3.0 + 20.0 * 4.0 + 0.2 = 101.2
10.0 * 5.0 + 20.0 * 6.0 + 0.3 = 170.3
步骤3:手动计算 xW + b 验证
# 手动提取weight和bias
W = linear_layer.weight # 形状[3, 2]
b = linear_layer.bias # 形状[3]
# 手动计算:x @ W.T + b(W.T 转置为[2,3],才能和x[1,2]做矩阵乘法)
y_manual = x @ W.T + b
print("手动计算 xW + b 输出:", y_manual)
# 输出:tensor([[ 50.1000, 110.2000, 170.3000]], grad_fn=<AddBackward0>)
步骤4:对比结果
# 验证两者是否完全相等(忽略梯度信息,只看数值)
is_equal = torch.allclose(y_linear, y_manual)
print("两者结果是否一致:", is_equal) # 输出:True
例子
先通过极简示例掌握 nn.Linear 的核心用法:
import torch
import torch.nn as nn
# 步骤1:定义线性层(输入特征数=5,输出特征数=3,启用偏置)
linear = nn.Linear(in_features=5, out_features=3, bias=True)
# 步骤2:准备输入(最后一维必须=in_features=5,前置维度任意)
# 示例1:单样本(shape [5])
x1 = torch.randn(5) # 1个样本,5维特征
y1 = linear(x1)
print("单样本输出形状:", y1.shape) # torch.Size([3])
# 示例2:批量样本(shape [4,5] → 4个样本,5维特征)
x2 = torch.randn(4, 5)
y2 = linear(x2)
print("批量样本输出形状:", y2.shape) # torch.Size([4, 3])
# 示例3:高维输入(shape [2,4,5] → 2个序列,4个样本,5维特征)
x3 = torch.randn(2, 4, 5)
y3 = linear(x3)
print("高维输入输出形状:", y3.shape) # torch.Size([2, 4, 3])(仅最后一维变换)
参数示例(禁用偏置/指定设备)
# 示例1:禁用偏置的线性层
linear_no_bias = nn.Linear(5, 3, bias=False)
print("是否有偏置参数:", hasattr(linear_no_bias, 'bias')) # False
# 示例2:指定设备(需CUDA环境)
if torch.cuda.is_available():
linear_cuda = nn.Linear(5, 3, device="cuda")
print("权重设备:", linear_cuda.weight.device) # cuda:0
print("偏置设备:", linear_cuda.bias.device) # cuda:0
else:
print("无CUDA环境,参数默认在CPU")
核心特性:可训练参数
nn.Linear 的权重(weight)和偏置(bias)是 nn.Parameter 类型的可训练参数,会自动注册到模型参数列表中:
# 查看线性层的可训练参数
print("=== 线性层参数 ===")
# 权重:shape [out_features, in_features] = [3,5]
print("权重形状:", linear.weight.shape)
print("权重初始化值:\n", linear.weight)
# 偏置:shape [out_features] = [3]
print("\n偏置形状:", linear.bias.shape)
print("偏置初始化值:", linear.bias) # 默认为0
# 遍历参数(模型训练时优化器会更新这些参数)
for name, param in linear.named_parameters():
print(f"\n参数名:{name},形状:{param.shape},是否可求梯度:{param.requires_grad}")
# 输出:
# 参数名:weight,形状:torch.Size([3, 5]),是否可求梯度:True
# 参数名:bias,形状:torch.Size([3]),是否可求梯度:True
实战场景:集成到自定义模型
nn.Linear 几乎是所有自定义模型的基础组件,以下是分类任务的典型用法:
# 示例:自定义简单分类模型(输入28×28=784维,输出10类)
class SimpleClassifier(nn.Module):
def __init__(self):
super().__init__()
# 线性层1:784 → 128(隐藏层)
self.fc1 = nn.Linear(784, 128)
# 线性层2:128 → 64(隐藏层)
self.fc2 = nn.Linear(128, 64)
# 线性层3:64 → 10(输出层,对应10个类别)
self.fc3 = nn.Linear(64, 10)
# 激活函数(线性层后需加非线性激活,否则多层等价于单层)
self.relu = nn.ReLU()
def forward(self, x):
# 输入:[batch, 28, 28] → 展平为 [batch, 784]
x = x.flatten(1) # 从第1维开始展平(保留batch维度)
# 前向传播:线性层 + 激活函数
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x) # 输出层不激活(交给CrossEntropyLoss处理)
return x
# 测试模型
model = SimpleClassifier()
# 模拟输入:4个样本,28×28像素
x = torch.randn(4, 28, 28)
output = model(x)
print("模型输出形状:", output.shape) # torch.Size([4, 10])(4个样本,10个类别logits)
# 查看模型所有线性层参数
print("\n=== 模型所有参数 ===")
for name, param in model.named_parameters():
print(f"{name}: {param.shape}")
常见易错点与技巧
(1)输入维度不匹配(最常见错误)
# 错误示例:输入最后一维≠in_features
linear = nn.Linear(5, 3)
x_bad = torch.randn(4, 4) # 最后一维=4≠5
# y_bad = linear(x_bad) # 报错:size mismatch, m1: [4 x 4], m2: [5 x 3]
# 解决:确保输入最后一维等于in_features
x_good = torch.randn(4, 5)
y_good = linear(x_good) # 正常运行
(2)手动初始化参数
默认初始化可能不满足需求,可手动重置权重/偏置:
# 手动初始化权重(正态分布)
nn.init.normal_(linear.weight, mean=0.0, std=0.01)
# 手动初始化偏置(常数1)
nn.init.constant_(linear.bias, 1.0)
print("手动初始化后权重:\n", linear.weight)
print("手动初始化后偏置:", linear.bias)
(3)冻结线性层参数(不更新)
训练时若想冻结某层参数,设置 requires_grad=False:
# 冻结fc1层的权重(仅更新其他层)
for param in model.fc1.parameters():
param.requires_grad = False
# 优化器仅更新未冻结的参数
optimizer = torch.optim.SGD(
[p for p in model.parameters() if p.requires_grad],
lr=0.1
)
(4)线性层的“维度变换”特性
nn.Linear 仅关注最后一维,因此可处理任意前置维度的输入(如序列、图像展平后的数据):
# 图像数据:[batch, channel, H, W] → 展平为 [batch, C×H×W]
img = torch.randn(4, 1, 28, 28) # 4张灰度图,28×28
img_flat = img.flatten(1) # [4, 784]
linear_img = nn.Linear(784, 10)
output_img = linear_img(img_flat)
print("图像输入输出形状:", output_img.shape) # [4, 10]
总结
nn.Linear(in_features, out_features)实现线性变换 $y = xW^T + b$,核心是将输入最后一维从in_features映射到out_features。- 关键参数:
in_features匹配输入最后一维,out_features定义输出维度,bias控制是否启用偏置。 - 核心特性:权重/偏置是可训练的
nn.Parameter,自动注册到模型参数列表,支持梯度更新。 - 实战技巧:线性层后需加非线性激活(如ReLU);输入需展平为二维(batch, features);可手动初始化参数或冻结部分层。