基于简单粗暴TensorFlow2的笔记记录
TensorFlow 基础
with语句
- with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的
__enter__()
,执行完语句体之后会执行__exit__()
- 上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象
- 如果用as指定了一个目标,会将
__enter__()
返回值赋予这个目标其他介绍
- 可以用with 语句来代替 try…except…finally
- 一个上下文管理器(context manager)是一个对象,定义了运行一个with语句时候要建立的运行时上下文(runtime context),掌控了何处进入,何处退出以及一个代码块运行所需的运行时上下文
张量
- 张量 (Tensor)为TensorFlow中数据的基本单位,在概念上等同于多维数组——描述标量(0 维数组)、向量(1 维数组)、矩阵(2 维数组)
1
2
3
4
5
6
7
8# 定义一个随机数(标量)
random_float = tf.random.uniform(shape=())
# 定义一个有2个元素的零向量
zero_vector = tf.zeros(shape=(2))
# 定义一个2×2的常量矩阵
A = tf.constant([[1., 2.], [3., 4.]]) - 属性包括形状、类型和值,通过张量的
shape
、dtype
属性和numpy()
方法获取 - 张量中元素的类型默认为
tf.float32
,可以通过加入dtype
参数来自行指定类型:zero_vector = tf.zeros(shape=(2), dtype=tf.int32)
- 将已有的张量运算后得到新的张量:张量相加
C = tf.add(A, B)
自动求导
tf.GradientTape()
实现自动求导1
2
3
4
5
6
7
8
9
10# 计算y = x^2 在3的导数
import tensorflow as tf
x = tf.Variable(initial_value=3.)
with tf.GradientTape() as tape: # 在 tf.GradientTape() 的上下文内,所有计算步骤都会被记录以用于求导
y = tf.square(x)
y_grad = tape.gradient(y, x) # 计算y关于x的导数
print([y, y_grad])
输出:
[array([9.], dtype=float32), array([6.], dtype=float32)]- x 是变量,同样具有形状、类型和值三种属性。变量需要有一个初始化过程,通过在
tf.Variable()
中指定initial_value
初始化。 - 变量与普通张量的一个重要区别是其默认能够被自动求导机制所求导,因此常被用于定义模型的参数
- 同样可计算偏导、矩阵或向量的导数
tf.square()
:对输入张量的每一个元素求平方,不改变张量形状tf.reduce_sum()
:对输入张量的所有元素求和,输出一个形状为空的纯量张量,可通过axis
参数来指定求和的维度示例:线性回归
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# 对一组离散值(X,Y)线性回归
import numpy as np
# 定义数据
X_raw = np.array([2013, 2014, 2015, 2016, 2017], dtype=np.float32)
y_raw = np.array([12000, 14000, 15000, 16500, 17500], dtype=np.float32)
# 数据归一化
X = (X_raw - X_raw.min()) / (X_raw.max() - X_raw.min())
y = (y_raw - y_raw.min()) / (y_raw.max() - y_raw.min())
* 梯度下降过程:
* 初始化自变量为$x_0$,$k=0$
* 迭代:
* 求函数$f(x)$关于自变量$x$的梯度$\nabla f(x_k)$
* 更新自变量:$x_{k+1}=x_k-\gamma \nabla f(x_k)$,$\gamma$为学习率
* $k\leftarrow k+1$
```python
X = tf.constant(X)
y = tf.constant(y)
a = tf.Variable(initial_value=0.)
b = tf.Variable(initial_value=0.)
variables = [a, b]
num_epoch = 10000
optimizer = tf.keras.optimizers.SGD(learning_rate=1e-3)
#声明了一个梯度下降优化器 Optimizer,学习率为 1e-3,计算出求导结果更新模型参数,以最小化特定的损失函数,具体使用方式是调用其apply_gradients() 方法
for e in range(num_epoch):
# 使用tf.GradientTape()记录损失函数的梯度信息
with tf.GradientTape() as tape:
y_pred = a * X + b
loss = 0.5 * tf.reduce_sum(tf.square(y_pred - y))
# TensorFlow自动计算损失函数关于自变量(模型参数)的梯度——求出 tape 中记录的 loss 关于 variables = [a, b] 中每个变量的偏导数
grads = tape.gradient(loss, variables)
# TensorFlow自动根据梯度更新参数
# 参数 grads_and_vars,即待更新的变量,和损失函数关于这些变量的偏导数
optimizer.apply_gradients(grads_and_vars=zip(grads, variables))
print(a, b)zip()
为内置函数:若 a = [1, 3, 5], b = [2, 4, 6],则zip(a, b) = [(1, 2), (3, 4), (5, 6)]
。zip()
返回一个 zip 对象,本需要调用list()
来将生成器转换成列表
TensorFlow 模型建立与训练
模型(Model)与层(Layer)
常使用 Keras(
tf.keras
)构建模型有两个重要概念: 模型(Model) 和 层(Layer)
- 层封装各种计算流程和变量
- 模型组织和连接各种层,封装成一个整体
Keras 模型以类的形式使用,可以通过继承 tf.keras.Model 类来定义新的模型。继承类中,需要重写
__init__()
(构造函数,初始化)和call(input)
(模型调用)1
2
3
4
5
6
7
8
9
10
11
12class MyModel(tf.keras.Model):
def __init__(self):
super().__init__()
# 添加初始化代码(包含 call 方法中会用到的层),例如
# layer1 = tf.keras.layers.BuiltInLayer(...)
# layer2 = MyCustomLayer(...)
def call(self, input):
# 添加模型调用的代码(处理输入并返回输出),例如
# x = layer1(input)
# output = layer2(x)
return outputKeras 的全连接层
tf.keras.layers.Dense
输入矩阵 $A$ 进行 $f(AW + b)$ 的线性变换 + 激活函数操作。不指定激活函数,则为线性变换 $AW + b$。输入矩阵 $A$
= [batch_size, input_dim]
输出二维张量
[batch_size, units]
常用的激活函数包括
tf.nn.relu
、tf.nn.tanh
和tf.nn.sigmoid
可训练变量:
多层全连接神经网络
完成 MNIST 手写体数字图片数据集的分类任务
数据获取及预处理: tf.keras.datasets
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class MNISTLoader():
def __init__(self):
mnist = tf.keras.datasets.mnist # 从网上自动下载 MNIST 数据集并加载,放置在:C:\Users\ASUS\.keras/dataset 目录下
(self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data() # 载入数据
# MNIST中的图像默认为uint8(0-255的数字)。将其归一化到0-1之间的浮点数,并在最后增加一维作为颜色通道
self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1) # [60000, 28, 28, 1]
self.test_data = np.expand_dims(self.test_data.astype(np.float32) / 255.0, axis=-1) # [10000, 28, 28, 1]
self.train_label = self.train_label.astype(np.int32) # [60000]
self.test_label = self.test_label.astype(np.int32) # [10000]
self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]
def get_batch(self, batch_size):
# 从数据集中随机取出batch_size个元素并返回
index = np.random.randint(0, np.shape(self.train_data)[0], batch_size)
return self.train_data[index, :], self.train_label[index]图像数据集的一种典型表示是
[图像数目,长,宽,色彩通道数]
的四维张量模型的构建: tf.keras.Model 和 tf.keras.layers
引入非线性激活函数
ReLU
输入一个向量(这里是拉直的 1×784 手写体数字图片),输出 10 维的向量,代表图片属于 0 到 9 的概率
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13class MLP(tf.keras.Model):
def __init__(self):
super().__init__()
self.flatten = tf.keras.layers.Flatten() # Flatten层将除第一维(batch_size)以外的维度展平 这也对应神经网络的输入层
self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu) # 中间层
self.dense2 = tf.keras.layers.Dense(units=10) # 输出层
def call(self, inputs): # [batch_size, 28, 28, 1]
x = self.flatten(inputs) # [batch_size, 784]
x = self.dense1(x) # [batch_size, 100]
x = self.dense2(x) # [batch_size, 10]
output = tf.nn.softmax(x)
return outputtf.nn.softmax
: 归一化指数函数,将模型的原始输出归一化,使输出向量:- 该向量的每个元素属于 [0, 1]
- 该向量所有元素之和为 1
原本输入为 28*28 的图片(向量),通过
tf.keras.layers.Flatten()
将其展平为 784*1 的向量2.2.3. 模型的训练: tf.keras.losses 和 tf.keras.optimizer
tf.keras.losses
计算损失函数,tf.keras.optimizer
优化模型定义模型超参数——模型超参数是模型外部的配置,其值不能从数据估计
1
2
3num_epochs = 5
batch_size = 50
learning_rate = 0.001实例化模型和数据读取类,并实例一个优化器
1
2
3model = MLP()
data_loader = MNISTLoader()
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) # Adam优化器迭代:
- DataLoader 中随机取一批训练数据送入模型,计算出模型的预测值
- 模型预测值与真实值进行比较,计算损失函数(loss)——使用交叉熵
- 计算损失函数关于模型变量的导数
- 导数值传入优化器,使用
apply_gradients
更新模型参数以最小化损失函数
代码
1
2
3
4
5
6
7
8
9
10num_batches = int(data_loader.num_train_data // batch_size * num_epochs)
for batch_index in range(num_batches):
X, y = data_loader.get_batch(batch_size)
with tf.GradientTape() as tape:
y_pred = model(X)
loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred) # 交叉熵函数,参数为模型预测值和真实标签值
loss = tf.reduce_mean(loss)
print("batch %d: loss %f" % (batch_index, loss.numpy()))
grads = tape.gradient(loss, model.variables)
optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))模型的评估: tf.keras.metrics
测试集评估模型的性能:
tf.keras.metrics
中的SparseCategoricalAccuracy
评估器来评估性能,能够对模型预测的结果与真实结果进行比较,并输出预测正确的样本数占总样本数的比例代码
1
2
3
4
5
6
7sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy() # 实例化一个评估器
num_batches = int(data_loader.num_test_data // batch_size)
for batch_index in range(num_batches):
start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
y_pred = model.predict(data_loader.test_data[start_index: end_index])
sparse_categorical_accuracy.update_state(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred) # 参数为预测出的结果和真实结果
print("test accuracy: %f" % sparse_categorical_accuracy.result()) # result() 输出评估指标值卷积神经网络 CNN
预备知识
结构类似于视觉系统的人工神经网络,包含一个或多个卷积层(Convolutional Layer)、池化层(Pooling Layer)和全连接层
CNN中的卷积:
$$ s(t) = (XW)(t) $$
$$ s(i,j) = (XW)(i,j) = \sum_m \sum_n x(i+m,j+n)w(m,n) $$
直观上,两个矩阵对应位置元素相乘,之后相加
$W$为卷积核,也称感受野,$X$为输入
有时会存在偏置卷积层+池化层(卷积层+卷积层)的组合可以在隐藏层出现很多次,最常见的 CNN 都是若干卷积层+池化层的组合
输出层使用
Softmax
激活函数来做图像识别的分类卷积层:
- 进行特征提取
- 感受野深度和图像深度应当一致
- 输入 3*4 ,核矩阵 2*2,则输出为 2*3
- 对于卷积后的输出,一般会通过
ReLU
激活函数,将输出的张量中小于0的位置对应的元素值都变为0 - 示例:
![](简单粗暴 TensorFlow 2/1.PNG)- 这里图片原本为 5*5,但增加了一个边界,边界元素为 0(+pad 1)。增加的圈数一般为经验值
- 此处卷积步幅为2,感受野深度和图像深度为3——卷积核由三个矩阵组成
- 两个卷积核的对应偏置不同
池化层:
CNN 类与多层感知机 类相似
代码:
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
31class CNN(tf.keras.Model):
def __init__(self):
super().__init__()
self.conv1 = tf.keras.layers.Conv2D(
filters=32, # 卷积层神经元(卷积核)数目
kernel_size=[5, 5], # 感受野大小
padding='same', # padding策略(vaild 或 same)
activation=tf.nn.relu # 激活函数
)
self.pool1 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2) # 池化
self.conv2 = tf.keras.layers.Conv2D(
filters=64,
kernel_size=[5, 5],
padding='same',
activation=tf.nn.relu
)
self.pool2 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2) # 池化
self.flatten = tf.keras.layers.Reshape(target_shape=(7 * 7 * 64,))
self.dense1 = tf.keras.layers.Dense(units=1024, activation=tf.nn.relu)
self.dense2 = tf.keras.layers.Dense(units=10)
def call(self, inputs):
x = self.conv1(inputs) # [batch_size, 28, 28, 32]
x = self.pool1(x) # [batch_size, 14, 14, 32]
x = self.conv2(x) # [batch_size, 14, 14, 64]
x = self.pool2(x) # [batch_size, 7, 7, 64]
x = self.flatten(x) # [batch_size, 7 * 7 * 64]
x = self.dense1(x) # [batch_size, 1024]
x = self.dense2(x) # [batch_size, 10]
output = tf.nn.softmax(x)
return output2.3.3. Keras 中预定义的卷积神经网络
tf.keras.applications
中有预定义好的经典卷积神经网络结构,如VGG16
、VGG19
、ResNet
、MobileNet
等实例化
MobileNet
(参数均取默认值):1
model = tf.keras.applications.MobileNetV2()
一些通用的模型参数:
适合处理序列数据——语言模型、文本生成、机器翻译——对于一个序列,当前的输出与前面的输出也有关
隐藏层之间的节点有连接,隐藏层的输入来自输入层的输出和上一时刻隐藏层的输出
![](简单粗暴 TensorFlow 2/2.PNG)$$s_t = f(Us_t + Wx_{t-1}) $$
RNN 对于长时记忆的困难主要来自梯度爆炸/梯度消失
2.4.2. 示例:RNN 自动生成尼采风格文本
设置
DataLoader
类读取文本,以字符为单位进行编码:字符种类数为num_chars
,每种字符赋予一个唯一整数编号i
(0 到 num_chars-1)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class DataLoader():
def __init__(self): # 创建对象会自动调用初始化方法__init__
path = tf.keras.utils.get_file('nietzsche.txt',
origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt') # 从远程网址下载数据,并保存到文件中
# tf.keras.utils.get_file(fname=TRAIN_URL.split('/')[-1],orgin=TRAIN_URL, cache_dir='.') fname:文件命名;origin:文件的url地址;cache_dir:存储地址,此处指存储到当前目录下,默认为:C:\Users\ASUS\.keras\datasets
with open(path, encoding='utf-8') as f:
self.raw_text = f.read().lower()
self.chars = sorted(list(set(self.raw_text))) # set()创建一个无序不重复元素集
# list()将元组转换为列表
# sorted()对所有可迭代的对象从小到大排序
self.char_indices = dict((c, i) for i, c in enumerate(self.chars)) # enumerate()将可遍历数据对象组合为一个索引序列,序列的元素为数据和数据对应下标,即(0,数据0),(1,数据1)
# dict() 转为字典,如{'a': 0, 'h': 1}
self.indices_char = dict((i, c) for i, c in enumerate(self.chars))
self.text = [self.char_indices[c] for c in self.raw_text] # 根据字典的索引找到对应数字,如'ah'变为[1,0]
def get_batch(self, seq_length, batch_size):
seq = []
next_char = []
for i in range(batch_size):
index = np.random.randint(0, len(self.text) - seq_length)
seq.append(self.text[index:index+seq_length])
next_char.append(self.text[index+seq_length])
return np.array(seq), np.array(next_char) # [batch_size, seq_length], [num_batch] np即numpy模型的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class RNN(tf.keras.Model):
def __init__(self, num_chars, batch_size, seq_length):
super().__init__()
self.num_chars = num_chars
self.seq_length = seq_length
self.batch_size = batch_size
self.cell = tf.keras.layers.LSTMCell(units=256) # 实例化LSTMCell
self.dense = tf.keras.layers.Dense(units=self.num_chars) # 实例化全连接层
def call(self, inputs, from_logits=False):
inputs = tf.one_hot(inputs, depth=self.num_chars) # [batch_size, seq_length, num_chars]
# 编码:独热码,序列转为[seq_length,num_chars] 张量
state = self.cell.get_initial_state(batch_size=self.batch_size, dtype=tf.float32) # 初始化cell状态
for t in range(self.seq_length): # 将序列送入RNN单元
output, state = self.cell(inputs[:, t, :], state) # 当前
logits = self.dense(output) # 取最后一次的输出
if from_logits:
return logits
else:
return tf.nn.softmax(logits)训练:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# 超参数设置
num_batches = 1000
seq_length = 40
batch_size = 50
learning_rate = 1e-3
# 训练
data_loader = DataLoader()
model = RNN(num_chars=len(data_loader.chars), batch_size=batch_size, seq_length=seq_length)
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
for batch_index in range(num_batches): # 迭代
X, y = data_loader.get_batch(seq_length, batch_size) # 取数据
with tf.GradientTape() as tape: # 送入模型,计算损失函数
y_pred = model(X)
loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
loss = tf.reduce_mean(loss)
print("batch %d: loss %f" % (batch_index, loss.numpy()))
grads = tape.gradient(loss, model.variables) # 关于求导
optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables)) # 优化器更新模型预测与生成文本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def predict(self, inputs, temperature=1.): # temperature参数控制分布的形状,参数越大分布越平缓(最大值和最小值差值小)
batch_size, _ = tf.shape(inputs)
logits = self(inputs, from_logits=True)
prob = tf.nn.softmax(logits / temperature).numpy()
return np.array([np.random.choice(self.num_chars, p=prob[i, :])
for i in range(batch_size.numpy())])
X_, _ = data_loader.get_batch(seq_length, 1)
for diversity in [0.2, 0.5, 1.0, 1.2]:
X = X_
print("diversity %f:" % diversity)
for t in range(400):
y_pred = model.predict(X, diversity)
print(data_loader.indices_char[y_pred[0]], end='', flush=True)
X = np.concatenate([X[:, 1:], np.expand_dims(y_pred, axis=1)], axis=-1)
print("\n")TensorFlow 常用模块
预备知识:Python迭代器
迭代器是一个可以记住遍历位置的对象,有两个基本的方法:
iter()
和next()
每调用一次
next()
返回下一个元素,若下一个元素不存在返回StopIteration
异常1
2
3
4
5
6s = 'ab'
it = iter(s)
print(it.__next__())
'a'
print(it.__next__())
'b'tf.train.Checkpoint类 :变量的保存与恢复
方法
save()
和restore()
保存和恢复Checkpointable State
的对象——tf.keras.optimizer
、tf.Variable
、tf.keras.Layer
或者tf.keras.Model
实例保存
只保存模型的参数,不保存模型的计算过程
tf.train.Checkpoint()
的实例,接受的初始化参数是一个**kwargs
,即一系列的键值对。键名随意取,值为需要保存的对象。恢复变量的时候,还将使用同一键名1
2
3# 举例:
checkpointA = tf.train.Checkpoint(model=model) # 实例化
checkpointB = tf.train.Checkpoint(myAwesomeModel=model, myAwesomeOptimizer=optimizer)模型训练后需要保存时:
checkpointA.save('保存文件目录+前缀')
1
2
3
4
5
6
7# 具体框架如下:
model = MyModel()
# 实例化Checkpoint,指定保存对象为model
checkpoint = tf.train.Checkpoint(myModel=model)
# ...(模型训练代码)
# 模型训练完毕后将参数保存到文件(也可在模型训练过程中每隔一段时间就保存一次)
checkpoint.save('./save/model.ckpt')3.2.2. 载入
为模型重新载入之前保存的参数时,再次实例化一个
checkpoint
,并保持键名一致例如:
1
2
3model_to_be_restored = MyModel() # 待恢复参数的同一模型
checkpoint = tf.train.Checkpoint(myAwesomeModel=model_to_be_restored) # 键名保持为“myAwesomeModel”
checkpoint.restore(save_path_with_prefix_and_index) # 之前保存的文件的目录 + 前缀 + 编号,即:checkpoint.restore('./save/model.ckpt-1') ,载入前缀为 model.ckpt,序号为 1 的文件来恢复模型载入最新的模型:
tf.train.latest_checkpoint(save_path)
删除旧的 Checkpoint
tf.train.CheckpointManager
- 训练过程产生大量
Checkpoint
,但只想保留最后的几个 - 使用别的编号方式(例如使用当前 Batch 的编号作为文件编号
- 训练过程产生大量
在定义
Checkpoint
后定义一个CheckpointManager
:1
2checkpoint = tf.train.Checkpoint(model=model)
manager = tf.train.CheckpointManager(checkpoint, directory='./save', checkpoint_name='model.ckpt', max_to_keep=k)directory
为文件保存的路径,checkpoint_name
为文件名前缀(默认为ckpt),max_to_keep
为保留的Checkpoint
数目manager.save()
:代码目录下建立文件夹(如
./tensorboard
)存放记录文件,并实例化一个记录器:1
summary_writer = tf.summary.create_file_writer('./tensorboard') # 参数为保存的目录
with
语句指定使用的记录器,并记录参数在step
时候的值:tf.summary.scalar(name, tensor, step=batch_index)
(参数为标量时,用scalar()
)。运行一次tf.summary.scalar()
,记录器向记录文件中写入一条记录一般设置
step
为当前训练过程中的batch
序号。代码如下1
2
3
4
5
6
7summary_writer = tf.summary.create_file_writer('./tensorboard')
# 模型训练
for batch_index in range(num_batches):
# ...(训练代码,变量loss存储batch的损失值)
with summary_writer.as_default(): # 希望使用的记录器
tf.summary.scalar("loss", loss, step=batch_index)
tf.summary.scalar("MyScalar", my_scalar, step=batch_index)代码目录打开终端,运行
tensorboard --logdir=./tensorboard
,访问命令行所输出的网址tf.data :数据集的构建与预处理
数据集对象的建立
tf.data
的核心是tf.data.Dataset
类,由一系列可迭代访问的元素组成——元素包含一个或多个张量(如:一个 长*宽*通道数 的图片张量或图片张量与标签张量组成的元组)tf.data.Dataset.from_tensor_slices()
适用于数据能整个装入内存的情况——所有张量可拼接成一个大的张量- 如[60000,28,28,1](60000张 28*28的单通道灰度图像),此时数据集元素数量和第0维大小相同,即60000
- 多个张量作为输入时,第0维必须相同,且必须将张量拼接成元组
1
2
3
4
5
6
7
8import tensorflow as tf
X = tf.constant([2013, 2014, 2015, 2016, 2017])
Y = tf.constant([12000, 14000, 15000, 16500, 17500]) # 创建常量张量
dataset = tf.data.Dataset.from_tensor_slices((X, Y)) # 形成[5,2]的张量
for x, y in dataset:
print(x.numpy(), y.numpy())
下载keras的datasets中得数据:
data = tf.keras.datasets.mnist.load_data()
对特别巨大而无法完整载入内存的数据集,可以先将数据集处理为 TFRecord 格式
数据集对象的预处理
tf.data.Dataset
类中常用的处理方法:for
循环迭代iter()
显式创建 Python 迭代器,next()
获取下一个元素1
2
3
4dataset = tf.data.Dataset.from_tensor_slices((A, B, C, ...))
it = iter(dataset)
a_0, b_0, c_0, ... = next(it)
a_1, b_1, c_1, ... = next(it)3.4.4. TFRecord :TensorFlow 数据集存储格式
可视为一组序列化的
tf.train.Example
元素所组成的列表文件,tf.train.Example
又由若干个tf.train.Feature
的字典组成1
2
3
4
5
6
7
8
9
10
11
12
13[
{ # example 1 (tf.train.Example)
'feature_1': tf.train.Feature,
...
'feature_k': tf.train.Feature
},
...
{ # example N (tf.train.Example)
'feature_1': tf.train.Feature,
...
'feature_k': tf.train.Feature
}
]存储为TFRecord:
- 读入元素到内存
- 建立 Feature 字典,再建立
tf.train.Feature
的字典,并将元素转换为tf.train.Example
对象 - 序列化为字符串,通过
tf.io.TFRecordWriter
写入文件
读取TFRecord:
tf.data.TFRecordDataset
读入原始文件,获得tf.data.Dataset
对象Dataset.map(f)
取tf.io.parse_single_example
,反序列化tf.train.Example
字符串
存储:
1
2
3
4
5
6
7
8
9with tf.io.TFRecordWriter(tfrecord_file) as writer: # tfrecord_file 为保存的目录
for filename, label in zip(train_filenames, train_labels):
image = open(filename, 'rb').read() # 读取数据集图片到内存,image 为一个 Byte 类型的字符串
feature = { # 建立 tf.train.Feature 字典
'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])), # 图片是一个 Bytes 对象
'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])) # 标签是一个 Int 对象
}
example = tf.train.Example(features=tf.train.Features(feature=feature)) # 通过字典建立 Example
writer.write(example.SerializeToString()) # 将Example序列化并写入 TFRecord 文件读取:
1
2
3
4
5
6
7
8
9
10
11
12
13raw_dataset = tf.data.TFRecordDataset(tfrecord_file) # 读取 TFRecord 文件
feature_description = { # 类似于数据集的 “描述文件”,定义Feature结构,告诉解码器每个Feature的类型是什么
'image': tf.io.FixedLenFeature([], tf.string),
'label': tf.io.FixedLenFeature([], tf.int64),
}
def _parse_example(example_string): # 将 TFRecord 文件中的每一个序列化的 tf.train.Example 解码
feature_dict = tf.io.parse_single_example(example_string, feature_description)
feature_dict['image'] = tf.io.decode_jpeg(feature_dict['image']) # 解码
return feature_dict['image'], feature_dict['label']
dataset = raw_dataset.map(_parse_example)TensorFlow 模型导出
将训练好的整个模型完整导出为一系列标准格式的文件,以在不同的平台上部署模型
SavedModel 完整导出模型
SavedModel 包含了 TensorFlow 程序的完整信息:参数的权值和计算的流程
使用继承
tf.keras.Model
类建立的 Keras 模型,其需要导出到 SavedModel 格式的方法(比如call
)都需要使用@tf.function
修饰导出
1
tf.saved_model.save(model, "保存的目标文件夹名称")
导入
1
model = tf.saved_model.load("保存的目标文件夹名称")
继承
tf.keras.Model
类建立的 Keras 模型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class MLP(tf.keras.Model):
def __init__(self):
super().__init__()
self.flatten = tf.keras.layers.Flatten()
self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)
self.dense2 = tf.keras.layers.Dense(units=10)
def call(self, inputs): # [batch_size, 28, 28, 1]
x = self.flatten(inputs) # [batch_size, 784]
x = self.dense1(x) # [batch_size, 100]
x = self.dense2(x) # [batch_size, 10]
output = tf.nn.softmax(x)
return output
model = MLP()
Keras自有的模型导出格式
导出
1
model.save('mnist_cnn.h5')
导入
1
keras.models.load_model("mnist_cnn.h5")