第2章 TensorFlow 2快速入门

目录

一、TensorFlow 2环境搭建

1.1 什么是TensorFlow?

TensorFlow是谷歌开发的开源深度学习框架,它可以帮助我们:

  • 构建神经网络(就像搭积木一样)
  • 训练模型(让计算机学会处理数据)
  • 使用模型进行预测

通俗理解:TensorFlow就像一个"人工智能工具箱",里面有很多现成的工具,我们只需要学会如何使用这些工具,就能快速搭建AI应用。

1.2 安装TensorFlow

1.2.1 安装前的准备

需要区分两种版本

版本 适用情况 特点
CPU版本 普通笔记本电脑 安装简单,适合学习和练习
GPU版本 有独立NVIDIA显卡的电脑 训练速度快,适合处理大数据

查看自己电脑的显卡

  • Windows:右键"此电脑" → “管理” → “设备管理器” → “显示适配器”
  • 如果有NVIDIA显卡(如GTX、RTX系列),可以安装GPU版本
  • 如果没有独立显卡或者是AMD/Intel显卡,只能安装CPU版本

1.2.2 安装CPU版本(推荐学生使用)

方法一:直接安装(最简单)

打开命令提示符(CMD)或Anaconda Prompt,输入:

1
pip install tensorflow

方法二:使用国内镜像源(下载更快)

1
pip install tensorflow -i https://pypi.tuna.tsinghua.edu.cn/simple

为什么使用国内镜像?

  • 默认的下载源在国外,下载速度很慢
  • 使用清华源、阿里源等国内镜像,速度可以提升10-100倍

1.2.3 验证安装是否成功

打开Python交互式环境(输入pythonipython),然后输入:

1
2
import tensorflow as tf
print(tf.__version__)

如果没有报错,并能看到版本号(如2.10.0),说明安装成功!

学生可能遇到的错误

  • ModuleNotFoundError: No module named 'tensorflow':说明没有安装成功,重新安装
  • 安装过程中出现红色错误信息:可能是网络问题,尝试使用国内镜像源

二、TensorFlow 2基本数据类型

2.1 什么是张量(Tensor)?

张量是TensorFlow中最基本的数据单位,可以理解为:

  • 0阶张量:就是一个数(标量) → 例如:5, 3.14
  • 1阶张量:就是一个列表(向量) → 例如:[1, 2, 3]
  • 2阶张量:就是一个表格(矩阵) → 例如:[[1,2], [3,4]]
  • 3阶张量:就是多个表格叠在一起 → 例如:图片的RGB三个通道

生活中的例子:

张量阶数 数学名称 生活中的例子
0阶 标量 一个人的年龄、体重
1阶 向量 班级所有学生的成绩列表
2阶 矩阵 Excel表格
3阶 3维张量 彩色图片(高度×宽度×RGB通道)

2.2 创建张量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import tensorflow as tf
import numpy as np

## 1. 从Python列表创建张量
a = tf.constant([1, 2, 3, 4])
print("a:", a)
print("a的形状:", a.shape)  ## 输出: (4,)

## 2. 创建2阶张量(矩阵)
b = tf.constant([[1, 2], [3, 4]])
print("b:", b)
print("b的形状:", b.shape)  ## 输出: (2, 2)

## 3. 创建全0张量
c = tf.zeros([3, 3])  ## 3行3列的全0矩阵
print("全0张量:\n", c)

## 4. 创建全1张量
d = tf.ones([2, 3])  ## 2行3列的全1矩阵
print("全1张量:\n", d)

## 5. 创建随机张量
e = tf.random.normal([2, 2])  ## 正态分布的随机数
print("随机张量:\n", e)

2.3 张量的重要属性

每个张量都有三个重要属性:

1
2
3
4
5
6
7
8
9
import tensorflow as tf

## 创建一个张量
tensor = tf.constant([[1, 2, 3], [4, 5, 6]])

print("张量内容:\n", tensor)
print("数据类型:", tensor.dtype)      ## 输出: <dtype: 'int32'>
print("形状:", tensor.shape)          ## 输出: (2, 3)
print("维度数:", tensor.ndim)         ## 输出: 2

2.3.1 形状(shape)

  • 用元组表示,每个数字代表该维度的大小
  • (2, 3) 表示2行3列
  • (28, 28) 表示28×28的矩阵(如手写数字图片)
  • (100, 28, 28, 3) 表示100张28×28的彩色图片(3个颜色通道)

2.3.2 数据类型(dtype)

数据类型 说明 示例
int32 32位整数 tf.constant([1,2,3])
float32 32位浮点数 tf.constant([1.0, 2.0])
string 字符串 tf.constant(“Hello”)
bool 布尔值 tf.constant([True, False])

2.3.3 维度数(ndim/rank)

  • 0维:标量
  • 1维:向量
  • 2维:矩阵
  • 3维及以上:高维张量

2.4 张量的常用操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import tensorflow as tf

## 创建两个张量
a = tf.constant([[1, 2], [3, 4]])
b = tf.constant([[5, 6], [7, 8]])

## 加法
c = a + b
print("加法结果:\n", c)

## 乘法(对应元素相乘)
d = a * b
print("对应元素相乘:\n", d)

## 矩阵乘法
e = tf.matmul(a, b)
print("矩阵乘法:\n", e)

## 改变形状
f = tf.reshape(a, [4, 1])  ## 变成4行1列
print("改变形状后:\n", f)

## 数据类型转换
g = tf.cast(a, dtype=tf.float32)
print("转换类型后:\n", g)

2.5 动手练习

练习1:创建以下张量

  1. 一个3×3的全1矩阵
  2. 一个包含数字1-9的3×3矩阵
  3. 一个形状为(2, 3, 4)的随机张量

练习2:对两个2×2矩阵进行加法和乘法运算


三、用TensorFlow 2训练一个线性模型

3.1 问题描述

我们有一份包含100个样本的数据,每个样本有:

  • x:输入数据(特征)
  • y:输出数据(目标值)

我们要找到一条直线 y = w*x + b 来拟合这些数据,其中:

  • w(权重):直线的斜率
  • b(偏置):直线的截距

现实意义

  • 预测房价:x是房子面积,y是房价
  • 预测销量:x是广告投入,y是产品销量
  • 预测成绩:x是学习时间,y是考试成绩

3.2 完整代码实现

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import tensorflow as tf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

## 设置中文字体(解决绘图中文乱码)
plt.rcParams['font.sans-serif'] = ['SimHei']  ## 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False    ## 用来正常显示负号

## 1. 加载数据
print("="*50)
print("1. 加载数据")
print("="*50)

## 读取CSV文件
data = pd.read_csv('../data/line_fit_data.csv')
print("数据前5行:")
print(data.head())
print("数据形状:", data.shape)

## 2. 数据预处理
print("\n" + "="*50)
print("2. 数据预处理")
print("="*50)

## 提取特征x和目标值y
x = data['x'].values
y = data['y'].values

## 划分训练集和测试集
## 前90个样本用于训练,后10个用于测试
x_train = x[:-10]
y_train = y[:-10]
x_test = x[-10:]
y_test = y[-10:]

print(f"训练集大小: {len(x_train)}")
print(f"测试集大小: {len(x_test)}")

## 3. 构建模型
print("\n" + "="*50)
print("3. 构建Sequential模型")
print("="*50)

## 创建Sequential模型(顺序模型,一层接一层)
model = tf.keras.models.Sequential()

## 添加全连接层(Dense层)
## 参数1: 1个神经元(输出维度)
## input_shape: 输入形状为(1,),即每个样本有1个特征
model.add(tf.keras.layers.Dense(1, input_shape=(1,)))

## 查看模型结构
print("模型结构:")
model.summary()

