这个程序实现了一个完整的深度学习框架,包含以下核心组件:

1. 自动微分系统

  • Tensor类:支持梯度追踪和反向传播

  • Function类:所有运算的基类,实现前向和反向传播

  • 计算图:通过拓扑排序实现自动梯度计算

2. 神经网络层

  • Conv2d:卷积层,使用im2col技术优化

  • Linear:全连接层

  • BatchNorm:批量归一化层

  • Dropout:正则化层

  • Sequential:顺序容器

3. 损失函数

  • CrossEntropyLoss:交叉熵损失(分类任务)

  • MSELoss:均方误差损失(回归任务)

4. 优化器

  • SGD:支持动量

  • Adam:自适应学习率优化器

5. 技术亮点

  • 使用im2col/col2im高效实现卷积

  • BatchNorm完整的反向传播推导

  • 支持广播操作的梯度归约

  • 数值稳定的Softmax实现

import numpy as np
from typing import List, Union, Callable, Any, Optional, Tuple

# ============================================================================
# 第一部分:核心张量实现(支持自动微分)
# ============================================================================

class Tensor:
    """
    简易版张量,支持自动微分
    这是整个深度学习框架的基础,类似于PyTorch的Tensor
    
    核心功能:
    1. 存储数据(numpy数组)
    2. 记录计算历史(通过grad_fn)
    3. 支持反向传播(backward方法)
    """
    
    def __init__(self, data: Union[float, list, np.ndarray], requires_grad=False, grad_fn=None, training=True):
        # 将数据统一转为 numpy 数组存储,确保数据类型为float32
        if isinstance(data, (int, float)):
            data = np.array([data], dtype=np.float32)
        elif isinstance(data, list):
            data = np.array(data, dtype=np.float32)
        elif isinstance(data, np.ndarray):
            data = data.astype(np.float32)
        
        self.data = data                # 实际存储的数据
        self.requires_grad = requires_grad  # 是否需要计算梯度
        self.grad = None                # 梯度值,初始为None
        self.grad_fn = grad_fn          # 梯度函数,记录如何计算梯度
        self.training = training        # 训练模式标志(用于Dropout和BatchNorm)
        
    @property
    def shape(self):
        """返回张量形状"""
        return self.data.shape
    
    def __repr__(self):
        return f"Tensor(shape={self.shape}, requires_grad={self.requires_grad})"
    
    def backward(self, gradient=None):
        """
        反向传播,计算所有 requires_grad=True 的 Tensor 的梯度
        使用拓扑排序遍历计算图,从输出到输入依次计算梯度
        
        参数:
            gradient: 初始梯度,如果是标量张量可以省略
        """
        # 如果没有提供初始梯度且当前是标量,则初始梯度设为1
        if gradient is None:
            if self.shape == (1,) or self.shape == ():
                gradient = np.ones_like(self.data)
            else:
                raise RuntimeError("backward需要传入gradient参数,除非是标量张量")
        
        # 拓扑排序:收集所有需要计算梯度的节点
        topo = []           # 存储拓扑排序的结果
        visited = set()     # 记录已访问的节点
        
        def build_topo(tensor):
            """递归构建拓扑排序"""
            if tensor not in visited:
                visited.add(tensor)
                # 如果有梯度函数,先处理它的前驱节点
                if tensor.grad_fn:
                    for prev_tensor in tensor.grad_fn.prev_tensors:
                        if isinstance(prev_tensor, Tensor):
                            build_topo(prev_tensor)
                topo.append(tensor)  # 后序添加
        
        build_topo(self)
        
        # 初始化当前张量的梯度
        self.grad = gradient.copy()
        
        # 反向遍历拓扑序列,从输出到输入
        for tensor in reversed(topo):
            if tensor.grad_fn and tensor.grad is not None:
                # 调用梯度函数计算前驱节点的梯度
                grad_inputs = tensor.grad_fn.backward(tensor.grad)
                if grad_inputs is not None:
                    # 确保返回的是列表或元组
                    if not isinstance(grad_inputs, (list, tuple)):
                        grad_inputs = [grad_inputs]
                    # 将梯度累加到前驱张量上
                    for i, prev_tensor in enumerate(tensor.grad_fn.prev_tensors):
                        if i < len(grad_inputs) and grad_inputs[i] is not None and isinstance(prev_tensor, Tensor):
                            if prev_tensor.requires_grad:
                                if prev_tensor.grad is None:
                                    prev_tensor.grad = grad_inputs[i]
                                else:
                                    prev_tensor.grad += grad_inputs[i]
    
    def zero_grad(self):
        """清零梯度,在每次反向传播前调用"""
        self.grad = None
        # 递归清零所有前驱节点的梯度
        if self.grad_fn:
            for prev in self.grad_fn.prev_tensors:
                if isinstance(prev, Tensor) and prev.requires_grad:
                    prev.zero_grad()
    
    def detach(self):
        """返回一个新的Tensor,不跟踪梯度(类似PyTorch的detach)"""
        return Tensor(self.data.copy(), requires_grad=False)
    
    def numpy(self):
        """转换为numpy数组"""
        return self.data
    
    def item(self):
        """如果是标量,返回Python数值"""
        return self.data.item()
    
    # 以下是一些便捷方法,内部调用对应的Function
    def sum(self, axis=None, keepdims=False):
        return Sum().apply(self, axis, keepdims)
    
    def mean(self, axis=None, keepdims=False):
        return Mean().apply(self, axis, keepdims)
    
    def reshape(self, *shape):
        return Reshape().apply(self, *shape)
    
    def transpose(self, axes=None):
        return Transpose().apply(self, axes)


