import numpy as np
import tensorflow as tf
from tensorflow.keras import layers, models, regularizers
from tensorflow.keras.datasets import cifar100
import matplotlib.pyplot as plt
import time
plt.rcParams["font.sans-serif"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams['axes.unicode_minus'] = False
def resnet_block(input_tensor, filters, stride=1, conv_shortcut=True, attention=None):
bn_axis = 3 if tf.keras.backend.image_data_format() == 'channels_last' else 1
if conv_shortcut:
shortcut = layers.Conv2D( 4 * filters, 1, strides=stride, kernel_regularizer=regularizers.l2(1e-4))(input_tensor)
shortcut = layers.BatchNormalization(axis=bn_axis)(shortcut)
else:
shortcut = input_tensor
x = layers.Conv2D( filters, 1, strides=stride, kernel_regularizer=regularizers.l2(1e-4))(input_tensor)
x = layers.BatchNormalization(axis=bn_axis)(x)
x = layers.Activation('relu')(x)
x = layers.Conv2D(filters, 3, padding='same', kernel_regularizer=regularizers.l2(1e-4))(x)
x = layers.BatchNormalization(axis=bn_axis)(x)
x = layers.Activation('relu')(x)
x = layers.Conv2D(4 * filters, 1, kernel_regularizer=regularizers.l2(1e-4))(x)
x = layers.BatchNormalization(axis=bn_axis)(x)
x = layers.Add()([shortcut, x])
x = layers.Activation('relu')(x)
return x
def build_resnet34(input_shape=(32, 32, 3), classes=100, attention=None):
inputs = layers.Input(shape=input_shape)
x = layers.ZeroPadding2D(padding=(3, 3))(inputs)
x = layers.Conv2D( 64, 7, strides=2, use_bias=False, kernel_regularizer=regularizers.l2(1e-4) )(x)
x = layers.BatchNormalization()(x)
x = layers.Activation('relu')(x)
x = layers.MaxPooling2D(3, strides=2, padding='same')(x)
def make_layer(x, filters, blocks, stride=1):
x = resnet_block(x, filters, stride=stride, conv_shortcut=True, attention=attention)
for _ in range(1, blocks):
x = resnet_block(x, filters, conv_shortcut=False, attention=attention)
return x
x = make_layer(x, 64, 3, stride=1)
x = make_layer(x, 128, 4, stride=2)
x = make_layer(x, 256, 6, stride=2)
x = make_layer(x, 512, 3, stride=2)
x = layers.GlobalAveragePooling2D()(x)
outputs = layers.Dense( classes, activation='softmax', kernel_regularizer=regularizers.l2(1e-4))(x)
return models.Model(inputs, outputs)
# 改进的通道重要性计算,考虑残差结构
def compute_channel_importance(model):
"""计算每个卷积层通道的重要性(基于L1范数)"""
importance = {}
for layer in model.layers:
if isinstance(layer, layers.Conv2D):
weights = layer.get_weights()[0] # 获取卷积核权重
# 计算每个通道的L1范数
channel_importance = np.sum(np.abs(weights), axis=(0, 1, 2))
importance[layer.name] = channel_importance
return importance
# 改进的剪枝掩码生成,考虑残差连接的依赖关系
# 仅修改此函数,其他代码保持不变
def generate_prune_mask(importance, prune_ratio, model):
"""基于通道重要性生成剪枝掩码,确保残差连接通道数匹配"""
prune_mask = {}
# 分析模型结构,建立层依赖关系
layer_dependencies = {}
for layer in model.layers:
try:
# 获取每层的输入层
inbound_nodes = layer._inbound_nodes[0]
input_layers = [node.outbound_layer for node in inbound_nodes.inbound_layers]
layer_dependencies[layer.name] = input_layers
except:
continue
# 处理所有卷积层
conv_layers = [layer for layer in model.layers if isinstance(layer, layers.Conv2D)]
for layer in conv_layers:
layer_name = layer.name
imp = importance[layer_name]
num_channels = len(imp)
num_keep = int(num_channels * (1 - prune_ratio))
# 生成通道掩码
keep_indices = np.argsort(-imp)[:num_keep]
mask = np.zeros(num_channels, dtype=bool)
mask[keep_indices] = True
# 检查是否为残差块的主路径卷积层
# 如果该卷积层的输出被Add层使用,则寻找对应的捷径连接
for add_layer in model.layers:
if isinstance(add_layer, layers.Add) and layer_name in [l.name for l in layer_dependencies.get(add_layer.name, [])]:
# 找到捷径连接的卷积层
shortcut_layers = [l for l in layer_dependencies[add_layer.name] if l.name != layer_name]
for shortcut_layer in shortcut_layers:
if isinstance(shortcut_layer, layers.Conv2D):
# 强制捷径连接与主路径使用相同的掩码
prune_mask[shortcut_layer.name] = mask
prune_mask[layer_name] = mask
return prune_mask
# 改进的创建剪枝模型函数,处理残差结构
def create_pruned_model(original_model, prune_mask):
"""根据剪枝掩码创建剪枝后的模型,同步处理输入通道和BN层"""
# 提取原始模型的配置
config = original_model.get_config()
# 1. 修改卷积层配置(输出通道)
for layer_name, mask in prune_mask.items():
for layer in config['layers']:
if layer['name'] == layer_name and layer['class_name'] == 'Conv2D':
layer['config']['filters'] = int(np.sum(mask))
break
# 2. 构建剪枝后的模型结构
pruned_model = models.Model.from_config(config)
# 3. 建立层名到剪枝掩码的映射(用于处理输入通道)
name_to_mask = {k: v for k, v in prune_mask.items()}
# 4. 复制并调整权重
for layer in original_model.layers:
if layer.name in prune_mask:
# 处理卷积层
if isinstance(layer, layers.Conv2D):
weights = layer.get_weights()
out_mask = prune_mask[layer.name] # 输出通道掩码
# 处理输入通道:找到前一层的剪枝掩码
in_mask = None
try:
# 尝试获取该层的输入节点
inbound_nodes = layer._inbound_nodes
if inbound_nodes and len(inbound_nodes) > 0:
# 获取第一个输入节点的输入层
input_layers = [node.layer for node in inbound_nodes[0].inbound_layers]
for input_layer in input_layers:
if input_layer.name in name_to_mask:
in_mask = name_to_mask[input_layer.name]
break
except Exception as e:
print(f"警告: 获取层 {layer.name} 的输入层失败: {e}")
# 调整卷积核权重(输入通道×输出通道)
if in_mask is not None:
# 输入通道剪枝
pruned_kernel = weights[0][:, :, in_mask, :]
# 输出通道剪枝
pruned_kernel = pruned_kernel[:, :, :, out_mask]
else:
# 仅输出通道剪枝
pruned_kernel = weights[0][:, :, :, out_mask]
pruned_weights = [pruned_kernel]
if len(weights) > 1: # 偏置剪枝
pruned_weights.append(weights[1][out_mask])
pruned_model.get_layer(layer.name).set_weights(pruned_weights)
# 处理批归一化层
elif isinstance(layer, layers.BatchNormalization):
weights = layer.get_weights()
# 找到对应的卷积层掩码
conv_mask = None
try:
# 获取该BN层的输入节点
inbound_nodes = layer._inbound_nodes
if inbound_nodes and len(inbound_nodes) > 0:
# 获取输入层
input_layers = [node.layer for node in inbound_nodes[0].inbound_layers]
for input_layer in input_layers:
if input_layer.name in name_to_mask and isinstance(input_layer, layers.Conv2D):
conv_mask = name_to_mask[input_layer.name]
break
except Exception as e:
print(f"警告: 获取层 {layer.name} 的输入层失败: {e}")
if conv_mask is not None:
# 调整BN层参数(与卷积输出通道匹配)
pruned_weights = [w[conv_mask] for w in weights]
pruned_model.get_layer(layer.name).set_weights(pruned_weights)
else:
# 复制未剪枝层的权重
try:
pruned_model.get_layer(layer.name).set_weights(layer.get_weights())
except Exception as e:
print(f"警告: 无法设置层 {layer.name} 的权重: {e}")
return pruned_model
# 评估模型性能
def evaluate_model(model, x_test, y_test):
"""评估模型的准确率和推理速度"""
# 评估准确率
loss, accuracy = model.evaluate(x_test, y_test, verbose=0)
# 评估推理速度
start_time = time.time()
predictions = model.predict(x_test, batch_size=128)
end_time = time.time()
inference_time = end_time - start_time
samples_per_second = len(x_test) / inference_time
return accuracy, samples_per_second
# 主函数
def main():
# 加载CIFAR-100数据
(x_train, y_train), (x_test, y_test) = cifar100.load_data()
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
# 构建基础模型
model = build_resnet34(attention=None)
model.load_weights('base_resnet34_cifar100.h5')
print("模型权重加载成功")
# 编译模型
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 评估原始模型
print("\n评估原始模型...")
original_accuracy, original_speed = evaluate_model(model, x_test, y_test)
print(f"原始模型准确率: {original_accuracy:.4f}")
print(f"原始模型速度: {original_speed:.2f} samples/sec")
# 计算通道重要性
importance = compute_channel_importance(model)
# 测试不同剪枝比例
prune_ratios = [0.1, 0.2] # 10%和20%的通道剪枝
results = []
for ratio in prune_ratios:
print(f"\n=== 剪枝比例: {ratio*100:.0f}% ===")
# 生成剪枝掩码(考虑残差结构)
prune_mask = generate_prune_mask(importance, ratio, model)
# 创建剪枝模型
pruned_model = create_pruned_model(model, prune_mask)
# 编译剪枝模型
pruned_model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 评估剪枝模型
pruned_accuracy, pruned_speed = evaluate_model(pruned_model, x_test, y_test)
print(f"剪枝后准确率: {pruned_accuracy:.4f}")
print(f"剪枝后速度: {pruned_speed:.2f} samples/sec")
print(f"精度损失: {original_accuracy - pruned_accuracy:.4f}")
print(f"加速比: {pruned_speed / original_speed:.2f}x")
# 记录结果
results.append({
'ratio': ratio,
'accuracy': pruned_accuracy,
'speed': pruned_speed,
'accuracy_loss': original_accuracy - pruned_accuracy,
'speedup': pruned_speed / original_speed
})
# 可视化结果
plt.figure(figsize=(15, 6))
plt.subplot(1, 2, 1)
ratios = [r['ratio'] * 100 for r in results]
accuracies = [r['accuracy'] for r in results]
plt.plot(ratios, accuracies, 'o-', label='剪枝后准确率')
plt.axhline(y=original_accuracy, color='r', linestyle='--', label='原始准确率')
plt.xlabel('剪枝比例 (%)')
plt.ylabel('准确率')
plt.title('不同剪枝比例下的模型准确率')
plt.legend()
plt.grid(True)
plt.subplot(1, 2, 2)
speedups = [r['speedup'] for r in results]
plt.plot(ratios, speedups, 'o-', label='加速比')
plt.axhline(y=1.0, color='r', linestyle='--', label='基线')
plt.xlabel('剪枝比例 (%)')
plt.ylabel('加速比 (x)')
plt.title('不同剪枝比例下的模型加速比')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
return results
if __name__ == "__main__":
results = main()//报错=== 剪枝比例: 10% ===
警告: 获取层 conv2d 的输入层失败: 'ZeroPadding2D' object is not iterable
警告: 无法设置层 batch_normalization 的权重: Layer weight shape (57,) not compatible with provided weight shape (64,)
警告: 获取层 conv2d_2 的输入层失败: 'MaxPooling2D' object is not iterable
Traceback (most recent call last):
File "c:/Users/赵培伊/Desktop/大三下文件存放处/神经网络代码/大作业卷积任务4.py", line 312, in <module>
results = main()
File "c:/Users/赵培伊/Desktop/大三下文件存放处/神经网络代码/大作业卷积任务4.py", line 257, in main
pruned_model = create_pruned_model(model, prune_mask)
File "c:/Users/赵培伊/Desktop/大三下文件存放处/神经网络代码/大作业卷积任务4.py", line 170, in create_pruned_model
pruned_model.get_layer(layer.name).set_weights(pruned_weights)
File "C:\anaconda\envs\tensorflow2.4\lib\site-packages\tensorflow\python\keras\engine\base_layer.py", line 1871, in set_weights
raise ValueError(
最新发布