## 4. 编译模型
print("\n" + "="*50)
print("4. 编译模型")
print("="*50)

## 配置模型训练参数
## loss: 损失函数,用均方误差(MSE)衡量预测值与真实值的差距
## optimizer: 优化器,用随机梯度下降(SGD)更新参数
model.compile(
    loss='mse',  ## 均方误差
    optimizer=tf.keras.optimizers.SGD(learning_rate=0.5)  ## 学习率0.5
)
print("模型编译完成")

## 5. 训练模型
print("\n" + "="*50)
print("5. 训练模型")
print("="*50)

## 训练模型
## epochs: 训练轮数,每轮都会遍历整个训练集一次
## validation_split: 从训练集中分出20%作为验证集
history = model.fit(
    x_train, y_train,
    epochs=20,
    validation_split=0.2,
    verbose=1  ## 显示训练进度
)

## 6. 模型预测
print("\n" + "="*50)
print("6. 模型预测")
print("="*50)

## 对测试集进行预测
y_pred = model.predict(x_test)

## 显示预测结果
print("预测结果对比:")
print("真实值\t预测值")
for i in range(len(y_test)):
    print(f"{y_test[i]:.2f}\t{y_pred[i][0]:.2f}")

## 7. 模型评估
print("\n" + "="*50)
print("7. 模型评估")
print("="*50)

## 计算均方误差
mse = np.mean((y_test - y_pred.flatten())**2)
print(f"测试集均方误差: {mse:.4f}")

## 8. 可视化结果
print("\n" + "="*50)
print("8. 可视化结果")
print("="*50)

## 绘制训练过程中的损失变化
plt.figure(figsize=(12, 4))

## 子图1:损失曲线
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='训练损失')
plt.plot(history.history['val_loss'], label='验证损失')
plt.xlabel('训练轮数')
plt.ylabel('损失')
plt.title('训练过程中的损失变化')
plt.legend()
plt.grid(True)

## 子图2:拟合效果
plt.subplot(1, 2, 2)
plt.scatter(x_train, y_train, label='训练数据', alpha=0.5)
plt.scatter(x_test, y_test, label='测试数据', alpha=0.5, c='red')

## 绘制拟合直线
x_line = np.linspace(0, 1, 100)
y_line = model.predict(x_line)
plt.plot(x_line, y_line, 'g-', label='拟合直线', linewidth=2)

plt.xlabel('x')
plt.ylabel('y')
plt.title('线性模型拟合效果')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

## 9. 查看模型参数
print("\n" + "="*50)
print("9. 模型参数")
print("="*50)

## 获取模型的权重和偏置
weights = model.layers[0].get_weights()
w = weights[0][0][0]  ## 权重
b = weights[1][0]     ## 偏置

print(f"拟合得到的直线方程: y = {w:.4f} * x + {b:.4f}")

3.3 逐行详细解释

3.3.1 导入必要的库

1
2
3
4
import tensorflow as tf          ### 深度学习框架
import pandas as pd              ### 数据处理
import numpy as np               ### 数值计算
import matplotlib.pyplot as plt  ### 数据可视化
  • TensorFlow是谷歌开发的深度学习库,提供了构建和训练神经网络所需的所有工具

  • pandas用于数据处理和分析,特别是处理表格数据(如CSV文件)

  • NumPy提供了高性能的多维数组对象和数学函数

  • matplotlib是Python最常用的数据可视化库,plt是pyplot模块的常用别名,用于绘制图表、显示图片、可视化训练过程等

1
2
3
## 设置中文字体(解决绘图中文乱码)
plt.rcParams['font.sans-serif'] = ['SimHei']  ## 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False    ## 用来正常显示负号

作用:解决matplotlib绘图时中文显示乱码的问题。

  • rcParams是matplotlib的运行时配置参数
  • font.sans-serif设置无衬线字体为SimHei(黑体),这样中文就能正常显示
  • axes.unicode_minus设置为False,解决负号显示为方块的问题

3.3.2 加载数据

1
2
3
print("="*50)
print("1. 加载数据")
print("="*50)

作用:打印分隔线,让输出更清晰易读。

  • "="*50表示将字符串"="重复50次,形成一个分隔线
  • 这样在控制台输出时,每个步骤都有明显的分隔,便于查看
1
data = pd.read_csv('../data/line_fit_data.csv')

作用:使用pandas读取CSV文件。

  • read_csv()是pandas的函数,用于读取CSV格式的文件
  • '../data/line_fit_data.csv'是文件路径,..表示上级目录
  • 返回的data是一个DataFrame对象,类似于Excel表格,有行和列
  • CSV文件内容示例:
    1
    2
    3
    
    x,y
    0.8521031222662713,6.1302578056656785
    0.6309219899845755,5.5773049749614385
1
2
print("数据前5行:")
print(data.head())

作用:显示数据的前5行,快速查看数据格式。

  • head()是DataFrame的方法,默认显示前5行数据
  • 可以让我们确认数据是否正确加载,查看列名和数据格式
1
print("数据形状:", data.shape)

作用:显示数据的形状(行数和列数)。

  • shape是DataFrame的属性,返回一个元组(行数, 列数)
  • 例如(100, 2)表示有100行数据,2列(x和y)

3.3.3 数据预处理

1
2
x = data['x'].values
y = data['y'].values

作用:从DataFrame中提取特征x和目标值y。

  • data['x']选取名为’x’的列,返回一个Series对象
  • .values将Series转换为NumPy数组,便于后续计算
  • x是输入特征,y是要预测的目标值
1
2
3
4
5
6
## 划分训练集和测试集
## 前90个样本用于训练,后10个用于测试
x_train = x[:-10]
y_train = y[:-10]
x_test = x[-10:]
y_test = y[-10:]

作用:将数据集划分为训练集和测试集。

  • [:-10]是Python的切片语法,表示从开头到倒数第10个元素(不包括最后10个)
    • 例如:x[:-10]取x的前90个元素
  • [-10:]表示取最后10个元素
  • 训练集:用于训练模型,让模型学习数据规律
  • 测试集:用于评估模型性能,测试模型在未见过的数据上的表现
1
2
print(f"训练集大小: {len(x_train)}")
print(f"测试集大小: {len(x_test)}")

作用:打印训练集和测试集的大小。

  • f"..."是f-string格式化字符串,可以在字符串中直接嵌入变量
  • len(x_train)获取训练集样本数量

3.3.4 构建模型

1
model = tf.keras.models.Sequential()

作用:创建一个Sequential模型实例。

  • Sequential:顺序模型,一层接一层堆叠,像搭积木一样
  • tf.keras.models是TensorFlow中Keras API的模型模块
  • 此时model是一个空模型,还没有任何层
1
model.add(tf.keras.layers.Dense(1, input_shape=(1,)))

作用:向模型中添加一个全连接层(Dense层)。

参数详解

  • 第一个参数 1:输出维度,即本层神经元的数量
    • 这里设置为1,因为我们只需要预测一个数值y
  • input_shape=(1,):输入数据的形状
    • (1,)表示每个样本有1个特征(即x值)
    • 注意:input_shape只需要在模型的第一层指定,后续层会自动推导
  • tf.keras.layers.Dense:全连接层,每个神经元与上一层的所有神经元相连

数学原理

  • 这一层实现的运算:output = activation(w * input + b)
  • 由于没有指定激活函数(activation),默认使用线性激活函数
  • 所以实际上就是:y = w * x + b,这正是我们需要的线性模型
1
2
print("模型结构:")
model.summary()

作用:打印模型的详细结构信息。

  • summary()会显示:
    • 每一层的名称、类型、输出形状
    • 每一层的参数数量(需要学习的权重和偏置)
    • 模型的总参数数量
  • 帮助我们确认模型构建是否正确

