Machine Learning Mastery Python 教程(五)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

猴子补丁 Python 代码

原文:machinelearningmastery.com/monkey-patching-python-code/

Python 是一种动态脚本语言。它不仅具有动态类型系统,允许变量首先分配为一种类型然后后续改变,而且它的对象模型也是动态的。这使得我们可以在运行时修改其行为。其结果是可以进行猴子补丁。这是一个想法,我们可以在不修改高层代码的情况下修改程序的基础层。想象一下,你可以使用 print() 函数将内容打印到屏幕上,我们可以修改该函数的定义,将其打印到文件中,而无需修改你的任何一行代码。

这是可能的,因为 Python 是一种解释型语言,因此我们可以在程序运行时进行更改。我们可以利用这一特性在 Python 中修改类或模块的接口。如果我们处理遗留代码或其他人的代码,我们不想对其进行广泛修改,但仍然希望在不同版本的库或环境中运行它,这就很有用。在本教程中,我们将看到如何将这一技术应用于一些 Keras 和 TensorFlow 代码。

完成本教程后,你将学到:

  • 什么是猴子补丁

  • 如何在运行时更改 Python 中的对象或模块

启动你的项目,通过我的新书 Python for Machine Learning,包括 一步步的教程Python 源代码 文件,用于所有示例。

让我们开始吧!

猴子补丁 Python 代码。照片由 Juan Rumimpunu 提供。保留所有权利。

教程概述

本教程分为三部分;它们是:

  • 一个模型,两种接口

  • 使用猴子补丁扩展对象

  • 猴子补丁以复兴遗留代码

一个模型,两种接口

TensorFlow 是一个庞大的库。它提供了一个高层 Keras API 来描述深度学习模型的层次结构。它还附带了很多用于训练的函数,如不同的优化器和数据生成器。仅仅因为我们需要运行训练后的模型,安装 TensorFlow 就显得很繁琐。因此,TensorFlow 提供了一个名为 TensorFlow Lite 的对等产品,体积更小,适合在诸如移动设备或嵌入式设备等小型设备上运行。

我们希望展示原始 TensorFlow Keras 模型和 TensorFlow Lite 模型的不同使用方式。因此,让我们制作一个中等大小的模型,比如 LeNet-5 模型。以下是我们如何加载 MNIST 数据集并训练一个分类模型:

import numpy as np
import tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, Dense, AveragePooling2D, Dropout, Flatten
from tensorflow.keras.callbacks import EarlyStopping

# Load MNIST data
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# Reshape data to shape of (n_sample, height, width, n_channel)
X_train = np.expand_dims(X_train, axis=3).astype('float32')
X_test = np.expand_dims(X_test, axis=3).astype('float32')

