CNN图像分类入门:基于TensorFlow+Keras的CIFAR-10数据集全流程实战
文章目录
- CNN图像分类入门:基于TensorFlow+Keras的CIFAR-10数据集全流程实战
-
- 一、项目背景与意义
-
- [1.1 为什么选择CIFAR-10?](#1.1 为什么选择CIFAR-10?)
- [1.2 本文目标](#1.2 本文目标)
- 二、技术原理与架构设计
-
- [2.1 ANN vs CNN:为什么CNN更适合图像?](#2.1 ANN vs CNN:为什么CNN更适合图像?)
- [2.2 本文CNN架构设计](#2.2 本文CNN架构设计)
- 三、环境搭建与数据准备
-
- [3.1 依赖安装](#3.1 依赖安装)
- [3.2 导入库与加载数据](#3.2 导入库与加载数据)
- [3.3 数据预处理](#3.3 数据预处理)
- [3.4 可视化样本](#3.4 可视化样本)
- 四、ANN基线模型:建立对比基准
-
- [4.1 ANN模型构建](#4.1 ANN模型构建)
- [4.2 ANN结果分析](#4.2 ANN结果分析)
- 五、CNN模型:从原理到实现
-
- [5.1 CNN模型构建](#5.1 CNN模型构建)
- [5.2 模型编译与训练](#5.2 模型编译与训练)
- [5.3 训练过程解读](#5.3 训练过程解读)
- 六、模型评估与可视化
-
- [6.1 测试集评估](#6.1 测试集评估)
- [6.2 单样本预测验证](#6.2 单样本预测验证)
- [6.3 性能对比总结](#6.3 性能对比总结)
- 七、常见错误与解决方案
- 八、进阶优化方向
-
- [8.1 数据增强(Data Augmentation)](#8.1 数据增强(Data Augmentation))
- [8.2 更深的网络架构](#8.2 更深的网络架构)
- [8.3 迁移学习](#8.3 迁移学习)
- 九、总结与展望
- 参考链接
一、项目背景与意义
1.1 为什么选择CIFAR-10?
CIFAR-10是深度学习图像分类领域最经典的数据集之一,由Alex Krizhevsky、Vinod Nair和Geoffrey Hinton于2009年收集整理。它包含60000张32×32彩色图像,分为10个类别,每个类别6000张,是计算机视觉入门和模型验证的"金标准"。
| 特性 | CIFAR-10 | MNIST | ImageNet |
|---|---|---|---|
| 图像尺寸 | 32×32×3 | 28×28×1 | 224×224×3 |
| 类别数 | 10 | 10 | 1000 |
| 训练集 | 50000 | 60000 | 128万 |
| 测试集 | 10000 | 10000 | 5万 |
| 难度 | 中等 | 低 | 高 |
| 适用场景 | CNN入门验证 | MLP基础 | 迁移学习/大规模训练 |
CIFAR-10的10个类别涵盖了日常生活中的常见物体:
python
# CIFAR-10 的 10 个类别
classes = [
"airplane", # 飞机
"automobile", # 汽车
"bird", # 鸟
"cat", # 猫
"deer", # 鹿
"dog", # 狗
"frog", # 青蛙
"horse", # 马
"ship", # 船
"truck" # 卡车
]
1.2 本文目标
通过本文,你将:
- 理解ANN(人工神经网络)与CNN(卷积神经网络)在图像分类上的本质差异
- 掌握使用TensorFlow/Keras构建CNN模型的完整流程
- 学会数据预处理、归一化和模型评估的标准方法
- 获得一个可直接运行的完整代码项目
核心理念:CNN之于图像就像人类的眼睛之于视觉------它通过卷积操作自动提取空间特征,而不是像ANN那样把图像"压扁"成一条线。
二、技术原理与架构设计
2.1 ANN vs CNN:为什么CNN更适合图像?
先看两种网络架构的对比:
┌─────────────────────────────────────────────────────────┐
│ ANN 图像分类流程 │
│ [32×32×3] → Flatten → [3072] → Dense → Dense → Output │
│ 图像被压扁成一维向量,丢失全部空间结构信息 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ CNN 图像分类流程 │
│ [32×32×3] → Conv2D → Pool → Conv2D → Pool → FC → Out │
│ 通过卷积核滑动提取局部特征,保留空间层次结构 │
└─────────────────────────────────────────────────────────┘
ANN的致命缺陷:将32×32×3=3072维的图像直接展平,空间信息完全丢失------左上角的像素和右下角的像素在展平后没有任何区别。
CNN的核心优势:卷积核在图像上滑动,每次只关注一个小区域(感受野),层层提取从边缘到纹理再到语义的层次化特征。
2.2 本文CNN架构设计
#mermaid-svg-cFI55ZLiAsnwVSGe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cFI55ZLiAsnwVSGe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cFI55ZLiAsnwVSGe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cFI55ZLiAsnwVSGe .error-icon{fill:#552222;}#mermaid-svg-cFI55ZLiAsnwVSGe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cFI55ZLiAsnwVSGe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cFI55ZLiAsnwVSGe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cFI55ZLiAsnwVSGe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cFI55ZLiAsnwVSGe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cFI55ZLiAsnwVSGe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cFI55ZLiAsnwVSGe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cFI55ZLiAsnwVSGe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cFI55ZLiAsnwVSGe .marker.cross{stroke:#333333;}#mermaid-svg-cFI55ZLiAsnwVSGe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cFI55ZLiAsnwVSGe p{margin:0;}#mermaid-svg-cFI55ZLiAsnwVSGe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cFI55ZLiAsnwVSGe .cluster-label text{fill:#333;}#mermaid-svg-cFI55ZLiAsnwVSGe .cluster-label span{color:#333;}#mermaid-svg-cFI55ZLiAsnwVSGe .cluster-label span p{background-color:transparent;}#mermaid-svg-cFI55ZLiAsnwVSGe .label text,#mermaid-svg-cFI55ZLiAsnwVSGe span{fill:#333;color:#333;}#mermaid-svg-cFI55ZLiAsnwVSGe .node rect,#mermaid-svg-cFI55ZLiAsnwVSGe .node circle,#mermaid-svg-cFI55ZLiAsnwVSGe .node ellipse,#mermaid-svg-cFI55ZLiAsnwVSGe .node polygon,#mermaid-svg-cFI55ZLiAsnwVSGe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cFI55ZLiAsnwVSGe .rough-node .label text,#mermaid-svg-cFI55ZLiAsnwVSGe .node .label text,#mermaid-svg-cFI55ZLiAsnwVSGe .image-shape .label,#mermaid-svg-cFI55ZLiAsnwVSGe .icon-shape .label{text-anchor:middle;}#mermaid-svg-cFI55ZLiAsnwVSGe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cFI55ZLiAsnwVSGe .rough-node .label,#mermaid-svg-cFI55ZLiAsnwVSGe .node .label,#mermaid-svg-cFI55ZLiAsnwVSGe .image-shape .label,#mermaid-svg-cFI55ZLiAsnwVSGe .icon-shape .label{text-align:center;}#mermaid-svg-cFI55ZLiAsnwVSGe .node.clickable{cursor:pointer;}#mermaid-svg-cFI55ZLiAsnwVSGe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cFI55ZLiAsnwVSGe .arrowheadPath{fill:#333333;}#mermaid-svg-cFI55ZLiAsnwVSGe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cFI55ZLiAsnwVSGe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cFI55ZLiAsnwVSGe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cFI55ZLiAsnwVSGe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cFI55ZLiAsnwVSGe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cFI55ZLiAsnwVSGe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cFI55ZLiAsnwVSGe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cFI55ZLiAsnwVSGe .cluster text{fill:#333;}#mermaid-svg-cFI55ZLiAsnwVSGe .cluster span{color:#333;}#mermaid-svg-cFI55ZLiAsnwVSGe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cFI55ZLiAsnwVSGe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cFI55ZLiAsnwVSGe rect.text{fill:none;stroke-width:0;}#mermaid-svg-cFI55ZLiAsnwVSGe .icon-shape,#mermaid-svg-cFI55ZLiAsnwVSGe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cFI55ZLiAsnwVSGe .icon-shape p,#mermaid-svg-cFI55ZLiAsnwVSGe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cFI55ZLiAsnwVSGe .icon-shape rect,#mermaid-svg-cFI55ZLiAsnwVSGe .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cFI55ZLiAsnwVSGe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cFI55ZLiAsnwVSGe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cFI55ZLiAsnwVSGe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Input: 32x32x3
Conv2D: 32 filters, 3x3
MaxPooling2D: 2x2
Conv2D: 64 filters, 3x3
MaxPooling2D: 2x2
Flatten
Dense: 64, ReLU
Dense: 10, Softmax
Output: 10-class probabilities
网络分为三个层次:
| 层次 | 操作 | 输出形状 | 参数量 | 作用 |
|---|---|---|---|---|
| 输入层 | - | 32×32×3 | 0 | 原始RGB图像 |
| 卷积层1 | Conv2D(32,3×3)+ReLU | 30×30×32 | 896 | 提取低级特征(边缘/颜色) |
| 池化层1 | MaxPooling(2×2) | 15×15×32 | 0 | 降采样,减少计算量 |
| 卷积层2 | Conv2D(64,3×3)+ReLU | 13×13×64 | 18,496 | 提取高级特征(纹理/形状) |
| 池化层2 | MaxPooling(2×2) | 6×6×64 | 0 | 再次降采样 |
| 展平层 | Flatten | 2304 | 0 | 将特征图展平为向量 |
| 全连接层 | Dense(64)+ReLU | 64 | 147,520 | 特征组合与分类决策 |
| 输出层 | Dense(10)+Softmax | 10 | 650 | 10类概率输出 |
设计要点:模型总参数量约16.7万,对于CIFAR-10来说是合理的规模。过大的模型容易过拟合,过小的模型则欠拟合。
三、环境搭建与数据准备
3.1 依赖安装
bash
# 安装核心依赖
pip install tensorflow matplotlib numpy scikit-learn
# 验证安装
python -c "import tensorflow as tf; print(tf.__version__)"
3.2 导入库与加载数据
CIFAR-10数据集已内置在TensorFlow/Keras中,无需手动下载:
python
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt
import numpy as np
# 加载 CIFAR-10 数据集(内置,无需下载)
(X_train, y_train), (X_test, y_test) = datasets.cifar10.load_data()
# 查看数据形状
print(f"训练集图像形状: {X_train.shape}") # (50000, 32, 32, 3)
print(f"测试集图像形状: {X_test.shape}") # (10000, 32, 32, 3)
print(f"训练集标签形状: {y_train.shape}") # (50000, 1)
3.3 数据预处理
python
# 将标签从2D转为1D(CNN要求的格式)
y_train = y_train.reshape(-1,)
y_test = y_test.reshape(-1,)
# 归一化:将像素值从 [0, 255] 缩放到 [0, 1]
X_train = X_train / 255.0
X_test = X_test / 255.0
# 类别名称映射
classes = [
"airplane", "automobile", "bird", "cat",
"deer", "dog", "frog", "horse", "ship", "truck"
]
归一化的重要性:神经网络对输入尺度非常敏感。如果不归一化,像素值范围0,255会导致梯度更新不稳定,模型收敛困难。这是最常见的"模型不收敛"原因之一。
3.4 可视化样本
python
def plot_sample(X, y, index):
"""绘制单张样本图像及其标签"""
plt.figure(figsize=(15, 2))
plt.imshow(X[index])
plt.xlabel(classes[y[index]])
plt.show()
# 可视化前几张图片
plot_sample(X_train, y_train, 0) # 飞机
plot_sample(X_train, y_train, 1) # 汽车
四、ANN基线模型:建立对比基准
在构建CNN之前,先用一个简单的ANN(人工神经网络)作为基线,这样可以直观对比CNN的性能提升。
4.1 ANN模型构建
python
# 构建 ANN 基线模型
ann = models.Sequential([
# 将 32×32×3=3072 维的图像展平
layers.Flatten(input_shape=(32, 32, 3)),
# 隐藏层1:3000个神经元
layers.Dense(3000, activation='relu'),
# 隐藏层2:1000个神经元
layers.Dense(1000, activation='relu'),
# 输出层:10个类别
layers.Dense(10, activation='softmax')
])
# 编译模型
ann.compile(
optimizer='SGD',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 训练
ann.fit(X_train, y_train, epochs=5)
4.2 ANN结果分析
经过5个epoch训练后,ANN的准确率大约在**49%**左右。对于10分类任务,随机猜测的准确率是10%,49%虽然比随机好,但远未达到实用水平。
python
# 评估 ANN 性能
from sklearn.metrics import confusion_matrix, classification_report
y_pred = ann.predict(X_test)
y_pred_classes = [np.argmax(element) for element in y_pred]
print("ANN 分类报告:\n", classification_report(y_test, y_pred_classes))
ANN表现差的原因:
- 图像被展平为一维向量,完全丢失了空间结构
- 无法利用像素间的局部相关性
- 参数量巨大(3000×3072≈9.2M),但学习能力有限
五、CNN模型:从原理到实现
5.1 CNN模型构建
python
# 构建 CNN 模型
cnn = models.Sequential([
# 卷积层1:32个3×3卷积核,提取低级特征
layers.Conv2D(
filters=32,
kernel_size=(3, 3),
activation='relu',
input_shape=(32, 32, 3)
),
# 池化层1:2×2最大池化,降采样
layers.MaxPooling2D((2, 2)),
# 卷积层2:64个3×3卷积核,提取高级特征
layers.Conv2D(
filters=64,
kernel_size=(3, 3),
activation='relu'
),
# 池化层2:再次降采样
layers.MaxPooling2D((2, 2)),
# 展平 + 全连接层
layers.Flatten(),
layers.Dense(64, activation='relu'),
# 输出层:10个类别
layers.Dense(10, activation='softmax')
])
# 查看模型结构
cnn.summary()
5.2 模型编译与训练
python
# 编译模型
cnn.compile(
optimizer='adam', # Adam优化器,自适应学习率
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 训练模型(10个epoch)
history = cnn.fit(X_train, y_train, epochs=10)
5.3 训练过程解读
训练过程中你会看到类似这样的输出:
Epoch 1/10 loss: 1.4823 - accuracy: 0.4621
Epoch 5/10 loss: 0.7821 - accuracy: 0.7268
Epoch 10/10 loss: 0.5214 - accuracy: 0.8176
| 观察指标 | 预期表现 | 含义 |
|---|---|---|
| loss持续下降 | ✅ 正常 | 模型在学习,损失在减小 |
| accuracy稳步上升 | ✅ 正常 | 分类准确率提升 |
| loss下降但accuracy不变 | ⚠️ 过拟合 | 需增加正则化 |
| loss震荡不收敛 | ❌ 异常 | 学习率过大或数据未归一化 |
关键结论 :CNN在10个epoch后准确率达到约70% ,相比ANN的49%提升了21个百分点,这充分证明了卷积操作在图像特征提取上的巨大优势。
六、模型评估与可视化
6.1 测试集评估
python
# 在测试集上评估
test_loss, test_acc = cnn.evaluate(X_test, y_test)
print(f"测试集准确率: {test_acc:.4f}")
print(f"测试集损失: {test_loss:.4f}")
# 预测
y_pred = cnn.predict(X_test)
y_classes = [np.argmax(element) for element in y_pred]
6.2 单样本预测验证
python
# 验证单张图片的预测结果
index = 3
plot_sample(X_test, y_test, index)
print(f"真实标签: {classes[y_test[index]]}")
print(f"预测标签: {classes[y_classes[index]]}")
6.3 性能对比总结
| 模型 | 训练集准确率 | 测试集准确率 | 参数量 | 训练时间 |
|---|---|---|---|---|
| ANN | ~52% | ~49% | ~9.2M | 较快 |
| CNN | ~85% | ~70% | ~167K | 中等 |
| 提升幅度 | +33% | +21% | -98% | - |
反直觉的发现 :CNN参数量只有ANN的1/55(167K vs 9.2M),但准确率却高出21个百分点。这说明参数效率比参数数量更重要------CNN用更少的参数学到了更好的特征表示。
七、常见错误与解决方案
错误1:忘记归一化数据
python
# ❌ 错误:未归一化
X_train = datasets.cifar10.load_data()[0][0] # 像素值 [0, 255]
cnn.fit(X_train, y_train, epochs=10)
# 结果:loss不下降或震荡,模型无法收敛
# ✅ 正确:先归一化
X_train = X_train / 255.0
cnn.fit(X_train, y_train, epochs=10)
# 结果:loss稳定下降,模型正常收敛
根因:未归一化的数据使激活函数工作在饱和区,梯度消失;归一化后数据分布更均匀,优化更稳定。
错误2:标签维度不匹配
python
# ❌ 错误:标签是2D的 (50000, 1)
y_train = datasets.cifar10.load_data()[0][1] # shape: (50000, 1)
cnn.fit(X_train, y_train, epochs=10)
# 报错:sparse_categorical_crossentropy 期望1D标签
# ✅ 正确:转为1D
y_train = y_train.reshape(-1,) # shape: (50000,)
cnn.fit(X_train, y_train, epochs=10)
错误3:过度训练导致过拟合
python
# ❌ 错误:epochs设置过大
cnn.fit(X_train, y_train, epochs=50)
# 训练集准确率 95%+,测试集只有 70% → 严重过拟合
# ✅ 正确:使用EarlyStopping
from tensorflow.keras.callbacks import EarlyStopping
early_stop = EarlyStopping(
monitor='val_loss',
patience=3,
restore_best_weights=True
)
cnn.fit(
X_train, y_train,
epochs=50,
validation_split=0.2,
callbacks=[early_stop]
)
错误4:混淆矩阵解读错误
python
# ❌ 错误:只看整体准确率就下结论
# 70%准确率意味着每个类别都有70%正确?错!
# ✅ 正确:查看每个类别的详细指标
from sklearn.metrics import classification_report
print(classification_report(y_test, y_classes))
# 可能发现:cat类别只有55%准确率,而automobile有85%
# 这说明模型对不同类别学习能力不同
八、进阶优化方向
8.1 数据增强(Data Augmentation)
python
from tensorflow.keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
rotation_range=15, # 随机旋转 ±15度
width_shift_range=0.1, # 水平平移
height_shift_range=0.1, # 垂直平移
horizontal_flip=True, # 水平翻转
zoom_range=0.1 # 缩放
)
datagen.fit(X_train)
cnn.fit(datagen.flow(X_train, y_train, batch_size=32), epochs=20)
8.2 更深的网络架构
python
# 增加卷积层和BatchNormalization
improved_cnn = models.Sequential([
layers.Conv2D(32, (3,3), padding='same', activation='relu', input_shape=(32,32,3)),
layers.BatchNormalization(),
layers.Conv2D(32, (3,3), padding='same', activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D((2,2)),
layers.Dropout(0.25),
layers.Conv2D(64, (3,3), padding='same', activation='relu'),
layers.BatchNormalization(),
layers.Conv2D(64, (3,3), padding='same', activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D((2,2)),
layers.Dropout(0.25),
layers.Conv2D(128, (3,3), padding='same', activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D((2,2)),
layers.Dropout(0.25),
layers.Flatten(),
layers.Dense(256, activation='relu'),
layers.BatchNormalization(),
layers.Dropout(0.5),
layers.Dense(10, activation='softmax')
])
8.3 迁移学习
python
# 使用预训练的ResNet50
from tensorflow.keras.applications import ResNet50
base_model = ResNet50(weights='imagenet', include_top=False, input_shape=(32,32,3))
base_model.trainable = False # 冻结预训练权重
model = models.Sequential([
base_model,
layers.GlobalAveragePooling2D(),
layers.Dense(256, activation='relu'),
layers.Dense(10, activation='softmax')
])
| 优化方案 | 预期准确率提升 | 实现难度 | 推荐场景 |
|---|---|---|---|
| 数据增强 | +5~10% | ⭐ | 所有场景 |
| 更深网络 | +5~15% | ⭐⭐ | 数据量充足 |
| 迁移学习 | +15~25% | ⭐⭐ | 追求高精度 |
| 集成学习 | +3~8% | ⭐⭐⭐ | 竞赛场景 |
九、总结与展望
本文从零开始,带你完整走通了CIFAR-10图像分类的CNN实战流程。我们从ANN基线模型(49%准确率)出发,构建了一个简洁的CNN模型(70%准确率),以仅1/55的参数量实现了21个百分点的性能飞跃。
核心收获:
- 卷积操作是CNN的灵魂------它通过权值共享和局部连接,高效提取图像的空间层次特征
- 数据归一化是基础但常被忽略的步骤------不归一化是"模型不收敛"的头号元凶
- ANN vs CNN的对比直观展示了"参数效率"远比"参数数量"重要
- CIFAR-10是绝佳的CNN入门数据集------不大不小,刚好够你验证想法
下一篇预告:我们将继续深入图像分类领域,探索一个更贴近实际应用的深度学习图像分类入门项目,使用TensorFlow构建端到端的分类pipeline。
参考链接
- TensorFlow官方CNN教程
- CIFAR-10数据集官网
- Deep Learning with TensorFlow 2.0 - codebasics
- CS231n: Convolutional Neural Networks for Visual Recognition