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

如何在Keras中冻结层内部分神经元而非整个层(CNN场景)

在Keras中按百分比冻结CNN/神经网络的部分神经元

要实现按百分比冻结每层的部分神经元(让它们的权重固定,不参与训练更新),Keras并没有直接提供开箱即用的工具,但我们可以通过自定义梯度掩码+自定义训练循环的方式来轻松实现,核心思路是在计算梯度后,对需要冻结的神经元对应的权重梯度置为0,这样优化器就不会更新这些权重了。

下面我会结合示例代码,详细说明具体步骤:

1. 核心思路拆解

每个神经元对应着层中的一组权重(比如Dense层的一个神经元对应权重矩阵的一列+一个bias值;Conv2D层的一个滤波器对应一组卷积核+一个bias值)。我们要做的就是:

  • 为每个层选择要冻结的神经元/滤波器(按指定百分比)
  • 在训练时,让这些神经元对应的权重梯度为0,阻止优化器更新它们

2. 实现步骤与代码示例

2.1 导入依赖库

import tensorflow as tf
from tensorflow.keras import layers, models
import numpy as np

2.2 编写梯度掩码生成函数

这个函数会根据层的类型(Dense/Conv2D)和冻结百分比,生成对应的权重/bias掩码,掩码中0的位置对应要冻结的神经元:

def create_gradient_mask(layer, freeze_percent):
    """为指定层生成梯度掩码,冻结指定百分比的神经元/滤波器"""
    # 处理全连接层(Dense)
    if isinstance(layer, layers.Dense):
        num_units = layer.units
        num_freeze = int(num_units * freeze_percent)
        # 随机选择要冻结的神经元索引(你也可以改成按权重大小筛选)
        freeze_indices = np.random.choice(num_units, num_freeze, replace=False)
        
        # 权重掩码:shape=(输入维度, 神经元数),冻结的神经元对应列设为0
        weight_mask = np.ones(layer.get_weights()[0].shape)
        weight_mask[:, freeze_indices] = 0.0
        
        # Bias掩码:shape=(神经元数,),冻结的神经元对应位置设为0
        bias_mask = np.ones(layer.get_weights()[1].shape)
        bias_mask[freeze_indices] = 0.0
        
        return [weight_mask, bias_mask]
    
    # 处理卷积层(Conv2D)
    elif isinstance(layer, layers.Conv2D):
        num_filters = layer.filters
        num_freeze = int(num_filters * freeze_percent)
        freeze_indices = np.random.choice(num_filters, num_freeze, replace=False)
        
        # 卷积核掩码:shape=(核尺寸, 核尺寸, 输入通道, 输出通道),冻结的滤波器对应通道设为0
        weight_mask = np.ones(layer.get_weights()[0].shape)
        weight_mask[:, :, :, freeze_indices] = 0.0
        
        # Bias掩码(如果层使用bias的话)
        bias_mask = None
        if layer.use_bias:
            bias_mask = np.ones(layer.get_weights()[1].shape)
            bias_mask[freeze_indices] = 0.0
        
        return [weight_mask, bias_mask]
    
    # 其他层(比如池化层)不需要处理,返回None
    else:
        return None

2.3 构建并准备模型

这里用一个简单的CNN示例,你可以替换成自己的模型:

# 构建示例CNN模型
model = models.Sequential([
    layers.Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1)),
    layers.MaxPooling2D((2,2)),
    layers.Conv2D(64, (3,3), activation='relu'),
    layers.MaxPooling2D((2,2)),
    layers.Flatten(),
    layers.Dense(64, activation='relu'),
    layers.Dense(10, activation='softmax')
])

# 定义损失函数和优化器(不直接用model.compile,因为要自定义训练逻辑)
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam()

2.4 为每个层生成掩码

比如我们要冻结每层20%的神经元:

freeze_percent = 0.2  # 冻结20%的神经元
layer_masks = {}

for layer in model.layers:
    # 只处理有可训练权重的层
    if hasattr(layer, 'trainable_weights') and len(layer.trainable_weights) > 0:
        mask = create_gradient_mask(layer, freeze_percent)
        if mask is not None:
            layer_masks[layer.name] = mask

2.5 自定义训练循环

在训练时,计算完梯度后应用掩码,把冻结神经元的梯度置为0,再更新权重:

# 准备MNIST训练数据(替换成你的数据集即可)
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape(-1,28,28,1).astype('float32') / 255.0
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(10000).batch(32)

# 定义训练步骤(用tf.function加速)
@tf.function
def train_step(x_batch, y_batch):
    with tf.GradientTape() as tape:
        predictions = model(x_batch, training=True)
        loss = loss_fn(y_batch, predictions)
    
    # 计算所有可训练权重的梯度
    gradients = tape.gradient(loss, model.trainable_weights)
    
    # 应用梯度掩码,冻结对应神经元的权重更新
    grad_idx = 0
    for layer in model.layers:
        if layer.name in layer_masks:
            weight_mask, bias_mask = layer_masks[layer.name]
            # 处理权重梯度
            gradients[grad_idx] = gradients[grad_idx] * tf.convert_to_tensor(weight_mask, dtype=tf.float32)
            grad_idx += 1
            # 处理bias梯度(如果存在)
            if bias_mask is not None:
                gradients[grad_idx] = gradients[grad_idx] * tf.convert_to_tensor(bias_mask, dtype=tf.float32)
                grad_idx += 1
        else:
            # 跳过无掩码的层,移动梯度索引
            grad_idx += len(layer.trainable_weights)
    
    # 更新权重
    optimizer.apply_gradients(zip(gradients, model.trainable_weights))
    return loss

# 开始训练
epochs = 5
for epoch in range(epochs):
    print(f"Epoch {epoch+1}/{epochs}")
    total_loss = 0.0
    step_count = 0
    
    for x_batch, y_batch in train_dataset:
        batch_loss = train_step(x_batch, y_batch)
        total_loss += batch_loss.numpy()
        step_count += 1
    
    avg_loss = total_loss / step_count
    print(f"平均损失: {avg_loss:.4f}\n")

3. 可选优化与注意事项

  • 固定冻结特定神经元:上面的代码是随机选择冻结的神经元,如果你想冻结权重较小的神经元,可以在create_gradient_mask函数里先计算每个神经元的权重范数,再选择范数最小的p%神经元。
  • 支持更多层类型:如果你的模型包含LSTM、GRU等层,可以参照Dense/Conv2D的逻辑,扩展create_gradient_mask函数,找到对应神经元的权重位置生成掩码。
  • 避免动态调整掩码:尽量在训练前固定掩码,不要在训练过程中频繁修改,否则会导致训练不稳定。

内容的提问来源于stack exchange,提问作者Shubham Dash

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:26:35