盒子
盒子

当我遇到tensorflow2.x时

前言

近日,使用tensorflow的频率比较高,使用过程中也是遇到了一些大大小小的问题。有些着实让人脑瓜子疼。此刻借着模型训练的时间,开始码码字。本来标题想取:

  • 什么?2021了,还有人用Tensorflow?
  • 震惊!代码练习生竟然在用······Tensorflow?
  • 我和tensorflow不共戴天
  • 我想给tensorflow来一大嘴巴子
  • ······

最后,用了这个当我遇到tensorflow2.x时 。无论学习还是生活中,我们都会遇到各种各样的人或事或物。当我们遇到时,会发生什么?我们是会充满期待的。当我遇到···时,我会··· 。这个句式是我喜欢的,大部分人习惯在前半部分大胆设想,后半句夸下豪言壮语。这里取前半句,是因为已经发生了,而省去后半句,恰恰是因为豪言壮语很容易翻车。

这是一篇记录使用tensorflow过程中遇到的一些小而折磨人的问题的博文。但我预言这也将是一篇持久的对tensorflow的血泪吐槽文。

如何看待Keras正式从TensorFlow中分离?

不知道为什么想到了这个知乎话题。六月份的某天,Keras 之父 Francois Chollet宣布将 Keras 的代码从 TensorFlow 代码库中分离出来,移回到了自己的 repo。乍一看,还以为以后tensorflow的keras接口用不了了。但人家只是把keras代码搬回了属于自己的repo。原本的tf.keras 还是能用的。

For you as a user, absolutely nothing changes, now or in the future.

底下全是一片叫好,天下苦tensorflow久已。而我也并不看好这对情侣或者说组合。各自单飞,独自美丽不好吗?keras何必委曲求全做别人的嫁衣。

抛开keras,tensorflow还剩什么?

我想这应该是吐槽后,该冷静思考的问题。而回答这个问题,是需要去阅读官方文档以及实践的。所以,那个句式的后半句也可以是下面的记录。才疏学浅,当厚积薄发。

言归正传,之后遇到的bug都记录在下面部分。

—————————————–———-—-————分割线——————-—————————————————–—

tf.config.run_functions_eagerly(True)

有关Eager Execution 戳这里

然后以下是我粗俗的理解:

这是即时运行和计算图运行相关的概念。即时运行可以让你的程序立马返回结果,计算图运行会先构建计算图(记录你的程序执行行为及顺序),在最后按照构建的图进行计算。

有些晦涩难理解。

模型训练时一般有:

1
2
3
4
@tf.function
def train_step():
with tf.GradientTape() as tape:
···train model code···

这在模型训练过程中是会构建计算图的(具体参考戳这里),构建计算图可以,这时如果在代码中print(x) 一下,就会发现这是没有具体值的,而且没有.numpy() 属性。返回的即计算图中节点的符号句柄 。所以我为什么要在这里print呢?当然是为了调试代码(/滑稽.jpg)。

1
Tensor("x:0", shape=(32, 32), dtype=int32)

官网提到tensorflow2.x是默认开启Eager Execution 的,然而代码中(如上)使用了@tf.function 装饰器,默认以图的方式执行。

1
The code in a Function can be executed both eagerly and as a graph. By default, Function executes its code as a graph.

要关闭默认方式,可以通过设置:tf.config.run_functions_eagerly(True) 来实现。或者干脆不要加这个装饰器。

最后,Eager Execution 增强了开发和调试的交互性,而@tf.function 计算图执行在分布式训练、性能优化和生产部署方面具有优势。简而言之,Eager Execution适合开发过程中调试,@tf.function适合线上部署。

——————-2021.9.17更新———————

自定义

参考->这里

定义模型

抛开keras的sequential, 使用 tensorflow定义模型时,可以有两种继承选择:tf.keras.Modeltf.Module

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class TFModel(tf.Module):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.w = tf.Variable(5.0)
self.b = tf.Variable(0.0)

def __call__(self, x):
return self.w * x + self.b
tf_model = TFModel()

class KerasModel(tf.keras.Model):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.w = tf.Variable(5.0)
self.b = tf.Variable(0.0)

def call(self, x, **kwargs):
return self.w * x + self.b
keras_model = KerasModel()

定义训练循环:

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
import tensorflow as tf
import random
import numpy as np

def train_model(x_train,y_train,x_valid,y_valid,model,epochs = 5,batch_size = 64, lr =0.001, print_freq = 10):
loss_object = tf.keras.losses.SparseCategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam(learning_rate=lr)

train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')

for epoch in range(epochs):
# 在下一个epoch开始时,重置评估指标
train_loss.reset_states()
train_accuracy.reset_states()
test_loss.reset_states()
test_accuracy.reset_states()
for step in range(int(len(x_train)/batch_size)):
rand_id = np.asarray(random.sample(range(len(x_train)), batch_size))
bs_x_train = x_train[rand_id]
bs_y_train = y_train[rand_id]

