基于TensorFlow后端Keras的坑洼与垃圾检测模型异常排查
嘿,我来帮你排查这个问题!首先看你的代码和问题描述,有几个关键问题导致了非目标图像全被识别成垃圾的情况,咱们一步步拆解解决:
一、核心问题定位
1. 模型架构与训练类别不匹配
你的代码里,训练时用np_utils.to_categorical(y_train, 2)把标签转换成了2分类的独热编码,但模型最后一层却是Dense(3, activation='softmax')——这意味着模型会输出3个类别的概率,但你的训练数据里只有坑洼(1)和垃圾(0)两类,第三个类别从来没被训练过。这种不匹配会让模型对非目标图像的概率分配完全混乱,最终只会把它分到训练过的两类里概率相对较高的那类(也就是垃圾类)。
2. 训练数据缺乏负样本
你的模型从来没见过汽车、自行车这类非目标图像,它根本不知道“这些不是我要检测的对象”,自然只会从它学过的两类里选一个,而垃圾类可能刚好是模型更“熟悉”的默认选项。
二、最优输出是什么?
对于非目标图像,最优输出应该是明确的“非目标/未知类别”提示,而不是错误地归类到坑洼或垃圾里。这样用户能清楚知道输入的图像不在模型的检测范围内,而不是得到一个误导性的结果。
三、具体修复步骤
1. 先修正模型架构的基础错误
如果你暂时只想区分坑洼和垃圾,把最后一层的神经元数量改成2:
# 原代码错误写法 model.add(Dense(3, activation='softmax')) # 修正后 model.add(Dense(2, activation='softmax'))
如果你想让模型能识别“非目标”,那需要重新整理数据集,加入汽车、猫等非目标图像并标注为第3类(比如标签2),然后把独热编码的类别数改成3:
Y_train = np_utils.to_categorical(y_train, 3) Y_test = np_utils.to_categorical(y_test, 3) # 模型最后一层对应改成3个神经元 model.add(Dense(3, activation='softmax'))
2. 加入负样本训练
收集足够多的非目标图像(汽车、自行车、猫、人类、建筑物等),给它们打上统一的标签(比如2),混入你的训练集和测试集。只有让模型见过“什么不是坑洼/垃圾”,它才能学会区分。
3. 优化推理逻辑,加入概率阈值判断
即使加入了第三类,有时候模型对某些边缘图像的概率会很低,这时候直接输出第三类也不够严谨。可以在推理时检查最高概率值,低于阈值就输出“未知”:
# 替换原有的推理代码部分 prediction_probs = loaded_model.predict(X_test)[0] max_prob = np.max(prediction_probs) predicted_class = np.argmax(prediction_probs) # 设置一个概率阈值,比如0.7 if max_prob < 0.7: print("未知/非目标图像!") elif predicted_class == 1: print("Pothole") elif predicted_class == 0: print("Garbage") elif predicted_class == 2: print("非目标图像")
4. 可选:提升模型泛化能力
- 数据增强:在训练时加入图像翻转、缩放、旋转等操作,让模型学习到更多图像变体,减少过拟合:
from keras.preprocessing.image import ImageDataGenerator datagen = ImageDataGenerator( rotation_range=15, width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True ) datagen.fit(X_train) # 训练时用datagen.flow替代直接传入X_train model.fit(datagen.flow(X_train, Y_train, batch_size=32), nb_epoch=20, verbose=1) - 迁移学习:如果你的数据集不大,可以用预训练的CNN模型(比如VGG16、ResNet50)做迁移学习,它们的特征提取能力更强,泛化效果更好。
四、修正后的完整训练代码示例(以3分类为例)
import numpy as np np.random.seed(123) # for reproducibility from keras.models import Sequential from keras.layers import Dense, Dropout, Activation, Flatten from keras.layers import Convolution2D, MaxPooling2D from keras.utils import np_utils from dataset_pothole import pothole # 假设你已经更新了数据集包含第三类 from keras.models import model_from_json from keras.preprocessing.image import ImageDataGenerator # 加载包含3类的数据集:坑洼(1)、垃圾(0)、非目标(2) (X_train, y_train), (X_test, y_test) = pothole.load_data() # 预处理输入数据 X_train = X_train.reshape(X_train.shape[0], 50, 50, 3) X_test = X_test.reshape(X_test.shape[0], 50, 50, 3) X_train = X_train.astype('float32') X_test = X_test.astype('float32') X_train /= 255 X_test /= 255 # 预处理标签为3分类独热编码 Y_train = np_utils.to_categorical(y_train, 3) Y_test = np_utils.to_categorical(y_test, 3) # 定义模型架构 model = Sequential() model.add(Convolution2D(32, 3, 3, activation='relu', input_shape=(50, 50, 3))) model.add(Convolution2D(32, 3, 3, activation='relu')) model.add(MaxPooling2D(pool_size=(2,2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(128, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(3, activation='softmax')) # 对应3分类 # 编译模型 model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 数据增强 datagen = ImageDataGenerator( rotation_range=15, width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True ) datagen.fit(X_train) # 训练模型 model.fit(datagen.flow(X_train, Y_train, batch_size=32), nb_epoch=20, verbose=1) # 评估与保存模型 score = model.evaluate(X_test, Y_test, verbose=0) model_json = model.to_json() with open("model.json", "w") as json_file: json_file.write(model_json) model.save_weights("model.h5") print("Saved model to disk") print('Test loss: ', score[0]) print('Test accuracy: ', score[1])
内容的提问来源于stack exchange,提问作者KETAK SINGH