3.3.5 编译模型

1
2
3
4
model.compile(
    loss='mse',
    optimizer=tf.keras.optimizers.SGD(learning_rate=0.5)
)

作用:配置模型的学习过程。

参数详解

  1. loss='mse':损失函数

    • MSE:Mean Squared Error(均方误差)
    • 公式:MSE = 1/n * Σ(y_true - y_pred)²
    • 作用:衡量模型预测值与真实值的差距
    • 值越小,表示预测越准确
    • 适合回归问题(预测连续值)
  2. optimizer=tf.keras.optimizers.SGD(learning_rate=0.5):优化器

    • SGD:Stochastic Gradient Descent(随机梯度下降)
    • 作用:根据损失函数的值,更新模型的权重和偏置
    • learning_rate=0.5:学习率
      • 控制每次参数更新的步长
      • 学习率太大:可能跳过最优解,训练不稳定
      • 学习率太小:训练速度慢,可能陷入局部最优
      • 0.5是一个相对较大的学习率,适合这个简单问题
1
print("模型编译完成")

作用:提示模型编译完成。

3.3.6 训练模型

1
2
3
4
5
6
history = model.fit(
    x_train, y_train,
    epochs=20,
    validation_split=0.2,
    verbose=1
)

作用:训练模型。

参数详解

  1. x_train, y_train:训练数据和对应的标签

    • x_train:输入特征(90个x值)
    • y_train:目标值(90个y值)
  2. epochs=20:训练轮数

    • 1个epoch表示使用全部训练数据训练一次
    • 这里训练20轮,即整个数据集被用来训练20次
    • 每轮结束后,模型参数都会更新
  3. validation_split=0.2:验证集比例

    • 从训练集中分出20%的数据作为验证集
    • 验证集不参与训练,只用于评估模型在训练过程中的表现
    • 这里90个训练样本中,72个用于训练,18个用于验证
  4. verbose=1:日志显示模式

    • 0:不显示训练进度
    • 1:显示进度条
    • 2:每个epoch显示一行

返回值

  • history:记录训练过程中的损失值和评估指标
  • 可以通过history.history查看训练损失和验证损失的变化

3.3.7 模型预测

1
y_pred = model.predict(x_test)

作用:使用训练好的模型对测试集进行预测。

  • predict()方法接收输入数据,返回模型的预测结果
  • x_test是10个测试样本
  • y_pred的形状是(10, 1),每行是一个样本的预测值
1
2
3
4
print("预测结果对比:")
print("真实值\t预测值")
for i in range(len(y_test)):
    print(f"{y_test[i]:.2f}\t{y_pred[i][0]:.2f}")

作用:打印真实值和预测值的对比。

  • range(len(y_test))生成0到9的索引
  • y_test[i]:第i个样本的真实值
  • y_pred[i][0]:第i个样本的预测值(因为预测结果是二维数组,取第一个元素)
  • :.2f:格式化输出,保留两位小数
  • \t:制表符,用于对齐输出

3.3.8 模型评估

1
mse = np.mean((y_test - y_pred.flatten())**2)

作用:计算测试集上的均方误差(MSE)。

逐步解析

  1. y_test - y_pred.flatten()
    • y_pred.flatten()将预测结果从(10,1)展平为(10,),使形状与y_test一致
    • 计算真实值与预测值的差(残差)
  2. (...)**2:对每个差值求平方
    • 消除正负号,放大较大的误差
  3. np.mean(...):计算所有平方差的平均值
    • 得到最终的均方误差
1
print(f"测试集均方误差: {mse:.4f}")

作用:打印均方误差,保留4位小数。

3.3.9 可视化结果

1
plt.figure(figsize=(12, 4))

作用:创建新图形,设置图形大小。

  • figsize=(12,4):图形宽度12英寸,高度4英寸
1
plt.subplot(1, 2, 1)

作用:创建子图,将图形划分为1行2列,选择第1个子图。

  • 参数(1, 2, 1)表示:1行2列,当前是第1个子图
1
2
plt.plot(history.history['loss'], label='训练损失', marker='o')
plt.plot(history.history['val_loss'], label='验证损失', marker='s')

作用:绘制损失曲线。

  • history.history['loss']:训练过程中的训练损失值列表
  • history.history['val_loss']:训练过程中的验证损失值列表
  • label:图例标签
  • marker='o':数据点用圆圈标记
  • marker='s':数据点用方块标记
1
2
3
4
5
plt.xlabel('训练轮数')
plt.ylabel('损失')
plt.title('训练过程中的损失变化')
plt.legend()
plt.grid(True)

作用:设置图表的标签、标题、图例和网格。

  • xlabel:x轴标签
  • ylabel:y轴标签
  • title:图表标题
  • legend():显示图例
  • grid(True):显示网格线
1
2
3
plt.subplot(1, 2, 2)
plt.scatter(x_train, y_train, label='训练数据', alpha=0.5)
plt.scatter(x_test, y_test, label='测试数据', alpha=0.5, c='red')

作用:绘制散点图,显示数据分布。

  • scatter():绘制散点图
  • alpha=0.5:设置透明度为0.5(0透明,1不透明)
  • c='red':设置测试数据点的颜色为红色
1
2
3
x_line = np.linspace(0, 1, 100)
y_line = model.predict(x_line)
plt.plot(x_line, y_line, 'g-', label='拟合直线', linewidth=2)

作用:绘制拟合的直线。

  • np.linspace(0, 1, 100):在0到1之间生成100个等间隔的点
  • model.predict(x_line):用模型预测这些点的y值
  • 'g-':绿色实线
  • linewidth=2:线宽为2
1
2
3
4
5
6
7
8
plt.xlabel('x')
plt.ylabel('y')
plt.title('线性模型拟合效果')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

作用:设置图表属性并显示。

  • tight_layout():自动调整子图参数,使布局更紧凑
  • show():显示图形

3.3.10 查看模型参数

1
weights = model.layers[0].get_weights()

作用:获取模型第一层(也是唯一一层)的权重参数。

  • model.layers[0]:获取模型的第一层(Dense层)
  • get_weights():返回该层的权重和偏置
  • 返回值是一个列表:[weights, biases]
1
2
w = weights[0][0][0]  ## 权重
b = weights[1][0]     ## 偏置

作用:提取权重和偏置的具体数值。

  • weights[0]:权重矩阵,形状为(1, 1)
    • weights[0][0][0]:取第一个(也是唯一一个)权重值
  • weights[1]:偏置向量,形状为(1,)
    • weights[1][0]:取第一个(也是唯一一个)偏置值
1
print(f"拟合得到的直线方程: y = {w:.4f} * x + {b:.4f}")

作用:打印拟合得到的直线方程。

  • w:.4f:权重保留4位小数
  • b:.4f:偏置保留4位小数
  • 例如输出:拟合得到的直线方程: y = 5.1234 * x + 4.5678

3.4 关键概念详细解释

3.4.1 模型(Model)

概念 通俗解释 代码对应
模型 一个数学公式,描述输入和输出的关系 Sequential()
参数 模型中需要学习的变量(w和b) get_weights()返回的值
前向传播 输入数据经过模型计算得到输出 model.predict()

3.4.2 层(Layer)

概念 通俗解释 代码对应
模型的基本组成单元 Dense()
Dense层 全连接层,每个神经元连接所有输入 layers.Dense()
神经元 层中的基本计算单元 Dense(1)中的1
激活函数 引入非线性,让模型学习复杂模式 activation参数

3.4.3 训练过程