# train step
with tf.GradientTape() as tape:
predictions = model(bs_x_train)
loss = loss_object(bs_y_train, predictions)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
train_loss(loss)
train_accuracy(bs_y_train, predictions)

# test step
predictions = model(x_valid)
t_loss = loss_object(y_valid, predictions)
test_loss(t_loss)
test_accuracy(y_valid, predictions)

# print info
if step%10==0:
template = 'Epoch {},step {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
print(template.format(epoch+1,
step,
train_loss.result(),
train_accuracy.result()*100,
test_loss.result(),
test_accuracy.result()*100))

template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
print(template.format(epoch+1,
train_loss.result(),
train_accuracy.result()*100,
test_loss.result(),
test_accuracy.result()*100))

return model

若是继承自tf.keras.Model 则可以使用 model.compile() 去设置参数, 使用model.fit() 进行训练。

1
2
3
4
5
6
7
8
keras_model = KerasModel()
keras_model.compile(
# 默认情况下,fit()调用tf.function()。
# Debug时你可以关闭这一功能,但是现在是打开的。
run_eagerly=False,
optimizer=tf.keras.optimizers.SGD(learning_rate=0.1),
loss=tf.keras.losses.mean_squared_error,
)

——————-2021.9.18更———————

种子

为了确保每次运行结果的稳定,设置固定种子是有必要的。

1
2
3
random.seed(2021)
np.random.seed(2021)
tf.random.set_seed(2021)

——————-2021.9.19更———————

当自定义模型时,继承tf.keras.Model 则需要实现call 方法而不是__call__ 。如果是tf.Module 就实现__call__ 。尽量使用tf.keras.Model ,因为真的很方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class KerasModel(tf.keras.Model):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.w = tf.Variable(5.0)
self.b = tf.Variable(0.0)

def call(self, x, **kwargs):
return self.w * x + self.b
model = KerasModel()
bst_model_path = "./best.model.h5"

early_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_accuracy', patience=5)

model_checkpoint = tf.keras.callbacks.ModelCheckpoint(bst_model_path, save_best_only=True, save_weights_only=True)

model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
optimizer=tf.keras.optimizers.Adam(1e-3),
metrics=['accuracy'])
model.fit(x_train, train_y,
batch_size=16,
validation_data=(x_valid,valid_y),
epochs=200,
callbacks=[early_stopping,model_checkpoint]
)

——————-2021.10.4更———————

从python生成器中加载数据

官方文档:consuming_python_generators

code template:

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

def get_dataset(args, batch_size,shuffle=False):
output_types = (tf.int32, tf.int32, tf.int32, tf.int32)
output_shapes = ((None,),
(None,),
(None,2),
(None,)
)
dataset = tf.data.Dataset.from_generator(generator_fn,
args=args,
output_types= output_types,
output_shapes = output_shapes
)
if shuffle:
dataset = dataset.shuffle(buffer_size = 1000*batch_size)

# dataset = dataset.repeat() 这行代码有毒
dataset = dataset.padded_batch(batch_size=batch_size, padded_shapes=output_shapes, padding_values=(0,0,0,0))
dataset = dataset.prefetch(1)

return dataset

output_types 是必须的,buffer_size 一般取大于等于数据集大小。generator_fn 为生成器函数。如下:

1
2
3
4
5
def count(stop):
i = 0
while i<stop:
yield i
i += 1

——————-2021.10.5更———————

有关call()

子类化tf.keras.Model时,在实现call() 函数需要注意的是接受的参数一般只能是两个:inputstraining

training 一般给用户自定义训练模式提供一定的自由度,training 为布尔类型。当然,training 是非必须的。

1
2
3
4
5
6
7
8
9
class KerasModel(tf.keras.Model):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.w = tf.Variable(5.0)
self.b = tf.Variable(0.0)

def call(self, x):

return self.w * x + self.b

如果模型需要多个输入时:可以通过 inputs = (x1,x2,x3,...)inputs 传入

1
2
3
··省略··
def call(self, inputs):
x1,x2,x3,... = inputs

当然也可以通过**kwargs 将其他数据传入。而不是像这样:

1
2
def call(self, x1,x2,x3,...):
···

类型转换

1
tf.cast(x, dtype=tf.int64)

——————-2021.10.16更———————

tf.keras.layers.MultiHeadAttention

1
2
3
multi = tf.keras.layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)

out = multi(v,k,q,mask*-1e9)

如果mask是0、1矩阵,记得乘以-1e9 ,否者掩码无效。

TODO

支持一下
  • 微信扫一扫
  • 支付宝扫一扫