基于Keras的CNN训练准确率停滞在约1/n%,求助问题排查
Hey Ben, let's break down why your CNN is stuck at ~19% accuracy for 5-class vegetable classification—this is a common issue with small datasets, so let's go through the most likely culprits and fixes:
1. Dataset Size & Distribution: The Biggest Red Flag
200 total images split across 5 classes means only ~40 images per class. That's way too little data for a CNN to learn meaningful, generalizable features. Your model is basically guessing randomly (5 classes = 20% random accuracy), which aligns perfectly with your current results.
Fixes:
- Add aggressive data augmentation: Use Keras'
ImageDataGeneratorto generate synthetic variations of your existing images. This effectively expands your dataset without collecting new photos. Example code:from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen = ImageDataGenerator( rotation_range=30, # Rotate images up to 30 degrees width_shift_range=0.2, # Shift horizontally by 20% of width height_shift_range=0.2, # Shift vertically by 20% of height horizontal_flip=True, # Flip images horizontally zoom_range=0.2, # Zoom in/out by 20% brightness_range=[0.7, 1.3],# Adjust brightness fill_mode='nearest' # Fill empty pixels after transformations ) - Check class balance: Ensure no class has drastically fewer images than others (e.g., one class with 10 images vs. others with 50). If so, either collect more data for the underrepresented class or use weighted loss (
class_weightparameter inmodel.fit()) to compensate. - Verify train/validation split: Make sure you're using a stratified split (preserves class distribution) instead of random split. For example, 80% train / 20% validation should have ~32 images per class in training, ~8 in validation.
2. Model Structure Issues
Your model might be either too shallow (can't extract enough features) or too deep (overfits the tiny dataset instantly).
Fixes:
- Start with a lightweight, pre-trained model (transfer learning): Instead of building a CNN from scratch, use transfer learning with models like MobileNetV2 or EfficientNetB0. These models are pre-trained on millions of images and can adapt to your small dataset quickly. Example snippet:
from tensorflow.keras.applications import MobileNetV2 from tensorflow.keras.layers import Dense, GlobalAveragePooling2D from tensorflow.keras.models import Model base_model = MobileNetV2(weights='imagenet', include_top=False, input_shape=(224,224,3)) # Freeze base model initially base_model.trainable = False x = base_model.output x = GlobalAveragePooling2D()(x) x = Dense(128, activation='relu')(x) predictions = Dense(5, activation='softmax')(x) model = Model(inputs=base_model.input, outputs=predictions) - Add regularization if building from scratch: If you want a custom CNN, add Dropout layers and L2 regularization to prevent overfitting:
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense from tensorflow.keras.regularizers import l2 model = Sequential([ Conv2D(32, (3,3), activation='relu', input_shape=(128,128,3), kernel_regularizer=l2(0.001)), MaxPooling2D((2,2)), Dropout(0.25), Conv2D(64, (3,3), activation='relu', kernel_regularizer=l2(0.001)), MaxPooling2D((2,2)), Dropout(0.25), Flatten(), Dense(128, activation='relu', kernel_regularizer=l2(0.001)), Dropout(0.5), Dense(5, activation='softmax') ]) - Double-check output layer: Ensure your output layer uses
softmaxactivation (for multi-class classification) and your loss function matches your label format:- Use
sparse_categorical_crossentropyif labels are integer values (e.g., 0,1,2,3,4) - Use
categorical_crossentropyif labels are one-hot encoded (e.g., [1,0,0,0,0])
- Use
3. Training Strategy Missteps
You mentioned tuning optimizer LR and decay, but there are other critical training details to check:
Fixes:
- Correct step count: You noted 800 steps ×30 epochs, but with a batch size of 32 and 200 total images, your training set (80% = 160 images) should have only
160 // 32 = 5 steps per epoch. Setting 800 steps means you're repeatedly feeding the same small dataset hundreds of times per epoch—this leads to overfitting to noise, not meaningful features. Always set steps totrain_samples // batch_size, or let Keras calculate it automatically if usingflow_from_directory. - Use learning rate scheduling: Instead of manually tuning LR, use a scheduler to adjust it during training. For example,
ReduceLROnPlateaulowers LR when validation accuracy plateaus:from tensorflow.keras.callbacks import ReduceLROnPlateau lr_scheduler = ReduceLROnPlateau( monitor='val_accuracy', factor=0.5, # Halve LR when triggered patience=3, # Wait 3 epochs with no improvement min_lr=1e-6 # Minimum LR to avoid stopping learning ) # Add to model.fit() callbacks model.fit(..., callbacks=[lr_scheduler]) - Monitor validation accuracy: Always track
val_accuracyalongsideaccuracy. If training accuracy is high but validation is low, you're overfitting. If both are low (like your case), you're underfitting—meaning your model isn't learning enough from the tiny dataset (hence the need for data augmentation or transfer learning).
4. Data Preprocessing Errors
Even small preprocessing mistakes can derail your model:
Fixes:
- Normalize pixel values: Scale image pixels to the range [0,1] by dividing by 255.0. CNNs converge much faster on normalized data.
- Ensure consistent image size: All input images must match the input shape defined in your model (e.g., (224,224,3)). Use
ImageDataGenerator'starget_sizeparameter to resize images automatically. - Validate label correctness: Double-check that your image labels are accurate—mislabeled data (e.g., a carrot labeled as broccoli) will make it impossible for the model to learn correctly.
Start with the dataset fixes (data augmentation + stratified split) first—this will have the biggest impact. Then move to transfer learning if building from scratch isn't working.
内容的提问来源于stack exchange,提问作者Ben