概念 通俗解释 代码对应
损失函数 衡量预测值与真实值的差距 loss='mse'
优化器 决定如何更新参数 optimizer='sgd'
学习率 参数更新的步长 learning_rate=0.5
Epoch 使用全部数据训练一次 epochs=20
Batch 一次更新使用的样本数 batch_size(默认32)
训练集 用于训练模型的数据 x_train, y_train
验证集 用于调整超参数的数据 validation_split=0.2
测试集 用于最终评估的数据 x_test, y_test

3.4.4 评估指标

概念 公式 含义 代码对应
均方误差(MSE) 1/n * Σ(y - ŷ)² 预测误差的平方的平均值 loss='mse'
平均绝对误差(MAE) `1/n * Σ y - ŷ `

3.5 常见问题及解决方法

Q1: 损失值不下降怎么办?

可能原因

  • 学习率太小或太大
  • 网络结构不合适
  • 数据未正确归一化

解决方法

1
2
3
4
5
## 尝试调整学习率
model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=0.1))

## 或尝试其他优化器
model.compile(optimizer='adam', loss='mse')

Q2: 预测结果都是同一个值?

可能原因

  • 模型没有收敛
  • 数据有问题
  • 网络太简单

解决方法

  • 增加训练轮数
  • 检查数据是否有问题
  • 尝试增加网络层数

Q3: 训练速度太慢?

可能原因

  • 数据量太大
  • 网络太复杂
  • 学习率太小

解决方法

1
2
3
4
5
## 增大batch_size
model.fit(x_train, y_train, batch_size=64, epochs=20)

## 增大学习率
model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=0.8))

3.6 动手练习

练习1:调整训练轮数

修改代码,将训练轮数改为50轮,观察损失值的变化。回答以下问题:

  • 损失值是否继续下降?
  • 50轮后是否出现过拟合?

练习2:调整学习率

尝试不同的学习率(0.1、0.5、1.0),比较训练效果:

  • 哪个学习率收敛最快?
  • 哪个学习率最终损失最小?

练习3:尝试不同的优化器

将优化器改为Adam,比较SGD和Adam的区别:

1
model.compile(optimizer='adam', loss='mse')

四、深度学习通用流程

4.1 深度学习流程图

1
数据加载 → 数据预处理 → 构建网络 → 编译网络 → 训练网络 → 性能评估 → 模型保存与调用

4.1.1 为什么需要这个流程?

就像做饭的步骤:

  1. 买菜(数据加载)
  2. 洗菜切菜(数据预处理)
  3. 准备锅具(构建网络)
  4. 设置火候(编译网络)
  5. 炒菜(训练网络)
  6. 尝味道(性能评估)
  7. 保存菜谱(模型保存)

4.2 数据加载

TensorFlow提供了多种方式加载不同类型的数据:

4.2.1 加载自带数据集

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import tensorflow as tf
from tensorflow.keras import datasets

## 加载MNIST手写数字数据集
(x_train, y_train), (x_test, y_test) = datasets.mnist.load_data()

print("训练集图片形状:", x_train.shape)  ## (60000, 28, 28)
print("训练集标签形状:", y_train.shape)  ## (60000,)
print("测试集图片形状:", x_test.shape)   ## (10000, 28, 28)
print("测试集标签形状:", y_test.shape)   ## (10000,)

## 查看第一个样本
print("\n第一个样本:")
print("标签:", y_train[0])  ## 数字是几
print("图片数据:\n", x_train[0])  ## 28×28的像素值

4.2.2 加载CSV文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pandas as pd
import tensorflow as tf

## 用pandas读取CSV
df = pd.read_csv('../data/titanic_file.csv')
print("数据前3行:")
print(df.head(3))

## 转换为TensorFlow Dataset对象
dataset = tf.data.Dataset.from_tensor_slices(dict(df))

4.2.3 加载TFRecord文件

1
2
3
4
5
6
7
8
## 加载TFRecord文件(TensorFlow专用格式)
dataset = tf.data.TFRecordDataset(filenames=['../data/fsns.tfrec'])
print(dataset)

## 解析TFRecord数据
raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())
print(parsed.features.feature['image/text'])

4.2.4 加载文本文件

1
2
3
4
## 加载文本文件,每行一个样本
dataset = tf.data.TextLineDataset('../data/cowper.txt')
for line in dataset.take(5):
    print(line.numpy())

4.2.5 加载图片文件集

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pathlib
import random

## 获取所有图片路径
data_path = pathlib.Path('../data/flower_photos')
all_image_paths = list(data_path.glob('*/*'))
all_image_paths = [str(path) for path in all_image_paths]
random.shuffle(all_image_paths)

## 获取分类名
label_names = sorted([item.name for item in data_path.glob('*/') if item.is_dir()])
print('分类名', label_names)

## 创建标签映射
label_to_index = {name: i for i, name in enumerate(label_names)}

## 为每张图片创建标签
all_image_labels = [label_to_index[pathlib.Path(path).parent.name] 
                    for path in all_image_paths]

## 创建Dataset
ds = tf.data.Dataset.from_tensor_slices((all_image_paths, all_image_labels))

4.3 数据预处理

4.3.1 图片数据预处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import matplotlib.pyplot as plt

def preprocess_image(filename):
    ## 读取图片
    image = tf.io.read_file(filename)
    ## 解码JPEG
    image = tf.image.decode_jpeg(image, channels=3)
    ## 转换为float32并归一化
    image = tf.image.convert_image_dtype(image, tf.float32)
    ## 调整大小
    image = tf.image.resize(image, [128, 128])
    return image

## 应用预处理
images_ds = list_ds.map(preprocess_image)

4.3.2 MNIST数据预处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
## 随机打乱数据
train = train.shuffle(10000)

## 定义预处理函数
def train_preprocess(x, y):
    ## 归一化到0-1
    x = tf.cast(x, dtype=tf.float32) / 255.
    ## 展平为784维向量
    x = tf.reshape(x, [-1, 28*28])
    ## one-hot编码标签
    y = tf.one_hot(y, depth=10)
    return x, y

## 应用预处理
train = train.map(train_preprocess)

4.3.3 文本数据预处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import tensorflow_datasets as tfds

## 分词器
tokenizer = tfds.deprecated.text.Tokenizer()
vocabulary_set = set()

## 构建词汇表
for text_tensor in cowper:
    tokens = tokenizer.tokenize(text_tensor.numpy())
    vocabulary_set.update(tokens)

## 创建编码器
encoder = tfds.deprecated.text.TokenTextEncoder(vocabulary_set)

## 编码文本
example_text = next(iter(cowper)).numpy()
encoded = encoder.encode(example_text)
print('编码结果:', encoded)

4.4 数据预处理的常用操作

操作 作用 代码示例
归一化 将数据缩放到0-1 image / 255.0
打乱 随机打乱顺序 .shuffle(buffer_size)
分批 将数据分成小批量 .batch(batch_size)
one-hot编码 将类别转换为向量 tf.one_hot(label, depth)
调整大小 改变图片尺寸 tf.image.resize()

4.5 动手练习

练习1:加载Fashion-MNIST数据集,查看数据形状

练习2:对MNIST数据做归一化和分批处理

练习3:加载自己的图片文件夹,创建Dataset对象


五、项目一:MNIST手写数字识别

5.1 项目概述

5.1.1 项目目标

使用TensorFlow 2构建一个神经网络模型,对MNIST手写数字图片进行分类,识别0-9十个数字。

5.1.2 数据集说明

  • 训练集:60,000张28×28的灰度图片
  • 测试集:10,000张28×28的灰度图片
  • 标签:0-9的数字
  • 数据格式:mnist.npz文件(包含x_train, y_train, x_test, y_test)

5.1.3 项目流程

1
加载数据 → 查看数据 → 数据预处理 → 构建模型 → 编译模型 → 训练模型 → 评估模型 → 保存模型 → 预测新样本

