tensorflow-keras使用tips(一)
前言
很久很久没有写博客了,主要公司原因不方便分享整理,所以就来分享一些小tips了。
言归正传,最近学习了一下ML的相关内容,补了很长一段时间的数学知识,也踩了很多使用keras
和tensorflow
的坑,这里进行一个阶段性的总结,全程不涉及数学方面的内容,只有编程时候踩过的坑。下面分成三个模块,主要都是使用tensorflow.data.Dataset
时出现的问题。
<!--more-->
在预处理阶段使用dataset
dataset
包如其名,其实就是一群可迭代的数据集合,他本身类似于迭代器,相比于直接传入numpy
数组会更节省内存,且内置了许多预处理方式,我们可以使用类似于链式调用的形式,简单的处理好数据,这里默认大家已经比较熟悉DataSet
的创建了,下面展示一个多输入的实例
import tensorflow as tf
testData = [1,2,3,4,5,6]
testData2 = [7,8,9,10,11,12]
data = tf.data.Dataset.from_tensor_slices(
{"x1":testData,"x2":testData2},
)
# 在多输入情况下,dataset构建时可以在里面构造两个字典,第一个字典是训练数据的集合
# 第二个字典则是label的集合简单来说就是
# model.fit(x={first_dict},y={second_dict})
# 在这个例子中,我们不输入label,只输入x
构造好对应数据以后,我们需要对数据进行预处理,在ML中常见的预处理就是取batch
和shuffle
,前者是分割小数据集,后者则是打散数据。使用dataset
处理非常简单,一句代码就可以搞定了
data = data.shuffle(10000).batch(2)
这里我们为打散数据,给了tensorflow
大小为1W的缓冲区,并将打散后的数据分成两组
由于我们给出的数据大小为6
,batch_size
为2
,batch
大小就为3
了,每一次调用都会返回batch
,如果数据格式是我们需要的,就不需要再进行后续处理了,接下是用dataset
进行训练,这个时候我们的数据已经打乱过一次了。
使用dataset训练模型
我们随意创建一个多输入的全连接模型,这是一个非常简单的模型。
import tensorflow as tf
import tensorflow.keras as K
testData = [1,2,3,4,5,6]
testData2 = [7,8,9,10,11,12]
data = tf.data.Dataset.from_tensor_slices(
{"x1":testData,"x2":testData2}
)
data = data.shuffle(10000).batch(2)
input1 = K.layers.Input(shape=(1,))
input2 = K.layers.Input(shape=(1,))
dense = K.layers.Dense(units=10)
a = dense(input1)
b = dense(input2)
output = K.layers.Concatenate()([a,b])
# 这里创建了一个具有共享参数的全连接层,且为双输入的模型。
model = K.models.Model(inputs=[input1,input2],outputs = [output])
model.compile(loss="mse",optimizer="adam")
model.summary()
先输出我们的模型结构check一下正不正确:
结构没问题,由于这里使用了默认的loss函数,因此我们还需要把label输入进去,那就稍微修改一下dataset,如下:
data = tf.data.Dataset.from_tensor_slices(
{"x1":testData,"x2":testData2},
{"y":[1,2,3,4,5,6]}
)
接着我们来训练模型,使用dataset传入参数时,就不可以使用vaild_split
参数了,因此如果需要验证集,我们需要自己构造,所以这里再构造一个验证集:
testData.reverse()
testData2.reverse()
vaild_data = tf.data.Dataset.from_tensor_slices(
{"x1":testData,"x2":testData2},
{"y":[6,5,4,3,2,1]}
)
这里使用了反转数组的形式构造验证集,然后我们对模型进行训练:
import tensorflow as tf
import tensorflow.keras as K
testData = [1,2,3,4,5,6]
testData2 = [7,8,9,10,11,12]
data = tf.data.Dataset.from_tensor_slices(
({"x1":testData,"x2":testData2},
{"y":[1,2,3,4,5,6]}),
)
testData.reverse()
testData2.reverse()
vaild_data = tf.data.Dataset.from_tensor_slices(
({"x1":testData,"x2":testData2},
{"y":[6,5,4,3,2,1]}),
).batch(2)
data = data.shuffle(10000).batch(2)
input1 = K.layers.Input(shape=(1,),name="x1")
input2 = K.layers.Input(shape=(1,),name="x2")
# 多输入情况下需要对输入层命名,这样在训练时才能传入正确的参数
dense = K.layers.Dense(units=10)
a = dense(input1)
b = dense(input2)
output = K.layers.Concatenate(name="y")([a,b])
model = K.models.Model(inputs=[input1,input2],outputs = [output])
model.compile(loss="mse",optimizer="adam")
model.summary()
model.fit(data,epochs=3,validation_data=vaild_data)
# 因为数据预处理的时候我们已经分了batch,所以这里也不需要传入batch_size
输出如下:
在训练中使用dataset
有时候我们需要对模型中输入的数据进行处理,比如对词进行编码的操作,我们期望模型自己能够实现词的编码,而不是每次都需要认为编码完成后,再输入进行。在tensorflow
的例子中是
K.layers.TextVectorization
这个类是常用keras.preprocessing.text.Tokenizer
的layer
版。如果我们要在训练中修改传入的数据,首先我们需要明白层级之间传入的数据到底是什么,答案是tensor
我们构造一个稍微复杂一点的模型,假设我们有一个NLP任务,由于数据集不够多,我们需要对数据进行增强,来扩充数据集。
这个时候我们可以通过自定义keras层来实现反转张量的操作,由于大部分情况下NLP直接输入的是一段词序列如:
data = ["i am john",
"you know something about john",
"i am john's friend",
"we group up and play together",
"you want to know jonh right",
]
# 我们假设为每个单词编号,一些没有实义的单词用tag填充,这个tag我们随意指定一个数组,
# 数据会变成这样:
data = [
[1,-1,2],
[3,4,5,-1,2],
[1,-1,6,7],
[8,9,10,11,12,13],
[3,14,-1,4,2,14],
]
# 为了保证句子长度一致会进行padding操作,这里使用0进行padding,
# 大部分分词器支持分词时填充到等长的序列
data = [
[1,-1,2,0,0,0],
[3,4,5,-1,2,0],
[1,-1,6,7,0,0],
[8,9,10,11,12,13],
[3,14,-1,4,2,14],
]
# 因为是分类任务,我们为句型打上标签,并转换为one-hot编码
y = to_categorical([0, 1, 0, 0, 2], num_classes=3)
data = tf.data.Dataset.from_tensor_slices(
(
{"x": [
[1, -1, 2, 0, 0, 0],
[3, 4, 5, -1, 2, 0],
[1, -1, 6, 7, 0, 0],
[8, 9, 10, 11, 12, 13],
[3, 14, -1, 4, 2, 14],
]
},
{"y": y})
)
# y = [[1. 0. 0.], [0. 1. 0.], [1. 0. 0.], [1. 0. 0.], [0. 0. 1.]] --narray
数据量非常少的情况,我们准备反转每个序列来进行操作,虽然这一步在预处理当中进行也可以,但是我们选择使用tf.data.Dataset
输入数据,使用自定义层进行反转操作。
class ReverseLayer(K.layers.Layer):
def __init__(self):
super(ReverseLayer, self).__init__()
def call(self, inputs, *args, **kwargs):
result = K.backend.reverse(inputs,-1)
return result
我们需要随时明确一个点,keras模型中任何一个部分传递的都是张量或者张量列表,实例如下,我们随意实现一个模型架构:
代码如下,这里用了重复、反转、随机化三个方法进行数据增强:
import tensorflow as tf
import tensorflow.keras as K
from keras.utils.np_utils import to_categorical
y = to_categorical([0, 1, 0, 0, 2], num_classes=3)
data = tf.data.Dataset.from_tensor_slices(
(
{"x": [
[1, -1, 2, 0, 0, 0],
[3, 4, 5, -1, 2, 0],
[1, -1, 6, 7, 0, 0],
[8, 9, 10, 11, 12, 13],
[3, 14, -1, 4, 2, 14],
]
},
{"y": y})
).repeat(count=100).batch(10).shuffle(1000)
class ReverseLayer(K.layers.Layer):
def __init__(self):
super(ReverseLayer, self).__init__()
def call(self, inputs, *args, **kwargs):
result = K.backend.reverse(inputs, -1)
return result
input1 = K.layers.Input(shape=(6,), name="x")
# 输入可以看作每个数据的格式是怎么样,这个与batchSize无关,除非你是batchSize等于
# dataSize
denseN = ReverseLayer()(input1)
# 自定义反转方法
denseN = K.layers.Dense(units=3,activation="relu")(denseN)
dense = K.layers.Dense(units=3,activation="relu")(input1)
# 为了保持和输出一致,首先使用全连接将(6,)->(3,)
Embedded = K.layers.Embedding(output_dim=10)
# 嵌入层会将向量维度转化成(3,10)也就是(3,output_dim)
denseN = Embedded(denseN)
dense = Embedded(dense)
output = K.layers.Add()([dense,denseN])
dense = K.layers.Dense(units=1,activation="softmax",name="y")(output)
# 将(3,10)->(3,1) 这个时候由于需要和标签对应,因此这里会将格式帮我们转成(None,3)
model = K.models.Model(inputs=input1, outputs=dense)
model.compile(loss="categorical_crossentropy", optimizer="adam")
model.summary()
model.fit(data, epochs=50, )
K.utils.plot_model(model,to_file="test.png")
看模型整体架构会更加清晰:
点击运行就可以成功运行了:
这里还有一些小问题没有修复,比如会提示warning 不存在梯度
,但这些tips主要是数据处理方面的,因此就暂不提了。