类是 Python 语言的基本构建块之一,可应用于机器学习应用程序的开发。正如我们将看到的,用于开发类的 Python 语法很简单,可以用于在 Keras 中实现回调。
在本教程中,您将发现 Python 类及其功能。:
- 为什么 Python 类很重要?
- 如何定义和实例化一个类并设置它的属性 ?
- 如何创建方法和传递参数?
- 什么是类继承?
- 如何在 Keras 中使用类实现回调?
让我们开始吧。
Python 类及其在 Keras 中的使用
类是 Python 语言的基本构建块之一,可应用于机器学习应用程序的开发。正如我们将看到的,用于开发类的 Python 语法很简单,可以用于在 Keras 中实现回调。
在本教程中,您将发现 Python 类及其功能。
完成本教程后,您将了解:
- 为什么 Python 类很重要
- 如何定义和实例化一个类并设置它的属性
- 如何创建方法和传递参数
- 什么是类继承
- 如何在 Keras 中使用类实现回调
让我们开始吧。
教程概述
本教程分为六个部分;他们是:
- 类介绍
- 定义一个类
- 实例化和属性引用
- 创建方法和传递参数
- 类继承
- 在 Keras 中使用类
类介绍
在面向对象的语言(如 Python)中,类是基本构建块之一。
创建一个新类会创建一个新对象,其中每个类实例都可以通过其属性来维护其状态和修改其状态的方法来表征。
定义一个类
class关键字允许创建一个新的类定义,紧跟类名:
class MyClass:
<statements>
以这种方式,将创建一个绑定到指定类名(在本例中为MyClass )的新类对象。每个类对象都可以支持实例化和属性引用,我们很快就会看到。
实例化和属性引用
实例化是创建一个类的新实例。
要创建一个类的新实例,我们可以使用它的类名调用它并将它分配给一个变量。这将创建一个新的空类对象:
x = MyClass()
在创建类的新实例时,Python 调用其对象构造方法__init()__,该方法通常采用用于设置实例化对象属性的参数。
例如,假设我们要定义一个名为Dog的新类:
class Dog:
family = "Canine"
def __init__(self, name, breed):
self.name = name
self.breed = breed
在这里,构造函数方法有两个参数,名称和品种,可以在实例化对象时传递给它:
dog1 = Dog("Lassie", "Rough Collie")
在我们正在考虑的示例中,名称和品种被称为实例变量(或属性),因为它们绑定到特定实例。这意味着这些属性仅属于已设置它们的对象,而不属于从同一类实例化的任何其他对象。
另一方面,family是一个类变量(或属性),因为它由同一类的所有实例共享。
您可能还注意到构造方法(或任何其他方法)的第一个参数通常称为self。这个参数是指我们正在创建的对象。遵循将第一个参数设置为self的约定是一种很好的做法, 以确保您的代码对其他程序员的可读性。
一旦我们设置了对象的属性,就可以使用点运算符来访问它们。例如,再次考虑Dog类的dog1实例,它的name属性可以按如下方式访问:
print(dog1.name)
产生以下输出:
Lassie
创建方法和传递参数
除了具有构造方法之外,类对象还可以具有其他几种修改其状态的方法。
与构造函数方法类似,每个实例方法都可以接受多个参数,第一个是参数self,它允许我们设置和访问对象的属性:
class Dog:
family = "Canine"
def __init__(self, name, breed):
self.name = name
self.breed = breed
def info(self):
print(self.name, "is a female", self.breed)
同一个对象的不同方法也可以使用self参数相互调用:
class Dog:
family = "Canine"
def __init__(self, name, breed):
self.name = name
self.breed = breed
self.tricks = []
def add_tricks(self, x):
self.tricks.append(x)
def info(self, x):
self.add_tricks(x)
print(self.name, "is a female", self.breed, "that", self.tricks[0])
然后可以按如下方式生成输出字符串:
dog1 = Dog("Lassie", "Rough Collie")
dog1.info("barks on command")
我们发现,这样做时,命令输入的吠声会在info()方法调用add_tricks()方法时附加到技巧列表中。产生以下输出:
Lassie is a female Rough Collie that barks on command
类继承
Python 支持的另一个特性是类继承。
继承是一种机制,它允许子类(也称为派生类或子类)访问超类(也称为基类或父类)的所有属性和方法。
使用子类的语法如下:
class SubClass(BaseClass):
<statements>
一个子类也有可能继承自多个基类。在这种情况下,语法如下:
class SubClass(BaseClass1, BaseClass2, BaseClass3):
<statements>
在基类中搜索类属性和方法,在多重继承的情况下也在后续基类中搜索。
Python 进一步允许子类中的方法覆盖基类中具有相同名称的另一个方法。子类中的覆盖方法可能正在替换基类方法或只是扩展其功能。当覆盖子类方法可用时,调用时执行的是该方法,而不是基类中的同名方法。
在 Keras 中使用类
Keras 中类的一个实际用途是编写自己的回调。
回调是 Keras 中的一个强大工具,它允许我们查看模型在训练、测试和预测的不同阶段的行为。
实际上,我们可以将回调列表传递给以下任何一个:
- keras.Model.fit()
- keras.Model.evaluate()
- keras.Model.predict()
Keras API 带有几个内置回调。尽管如此,我们可能希望编写自己的,为此,我们将研究如何构建自定义回调类。为此,我们可以从回调基类继承几个方法,这些方法可以为我们提供何时的信息:
- 训练、测试和预测的开始和结束
- 一个时代的开始和结束
- 训练、测试和预测批次的开始和结束
让我们首先考虑一个自定义回调的简单示例,该回调在每次 epoch 开始和结束时进行报告。我们将这个自定义回调类命名为EpochCallback ,并覆盖基类keras.callbacks.Callback中的纪元级方法on_epoch_begin()和on_epoch_end():
import tensorflow.keras as keras
class EpochCallback(keras.callbacks.Callback):
def on_epoch_begin(self, epoch, logs=None):
print("Starting epoch {}".format(epoch + 1))
def on_epoch_end(self, epoch, logs=None):
print("Finished epoch {}".format(epoch + 1))
为了测试我们刚刚定义的自定义回调,我们需要一个模型来训练。为此,让我们定义一个简单的 Keras 模型:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
def simple_model():
model = Sequential()
model.add(Flatten(input_shape=(28, 28)))
model.add(Dense(128, activation="relu"))
model.add(Dense(10, activation="softmax"))
model.compile(loss="categorical_crossentropy",
optimizer="sgd",
metrics=["accuracy"])
return model
我们还需要一个数据集进行训练,为此我们将使用 MNIST 数据集:
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
# Loading the MNIST training and testing data splits
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# Pre-processing the training data
x_train = x_train / 255.0
x_train = x_train.reshape(60000, 28, 28, 1)
y_train_cat = to_categorical(y_train, 10)
现在,让我们通过将自定义回调添加到我们作为输入传递给keras.Model.fit()方法的回调列表中来尝试自定义回调:
model = simple_model()
model.fit(x_train,
y_train_cat,
batch_size=32,
epochs=5,
callbacks=[EpochCallback()],
verbose=0)
我们刚刚创建的回调会产生以下输出:
Starting epoch 1
Finished epoch 1
Starting epoch 2
Finished epoch 2
Starting epoch 3
Finished epoch 3
Starting epoch 4
Finished epoch 4
Starting epoch 5
Finished epoch 5
我们可以创建另一个自定义回调来监控每个 epoch 结束时的损失值,并仅在损失减少时存储模型权重。为此,我们将从log dict 中读取损失值,它存储每个批次和 epoch 结束时的指标。我们还将通过self.model访问与当前一轮训练、测试或预测相对应的模型。
让我们将此自定义回调称为CheckpointCallback:
import numpy as np
class CheckpointCallback(keras.callbacks.Callback):
def __init__(self):
super(CheckpointCallback, self).__init__()
self.best_weights = None
def on_train_begin(self, logs=None):
self.best_loss = np.Inf
def on_epoch_end(self, epoch, logs=None):
current_loss = logs.get("loss")
print("Current loss is {}".format(current_loss))
if np.less(current_loss, self.best_loss):
self.best_loss = current_loss
self.best_weights = self.model.get_weights()
print("Storing the model weights at epoch {} \n".format(epoch + 1))
我们可以再试一次,这次将CheckpointCallback加入到回调列表中:
model = simple_model()
model.fit(x_train,
y_train_cat,
batch_size=32,
epochs=5,
callbacks=[EpochCallback(), CheckpointCallback()],
verbose=0)
现在生成了两个回调的以下输出:
Starting epoch 1
Finished epoch 1
Current loss is 0.6327750086784363
Storing the model weights at epoch 1
Starting epoch 2
Finished epoch 2
Current loss is 0.3391888439655304
Storing the model weights at epoch 2
Starting epoch 3
Finished epoch 3
Current loss is 0.29216915369033813
Storing the model weights at epoch 3
Starting epoch 4
Finished epoch 4
Current loss is 0.2625095248222351
Storing the model weights at epoch 4
Starting epoch 5
Finished epoch 5
Current loss is 0.23906977474689484
Storing the model weights at epoch 5
Keras的其他类
除了回调,我们还可以在 Keras 中为自定义指标(派生自keras.metrics.Metrics
)、自定义层(派生自keras.layers.Layer
)、自定义正则化器(派生自keras.regularizers.Regularizer
)甚至自定义模型(派生自keras.Model
,例如更改调用模型)。您所要做的就是按照指南更改类的成员函数。您必须在成员函数中使用完全相同的名称和参数。
以下是 Keras 文档中的示例:
class BinaryTruePositives(tf.keras.metrics.Metric):
def __init__(self, name='binary_true_positives', **kwargs):
super(BinaryTruePositives, self).__init__(name=name, **kwargs)
self.true_positives = self.add_weight(name='tp', initializer='zeros')
def update_state(self, y_true, y_pred, sample_weight=None):
y_true = tf.cast(y_true, tf.bool)
y_pred = tf.cast(y_pred, tf.bool)
values = tf.logical_and(tf.equal(y_true, True), tf.equal(y_pred, True))
values = tf.cast(values, self.dtype)
if sample_weight is not None:
sample_weight = tf.cast(sample_weight, self.dtype)
values = tf.multiply(values, sample_weight)
self.true_positives.assign_add(tf.reduce_sum(values))
def result(self):
return self.true_positives
def reset_states(self):
self.true_positives.assign(0)
m = BinaryTruePositives()
m.update_state([0, 1, 1, 1], [0, 1, 0, 0])
print('Intermediate result:', float(m.result()))
m.update_state([1, 1, 1, 1], [0, 1, 1, 0])
print('Final result:', float(m.result()))
这揭示了为什么我们需要一个自定义指标的类:指标不仅仅是一个函数,而是一个增量计算其值的函数,在训练周期中每批训练数据一次。最终,结果会在result()
一个 epoch 结束时在函数中报告,并使用该reset_state()
函数重置其内存,以便您可以在下一个 epoch 重新开始。
更多技术干货请关注小普