You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TensorFlow实现LeNet-5的C3层:非全连接特征图配置问题

解决LeNet-5 C3层的定制连接问题

首先,你遇到的核心问题是:LeNet-5的C3层并非和S2层全连接,而是每个输出特征图只连接到S2层的特定输入特征图子集,以此减少参数数量并引入局部感受野的多样性。你之前的全连接方式参数是5×5×6×16+16=2416,而论文要求的1516参数正是这种定制连接带来的结果。

先解释你更新1里的错误:你用tf.split(C3_w, [10,6], axis=1)时,axis=1对应的是卷积核的高度维度(大小为5),你要拆分的尺寸总和16≠5,自然会报错。而且这种简单拆分也无法实现论文里的复杂连接规则,我们需要用连接掩码来精确控制哪些输入输出通道相连。

方法一:使用掩码过滤可训练权重

这是最直观的实现方式,先定义一个掩码矩阵标记有效连接,再将可训练权重与掩码相乘,只有掩码为1的位置保留可训练参数:

步骤1:定义C3层的连接掩码

根据论文第8页的表I,C3层16个输出特征图的连接规则如下,我们把它转换成二进制掩码:

import numpy as np
import tensorflow as tf

mu = 0
sigma = 0.1

# 定义[输入通道数, 输出通道数]的掩码矩阵
c3_mask = np.zeros((6, 16), dtype=np.float32)

# 前6个输出:每个连接3个相邻的S2输入特征图
for i in range(6):
    c3_mask[(i, (i+1)%6, (i+2)%6), i] = 1.0

# 中间6个输出:每个连接4个相邻的S2输入特征图
for i in range(6):
    c3_mask[(i, (i+1)%6, (i+2)%6, (i+3)%6), i+6] = 1.0

# 接下来3个输出:每个连接4个不相邻的S2输入特征图
c3_mask[(0,1,4,5), 12] = 1.0
c3_mask[(1,2,5,0), 13] = 1.0
c3_mask[(2,3,0,1), 14] = 1.0

# 最后1个输出:连接所有6个S2输入特征图
c3_mask[:, 15] = 1.0

# 将掩码扩展为卷积核形状[5,5,6,16](卷积核高×宽×输入通道×输出通道)
c3_mask = np.tile(c3_mask, (5,5,1,1))

步骤2:应用掩码构建C3层

将可训练权重与掩码相乘,这样只有有效连接的位置保留可训练参数:

def LeNet(x):
    # C1层(你的原有代码)
    C1_w = tf.Variable(tf.truncated_normal(shape=[5,5,1,6], mean=mu, stddev=sigma))
    C1_b = tf.Variable(tf.zeros(6))
    C1 = tf.nn.conv2d(x, C1_w, strides=[1,1,1,1], padding='VALID') + C1_b
    C1 = tf.nn.sigmoid(C1)

    # S2层(你的原有代码)
    S2 = tf.nn.avg_pool(C1, ksize=[1,2,2,1], strides=[1,2,2,1], padding='VALID')
    S2 = tf.nn.sigmoid(S2)

    # C3层(修改后的实现)
    # 初始化完整的可训练权重
    C3_w_raw = tf.Variable(tf.truncated_normal(shape=[5,5,6,16], mean=mu, stddev=sigma))
    # 用掩码过滤无效连接,只保留有效权重
    C3_w = C3_w_raw * tf.constant(c3_mask)
    C3_b = tf.Variable(tf.zeros(16))

    # 计算卷积与激活
    C3 = tf.nn.conv2d(S2, C3_w, strides=[1,1,1,1], padding='VALID') + C3_b
    C3 = tf.nn.sigmoid(C3)

    # 后续层可以继续按论文实现...
    return C3

这种方式的参数数量完全符合论文要求:

  • 前6个输出:6×(5×5×3) = 450
  • 中间6个输出:6×(5×5×4) = 600
  • 接下来3个输出:3×(5×5×4) = 300
  • 最后1个输出:1×(5×5×6) = 150
  • 偏置:16
    总和:450+600+300+150+16 = 1516

方法二:逐通道构建并拼接(更节省内存)

如果想避免存储无效的0权重,可以为每个输出通道单独构建卷积,再拼接结果:

def LeNet(x):
    # C1和S2层代码同上...

    # C3层:逐通道构建
    C3_b = tf.Variable(tf.zeros(16))
    c3_outputs = []

    # 前6个输出:3个相邻输入通道
    for i in range(6):
        input_channels = [i, (i+1)%6, (i+2)%6]
        # 提取S2的对应输入通道
        s2_slice = tf.gather(S2, input_channels, axis=3)
        # 初始化对应权重
        w = tf.Variable(tf.truncated_normal(shape=[5,5,len(input_channels),1], mean=mu, stddev=sigma))
        # 计算卷积
        conv = tf.nn.conv2d(s2_slice, w, strides=[1,1,1,1], padding='VALID')
        c3_outputs.append(conv)

    # 中间6个输出:4个相邻输入通道
    for i in range(6):
        input_channels = [i, (i+1)%6, (i+2)%6, (i+3)%6]
        s2_slice = tf.gather(S2, input_channels, axis=3)
        w = tf.Variable(tf.truncated_normal(shape=[5,5,len(input_channels),1], mean=mu, stddev=sigma))
        conv = tf.nn.conv2d(s2_slice, w, strides=[1,1,1,1], padding='VALID')
        c3_outputs.append(conv)

    # 接下来3个输出:4个不相邻输入通道
    input_sets = [[0,1,4,5], [1,2,5,0], [2,3,0,1]]
    for input_channels in input_sets:
        s2_slice = tf.gather(S2, input_channels, axis=3)
        w = tf.Variable(tf.truncated_normal(shape=[5,5,len(input_channels),1], mean=mu, stddev=sigma))
        conv = tf.nn.conv2d(s2_slice, w, strides=[1,1,1,1], padding='VALID')
        c3_outputs.append(conv)

    # 最后1个输出:所有6个输入通道
    w = tf.Variable(tf.truncated_normal(shape=[5,5,6,1], mean=mu, stddev=sigma))
    conv = tf.nn.conv2d(S2, w, strides=[1,1,1,1], padding='VALID')
    c3_outputs.append(conv)

    # 拼接所有输出通道并添加偏置
    C3 = tf.concat(c3_outputs, axis=3) + C3_b
    C3 = tf.nn.sigmoid(C3)

    # 后续层...
    return C3

这种方式不需要存储无效的0权重,内存利用率更高,参数数量和方法一完全一致。

内容的提问来源于stack exchange,提问作者Alejandro Calle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:19:34