# ============================================================================
# 第二部分:运算操作与计算图节点
# ============================================================================

class Function:
    """
    运算的基类,所有运算都继承此类
    实现前向传播和反向传播,是自动微分的核心
    
    设计模式:每个运算都是一个Function对象,记录了输入和输出,实现了forward和backward
    """
    
    def __init__(self):
        self.prev_tensors = []   # 前驱张量(输入)
        self.saved_tensors = []  # 保存的中间结果(用于反向传播)
    
    def apply(self, *tensors, **kwargs):
        """
        应用函数到张量,执行前向传播
        参数:
            *tensors: 输入张量
            **kwargs: 额外参数(如axis、keepdims等)
        返回:
            结果张量
        """
        self.prev_tensors = tensors
        # 提取数据并调用forward(如果是Tensor则取data,否则直接使用)
        data_inputs = [t.data if isinstance(t, Tensor) else t for t in tensors]
        result_data = self.forward(*data_inputs, **kwargs)
        
        # 判断结果是否需要梯度:只要任一输入需要梯度,结果就需要梯度
        requires_grad = any(
            (isinstance(t, Tensor) and t.requires_grad) or
            (hasattr(t, 'parameters') and any(isinstance(p, Tensor) and p.requires_grad for p in getattr(t, 'parameters')))
            for t in tensors
        )
        # 创建结果张量,grad_fn指向当前Function
        result = Tensor(result_data, requires_grad=requires_grad, grad_fn=self)
        return result
    
    def save_for_backward(self, *tensors):
        """保存用于反向传播的中间结果"""
        self.saved_tensors = tensors
    
    def forward(self, *inputs):
        """前向传播,子类必须实现"""
        raise NotImplementedError
    
    def backward(self, *grad_output):
        """反向传播,子类必须实现"""
        raise NotImplementedError


def _reduce_grad_to_shape(grad, shape):
    """
    将梯度reduce到指定形状
    用于处理广播操作的反向传播
    """
    gshape = grad.shape
    if gshape == shape:
        return grad
    # 如果梯度维度更多,先求和降维
    if len(gshape) > len(shape):
        for _ in range(len(gshape) - len(shape)):
            grad = np.sum(grad, axis=0)
        gshape = grad.shape
    # 处理维度对齐后的求和
    aligned_shape = (1,) * (len(gshape) - len(shape)) + tuple(shape)
    for i in range(len(gshape)):
        if aligned_shape[i] == 1 and gshape[i] > 1:
            grad = np.sum(grad, axis=i, keepdims=True)
    return grad.reshape(shape)


class Add(Function):
    """加法运算"""
    def forward(self, x, y):
        self.save_for_backward(x.shape, y.shape)
        return x + y
    
    def backward(self, grad_output):
        x_shape, y_shape = self.saved_tensors
        # 梯度需要广播回原始形状
        return _reduce_grad_to_shape(grad_output, x_shape), _reduce_grad_to_shape(grad_output, y_shape)


class Mul(Function):
    """乘法运算"""
    def forward(self, x, y):
        self.save_for_backward(x, y)
        return x * y
    
    def backward(self, grad_output):
        x, y = self.saved_tensors
        grad_x = grad_output * y
        grad_y = grad_output * x
        return _reduce_grad_to_shape(grad_x, x.shape), _reduce_grad_to_shape(grad_y, y.shape)


class MatMul(Function):
    """矩阵乘法运算"""
    def forward(self, x, y):
        self.save_for_backward(x, y)
        return x @ y
    
    def backward(self, grad_output):
        x, y = self.saved_tensors
        # d(x@y)/dx = grad_output @ y.T
        grad_x = grad_output @ y.T
        # d(x@y)/dy = x.T @ grad_output
        grad_y = x.T @ grad_output
        return grad_x, grad_y


class ReLU(Function):
    """ReLU激活函数:max(0, x)"""
    def forward(self, x):
        self.save_for_backward(x)
        return np.maximum(0, x)
    
    def backward(self, grad_output):
        x = self.saved_tensors[0]
        grad = grad_output.copy()
        # 负值处的梯度为0
        grad[x <= 0] = 0
        return grad


class Sum(Function):
    """求和运算"""
    def forward(self, x, axis=None, keepdims=False):
        self.save_for_backward(x, axis, keepdims)
        return np.sum(x, axis=axis, keepdims=keepdims)
    
    def backward(self, grad_output):
        x, axis, keepdims = self.saved_tensors
        # 将梯度的形状还原到输入形状
        if axis is not None:
            if keepdims:
                grad = grad_output
            else:
                # 扩展缺失的维度
                if isinstance(axis, int):
                    grad = np.expand_dims(grad_output, axis=axis)
                else:
                    for ax in sorted(axis):
                        grad_output = np.expand_dims(grad_output, axis=ax)
                    grad = grad_output
            # 广播到原始形状
            grad = np.broadcast_to(grad, x.shape)
        else:
            grad = grad_output * np.ones_like(x)
        return grad