5.2 完整项目代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
"""
项目一:MNIST手写数字识别
功能:训练一个神经网络模型,识别手写数字0-9
作者:深度学习课程
日期:2026
"""

## ==================== 导入必要的库 ====================
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import os
import pathlib

## 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  ## 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False    ## 用来正常显示负号

print("=" * 60)
print("项目一:MNIST手写数字识别")
print("=" * 60)

## ==================== 1. 加载数据 ====================
print("\n【第1步】加载MNIST数据集")
print("-" * 40)

## 方法一:使用Keras自带的数据集(推荐,最简单)
## (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

## 方法二:从本地npz文件加载(对应课件中的数据)
data = np.load('../data/mnist.npz')
print("数据文件中的内容:", data.files)

## 从npz文件中提取数据
x_train = data['x_train']
y_train = data['y_train']
x_test = data['x_test']
y_test = data['y_test']

print(f"训练集图片形状: {x_train.shape}")  ## (60000, 28, 28)
print(f"训练集标签形状: {y_train.shape}")  ## (60000,)
print(f"测试集图片形状: {x_test.shape}")   ## (10000, 28, 28)
print(f"测试集标签形状: {y_test.shape}")   ## (10000,)

## ==================== 2. 查看数据 ====================
print("\n【第2步】查看数据样例")
print("-" * 40)

## 查看第一个样本
print("第一个样本的标签:", y_train[0])
print("第一个样本的图片数据(部分):")
print(x_train[0][:5, :5])  ## 只显示前5行前5列

## 显示前9张图片
plt.figure(figsize=(10, 10))
for i in range(9):
    plt.subplot(3, 3, i + 1)
    plt.imshow(x_train[i], cmap='gray')
    plt.title(f'标签: {y_train[i]}')
    plt.axis('off')
plt.suptitle('MNIST训练集样本示例')
plt.tight_layout()
plt.show()

## ==================== 3. 数据预处理 ====================
print("\n【第3步】数据预处理")
print("-" * 40)

## 3.1 转换为Dataset对象
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
print("已转换为Dataset对象")


## 3.2 定义预处理函数
def preprocess_train(image, label):
    """
    训练集预处理函数
    参数:
        image: 输入图片,形状(28, 28)
        label: 标签,0-9的整数
    返回:
        处理后的图片和标签
    """
    ## 归一化:将像素值从0-255缩放到0-1
    image = tf.cast(image, tf.float32) / 255.0

    ## 将28×28的图片展平为784维的向量
    image = tf.reshape(image, [-1, 28 * 28])

    ## 标签转换为one-hot编码
    ## 例如:3 -> [0,0,0,1,0,0,0,0,0,0]
    label = tf.one_hot(label, depth=10)

    return image, label


def preprocess_test(image, label):
    """
    测试集预处理函数(不需要one-hot编码)
    """
    image = tf.cast(image, tf.float32) / 255.0
    image = tf.reshape(image, [-1, 28 * 28])
    return image, label


## 3.3 应用预处理
train_dataset = train_dataset.map(preprocess_train)
test_dataset = test_dataset.map(preprocess_test)
print("已完成归一化和reshape")

## 3.4 数据增强操作
BUFFER_SIZE = 10000  ## 打乱缓冲区大小
BATCH_SIZE = 128     ## 批处理大小

train_dataset = train_dataset.shuffle(BUFFER_SIZE)  ## 随机打乱
train_dataset = train_dataset.batch(BATCH_SIZE)     ## 分批
test_dataset = test_dataset.batch(BATCH_SIZE)       ## 测试集也分批

## 预取数据(提高性能)
train_dataset = train_dataset.prefetch(tf.data.AUTOTUNE)
test_dataset = test_dataset.prefetch(tf.data.AUTOTUNE)

print(f"批处理大小: {BATCH_SIZE}")
print("数据预处理完成")

## 查看预处理后的数据形状
for images, labels in train_dataset.take(1):
    print(f"\n预处理后的一批数据:")
    print(f"  图片形状: {images.shape}")  ## (128, 784)
    print(f"  标签形状: {labels.shape}")  ## (128, 10)
    print(f"  第一个标签的one-hot编码: {labels[0].numpy()}")

## ==================== 修复问题:检查数据形状 ====================
print("\n【调试】检查数据形状")
print("-" * 40)

## 检查原始数据形状
print(f"原始训练数据形状: {x_train.shape}")
print(f"原始标签形状: {y_train.shape}")

## 检查Dataset中的数据形状
for images, labels in train_dataset.take(1):
    print(f"Dataset中的图片形状: {images.shape}")
    print(f"Dataset中的标签形状: {labels.shape}")

## ==================== 4. 构建模型 ====================
print("\n【第4步】构建神经网络模型")
print("-" * 40)

## 修复:使用英文层名,避免中文导致错误
model = tf.keras.Sequential([
    ## 输入层:将28×28的图片展平
    ## 注意:这里不需要命名,或者使用英文命名
    tf.keras.layers.Flatten(input_shape=(28, 28), name='flatten_layer'),

    ## 第一个隐藏层:128个神经元,ReLU激活函数
    tf.keras.layers.Dense(128, activation='relu', name='hidden_layer_1'),

    ## 第二个隐藏层:64个神经元,ReLU激活函数
    tf.keras.layers.Dense(64, activation='relu', name='hidden_layer_2'),

    ## 输出层:10个神经元(对应0-9),softmax激活函数
    tf.keras.layers.Dense(10, activation='softmax', name='output_layer')
])

## 查看模型结构
print("\n模型结构:")
model.summary()

## 查看每一层的参数数量
print("\n各层参数详情:")
total_params = 0
for i, layer in enumerate(model.layers):
    weights = layer.get_weights()
    if weights:  ## 如果有参数(不是Flatten层)
        w = weights[0]  ## 权重矩阵
        b = weights[1]  ## 偏置向量
        layer_params = w.size + b.size
        total_params += layer_params
        print(f"{layer.name}:")
        print(f"  权重矩阵形状: {w.shape}")
        print(f"  偏置向量形状: {b.shape}")
        print(f"  参数数量: {layer_params}")
print(f"总参数数量: {total_params}")

## ==================== 5. 编译模型 ====================
print("\n【第5步】编译模型")
print("-" * 40)

model.compile(
    optimizer='adam',                          ## 优化器:Adam
    loss='categorical_crossentropy',           ## 损失函数:分类交叉熵
    metrics=['accuracy']                        ## 评估指标:准确率
)

print("编译设置:")
print(f"  优化器: Adam")
print(f"  损失函数: 分类交叉熵")
print(f"  评估指标: 准确率")

## ==================== 6. 训练模型 ====================
print("\n【第6步】训练模型")
print("-" * 40)

EPOCHS = 10  ## 训练轮数

## 修复:使用更简单的训练方式,避免Dataset可能的问题
## 方法1:直接使用numpy数组训练(更简单可靠)
print("使用numpy数组直接训练...")

## 预处理数据为numpy数组格式
## x_train_flat = x_train.reshape(-1, 28*28) / 255.0
## x_test_flat = x_test.reshape(-1, 28*28) / 255.0
x_train_flat = x_train / 255.0
x_test_flat = x_test / 255.0
y_train_onehot = tf.keras.utils.to_categorical(y_train, 10)
y_test_onehot = tf.keras.utils.to_categorical(y_test, 10)

print(f"处理后训练数据形状: {x_train_flat.shape}")
print(f"处理后训练标签形状: {y_train_onehot.shape}")

history = model.fit(
    x_train_flat, y_train_onehot,               ## 训练数据
    batch_size=BATCH_SIZE,                       ## 批处理大小
    epochs=EPOCHS,                                ## 训练轮数
    validation_data=(x_test_flat, y_test_onehot), ## 验证数据
    verbose=1                                      ## 显示进度条
)

