类是 Python 语言的基本构建块之一,可用于开发机器学习应用程序。正如我们将看到的,用于开发类的 Python 语法很简单,可以应用于在 Keras 中实现回调。
在本教程中,您将了解 Python 类及其功能。
完成本教程后,您将了解:
- 为什么 Python 类很重要
- 如何定义和实例化类并设置其属性
- 如何创建方法并传递参数
- 什么是类继承
- 如何使用类在 Keras 中实现回调
使用我的新书《Python for Machine Learning》快速开始您的项目,其中包含分步教程和所有示例的Python 源代码文件。
让我们开始吧。
Python 类及其在 Keras 中的使用
照片来源:S Migaj,部分权利保留。
教程概述
本教程分为六个部分;它们是:
- 类简介
- 定义类
- 实例化和属性引用
- 创建方法和传递参数
- 类继承
- 在 Keras 中使用类
类简介
在面向对象的语言(如 Python)中,类是基本构建块之一。
它们可以被比作对象的蓝图,因为它们定义了对象应具有的属性和方法/行为。
-《Python Fundamentals》,2018 年。
创建新类会创建一个新对象,每个类实例都可以通过其属性来维护其状态,并通过方法来修改其状态。
定义类
class 关键字允许创建新的类定义,后面立即跟着类名
1 2 |
class MyClass: <statements> |
通过这种方式,将创建一个绑定到指定类名(在本例中为 MyClass)的新类对象。每个类对象都可以支持实例化和属性引用,我们稍后将看到。
实例化和属性引用
实例化是创建类的另一个实例。
要创建类的另一个实例,我们可以使用其类名对其进行调用,并将其分配给一个变量。这将创建一个新的、空的类对象
1 |
x = MyClass() |
创建类的实例后,Python 会调用其对象构造函数方法 __init__(),该方法通常接受参数,用于设置已实例化对象的属性。
我们可以在类中像定义函数一样定义此构造函数方法,并指定在实例化对象时需要传递的属性。
-《Python Fundamentals》,2018 年。
例如,假设我们想定义一个名为 Dog 的新类
1 2 3 4 5 6 |
class Dog: family = "Canine" def __init__(self, name, breed): self.name = name self.breed = breed |
在这里,构造函数方法接受两个参数 name 和 breed,在实例化对象时可以将它们传递给它。
1 |
dog1 = Dog("Lassie", "Rough Collie") |
在我们考虑的示例中,name 和 breed 被称为实例变量(或属性),因为它们绑定到特定实例。这意味着这些属性仅属于设置了它们的特定对象,而不属于从同一类实例化的任何其他对象。
另一方面,family 是一个类变量(或属性),因为它由同一类的所有实例共享。
您可能还会注意到,构造函数方法(或任何其他方法)的第一个参数通常称为 self。此参数引用我们正在创建的对象。为了确保您的代码对其他程序员来说易于阅读,遵循将第一个参数设置为 self 的约定是一个好习惯。
一旦设置了对象的属性,就可以使用点运算符访问它们。例如,再次考虑 Dog 类的 dog1 实例,其 name 属性可以按如下方式访问:
1 |
print(dog1.name) |
产生以下输出
1 |
Lassie |
想开始学习机器学习 Python 吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
创建方法和传递参数
除了具有构造函数方法外,类对象还可以有几个其他方法来修改其状态。
定义实例方法的语法很熟悉。我们传递参数 self……它始终是实例方法的第一个参数。
-《Python Fundamentals》,2018 年。
与构造函数方法类似,每个实例方法都可以接受几个参数,第一个参数是 self 参数,它允许我们设置和访问对象的属性。
1 2 3 4 5 6 7 8 9 |
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 参数相互调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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]) |
然后可以按如下方式生成输出字符串
1 2 |
dog1 = Dog("Lassie", "Rough Collie") dog1.info("barks on command") |
我们发现,通过这样做,当 info() 方法调用 add_tricks() 方法时,“barks on command”输入会被追加到 tricks 列表中。产生以下输出:
1 |
Lassie is a female Rough Collie that barks on command |
类继承
Python 还支持另一个功能,即类的继承。
继承是一种机制,它允许子类(也称为派生类或子类)访问超类(也称为基类或父类)的所有属性和方法。
使用子类的语法如下
1 2 |
class SubClass(BaseClass): <statements> |
子类也可以继承自多个基类。在这种情况下,语法如下
1 2 |
class SubClass(BaseClass1, BaseClass2, BaseClass3): <statements> |
在基类以及多重继承的情况下,后续基类中也会搜索类属性和方法。
Python 还允许子类中的方法覆盖基类中同名的方法。子类中的覆盖方法可以替换基类方法,也可以仅扩展其功能。当存在覆盖的子类方法时,执行调用时将执行此方法,而不是基类中同名的方法。
在 Keras 中使用类
在 Keras 中使用类的实际用途是编写自己的回调。
回调是 Keras 中一个强大的工具,它允许我们在模型训练、测试和预测的不同阶段查看模型的行为。
事实上,我们可以将回调列表传递给以下任何一个:
- keras.Model.fit()
- keras.Model.evaluate()
- keras.Model.predict()
Keras API 附带了几个内置回调。尽管如此,我们可能希望编写自己的回调,为此,我们将研究如何构建一个自定义回调类。要做到这一点,我们可以从回调基类继承几个方法,这些方法可以为我们提供有关何时
- 训练、测试和预测开始和结束
- 一个 epoch 开始和结束
- 训练、测试和预测批次开始和结束
让我们首先考虑一个自定义回调的简单示例,该回调在每个 epoch 开始和结束时报告。我们将把这个自定义回调类命名为 EpochCallback,并覆盖基类 keras.callbacks.Callback 中的 epoch 级方法 on_epoch_begin() 和 on_epoch_end()。
1 2 3 4 5 6 7 8 |
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 模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 数据集。
1 2 3 4 5 6 7 8 9 10 |
from tensorflow.keras.datasets import mnist from tensorflow.keras.utils import to_categorical # 加载 MNIST 训练和测试数据 (x_train, y_train), (x_test, y_test) = mnist.load_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() 方法的回调列表中来尝试自定义回调。
1 2 3 4 5 6 7 8 |
model = simple_model() model.fit(x_train, y_train_cat, batch_size=32, epochs=5, callbacks=[EpochCallback()], verbose=0) |
我们刚刚创建的回调产生以下输出:
1 2 3 4 5 6 7 8 9 10 |
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 字典中读取损失值,该字典存储每个批次和 epoch 结束时的指标。我们还将通过 self.model 访问对应于当前训练、测试或预测回合的模型。
我们将这个自定义回调称为 CheckpointCallback。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 包含在回调列表中。
1 2 3 4 5 6 7 8 |
model = simple_model() model.fit(x_train, y_train_cat, batch_size=32, epochs=5, callbacks=[EpochCallback(), CheckpointCallback()], verbose=0) |
现在将生成两个回调组合的以下输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 文档中的一个示例:
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 |
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())) |
这揭示了为什么我们需要为自定义指标创建一个类:指标不仅仅是一个函数,而是一个在训练周期中每批训练数据计算其值的函数。最终,结果将在一个 epoch 结束时通过 result()
函数报告,并使用 reset_state()
函数重置其内存,以便您可以在下一个 epoch 中重新开始。
有关必须派生的确切内容的详细信息,您应该参考 Keras 的文档。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
书籍
- Python 基础知识, 2018.
网站
- Python 类,https://docs.pythonlang.cn/3/tutorial/classes.html
- 在 Keras 中创建自定义回调,https://tensorflowcn.cn/guide/keras/custom_callback
- 在 Keras 中创建自定义指标,https://keras.org.cn/api/metrics/#creating-custom-metrics
- 通过子类化创建新层和模型,https://keras.org.cn/guides/making_new_layers_and_models_via_subclassing/
总结
在本教程中,您将了解 Python 类及其功能。
具体来说,你学到了:
- 为什么 Python 类很重要
- 如何定义和实例化类并设置其属性
- 如何创建方法并传递参数
- 什么是类继承
- 如何使用类在 Keras 中实现回调
你有什么问题吗?
在下面的评论中提出您的问题,我将尽力回答。
对代码示例的一些小更正/评论
– 为 simple_model 函数定义添加冒号
– y_train -> y_train_cat 在 model.fit 中
– verbose=0 也添加到第一个 EpochCallback model.fit 示例中
感谢 Walle 的反馈!
谢谢 Stefania。非常重要的主题。
您能否说明一下,将模型定义为类(即 https://tensorflowcn.cn/guide/keras/custom_layers_and_models#the_model_class)还是通过函数简单地定义模型是更可取的(以及为什么)?
我最近一直在使用子类化来构建模型,因为这似乎是更 Pythonic 的方法。定义一个类,然后实例化该类的实例作为模型。但是,我见过的每个代码库和网站都使用函数来构建模型。子类化有什么好处吗?
如果没有,为什么 TensorFlow 要费力实现它?
你好 Michael…将模型定义为类取决于它是否会被频繁使用以及是否会被其他开发人员使用。
以下展示了如何利用包装器会很有益
https://www.programcreek.com/python/example/88637/keras.wrappers.scikit_learn.KerasClassifier