class Mean(Function):
    """均值运算"""
    def forward(self, x, axis=None, keepdims=False):
        self.save_for_backward(x, axis, keepdims)
        return np.mean(x, axis=axis, keepdims=keepdims)
    
    def backward(self, grad_output):
        x, axis, keepdims = self.saved_tensors
        # 计算缩放因子:1/元素个数
        if axis is None:
            scale = 1.0 / x.size
        else:
            if isinstance(axis, int):
                scale = 1.0 / x.shape[axis]
            else:
                size = 1
                for ax in axis:
                    size *= x.shape[ax]
                scale = 1.0 / size
        
        # 还原形状(同Sum)
        if axis is not None:
            if keepdims:
                grad = grad_output
            else:
                if isinstance(axis, int):
                    grad = np.expand_dims(grad_output, axis=axis)
                else:
                    for ax in sorted(axis):
                        grad_output = np.expand_dims(grad_output, axis=ax)
                    grad = grad_output
            grad = np.broadcast_to(grad, x.shape)
        else:
            grad = grad_output * np.ones_like(x)
        
        return grad * scale


class Reshape(Function):
    """改变形状"""
    def forward(self, x, *shape):
        self.save_for_backward(x.shape)
        return x.reshape(*shape)
    
    def backward(self, grad_output):
        original_shape = self.saved_tensors[0]
        return grad_output.reshape(original_shape)


class Transpose(Function):
    """转置操作"""
    def forward(self, x, axes):
        self.save_for_backward(axes)
        return np.transpose(x, axes)
    
    def backward(self, grad_output):
        axes = self.saved_tensors[0]
        # 计算逆转置:反向应用相同的转置
        if axes is None:
            inv_axes = None
        else:
            inv_axes = [0] * len(axes)
            for i, a in enumerate(axes):
                inv_axes[a] = i
        return np.transpose(grad_output, inv_axes)


class Log(Function):
    """自然对数,添加小量防止数值问题"""
    def forward(self, x):
        self.save_for_backward(x)
        return np.log(x + 1e-8)
    
    def backward(self, grad_output):
        x = self.saved_tensors[0]
        return grad_output / (x + 1e-8)


class Exp(Function):
    """指数函数"""
    def forward(self, x):
        self.save_for_backward(x)
        return np.exp(x)
    
    def backward(self, grad_output):
        x = self.saved_tensors[0]
        return grad_output * np.exp(x)


class Negative(Function):
    """取负"""
    def forward(self, x):
        return -x
    
    def backward(self, grad_output):
        return -grad_output


class Power(Function):
    """幂运算"""
    def forward(self, x, power):
        self.save_for_backward(x, power)
        return x ** power
    
    def backward(self, grad_output):
        x, power = self.saved_tensors
        return grad_output * power * (x ** (power - 1))


class Div(Function):
    """除法运算"""
    def forward(self, x, y):
        self.save_for_backward(x, y)
        return x / y
    
    def backward(self, grad_output):
        x, y = self.saved_tensors
        grad_x = grad_output / y
        grad_y = -grad_output * x / (y ** 2)
        return _reduce_grad_to_shape(grad_x, x.shape), _reduce_grad_to_shape(grad_y, y.shape)


# ============================================================================
# 运算符重载:让Tensor支持 + - * / @ 等操作符
# ============================================================================

def _add(self, other):
    if not isinstance(other, Tensor):
        other = Tensor(other)
    return Add().apply(self, other)

def _mul(self, other):
    if not isinstance(other, Tensor):
        other = Tensor(other)
    return Mul().apply(self, other)

def _matmul(self, other):
    if not isinstance(other, Tensor):
        other = Tensor(other)
    return MatMul().apply(self, other)

def _neg(self):
    return Negative().apply(self)

def _sub(self, other):
    return self + (-other)

def _truediv(self, other):
    if not isinstance(other, Tensor):
        other = Tensor(other)
    return Div().apply(self, other)

def _pow(self, power):
    return Power().apply(self, power)

# 将运算符绑定到Tensor类
Tensor.__add__ = _add
Tensor.__mul__ = _mul
Tensor.__matmul__ = _matmul
Tensor.__neg__ = _neg
Tensor.__sub__ = _sub
Tensor.__truediv__ = _truediv
Tensor.relu = lambda self: ReLU().apply(self)
Tensor.sum = lambda self, axis=None, keepdims=False: Sum().apply(self, axis, keepdims)
Tensor.mean = lambda self, axis=None, keepdims=False: Mean().apply(self, axis, keepdims)
Tensor.log = lambda self: Log().apply(self)
Tensor.exp = lambda self: Exp().apply(self)


# ============================================================================
# 第三部分:卷积层(使用 im2col 实现)
# ============================================================================

def im2col(img, kernel_h, kernel_w, stride=1, pad=0):
    """
    将图像转换为列矩阵,用于高效实现卷积
    
    原理:将每个卷积窗口展平成一列,所有列组成一个大矩阵
    这样卷积运算就变成了矩阵乘法
    
    参数:
        img: 输入图像 [N, C, H, W] (N:批量大小, C:通道数, H:高度, W:宽度)
        kernel_h, kernel_w: 卷积核尺寸
        stride: 步长
        pad: 填充
    
    返回:
        cols: [N*out_h*out_w, C*kernel_h*kernel_w]
              每一行对应一个卷积窗口的展平向量
    """
    # 应用填充
    if pad > 0:
        img = np.pad(img, ((0,0), (0,0), (pad,pad), (pad,pad)), mode='constant')
    
    N, C, H, W = img.shape
    # 计算输出特征图的尺寸
    out_h = (H - kernel_h) // stride + 1
    out_w = (W - kernel_w) // stride + 1
    
    # 初始化列矩阵
    cols = np.zeros((N, out_h, out_w, C * kernel_h * kernel_w))
    
    # 对每个位置提取窗口并展平
    for n in range(N):
        for i in range(out_h):
            for j in range(out_w):
                h_start = i * stride
                w_start = j * stride
                patch = img[n, :, h_start:h_start+kernel_h, w_start:w_start+kernel_w]
                cols[n, i, j, :] = patch.flatten()
    
    return cols.reshape(-1, C * kernel_h * kernel_w)


