一.建模流程
通常用TensorFlow实现机器学习模型,尤其常用于实现神经网络模型,但为简洁起见,一般推荐使用TensorFlow的高层次keras接口来实现神经网络模型
使用TensorFlow实现神经网络模型的一般流程为:准备数据 → 定义模型 → 训练模型 → 评估模型 → 使用模型 → 保存模型
1.准备数据
df = pd.read_csv('./data.csv')
2.定义模型
使用keras接口有三种方式构建模型
①使用Sequential按层顺序构建模型:最简单
model = models.Sequential()
model.add(layers.Dense(20,activation = 'relu'))
...
model.summary()
②使用函数式API构建任意结构模型
inputs = layers.Input(shape=(32,32,3))
x = layers.Conv2D(32,kernel_size=(3,3))(inputs)
x = layers.MaxPool2D()(x)
...
outputs = layers.Dense(1,activation = 'sigmoid')(x)
model = models.Model(inputs = inputs,outputs = outputs)
model.summary()
③继承Model基类构建自定义模型
class CnnModel(models.Model):
def __init__(self):
super(CnnModel,self).__init__()
def build(self,input_shape):
self.embedding = layers.Embedding(MAX_WORDS,7,input_length = MAX_LEN)
self.conv_1 = layers.Conv1D(16,kernel_size = 5,name = "conv_1",activation = "relu")
self.pool = layers.MaxPool1D()
self.conv_2 = layers.Conv1D(128,kernel_size = 2,name = "conv_2",activation = "relu")
self.flatten = layers.Flatten()
self.dense = layers.Dense(1,activation = "sigmoid")
super(CnnModel,self).build(input_shape)
def call(self,x):
x = self.embedding(x)
x = self.conv_1(x)
x = self.pool(x)
x = self.conv_2(x)
x = self.pool(x)
x = self.flatten(x)
x = self.dense(x)
return(x)
model = CnnModel()
model.build(input_shape = (None,MAX_LEN))
model.summary()
3.训练模型
训练模型通常有三种方法
①内置fit方法:最简单
#二分类问题选择二元交叉熵损失函数
model.compile(optimizer = 'adam',
loss = 'binary_crossentropy',
metrics = ['AUC'])
history = model.fit(x_train,y_train,
batch_size = 64,
epochs = 30,
validation_split = 0.2 #分割一部分训练数据用于验证
)
②内置train_on_batch方法
③自定义训练循环
4.评估模型
def plot_metric(history,metric):
train_metrics = history.history[metric]
val_metrics = history.history['val_' + metric]
epochs = range(1,len(train_metrics) + 1)
plt.plot(epochs,train_metrics,'bo--')
plt.plot(epochs,val_metrics,'ro--')
plt.title('training and validation' + metric)
plt.xlabel('epochs')
plt.ylabel(metric)
plt.legend(["train_" + metric,"val_" + metric])
plt.show()
plot_metric(history,"loss")
#评估模型在测试集上的效果
model.evaluate(x = x_test, y = y_test)
5.使用模型
model.predict(x_test[0:10])
model(x_test)
model.call(x_test)
model.predict_on_batch(x_test)
6.保存模型
keras方式:仅仅适合使用python环境恢复模型
tensorflow方式:可以跨平台进行模型部署
①Keras方式保存
model.save('./keras_model.h5')
#保存模型结构
json_str = model.to_json()
#保存模型权重
model.save_weights('./model_weights.h5')
②TensorFlow方式保存
model.save('./tf_savemodel',save_format="tf")
二.核心概念
TensorFlow底层最核心的概念是张量,计算图以及自动微分
1.张量数据结构
TensorFlow程序 = 张量数据结构 + 计算图算法语言
从行为特性来看,有两种类型的张量,常量和变量
常量的值在计算图中不可以被重新赋值,变量可以在计算图中用assign等算子重新赋值
a = tf.constant(1)
v = tf.Variable([1.0,2.0],name="v")
2.三种计算图
有三种计算图的构建方式:静态计算图,动态计算图,autograph
静态计算图已经不推荐使用了
动态计算图
x = tf.constant("hello")
y = tf.constant("world")
z = tf.strings.join([x,y],seperator=" ")
autograph
如果采用autograph的方式使用计算图,第一步定义计算图变成了定义函数,第二步执行计算图变成了调用函数
在实践中,我们一般会先用动态计算图调试代码,然后在需要提高性能的地方利用@tf.function切换成autograph获得更高的效率
3.自动微分机制
TensorFlow一般使用梯度磁带tf.GradientTape来记录正想运算过程,然后反播磁带自动得到梯度值
这种利用tf.GradientTape求微分的方法叫做TensorFlow的自动微分机制
利用梯度磁带求导数
x = tf.Variable(0.0,name = "x",dtype = tf.float32)
a = tf.constant(1.0)
b = tf.constant(-2.0)
c = tf.constant(1.0)
with tf.GradientTape() as tape:
y = a*tf.pow(x,2) + b*x +c
d = tape.gradient(y,x)
print(d)
利用梯度磁带和优化器求最小值
optimizer.apply_gradients(grads_and_vars=[(dy_dx,x)])
optimizer.minimize(f,[x])
三.低阶API
TensorFlow的低阶API主要包括张量操作,计算图和自动微分
张量的操作主要包括张量的结构操作和张量的数学运算
张量结构操作:张量创建,索引切片,维度变换,合并分割
张量数学运算:标量运算,向量运算,矩阵运算
张量运算的广播机制
1.张量的结构操作
创建张量:许多方法和numpy中创建array的方法很像
a = tf.constant([1,2,3],dtype = tf.float32)
b = tf.range(1,10,delta = 2)
c = tf.linspace(0,2*3,100)
d = tf.zeros([3,3])
索引切片:和numpy几乎一样
t = tf.random.uniform([5,5],minval=0,maxval=10,dtype=tf.int32)
tf.print(t)
tf.print(t[0])
tf.print(t[1,3])
tf.print(t[1:4,:])
维度变换
tf.reshape:改变张量的形状
tf.squeeze:减少维度
tf.expand_dims:增加维度
tf.transpose:交换维度,常用于图片存储格式的交换上
合并分割
tf.concat:连接,不会增加维度
tf.stack:堆叠,会增加维度
tf.split:是tf.concat的逆运算,可以指定分割份数平均分割,也可以通过指定每份的记录数量进行分割
2.张量的数学运算
标量运算
加减乘除乘方,三角函数,指数,对数,逻辑比较运算符
标量运算符的特点是对张量实施逐元素运算
向量运算
只在特定轴上运算,将一个向量映射到一个标量或者另一个向量
矩阵运算
矩阵必须是二维的
矩阵乘法,矩阵转置,矩阵逆,矩阵求迹,矩阵范数,矩阵行列式,矩阵求特征值,矩阵分解
除了一些常用的运算外,大部分和矩阵有关的运算都在tf.linalg子包中
3.广播机制
TensorFlow的广播规则和numpy是一样的
如果张量的维度不同,将维度较小的张量进行扩展,直到两个张量的维度都一样
如果两个张量在某个维度上的长度是相同的,或者其中一个张量在该维度上的长度是1,那么我们就说这两个张量在该维度上是相容的
如果两个张量在所有维度上都是相容的,它们就能够使用广播
广播之后,每个维度的长度将取两个张量在该维度长度的较大值
在任何一个维度上,如果一个张量的长度为1.另一个张量长度大于1,那么在该维度上,就好像是对第一个张量进行了复制
4.autograph的使用规范
有三种计算图的构建方式:静态计算图,动态计算图,autograph
动态计算图易于调试,编码效率高,但执行效率偏低
静态计算图执行效率很高,但较难调试
autograph机制可以将动态图转换成静态计算图,兼收执行效率和编码效率之利
编码规范总结
被@tf.function修饰的函数应该尽可能使用TensorFlow中的函数而不是python中的其他函数
避免在@tf.function修饰的函数内部定义tf.Variable
被@tf.function修饰的函数不可修改该函数外部的python列表或字典等数据结构变量
机制原理
当我们第一次调用被@tf.function装饰的函数时,发生了什么?
第一件事:创建计算图
第二件事:执行计算图
5.autograph和tf.Module
利用tf.Module提供的封装,再结合TensorFlow丰富的低阶API,我们能够基于TensorFlow开发任意机器学习模型,并实现跨平台部署使用
tf.keras中的模型和层都是继承tf.Module实现的,也具有管理和子模块管理功能
四.中阶API
1.数据管道Dataset
构建数据管道
#从numpy array构建数据管道
iris = datasets.load_iris()
ds1 = tf.data.Dataset.from_tensor_slices((iris["data"],iris["target"]))
#从pandas dataframe构建数据管道
dfiris = pd.DataFrame(iris["data"],columns = iris.feature_names)
ds2 = tf.data.Dataset.from_tensor_slices((dfiris.to_dict("list"),iris["target"]))
#从python generator构建数据管道
image_generator = ImageDataGenerator(rescale = 1.0/255).flow_from_directory(
"./data/test/",
target_size=(32,32)
batch_size=20,
class_mode='binary')
classdict = image_generator.class_indices
def generator():
for features,label in image_generator:
yield (features,label)
ds3 = tf.data.Dataset.from_generator(generator,output_types = (tf.float32,tf.int32)
#从csv文件构建数据管道
ds4 = tf.data.experimental.make_csv_dataset(
file_pattern = ["./data/train.csv","./data/test.csv"],
batch_size = 3,
label_name = "survived",
num_epochs = 1,
ignore_errors = True)
for data,label in ds4.take(2):
print(data,label)
#从文本文件构建数据管道
ds5 = tf.data.TextLineDataset(
filenames = ["./data/train.csv","./data/test.csv"]
).skip(1)
for line in ds5.take(5):
print(line)
#从文件路径构建数据管道
ds6 = tf.data.Dataset.list_files("./data/train/*/*.jpg)
for file in ds6.take(5):
print(file)
应用数据转换
map:将转换函数映射到数据集的每一个元素
flat_map:将转换函数映射到数据集的每一个元素,并将嵌套的dataset压平
interleave:效果类似flat_map,但可以将不同来源的数据夹在一起
filter:过滤掉某些元素
zip:将两个长度相同的dataset横向胶合
concatenate:将两个dataset纵向连接
reduce:执行归并操作
batch:构建批次,每次放一个批次,比原始数据增加一个维度
padded_batch:构建批次,类似batch,但可以填充到相同的形状
window:构建滑动窗口,返回dataset of dataset
shuffle:数据顺序洗牌
repeat:重复数据若干次,不带参数时,重复无数次
shard:采样,从某个位置开始隔固定距离采样一个元素
take:采样,从开始位置取前几个元素
提升管道性能
使用prefetch方法让数据准备和参数迭代两个过程相互并行
使用interleave方法可以让数据读取过程多进程执行,并将不同来源数据夹在一起
使用map时设置num_parallel_calls让数据转换过程多进行执行
使用catch方法让数据在第一个epoch后缓存到内存中,仅限于数据集不大情形
使用map转换时,先batch,然后采用向量化的转换方法对每个batch进行转换
2.激活函数
tf.nn.sigmoid:将实数压缩到0到1之间,一般只在二分类的最后输出层使用。主要缺陷是存在梯度消失问题,计算复杂度高,输出不以0为中心
tf.nn.softmax:sigmoid多分类扩展,一般只在多分类问题的最后输出层使用
tf.nn.tanh:将实数压缩到-1到1之间,输出期望为0,主要缺陷是存在梯度消失问题,计算复杂度高
tf.nn.relu:修正线性单元,最流行的激活函数,一般隐藏层使用,主要缺陷是输出不以0为中心,输入小于0时存在梯度消失问题(死亡relu)
tf.nn.leaky_relu:对修正线性单元的改进,解决了死亡relu问题
tf.nn.elu:指数线性单元,对relu的改进,能够缓解死亡relu问题
在keras模型中使用激活函数一般有两种方式,一种是作为某些层的activation参数指定,另一种是显式添加layers.Activation激活层
3.模型层
如果内置模型层不能够满足需求,可以通过编写tf.keras.Lambda匿名模型层或继承tf.keras.layers.Layer基类构建自定义的模型层
如果自定义模型层没有需要被训练的参数,一般推荐使用Lambda层实现
如果自定义模型层有需要被训练的参数,则可以通过对layer基类子类化实现
lamda层由于没有需要被训练的参数,只需要定义正向传播逻辑即可,使用此layer基类子类化更加简单,lamda层的正向逻辑可以使用python的lambda函数来表达,也可以使用def关键字定义函数来表达
4.损失函数
内置损失函数
5.评估指标
内置评估指标
自定义评估指标
6.优化器
内置优化器
7.回调函数
内置回调函数
自定义回调函数
五.高阶API
TensorFlow的高阶API主要是tensorflow.keras.models
1.构建模型的三种方法
对于顺序结构的模型,优先使用sequential方法构建
如果模型有多输入或者多输出,或者模型需要共享权重,或者模型具有残差连接等非顺序结构,推荐使用函数式API进行创建
如果无特定必要,尽可能避免使用model子类化的方式构建模型,这种方式提供了极大的灵活性,但也有很大概率出错
Sequential按层顺序创建模型
model = models.Sequential()
model.add(layers.Embedding(MAX_WORDS,7,input_length=MAX_LEN))
model.add(layers.Conv1D(filters = 64,kernel_size = 5,activation = "relu"))
model.add(layers.MaxPool1D(2))
...
model.add(layers.Flatten())
model.add(layers.Dense(1,activation = "sigmoid"))
model.compile(optimizer = 'nadam',
loss = 'binary_crossentropy',
metrics = ['accuracy','AUC'])
model.summary()
函数式API创建任意结构模型
inputs = layers.Input(shape = [MAX_LEN])
x = layers.Embedding(MAX_WORDS,7)(inputs)
branch1 = layers.SeparableConv1D(64,3,activation="relu")(x)
branch1 = layers.MaxPool1D(3)(branch1)
branch1 = layers.SeparableConv1D(32,3,activation="relu")(branch1)
branch1 = layers.GlobalMaxPool1D()(branch1)
...
concat = layers.Concatenate()([branch1,branch2,branch3])
outputs = layers.Dense(1,activation = "sigmoid")(concat)
model = models.Model(inputs = inputs,outputs = outputs)
model.compile(optimizer ='nadam',
loss = 'binary_crossentropy',
metrics = ['accuracy','AUC'])
model.summary
model子类化创建自定义模型
class ImdbModel(models.Model):
def __init__(self):
super(ImdbModel,self).__init__()
def build(self,input_shape):
self.embedding = layers.Embedding(MAX_WORDS,7)
self.block1 = ResBlock(7)
self.block2 = ResBlock(5)
self.dense = layers.Dense(1,activation = "sigmoid")
super(ImdbModel,self).build(input_shape)
def call(self,x):
x = self.embedding(x)
x = self.block1(x)
x = self.block2(x)
x = layers.Flatten()(x)
x = self.dense(x)
return(x)
2.训练模型的三种方法
内置fit方法
该方法功能非常强大,支持对numpy.array,tf.data.Dataset以及python generator数据进行训练,并且可以通过设置回调函数实现对训练过程的复杂控制逻辑
内置train_on_batch方法
比fit方法更灵活,可以不通过回调函数而直接在批次层次上更加精细地控制训练的过程
自定义训练循环
无需编译模型,直接利用优化器根据损失函数反向传播迭代参数,拥有最高的灵活性