class类 是 Python 语言的基本构建块之一,可应用于机器学习应用程序的开发。用于开发的 class类 的 Python 语法很简单,也可以实现在 Keras 中实现回调。
Class 类
在面向对象的语言(如 Python)中,类是基本构建块之一。
《Python 基础》2018 年
They can be likened to blueprints for an object, as they define what properties and methods/behaviors an object should have.
它们可以比作对象的蓝图,因为它们定义了对象应具有的属性和方法/行为。
创建一个新类会创建一个新对象,其中每个类实例都可以通过其属性来维护其状态以及修改其状态的方法进行操作。
定义 Class 类
class 关键字允许创建一个新的类定义,紧随其后的是类名:
class MyClass:
<statements>
以这种方式,将创建一个绑定到指定类名的新类对象。每个类对象都可以支持实例化和属性引用。
实例化和属性引用
实例化是创建一个类的新实例。要创建一个类的新实例,可以使用它的类名调用它并将它分配给一个变量,创建一个新的空类对象。
x = MyClass()
在创建类的新实例时,Python 调用其对象构造方法 __ init() __ ,该方法通常采用用于设置实例化对象属性的参数。
《Python 基础》2018 年
We can define this constructor method in our class just like a function and specify attributes that will need to be passed in when instantiating an object.
我们可以像函数一样在类中定义这个构造方法,并指定实例化对象时需要传入的属性。
例如定义一个名为Dog的新类
class Dog:
family = "Canine"
def __init__(self, name, breed):
self.name = name
self.breed = breed
这里构造函数方法的两个参数,名称和品种。可以在实例化对象时传递:
dog1 = Dog("Lassie", "Rough Collie")
名称和品种被称为实例变量(或属性),因为它们绑定到特定实例。这意味着这些属性仅属于设置它们的对象,而不属于从同一类实例化的任何其他对象。
可能还注意到构造方法(或任何其他方法)的第一个参数通常称为self。这个参数是指我们正在创建的对象。遵循将第一个参数设置为self的约定是一种很好的做法,以确保您的代码对其他程序员的可读性。
一旦我们设置了对象的属性,就可以使用点运算符来访问它们。例如,再次考虑Dog类的dog1实例,它的name属性可以按如下方式访问:
print(dog1.name)
>>> Lassie
创建方法和传递参数
除了具有构造方法之外,类对象还可以具有其他几种修改其状态的方法。
《Python 基础》2018 年
The syntax for defining an instance method is familiar. We pass the argument self … It is always the first argument of an instance method.
定义实例方法的语法很熟悉。我们传递参数 self … 它始终是实例方法的第一个参数。
与构造方法类似,每个实例方法都可以接受多个参数,第一个是参数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 中类的一个实际用途是 callback 。 callback 是 Keras 中的一个强大工具,可以利用其查看模型在训练、测试和预测的不同阶段的操作行为。
实际上,我们可以将回调列表传递给以下任何一个:
- keras.Model.fit()
- hard.Model.evaluate()
- hard.Model.predict()
Keras API 带有几个内置 callback。可以基于此通过集成的方式构建自己的 callback 函数方法类,这些方法可以为我们提供何时的信息。
- 训练、测试和预测开始和结束。
- 一个 epoch 开始和结束。
- 训练、测试和预测 batch 开始和结束。
一个自定义回调的简单示例,callback 在每次 epoch 开始和结束时进行报告。将这个自定义回调类命名为 EpochCallback ,并覆盖基类keras.callbacks.Callback 中的 epoch 方法 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))
通过模型训练来测试自定义回调。
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
# 加载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() 方法的回调列表测试自定义 callback。
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 中读取损失值,它存储每个 batch 和 epoch 结束时的指标。还将通过 self.model 访问与当前一轮训练、测试或预测相对应的模型。
# 检查回调
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))
再试一次将C heckpointCallback 加入到回调列表中。
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()))
需要一个用于自定义指标的类,指标不仅仅是一个函数,而是一个增量计算其值的函数,在训练周期中 batch 训练数据一次,最终结果会在 result() 一个 epoch 结束时在函数中报告,并使用 reset_state() 函数重置其内存,以便您可以在下一个 epoch 重新开始。