# LeNet5 model: ReLU can be used intead of tanh
model = Sequential([
    Conv2D(6, (5,5), input_shape=(28,28,1), padding="same", activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(16, (5,5), activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(120, (5,5), activation="tanh"),
    Flatten(),
    Dense(84, activation="tanh"),
    Dense(10, activation="softmax")
])

# Training
model.compile(loss="sparse_categorical_crossentropy", optimizer="adam", metrics=["sparse_categorical_accuracy"])
earlystopping = EarlyStopping(monitor="val_loss", patience=4, restore_best_weights=True)
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=32, callbacks=[earlystopping])

运行上述代码将使用 TensorFlow 的数据集 API 下载 MNIST 数据集并训练模型。之后,我们可以保存模型:

model.save("lenet5-mnist.h5")

或者我们可以用测试集评估模型:

print(np.argmax(model.predict(X_test), axis=1))
print(y_test)

然后我们应该看到:

[7 2 1 ... 4 5 6]
[7 2 1 ... 4 5 6]

但如果我们打算在 TensorFlow Lite 中使用它,我们希望将其转换为 TensorFlow Lite 格式,如下所示:

# tflite conversion with dynamic range optimization
import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()

# Optional: Save the data for testing
import numpy as np
np.savez('mnist-test.npz', X=X_test, y=y_test)

# Save the model.
with open('lenet5-mnist.tflite', 'wb') as f:
    f.write(tflite_model)

我们可以向转换器添加更多选项,例如将模型减少为使用 16 位浮点数。但在所有情况下,转换的输出是二进制字符串。转换不仅会将模型缩减到比从 Keras 保存的 HDF5 文件小得多的尺寸,还会允许我们使用轻量级库。有适用于 Android 和 iOS 移动设备的库。如果你使用嵌入式 Linux,可能会找到来自 PyPI 仓库的 tflite-runtime 模块(或从 TensorFlow 源代码编译一个)。下面是如何使用 tflite-runtime 运行转换后的模型:

import numpy as np
import tflite_runtime.interpreter as tflite

loaded = np.load('mnist-test.npz')
X_test = loaded["X"]
y_test = loaded["y"]
interpreter = tflite.Interpreter(model_path="lenet5-mnist.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
print(input_details[0]['shape'])

rows = []
for n in range(len(X_test)):
    # this model has single input and single output
    interpreter.set_tensor(input_details[0]['index'], X_test[n:n+1])
    interpreter.invoke()
    row = interpreter.get_tensor(output_details[0]['index'])
    rows.append(row)
rows = np.vstack(rows)

accuracy = np.sum(np.argmax(rows, axis=1) == y_test) / len(y_test)
print(accuracy)

实际上,更大的 TensorFlow 库也可以用类似的语法运行转换后的模型:

import numpy as np
import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path="lenet5-mnist.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

rows = []
for n in range(len(X_test)):
    # this model has single input and single output
    interpreter.set_tensor(input_details[0]['index'], X_test[n:n+1])
    interpreter.invoke()
    row = interpreter.get_tensor(output_details[0]['index'])
    rows.append(row)
rows = np.vstack(rows)

accuracy = np.sum(np.argmax(rows, axis=1) == y_test) / len(y_test)
print(accuracy)

注意使用模型的不同方式:在 Keras 模型中,我们有 predict() 函数,它以批次为输入并返回结果。然而,在 TensorFlow Lite 模型中,我们必须一次注入一个输入张量到“解释器”并调用它,然后检索结果。

将所有内容结合起来,下面的代码展示了如何构建一个 Keras 模型、训练它、将其转换为 TensorFlow Lite 格式,并用转换后的模型进行测试:

import numpy as np
import tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, Dense, AveragePooling2D, Dropout, Flatten
from tensorflow.keras.callbacks import EarlyStopping

# Load MNIST data
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# Reshape data to shape of (n_sample, height, width, n_channel)
X_train = np.expand_dims(X_train, axis=3).astype('float32')
X_test = np.expand_dims(X_test, axis=3).astype('float32')

# LeNet5 model: ReLU can be used intead of tanh
model = Sequential([
    Conv2D(6, (5,5), input_shape=(28,28,1), padding="same", activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(16, (5,5), activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(120, (5,5), activation="tanh"),
    Flatten(),
    Dense(84, activation="tanh"),
    Dense(10, activation="softmax")
])

# Training
model.compile(loss="sparse_categorical_crossentropy", optimizer="adam", metrics=["sparse_categorical_accuracy"])
earlystopping = EarlyStopping(monitor="val_loss", patience=4, restore_best_weights=True)
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=32, callbacks=[earlystopping])

# Save model
model.save("lenet5-mnist.h5")

# Compare the prediction vs test data
print(np.argmax(model.predict(X_test), axis=1))
print(y_test)

# tflite conversion with dynamic range optimization
import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()

# Optional: Save the data for testing
import numpy as np
np.savez('mnist-test.npz', X=X_test, y=y_test)

# Save the tflite model.
with open('lenet5-mnist.tflite', 'wb') as f:
    f.write(tflite_model)

# Load the tflite model and run test
interpreter = tf.lite.Interpreter(model_path="lenet5-mnist.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

rows = []
for n in range(len(X_test)):
    # this model has single input and single output
    interpreter.set_tensor(input_details[0]['index'], X_test[n:n+1])
    interpreter.invoke()
    row = interpreter.get_tensor(output_details[0]['index'])
    rows.append(row)
rows = np.vstack(rows)

accuracy = np.sum(np.argmax(rows, axis=1) == y_test) / len(y_test)
print(accuracy)

想要开始使用 Python 进行机器学习吗?

现在就领取我的免费 7 天电子邮件速成课程(包括示例代码)。

点击注册,还可以获得课程的免费 PDF 电子书版本。

使用猴子补丁扩展对象

我们可以在 TensorFlow Lite 解释器中使用 predict() 吗?

解释器对象没有这样的函数。但是,由于我们使用 Python,我们可以使用 猴子补丁 技术添加它。要理解我们在做什么,首先我们要注意,在之前的代码中定义的 interpreter 对象可能包含许多属性和函数。当我们像调用函数一样调用 interpreter.predict() 时,Python 会在对象内部寻找这样一个名称,然后执行它。如果没有找到这样的名称,Python 会引发 AttributeError 异常:

...
interpreter.predict()

这将产生:

Traceback (most recent call last):
  File "/Users/MLM/pred_error.py", line 13, in <module>
    interpreter.predict()
AttributeError: 'Interpreter' object has no attribute 'predict'

要使其工作,我们需要向 interpreter 对象添加一个名称为 predict 的函数,并且在调用时应表现得像一个函数。为了简单起见,我们注意到我们的模型是一个顺序模型,输入是一个数组,输出是一个 softmax 结果的数组。因此,我们可以编写一个类似于 Keras 模型中 predict() 函数的函数,但使用 TensorFlow Lite 解释器:

...

# Monkey patching the tflite model
def predict(self, input_batch):
    batch_size = len(input_batch)
    output = []

    input_details = self.get_input_details()
    output_details = self.get_output_details()
    # Run each sample from the batch
    for sample in range(batch_size):
        self.set_tensor(input_details[0]["index"], input_batch[sample:sample+1])
        self.invoke()
        sample_output = self.get_tensor(output_details[0]["index"])
        output.append(sample_output)

    # vstack the output of each sample
    return np.vstack(output)

interpreter.predict = predict.__get__(interpreter)

上述最后一行将我们创建的函数分配给 interpreter 对象,名称为 predict__get__(interpreter) 部分是必需的,以便将我们定义的函数变为 interpreter 对象的成员函数。

有了这些,我们现在可以运行一个批次:

...
out_proba = interpreter.predict(X_test)
out = np.argmax(out_proba, axis=1)
print(out)

accuracy = np.sum(out == y_test) / len(y_test)
print(accuracy)
[7 2 1 ... 4 5 6]
0.9879

这是可能的,因为 Python 具有动态对象模型。我们可以在运行时修改对象的属性或成员函数。实际上,这不应该让我们感到惊讶。一个 Keras 模型需要运行model.compile()才能运行model.fit()compile函数的一个效果是将loss属性添加到模型中以保存损失函数。这是在运行时完成的。

添加了predict()函数到interpreter对象后,我们可以像使用训练好的 Keras 模型进行预测一样传递interpreter对象。尽管在幕后它们有所不同,但它们共享相同的接口,因此其他函数可以在不修改任何代码行的情况下使用它。

下面是完整的代码,用于加载我们保存的 TensorFlow Lite 模型,然后对predict()函数进行猴子补丁,使其看起来像 Keras 模型:

import numpy as np
import tensorflow as tf
from tensorflow.keras.datasets import mnist

# Load MNIST data and reshape
(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = np.expand_dims(X_train, axis=3).astype('float32')
X_test = np.expand_dims(X_test, axis=3).astype('float32')

# Monkey patching the tflite model
def predict(self, input_batch):
    batch_size = len(input_batch)
    output = []

    input_details = self.get_input_details()
    output_details = self.get_output_details()
    # Run each sample from the batch
    for sample in range(batch_size):
        self.set_tensor(input_details[0]["index"], input_batch[sample:sample+1])
        self.invoke()
        sample_output = self.get_tensor(output_details[0]["index"])
        output.append(sample_output)

    # vstack the output of each sample
    return np.vstack(output)

# Load and monkey patch
interpreter = tf.lite.Interpreter(model_path="lenet5-mnist.tflite")
interpreter.predict = predict.__get__(interpreter)
interpreter.allocate_tensors()

# test output
out_proba = interpreter.predict(X_test)
out = np.argmax(out_proba, axis=1)
print(out)
accuracy = np.sum(out == y_test) / len(y_test)
print(accuracy)

猴子补丁以恢复遗留代码

我们可以给出一个 Python 中猴子补丁的另一个示例。考虑以下代码:

# https://machinelearningmastery.com/dropout-regularization-deep-learning-models-keras/
# Example of Dropout on the Sonar Dataset: Hidden Layer
from pandas import read_csv
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.wrappers.scikit_learn import KerasClassifier
from keras.constraints import maxnorm
from keras.optimizers import SGD
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# load dataset
dataframe = read_csv("sonar.csv", header=None)
dataset = dataframe.values
# split into input (X) and output (Y) variables
X = dataset[:,0:60].astype(float)
Y = dataset[:,60]
# encode class values as integers
encoder = LabelEncoder()
encoder.fit(Y)
encoded_Y = encoder.transform(Y)

# dropout in hidden layers with weight constraint
def create_model():
	# create model
	model = Sequential()
	model.add(Dense(60, input_dim=60, activation='relu', kernel_constraint=maxnorm(3)))
	model.add(Dropout(0.2))
	model.add(Dense(30, activation='relu', kernel_constraint=maxnorm(3)))
	model.add(Dropout(0.2))
	model.add(Dense(1, activation='sigmoid'))
	# Compile model
	sgd = SGD(lr=0.1, momentum=0.9)
	model.compile(loss='binary_crossentropy', optimizer=sgd, metrics=['accuracy'])
	return model

estimators = []
estimators.append(('standardize', StandardScaler()))
estimators.append(('mlp', KerasClassifier(build_fn=create_model, epochs=300, batch_size=16, verbose=0)))
pipeline = Pipeline(estimators)
kfold = StratifiedKFold(n_splits=10, shuffle=True)
results = cross_val_score(pipeline, X, encoded_Y, cv=kfold)
print("Hidden: %.2f%% (%.2f%%)" % (results.mean()*100, results.std()*100))

这段代码编写于几年前,假设使用的是旧版本的 Keras 和 TensorFlow 1.x。数据文件sonar.csv可以在另一篇文章中找到。如果我们使用 TensorFlow 2.5 运行此代码,将会看到SGD行出现ImportError。我们需要至少在上述代码中进行两个更改以使其运行:

  1. 函数和类应该从tensorflow.keras而不是keras中导入

  2. 约束类maxnorm应该使用驼峰命名法,MaxNorm

以下是更新后的代码,其中我们仅修改了导入语句:

# Example of Dropout on the Sonar Dataset: Hidden Layer
from pandas import read_csv
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Dropout
from tensorflow.keras.wrappers.scikit_learn import KerasClassifier
from tensorflow.keras.constraints import MaxNorm as maxnorm
from tensorflow.keras.optimizers import SGD
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# load dataset
dataframe = read_csv("sonar.csv", header=None)
dataset = dataframe.values
# split into input (X) and output (Y) variables
X = dataset[:,0:60].astype(float)
Y = dataset[:,60]
# encode class values as integers
encoder = LabelEncoder()
encoder.fit(Y)
encoded_Y = encoder.transform(Y)

# dropout in hidden layers with weight constraint
def create_model():
	# create model
	model = Sequential()
	model.add(Dense(60, input_dim=60, activation='relu', kernel_constraint=maxnorm(3)))
	model.add(Dropout(0.2))
	model.add(Dense(30, activation='relu', kernel_constraint=maxnorm(3)))
	model.add(Dropout(0.2))
	model.add(Dense(1, activation='sigmoid'))
	# Compile model
	sgd = SGD(lr=0.1, momentum=0.9)
	model.compile(loss='binary_crossentropy', optimizer=sgd, metrics=['accuracy'])
	return model

estimators = []
estimators.append(('standardize', StandardScaler()))
estimators.append(('mlp', KerasClassifier(build_fn=create_model, epochs=300, batch_size=16, verbose=0)))
pipeline = Pipeline(estimators)
kfold = StratifiedKFold(n_splits=10, shuffle=True)
results = cross_val_score(pipeline, X, encoded_Y, cv=kfold)
print("Hidden: %.2f%% (%.2f%%)" % (results.mean()*100, results.std()*100))

如果我们有一个更大的项目,包含许多脚本,那么修改每一行导入将是繁琐的。但 Python 的模块系统实际上是一个sys.modules中的字典。因此,我们可以对其进行猴子补丁,使旧代码适配新库。以下是如何做的。这适用于 TensorFlow 2.5 安装(Keras 代码的向后兼容性问题在 TensorFlow 2.9 中已修复;因此在最新版本的库中不需要这种补丁):

# monkey patching
import sys
import tensorflow.keras
tensorflow.keras.constraints.maxnorm = tensorflow.keras.constraints.MaxNorm
for x in sys.modules.keys():
    if x.startswith("tensorflow.keras"):
        sys.modules[x[len("tensorflow."):]] = sys.modules[x]

# Old code below:

# Example of Dropout on the Sonar Dataset: Hidden Layer
from pandas import read_csv
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.wrappers.scikit_learn import KerasClassifier
from keras.constraints import maxnorm
from keras.optimizers import SGD
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# load dataset
dataframe = read_csv("sonar.csv", header=None)
dataset = dataframe.values
# split into input (X) and output (Y) variables
X = dataset[:,0:60].astype(float)
Y = dataset[:,60]
# encode class values as integers
encoder = LabelEncoder()
encoder.fit(Y)
encoded_Y = encoder.transform(Y)

# dropout in hidden layers with weight constraint
def create_model():
	# create model
	model = Sequential()
	model.add(Dense(60, input_dim=60, activation='relu', kernel_constraint=maxnorm(3)))
	model.add(Dropout(0.2))
	model.add(Dense(30, activation='relu', kernel_constraint=maxnorm(3)))
	model.add(Dropout(0.2))
	model.add(Dense(1, activation='sigmoid'))
	# Compile model
	sgd = SGD(lr=0.1, momentum=0.9)
	model.compile(loss='binary_crossentropy', optimizer=sgd, metrics=['accuracy'])
	return model

estimators = []
estimators.append(('standardize', StandardScaler()))
estimators.append(('mlp', KerasClassifier(build_fn=create_model, epochs=300, batch_size=16, verbose=0)))
pipeline = Pipeline(estimators)
kfold = StratifiedKFold(n_splits=10, shuffle=True)
results = cross_val_score(pipeline, X, encoded_Y, cv=kfold)
print("Hidden: %.2f%% (%.2f%%)" % (results.mean()*100, results.std()*100))

这绝对不是干净整洁的代码,并且未来维护将是一个问题。因此,猴子补丁在生产代码中是不受欢迎的。然而,这是一种快速技术,利用了 Python 语言的内部机制,使事情能够轻松工作。

进一步阅读

本节提供了更多有关该主题的资源,如果你希望深入了解。

文章

总结

在本教程中,我们学习了什么是猴子补丁以及如何进行猴子补丁。具体来说,

  • 我们学习了如何向现有对象添加成员函数

  • 如何修改 sys.modules 中的 Python 模块缓存以欺骗 import 语句

Python 中的多处理

原文:machinelearningmastery.com/multiprocessing-in-python/

当你在进行计算机视觉项目时,你可能需要处理大量图像数据。这是耗时的,如果能够并行处理多个图像会更好。多处理是指系统能够同时运行多个处理器。如果你有一台单处理器的计算机,它会在多个进程之间切换以保持它们的运行。然而,如今大多数计算机至少配备了多核处理器,可以同时执行多个进程。Python 的多处理模块是一个工具,可以通过将任务分配给不同的进程来提高脚本的效率。

完成本教程后,你将会知道:

  • 为什么我们要使用多处理

  • 如何使用 Python 多处理模块中的基本工具

**通过我的新书《机器学习的 Python》**启动你的项目,其中包括一步步的教程和所有示例的Python 源代码文件。

开始吧.外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Python 中的多处理

图片来源:Thirdman。保留部分权利。

概述

本教程分为四个部分,它们是:

  • 多处理的好处

  • 基本多处理

  • 实际应用中的多处理

  • 使用 joblib

多处理的好处

你可能会问,“为什么要使用多处理?”多处理可以通过并行运行多个任务而不是顺序运行,显著提高程序的效率。一个类似的术语是多线程,但它们是不同的。

进程是加载到内存中运行的程序,并且不与其他进程共享内存。线程是进程中的一个执行单元。多个线程在一个进程中运行,并且共享进程的内存空间。

Python 的全局解释器锁(GIL)只允许解释器下的一个线程同时运行,这意味着如果需要 Python 解释器,你不能享受多线程的性能提升。这就是为什么多处理在 Python 中优于多线程。多个进程可以并行运行,因为每个进程都有自己的解释器来执行分配给它的指令。此外,操作系统会将你的程序视为多个进程,并分别调度它们,即你的程序总的来说获得了更多的计算机资源。因此,当程序是 CPU 绑定时,多处理更快。在程序中有大量 I/O 的情况下,线程可能更有效,因为大多数时候,程序在等待 I/O 完成。然而,多处理通常更高效,因为它是并发运行的。

基本多处理

让我们使用 Python 的 Multiprocessing 模块编写一个基本程序,演示如何进行并发编程。

让我们看看这个函数task(),它在休眠 0.5 秒钟后打印休眠前后的内容:

import time

def task():
    print('Sleeping for 0.5 seconds')
    time.sleep(0.5)
    print('Finished sleeping')

要创建一个进程,我们只需使用 multiprocessing 模块:

...
import multiprocessing
p1 = multiprocessing.Process(target=task)
p2 = multiprocessing.Process(target=task)

Process()target参数指定了进程运行的目标函数。但这些进程不会立即运行,直到我们启动它们:

...
p1.start()
p2.start()

完整的并发程序如下:

import multiprocessing
import time

def task():
    print('Sleeping for 0.5 seconds')
    time.sleep(0.5)
    print('Finished sleeping')

if __name__ == "__main__":
    start_time = time.perf_counter()

    # Creates two processes
    p1 = multiprocessing.Process(target=task)
    p2 = multiprocessing.Process(target=task)

    # Starts both processes
    p1.start()
    p2.start()

    finish_time = time.perf_counter()

    print(f"Program finished in {finish_time-start_time} seconds")

我们必须将主程序放在if __name__ == "__main__"下,否则multiprocessing模块会报错。这种安全结构确保 Python 在创建子进程之前完成程序分析。

但是,代码有个问题,因为程序计时器在我们创建的进程执行之前就已打印出来。以下是上面代码的输出:

Program finished in 0.012921249988721684 seconds
Sleeping for 0.5 seconds
Sleeping for 0.5 seconds
Finished sleeping
Finished sleeping

我们需要对两个进程调用join()函数,以确保它们在时间打印之前运行。这是因为有三个进程在运行:p1p2和主进程。主进程负责跟踪时间并打印执行所需的时间。我们应该确保finish_time的那一行在p1p2进程完成之前不会运行。我们只需在start()函数调用后立即添加这段代码:

...
p1.join()
p2.join()

join()函数允许我们使其他进程等待,直到对其调用了join()的进程完成。以下是添加了join()语句后的输出:

Sleeping for 0.5 seconds
Sleeping for 0.5 seconds
Finished sleeping
Finished sleeping
Program finished in 0.5688213340181392 seconds

使用类似的推理,我们可以运行更多的进程。以下是从上面修改的完整代码,设置为 10 个进程:

import multiprocessing
import time

def task():
    print('Sleeping for 0.5 seconds')
    time.sleep(0.5)
    print('Finished sleeping')

if __name__ == "__main__": 
    start_time = time.perf_counter()
    processes = []

    # Creates 10 processes then starts them
    for i in range(10):
        p = multiprocessing.Process(target = task)
        p.start()
        processes.append(p)

    # Joins all the processes 
    for p in processes:
        p.join()

    finish_time = time.perf_counter()

    print(f"Program finished in {finish_time-start_time} seconds")

想要开始学习 Python 进行机器学习吗?

现在立即参加我的免费 7 天邮件速成课程(附有示例代码)。

点击注册,并获取课程的免费 PDF 电子书版本。

实际应用中的多进程

启动一个新进程然后将其合并回主进程是 Python(以及许多其他语言)中多进程的工作方式。我们想要运行多进程的原因可能是为了并行执行多个不同的任务以提高速度。这可以是一个图像处理函数,我们需要对数千张图像进行处理。也可以是将 PDF 转换为文本以进行后续的自然语言处理任务,我们需要处理一千个 PDF。通常,我们会创建一个接收参数(例如,文件名)的函数来完成这些任务。

让我们考虑一个函数:

def cube(x):
    return x**3

如果我们想要运行从 1 到 1,000 的参数,我们可以创建 1,000 个进程并行运行:

import multiprocessing

def cube(x):
    return x**3

if __name__ == "__main__":
    # this does not work
    processes = [multiprocessing.Process(target=cube, args=(x,)) for x in range(1,1000)]
    [p.start() for p in processes]
    result = [p.join() for p in processes]
    print(result)

但是,这不会有效,因为你可能只有少量的核心。运行 1,000 个进程会造成过多的开销,超出操作系统的容量。此外,你可能已经耗尽了内存。更好的方法是运行进程池,以限制同时运行的进程数量:

import multiprocessing
import time

def cube(x):
    return x**3

if __name__ == "__main__":
    pool = multiprocessing.Pool(3)
    start_time = time.perf_counter()
    processes = [pool.apply_async(cube, args=(x,)) for x in range(1,1000)]
    result = [p.get() for p in processes]
    finish_time = time.perf_counter()
    print(f"Program finished in {finish_time-start_time} seconds")
    print(result)

multiprocessing.Pool()的参数是要在池中创建的进程数。如果省略,Python 将其设置为你计算机中的核心数量。

我们使用apply_async()函数将参数传递给cube函数的列表推导。这将为池创建任务。它被称为“async”(异步),因为我们没有等待任务完成,主进程可能会继续运行。因此,apply_async()函数不会返回结果,而是一个我们可以使用的对象get(),以等待任务完成并检索结果。由于我们在列表推导中获取结果,因此结果的顺序对应于我们在异步任务中创建的参数。然而,这并不意味着进程在池中按此顺序启动或完成。

如果你认为编写代码行来启动进程并将其连接起来过于显式,你可以考虑使用map()代替:

import multiprocessing
import time

def cube(x):
    return x**3

if __name__ == "__main__":
    pool = multiprocessing.Pool(3)
    start_time = time.perf_counter()
    result = pool.map(cube, range(1,1000))
    finish_time = time.perf_counter()
    print(f"Program finished in {finish_time-start_time} seconds")
    print(result)

我们在这里没有使用 start 和 join,因为它们被隐藏在pool.map()函数后面。pool.map()的作用是将可迭代对象range(1,1000)拆分成块,并在池中运行每个块。map 函数是列表推导的并行版本:

result = [cube(x) for x in range(1,1000)]

但现代的替代方法是使用concurrent.futures中的map,如下所示:

import concurrent.futures
import time

def cube(x):
    return x**3

if __name__ == "__main__":
    with concurrent.futures.ProcessPoolExecutor(3) as executor:
        start_time = time.perf_counter()
        result = list(executor.map(cube, range(1,1000)))
        finish_time = time.perf_counter()
    print(f"Program finished in {finish_time-start_time} seconds")
    print(result)

这段代码在后台运行了multiprocessing模块。这样做的好处是,我们可以通过将ProcessPoolExecutor替换为ThreadPoolExecutor,将程序从多进程改为多线程。当然,你必须考虑全局解释器锁是否对你的代码构成问题。

使用 joblib

joblib包是一组使并行计算更简单的工具。它是用于多进程的常见第三方库。它还提供缓存和序列化功能。要安装joblib包,请在终端中使用以下命令:

pip install joblib

我们可以将之前的例子转换为如下,以使用joblib

import time
from joblib import Parallel, delayed

def cube(x):
    return x**3

start_time = time.perf_counter()
result = Parallel(n_jobs=3)(delayed(cube)(i) for i in range(1,1000))
finish_time = time.perf_counter()
print(f"Program finished in {finish_time-start_time} seconds")
print(result)

实际上,看到它的作用是直观的。delayed()函数是对另一个函数的包装器,旨在创建一个“延迟”的函数调用版本。这意味着它在被调用时不会立即执行函数。

然后我们多次调用延迟函数,并传递我们希望传递给它的不同参数集。例如,当我们将整数1传递给cube函数的延迟版本时,我们不会计算结果,而是为函数对象、位置参数和关键字参数分别生成一个元组(cube, (1,), {})

我们使用Parallel()创建了引擎实例。当它像函数一样被调用并传入包含元组的列表作为参数时,它实际上会并行执行每个元组指定的任务,并在所有任务完成后将结果汇总为一个列表。在这里,我们使用n_jobs=3创建了Parallel()实例,因此会有三个进程并行运行。

我们还可以直接写出元组。因此,上面的代码可以重写为:

result = Parallel(n_jobs=3)((cube, (i,), {}) for i in range(1,1000))

使用 joblib 的好处在于,我们可以通过简单地添加一个额外的参数来在多线程中运行代码:

result = Parallel(n_jobs=3, prefer="threads")(delayed(cube)(i) for i in range(1,1000))

这隐藏了并行运行函数的所有细节。我们只需使用一种与普通列表推导式差别不大的语法。

进一步阅读

本节提供了更多关于该主题的资源,如果你想深入了解。

书籍
API

总结

在本教程中,你学习了如何并行运行 Python 函数以提高速度。特别是,你学到了:

  • 如何使用 multiprocessing 模块在 Python 中创建运行函数的新进程

  • 启动和完成进程的机制

  • multiprocessing 中使用进程池进行受控的多进程处理以及 concurrent.futures 中的对应语法

  • 如何使用第三方库 joblib 进行多进程处理

Python 类及其在 Keras 中的使用

原文:machinelearningmastery.com/python-classes-and-their-use-in-keras/

类是 Python 语言的基本构建块之一,可以应用于机器学习应用的开发。正如我们将看到的,Python 的类开发语法很简单,可以用于实现 Keras 中的回调。

在本教程中,你将发现 Python 类及其功能。

完成本教程后,你将知道:

  • 为什么 Python 类很重要

  • 如何定义和实例化类并设置其属性

  • 如何创建方法并传递参数

  • 什么是类继承

  • 如何使用类来实现 Keras 中的回调

通过我的新书Python for Machine Learning逐步教程Python 源代码文件,启动你的项目

让我们开始吧。外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Python 类及其在 Keras 中的使用

图片由 S Migaj 提供,部分权利保留。

教程概述

本教程分为六个部分,它们是:

  • 类的介绍

  • 定义一个类

  • 实例化和属性引用

  • 创建方法并传递参数

  • 类继承

  • 在 Keras 中使用类

类的介绍

在面向对象的语言中,如 Python,类是基本构建块之一。

它们可以比作对象的蓝图,因为它们定义了对象应具有的属性和方法/行为。

Python Fundamentals, 2018.

创建一个新类会创建一个新对象,其中每个类实例可以通过其属性来描述,以保持其状态,并通过方法来修改其状态。

定义一个类

class 关键字允许创建新的类定义,紧接着是类名:

Python

class MyClass:
    <statements>

这样,就创建了一个绑定到指定类名(MyClass,在此情况下)的新类对象。每个类对象都可以支持实例化和属性引用,我们将很快看到。

实例化和属性引用

实例化是创建类的新实例。

要创建类的新实例,我们可以使用类名调用它,并将其分配给一个变量。这将创建一个新的空类对象:

Python

x = MyClass()

创建类的新实例时,Python 调用其对象构造方法, init(),该方法通常接受用于设置实例化对象属性的参数。

我们可以像定义函数一样在类中定义这个构造函数方法,并指定在实例化对象时需要传递的属性。

Python 基础,2018 年。

比如说,我们希望定义一个名为 Dog 的新类:

Python

class Dog:
	family = "Canine"

	def __init__(self, name, breed):
		self.name = name
		self.breed = breed

在这里,构造函数方法接受两个参数,namebreed,这些参数在实例化对象时可以传递给它:

Python

dog1 = Dog("Lassie", "Rough Collie")

在我们考虑的例子中,namebreed 被称为 实例变量(或属性),因为它们绑定到特定的实例。这意味着这些属性属于它们被设置的对象,而不属于从同一类实例化的其他对象。

另一方面,family 是一个 类变量(或属性),因为它由同一类的所有实例共享。

您还可以注意到,构造函数方法(或任何其他方法)的第一个参数通常被称为 self。这个参数指的是我们正在创建的对象。遵循将第一个参数设置为 self 的惯例,有助于提高代码的可读性,便于其他程序员理解。

一旦我们设置了对象的属性,可以使用点操作符来访问它们。例如,再考虑 Dog 类的 dog1 实例,它的 name 属性可以如下访问:

Python

print(dog1.name)

产生如下输出:

Python

Lassie

想要开始学习用于机器学习的 Python 吗?

立即参加我的免费 7 天电子邮件速成课程(附有示例代码)。

点击注册,您还可以获得课程的免费 PDF 电子书版本。

创建方法和传递参数

除了拥有构造函数方法,类对象还可以有多个其他方法来修改其状态。

定义实例方法的语法很熟悉。我们传递参数 self … 它总是实例方法的第一个参数。

Python 基础,2018 年。

类似于构造函数方法,每个实例方法可以接受多个参数,第一个参数是 self,它让我们能够设置和访问对象的属性:

Python

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 参数来相互调用:

Python

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])

然后可以生成如下输出字符串:

Python

dog1 = Dog("Lassie", "Rough Collie")
dog1.info("barks on command")

我们发现,在这样做时,barks on command 输入会在 info() 方法调用 add_tricks() 方法时附加到 tricks 列表中。产生如下输出:

Python

Lassie is a female Rough Collie that barks on command

类继承

Python 还支持另一个特性,即类的 继承

继承是一种机制,允许子类(也称为派生类)访问超类(也称为类或类)的所有属性和方法。

使用子类的语法如下:

Python

class SubClass(BaseClass):
    <statements>

子类也可以从多个基类继承。在这种情况下,语法如下:

Python

class SubClass(BaseClass1, BaseClass2, BaseClass3):
    <statements>

类属性和方法在基类中以及在多重继承的情况下也会在后续的基类中进行搜索。

Python 还允许子类中的方法覆盖基类中具有相同名称的另一个方法。子类中的覆盖方法可能会替代基类方法或只是扩展其功能。当存在覆盖的子类方法时,调用时执行的是这个方法,而不是基类中具有相同名称的方法。

在 Keras 中使用类

在 Keras 中使用类的一个实际用途是编写自己的回调。

回调是 Keras 中的一个强大工具,它允许我们在训练、测试和预测的不同阶段观察模型的行为。

确实,我们可以将回调列表传递给以下任意一个:

  • keras.Model.fit()

  • keras.Model.evaluate()

  • keras.Model.predict()

Keras API 提供了几个内置回调。尽管如此,我们可能希望编写自己的回调,为此,我们将看看如何构建一个custom回调类。为此,我们可以继承回调基类中的几个方法,这些方法可以为我们提供以下信息:

  • 训练、测试和预测开始和结束

  • 一个周期开始和结束

  • 训练、测试和预测批次开始和结束

让我们首先考虑一个简单的自定义回调的示例,该回调每次周期开始和结束时都会报告。我们将这个自定义回调类命名为EpochCallback,并覆盖基类keras.callbacks.Callback中的周期级方法on_epoch_begin()on_epoch_end()

Python

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 模型:

Python

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 数据集:

Python

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()*方法的回调列表中来尝试一下自定义回调:

Python

model = simple_model()

model.fit(x_train,
          y_train_cat,
          batch_size=32,
          epochs=5,
          callbacks=[EpochCallback()],
          verbose=0)

我们刚刚创建的回调产生了以下输出:

Python

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

我们可以创建另一个自定义回调,在每个周期结束时监控损失值,并仅在损失减少时存储模型权重。为此,我们将从log字典中读取损失值,该字典存储每个批次和周期结束时的指标。我们还将通过self.model访问与当前训练、测试或预测轮次对应的模型。

我们将这个自定义回调称为CheckpointCallback

Python

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包含到回调列表中:

Python

model = simple_model()

model.fit(x_train,
          y_train_cat,
          batch_size=32,
          epochs=5,
          callbacks=[EpochCallback(), CheckpointCallback()],
          verbose=0)

现在产生了两个回调的以下输出:

Python

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()函数报告,并使用reset_state()函数重置其内存,以便在下一个纪元中重新开始。

有关具体需要派生的内容,请参阅 Keras 的文档。

进一步阅读

如果你希望深入了解这个主题,这部分提供了更多的资源。

书籍

网站

总结

在本教程中,你了解了 Python 类及其功能。

具体来说,你学到了:

  • 为什么 Python 类很重要

  • 如何定义和实例化类并设置其属性

  • 如何创建方法和传递参数

  • 什么是类继承

  • 如何使用类来实现 Keras 中的回调

你有任何问题吗?

在下面的评论中提问,我会尽力回答。

Python 调试工具

原文:machinelearningmastery.com/python-debugging-tools/

在所有编程练习中,没有一个方便的调试器是很难深入进行的。Python 内置的调试器 pdb 是一个成熟且强大的工具,如果你知道如何使用它,它能为我们提供很大帮助。在本教程中,我们将了解 pdb 能为你做什么以及一些替代工具。

在本教程中,你将学习:

  • 调试器能做什么

  • 如何控制调试器

  • Python 的 pdb 及其替代方案的局限性

启动你的项目,请查看我的新书《机器学习 Python》,包括逐步教程所有示例的 Python 源代码文件。

让我们开始吧!

Python 调试工具

图片由托马斯·帕克提供。保留所有权利。

教程概述

本教程分为 4 部分;它们是

  • 调试器的运行概念

  • 使用调试器的演练

  • Visual Studio Code 中的调试器

  • 在运行中的 Python 程序上使用 GDB

调试器的运行概念

调试器的目的是为你提供一个慢动作按钮来控制程序的流程。它还允许你在某个时间点冻结程序并检查状态。

在调试器下,最简单的操作是逐步执行代码。即一次运行一行代码,等待你的确认后再继续下一行。我们希望以这种停停走走的方式运行程序,是为了检查逻辑和数值或验证算法。

对于较大的程序,我们可能不想从头开始逐步执行代码,因为这可能需要很长时间才能到达我们感兴趣的行。因此,调试器还提供了一个断点功能,当达到特定代码行时它会触发。从那时起,我们可以逐行执行代码。

使用调试器的演练

让我们看看如何使用调试器,通过一个例子来演示。以下是用于显示粒子群优化动画的 Python 代码:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

def f(x,y):
    "Objective function"
    return (x-3.14)**2 + (y-2.72)**2 + np.sin(3*x+1.41) + np.sin(4*y-1.73)

# Compute and plot the function in 3D within [0,5]x[0,5]
x, y = np.array(np.meshgrid(np.linspace(0,5,100), np.linspace(0,5,100)))
z = f(x, y)

# Find the global minimum
x_min = x.ravel()[z.argmin()]
y_min = y.ravel()[z.argmin()]

# Hyper-parameter of the algorithm
c1 = c2 = 0.1
w = 0.8

# Create particles
n_particles = 20
np.random.seed(100)
X = np.random.rand(2, n_particles) * 5
V = np.random.randn(2, n_particles) * 0.1

# Initialize data
pbest = X
pbest_obj = f(X[0], X[1])
gbest = pbest[:, pbest_obj.argmin()]
gbest_obj = pbest_obj.min()

def update():
    "Function to do one iteration of particle swarm optimization"
    global V, X, pbest, pbest_obj, gbest, gbest_obj
    # Update params
    r1, r2 = np.random.rand(2)
    V = w * V + c1*r1*(pbest - X) + c2*r2*(gbest.reshape(-1,1)-X)
    X = X + V
    obj = f(X[0], X[1])
    pbest[:, (pbest_obj >= obj)] = X[:, (pbest_obj >= obj)]
    pbest_obj = np.array([pbest_obj, obj]).min(axis=0)
    gbest = pbest[:, pbest_obj.argmin()]
    gbest_obj = pbest_obj.min()

# Set up base figure: The contour map
fig, ax = plt.subplots(figsize=(8,6))
fig.set_tight_layout(True)
img = ax.imshow(z, extent=[0, 5, 0, 5], origin='lower', cmap='viridis', alpha=0.5)
fig.colorbar(img, ax=ax)
ax.plot([x_min], [y_min], marker='x', markersize=5, color="white")
contours = ax.contour(x, y, z, 10, colors='black', alpha=0.4)
ax.clabel(contours, inline=True, fontsize=8, fmt="%.0f")
pbest_plot = ax.scatter(pbest[0], pbest[1], marker='o', color='black', alpha=0.5)
p_plot = ax.scatter(X[0], X[1], marker='o', color='blue', alpha=0.5)
p_arrow = ax.quiver(X[0], X[1], V[0], V[1], color='blue', width=0.005, angles='xy', scale_units='xy', scale=1)
gbest_plot = plt.scatter([gbest[0]], [gbest[1]], marker='*', s=100, color='black', alpha=0.4)
ax.set_xlim([0,5])
ax.set_ylim([0,5])

def animate(i):
    "Steps of PSO: algorithm update and show in plot"
    title = 'Iteration {:02d}'.format(i)
    # Update params
    update()
    # Set picture
    ax.set_title(title)
    pbest_plot.set_offsets(pbest.T)
    p_plot.set_offsets(X.T)
    p_arrow.set_offsets(X.T)
    p_arrow.set_UVC(V[0], V[1])
    gbest_plot.set_offsets(gbest.reshape(1,-1))
    return ax, pbest_plot, p_plot, p_arrow, gbest_plot

anim = FuncAnimation(fig, animate, frames=list(range(1,50)), interval=500, blit=False, repeat=True)
anim.save("PSO.gif", dpi=120, writer="imagemagick")

print("PSO found best solution at f({})={}".format(gbest, gbest_obj))
print("Global optimal at f({})={}".format([x_min,y_min], f(x_min,y_min)))

粒子群优化通过执行 update() 函数若干次来完成。每次运行时,我们距离目标函数的最优解越来越近。我们使用 matplotlib 的 FuncAnimation() 函数来代替循环执行 update(),以便我们可以捕捉每次迭代中粒子的位置。

假设这个程序保存为 pso.py。要在命令行中运行这个程序,只需输入:

python pso.py

结果将被打印到屏幕上,动画将保存为 PSO.gif。但如果我们想使用 Python 调试器运行它,我们需要在命令行中输入以下内容:

python -m pdb pso.py

-m pdb部分将加载pdb模块并让该模块为你执行文件pso.py。当你运行此命令时,你将看到如下的pdb提示符:

> /Users/mlm/pso.py(1)<module>()
-> import numpy as np
(Pdb)

在提示符下,你可以输入调试器命令。要显示支持的命令列表,我们可以使用h。要显示特定命令的详细信息(例如list),我们可以使用h list

> /Users/mlm/pso.py(1)<module>()
-> import numpy as np
(Pdb) h

Documented commands (type help <topic>):
========================================
EOF    c          d        h         list      q        rv       undisplay
a      cl         debug    help      ll        quit     s        unt
alias  clear      disable  ignore    longlist  r        source   until
args   commands   display  interact  n         restart  step     up
b      condition  down     j         next      return   tbreak   w
break  cont       enable   jump      p         retval   u        whatis
bt     continue   exit     l         pp        run      unalias  where

Miscellaneous help topics:
==========================
exec  pdb

(Pdb)

在调试会话开始时,我们从程序的第一行开始。通常,Python 程序会以几行import开头。我们可以使用n移动到下一行,或使用s进入函数:

> /Users/mlm/pso.py(1)<module>()
-> import numpy as np
(Pdb) n
> /Users/mlm/pso.py(2)<module>()
-> import matplotlib.pyplot as plt
(Pdb) n
> /Users/mlm/pso.py(3)<module>()
-> from matplotlib.animation import FuncAnimation
(Pdb) n
> /Users/mlm/pso.py(5)<module>()
-> def f(x,y):
(Pdb) n
> /Users/mlm/pso.py(10)<module>()
-> x, y = np.array(np.meshgrid(np.linspace(0,5,100), np.linspace(0,5,100)))
(Pdb) n
> /Users/mlm/pso.py(11)<module>()
-> z = f(x, y)
(Pdb) s
--Call--
> /Users/mlm/pso.py(5)f()
-> def f(x,y):
(Pdb) s
> /Users/mlm/pso.py(7)f()
-> return (x-3.14)**2 + (y-2.72)**2 + np.sin(3*x+1.41) + np.sin(4*y-1.73)
(Pdb) s
--Return--
> /Users/mlm/pso.py(7)f()->array([[17.25... 7.46457344]])
-> return (x-3.14)**2 + (y-2.72)**2 + np.sin(3*x+1.41) + np.sin(4*y-1.73)
(Pdb) s
> /Users/mlm/pso.py(14)<module>()
-> x_min = x.ravel()[z.argmin()]
(Pdb)

pdb中,代码行会在提示符之前打印出来。通常,我们更倾向于使用n命令,因为它会执行那一行代码并在相同级别上移动流程,而不会进一步深入。当我们处于调用函数的行(例如上述程序中的第 11 行,运行z = f(x, y))时,我们可以使用s进入函数。

在上述示例中,我们首先进入了f()函数,然后执行计算,再将返回值从函数中收集出来并返回到调用函数的行。我们发现即使是像一行这样的简单函数,也需要多个s命令,因为从语句中找到函数、调用函数和返回函数各需一步。我们还看到,在函数体内,我们像调用函数一样调用了np.sin(),但调试器的s命令没有进入它。这是因为np.sin()函数不是用 Python 实现的,而是用 C 实现的。pdb不支持编译代码。

如果程序很长,多次使用n命令移动到我们感兴趣的地方会非常无聊。我们可以使用until命令和行号,让调试器运行程序直到到达那一行:

> /Users/mlm/pso.py(1)<module>()
-> import numpy as np
(Pdb) until 11
> /Users/mlm/pso.py(11)<module>()
-> z = f(x, y)
(Pdb) s
--Call--
> /Users/mlm/pso.py(5)f()
-> def f(x,y):
(Pdb) s
> /Users/mlm/pso.py(7)f()
-> return (x-3.14)**2 + (y-2.72)**2 + np.sin(3*x+1.41) + np.sin(4*y-1.73)
(Pdb) s
--Return--
> /Users/mlm/pso.py(7)f()->array([[17.25... 7.46457344]])
-> return (x-3.14)**2 + (y-2.72)**2 + np.sin(3*x+1.41) + np.sin(4*y-1.73)
(Pdb) s
> /Users/mlm/pso.py(14)<module>()
-> x_min = x.ravel()[z.argmin()]
(Pdb)

类似于until的命令是return,它将执行当前函数直到即将返回的点。你可以将其视为until,行号等于当前函数的最后一行。until命令是一次性的,意味着它只会将你带到那一行。如果你希望每次程序运行到特定行时停下来,我们可以在该行设置断点。例如,如果我们对优化算法每次迭代如何移动解感兴趣,可以在应用更新后立即设置一个断点:

> /Users/mlm/pso.py(1)<module>()
-> import numpy as np
(Pdb) b 40
Breakpoint 1 at /Users/mlm/pso.py:40
(Pdb) c
> /Users/mlm/pso.py(40)update()
-> obj = f(X[0], X[1])
(Pdb) bt
  /usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/bdb.py(580)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  /Users/mlm/pso.py(76)<module>()
-> anim.save("PSO.gif", dpi=120, writer="imagemagick")
  /usr/local/lib/python3.9/site-packages/matplotlib/animation.py(1078)save()
-> anim._init_draw()  # Clear the initial frame
  /usr/local/lib/python3.9/site-packages/matplotlib/animation.py(1698)_init_draw()
-> self._draw_frame(frame_data)
  /usr/local/lib/python3.9/site-packages/matplotlib/animation.py(1720)_draw_frame()
-> self._drawn_artists = self._func(framedata, *self._args)
  /Users/mlm/pso.py(65)animate()
-> update()
> /Users/mlm/pso.py(40)update()
-> obj = f(X[0], X[1])
(Pdb) p r1
0.8054505373292797
(Pdb) p r2
0.7543489945823536
(Pdb) p X
array([[2.77550474, 1.60073607, 2.14133019, 4.11466522, 0.2445649 ,
        0.65149396, 3.24520628, 4.08804798, 0.89696478, 2.82703884,
        4.42055413, 1.03681404, 0.95318658, 0.60737118, 1.17702652,
        4.67551174, 3.95781321, 0.95077669, 4.08220292, 1.33330594],
       [2.07985611, 4.53702225, 3.81359193, 1.83427181, 0.87867832,
        1.8423856 , 0.11392109, 1.2635162 , 3.84974582, 0.27397365,
        2.86219806, 3.05406841, 0.64253831, 1.85730719, 0.26090638,
        4.28053621, 4.71648133, 0.44101305, 4.14882396, 2.74620598]])
(Pdb) n
> /Users/mlm/pso.py(41)update()
-> pbest[:, (pbest_obj >= obj)] = X[:, (pbest_obj >= obj)]
(Pdb) n
> /Users/mlm/pso.py(42)update()
-> pbest_obj = np.array([pbest_obj, obj]).min(axis=0)
(Pdb) n
> /Users/mlm/pso.py(43)update()
-> gbest = pbest[:, pbest_obj.argmin()]
(Pdb) n
> /Users/mlm/pso.py(44)update()
-> gbest_obj = pbest_obj.min()
(Pdb)

在我们使用b命令设置断点后,可以让调试器运行程序直到断点被触发。c命令表示继续执行,直到遇到触发条件。任何时候,我们都可以使用bt命令显示 traceback 来检查我们是如何到达当前点的。我们也可以使用p命令打印变量(或表达式)来检查它们保存的值。

确实,我们可以设置带条件的断点,以便只有在满足条件时才会停下。下面的条件是第一个随机数(r1)大于 0.5:

(Pdb) b 40, r1 > 0.5
Breakpoint 1 at /Users/mlm/pso.py:40
(Pdb) c
> /Users/mlm/pso.py(40)update()
-> obj = f(X[0], X[1])
(Pdb) p r1, r2
(0.8054505373292797, 0.7543489945823536)
(Pdb) c
> /Users/mlm/pso.py(40)update()
-> obj = f(X[0], X[1])
(Pdb) p r1, r2
(0.5404045753007164, 0.2967937508800147)
(Pdb)

确实,我们还可以尝试在调试过程中操作变量。

(Pdb) l
 35  	    global V, X, pbest, pbest_obj, gbest, gbest_obj
 36  	    # Update params
 37  	    r1, r2 = np.random.rand(2)
 38  	    V = w * V + c1*r1*(pbest - X) + c2*r2*(gbest.reshape(-1,1)-X)
 39  	    X = X + V
 40 B->	    obj = f(X[0], X[1])
 41  	    pbest[:, (pbest_obj >= obj)] = X[:, (pbest_obj >= obj)]
 42  	    pbest_obj = np.array([pbest_obj, obj]).min(axis=0)
 43  	    gbest = pbest[:, pbest_obj.argmin()]
 44  	    gbest_obj = pbest_obj.min()
 45
(Pdb) p V
array([[ 0.03742722,  0.20930531,  0.06273426, -0.1710678 ,  0.33629384,
         0.19506555, -0.10238065, -0.12707257,  0.28042122, -0.03250191,
        -0.14004886,  0.13224399,  0.16083673,  0.21198813,  0.17530208,
        -0.27665503, -0.15344393,  0.20079061, -0.10057509,  0.09128536],
       [-0.05034548, -0.27986224, -0.30725954,  0.11214169,  0.0934514 ,
         0.00335978,  0.20517519,  0.06308483, -0.22007053,  0.26176423,
        -0.12617228, -0.05676629,  0.18296986, -0.01669114,  0.18934933,
        -0.27623121, -0.32482898,  0.213894  , -0.34427909, -0.12058168]])
(Pdb) p r1, r2
(0.5404045753007164, 0.2967937508800147)
(Pdb) r1 = 0.2
(Pdb) p r1, r2
(0.2, 0.2967937508800147)
(Pdb) j 38
> /Users/mlm/pso.py(38)update()
-> V = w * V + c1*r1*(pbest - X) + c2*r2*(gbest.reshape(-1,1)-X)
(Pdb) n
> /Users/mlm/pso.py(39)update()
-> X = X + V
(Pdb) p V
array([[ 0.02680837,  0.16594979,  0.06350735, -0.15577623,  0.30737655,
         0.19911613, -0.08242418, -0.12513798,  0.24939995, -0.02217463,
        -0.13474876,  0.14466204,  0.16661846,  0.21194543,  0.16952298,
        -0.24462505, -0.138997  ,  0.19377154, -0.10699911,  0.10631063],
       [-0.03606147, -0.25128615, -0.26362411,  0.08163408,  0.09842085,
         0.00765688,  0.19771385,  0.06597805, -0.20564599,  0.23113388,
        -0.0956787 , -0.07044121,  0.16637064, -0.00639259,  0.18245734,
        -0.25698717, -0.30336147,  0.19354112, -0.29904698, -0.08810355]])
(Pdb)

在上述内容中,我们使用 l 命令列出当前语句周围的代码(由箭头 -> 标识)。在列表中,我们还可以看到断点(用 B 标记)设置在第 40 行。我们可以看到 Vr1 的当前值,我们可以将 r1 从 0.54 修改为 0.2,然后使用 j(跳转)到第 38 行再次运行语句。正如我们所见,使用 n 命令执行语句后,V 的值发生了变化。

如果我们使用断点并发现了一些意外情况,可能是由于调用堆栈中不同级别的问题导致的。调试器允许你导航到不同的级别:

(Pdb) bt
  /usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/bdb.py(580)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  /Users/mlm/pso.py(76)<module>()
-> anim.save("PSO.gif", dpi=120, writer="imagemagick")
  /usr/local/lib/python3.9/site-packages/matplotlib/animation.py(1091)save()
-> anim._draw_next_frame(d, blit=False)
  /usr/local/lib/python3.9/site-packages/matplotlib/animation.py(1126)_draw_next_frame()
-> self._draw_frame(framedata)
  /usr/local/lib/python3.9/site-packages/matplotlib/animation.py(1720)_draw_frame()
-> self._drawn_artists = self._func(framedata, *self._args)
  /Users/mlm/pso.py(65)animate()
-> update()
> /Users/mlm/pso.py(39)update()
-> X = X + V
(Pdb) up
> /Users/mlm/pso.py(65)animate()
-> update()
(Pdb) bt
  /usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/bdb.py(580)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  /Users/mlm/pso.py(76)<module>()
-> anim.save("PSO.gif", dpi=120, writer="imagemagick")
  /usr/local/lib/python3.9/site-packages/matplotlib/animation.py(1091)save()
-> anim._draw_next_frame(d, blit=False)
  /usr/local/lib/python3.9/site-packages/matplotlib/animation.py(1126)_draw_next_frame()
-> self._draw_frame(framedata)
  /usr/local/lib/python3.9/site-packages/matplotlib/animation.py(1720)_draw_frame()
-> self._drawn_artists = self._func(framedata, *self._args)
> /Users/mlm/pso.py(65)animate()
-> update()
  /Users/mlm/pso.py(39)update()
-> X = X + V
(Pdb) l
 60
 61     def animate(i):
 62         "Steps of PSO: algorithm update and show in plot"
 63         title = 'Iteration {:02d}'.format(i)
 64         # Update params
 65  ->     update()
 66         # Set picture
 67         ax.set_title(title)
 68         pbest_plot.set_offsets(pbest.T)
 69         p_plot.set_offsets(X.T)
 70         p_arrow.set_offsets(X.T)
(Pdb) p title
'Iteration 02'
(Pdb)

在上述内容中,第一个 bt 命令在我们处于底部帧时给出调用堆栈,即调用堆栈的最深处。我们可以看到我们即将执行语句 X = X + V。然后 up 命令将我们的焦点移动到调用堆栈上一级,即运行 update() 函数的行(如我们在前面带有 > 的行中看到的)。由于我们的焦点发生了变化,列表命令 l 将打印出不同的代码片段,而 p 命令可以检查不同作用域中的变量。

上述内容涵盖了调试器中大部分有用的命令。如果我们想终止调试器(也会终止程序),可以使用 q 命令退出,或者如果终端支持的话按 Ctrl-D。

想开始使用 Python 进行机器学习吗?

立即参加我的免费 7 天邮件速成课程(包含示例代码)。

点击注册并获得免费 PDF 电子书版本的课程。

Visual Studio Code 中的调试器

如果你在命令行中运行调试器不是很舒适,可以依赖 IDE 中的调试器。几乎所有情况下,IDE 都会提供一些调试功能。例如,在 Visual Studio Code 中,你可以在“运行”菜单中启动调试器。

下面的屏幕显示了在调试会话期间的 Visual Studio Code。中心顶部的按钮分别对应 pdb 命令 continuenextstepreturnrestartquit。通过点击行号可以创建一个断点,并且会出现一个红点来标识它。使用 IDE 的好处是可以在每一步调试中立即显示变量。我们还可以查看表达式并显示调用堆栈。这些功能位于下面屏幕的左侧。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在运行中的 Python 程序中使用 GDB

Python 的 pdb 仅适用于从头开始运行的程序。如果我们有一个已经运行但卡住的程序,则不能使用 pdb 来 hook into 它以检查发生了什么。然而,GDB 的 Python 扩展可以做到这一点。

举个例子,让我们考虑一个 GUI 应用程序。它会等待用户操作后才会结束。因此,它是一个完美的例子,说明我们如何使用 gdb 挂钩到正在运行的进程中。下面的代码是一个使用 PyQt5 的“hello world”程序,它只是创建一个空窗口并等待用户关闭它:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow

class Frame(QMainWindow):
        def __init__(self):
                super().__init__()
                self.initUI()
        def initUI(self):
                self.setWindowTitle("Simple title")
                self.resize(800,600)

def main():
        app = QApplication(sys.argv)
        frame = Frame()
        frame.show()
        sys.exit(app.exec_())

if __name__ == '__main__':
        main()

让我们将此程序保存为 simpleqt.py 并在 Linux 下的 X 窗口环境中使用以下命令运行:

python simpleqt.py &

最终的 & 将使其在后台运行。现在我们可以使用 ps 命令检查其进程 ID:

ps a | grep python
...
   3997 pts/1    Sl     0:00 python simpleqt.py
...

ps 命令将告诉你第一列中的进程 ID。如果你安装了带有 Python 扩展的 gdb,我们可以运行:

gdb python 3997

它将带你进入 GDB 的提示符:

GNU gdb (Debian 10.1-1.7) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from python...
Reading symbols from /usr/lib/debug/.build-id/f9/02f8a561c3abdb9c8d8c859d4243bd8c3f928f.debug...
Attaching to program: /usr/local/bin/python, process 3997
[New LWP 3998]
[New LWP 3999]
[New LWP 4001]
[New LWP 4002]
[New LWP 4003]
[New LWP 4004]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x00007fb11b1c93ff in __GI___poll (fds=0x7fb110007220, nfds=3, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
29      ../sysdeps/unix/sysv/linux/poll.c: No such file or directory.
(gdb) py-bt
Traceback (most recent call first):
  <built-in method exec_ of QApplication object at remote 0x7fb115f64c10>
  File "/mnt/data/simpleqt.py", line 16, in main
    sys.exit(app.exec_())
  File "/mnt/data/simpleqt.py", line 19, in <module>
    main()
(gdb) py-list
  11    
  12    def main():
  13            app = QApplication(sys.argv)
  14            frame = Frame()
  15            frame.show()
 >16            sys.exit(app.exec_())
  17    
  18    if __name__ == '__main__':
  19            main()
(gdb)

GDB 应该是一个用于编译程序(通常是 C 或 C++)的调试器。Python 扩展允许你检查由 Python 解释器(用 C 编写)运行的代码(用 Python 编写)。在处理 Python 代码方面,它的功能不如 Python 的PDB丰富,但当你需要将其挂钩到正在运行的进程中时,它非常有价值。

GDB 支持的命令有 py-listpy-btpy-uppy-downpy-print。它们与 pdb 中相同的命令类似,只是没有 py- 前缀。

如果你的 Python 代码使用了从 C 编译的库(如 numpy),并且你想调查它的运行情况,GDB 很有用。它也有助于通过检查运行时的调用栈来了解程序为何被冻结。然而,使用 GDB 调试你的机器学习项目可能比较少见。

进一步阅读

Python pdb 模块的文档在

pdb 不是唯一的调试器。一些第三方工具列在:

对于带有 Python 扩展的 GDB,最佳使用环境是 Linux。有关其使用的更多细节,请参见以下内容:

pdb 的命令接口受到 GDB 的影响。因此,我们可以从后者学习调试程序的一般技术。一个很好的调试器使用入门书籍是:

总结

在本教程中,你发现了 Python 的 pdb 的功能。

具体来说,你学到了:

  • pdb 能做什么以及如何使用它

  • pdb 的限制和替代方案

在下一篇文章中,我们将看到 pdb 也是一个可以在 Python 程序内部调用的 Python 函数。

Python 机器学习(7 天迷你课程)

原文:machinelearningmastery.com/python-for-machine-learning-7-day-mini-course/

Python 机器学习速成课程。

在 7 天内学习核心 Python。

Python 是一种了不起的编程语言。它不仅在机器学习项目中被广泛使用,你还可以在系统工具、网页项目等许多领域中找到它的身影。拥有良好的 Python 技能可以让你的工作更加高效,因为它以简洁著称。你可以更快地尝试你的想法。你还可以用简洁的 Python 代码展示你的想法。

作为从业者,你不需要知道语言的构建方式,但你应该知道语言可以帮助你完成各种任务。你可以看到 Python 代码的简洁性,以及其库中的函数可以完成的任务。

在本速成课程中,你将发现一些常见的 Python 技巧,通过在七天内完成练习来掌握它们。

这是一个重要且内容丰富的帖子。你可能想要收藏它。

让我们开始吧。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Python 机器学习(7 天迷你课程)

图片由 David Clode 提供,版权所有。

这个速成课程适合谁?

在你开始之前,让我们确保你在正确的地方。

本课程适合那些可能了解一些编程的开发者。也许你知道另一种语言,或者你可能能够用 Python 编写几行代码来完成简单的任务。

本课程的内容确实对你有一些假设,例如:

  • 你对基本的 Python 知识很熟悉。

  • 你理解基本的编程概念,如变量、数组、循环和函数。

  • 你可以在命令行或 IDE 中使用 Python。

你不需要是:

  • 一位明星程序员

  • 一个 Python 专家

本速成课程可以帮助你从一个新手程序员成长为一个可以自如编写 Python 代码的专家。

本速成课程假设你已经安装了一个正常工作的 Python 3.7 环境。如果你需要环境设置方面的帮助,可以按照这里的逐步教程进行操作:

速成课程概览

本速成课程分为七节课。

你可以每天完成一节课(推荐)或在一天内完成所有课程(高强度)。这完全取决于你可用的时间和你的热情程度。

以下是将帮助你开始并高效使用 Python 的七节课列表:

  • 第 01 课:操作列表

  • 第 02 课:字典

  • 第 03 课:元组

  • 第 04 课:字符串

  • 第 05 课:列表推导式

  • 第 06 课:枚举和压缩

  • 第 07 课:映射、过滤和减少

每节课可能需要你花费 5 到 30 分钟。请按自己的节奏完成课程。提问,甚至可以在网上评论区发布结果。

课程可能会要求你自己去找出如何做。这份指南会给你一些提示,但每节课的部分重点是强迫你学习去哪里寻求有关算法和 Python 最佳工具的帮助。

在评论区发布你的结果;我会为你加油!

坚持下去,别放弃。

课程 01:操作列表

在本节课中,你将了解 Python 中的基础数据结构——列表。

在其他编程语言中,有数组。Python 中的对应物是列表。Python 列表没有限制它存储的元素数量。你可以随时向其中添加元素,它会自动扩展大小。Python 列表也不要求其元素类型相同。你可以在一个列表中混合不同的元素。

接下来,我们创建一个整数列表,然后向其中添加一个字符串:

x = [1, 2, 3]
x.append("that's all")

Python 列表是零索引的。也就是说,要获取上面列表中的第一个元素,我们可以这样做:

print(x[0])

这将打印1到屏幕上。

Python 列表允许负索引表示从后往前计数。因此,打印上述列表的最后一个元素的方式是:

print(x[-1])

Python 还具有一个方便的语法来查找列表的切片。要打印最后两个元素,我们可以这样做:

print(x[-2:])

通常,切片语法是start:end,其中 end 不包括在结果中。如果省略,默认起始元素为第一个元素,结束元素为整个列表的最后一个元素之后的元素。我们还可以使用切片语法来设置step。例如,这样我们可以提取偶数和奇数:

py` x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] odd = x[::2] even = x[1::2] print(odd) print(even) py ### Your Task In the above example of getting odd numbers from a list of 1 to 10, you can make a step size of `-2` to ask the list go backward. How can you use the slicing syntax to print `[9,7,5,3,1]`? How about `[7,5,3]`? Post your answer in the comments below. I would love to see what you come up with. In the next lesson, you will discover the Python dictionary. ## Lesson 02: Dictionaries In this lesson, you will learn Python’s way of storing a mapping. Similar to Perl, an associative array is also a native data structure in Python. It is called a dictionary or dict. Python uses square brackets `[]` for list and uses curly brackets `{}` for dict. A Python dict is for key-value mapping, but the key must be **hashable**, such as a number or a string. Hence we can do the following: price = { “apple”: 1.5, “orange”: 1.25, “banana”: 0.5 } print(“apple costs $”, price[“apple”]) py Adding a key-value mapping to a dict is similar to indexing a list: price[“lemon”] = 0.6 print(“lemon costs $”, price[“lemon”]) py We can check if a key is in a dict using the \codetext{in} operator, for example: if “strawberry” in price: print(“strawberry costs $”, price[“strawberry”]) else: # if price is not found, assume $1 print(“strawberry costs $1”) py But in Python dict, we can use the \codetext{get()} function to give us a default value if the key is not found: print(“strawberry costs $”, price.get(“strawberry”, 1)) py But indeed, you are not required to provide a default to \codetext{get()}. If you omitted it, it will return \codetext{None}. For example: print(“strawberry costs $”, price.get(“strawberry”)) py It will produce strawberry costs $ None py Since the Python dict is a key-value mapping, we can extract only the keys or only the values, using: fruits = list(price.keys()) numbers = list(price.values()) print(fruits) print(numbers) py We used `list()` to convert the keys or values to a list for better printing. % The other way to manipulate a list is with the `items()` function. Its result would be key-value pairs: pairs = list(price.items()) print(pairs) py This prints: [(‘apple’, 1.5), (‘orange’, 1.25), (‘banana’, 0.5), (‘lemon’, 0.6)] py Since they are pairs in a list, we can use list manipulation syntax to combine items from two dicts and produce a combined dict. The following is an example: price1 = { “apple”: 1.5, “orange”: 1.25, “strawberry”: 1.0 } price2 = { “banana”: 0.5 } pairs1 = list(price1.items()) pairs2 = list(price2.items()) price = dict(pairs1 + pairs2) print(price) py This will print: {‘apple’: 1.5, ‘orange’: 1.25, ‘strawberry’: 1.0, ‘banana’: 0.5} py ### Your Task Depending on your version of Python, the last example above can have a simplified syntax: price = price1 | price2 price = {price1, price2} py Check in your installation if you can reproduce the same result as the last example. In the next lesson, you will discover the tuple as a read-only list. ## Lesson 03: Tuples In this lesson, you will learn the tuple as a read-only data structure. Python has a list that behaves like an array of mixed data. A Python tuple is very much like a list, but it cannot be modified after it is created. It is **immutable**. Creating a tuple is just like creating a list, except using parentheses, `()`: x = (1, 2, 3) py You can refer to the first element as `x[0]` just like the case of a list. But you cannot assign a new value to `x[0]` because a tuple is immutable. If you try to do it, Python will throw a TypeError with the reason that the tuple does not support the item assignment. A tuple is handy to represent multiple return values of a function. For example, the following function produces a value’s multiple powers as a tuple: def powers(n): return n, n2, n3 x = powers(2) print(x) py This will print: (2, 4, 8) py which is a tuple. But we usually use the unpacking syntax: itself, squared, cubed = powers(2) py In fact, this is a powerful syntax in Python in which we can assign multiple variables in one line. For example, count, elements = 0, [] py This will assign variable `count` to integer `0` and variable `elements` to an empty list. Because of the unpacking syntax, this is the **Pythonic** way of swapping the value of two variables: a, b = b, a py ### Your Task Consider a list of tuples: x = [(“alpha”, 0.5), (“gamma”, 0.1), (“beta”, 1.1), (“alpha”, -3)] py You can sort this list using `sorted(x)`. What is the result? From the result of comparing tuples, how does Python understand which tuple is less than or greater than another? Which is greater, the tuple `("alpha", 0.5)` or the tuple `("alpha", 0.5, 1)`? Post your answer in the comments below. I would love to see what you come up with. In the next lesson, you will learn about Python strings. ## Lesson 04: Strings In this lesson, you will learn about creating and using strings in Python. A string is the basic way of storing text in Python. All Python strings are unicode strings, meaning you can put unicode into it. For example: x = “Hello ???” print(x) py The smiley is a unicode character of code point 0x1F600\. Python string comes with a lot of functions. For example, we can check if a string begins or ends with a substring using: if x.startswith(“Hello”): print(“x starts with Hello”) if not x.endswith(“World”): print(“x does not end with World”) py Then to check whether a string contains a substring, use the “`in`” operator: if “ll” in x: print(“x contains double-l”) py There is a lot more. Such as `split()` to split a string, or `upper()` to convert the entire string into uppercase. One special property of Python strings is the **implicit concatenation**. All of the following produce the string `"hello world"`: x = “hel” \ “lo world” x = “hello” " world" x = (“hello " “world”) py The rule is, Python will normally use `\` as a line continuation. But if Python sees two strings placed together without anything separating them, the strings will be concatenated. Hence the first example above is to concatenate `"hel"` with `"lo world"`. Likewise, the last example concatenated two strings because they are placed inside parentheses. A Python string can also be created using a template. It is often seen in `print()` functions. For example, below all produce `"hello world"` for variable `y`: x = “world” y = “hello %s” % x y = “hello {}”.format(x) y = f"hello {x}” py ### Your Task Try to run this code: coord = {“lat”: 51.5072, “lon”: -0.1276} print(“latitude %(lat)f, longitude %(lon)f” % coord) print(“latitude {lat}, longitude {lon}”.format(coord)) py This is to fill a template using a dictionary. The first uses the `%`-syntax while the second uses format syntax. Can you modify the code above to print only 2 decimal places? Hints: Check out [`docs.python.org/3/library/string.html`](https://docs.python.org/3/library/string.html)! Post your answer in the comments below. I would love to see what you come up with. In the next lesson, you will discover list comprehension syntax in Python. ## Lesson 05: List Comprehension In this lesson, you will see how list comprehension syntax can build a list on the fly. The famous fizz-buzz problem prints 1 to 100 with all 3-multiples replaced with “fizz,” all 5-multiples replaced with “buzz,” and if a number is both a multiple of 3 and 5, print “fizzbuzz.” You can make a `for` loop and some `if` statements to do this. But we can also do it in one line: numbers = [“fizzbuzz” if n%150 else “fizz” if n%30 else “buzz” if n%50 else str(n) for n in range(1,101)] print(“\n”.join(numbers)) py We set up the list `numbers` using list comprehension syntax. The syntax looks like a list but with a `for` inside. Before the keyword `for`, we define how each element in the list will be created. List comprehension can be more complicated. For example, this is how to produce all multiples of 3 from 1 to 100: mul3 = [n for n in range(1,101) if n%3 == 0] py And this is how we can print a $10\times 10$ multiplication table: table = [[m*n for n in range(1,11)] for m in range(1,11)] for row in table: print(row) py And this is how we can combine strings: directions = [a+b for a in [“north”, “south”, “”] for b in [“east”, “west”, “”] if not (a"" and b==“”)] print(directions) py This prints: [‘northeast’, ‘northwest’, ‘north’, ‘southeast’, ‘southwest’, ‘south’, ‘east’, ‘west’] py ### Your Task Python also has a dictionary comprehension. The syntax is: double = {n: 2*n for n in range(1,11)} py Now try to create a dictionary `mapping` using dictionary comprehension that maps a string `x` to its length `len(x)` for these strings: keys = [“one”, “two”, “three”, “four”, “five”, “six”, “seven”, “eight”, “nine”, “ten”] mapping = {…} py Post your answer in the comments below. I would love to see what you come up with. In the next lesson, you will discover two very useful Python functions: `enumerate()` and `zip()`. ## Lesson 06: Enumerate and Zip In this lesson, you will learn an the `enumerate()` function and `zip()` function. Very often, you will see you’re writing a for-loop like this: x = [“alpha”, “beta”, “gamma”, “delta”] for n in range(len(x)): print(“{}: {}”.format(n, x[n])) py But here we need the loop variable `n` just to use as an index to access the list `x`. In this case, we can ask Python to index the list while doing the loop, using `enumerate()`: x = [“alpha”, “beta”, “gamma”, “delta”] for n,string in enumerate(x): print(“{}: {}”.format(n, string)) py The result of `enumerate()` produces a tuple of the counter (default starts with zero) and the element of the list. We use the unpacking syntax to set it to two variables. If we use the for-loop like this: x = [“blue”, “red”, “green”, “yellow”] y = [“cheese”, “apple”, “pea”, “mustard”] for n in range(len(x)): print(“{} {}”.format(x[n], y[n])) py Python has a function `zip()` to help: x = [“blue”, “red”, “green”, “yellow”] y = [“cheese”, “apple”, “pea”, “mustard”] for a, b in zip(x, y): print(“{} {}”.format(a, b)) py The `zip()` function is like a zipper, taking one element from each input list and putting them side by side. You may provide more than two lists to `zip()`. It will produce all matching items (i.e., stop whenever it hits the end of the shortest input list). ### Your task Very common in Python programs, we may do this: results = [] for n in range(1, 11): squared, cubed = n2, n3 results.append([n, squared, cubed]) py Then, we can get the list of 1 to 10, the square of them, and the cube of them using `zip()` (note the `*` before `results` in the argument): numbers, squares, cubes = zip(*results) py Try this out. Can you recombine `numbers`, `squares`, and `cubes` back to `results`? Hints: Just use `zip()`. In the next lesson, you will discover three more Python functions: `map()`, `filter()`, and `reduce()`. ## Lesson 07: Map, Filter, and Reduce In this lesson, you will learn the Python functions `map()`, `filter()`, and `reduce()`. The name of these three functions came from the functional programming paradigm. In simple terms, `map()` is to transform elements of a list using some function, and `filter()` is to short list the elements based on certain criteria. If you learned list comprehension, they are just another method of list comprehension. Let’s consider an example we saw previously: def fizzbuzz(n): if n%15 == 0: return “fizzbuzz” if n%3 == 0: return “fizz” if n%5 == 0: return “buzz” return str(n) numbers = map(fizzbuzz, range(1,101)) print(“\n”.join(numbers)) py Here we have a function defined, and `map()` uses the function as the first argument and a list as the second argument. It will take each element from a list and transform it using the provided function. Using `filter()` is likewise: def multiple3(n): return n % 3 == 0 mul3 = filter(multiple3, range(1,101)) print(list(mul3)) py If that’s appropriate, you can pass the return value from `map()` to `filter()` or vice versa. You may consider `map()` and `filter()` as another way to write list comprehension (sometimes easier to read as the logic is modularized). The `reduce()` function is not replaceable by list comprehension. It scans the elements from a list and combines them using a function. While Python has a `max()` function built-in, we can use `reduce()` for the same purpose. Note that `reduce()` is a function from the module `functools`: from functools import reduce def maximum(a,b): if a > b: return a else: return b x = [-3, 10, 2, 5, -6, 12, 0, 1] max_x = reduce(maximum, x) print(max_x) py By default, `reduce()` will give the first two elements to the provided function, then the result will be passed to the function again with the third element, and so on until the input list is exhausted. But there is another way to invoke `reduce()`: x = [-3, 10, 2, 5, -6, 12, 0, 1] max_x = reduce(maximum, x, -float(“inf”)) print(max_x) py This result is the same, but the first call to the function uses the default value (`-float("inf")` in this case, which is negative infinity) and the first element of the list. Then uses the result and the second element from the list, and so on. Providing a default value is appropriate in some cases, such as the exercise below. ### Your Task Let’s consider a way to convert a bitmap to an integer. If a list `[6,2,0,3]` is provided, we should consider the list as which bit to assert, and the result should be in binary, 1001101, or in decimal, 77\. In this case, bit 0 is defined to be the least significant bit or the right most bit. We can use reduce to do this and print 77: def setbit(bitmap, bit): return bitmap | (2bit) assertbits = [6, 2, 0, 3] bitmap = reduce(setbit, assertbits, ???) print(bitmap) py What should be the `???` above? Why? Post your answer in the comments below. I would love to see what you come up with. This was the final lesson. ## The End! (*Look How Far You Have Come*) You made it. Well done! Take a moment and look back at how far you have come. You discovered: * Python list and the slicing syntax * Python dictionary, how to use it, and how to combine two dictionaries * Tuples, the unpacking syntax, and how to use it to swap variables * Strings, including many ways to create a new string from a template * List comprehension * The use of functions `enumerate()` and `zip()` * How to use `map()`, `filter()`, and `reduce()` ## Summary **How did you do with the mini-course?** Did you enjoy this crash course? **Do you have any questions? Were there any sticking points?** Let me know. Leave a comment below.

Python 中的更多特性

原文:machinelearningmastery.com/python-special-features/

Python 是一门非常棒的编程语言!它是开发 AI 和机器学习应用的最受欢迎的语言之一。Python 的语法非常易学,且具有一些特别的功能,使其与其他语言区分开来。在本教程中,我们将讨论 Python 编程语言的一些独特特性。

完成本教程后,你将会了解到:

  • 列表和字典推导的构造

  • 如何使用 zip 和 enumerate 函数

  • 什么是函数上下文和装饰器

  • Python 中生成器的目的是什么

启动你的项目,通过我的新书 Python for Machine Learning,包括 逐步教程 和所有示例的 Python 源代码 文件。

让我们开始吧。外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Python 特性

图片来源 M Mani,保留部分权利。

教程概述

本教程分为四部分,它们是:

  1. 列表和字典推导

  2. Zip 和 enumerate 函数

  3. 函数上下文和装饰器

  4. Python 中的生成器示例,使用 Keras 生成器

导入部分

本教程中使用的库在下面的代码中进行了导入。

from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import numpy as np
import matplotlib.pyplot as plt
import math

列表推导

列表推导提供了一种简短、简单的语法,用于从现有列表创建新列表。例如,假设我们需要一个新列表,其中每个新项是旧项乘以 3。一个方法是使用 for 循环,如下所示:

original_list = [1, 2, 3, 4]
times3_list = []

for i in original_list:
        times3_list.append(i*3)
print(times3_list)

输出

[3, 6, 9, 12]

使用列表推导式的简短方法只需一行代码:

time3_list_awesome_method = [i*3 for i in original_list]
print(time3_list_awesome_method)

输出

[3, 6, 9, 12]

你甚至可以基于特定的标准创建一个新列表。例如,如果我们只想将偶数添加到新列表中:

even_list_awesome_method = [i for i in original_list if i%2==0]
print(even_list_awesome_method)

输出

[2, 4]

也可以与上述代码一起使用 else。例如,我们可以保留所有偶数不变,并将奇数替换为零:

new_list_awesome_method = [i if i%2==0 else 0 for i in original_list]
print(new_list_awesome_method)

输出

[0, 2, 0, 4]

列表推导也可以用来替代嵌套循环。例如:

colors = ["red", "green", "blue"]
animals = ["cat", "dog", "bird"]
newlist = []
for c in colors:
    for a in animals:
        newlist.append(c + " " + a)
print(newlist)

输出

['red cat', 'red dog', 'red bird', 'green cat', 'green dog', 'green bird', 'blue cat', 'blue dog', 'blue bird']

可以如下完成,列表推导式中包含两个“for”:

colors = ["red", "green", "blue"]
animals = ["cat", "dog", "bird"]

newlist = [c+" "+a for c in colors for a in animals]
print(newlist)

语法

列表推导的语法如下:

newlist = [expression for item in iterable if condition == True]

或者

newList = [expression if condition == True else expression for item in iterable]

想要开始使用 Python 进行机器学习吗?

立即参加我的 7 天免费电子邮件速成课程(附示例代码)。

点击报名,还可以免费获得课程的 PDF 电子书版本。

字典推导

字典推导类似于列表推导,不过现在我们有了 (key, value) 对。这里是一个示例;我们将通过将字符串 'number ’ 连接到每个值来修改字典的每个值:

original_dict = {1: 'one', 2: 'two', 3: 'three', 4: 'four'}
new_dict = {key:'number ' + value for (key, value) in original_dict.items()}
print(new_dict)

输出

{1: 'number one', 2: 'number two', 3: 'number three', 4: 'number four'}

再次,条件判断也是可能的。我们可以根据标准在新字典中选择添加(key, value)对。

#Only add keys which are greater than 2
new_dict_high_keys = {key:'number ' + value for (key, value) in original_dict.items() if key>2}
print(new_dict_high_keys)

# Only change values with key>2
new_dict_2 = {key:('number ' + value if key>2 else value) for (key, value) in original_dict.items() }
print(new_dict_2)

输出

{3: 'number three', 4: 'number four'}
{1: 'one', 2: 'two', 3: 'number three', 4: 'number four'}

Python 中的枚举器和 Zip

在 Python 中,可迭代对象定义为任何可以逐个返回所有项的数据结构。这样,你可以使用for循环逐一处理所有项。Python 有两个附加的构造使for循环更易于使用,即enumerate()zip()

枚举

在传统编程语言中,你需要一个循环变量来遍历容器中的不同值。在 Python 中,这通过提供对循环变量和可迭代对象的一个值的访问来简化。enumerate(x)函数返回两个可迭代对象。一个可迭代对象从 0 到 len(x)-1。另一个是值等于 x 项的可迭代对象。下面显示了一个示例:

name = ['Triangle', 'Square', 'Hexagon', 'Pentagon']

# enumerate returns two iterables
for i, n in enumerate(name):
    print(i, 'name: ', n)

输出

0 name:  Triangle
1 name:  Square
2 name:  Hexagon
3 name:  Pentagon

默认情况下,enumerate 从 0 开始,但如果我们指定其他数字,则可以从其他数字开始。这在某些情况下非常有用,例如:

data = [1,4,1,5,9,2,6,5,3,5,8,9,7,9,3]
for n, digit in enumerate(data[5:], 6):
    print("The %d-th digit is %d" % (n, digit))
The 6-th digit is 2
The 7-th digit is 6
The 8-th digit is 5
The 9-th digit is 3
The 10-th digit is 5
The 11-th digit is 8
The 12-th digit is 9
The 13-th digit is 7
The 14-th digit is 9
The 15-th digit is 3

Zip

Zip 允许你创建一个由元组组成的可迭代对象。Zip 将多个容器 ( m 1 , m 2 , … , m n ) (m_1, m_2, \ldots, m_n) (m1,m2,,mn)作为参数,并通过配对每个容器中的一个项来创建第 i 个元组。第 i 个元组是 ( m 1 i , m 2 i , … , m n i ) (m_{1i}, m_{2i}, \ldots, m_{ni}) (m1i,m2i,,mni)。如果传递的对象长度不同,则形成的元组总数的长度等于传递对象的最小长度。

下面是使用zip()enumerate()的示例。

sides = [3, 4, 6, 5]
colors = ['red', 'green', 'yellow', 'blue']
shapes = zip(name, sides, colors)

# Tuples are created from one item from each list
print(set(shapes))

# Easy to use enumerate and zip together for iterating through multiple lists in one go
for i, (n, s, c) in enumerate(zip(name, sides, colors)):
    print(i, 'Shape- ', n, '; Sides ', s)

输出

{('Triangle', 3, 'red'), ('Square', 4, 'green'), ('Hexagon', 6, 'yellow'), ('Pentagon', 5, 'blue')}
0 Shape-  Triangle ; Sides  3
1 Shape-  Square ; Sides  4
2 Shape-  Hexagon ; Sides  6
3 Shape-  Pentagon ; Sides  5

函数上下文

Python 允许嵌套函数,你可以在外部函数内部定义一个内部函数。Python 中的嵌套函数有一些非常棒的特性。

  • 外部函数可以返回指向内部函数的句柄。

  • 内部函数保留了其环境和在其封闭函数中的所有局部变量,即使外部函数结束执行也不例外。

下面是一个示例,解释在注释中。

def circle(r):
    area = 0
    def area_obj():
        nonlocal area
        area = math.pi * r * r
        print("area_obj")
    return area_obj    

def circle(r):
    area_val = math.pi * r * r
    def area():
        print(area_val)
    return area    

# returns area_obj(). The value of r passed is retained
circle_1 = circle(1)
circle_2 = circle(2)

# Calling area_obj() with radius = 1
circle_1()
# Calling area_obj() with radius = 2
circle_2()

输出

3.141592653589793
12.566370614359172

Python 中的装饰器

装饰器是 Python 的一个强大特性。你可以使用装饰器来定制类或函数的工作。可以将它们看作是应用于另一个函数的函数。使用@符号与函数名来定义装饰器函数。装饰器以函数作为参数,提供了很大的灵活性。

考虑以下函数square_decorator(),它接受一个函数作为参数,并返回一个函数。

  • 内部嵌套函数square_it()接受一个参数arg

  • square_it()函数将函数应用于arg并对结果进行平方运算。

  • 我们可以将函数如sin传递给square_decorator(),它将返回 sin ⁡ 2 ( x ) \sin²(x) sin2(x)

  • 你还可以编写自定义函数,并使用特殊的@符号对其应用square_decorator()函数,如下所示。函数plus_one(x)返回x+1。这个函数被square_decorator()装饰,因此我们得到 ( x + 1 ) 2 (x+1)² (x+1)2

def square_decorator(function):
    def square_it(arg):
        x = function(arg)
        return x*x
    return square_it

size_sq = square_decorator(len)
print(size_sq([1,2,3]))

sin_sq = square_decorator(math.sin)
print(sin_sq(math.pi/4))

@square_decorator
def plus_one(a):
    return a+1

a = plus_one(3)
print(a)

输出

9
0.4999999999999999
16

Python 中的生成器

Python 中的生成器允许你生成序列。生成器通过多个 yield 语句返回多个值,而不是编写 return 语句。第一次调用函数时,返回的是 yield 的第一个值。第二次调用返回的是 yield 的第二个值,以此类推。

生成器函数可以通过 next() 调用。每次调用 next() 时,都会返回下一个 yield 值。下面是生成 Fibonacci 序列直到给定数字 x 的示例。

def get_fibonacci(x):
    x0 = 0
    x1 = 1
    for i in range(x):
        yield x0
        temp = x0 + x1
        x0 = x1
        x1 = temp

f = get_fibonacci(6)
for i in range(6):
    print(next(f))

输出

0
1
1
2
3
5

Keras 数据生成器示例

生成器的一个用途是 Keras 中的数据生成器。它非常有用,因为我们不想将所有数据保存在内存中,而是希望在训练循环需要时动态创建它。请记住,在 Keras 中,神经网络模型是按批训练的,因此生成器是用来发出数据批次的。下面的函数来自我们之前的帖子,“使用 CNN 进行金融时间序列预测”:

def datagen(data, seq_len, batch_size, targetcol, kind):
    "As a generator to produce samples for Keras model"
    batch = []
    while True:
        # Pick one dataframe from the pool
        key = random.choice(list(data.keys()))
        df = data[key]
        input_cols = [c for c in df.columns if c != targetcol]
        index = df.index[df.index < TRAIN_TEST_CUTOFF]
        split = int(len(index) * TRAIN_VALID_RATIO)
        if kind == 'train':
            index = index[:split]   # range for the training set
        elif kind == 'valid':
            index = index[split:]   # range for the validation set
        # Pick one position, then clip a sequence length
        while True:
            t = random.choice(index)      # pick one time step
            n = (df.index == t).argmax()  # find its position in the dataframe
            if n-seq_len+1 < 0:
                continue # can't get enough data for one sequence length
            frame = df.iloc[n-seq_len+1:n+1]
            batch.append([frame[input_cols].values, df.loc[t, targetcol]])
            break
        # if we get enough for a batch, dispatch
        if len(batch) == batch_size:
            X, y = zip(*batch)
            X, y = np.expand_dims(np.array(X), 3), np.array(y)
            yield X, y
            batch = []

上面的函数用于从 pandas 数据框中随机选择一行作为起点,并将接下来的几行剪切为一次时间间隔样本。这个过程重复几次,将许多时间间隔收集成一个批次。当我们收集到足够的间隔样本时,在上面函数的倒数第二行,使用 yield 命令分发批次。你可能已经注意到生成器函数没有返回语句。在这个示例中,函数将永远运行。这是有用且必要的,因为它允许我们的 Keras 训练过程运行任意多的轮次。

如果我们不使用生成器,我们将需要将数据框转换为所有可能的时间间隔,并将它们保存在内存中以供训练循环使用。这将涉及大量重复的数据(因为时间间隔是重叠的),并且占用大量内存。

由于它的实用性,Keras 库中预定义了一些生成器函数。下面是 ImageDataGenerator() 的示例。我们在 x_train 中加载了 32×32 图像的 cifar10 数据集。通过 flow() 方法将数据连接到生成器。next() 函数返回下一批数据。在下面的示例中,有 4 次对 next() 的调用。在每次调用中,返回 8 张图像,因为批量大小为 8。

以下是完整代码,也在每次调用 next() 后显示所有图像。

(x_train, y_train), _ = keras.datasets.cifar10.load_data()
datagen = ImageDataGenerator()
data_iterator = datagen.flow(x_train, y_train, batch_size=8)

fig,ax = plt.subplots(nrows=4, ncols=8,figsize=(18,6),subplot_kw=dict(xticks=[], yticks=[]))

for i in range(4):
    # The next() function will load 8 images from CIFAR
    X, Y = data_iterator.next()
    for j, img in enumerate(X):
        ax[i, j].imshow(img.astype('int'))

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

进一步阅读

如果你想更深入地了解这个主题,本节提供了更多资源。

Python 文档

书籍

API 参考

总结

在本教程中,你发现了一些 Python 的特殊功能。

具体来说,你学习了:

  • 列表和字典推导的目的

  • 如何使用 zip 和 enumerate

  • 嵌套函数、函数上下文和装饰器

  • Python 中的生成器和 Python 中的 ImageDataGenerator

对于本文讨论的 Python 功能,你有任何问题吗?在下方评论中提出你的问题,我会尽力回答。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值