print("\n训练完成!")

## ==================== 7. 评估模型 ====================
print("\n【第7步】评估模型")
print("-" * 40)

## 在测试集上评估
test_loss, test_acc = model.evaluate(x_test_flat, y_test_onehot, verbose=0)
print(f"测试集损失: {test_loss:.4f}")
print(f"测试集准确率: {test_acc:.4f}")
print(f"测试集准确率: {test_acc * 100:.2f}%")

## ==================== 8. 可视化训练过程 ====================
print("\n【第8步】可视化训练过程")
print("-" * 40)

plt.figure(figsize=(14, 5))

## 绘制损失曲线
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='训练损失', marker='o')
plt.plot(history.history['val_loss'], label='验证损失', marker='s')
plt.xlabel('训练轮数')
plt.ylabel('损失')
plt.title('损失曲线')
plt.legend()
plt.grid(True)

## 绘制准确率曲线
plt.subplot(1, 2, 2)
plt.plot(history.history['accuracy'], label='训练准确率', marker='o')
plt.plot(history.history['val_accuracy'], label='验证准确率', marker='s')
plt.xlabel('训练轮数')
plt.ylabel('准确率')
plt.title('准确率曲线')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

## ==================== 9. 保存模型 ====================
print("\n【第9步】保存模型")
print("-" * 40)

## 创建保存目录
save_dir = '../tmp/models'
os.makedirs(save_dir, exist_ok=True)

## 9.1 保存整个模型(推荐)
model_path = os.path.join(save_dir, 'mnist_model.h5')
model.save(model_path)
print(f"模型已保存为: {model_path}")

## 9.2 保存模型权重
## weights_path = os.path.join(save_dir, 'mnist_weights.h5')
weights_path = os.path.join(save_dir, 'mnist.weights.h5')
model.save_weights(weights_path)
print(f"模型权重已保存为: {weights_path}")

## ==================== 10. 加载模型并预测新样本 ====================
print("\n【第10步】加载模型并预测新样本")
print("-" * 40)

## 10.1 加载保存的模型
loaded_model = tf.keras.models.load_model(model_path)
print("已加载保存的模型")

## 10.2 从testimages文件夹读取新样本进行预测
test_images_dir = '../data/testimages'
if os.path.exists(test_images_dir):
    ## 获取所有jpg图片
    image_files = sorted(pathlib.Path(test_images_dir).glob('*.jpg'))

    print(f"\n找到{len(image_files)}张测试图片")

    plt.figure(figsize=(15, 10))

    for i, img_path in enumerate(image_files[:30]):  ## 最多显示30张
        ## 读取图片
        img = plt.imread(str(img_path))

        ## 如果是彩色图,转为灰度图
        if len(img.shape) == 3:
            img = np.mean(img, axis=2)

        ## 预处理:归一化并调整形状
        img_normalized = img / 255.0
        img_reshaped = img_normalized.reshape(1, 28, 28)

        ## 预测
        pred = loaded_model.predict(img_reshaped, verbose=0)
        pred_class = np.argmax(pred)
        confidence = np.max(pred)

        ## 显示结果
        plt.subplot(5, 6, i + 1)
        plt.imshow(img, cmap='gray')
        plt.title(f'预测:{pred_class}\n({confidence:.2f})', fontsize=8)
        plt.axis('off')

    plt.suptitle('新样本预测结果')
    plt.tight_layout()
    plt.show()

    ## 打印前10个预测结果
    print("\n前10个预测结果:")
    for i, img_path in enumerate(image_files[:10]):
        img = plt.imread(str(img_path))
        if len(img.shape) == 3:
            img = np.mean(img, axis=2)
        img_normalized = img / 255.0
        img_reshaped = img_normalized.reshape(1, 28, 28)
        pred = loaded_model.predict(img_reshaped, verbose=0)
        pred_class = np.argmax(pred)
        confidence = np.max(pred)
        print(f"  图片{i}: 预测数字={pred_class}, 置信度={confidence:.2f}")
else:
    print("未找到testimages文件夹,跳过新样本预测")

    ## 使用测试集进行演示预测
    print("\n使用测试集进行演示预测:")
    for i in range(10):
        img = x_test[i].reshape(1, 28, 28) / 255.0
        pred = loaded_model.predict(img, verbose=0)
        pred_class = np.argmax(pred)
        confidence = np.max(pred)
        print(f"  测试样本{i}: 真实={y_test[i]}, 预测={pred_class}, 置信度={confidence:.2f}")

## ==================== 11. 分析错误样本 ====================
print("\n【第11步】分析错误样本")
print("-" * 40)

## 对整个测试集进行预测
predictions = model.predict(x_test_flat)
predicted_classes = np.argmax(predictions, axis=1)

## 找出预测错误的样本
errors = np.where(predicted_classes != y_test)[0]
print(f"测试集总样本数: {len(y_test)}")
print(f"预测错误的样本数: {len(errors)}")
print(f"错误率: {len(errors) / len(y_test) * 100:.2f}%")

## 显示前9个错误样本
if len(errors) > 0:
    plt.figure(figsize=(12, 12))
    for i in range(min(9, len(errors))):
        idx = errors[i]
        plt.subplot(3, 3, i + 1)
        plt.imshow(x_test[idx], cmap='gray')
        plt.title(f'真实:{y_test[idx]}, 预测:{predicted_classes[idx]}',
                  color='red')
        plt.axis('off')
    plt.suptitle('错误分类样本示例')
    plt.tight_layout()
    plt.show()

print("\n" + "=" * 60)
print("MNIST手写数字识别 项目完成!")
print("=" * 60)

5.3 代码逐行解释

5.3.1 导入库

1
2
3
4
5
import tensorflow as tf          ## TensorFlow深度学习框架
import numpy as np               ## 数值计算库
import matplotlib.pyplot as plt  ## 数据可视化库
import os                        ## 文件和目录操作
import pathlib                   ## 路径操作

5.3.2 加载数据

1
data = np.load('../data/mnist.npz')
  • np.load():加载NumPy的.npz格式文件
  • data.files:查看文件中包含的所有数据键名
  • 从文件中提取训练集和测试集的图片和标签

5.3.3 数据预处理

1
2
3
4
5
def preprocess_train(image, label):
    image = tf.cast(image, tf.float32) / 255.0  ## 归一化
    image = tf.reshape(image, [-1, 28*28])      ## 展平
    label = tf.one_hot(label, depth=10)         ## one-hot编码
    return image, label
  • 归一化:将0-255的像素值缩放到0-1,有利于模型训练
  • 展平:将28×28的二维图片变成784的一维向量
  • one-hot编码:将数字标签转换为向量形式
    • 例如:3 → [0,0,0,1,0,0,0,0,0,0]

5.3.4 构建模型

1
2
3
4
5
6
model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])
  • Flatten层:将28×28的输入展平为784
  • Dense层:全连接层
    • 128/64:神经元数量
    • activation=‘relu’:ReLU激活函数
    • activation=‘softmax’:输出概率分布

5.3.5 编译模型

1
2
3
4
5
model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)
  • optimizer:优化器,决定如何更新参数
  • loss:损失函数,衡量预测值与真实值的差距
  • metrics:评估指标,监控模型性能

5.3.6 训练模型

1
2
3
4
5
history = model.fit(
    train_dataset,
    epochs=10,
    validation_data=test_dataset
)
  • epochs:训练轮数
  • validation_data:验证数据
  • history:记录训练过程中的损失和准确率

5.3.7 预测新样本

