跳转至

神经网络测试用例

测试目标: 验证神经网络模型的功能和性能 测试类型: 单元测试、集成测试、性能测试 涉及组件: 神经网络层、激活函数、损失函数、优化器


📋 测试概述

测试框架

测试目标

  1. 功能测试: 验证神经网络各组件的正确性
  2. 性能测试: 评估模型的计算效率和内存使用
  3. 鲁棒性测试: 测试模型对异常输入的稳定性
  4. 梯度测试: 验证梯度计算的正确性

测试环境

  • Python版本: 3.8+
  • 深度学习框架: PyTorch
  • 测试框架: pytest
  • 数值计算: NumPy

🧪 测试用例列表

1. 基础层测试

测试用例1.1: 全连接层

测试目标: 验证全连接层的前向传播和反向传播

测试代码:

Python
import pytest
import torch
import torch.nn as nn

def test_linear_layer_forward():
    """测试全连接层前向传播"""
    # 创建全连接层
    linear = nn.Linear(in_features=10, out_features=5)

    # 创建输入
    x = torch.randn(2, 10)  # batch_size=2, input_features=10

    # 前向传播
    output = linear(x)

    # 验证输出维度
    assert output.shape == (2, 5), f"输出维度错误: {output.shape}"

    # 验证输出类型
    assert isinstance(output, torch.Tensor), "输出类型错误"

    print("✓ 全连接层前向传播测试通过")

def test_linear_layer_backward():
    """测试全连接层反向传播"""
    # 创建全连接层
    linear = nn.Linear(in_features=10, out_features=5)

    # 创建输入
    x = torch.randn(2, 10, requires_grad=True)

    # 前向传播
    output = linear(x)

    # 计算损失
    loss = output.sum()

    # 反向传播
    loss.backward()

    # 验证梯度存在
    assert x.grad is not None, "输入梯度不存在"
    assert linear.weight.grad is not None, "权重梯度不存在"
    assert linear.bias.grad is not None, "偏置梯度不存在"

    # 验证梯度形状
    assert x.grad.shape == x.shape, "输入梯度形状错误"
    assert linear.weight.grad.shape == linear.weight.shape, "权重梯度形状错误"

    print("✓ 全连接层反向传播测试通过")

预期结果: 前向传播输出维度正确,反向传播梯度计算正确


测试用例1.2: 卷积层

测试目标: 验证卷积层的前向传播

测试代码:

Python
def test_conv2d_layer():
    """测试2D卷积层"""
    # 创建卷积层
    conv = nn.Conv2d(
        in_channels=3,
        out_channels=16,
        kernel_size=3,
        stride=1,
        padding=1,
    )

    # 创建输入
    x = torch.randn(2, 3, 32, 32)  # batch_size=2, channels=3, height=32, width=32

    # 前向传播
    output = conv(x)

    # 计算期望输出尺寸
    expected_height = (32 + 2 * 1 - 3) // 1 + 1
    expected_width = (32 + 2 * 1 - 3) // 1 + 1

    # 验证输出维度
    assert output.shape == (2, 16, expected_height, expected_width), \
        f"输出维度错误: {output.shape}"

    print("✓ 2D卷积层测试通过")

def test_conv2d_with_stride():
    """测试带步长的卷积层"""
    # 创建卷积层
    conv = nn.Conv2d(
        in_channels=3,
        out_channels=16,
        kernel_size=3,
        stride=2,
        padding=1,
    )

    # 创建输入
    x = torch.randn(1, 3, 32, 32)

    # 前向传播
    output = conv(x)

    # 计算期望输出尺寸
    expected_height = (32 + 2 * 1 - 3) // 2 + 1
    expected_width = (32 + 2 * 1 - 3) // 2 + 1

    # 验证输出维度
    assert output.shape == (1, 16, expected_height, expected_width), \
        f"输出维度错误: {output.shape}"

    print("✓ 带步长的卷积层测试通过")

预期结果: 输出维度符合卷积计算公式


