零基础Python深度学习入门到实战训练猫狗分类和手写数字识别算法
先深呼吸,别被”深度学习”这四个字吓到。我当年第一次接触的时候,也以为这是啥高深莫测的黑科技,结果学完之后发现——也就是那么回事儿。今天我就用大白话,带你从零开始,把猫狗分类和手写数字识别这两个经典项目搞定。
先说点实在的:深度学习到底是个啥?
想象一下,你小时候学认猫认狗的过程。大人给你看一百张猫的照片,说”这是猫”,再看一百张狗的照片,说”这是狗”。看多了之后,你自己就学会了分辨。深度学习干的,其实就是这事儿——给计算机喂大量图片,让它自己找出规律。
计算机不认识图片,它看到的是数字。一张28x28像素的灰色图片,在计算机眼里就是784个0到255之间的数字。深度学习模型,就是一个能把这784个数字映射成”这是猫还是狗”或”这是数字几”的函数。
别慌,咱们一步步来。
第一步:先把工具箱准备好
你不需要显卡,不需要服务器,只需要一台能跑Python的电脑。我推荐你这样装环境:
# 创建虚拟环境(推荐,避免包冲突)
# 在终端或命令行执行:
# python -m venv dl_env
# 激活环境:
# Windows: dl_env\Scripts\activate
# Mac/Linux: source dl_env/bin/activate
# 然后安装核心库:
pip install tensorflow matplotlib numpy pandas scikit-learn
安装完之后,我们做个小测试,确认一切正常:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
print(f"TensorFlow版本: {tf.__version__}")
print(f"GPU可用: {tf.config.list_physical_devices('GPU')}")
如果输出版本号,说明环境OK了。如果显示GPU为空也没关系,CPU跑小数据集完全够用。
第二个项目:手写数字识别——最友好的入门关卡
我先带你做手写数字识别,因为这个数据集(MNIST)很简单,只有28x28像素的黑白图片,而且标签很清晰。做完这个,你就能理解深度学习的整个流程了。
加载数据
# 加载MNIST数据集——这是Keras内置的,一行代码搞定
from tensorflow.keras.datasets import mnist
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# 看看数据长什么样
print(f"训练集大小: {X_train.shape}") # (60000, 28, 28)
print(f"标签示例: {y_train[:10]}") # 前10个标签
print(f"像素值范围: {X_train.min()} - {X_train.max()}") # 0-255
输出会告诉你:训练集有6万张图片,每张28x28像素,像素值在0到255之间(0是黑色,255是白色)。
让我给你看一张图片长什么样:
plt.figure(figsize=(2,2))
plt.imshow(X_train[0], cmap='gray') # 显示第0张图片
plt.title(f'标签: {y_train[0]}') # 显示对应的数字
plt.axis('off')
plt.show()
这张图显示的是数字”5”。你现在知道数据长啥样了。
数据预处理
深度学习模型对数据格式有要求,我们需要做几件事:
# 1. 把像素值从0-255缩放到0-1之间(归一化,帮助模型更快学习)
X_train = X_train.astype('float32') / 255.0
X_test = X_test.astype('float32') / 255.0
# 2. 调整维度:模型需要的是 (样本数, 高度, 宽度, 通道数)
# MNIST是灰度图,所以加一个通道维度
X_train = X_train.reshape(-1, 28, 28, 1)
X_test = X_test.reshape(-1, 28, 28, 1)
# 3. 标签编码:把数字标签变成"独热编码"
# 比如数字5变成 [0,0,0,0,0,1,0,0,0,0]
from tensorflow.keras.utils import to_categorical
y_train_encoded = to_categorical(y_train, 10)
y_test_encoded = to_categorical(y_test, 10)
print(f"独热编码示例: {y_train_encoded[0]}") # 对应数字5
为什么要独热编码?想象一下,如果你让模型输出”5”这个数字,它会以为5比3”大”,但数字之间并没有大小关系。独热编码把每个数字变成一个等长的向量,模型更容易理解。
搭建模型
现在到了最有趣的部分——搭建神经网络。我推荐你用Sequential(顺序模型),因为它简单直观:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten, Dropout
model = Sequential([
# 输入层:把28x28的图片"拉平"成一维向量(784个像素)
Flatten(input_shape=(28, 28, 1)),
# 隐藏层1:128个神经元,用ReLU激活函数
# ReLU的作用:让模型能学习非线性关系
Dense(128, activation='relu'),
# Dropout:随机"关掉"20%的神经元,防止过拟合
Dropout(0.2),
# 隐藏层2:64个神经元
Dense(64, activation='relu'),
# Dropout再关一次
Dropout(0.2),
# 输出层:10个神经元,对应0-9十个数字
# softmax让输出变成概率分布(总和为1)
Dense(10, activation='softmax')
])
# 查看模型结构
model.summary()
输出会显示模型有大约4.7万个参数。别被这个数字吓到,对计算机来说这完全是小意思。
让我解释一下这些层的含义:
- Flatten:把2D的图片”拍平”成1D的向量,就像把一张照片裁成一条长条
- Dense:全连接层,每个神经元都和上一层的所有神经元相连
- ReLU:激活函数,作用是告诉模型”这个特征重要就保留,不重要就关掉”
- Dropout:随机丢弃一些神经元,强迫模型学习更 robust 的特征
- Softmax:把输出转换成概率,比如 [0.1, 0.05, …, 0.8] 表示80%概率是数字9
编译模型
编译就是告诉模型用什么样的”学习方法”:
model.compile(
optimizer='adam', # 优化器:自动调整学习率的聪明方法
loss='categorical_crossentropy', # 损失函数:衡量预测和实际的差距
metrics=['accuracy'] # 评估指标:准确率
)
Adam优化器是深度学习中最常用的优化器之一,它会自动调整学习速率。损失函数选择”交叉熵”,这是分类问题中最标准的做法。
训练模型
# 开始训练,epochs=5表示遍历数据集5次
# batch_size=32表示每次用32张图片更新一次参数
history = model.fit(
X_train, y_train_encoded,
epochs=5,
batch_size=32,
validation_split=0.1, # 用10%训练数据做验证
verbose=1
)
训练过程中你会看到类似这样的输出:
Epoch 1/5
1575/1575 [==============================] - 3s 2ms/step - loss: 0.2563 - accuracy: 0.9201 - val_loss: 0.0876 - val_accuracy: 0.9712
Epoch 2/5
1575/1575 [==============================] - 3s 2ms/step - loss: 0.0912 - accuracy: 0.9715 - val_loss: 0.0645 - val_accuracy: 0.9789
...
损失在下降,准确率在上升,这就是好现象。
看看训练效果
# 可视化训练曲线
plt.figure(figsize=(12,4))
plt.subplot(1,2,1)
plt.plot(history.history['accuracy'], label='训练准确率')
plt.plot(history.history['val_accuracy'], label='验证准确率')
plt.title('准确率变化')
plt.xlabel('Epoch')
plt.ylabel('准确率')
plt.legend()
plt.subplot(1,2,2)
plt.plot(history.history['loss'], label='训练损失')
plt.plot(history.history['val_loss'], label='验证损失')
plt.title('损失变化')
plt.xlabel('Epoch')
plt.ylabel('损失')
plt.legend()
plt.show()
从曲线可以看出,模型在第一个epoch之后就达到了97%以上的准确率。对于MNIST数据集,这已经是很不错的成绩了。
测试和预测
# 在测试集上评估
test_loss, test_accuracy = model.evaluate(X_test, y_test_encoded, verbose=0)
print(f"测试集准确率: {test_accuracy*100:.2f}%")
# 预测几张图片看看效果
predictions = model.predict(X_test[:10])
predicted_labels = np.argmax(predictions, axis=1)
plt.figure(figsize=(10,2))
for i in range(10):
plt.subplot(2,5,i+1)
plt.imshow(X_test[i].reshape(28,28), cmap='gray')
color = 'green' if predicted_labels[i] == y_test[i] else 'red'
plt.title(f'预测:{predicted_labels[i]} 真实:{y_test[i]}', color=color)
plt.axis('off')
plt.tight_layout()
plt.show()
绿色表示预测正确,红色表示预测错误。你可以看到大部分都对了,偶尔有几张手写比较潦草的数字会识别错误,这很正常。
保存模型
# 保存模型,方便以后直接加载使用
model.save('mnist_digit_classifier.h5')
# 加载已保存的模型
from tensorflow.keras.models import load_model
new_model = load_model('mnist_digit_classifier.h5')
第三个项目:猫狗分类——稍微有点挑战
MNIST太简单了,现在我们挑战一下真正的图片分类任务。猫狗分类是深度学习领域的”Hello World”,有很多现成的数据集。
准备猫狗数据集
有两种方式:第一种是用Kaggle的经典数据集(猫狗大战),第二种是自己下载图片。我推荐第一种,但需要去Kaggle网站注册账号后下载。如果你不想去Kaggle,可以用下面的简化版——用TFDS直接下载:
# 方法一:用TFDS下载(推荐,不需要手动下载)
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical
# 注意:CIFAR-10不是猫狗数据集,但包含"猫"和"狗"这两个类别
# 为了简化教程,我们用CIFAR-10中的猫狗样本
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
# CIFAR-10的标签: 0=飞机, 1=汽车, 2=鸟, 3=猫, 4=狗, ...
# 我们只保留猫和狗
cat_dog_indices = (y_train.flatten() == 3) | (y_train.flatten() == 4)
X_train_cd = X_train[cat_dog_indices]
y_train_cd = y_train[cat_dog_indices][cat_dog_indices]
cat_dog_test_indices = (y_test.flatten() == 3) | (y_test.flatten() == 4)
X_test_cd = X_test[cat_dog_test_indices]
y_test_cd = y_test[y_test.flatten()][cat_dog_test_indices]
# 重新编码标签:猫=0, 狗=1
y_train_cd = (y_train_cd.flatten() == 4).astype(int)
y_test_cd = (y_test_cd.flatten() == 4).astype(int)
print(f"猫狗训练集: {X_train_cd.shape}") # 大约5000张图片
print(f"猫狗测试集: {X_test_cd.shape}") # 大约1000张图片
print(f"猫: {np.sum(y_train_cd==0)}, 狗: {np.sum(y_train_cd==1)}")
让我给你看几张样本:
plt.figure(figsize=(12,2))
for i in range(10):
plt.subplot(2,5,i+1)
plt.imshow(X_train_cd[i])
label = '猫' if y_train_cd[i] == 0 else '狗'
plt.title(label)
plt.axis('off')
plt.tight_layout()
plt.show()
从这些图片可以看出,CIFAR-10的图片是32x32像素的彩色图,比MNIST复杂多了。
数据预处理
# 归一化像素值
X_train_cd = X_train_cd.astype('float32') / 255.0
X_test_cd = X_test_cd.astype('float32') / 255.0
# 标签编码(二分类用sigmoid+binary_crossentropy更简单)
y_train_cd = y_train_cd.reshape(-1, 1)
y_test_cd = y_test_cd.reshape(-1, 1)
等等,为什么不继续用独热编码?因为这是二分类问题,用sigmoid输出0到1之间的概率,配合binary crossentropy损失函数更简洁。输出>0.5就是狗,<0.5就是猫。
数据增强——让模型更聪明
猫狗分类比手写数字识别难多了,因为图片背景复杂、角度各异。我们需要做数据增强, artificially增加训练数据的多样性:
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# 训练时的数据增强
train_datagen = ImageDataGenerator(
rotation_range=20, # 随机旋转20度
width_shift_range=0.2, # 随机水平移位
height_shift_range=0.2, # 随机垂直移位
zoom_range=0.2, # 随机缩放
horizontal_flip=True, # 随机水平翻转
fill_mode='nearest' # 填充方式
)
# 测试时不做增强
test_datagen = ImageDataGenerator()
# 用生成器提供数据(自动增强)
train_generator = train_datagen.flow(X_train_cd, y_train_cd, batch_size=32)
数据增强的原理很简单:你给模型看的图片稍微转一下、移动一下、翻转一下,模型就能学到”不管猫从哪个角度看,它都是猫”。这就像你认人一样,从不同角度都能认出同一个人。
搭建CNN模型
对于图像分类,我们必须用卷积神经网络(CNN),普通的全连接网络处理图像效果很差。CNN的核心思想是:先用小的卷积核在图片上滑动,提取边缘、纹理等低级特征,再逐步组合成高级特征。
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization
model_cd = Sequential([
# 第一组卷积:32个3x3的卷积核
Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)),
BatchNormalization(), # 批归一化,加速训练
Conv2D(32, (3, 3), activation='relu'),
BatchNormalization(),
MaxPooling2D((2, 2)), # 下采样,减少维度
Dropout(0.25),
# 第二组卷积:64个3x3的卷积核
Conv2D(64, (3, 3), activation='relu'),
BatchNormalization(),
Conv2D(64, (3, 3), activation='relu'),
BatchNormalization(),
MaxPooling2D((2, 2)),
Dropout(0.25),
# 第三组卷积:128个3x3的卷积核
Conv2D(128, (3, 3), activation='relu'),
BatchNormalization(),
Conv2D(128, (3, 3), activation='relu'),
BatchNormalization(),
MaxPooling2D((2, 2)),
Dropout(0.25),
# 全连接层
Flatten(),
Dense(256, activation='relu'),
Dropout(0.5),
Dense(1, activation='sigmoid') # 二分类输出
])
model_cd.summary()
让我解释一下CNN的工作原理:
输入: 32x32x3 (彩色图片)
|
v
Conv2D(32个3x3卷积核) → 提取32种特征图
|
v
Conv2D(32个3x3卷积核) → 进一步提取特征
|
v
MaxPooling(2x2) → 把图片缩小一半(保留重要信息)
|
v
... 重复这个过程 ...
|
v
Flatten → 把特征图拉平
|
v
Dense(256) → 高级特征组合
|
v
Dense(1, sigmoid) → 输出概率(0=猫, 1=狗)
每一层卷积都在做一件事:用一个小窗口(3x3)在图片上滑动,计算窗口内像素的加权和。不同的卷积核会学到不同的特征——有的检测边缘,有的检测角落,有的检测纹理。多层叠加后,后面的层能识别更复杂的特征,比如”猫耳朵的形状”、”狗鼻子”。
编译和训练
model_cd.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy']
)
# 训练,用数据增强生成器
history_cd = model_cd.fit(
train_generator,
steps_per_epoch=len(X_train_cd) // 32,
epochs=15,
validation_data=test_datagen.flow(X_test_cd, y_test_cd, batch_size=32),
validation_steps=len(X_test_cd) // 32,
verbose=1
)
训练15个epoch大概需要几分钟到十几分钟,取决于你的电脑配置。你会看到类似这样的输出:
Epoch 1/15
157/157 [==============================] - 12s 76ms/step - loss: 0.6234 - accuracy: 0.6512 - val_loss: 0.4123 - val_accuracy: 0.8234
Epoch 2/15
157/157 [==============================] - 11s 70ms/step - loss: 0.3987 - accuracy: 0.8345 - val_loss: 0.3512 - val_accuracy: 0.8456
...
评估和可视化
# 评估
test_loss, test_acc = model_cd.evaluate(
test_datagen.flow(X_test_cd, y_test_cd, batch_size=32),
steps=len(X_test_cd) // 32
)
print(f"测试集准确率: {test_acc*100:.2f}%")
# 可视化训练曲线
plt.figure(figsize=(12,4))
plt.subplot(1,2,1)
plt.plot(history_cd.history['accuracy'], label='训练准确率')
plt.plot(history_cd.history['val_accuracy'], label='验证准确率')
plt.title('准确率')
plt.xlabel('Epoch')
plt.ylabel('准确率')
plt.legend()
plt.grid(True)
plt.subplot(1,2,2)
plt.plot(history_cd.history['loss'], label='训练损失')
plt.plot(history_cd.history['val_loss'], label='验证损失')
plt.title('损失')
plt.xlabel('Epoch')
plt.ylabel('损失')
plt.legend()
plt.grid(True)
plt.show()
预测新图片
from tensorflow.keras.preprocessing import image
import numpy as np
# 加载一张测试图片
test_img = image.load_img('test_cat.jpg', target_size=(32, 32))
test_img = image.img_to_array(test_img)
test_img = np.expand_dims(test_img, axis=0) # 增加批次维度
test_img = test_img / 255.0
# 预测
prediction = model_cd.predict(test_img)[0][0]
if prediction > 0.5:
print(f"预测结果: 狗 (概率: {prediction*100:.1f}%)")
else:
print(f"预测结果: 猫 (概率: {(1-prediction)*100:.1f}%)")
几个让你少走弯路的建议
过拟合怎么办?
如果你在训练集上准确率很高,但测试集上很低,就是过拟合了。解决方法:
- 增加Dropout比例
- 增加数据增强
- 减少模型复杂度
- 用早停法(Early Stopping)
from tensorflow.keras.callbacks import EarlyStopping
early_stop = EarlyStopping(
monitor='val_loss',
patience=3, # 验证损失3个epoch不下降就停止
restore_best_weights=True # 恢复最佳权重
)
# 训练时加入回调
model_cd.fit(..., callbacks=[early_stop])
准确率上不去怎么办?
- 检查数据:打印几张图片看看,确认标签是否正确
- 增加训练时间:有时候epoch不够
- 调整学习率:用LearningRateScheduler
- 换预训练模型:用迁移学习,后面会讲
from tensorflow.keras.callbacks import LearningRateScheduler
def scheduler(epoch, lr):
if epoch < 5:
return 0.001
else:
return 0.001 / 10
lr_callback = LearningRateScheduler(scheduler)
想更快训练怎么办?
如果你有一张NVIDIA显卡,TensorFlow会自动使用GPU。如果没有,可以考虑:
- 减少图片尺寸(CIFAR-10已经是32x32了,不用改)
- 减少卷积核数量
- 用更少的epoch
进阶:用迁移学习提升猫狗分类准确率
如果你嫌上面的模型准确率不够高(通常能达到85-90%),可以用迁移学习。迁移学习的思想是:别人已经在ImageNet(1000类图片)上训练好了一个强大的模型,我们直接拿过来用,只需要微调最后几层。
from tensorflow.keras.applications import VGG16
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
# 加载预训练的VGG16(不含顶层分类器)
base_model = VGG16(weights='imagenet', include_top=False, input_shape=(32, 32, 3))
# 冻结底层参数(不训练)
base_model.trainable = False
# 构建新模型
x = base_model.output
x = GlobalAveragePooling2D()(x) # 替代Flatten,更稳定
x = Dense(256, activation='relu')(x)
x = Dropout(0.5)(x)
output = Dense(1, activation='sigmoid')(x)
transfer_model = Model(base_model.input, output)
transfer_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# 训练
transfer_history = transfer_model.fit(
train_generator,
steps_per_epoch=len(X_train_cd) // 32,
epochs=10,
validation_data=test_datagen.flow(X_test_cd, y_test_cd, batch_size=32),
validation_steps=len(X_test_cd) // 32
)
# 解冻部分层微调
base_model.trainable = True
# 只解冻最后几层
for layer in base_model.layers[:-4]:
layer.trainable = False
transfer_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# 微调
fine_tune_history = transfer_model.fit(
train_generator,
steps_per_epoch=len(X_train_cd) // 32,
epochs=5,
validation_data=test_datagen.flow(X_test_cd, y_test_cd, batch_size=32),
validation_steps=len(X_test_cd) // 32
)
迁移学习通常能达到95%以上的准确率。VGG16在ImageNet上见过1400万张图片,它学到的特征非常通用,我们只需要让它适应猫狗这两个类别。
总结:你学到了什么?
今天我们一起做了三件事:
第一,理解了深度学习的基本原理——就像教小孩认猫认狗一样,给模型看大量标注好的图片,让它自己找规律。
第二,学会了完整的深度学习工作流程:加载数据 → 预处理 → 搭建模型 → 编译 → 训练 → 评估 → 预测。这个流程适用于几乎所有图像分类任务。
第三,掌握了两个实战项目:MNIST手写数字识别(简单的二分类任务)和猫狗分类(稍微复杂一点)。
下一步建议
你现在已经入门了,如果想继续深入,我建议:
- 换个数据集练手:比如CIFAR-100(100类图片)或Fashion-MNIST(服饰分类)
- 学习更复杂的模型:ResNet、EfficientNet等现代架构
- 尝试目标检测:YOLO、SSD等,不只是分类,还能框出物体位置
- 学习模型部署:把训练好的模型部署成API服务或嵌入式应用
记住,深度学习不是魔法,它只是一套数学工具。你把每个概念拆开来理解,就会发现它们都很合理。我刚开始学的时候也困惑过,但每搞懂一个概念,那种”原来如此”的感觉真的很棒。
有问题随时回来翻这篇教程,或者把代码跑一遍看看输出。动手实践是学习深度学习最有效的方法,光看是学不会的。祝你学习愉快!
