# 导入所需的预训练模型
from tensorflow.keras.applications import VGG16, InceptionV3, MobileNetV2, ResNet50, DenseNet121, EfficientNetB0
# 导入自定义层所需的基础组件
from tensorflow.keras.layers import Layer, Dropout, LayerNormalization, Dense
# 导入tensorflow核心库(补充原代码缺失的导入)
import tensorflow as tf
# 导入keras模型和层(补充原代码缺失的导入)
from tensorflow.keras import models, layers
# 导入numpy(补充原代码缺失的导入)
import numpy as np
# 导入评估指标(补充原代码缺失的导入)
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, roc_curve, auc, \
label_binarize
# 导入可视化库(补充原代码缺失的导入)
import matplotlib.pyplot as plt
import seaborn as sns
"""
自定义多头自注意力层(Multi-Head Self-Attention)
用于捕获特征之间的依赖关系,增强模型对重要特征的关注
"""
class MultiHeadSelfAttention(Layer):
# 初始化函数
def __init__(self, embed_dim=256, num_heads=8, dropout_rate=0.1):
super(MultiHeadSelfAttention, self).__init__()
self.num_heads = num_heads # 注意力头的数量
self.embed_dim = embed_dim # 嵌入维度(特征维度)
self.dropout_rate = dropout_rate # Dropout比率
# 检查嵌入维度是否能被注意力头数整除(必须满足,否则无法均分)
if embed_dim % num_heads != 0:
raise ValueError(f"嵌入维度 = {embed_dim} 必须能被注意力头数 = {num_heads} 整除")
self.projection_dim = embed_dim // num_heads # 每个注意力头的投影维度
# 定义Q(查询)、K(键)、V(值)的投影全连接层
self.query_dense = Dense(embed_dim)
self.key_dense = Dense(embed_dim)
self.value_dense = Dense(embed_dim)
# 定义融合所有注意力头输出的全连接层
self.combine_heads = Dense(embed_dim)
# 定义Dropout层(防止过拟合)和层归一化层(稳定训练)
self.dropout = Dropout(dropout_rate)
self.layernorm = LayerNormalization(epsilon=1e-6)
# 核心注意力计算函数
def attention(self, query, key, value):
# 计算Q和K的点积(注意力分数)
score = tf.matmul(query, key, transpose_b=True)
# 获取K的最后一维维度并转换为浮点型(用于缩放)
dim_key = tf.cast(tf.shape(key)[-1], tf.float32)
# 缩放注意力分数(防止维度太大导致softmax饱和)
scaled_score = score / tf.math.sqrt(dim_key)
# 对注意力分数应用softmax,得到注意力权重
weights = tf.nn.softmax(scaled_score, axis=-1)
# 用注意力权重加权V,得到注意力输出
attention = tf.matmul(weights, value)
return attention
# 将特征拆分为多个注意力头
def separate_heads(self, x, batch_size):
# 重塑维度:(批次大小, 序列长度, 头数, 每个头的投影维度)
x = tf.reshape(x, (batch_size, -1, self.num_heads, self.projection_dim))
# 转置维度:(批次大小, 头数, 序列长度, 每个头的投影维度),方便并行计算
return tf.transpose(x, perm=[0, 2, 1, 3])
# 前向传播函数(核心逻辑)
def call(self, inputs):
# 获取批次大小(动态计算,适配不同批次)
batch_size = tf.shape(inputs)[0]
# 将输入特征投影到Q/K/V空间
query = self.query_dense(inputs)
key = self.key_dense(inputs)
value = self.value_dense(inputs)
# 将Q/K/V拆分为多个注意力头
query = self.separate_heads(query, batch_size)
key = self.separate_heads(key, batch_size)
value = self.separate_heads(value, batch_size)
# 计算多头注意力
attention = self.attention(query, key, value)
# 转置并拼接所有注意力头的输出:恢复为(批次大小, 序列长度, 嵌入维度)
attention = tf.transpose(attention, perm=[0, 2, 1, 3])
concat_attention = tf.reshape(attention, (batch_size, -1, self.embed_dim))
# 融合注意力输出 + Dropout + 残差连接(保持信息完整性) + 层归一化
output = self.combine_heads(concat_attention)
output = self.dropout(output)
output = self.layernorm(inputs + output)
# 对序列维度做均值池化,得到固定长度的输出(适配分类任务)
output = tf.reduce_mean(output, axis=1)
return output
# 重复定义的函数(保留原代码结构,实际以最后一个为准)
def compute_output_shape(self, input_shape):
# 输出形状:(批次大小, 嵌入维度)
return input_shape[0], self.embed_dim
"""
以下函数用于创建不同的预训练模型+多头注意力的混合模型
统一结构:预训练骨干网络(冻结) + 全局平均池化 + 全连接层 + 多头注意力 + 分类头
"""
# 创建自定义CNN模型(无预训练,纯手动构建)
def create_cnn_model():
model = models.Sequential()
# 第一卷积块:卷积 + 批归一化 + 最大池化 + Dropout
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)))
model.add(layers.BatchNormalization()) # 批归一化:加速训练,稳定梯度
model.add(layers.MaxPooling2D((2, 2))) # 最大池化:降维,提取关键特征
model.add(layers.Dropout(0.3)) # Dropout:防止过拟合
# 第二卷积块
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.3))
# 第三卷积块
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.3))
# 第四卷积块
model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.3))
# 展平特征图:转为一维向量
model.add(layers.Flatten())
# 全连接层 + L2正则化(防止过拟合)
model.add(layers.Dense(512, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.01)))
model.add(layers.Dropout(0.5))
# 添加多头自注意力层(适配512维特征)
model.add(MultiHeadSelfAttention(embed_dim=512, num_heads=8))
# 分类头
model.add(layers.Dense(256, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.01)))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(num_classes, activation='softmax'))
return model
# 导入更多预训练模型
from tensorflow.keras.applications import ResNet50
# 创建ResNet50模型
def create_resnet50_model():
base_model = ResNet50(
weights='/kaggle/input/tf-keras-pretrained-model-weights/No Top/resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5',
include_top=False, input_shape=(128, 128, 3))
for layer in base_model.layers:
layer.trainable = False
model = models.Sequential()
model.add(base_model)
model.add(layers.GlobalAveragePooling2D())
model.add(layers.Dense(256, activation='relu'))
model.add(MultiHeadSelfAttention(embed_dim=256, num_heads=8))
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(num_classes, activation='softmax'))
return model
# 模型字典:键为模型名称,值为对应的模型实例
# 注意:使用前需先定义num_classes(类别数)、X_train/y_train/X_val/y_val(数据集)、label_names(类别名称)
models_dict = {
"ResNet50": create_resnet50_model(),
}
# 导入Keras后端(用于自定义损失函数)
from tensorflow.keras import backend as K
"""
自定义Focal Loss(焦点损失)
解决类别不平衡问题,降低易分类样本的权重,关注难分类样本
"""
def focal_loss(gamma=2., alpha=0.25):
"""
计算多分类任务的焦点损失
参数:
gamma (float): 聚焦参数,gamma越大,对难分类样本的关注越高
alpha (float): 平衡参数,调节正负样本的权重
返回:
function: 损失函数
"""
def focal_loss_fixed(y_true, y_pred):
epsilon = K.epsilon() # 防止log(0)的极小值
y_pred = K.clip(y_pred, epsilon, 1. - epsilon) # 裁剪预测值,避免数值不稳定
# 将真实标签转换为one-hot编码(适配多分类)
y_true = tf.one_hot(tf.cast(y_true, tf.int32), depth=y_pred.shape[-1])
# 计算类别平衡权重
alpha_t = y_true * alpha + (K.ones_like(y_true) - y_true) * (1 - alpha)
# 计算真实类别对应的预测概率
p_t = y_true * y_pred + (K.ones_like(y_true) - y_true) * (1 - y_pred)
# 计算焦点损失核心公式
fl = - alpha_t * K.pow((K.ones_like(y_true) - p_t), gamma) * K.log(p_t)
# 对所有类别求和并取均值,得到最终损失
return K.mean(K.sum(fl, axis=-1))
return focal_loss_fixed
# 计算类别权重(解决类别不平衡)
from sklearn.utils.class_weight import compute_class_weight
# 注意:使用前需确保y_train已定义
class_weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)
class_weights_dict = {i: class_weights[i] for i in range(len(class_weights))}
# 导入优化器
from tensorflow.keras.optimizers import SGD, Adam
# 训练并评估所有模型
results = {} # 存储各模型的评估结果
for model_name, model in models_dict.items():
print(f"开始训练 {model_name} 模型...")
# 导入回调函数
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
# 定义早停回调:监控验证集准确率,最大值模式,耐心值8(8轮无提升则停止),恢复最佳权重
early_stopping = EarlyStopping(monitor='val_accuracy', mode='max', patience=8, restore_best_weights=True)
# 定义学习率衰减回调:监控验证集损失,最小值模式,衰减因子0.5,耐心值3,最小学习率1e-8
reduce_lr = ReduceLROnPlateau(monitor='val_loss', mode='min', factor=0.5, patience=3, min_lr=1e-8)
# 编译模型:使用Adam优化器,焦点损失,评估指标为准确率
model.compile(optimizer=Adam(learning_rate=1e-3), loss=focal_loss(gamma=2., alpha=0.25), metrics=['accuracy'])
# 训练模型:20轮,使用验证集,添加回调函数,静默模式(无详细输出)
# 注意:使用前需确保X_train/y_train/X_val/y_val已定义
model.fit(X_train, y_train, epochs=20, validation_data=(X_val, y_val), callbacks=[early_stopping, reduce_lr],
verbose=0)
# 恢复最佳权重(早停回调保存的最优权重)
if early_stopping.best_weights is not None:
model.set_weights(early_stopping.best_weights)
print(f"{model_name} 模型已恢复最佳权重")
print(f"开始评估 {model_name} 模型...")
# 预测验证集
y_pred = model.predict(X_val, verbose=0)
# 处理可能的稀疏张量(转换为普通张量)
if isinstance(y_pred, tf.RaggedTensor):
y_pred = y_pred.to_tensor()
# 将预测概率转换为类别标签(取最大值对应的索引)
y_pred_classes = np.argmax(y_pred, axis=1)
# 计算评估指标(加权平均,适配多分类)
accuracy = accuracy_score(y_val, y_pred_classes) # 准确率
precision = precision_score(y_val, y_pred_classes, average='weighted') # 精确率
recall = recall_score(y_val, y_pred_classes, average='weighted') # 召回率
f1 = f1_score(y_val, y_pred_classes, average='weighted') # F1分数
# 存储模型评估结果
results[model_name] = {
"Accuracy": accuracy,
"Precision": precision,
"Recall": recall,
"F1-score": f1
}
# 绘制混淆矩阵
conf_mat = confusion_matrix(y_val, y_pred_classes)
plt.figure(figsize=(7, 6))
# 热力图可视化混淆矩阵,标注数值,蓝色系,显示类别名称
sns.heatmap(conf_mat, annot=True, fmt='d', cmap='Blues', xticklabels=label_names, yticklabels=label_names)
plt.title(f'混淆矩阵 - {model_name}')
plt.xlabel('预测类别')
plt.ylabel('真实类别')
plt.savefig(f'confusion_matrix_{model_name}.png') # 保存图片
plt.show() # 显示图片
# 根据类别数选择不同的ROC曲线绘制方式
if num_classes == 2:
# 二分类任务:绘制单条ROC曲线
# 计算ROC曲线和AUC
fpr, tpr, _ = roc_curve(y_val, y_pred[:, 1]) # 使用正类的预测概率
roc_auc = auc(fpr, tpr)
# 绘制ROC曲线
plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2,
label=f'ROC曲线 (AUC = {roc_auc:.2f})')
# 绘制对角线(随机猜测基准)
plt.plot([0, 1], [0, 1], color='gray', lw=2, linestyle='--')
plt.xlim([-0.01, 1.0])
plt.ylim([-0.01, 1.05])
plt.xlabel('假阳性率 (FPR)')
plt.ylabel('真阳性率 (TPR)')
plt.title(f'ROC曲线 - {model_name}')
plt.legend(loc="lower right")
plt.savefig(f'roc_curve_{model_name}.png')
plt.show()
else:
# 多分类任务:绘制每个类别的ROC曲线
# 将真实标签转换为多标签二进制格式
y_val_bin = label_binarize(y_val, classes=label_names)
n_classes = y_val_bin.shape[1]
# 初始化存储每个类别ROC数据的字典
fpr = dict()
tpr = dict()
roc_auc = dict()
# 计算每个类别的ROC曲线和AUC
for i in range(n_classes):
fpr[i], tpr[i], _ = roc_curve(y_val_bin[:, i], y_pred[:, i])
roc_auc[i] = auc(fpr[i], tpr[i])
# 绘制多分类ROC曲线
plt.figure()
colors = ['aqua', 'darkorange', 'cornflowerblue'] # 颜色列表(可扩展)
for i, color in zip(range(n_classes), colors):
plt.plot(fpr[i], tpr[i], color=color, lw=2,
label=f'{label_names[i]} 的ROC曲线 (AUC = {roc_auc[i]:.2f})')
# 绘制对角线基准
plt.plot([0, 1], [0, 1], 'k--', lw=2)
plt.xlim([-0.01, 1.0])
plt.ylim([-0.01, 1.05])
plt.xlabel('假阳性率 (FPR)')
plt.ylabel('真阳性率 (TPR)')
plt.title(f'多分类ROC曲线 - {model_name}')
plt.legend(loc="lower right")
plt.savefig(f'roc_curve_{model_name}.png')
plt.show()
# 打印所有模型的评估结果
print("=" * 50)
print("所有模型评估结果汇总:")
print("=" * 50)
for model_name, metrics in results.items():
print(f"\n【{model_name}】")
for metric, value in metrics.items():
print(f"{metric}:{value:.4f}")如果我只想要resnet50可以直接删除自建的cnn代码嘛?
最新发布