def col2im(cols, img_shape, kernel_h, kernel_w, stride=1, pad=0):
    """
    将列矩阵转换回图像(用于反向传播)
    
    参数:
        cols: 列矩阵 [N*out_h*out_w, C*kernel_h*kernel_w]
        img_shape: 原始图像形状 (N, C, H, W)
        kernel_h, kernel_w: 卷积核尺寸
        stride: 步长
        pad: 填充
    """
    N, C, H, W = img_shape
    H_pad = H + 2 * pad
    W_pad = W + 2 * pad
    out_h = (H_pad - kernel_h) // stride + 1
    out_w = (W_pad - kernel_w) // stride + 1
    
    # 初始化梯度图像(带填充)
    img_grad = np.zeros((N, C, H_pad, W_pad))
    cols = cols.reshape(N, out_h, out_w, -1)
    
    # 将每个列累加到对应的图像区域
    for n in range(N):
        for i in range(out_h):
            for j in range(out_w):
                h_start = i * stride
                w_start = j * stride
                patch = cols[n, i, j, :].reshape(C, kernel_h, kernel_w)
                img_grad[n, :, h_start:h_start+kernel_h, w_start:w_start+kernel_w] += patch
    
    # 移除填充
    if pad > 0:
        img_grad = img_grad[:, :, pad:-pad, pad:-pad]
    
    return img_grad


class Conv2d:
    """
    二维卷积层
    
    使用im2col技术将卷积转换为矩阵乘法,提高效率
    """
    
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.kernel_size = kernel_size if isinstance(kernel_size, tuple) else (kernel_size, kernel_size)
        self.stride = stride
        self.padding = padding
        
        # Xavier初始化:保持梯度方差一致
        # 公式:Uniform(-limit, limit),其中 limit = sqrt(6/(fan_in+fan_out))
        limit = np.sqrt(6.0 / (in_channels * self.kernel_size[0] * self.kernel_size[1] + out_channels))
        self.weight = Tensor(
            np.random.uniform(-limit, limit, 
                            (out_channels, in_channels, self.kernel_size[0], self.kernel_size[1])),
            requires_grad=True
        )
        self.bias = Tensor(np.zeros(out_channels), requires_grad=True)
        self.parameters = [self.weight, self.bias]
    
    def __call__(self, x):
        return Conv2dFunction().apply(self, x)


class Conv2dFunction(Function):
    """卷积运算的函数包装器,实现前向和反向传播"""
    
    def forward(self, layer, x):
        """
        前向传播:使用im2col + 矩阵乘法实现卷积
        """
        self.layer = layer
        self.save_for_backward(x)
        
        N, C, H, W = x.shape
        k_h, k_w = layer.kernel_size
        stride = layer.stride
        padding = layer.padding
        
        # 步骤1:将输入图像转换为列矩阵
        x_cols = im2col(x.data, k_h, k_w, stride, padding)
        
        # 步骤2:将卷积核reshape为2D矩阵 [out_channels, in_channels*k_h*k_w]
        w = layer.weight.data.reshape(layer.out_channels, -1)
        
        # 步骤3:矩阵乘法实现卷积
        out_h = (H + 2*padding - k_h) // stride + 1
        out_w = (W + 2*padding - k_w) // stride + 1
        out = w @ x_cols.T  # [out_channels, N*out_h*out_w]
        
        # 步骤4:添加偏置
        out = out + layer.bias.data.reshape(-1, 1)
        
        # 步骤5:重塑为输出形状 [N, out_channels, out_h, out_w]
        out = out.reshape(layer.out_channels, N, out_h, out_w).transpose(1, 0, 2, 3)
        
        # 保存用于反向传播
        self.x_shape = x.shape
        self.x_cols = x_cols
        
        return out
    
    def backward(self, grad_output):
        """
        反向传播:计算权重、偏置和输入的梯度
        """
        layer = self.layer
        N, C, H, W = self.x_shape
        k_h, k_w = layer.kernel_size
        stride = layer.stride
        padding = layer.padding
        
        # 重塑输出梯度 [N, out_channels, out_h, out_w] -> [out_channels, N*out_h*out_w]
        grad_output_reshaped = grad_output.transpose(1, 0, 2, 3).reshape(layer.out_channels, -1)
        
        # 权重的梯度:dL/dW = grad_output @ x_cols
        grad_weight = grad_output_reshaped @ self.x_cols
        grad_weight = grad_weight.reshape(layer.out_channels, C, k_h, k_w)
        
        # 偏置的梯度:对输出梯度求和
        grad_bias = np.sum(grad_output_reshaped, axis=1)
        
        # 输入的梯度:dL/dx = W.T @ grad_output,然后col2im转换
        w_reshaped = layer.weight.data.reshape(layer.out_channels, -1)
        grad_x_cols = w_reshaped.T @ grad_output_reshaped
        grad_x = col2im(grad_x_cols, (N, C, H, W), k_h, k_w, stride, padding)
        
        # 更新参数的梯度
        if layer.weight.requires_grad:
            if layer.weight.grad is None:
                layer.weight.grad = grad_weight
            else:
                layer.weight.grad += grad_weight
        
        if layer.bias.requires_grad:
            if layer.bias.grad is None:
                layer.bias.grad = grad_bias
            else:
                layer.bias.grad += grad_bias
        
        return None, grad_x  # 第一个返回值为layer的梯度(None,因为layer不是Tensor)