1
2
3
4
5
img = plt.imread(str(img_path))                 ## 读取图片
img_normalized = img / 255.0                     ## 归一化
img_reshaped = img_normalized.reshape(1, 28, 28) ## 调整形状
pred = model.predict(img_reshaped)               ## 预测
pred_class = np.argmax(pred)                     ## 取概率最大的类别

六、项目二:TFRecord数据处理与模型应用

6.1 项目概述

6.1.1 项目目标

学习如何处理TFRecord格式的数据,并应用训练好的模型进行预测。

6.1.2 数据集说明

  • fsns.tfrec:TFRecord格式的数据文件
    • TFRecord是TensorFlow专用的二进制数据格式
    • 包含图片和对应的文本标签
  • testimages:30张手写数字测试图片

6.1.3 项目流程

1
了解TFRecord格式 → 解析TFRecord数据 → 查看数据内容 → 加载训练好的模型 → 预测新样本

6.2 完整项目代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
"""
项目二:TFRecord数据处理与模型应用
功能:学习处理TFRecord格式数据,并用模型预测新样本
作者:深度学习课程
日期:2024
"""

## ==================== 导入必要的库 ====================
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import os
import pathlib

## 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("="*60)
print("项目二:TFRecord数据处理与模型应用")
print("="*60)

## ==================== 1. 了解TFRecord格式 ====================
print("\n【第1步】了解TFRecord格式")
print("-"*40)

print("什么是TFRecord?")
print("  TFRecord是TensorFlow专用的二进制数据格式,特点:")
print("  - 占用空间小")
print("  - 读写速度快")
print("  - 适合大数据集")
print("  - 可以包含不同类型的数据(图片、文本、标签等)")

## ==================== 2. 加载TFRecord文件 ====================
print("\n【第2步】加载TFRecord文件")
print("-"*40)

## TFRecord文件路径
tfrecord_path = '../data/fsns.tfrec'

if os.path.exists(tfrecord_path):
    ## 创建TFRecordDataset
    dataset = tf.data.TFRecordDataset(filenames=[tfrecord_path])
    print(f"成功加载TFRecord文件: {tfrecord_path}")
    print(f"Dataset类型: {type(dataset)}")
    
    ## 查看数据集信息
    print(f"数据集元素: {dataset}")
else:
    print(f"文件不存在: {tfrecord_path}")
    print("将使用模拟数据演示TFRecord解析")
    ## 创建模拟数据用于演示
    dataset = None

## ==================== 3. 解析TFRecord数据 ====================
print("\n【第3步】解析TFRecord数据")
print("-"*40)

def parse_tfrecord(example_proto):
    """
    解析TFRecord样本的函数
    
    TFRecord中的数据以键值对形式存储,需要定义特征描述
    """
    ## 定义特征描述(根据实际数据格式)
    feature_description = {
        'image/encoded': tf.io.FixedLenFeature([], tf.string),      ## 编码后的图片
        'image/format': tf.io.FixedLenFeature([], tf.string),       ## 图片格式
        'image/height': tf.io.FixedLenFeature([], tf.int64),        ## 图片高度
        'image/width': tf.io.FixedLenFeature([], tf.int64),         ## 图片宽度
        'image/colorspace': tf.io.FixedLenFeature([], tf.string),   ## 色彩空间
        'image/text': tf.io.FixedLenFeature([], tf.string),         ## 文本标签
    }
    
    ## 解析样本
    example = tf.io.parse_single_example(example_proto, feature_description)
    
    ## 解码图片
    image = tf.image.decode_jpeg(example['image/encoded'], channels=3)
    
    ## 解码文本(bytes转字符串)
    text = tf.strings.to_number(example['image/text'], tf.int32)
    
    return image, text, example

## 如果存在真实数据,进行解析
if dataset is not None:
    ## 应用解析函数
    parsed_dataset = dataset.map(parse_tfrecord)
    
    ## 查看解析后的数据
    print("正在解析TFRecord数据...")
    for image, text, features in parsed_dataset.take(1):
        print(f"\n解析成功!")
        print(f"图片形状: {image.shape}")
        print(f"图片数据类型: {image.dtype}")
        print(f"图片值范围: [{tf.reduce_min(image)}, {tf.reduce_max(image)}]")
        print(f"文本标签: {text.numpy()}")
        print(f"图片格式: {features['image/format'].numpy().decode()}")
        print(f"图片高度: {features['image/height'].numpy()}")
        print(f"图片宽度: {features['image/width'].numpy()}")
        print(f"色彩空间: {features['image/colorspace'].numpy().decode()}")
        
        ## 显示图片
        plt.figure(figsize=(8, 4))
        plt.subplot(1, 2, 1)
        plt.imshow(image)
        plt.title(f'标签: {text.numpy()}')
        plt.axis('off')
        
        plt.subplot(1, 2, 2)
        plt.imshow(image[:, :, 0], cmap='gray')
        plt.title('灰度通道')
        plt.axis('off')
        plt.tight_layout()
        plt.show()

## ==================== 4. 手动解析TFRecord(了解内部结构) ====================
print("\n【第4步】深入了解TFRecord内部结构")
print("-"*40)

if dataset is not None:
    ## 获取原始数据
    raw_record = next(iter(dataset))
    
    ## 手动解析
    example = tf.train.Example()
    example.ParseFromString(raw_record.numpy())
    
    print("TFRecord内部结构:")
    print(example)
    
    ## 查看所有特征
    print("\n所有特征名称:")
    features = example.features.feature
    for key in features.keys():
        print(f"  - {key}")
    
    ## 查看'image/text'特征
    text_feature = features['image/text']
    print(f"\n'image/text'特征类型: {text_feature.WhichOneof('kind')}")
    print(f"值: {text_feature.int64_list.value}")

## ==================== 5. 创建TFRecord文件(演示) ====================
print("\n【第5步】创建TFRecord文件(演示)")
print("-"*40)

def create_tfrecord_demo():
    """
    演示如何创建TFRecord文件
    """
    ## 创建示例数据
    images = [np.random.randint(0, 255, (28, 28, 3), dtype=np.uint8) for _ in range(5)]
    labels = [0, 1, 2, 3, 4]
    
    ## 创建TFRecord文件
    with tf.io.TFRecordWriter('../tmp/demo.tfrecord') as writer:
        for img, label in zip(images, labels):
            ## 将图片编码为JPEG
            img_encoded = tf.io.encode_jpeg(img).numpy()
            
            ## 创建Example
            example = tf.train.Example(features=tf.train.Features(feature={
                'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_encoded])),
                'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
            }))
            
            ## 写入文件
            writer.write(example.SerializeToString())
    
    print("演示TFRecord文件已创建: ../tmp/demo.tfrecord")
    print("包含5个样本,每个样本有图片和标签")

create_tfrecord_demo()

## ==================== 6. 加载训练好的MNIST模型 ====================
print("\n【第6步】加载训练好的MNIST模型")
print("-"*40)

model_path = '../tmp/models/mnist_model.h5'

if os.path.exists(model_path):
    ## 加载模型
    model = tf.keras.models.load_model(model_path)
    print(f"成功加载模型: {model_path}")
    model.summary()