测试用例1.3: 池化层

测试目标: 验证池化层的前向传播

测试代码:

Python
def test_maxpool2d():
    """测试最大池化层"""
    # 创建最大池化层
    pool = nn.MaxPool2d(kernel_size=2, stride=2)

    # 创建输入
    x = torch.randn(1, 3, 32, 32)

    # 前向传播
    output = pool(x)

    # 验证输出维度
    assert output.shape == (1, 3, 16, 16), f"输出维度错误: {output.shape}"

    print("✓ 最大池化层测试通过")

def test_avgpool2d():
    """测试平均池化层"""
    # 创建平均池化层
    pool = nn.AvgPool2d(kernel_size=2, stride=2)

    # 创建输入
    x = torch.randn(1, 3, 32, 32)

    # 前向传播
    output = pool(x)

    # 验证输出维度
    assert output.shape == (1, 3, 16, 16), f"输出维度错误: {output.shape}"

    print("✓ 平均池化层测试通过")

预期结果: 输出维度为输入的一半


2. 激活函数测试

单元测试流程

测试用例2.1: ReLU激活函数

测试目标: 验证ReLU激活函数

测试代码:

Python
def test_relu():
    """测试ReLU激活函数"""
    relu = nn.ReLU()

    # 测试正值
    x_pos = torch.tensor([1.0, 2.0, 3.0])
    output_pos = relu(x_pos)
    assert torch.allclose(output_pos, x_pos), "正值处理错误"

    # 测试负值
    x_neg = torch.tensor([-1.0, -2.0, -3.0])
    output_neg = relu(x_neg)
    assert torch.allclose(output_neg, torch.zeros_like(x_neg)), "负值处理错误"

    # 测试混合值
    x_mixed = torch.tensor([-1.0, 0.0, 1.0])
    output_mixed = relu(x_mixed)
    expected = torch.tensor([0.0, 0.0, 1.0])
    assert torch.allclose(output_mixed, expected), "混合值处理错误"

    print("✓ ReLU激活函数测试通过")

def test_relu_gradient():
    """测试ReLU梯度"""
    x = torch.tensor([-2.0, -1.0, 0.0, 1.0, 2.0], requires_grad=True)
    relu = nn.ReLU()

    # 前向传播
    output = relu(x)

    # 计算损失
    loss = output.sum()

    # 反向传播
    loss.backward()

    # 验证梯度
    expected_grad = torch.tensor([0.0, 0.0, 0.0, 1.0, 1.0])
    assert torch.allclose(x.grad, expected_grad), "梯度计算错误"

    print("✓ ReLU梯度测试通过")

预期结果: 负值输出0,正值保持不变


测试用例2.2: Sigmoid激活函数

测试目标: 验证Sigmoid激活函数

测试代码:

Python
def test_sigmoid():
    """测试Sigmoid激活函数"""
    sigmoid = nn.Sigmoid()

    # 测试不同输入
    x = torch.tensor([-10.0, -1.0, 0.0, 1.0, 10.0])
    output = sigmoid(x)

    # 验证输出范围
    assert (output >= 0).all() and (output <= 1).all(), "输出范围错误"

    # 验证特定值
    assert abs(output[2] - 0.5) < 1e-6, "Sigmoid(0)应该等于0.5"
    assert output[0] < 0.001, "Sigmoid(-10)应该接近0"
    assert output[4] > 0.999, "Sigmoid(10)应该接近1"

    print("✓ Sigmoid激活函数测试通过")

def test_sigmoid_gradient():
    """测试Sigmoid梯度"""
    x = torch.tensor([0.0], requires_grad=True)
    sigmoid = nn.Sigmoid()

    # 前向传播
    output = sigmoid(x)

    # 计算损失
    loss = output.sum()

    # 反向传播
    loss.backward()

    # 验证梯度 (sigmoid在0处的导数应该是0.25)
    assert abs(x.grad.item() - 0.25) < 1e-6, "梯度计算错误"

    print("✓ Sigmoid梯度测试通过")