# ============================================================================
# 第四部分:神经网络层
# ============================================================================

class Linear:
    """
    全连接层(线性层)
    实现 y = x @ W + b
    """
    
    def __init__(self, in_features, out_features):
        # Xavier初始化
        limit = np.sqrt(6.0 / (in_features + out_features))
        self.weight = Tensor(
            np.random.uniform(-limit, limit, (in_features, out_features)),
            requires_grad=True
        )
        self.bias = Tensor(np.zeros(out_features), requires_grad=True)
        self.parameters = [self.weight, self.bias]
    
    def __call__(self, x):
        # 确保输入是二维的
        if x.data.ndim == 1:
            x = x.reshape(-1, x.shape[0])
        result = x @ self.weight + self.bias
        return result


class Dropout:
    """
    Dropout层
    
    训练时以概率p随机将神经元输出置0,并缩放1/(1-p)保持期望不变
    测试时不进行dropout,只使用完整的网络
    
    作用:防止过拟合,提高模型泛化能力
    """
    
    def __init__(self, p=0.5):
        self.p = p          # 失活概率
        self.training = True
        self.mask = None    # 保存掩码,反向传播时不需要梯度
    
    def __call__(self, x):
        if not self.training or self.p == 0:
            return x
        
        # 生成掩码:以概率1-p保留,并缩放
        # 缩放因子1/(1-p)保持期望不变:E[mask*x] = (1-p)*x/(1-p) = x
        self.mask = (np.random.rand(*x.shape) > self.p) / (1 - self.p)
        return x * self.mask
    
    def train(self):
        self.training = True
    
    def eval(self):
        self.training = False


class BatchNorm:
    """
    批量归一化层
    
    对每个特征维度进行归一化:使数据分布稳定,加速训练
    
    公式:y = gamma * (x - mean) / sqrt(var + eps) + beta
    
    训练时使用batch的均值和方差,同时更新running_mean/running_var
    测试时使用累积的running_mean和running_var
    """
    
    def __init__(self, num_features, eps=1e-5, momentum=0.1):
        self.num_features = num_features
        self.eps = eps              # 防止除零的小量
        self.momentum = momentum    # 运行时统计的更新动量
        
        # 可学习参数:缩放和平移
        self.gamma = Tensor(np.ones(num_features), requires_grad=True)
        self.beta = Tensor(np.zeros(num_features), requires_grad=True)
        
        # 运行时统计(用于推理)
        self.running_mean = np.zeros(num_features)
        self.running_var = np.ones(num_features)
        
        self.training = True
        self.parameters = [self.gamma, self.beta]
    
    def __call__(self, x):
        return BatchNormFunction().apply(self, x)