else:
    print(f"模型文件不存在: {model_path}")
    print("将训练一个简单模型用于演示")
    
    ## 训练一个简单模型
    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
    x_train, x_test = x_train / 255.0, x_test / 255.0
    
    model = tf.keras.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28)),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dense(10, activation='softmax')
    ])
    
    model.compile(optimizer='adam',
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    
    model.fit(x_train, y_train, epochs=3, verbose=0)
    print("简单模型训练完成")

## ==================== 7. 预测testimages中的新样本 ====================
print("\n【第7步】预测testimages中的新样本")
print("-"*40)

test_images_dir = '../data/testimages'

if os.path.exists(test_images_dir):
    ## 获取所有jpg图片
    image_files = sorted(pathlib.Path(test_images_dir).glob('*.jpg'))
    
    print(f"找到{len(image_files)}张测试图片")
    
    ## 创建结果表格
    print("\n" + "="*50)
    print("序号 | 文件名 | 预测结果 | 置信度")
    print("-"*50)
    
    predictions = []
    
    for i, img_path in enumerate(image_files):
        ## 读取图片
        img = plt.imread(str(img_path))
        
        ## 预处理:归一化并调整形状
        if img.max() > 1.0:  ## 如果像素值是0-255
            img_normalized = img / 255.0
        else:
            img_normalized = img
        
        img_reshaped = img_normalized.reshape(1, 28, 28)
        
        ## 预测
        pred = model.predict(img_reshaped, verbose=0)
        pred_class = np.argmax(pred)
        confidence = np.max(pred)
        
        ## 保存结果
        predictions.append({
            'file': img_path.name,
            'prediction': pred_class,
            'confidence': confidence
        })
        
        ## 打印结果
        print(f"{i:3d} | {img_path.name:10s} | {pred_class:3d} | {confidence:.4f}")
    
    print("="*50)
    
    ## 统计结果
    print("\n预测结果统计:")
    pred_counts = {}
    for p in predictions:
        pred_counts[p['prediction']] = pred_counts.get(p['prediction'], 0) + 1
    
    for digit in range(10):
        count = pred_counts.get(digit, 0)
        if count > 0:
            print(f"  数字{digit}: {count}张 ({count/len(predictions)*100:.1f}%)")
    
    ## 可视化预测结果
    plt.figure(figsize=(15, 10))
    
    for i, img_path in enumerate(image_files[:30]):  ## 最多显示30张
        plt.subplot(5, 6, i+1)
        
        ## 读取并显示图片
        img = plt.imread(str(img_path))
        plt.imshow(img, cmap='gray')
        
        ## 显示预测结果
        pred = predictions[i]['prediction']
        conf = predictions[i]['confidence']
        color = 'green' if conf > 0.8 else 'orange' if conf > 0.6 else 'red'
        plt.title(f'{pred}({conf:.2f})', color=color, fontsize=8)
        plt.axis('off')
    
    plt.suptitle('testimages预测结果', fontsize=14)
    plt.tight_layout()
    plt.show()
    
    ## 保存预测结果到文件
    result_file = '../tmp/prediction_results.txt'
    with open(result_file, 'w', encoding='utf-8') as f:
        f.write("文件名\t预测结果\t置信度\n")
        for p in predictions:
            f.write(f"{p['file']}\t{p['prediction']}\t{p['confidence']:.4f}\n")
    print(f"\n预测结果已保存到: {result_file}")
    
else:
    print(f"未找到testimages文件夹: {test_images_dir}")
    print("将使用MNIST测试集进行预测演示")

    ## 使用MNIST测试集
    (_, _), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
    x_test = x_test / 255.0
    
    ## 随机选择10个样本
    indices = np.random.choice(len(x_test), 10, replace=False)
    
    print("\n" + "="*50)
    print("序号 | 真实值 | 预测值 | 置信度 | 是否正确")
    print("-"*50)
    
    for i, idx in enumerate(indices):
        img = x_test[idx].reshape(1, 28, 28)
        pred = model.predict(img, verbose=0)
        pred_class = np.argmax(pred)
        confidence = np.max(pred)
        correct = pred_class == y_test[idx]
        
        mark = "✓" if correct else "✗"
        print(f"{i:3d} | {y_test[idx]:5d} | {pred_class:5d} | {confidence:.4f} | {mark}")
    
    print("="*50)

## ==================== 8. TFRecord与普通数据格式对比 ====================
print("\n【第8步】TFRecord与普通数据格式对比")
print("-"*40)

print("""
TFRecord的优点:
1. 存储效率高:二进制格式,占用空间小
2. 读写速度快:适合大数据集的流式读取
3. 与TensorFlow无缝集成:支持多线程、预取等优化
4. 可以包含多种数据类型:图片、文本、数值等

TFRecord的缺点:
1. 可读性差:不能直接用文本编辑器查看
2. 需要解析:使用前需要定义特征描述
3. 创建复杂:需要将原始数据转换为Example格式

普通数据格式(如图片文件):
优点:直观、易于查看和调试
缺点:大量小文件读写慢,占用空间大
""")

print("\n" + "="*60)
print("项目二完成!")
print("="*60)

6.3 代码逐行解释

6.3.1 TFRecord解析

1
2
3
4
5
6
7
8
def parse_tfrecord(example_proto):
    feature_description = {
        'image/encoded': tf.io.FixedLenFeature([], tf.string),
        'image/text': tf.io.FixedLenFeature([], tf.string),
    }
    example = tf.io.parse_single_example(example_proto, feature_description)
    image = tf.image.decode_jpeg(example['image/encoded'], channels=3)
    return image, text
  • FixedLenFeature:定长特征,指定数据类型
  • parse_single_example:解析单个TFRecord样本
  • decode_jpeg:解码JPEG格式的图片

6.3.2 创建TFRecord

1
2
3
4
5
6
with tf.io.TFRecordWriter('data.tfrecord') as writer:
    example = tf.train.Example(features=tf.train.Features(feature={
        'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_bytes])),
        'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
    }))
    writer.write(example.SerializeToString())
  • TFRecordWriter:创建TFRecord写入器
  • Example:TFRecord中的数据单元
  • SerializeToString:序列化为二进制字符串

6.3.3 模型预测

1
2
3
4
5
6
img = plt.imread('test.jpg')              ## 读取图片
img_normalized = img / 255.0               ## 归一化
img_reshaped = img_normalized.reshape(1, 28, 28)  ## 调整形状
pred = model.predict(img_reshaped)         ## 预测
pred_class = np.argmax(pred)               ## 取最大概率的类别
confidence = np.max(pred)                   ## 取最大概率作为置信度

附录:常见问题解答

Q1: 安装TensorFlow时出现错误怎么办?

A: 尝试以下解决方案:

  1. 使用国内镜像源:pip install tensorflow -i https://pypi.tuna.tsinghua.edu.cn/simple
  2. 更新pip:python -m pip install --upgrade pip
  3. 创建虚拟环境安装

Q2: 训练时内存不足怎么办?

A: 减小batch_size,例如从128改为64或32

Q3: 模型准确率不高怎么办?

A: 尝试:

  • 增加训练轮数
  • 增加网络层数或神经元数量
  • 调整学习率
  • 检查数据预处理是否正确

Q4: 加载mnist.npz时找不到文件怎么办?

A: 确保文件路径正确,或者使用tf.keras.datasets.mnist.load_data()直接下载

Q5: TFRecord解析出错怎么办?

A: 检查特征描述是否与数据格式匹配,可以使用tf.train.Example.FromString()查看原始数据


教学总结

本章重点

  1. TensorFlow环境搭建
  2. 张量的概念和操作
  3. 线性回归模型
  4. 深度学习通用流程
  5. 多种数据格式的加载(CSV、TFRecord、图片)
  6. 项目一:MNIST手写数字识别
  7. 项目二:TFRecord数据处理与应用

能力目标

学完本章后,学生应该能够:

  • 独立安装TensorFlow
  • 理解张量的基本概念
  • 使用TensorFlow构建分类模型
  • 处理不同格式的数据(CSV、TFRecord、图片)
  • 完成完整项目开发
  • 保存和加载模型
  • 对新样本进行预测

项目练习建议

  1. 修改MNIST项目的网络结构,观察准确率变化
  2. 尝试用Fashion-MNIST数据集训练模型
  3. 自己收集手写数字图片,用训练好的模型测试
  4. 尝试将其他格式的数据转换为TFRecord
0%