在使用python的时候,经常用到python中的特殊函数:__call()__函数,但是学习TensorFlow的时候,又会看到自定义模型的时候,经常用call()函数,不懂他们之间的区别。
python中的__call()__函数和TensorFlow中的call()函数是两个完全不同的函数,但是又有一定的联系。
该方法是对基类的重载函数,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。
比如:
class CLanguage:
# 定义__call__方法
def __call__(self,name,add):
print("调用__call__()方法",name,add)
clangs = CLanguage()
clangs("C语言中文网","http://c.biancheng.net")
程序执行结果为:
调用__call__()方法 C语言中文网 http://c.biancheng.net
可以看到,通过在 CLanguage 类中实现 __call__() 方法,使的 clangs 实例对象变为了可调用对象。Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及本节所讲的类实例对象。
对于可调用对象,实际上“名称()”可以理解为是“名称.__call__()”的简写。仍以上面程序中定义的 clangs 实例对象为例,其最后一行代码还可以改写为如下形式:
clangs.__call__("C语言中文网","http://c.biancheng.net")
运行程序会发现,其运行结果和之前完全相同。
def say():
print("Python教程:http://c.biancheng.net/python")
say()
say.__call__()
程序执行结果为:
Python教程:http://c.biancheng.net/python
Python教程:http://c.biancheng.net/python
用 __call__() 弥补 hasattr() 函数的短板:前面章节介绍了 hasattr() 函数的用法,该函数的功能是查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法。要解决这个问题,我们可以借助可调用对象的概念。要知道,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是。举个例子:
class CLanguage:
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def say(self):
print("我正在学Python")
clangs = CLanguage()
if hasattr(clangs,"name"):
print(hasattr(clangs.name,"__call__"))
print("**********")
if hasattr(clangs,"say"):
print(hasattr(clangs.say,"__call__"))
程序执行结果为:
False
**********
True
可以看到,由于 name 是类属性,它没有以 __call__ 为名的 __call__() 方法;而 say 是类方法,它是可调用对象,因此它有 __call__() 方法。
关于python中特殊函数的使用,可以看http://c.biancheng.net/view/2380.html中对部分特殊函数的详细介绍。
class Network(keras.Model):
# 回归网络模型
def __init__(self):
super(Network, self).__init__()
# 创建 3 个全连接层
self.fc1 = layers.Dense(64, activation='relu')
self.fc2 = layers.Dense(64, activation='relu')
self.fc3 = layers.Dense(1)
def call(self, inputs, training=None, mask=None):
# 依次通过 3 个全连接层
x = self.fc1(inputs)
x = self.fc2(x)
x = self.fc3(x)
return x
model = Network() # 创建网络类实例
# 通过 build 函数完成内部张量的创建,其中 4 为任意设置的 batch 数量,9 为输入特征长度
model.build(input_shape=(4, 9))
model.summary() # 打印网络信息
optimizer = tf.keras.optimizers.RMSprop(0.001) # 创建优化器,指定学习率
接下来实现网络训练部分。通过 Epoch 和 Step 组成的双层循环训练网络,共训练 200
个 Epoch,代码如下:
for epoch in range(200): # 200 个 Epoch
for step, (x,y) in enumerate(train_db): # 遍历一次训练集
# 梯度记录器,训练时需要使用它
with tf.GradientTape() as tape:
out = model(x) # 通过网络获得输出
loss = tf.reduce_mean(losses.MSE(y, out)) # 计算 MSE
mae_loss = tf.reduce_mean(losses.MAE(y, out)) # 计算 MAE
if step % 10 == 0: # 间隔性地打印训练误差
print(epoch, step, float(loss))
# 计算梯度,并更新
grads = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))