class BatchNormFunction(Function):
    """BatchNorm的函数包装器,实现前向和反向传播"""
    
    def forward(self, layer, x):
        self.layer = layer
        
        # 处理不同维度的输入
        # 对于卷积层:[N, C, H, W] -> reshape为 [C, N*H*W]
        if x.ndim > 2:
            N, C, H, W = x.shape
            x_reshaped = x.transpose(1, 0, 2, 3).reshape(C, -1)
        else:
            # 对于全连接层:[N, C] -> [C, N]
            N = x.shape[0]
            C = x.shape[1] if x.ndim > 1 else 1
            x_reshaped = x.T if x.ndim > 1 else x.reshape(1, -1)
        
        self.original_shape = x.shape
        self.x_ndim = x.ndim
        self.N = N
        self.C = C
        
        if layer.training:
            # 计算当前batch的均值和方差
            mean = np.mean(x_reshaped, axis=1, keepdims=True)
            var = np.var(x_reshaped, axis=1, keepdims=True)
            
            # 更新运行时统计(使用指数移动平均)
            layer.running_mean = (1 - layer.momentum) * layer.running_mean + layer.momentum * mean.flatten()
            layer.running_var = (1 - layer.momentum) * layer.running_var + layer.momentum * var.flatten()
        else:
            # 推理时使用累积的统计量
            mean = layer.running_mean.reshape(-1, 1)
            var = layer.running_var.reshape(-1, 1)
        
        # 归一化
        x_norm = (x_reshaped - mean) / np.sqrt(var + layer.eps)
        
        # 缩放和平移
        gamma = layer.gamma.data.reshape(-1, 1)
        beta = layer.beta.data.reshape(-1, 1)
        out = gamma * x_norm + beta
        
        # 保存用于反向传播
        self.x_reshaped = x_reshaped
        self.mean = mean
        self.var = var
        self.std_inv = 1.0 / np.sqrt(var + layer.eps)
        
        # 恢复原始形状
        if x.ndim > 2:
            out = out.reshape(C, N, H, W).transpose(1, 0, 2, 3)
        elif x.ndim == 2:
            out = out.T
        else:
            out = out.flatten()
        
        return out
    
    def backward(self, grad_output):
        """
        BatchNorm反向传播的推导较复杂
        需要计算输入、gamma、beta的梯度
        """
        layer = self.layer
        
        # 重塑梯度到 [C, N*...] 形状
        if self.x_ndim > 2:
            _, C, H, W = self.original_shape
            grad_reshaped = grad_output.transpose(1, 0, 2, 3).reshape(self.C, -1)
        elif self.x_ndim == 2:
            grad_reshaped = grad_output.T
        else:
            grad_reshaped = grad_output.reshape(1, -1)
        
        N = self.N
        
        # 中心化后的输入
        x_centered = self.x_reshaped - self.mean
        
        # 计算输入梯度
        # 公式推导较复杂,最终结果:
        # grad_norm = grad_output * gamma
        # grad_var = sum(grad_norm * x_centered * (-0.5 * std_inv^3))
        # grad_mean = sum(grad_norm * (-std_inv)) + grad_var * mean(-2*x_centered)
        # grad_x = grad_norm * std_inv + grad_var * (2*x_centered/N) + grad_mean/N
        
        grad_norm = grad_reshaped * layer.gamma.data.reshape(-1, 1)
        grad_var = np.sum(grad_norm * x_centered * -0.5 * self.std_inv**3, axis=1, keepdims=True)
        grad_mean = np.sum(grad_norm * -self.std_inv, axis=1, keepdims=True) + grad_var * np.mean(-2.0 * x_centered, axis=1, keepdims=True)
        
        grad_x = grad_norm * self.std_inv + grad_var * (2.0 * x_centered / N) + grad_mean / N
        
        # gamma和beta的梯度
        grad_gamma = np.sum(grad_reshaped * (self.x_reshaped - self.mean) * self.std_inv, axis=1)
        grad_beta = np.sum(grad_reshaped, axis=1)
        
        # 恢复输入梯度的形状
        if self.x_ndim > 2:
            grad_x = grad_x.reshape(self.C, N, H, W).transpose(1, 0, 2, 3)
        elif self.x_ndim == 2:
            grad_x = grad_x.T
        else:
            grad_x = grad_x.flatten()
        
        # 更新参数梯度
        if layer.gamma.requires_grad:
            if layer.gamma.grad is None:
                layer.gamma.grad = grad_gamma
            else:
                layer.gamma.grad += grad_gamma
        
        if layer.beta.requires_grad:
            if layer.beta.grad is None:
                layer.beta.grad = grad_beta
            else:
                layer.beta.grad += grad_beta
        
        return None, grad_x


class Sequential:
    """
    顺序容器,将多个层按顺序组合
    
    类似于PyTorch的nn.Sequential
    """
    
    def __init__(self, *layers):
        self.layers = layers
        # 收集所有层的参数
        self.parameters = []
        for layer in layers:
            if hasattr(layer, 'parameters'):
                self.parameters.extend(layer.parameters)
    
    def __call__(self, x):
        for layer in self.layers:
            x = layer(x)
        return x
    
    def train(self):
        """设置为训练模式"""
        for layer in self.layers:
            if hasattr(layer, 'training'):
                layer.training = True
    
    def eval(self):
        """设置为评估模式"""
        for layer in self.layers:
            if hasattr(layer, 'training'):
                layer.training = False


# ============================================================================
# 第五部分:损失函数
# ============================================================================

class CrossEntropyLoss:
    """
    交叉熵损失函数(用于分类任务)
    
    组合了Softmax + 负对数似然
    Loss = -log(softmax(logits)[正确类别])
    """
    
    def __call__(self, logits, targets):
        """
        参数:
            logits: [N, C] 未经过softmax的原始分数
            targets: [N] 真实类别索引(0到C-1)
        """
        if not isinstance(targets, Tensor):
            targets = Tensor(targets)
        
        return CrossEntropyFunction().apply(logits, targets.data)


class CrossEntropyFunction(Function):
    """交叉熵损失的函数包装器"""
    
    def forward(self, logits, targets):
        # 数值稳定的Softmax:减去最大值防止溢出
        logits_shifted = logits - np.max(logits, axis=1, keepdims=True)
        exp_logits = np.exp(logits_shifted)
        probs = exp_logits / np.sum(exp_logits, axis=1, keepdims=True)
        
        # 计算损失
        N = logits.shape[0]
        batch_indices = np.arange(N)
        target_indices = targets.astype(np.int32)
        correct_probs = probs[batch_indices, target_indices]
        
        loss = -np.log(correct_probs + 1e-8)
        loss_value = np.mean(loss)
        
        # 保存用于反向传播
        self.save_for_backward(probs, targets, N)
        
        return np.array(loss_value, dtype=np.float32)
    
    def backward(self, grad_output):
        """
        交叉熵 + Softmax 的梯度公式:
        dL/dlogits = probs - one_hot(target)
        """
        probs, targets, N = self.saved_tensors
        
        grad = probs.copy()
        batch_indices = np.arange(N)
        target_indices = targets.astype(np.int32)
        grad[batch_indices, target_indices] -= 1
        grad = grad / N * grad_output
        
        return grad, None  # logits的梯度,targets的梯度为None