预期结果: 输出在(0, 1)范围内,sigmoid(0)=0.5


测试用例2.3: Softmax激活函数

测试目标: 验证Softmax激活函数

测试代码:

Python
def test_softmax():
    """测试Softmax激活函数"""
    softmax = nn.Softmax(dim=1)

    # 创建输入
    x = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])

    # 前向传播
    output = softmax(x)

    # 验证输出范围
    assert (output >= 0).all() and (output <= 1).all(), "输出范围错误"

    # 验证和为1
    row_sums = output.sum(dim=1)
    assert torch.allclose(row_sums, torch.ones_like(row_sums)), "行和不为1"

    # 验证单调性
    for i in range(x.shape[0]):
        assert output[i, 0] < output[i, 1] < output[i, 2], "单调性错误"

    print("✓ Softmax激活函数测试通过")

预期结果: 输出在(0, 1)范围内,每行和为1


3. 损失函数测试

测试用例3.1: 交叉熵损失

测试目标: 验证交叉熵损失函数

测试代码:

Python
def test_cross_entropy_loss():
    """测试交叉熵损失"""
    criterion = nn.CrossEntropyLoss()

    # 创建输入和目标
    logits = torch.randn(3, 5)  # batch_size=3, num_classes=5
    targets = torch.tensor([0, 2, 4])

    # 计算损失
    loss = criterion(logits, targets)

    # 验证损失类型
    assert isinstance(loss, torch.Tensor), "损失类型错误"
    assert loss.dim() == 0, "损失应该是标量"

    # 验证损失为正
    assert loss.item() > 0, "损失应该为正"

    print("✓ 交叉熵损失测试通过")

def test_cross_entropy_perfect_prediction():
    """测试完美预测的损失"""
    criterion = nn.CrossEntropyLoss()

    # 创建完美预测
    logits = torch.tensor([
        [10.0, -10.0, -10.0],
        [-10.0, 10.0, -10.0],
        [-10.0, -10.0, 10.0],
    ])
    targets = torch.tensor([0, 1, 2])

    # 计算损失
    loss = criterion(logits, targets)

    # 验证损失接近0
    assert loss.item() < 1e-6, "完美预测的损失应该接近0"

    print("✓ 完美预测损失测试通过")

预期结果: 损失为正数,完美预测时损失接近0


测试用例3.2: MSE损失

测试目标: 验证均方误差损失函数

测试代码:

Python
def test_mse_loss():
    """测试MSE损失"""
    criterion = nn.MSELoss()

    # 创建输入和目标
    predictions = torch.randn(3, 5)
    targets = torch.randn(3, 5)

    # 计算损失
    loss = criterion(predictions, targets)

    # 验证损失类型
    assert isinstance(loss, torch.Tensor), "损失类型错误"
    assert loss.dim() == 0, "损失应该是标量"

    # 验证损失为正
    assert loss.item() >= 0, "损失应该非负"

    print("✓ MSE损失测试通过")

def test_mse_perfect_prediction():
    """测试完美预测的MSE损失"""
    criterion = nn.MSELoss()

    # 创建完美预测
    predictions = torch.tensor([[1.0, 2.0, 3.0]])
    targets = torch.tensor([[1.0, 2.0, 3.0]])

    # 计算损失
    loss = criterion(predictions, targets)

    # 验证损失为0
    assert loss.item() == 0, "完美预测的MSE损失应该为0"

    print("✓ 完美预测MSE损失测试通过")

预期结果: 损失非负,完美预测时损失为0


4. 优化器测试

测试用例4.1: SGD优化器

测试目标: 验证SGD优化器

测试代码:

Python
def test_sgd_optimizer():
    """测试SGD优化器"""
    # 创建模型
    model = nn.Linear(10, 5)

    # 创建优化器
    optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

    # 创建输入和目标
    x = torch.randn(2, 10)
    y = torch.randn(2, 5)

    # 记录初始参数
    initial_weight = model.weight.clone()

    # 前向传播
    output = model(x)
    loss = nn.MSELoss()(output, y)

    # 反向传播
    optimizer.zero_grad()
    loss.backward()

    # 更新参数
    optimizer.step()

    # 验证参数已更新
    assert not torch.allclose(model.weight, initial_weight), "参数未更新"

    print("✓ SGD优化器测试通过")

预期结果: 参数被正确更新


测试用例4.2: Adam优化器

测试目标: 验证Adam优化器

测试代码:

Python
def test_adam_optimizer():
    """测试Adam优化器"""
    # 创建模型
    model = nn.Linear(10, 5)

    # 创建优化器
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

    # 创建输入和目标
    x = torch.randn(2, 10)
    y = torch.randn(2, 5)

    # 记录初始参数
    initial_weight = model.weight.clone()

    # 前向传播
    output = model(x)
    loss = nn.MSELoss()(output, y)

    # 反向传播
    optimizer.zero_grad()
    loss.backward()

    # 更新参数
    optimizer.step()

    # 验证参数已更新
    assert not torch.allclose(model.weight, initial_weight), "参数未更新"

    print("✓ Adam优化器测试通过")

预期结果: 参数被正确更新


5. 网络架构测试

集成测试策略

测试用例5.1: 简单神经网络

测试目标: 验证简单神经网络的前向传播

测试代码:

Python
class SimpleNN(nn.Module):  # 继承nn.Module定义神经网络层
    """简单神经网络"""

    def __init__(self, input_size, hidden_size, output_size):  # __init__构造方法,创建对象时自动调用
        super().__init__()  # super()调用父类方法
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x

def test_simple_nn():
    """测试简单神经网络"""
    # 创建网络
    model = SimpleNN(input_size=10, hidden_size=20, output_size=5)

    # 创建输入
    x = torch.randn(2, 10)

    # 前向传播
    output = model(x)

    # 验证输出维度
    assert output.shape == (2, 5), f"输出维度错误: {output.shape}"

    print("✓ 简单神经网络测试通过")

预期结果: 输出维度正确


测试用例5.2: CNN网络

测试目标: 验证CNN网络的前向传播

测试代码:

Python
class SimpleCNN(nn.Module):
    """简单CNN"""

    def __init__(self, num_classes=10):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc = nn.Linear(32 * 16 * 16, num_classes)

    def forward(self, x):
        x = self.pool(torch.relu(self.conv1(x)))
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

def test_simple_cnn():
    """测试简单CNN"""
    # 创建网络
    model = SimpleCNN(num_classes=10)

    # 创建输入
    x = torch.randn(2, 3, 32, 32)

    # 前向传播
    output = model(x)

    # 验证输出维度
    assert output.shape == (2, 10), f"输出维度错误: {output.shape}"

    print("✓ 简单CNN测试通过")

预期结果: 输出维度正确


6. 梯度检查测试

测试用例6.1: 数值梯度检查

测试目标: 使用数值梯度验证解析梯度

测试代码:

Python
def numerical_gradient(f, x, eps=1e-6):
    """
    计算数值梯度

    Args:
        f: 函数
        x: 输入张量
        eps: 小量

    Returns:
        数值梯度
    """
    grad = torch.zeros_like(x)

    for i in range(x.numel()):
        # 创建扰动
        x_plus = x.clone()
        x_plus.view(-1)[i] += eps  # view重塑张量形状(要求内存连续)

        x_minus = x.clone()
        x_minus.view(-1)[i] -= eps

        # 计算数值梯度
        f_plus = f(x_plus)
        f_minus = f(x_minus)
        grad.view(-1)[i] = (f_plus - f_minus) / (2 * eps)

    return grad