class MSELossFunction(Function):
    """均方误差损失的函数包装器"""
    
    def forward(self, pred, target):
        self.save_for_backward(pred, target)
        diff = pred - target
        loss = np.mean(diff ** 2)
        return np.array(loss, dtype=np.float32)
    
    def backward(self, grad_output):
        pred, target = self.saved_tensors
        if pred.shape != target.shape:
            target = target.reshape(pred.shape)
        N = pred.shape[0]
        diff = pred - target
        grad = 2 * diff / N * grad_output
        return grad, None


class MSELoss:
    """均方误差损失"""
    
    def __call__(self, pred, target):
        if not isinstance(target, Tensor):
            target = Tensor(target)
        return MSELossFunction().apply(pred, target)


# ============================================================================
# 第六部分:优化器
# ============================================================================

class SGD:
    """
    随机梯度下降优化器
    
    支持动量和权重衰减
    """
    
    def __init__(self, parameters, lr=0.01, momentum=0, weight_decay=0):
        self.parameters = parameters
        self.lr = lr                      # 学习率
        self.momentum = momentum          # 动量系数
        self.weight_decay = weight_decay  # 权重衰减系数(L2正则化)
        self.velocities = [np.zeros_like(p.data) for p in parameters]  # 动量缓存
    
    def zero_grad(self):
        """清零所有参数的梯度"""
        for param in self.parameters:
            param.grad = None
    
    def step(self):
        """更新参数"""
        for i, param in enumerate(self.parameters):
            if param.grad is not None:
                # 添加权重衰减(L2正则化)
                grad = param.grad + self.weight_decay * param.data
                
                if self.momentum > 0:
                    # 带动量的更新:v = momentum*v - lr*grad
                    self.velocities[i] = self.momentum * self.velocities[i] - self.lr * grad
                    param.data += self.velocities[i]
                else:
                    # 标准SGD更新
                    param.data -= self.lr * grad


class Adam:
    """
    Adam优化器(Adaptive Moment Estimation)
    
    结合了动量和自适应学习率的优点
    """
    
    def __init__(self, parameters, lr=0.001, betas=(0.9, 0.999), eps=1e-8, weight_decay=0):
        self.parameters = parameters
        self.lr = lr
        self.betas = betas          # (beta1, beta2) 动量衰减系数
        self.eps = eps              # 防止除零
        self.weight_decay = weight_decay
        
        # 一阶动量(均值)和二阶动量(未中心化的方差)
        self.m = [np.zeros_like(p.data) for p in parameters]
        self.v = [np.zeros_like(p.data) for p in parameters]
        self.t = 0                  # 时间步数
    
    def zero_grad(self):
        for param in self.parameters:
            param.grad = None
    
    def step(self):
        self.t += 1
        
        for i, param in enumerate(self.parameters):
            if param.grad is not None:
                # 添加权重衰减
                grad = param.grad + self.weight_decay * param.data
                
                # 更新动量
                self.m[i] = self.betas[0] * self.m[i] + (1 - self.betas[0]) * grad
                self.v[i] = self.betas[1] * self.v[i] + (1 - self.betas[1]) * (grad ** 2)
                
                # 偏差修正(因为初始时为0,早期会偏向0)
                m_hat = self.m[i] / (1 - self.betas[0] ** self.t)
                v_hat = self.v[i] / (1 - self.betas[1] ** self.t)
                
                # 更新参数
                update = self.lr * m_hat / (np.sqrt(v_hat) + self.eps)
                if update.shape != param.data.shape:
                    update = update.reshape(param.data.shape)
                param.data -= update


# ============================================================================
# 第七部分:完整使用示例
# ============================================================================

def example_conv_and_batchnorm():
    """卷积层和BatchNorm示例"""
    print("="*50)
    print("示例1:卷积层 + BatchNorm")
    print("="*50)
    
    # 创建输入 [batch=2, channels=1, height=28, width=28]
    x = Tensor(np.random.randn(2, 1, 28, 28))
    
    # 创建卷积层 + BatchNorm
    conv = Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
    bn = BatchNorm(32)
    
    # 前向传播
    out = conv(x)
    out = bn(out)
    out = out.relu()
    
    print(f"输入形状: {x.shape}")
    print(f"输出形状: {out.shape}")
    print(f"卷积层权重形状: {conv.weight.shape}")
    print(f"BatchNorm gamma形状: {bn.gamma.shape}")
    print(f"BatchNorm beta形状: {bn.beta.shape}")
    
    # 测试反向传播
    loss = out.sum()
    loss.backward()
    print(f"反向传播完成")
    print(f"卷积层权重梯度形状: {conv.weight.grad.shape if conv.weight.grad is not None else 'None'}")
    print(f"BatchNorm gamma梯度形状: {bn.gamma.grad.shape if bn.gamma.grad is not None else 'None'}")
    print(f"BatchNorm beta梯度形状: {bn.beta.grad.shape if bn.beta.grad is not None else 'None'}")


def example_cross_entropy():
    """交叉熵损失示例"""
    print("\n" + "="*50)
    print("示例2:交叉熵损失")
    print("="*50)
    
    # 模拟分类任务:3个样本,5个类别
    logits = Tensor(np.random.randn(3, 5))
    targets = Tensor(np.array([0, 2, 4]))  # 真实类别
    
    loss_fn = CrossEntropyLoss()
    loss = loss_fn(logits, targets)
    
    print(f"Logits形状: {logits.shape}")
    print(f"目标: {targets.data}")
    print(f"损失值: {loss.item():.4f}")
    
    # 反向传播
    loss.backward()
    print(f"Logits梯度形状: {logits.grad.shape if logits.grad is not None else 'None'}")


def example_dropout():
    """Dropout示例"""
    print("\n" + "="*50)
    print("示例3:Dropout层(训练 vs 测试)")
    print("="*50)
    
    x = Tensor(np.ones((1, 10)))
    dropout = Dropout(p=0.5)
    
    # 训练模式
    dropout.training = True
    out_train = dropout(x)
    print(f"训练模式输出: {out_train.data}")
    print(f"训练模式非零比例: {np.mean(out_train.data != 0):.2f}")
    
    # 测试模式
    dropout.training = False
    out_test = dropout(x)
    print(f"测试模式输出: {out_test.data}")


def example_adam():
    """Adam优化器示例"""
    print("\n" + "="*50)
    print("示例4:Adam优化器 vs SGD")
    print("="*50)
    
    # 简单的线性回归
    np.random.seed(42)
    X_data = np.random.randn(100, 2)
    y_data = X_data @ np.array([[2.0], [3.0]]) + 1.0 + np.random.randn(100, 1) * 0.1
    
    X = Tensor(X_data)
    y = Tensor(y_data)
    
    # 使用Adam的模型
    model_adam = Sequential(Linear(2, 1))
    criterion = MSELoss()
    optimizer_adam = Adam(model_adam.parameters, lr=0.1)
    
    # 使用SGD的模型
    model_sgd = Sequential(Linear(2, 1))
    optimizer_sgd = SGD(model_sgd.parameters, lr=0.1)
    
    # 训练
    epochs = 100
    for epoch in range(epochs):
        # Adam
        pred_adam = model_adam(X)
        loss_adam = criterion(pred_adam, y)
        optimizer_adam.zero_grad()
        loss_adam.backward()
        optimizer_adam.step()
        
        # SGD
        pred_sgd = model_sgd(X)
        loss_sgd = criterion(pred_sgd, y)
        optimizer_sgd.zero_grad()
        loss_sgd.backward()
        optimizer_sgd.step()
        
        if epoch % 20 == 0:
            print(f"Epoch {epoch:3d} - Adam Loss: {loss_adam.item():.6f}, SGD Loss: {loss_sgd.item():.6f}")
    
    print(f"\n训练结果 - Adam: 权重={model_adam.layers[0].weight.data.flatten()}, 偏置={model_adam.layers[0].bias.data[0]:.4f}")
    print(f"训练结果 - SGD:  权重={model_sgd.layers[0].weight.data.flatten()}, 偏置={model_sgd.layers[0].bias.data[0]:.4f}")


def example_full_model():
    """完整模型示例:CNN + Dropout + BatchNorm"""
    print("\n" + "="*50)
    print("示例5:完整的卷积神经网络")
    print("="*50)
    
    class SimpleCNN:
        """简单的卷积神经网络用于分类"""
        def __init__(self):
            # 第一个卷积块:1通道 -> 32通道
            self.conv1 = Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
            self.bn1 = BatchNorm(32)
            
            # 第二个卷积块:32通道 -> 64通道
            self.conv2 = Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
            self.bn2 = BatchNorm(64)
            
            # Dropout防止过拟合
            self.dropout = Dropout(0.5)
            
            # 全连接层:64*28*28 -> 10类
            self.fc = Linear(64 * 28 * 28, 10)
            
            # 收集所有参数
            self.parameters = (self.conv1.parameters + self.bn1.parameters + 
                              self.conv2.parameters + self.bn2.parameters + 
                              self.fc.parameters)
        
        def __call__(self, x):
            x = self.conv1(x)
            x = self.bn1(x)
            x = x.relu()
            
            x = self.conv2(x)
            x = self.bn2(x)
            x = x.relu()
            
            x = self.dropout(x)
            x = x.reshape(x.shape[0], -1)  # 展平
            x = self.fc(x)
            return x
        
        def train(self):
            """切换到训练模式"""
            self.dropout.training = True
            self.bn1.training = True
            self.bn2.training = True
        
        def eval(self):
            """切换到评估模式"""
            self.dropout.training = False
            self.bn1.training = False
            self.bn2.training = False
    
    # 创建模型和数据
    model = SimpleCNN()
    x = Tensor(np.random.randn(4, 1, 28, 28))  # batch=4, 1通道, 28x28图像
    targets = Tensor(np.array([0, 1, 2, 3]))
    
    # 训练模式
    model.train()
    logits = model(x)
    loss_fn = CrossEntropyLoss()
    loss = loss_fn(logits, targets)
    
    print(f"训练模式 - 输入形状: {x.shape}")
    print(f"训练模式 - Logits形状: {logits.shape}")
    print(f"训练模式 - 损失: {loss.item():.4f}")
    
    # 反向传播
    optimizer = Adam(model.parameters, lr=0.001)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    print("反向传播和参数更新完成!")
    
    # 评估模式
    model.eval()
    logits_eval = model(x)
    print(f"评估模式 - Logits形状: {logits_eval.shape}")


if __name__ == "__main__":
    # 运行所有示例
    example_conv_and_batchnorm()
    example_cross_entropy()
    example_dropout()
    example_adam()
    example_full_model()

更多推荐