def test_gradient_check():
    """测试梯度检查"""
    # 创建模型
    model = nn.Linear(5, 3)

    # 创建输入
    x = torch.randn(1, 5, requires_grad=True)

    # 定义损失函数
    def loss_fn(x):
        output = model(x)
        return output.sum()

    # 计算解析梯度
    loss = loss_fn(x)
    loss.backward()
    analytical_grad = x.grad.clone()

    # 计算数值梯度
    numerical_grad = numerical_gradient(loss_fn, x)

    # 比较梯度
    relative_error = torch.abs(analytical_grad - numerical_grad) / \
                     (torch.abs(analytical_grad) + torch.abs(numerical_grad) + 1e-10)

    max_error = relative_error.max().item()  # 链式调用,连续执行多个方法

    assert max_error < 1e-4, f"梯度误差过大: {max_error}"

    print(f"✓ 梯度检查通过 (最大误差: {max_error:.2e})")

预期结果: 解析梯度和数值梯度的误差小于1e-4


7. 性能测试

性能指标

测试用例7.1: 推理速度测试

测试目标: 测试模型推理速度

测试代码:

Python
import time

def test_inference_speed():
    """测试推理速度"""
    # 创建模型
    model = SimpleCNN(num_classes=10)
    model.eval()  # eval()开启评估模式(关闭Dropout等)

    # 创建输入
    x = torch.randn(1, 3, 32, 32)

    # 预热
    for _ in range(10):
        _ = model(x)

    # 测试
    num_iterations = 100
    start_time = time.time()

    with torch.no_grad():  # 禁用梯度计算,节省内存
        for _ in range(num_iterations):
            _ = model(x)

    end_time = time.time()

    avg_time = (end_time - start_time) / num_iterations
    throughput = num_iterations / (end_time - start_time)

    print(f"✓ 平均推理时间: {avg_time * 1000:.2f} ms")
    print(f"✓ 吞吐量: {throughput:.2f} samples/s")

预期结果: 推理时间在合理范围内


测试用例7.2: 内存使用测试

测试目标: 测试模型内存使用

测试代码:

Python
def test_memory_usage():
    """测试内存使用"""
    if not torch.cuda.is_available():
        print("⚠ CUDA不可用,跳过内存测试")
        return

    # 创建模型
    model = SimpleCNN(num_classes=10).cuda()
    model.eval()

    # 创建输入
    x = torch.randn(16, 3, 32, 32).cuda()

    # 记录初始内存
    torch.cuda.empty_cache()
    initial_memory = torch.cuda.memory_allocated()

    # 推理
    with torch.no_grad():
        _ = model(x)

    # 记录峰值内存
    peak_memory = torch.cuda.max_memory_allocated()
    memory_used = (peak_memory - initial_memory) / 1024**2  # MB

    print(f"✓ 内存使用: {memory_used:.2f} MB")

    # 验证内存使用在合理范围内
    assert memory_used < 100, "内存使用过大"

预期结果: 内存使用在合理范围内


📊 测试执行

测试覆盖率

运行所有测试

Bash
# 运行所有测试
pytest tests/test_neural_network.py -v

# 运行特定测试
pytest tests/test_neural_network.py::test_linear_layer_forward -v

# 生成覆盖率报告
pytest tests/test_neural_network.py --cov=neural_network --cov-report=html

✅ 验证方法

模型验证

1. 自动化验证

  • 运行所有测试用例
  • 检查断言是否通过
  • 记录测试结果

2. 梯度验证

  • 使用数值梯度验证解析梯度
  • 检查梯度误差
  • 确保梯度计算正确

3. 性能基准

  • 建立性能基准
  • 监控性能变化
  • 优化模型性能

📝 测试报告

回归测试

测试报告应包含:

  1. 测试概览
  2. 测试用例数量
  3. 通过/失败统计
  4. 代码覆盖率

  5. 详细结果

  6. 每个测试用例的结果
  7. 性能指标
  8. 梯度检查结果

  9. 问题分析

  10. 失败原因分析
  11. 改进建议
  12. 后续计划

测试报告模板

测试完成标准: 所有测试用例通过 推荐测试频率: 每次代码更新 测试维护周期: 每周