使用检查工具开发 Python 程序
原文:
machinelearningmastery.com/developing-a-python-program-using-inspection-tools/
Python 是一种解释型语言。这意味着有一个解释器来运行我们的程序,而不是编译代码并直接运行。在 Python 中,REPL(读-评-打印循环)可以逐行运行命令。结合 Python 提供的一些检查工具,它有助于开发代码。
在接下来的内容中,你将看到如何利用 Python 解释器来检查一个对象并开发一个程序。
完成本教程后,你将学到:
-
如何在 Python 解释器中工作
-
如何在 Python 中使用检查函数
-
如何借助检查函数一步一步开发解决方案
快速启动你的项目,请参阅我新的书籍 《机器学习的 Python》,包括逐步教程和所有示例的Python 源代码文件。
使用检查工具开发 Python 程序。
图片来源:Tekton。保留所有权利。
教程概述
本教程分为四部分;它们是:
-
PyTorch 和 TensorFlow
-
寻找线索
-
从权重中学习
-
制作一个复制器
PyTorch 和 TensorFlow
PyTorch 和 TensorFlow 是 Python 中两个最大的神经网络库。它们的代码不同,但它们能做的事情类似。
考虑经典的 MNIST 手写数字识别问题;你可以构建一个 LeNet-5 模型来对数字进行分类,如下所示:
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
# Load MNIST training data
transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
train = torchvision.datasets.MNIST('./datafiles/', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train, batch_size=32, shuffle=True)
# LeNet5 model
torch_model = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=(5,5), stride=1, padding=2),
nn.Tanh(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0),
nn.Tanh(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(16, 120, kernel_size=5, stride=1, padding=0),
nn.Tanh(),
nn.Flatten(),
nn.Linear(120, 84),
nn.Tanh(),
nn.Linear(84, 10),
nn.Softmax(dim=1)
)
# Training loop
def training_loop(model, optimizer, loss_fn, train_loader, n_epochs=100):
model.train()
for epoch in range(n_epochs):
for data, target in train_loader:
output = model(data)
loss = loss_fn(output, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
model.eval()
# Run training
optimizer = optim.Adam(torch_model.parameters())
loss_fn = nn.CrossEntropyLoss()
training_loop(torch_model, optimizer, loss_fn, train_loader, n_epochs=20)
# Save model
torch.save(torch_model, "lenet5.pt")
这是一个简化的代码,不需要任何验证或测试。TensorFlow 中对应的代码如下:
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, Dense, AveragePooling2D, Flatten
from tensorflow.keras.datasets import mnist
# LeNet5 model
keras_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")
])
# Reshape data to shape of (n_sample, height, width, n_channel)
(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = np.expand_dims(X_train, axis=3).astype('float32')
# Train
keras_model.compile(loss="sparse_categorical_crossentropy", optimizer="adam", metrics=["accuracy"])
keras_model.fit(X_train, y_train, epochs=20, batch_size=32)
# Save
keras_model.save("lenet5.h5")
运行这个程序会生成 PyTorch 代码中的文件 lenet5.pt
和 TensorFlow 代码中的 lenet5.h5
文件。
寻找线索
如果你理解了上述神经网络的工作原理,你应该能判断出每一层中只有大量的乘法和加法计算。从数学上讲,在每个全连接层中,输入与核之间进行矩阵乘法,然后将偏差加到结果上。在卷积层中,将核与输入矩阵的一部分逐元素相乘,然后对结果求和,并将偏差作为特征图的一个输出元素。
想开始使用 Python 进行机器学习吗?
现在就参加我的免费 7 天邮件速成课程(包含示例代码)。
点击注册并免费获取课程的 PDF 电子书版本。
在使用两个不同框架开发相同的 LeNet-5 模型时,如果它们的权重相同,应该可以使它们的工作结果相同。鉴于它们的架构相同,你如何将一个模型的权重复制到另一个模型?
你可以按如下方式加载保存的模型:
import torch
import tensorflow as tf
torch_model = torch.load("lenet5.pt")
keras_model = tf.keras.models.load_model("lenet5.h5")
这可能不会告诉你太多。但如果你在命令行中运行 python
而不带任何参数,你将启动 REPL,你可以在其中输入上述代码(你可以通过 quit()
退出 REPL):
Python 3.9.13 (main, May 19 2022, 13:48:47)
[Clang 13.1.6 (clang-1316.0.21.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> import tensorflow as tf
>>> torch_model = torch.load("lenet5.pt")
>>> keras_model = tf.keras.models.load_model("lenet5.h5")
上述内容不会输出任何内容。但你可以使用 type()
内置命令检查加载的两个模型:
>>> type(torch_model)
<class 'torch.nn.modules.container.Sequential'>
>>> type(keras_model)
<class 'keras.engine.sequential.Sequential'>
所以这里你知道它们分别是来自 PyTorch 和 Keras 的神经网络模型。由于它们是训练好的模型,权重必须被存储在其中。那么如何找到这些模型中的权重呢?由于它们是对象,最简单的方法是使用 dir()
内置函数来检查它们的成员:
>>> dir(torch_model)
['T_destination', '__annotations__', '__call__', '__class__', '__delattr__',
'__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
...
'_slow_forward', '_state_dict_hooks', '_version', 'add_module', 'append', 'apply',
'bfloat16', 'buffers', 'children', 'cpu', 'cuda', 'double', 'dump_patches', 'eval',
'extra_repr', 'float', 'forward', 'get_buffer', 'get_extra_state', 'get_parameter',
'get_submodule', 'half', 'load_state_dict', 'modules', 'named_buffers',
'named_children', 'named_modules', 'named_parameters', 'parameters',
'register_backward_hook', 'register_buffer', 'register_forward_hook',
'register_forward_pre_hook', 'register_full_backward_hook', 'register_module',
'register_parameter', 'requires_grad_', 'set_extra_state', 'share_memory', 'state_dict',
'to', 'to_empty', 'train', 'training', 'type', 'xpu', 'zero_grad']
>>> dir(keras_model)
['_SCALAR_UPRANKING_ON', '_TF_MODULE_IGNORED_PROPERTIES', '__call__', '__class__',
'__copy__', '__deepcopy__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
...
'activity_regularizer', 'add', 'add_loss', 'add_metric', 'add_update', 'add_variable',
'add_weight', 'build', 'built', 'call', 'compile', 'compiled_loss', 'compiled_metrics',
'compute_dtype', 'compute_loss', 'compute_mask', 'compute_metrics',
'compute_output_shape', 'compute_output_signature', 'count_params',
'distribute_strategy', 'dtype', 'dtype_policy', 'dynamic', 'evaluate',
'evaluate_generator', 'finalize_state', 'fit', 'fit_generator', 'from_config',
'get_config', 'get_input_at', 'get_input_mask_at', 'get_input_shape_at', 'get_layer',
'get_output_at', 'get_output_mask_at', 'get_output_shape_at', 'get_weights', 'history',
'inbound_nodes', 'input', 'input_mask', 'input_names', 'input_shape', 'input_spec',
'inputs', 'layers', 'load_weights', 'loss', 'losses', 'make_predict_function',
'make_test_function', 'make_train_function', 'metrics', 'metrics_names', 'name',
'name_scope', 'non_trainable_variables', 'non_trainable_weights', 'optimizer',
'outbound_nodes', 'output', 'output_mask', 'output_names', 'output_shape', 'outputs',
'pop', 'predict', 'predict_function', 'predict_generator', 'predict_on_batch',
'predict_step', 'reset_metrics', 'reset_states', 'run_eagerly', 'save', 'save_spec',
'save_weights', 'set_weights', 'state_updates', 'stateful', 'stop_training',
'submodules', 'summary', 'supports_masking', 'test_function', 'test_on_batch',
'test_step', 'to_json', 'to_yaml', 'train_function', 'train_on_batch', 'train_step',
'train_tf_function', 'trainable', 'trainable_variables', 'trainable_weights', 'updates',
'variable_dtype', 'variables', 'weights', 'with_name_scope']
每个对象中有很多成员。有些是属性,有些是类的方法。按照惯例,以下划线开头的成员是内部成员,正常情况下不应访问。如果你想查看更多的每个成员,你可以使用 inspect
模块中的 getmembers()
函数:
>>> import inspect
>>> inspect(torch_model)
>>> inspect.getmembers(torch_model)
[('T_destination', ~T_destination), ('__annotations__', {'_modules': typing.Dict[str,
torch.nn.modules.module.Module]}), ('__call__', <bound method Module._call_impl of
Sequential(
...
getmembers()
函数的输出是一个元组列表,其中每个元组包含成员的名称和成员本身。例如,从上述内容你可以知道 __call__
是一个“绑定方法”,即类的成员方法。
通过仔细查看成员的名称,你可以看到在 PyTorch 模型中,“state” 应该是你的关注点,而在 Keras 模型中,你有一个名为 “weights” 的成员。要缩短它们的名称列表,你可以在解释器中执行以下操作:
>>> [n for n in dir(torch_model) if 'state' in n]
['__setstate__', '_load_from_state_dict', '_load_state_dict_pre_hooks',
'_register_load_state_dict_pre_hook', '_register_state_dict_hook',
'_save_to_state_dict', '_state_dict_hooks', 'get_extra_state', 'load_state_dict',
'set_extra_state', 'state_dict']
>>> [n for n in dir(keras_model) if 'weight' in n]
['_assert_weights_created', '_captured_weight_regularizer',
'_check_sample_weight_warning', '_dedup_weights', '_handle_weight_regularization',
'_initial_weights', '_non_trainable_weights', '_trainable_weights',
'_undeduplicated_weights', 'add_weight', 'get_weights', 'load_weights',
'non_trainable_weights', 'save_weights', 'set_weights', 'trainable_weights', 'weights']
这可能需要一些时间来试错。但并不太难,你可能会发现可以通过 state_dict
在 PyTorch 模型中查看权重:
>>> torch_model.state_dict
<bound method Module.state_dict of Sequential(
(0): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): Tanh()
(2): AvgPool2d(kernel_size=2, stride=2, padding=0)
(3): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(4): Tanh()
(5): AvgPool2d(kernel_size=2, stride=2, padding=0)
(6): Conv2d(16, 120, kernel_size=(5, 5), stride=(1, 1))
(7): Tanh()
(8): Flatten(start_dim=1, end_dim=-1)
(9): Linear(in_features=120, out_features=84, bias=True)
(10): Tanh()
(11): Linear(in_features=84, out_features=10, bias=True)
(12): Softmax(dim=1)
)>
>>> torch_model.state_dict()
OrderedDict([('0.weight', tensor([[[[ 0.1559, 0.1681, 0.2726, 0.3187, 0.4909],
[ 0.1179, 0.1340, -0.0815, -0.3253, 0.0904],
[ 0.2326, -0.2079, -0.8614, -0.8643, -0.0632],
[ 0.3874, -0.3490, -0.7957, -0.5873, -0.0638],
[ 0.2800, 0.0947, 0.0308, 0.4065, 0.6916]]],
[[[ 0.5116, 0.1798, -0.1062, -0.4099, -0.3307],
[ 0.1090, 0.0689, -0.1010, -0.9136, -0.5271],
[ 0.2910, 0.2096, -0.2442, -1.5576, -0.0305],
...
对于 TensorFlow/Keras 模型,你可以通过 get_weights()
查找权重:
>>> keras_model.get_weights
<bound method Model.get_weights of <keras.engine.sequential.Sequential object at 0x159d93eb0>>
>>> keras_model.get_weights()
[array([[[[ 0.14078194, 0.04990018, -0.06204645, -0.03128023,
-0.22033708, 0.19721672]],
[[-0.06618818, -0.152075 , 0.13130261, 0.22893831,
0.08880515, 0.01917628]],
[[-0.28716782, -0.23207009, 0.00505603, 0.2697424 ,
-0.1916888 , -0.25858143]],
[[-0.41863152, -0.20710683, 0.13254236, 0.18774481,
-0.14866787, -0.14398652]],
[[-0.25119543, -0.14405733, -0.048533 , -0.12108403,
0.06704573, -0.1196835 ]]],
[[[-0.2438466 , 0.02499897, -0.1243961 , -0.20115352,
-0.0241346 , 0.15888865]],
[[-0.20548582, -0.26495507, 0.21004884, 0.32183227,
-0.13990627, -0.02996112]],
...
这里也可以看到 weights
属性:
>>> keras_model.weights
[<tf.Variable 'conv2d/kernel:0' shape=(5, 5, 1, 6) dtype=float32, numpy=
array([[[[ 0.14078194, 0.04990018, -0.06204645, -0.03128023,
-0.22033708, 0.19721672]],
[[-0.06618818, -0.152075 , 0.13130261, 0.22893831,
0.08880515, 0.01917628]],
...
8.25365111e-02, -1.72486171e-01, 3.16280037e-01,
4.12595004e-01]], dtype=float32)>, <tf.Variable 'dense_1/bias:0' shape=(10,) dtype=float32, numpy=
array([-0.19007775, 0.14427921, 0.0571407 , -0.24149619, -0.03247226,
0.18109408, -0.17159976, 0.21736498, -0.10254183, 0.02417901],
dtype=float32)>]
在这里,你可以观察到以下内容:在 PyTorch 模型中,函数 state_dict()
返回一个 OrderedDict
,这是一个按指定顺序排列的字典。键中有 0.weight
之类的键,它们映射到一个张量值。在 Keras 模型中,get_weights()
函数返回一个列表。列表中的每个元素都是一个 NumPy 数组。weight
属性也包含一个列表,但元素是 tf.Variable
类型。
你可以通过检查每个张量或数组的形状来了解更多:
>>> [(key, val.shape) for key, val in torch_model.state_dict().items()]
[('0.weight', torch.Size([6, 1, 5, 5])), ('0.bias', torch.Size([6])), ('3.weight',
torch.Size([16, 6, 5, 5])), ('3.bias', torch.Size([16])), ('6.weight', torch.Size([120,
16, 5, 5])), ('6.bias', torch.Size([120])), ('9.weight', torch.Size([84, 120])),
('9.bias', torch.Size([84])), ('11.weight', torch.Size([10, 84])), ('11.bias',
torch.Size([10]))]
>>> [arr.shape for arr in keras_model.get_weights()]
[(5, 5, 1, 6), (6,), (5, 5, 6, 16), (16,), (5, 5, 16, 120), (120,), (120, 84), (84,),
(84, 10), (10,)]
尽管你在上述 Keras 模型中看不到层的名称,实际上你可以使用类似的推理来查找层并获取它们的名称:
>>> keras_model.layers
[<keras.layers.convolutional.conv2d.Conv2D object at 0x159ddd850>,
<keras.layers.pooling.average_pooling2d.AveragePooling2D object at 0x159ddd820>,
<keras.layers.convolutional.conv2d.Conv2D object at 0x15a12b1c0>,
<keras.layers.pooling.average_pooling2d.AveragePooling2D object at 0x15a1705e0>,
<keras.layers.convolutional.conv2d.Conv2D object at 0x15a1812b0>,
<keras.layers.reshaping.flatten.Flatten object at 0x15a194310>,
<keras.layers.core.dense.Dense object at 0x15a1947c0>, <keras.layers.core.dense.Dense
object at 0x15a194910>]
>>> [layer.name for layer in keras_model.layers]
['conv2d', 'average_pooling2d', 'conv2d_1', 'average_pooling2d_1', 'conv2d_2',
'flatten', 'dense', 'dense_1']
>>>
从权重中学习
通过比较 PyTorch 模型的 state_dict()
结果和 Keras 模型的 get_weights()
结果,你可以看到它们都包含 10 个元素。通过 PyTorch 张量和 NumPy 数组的形状,你可以进一步注意到它们的形状相似。这可能是因为两个框架都按从输入到输出的顺序识别模型。你可以通过 state_dict()
输出的键与 Keras 模型的层名称进行进一步确认。
你可以通过提取一个 PyTorch 张量并检查来查看如何操作它:
>>> torch_states = torch_model.state_dict()
>>> torch_states.keys()
odict_keys(['0.weight', '0.bias', '3.weight', '3.bias', '6.weight', '6.bias', '9.weight', '9.bias', '11.weight', '11.bias'])
>>> torch_states["0.weight"]
tensor([[[[ 0.1559, 0.1681, 0.2726, 0.3187, 0.4909],
[ 0.1179, 0.1340, -0.0815, -0.3253, 0.0904],
[ 0.2326, -0.2079, -0.8614, -0.8643, -0.0632],
[ 0.3874, -0.3490, -0.7957, -0.5873, -0.0638],
[ 0.2800, 0.0947, 0.0308, 0.4065, 0.6916]]],
...
[[[ 0.0980, 0.0240, 0.3295, 0.4507, 0.4539],
[-0.1530, -0.3991, -0.3834, -0.2716, 0.0809],
[-0.4639, -0.5537, -1.0207, -0.8049, -0.4977],
[ 0.1825, -0.1284, -0.0669, -0.4652, -0.2961],
[ 0.3402, 0.4256, 0.4329, 0.1503, 0.4207]]]])
>>> dir(torch_states["0.weight"])
['H', 'T', '__abs__', '__add__', '__and__', '__array__', '__array_priority__',
'__array_wrap__', '__bool__', '__class__', '__complex__', '__contains__',
...
'trunc', 'trunc_', 'type', 'type_as', 'unbind', 'unflatten', 'unfold', 'uniform_',
'unique', 'unique_consecutive', 'unsafe_chunk', 'unsafe_split',
'unsafe_split_with_sizes', 'unsqueeze', 'unsqueeze_', 'values', 'var', 'vdot', 'view',
'view_as', 'vsplit', 'where', 'xlogy', 'xlogy_', 'xpu', 'zero_']
>>> torch_states["0.weight"].numpy()
array([[[[ 0.15587455, 0.16805592, 0.27259687, 0.31871665,
0.49091515],
[ 0.11791296, 0.13400094, -0.08148099, -0.32530317,
0.09039831],
...
[ 0.18252987, -0.12838107, -0.0669101 , -0.4652463 ,
-0.2960882 ],
[ 0.34022188, 0.4256311 , 0.4328527 , 0.15025541,
0.4207182 ]]]], dtype=float32)
>>> torch_states["0.weight"].shape
torch.Size([6, 1, 5, 5])
>>> torch_states["0.weight"].numpy().shape
(6, 1, 5, 5)
从 PyTorch 张量的 dir()
输出中,你发现了一个名为 numpy
的成员,通过调用这个函数,它似乎将张量转换为 NumPy 数组。你可以相当有信心,因为你看到数字匹配,形状也匹配。实际上,通过查看文档,你可以更有信心:
>>> help(torch_states["0.weight"].numpy)
help()
函数将显示函数的文档字符串,这通常是其文档。
由于这是第一个卷积层的内核,通过将该内核的形状与 Keras 模型的形状进行比较,你会发现它们的形状不同:
>>> keras_weights = keras_model.get_weights()
>>> keras_weights[0].shape
(5, 5, 1, 6)
知道第一个层的输入是一个 28×28×1 的图像数组,而输出是 6 个特征图。将内核形状中的 1 和 6 对应为输入和输出中的通道数是很自然的。此外,根据我们对卷积层机制的理解,内核应该是一个 5×5 的矩阵。
此时,你可能已经猜到在 PyTorch 卷积层中,内核表示为 (output × input × height × width),而在 Keras 中,它表示为 (height × width × input × output)。
类似地,你也看到在全连接层中,PyTorch 将内核表示为 (output × input),而 Keras 中是 (input × output):
>>> keras_weights[6].shape
(120, 84)
>>> list(torch_states.values())[6].shape
torch.Size([84, 120])
匹配权重和张量并将它们的形状并排显示应该可以让这些更清楚:
>>> for k,t in zip(keras_weights, torch_states.values()):
... print(f"Keras: {k.shape}, Torch: {t.shape}")
...
Keras: (5, 5, 1, 6), Torch: torch.Size([6, 1, 5, 5])
Keras: (6,), Torch: torch.Size([6])
Keras: (5, 5, 6, 16), Torch: torch.Size([16, 6, 5, 5])
Keras: (16,), Torch: torch.Size([16])
Keras: (5, 5, 16, 120), Torch: torch.Size([120, 16, 5, 5])
Keras: (120,), Torch: torch.Size([120])
Keras: (120, 84), Torch: torch.Size([84, 120])
Keras: (84,), Torch: torch.Size([84])
Keras: (84, 10), Torch: torch.Size([10, 84])
Keras: (10,), Torch: torch.Size([10])
我们还可以匹配 Keras 权重和 PyTorch 张量的名称:
>>> for k, t in zip(keras_model.weights, torch_states.keys()):
... print(f"Keras: {k.name}, Torch: {t}")
...
Keras: conv2d/kernel:0, Torch: 0.weight
Keras: conv2d/bias:0, Torch: 0.bias
Keras: conv2d_1/kernel:0, Torch: 3.weight
Keras: conv2d_1/bias:0, Torch: 3.bias
Keras: conv2d_2/kernel:0, Torch: 6.weight
Keras: conv2d_2/bias:0, Torch: 6.bias
Keras: dense/kernel:0, Torch: 9.weight
Keras: dense/bias:0, Torch: 9.bias
Keras: dense_1/kernel:0, Torch: 11.weight
Keras: dense_1/bias:0, Torch: 11.bias
制作一个复制器
既然你已经了解了每个模型中的权重是什么样的,那么创建一个程序来将权重从一个模型复制到另一个模型似乎并不困难。关键是要回答:
-
如何在每个模型中设置权重
-
每个模型中权重的形状和数据类型应该是什么样的
第一个问题可以从之前使用 dir()
内置函数的检查中回答。你在 PyTorch 模型中看到了 load_state_dict
成员,它似乎是工具。同样,在 Keras 模型中,你看到了一个名为 set_weight
的成员,它正是 get_weight
的对应名称。你可以通过查看它们的文档或使用 help()
函数进一步确认这一点:
>>> keras_model.set_weights
<bound method Layer.set_weights of <keras.engine.sequential.Sequential object at 0x159d93eb0>>
>>> torch_model.load_state_dict
<bound method Module.load_state_dict of Sequential(
(0): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): Tanh()
(2): AvgPool2d(kernel_size=2, stride=2, padding=0)
(3): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(4): Tanh()
(5): AvgPool2d(kernel_size=2, stride=2, padding=0)
(6): Conv2d(16, 120, kernel_size=(5, 5), stride=(1, 1))
(7): Tanh()
(8): Flatten(start_dim=1, end_dim=-1)
(9): Linear(in_features=120, out_features=84, bias=True)
(10): Tanh()
(11): Linear(in_features=84, out_features=10, bias=True)
(12): Softmax(dim=1)
)>
>>> help(torch_model.load_state_dict)
>>> help(keras_model.set_weights)
你确认这些都是函数,它们的文档解释了它们的功能。根据文档,你进一步了解到 PyTorch 模型的 load_state_dict()
函数期望参数与 state_dict()
函数返回的格式相同;Keras 模型的 set_weights()
函数期望的格式与 get_weights()
函数返回的格式相同。
现在你已经完成了在 Python REPL 中的冒险(你可以输入 quit()
退出)。
通过研究如何 重塑 权重和 转换 数据类型,你得出了以下程序:
import torch
import tensorflow as tf
# Load the models
torch_model = torch.load("lenet5.pt")
keras_model = tf.keras.models.load_model("lenet5.h5")
# Extract weights from Keras model
keras_weights = keras_model.get_weights()
# Transform shape from Keras to PyTorch
for idx in [0, 2, 4]:
# conv layers: (out, in, height, width)
keras_weights[idx] = keras_weights[idx].transpose([3, 2, 0, 1])
for idx in [6, 8]:
# dense layers: (out, in)
keras_weights[idx] = keras_weights[idx].transpose()
# Set weights
torch_states = torch_model.state_dict()
for key, weight in zip(torch_states.keys(), keras_weights):
torch_states[key] = torch.tensor(weight)
torch_model.load_state_dict(torch_states)
# Save new model
torch.save(torch_model, "lenet5-keras.pt")
反之,从 PyTorch 模型到 Keras 模型复制权重也可以类似地完成,
import torch
import tensorflow as tf
# Load the models
torch_model = torch.load("lenet5.pt")
keras_model = tf.keras.models.load_model("lenet5.h5")
# Extract weights from PyTorch model
torch_states = torch_model.state_dict()
weights = list(torch_states.values())
# Transform tensor to numpy array
weights = [w.numpy() for w in weights]
# Transform shape from PyTorch to Keras
for idx in [0, 2, 4]:
# conv layers: (height, width, in, out)
weights[idx] = weights[idx].transpose([2, 3, 1, 0])
for idx in [6, 8]:
# dense layers: (in, out)
weights[idx] = weights[idx].transpose()
# Set weights
keras_model.set_weights(weights)
# Save new model
keras_model.save("lenet5-torch.h5")
然后,你可以通过传入一个随机数组来验证它们是否工作相同,你可以期待输出完全一致:
import numpy as np
import torch
import tensorflow as tf
# Load the models
torch_orig_model = torch.load("lenet5.pt")
keras_orig_model = tf.keras.models.load_model("lenet5.h5")
torch_converted_model = torch.load("lenet5-keras.pt")
keras_converted_model = tf.keras.models.load_model("lenet5-torch.h5")
# Create a random input
sample = np.random.random((28,28))
# Convert sample to torch input shape
torch_sample = torch.Tensor(sample.reshape(1,1,28,28))
# Convert sample to keras input shape
keras_sample = sample.reshape(1,28,28,1)
# Check output
keras_converted_output = keras_converted_model.predict(keras_sample, verbose=0)
keras_orig_output = keras_orig_model.predict(keras_sample, verbose=0)
torch_converted_output = torch_converted_model(torch_sample).detach().numpy()
torch_orig_output = torch_orig_model(torch_sample).detach().numpy()
np.set_printoptions(precision=4)
print(keras_orig_output)
print(torch_converted_output)
print()
print(torch_orig_output)
print(keras_converted_output)
在我们的例子中,输出是:
[[9.8908e-06 2.4246e-07 3.1996e-04 8.2742e-01 1.6853e-10 1.7212e-01
3.6018e-10 1.5521e-06 1.3128e-04 2.2083e-06]]
[[9.8908e-06 2.4245e-07 3.1996e-04 8.2742e-01 1.6853e-10 1.7212e-01
3.6018e-10 1.5521e-06 1.3128e-04 2.2083e-06]]
[[4.1505e-10 1.9959e-17 1.7399e-08 4.0302e-11 9.5790e-14 3.7395e-12
1.0634e-10 1.7682e-16 1.0000e+00 8.8126e-10]]
[[4.1506e-10 1.9959e-17 1.7399e-08 4.0302e-11 9.5791e-14 3.7395e-12
1.0634e-10 1.7682e-16 1.0000e+00 8.8127e-10]]
这在足够的精度下彼此一致。请注意,由于训练的随机性,你的结果可能不会完全相同。此外,由于浮点计算的特性,即使权重相同,PyTorch 和 TensorFlow/Keras 模型也不会产生完全相同的输出。
然而,目标是展示你如何利用 Python 的检查工具来理解你不熟悉的东西并开发解决方案。
进一步阅读
本节提供了更多关于该主题的资源,如果你希望深入了解。
文章
摘要
在本教程中,你学习了如何在 Python REPL 中工作并使用检查函数来开发解决方案。具体而言,
-
你学习了如何在 REPL 中使用检查函数来了解对象的内部成员
-
你学习了如何使用 REPL 来实验 Python 代码
-
结果是,你开发了一个将 PyTorch 和 Keras 模型转换的程序
Python 中的鸭子类型、作用域和探索性函数
Python 是一种鸭子类型的语言。这意味着变量的数据类型可以随着语法的兼容性而改变。Python 也是一种动态编程语言。这意味着我们可以在程序运行时更改它,包括定义新函数和名称解析的作用域。这不仅为编写 Python 代码提供了新的范式,还为调试提供了新的工具集。接下来,我们将看到在 Python 中可以做到的,而许多其他语言无法做到的事情。
完成本教程后,你将了解到:
-
Python 是如何管理你定义的变量的
-
Python 代码如何使用你定义的变量以及为什么我们不需要像在 C 或 Java 中那样定义其类型
用我的新书 Python 机器学习 来启动你的项目,包括逐步教程和所有示例的Python 源代码文件。
让我们开始吧!
Python 中的鸭子类型、作用域和探索性函数。照片由朱莉莎·赫尔穆特拍摄。部分权利保留
概述
这个教程分为三部分;它们是
-
编程语言中的鸭子类型
-
Python 中的作用域和命名空间
-
调查类型和作用域
编程语言中的鸭子类型
鸭子类型是一些现代编程语言的特性,允许数据类型是动态的。
编程风格不查看对象类型以确定其接口是否正确;而是直接调用或使用方法或属性(“如果它看起来像鸭子并且嘎嘎叫,那它肯定是只鸭子。”)。通过强调接口而不是特定类型,设计良好的代码通过允许多态替换来提高灵活性。
简单来说,只要相同的语法仍然有意义,程序应该允许你交换数据结构。例如,在 C 语言中,你必须像以下这样定义函数:
C 语言
float fsquare(float x)
{
return x * x;
};
int isquare(int x)
{
return x * x;
};
虽然操作 x * x
对于整数和浮点数来说是相同的,但接受整数参数和接受浮点数参数的函数并不相同。因为在 C 语言中类型是静态的,所以尽管它们执行相同的逻辑,我们必须定义两个函数。在 Python 中,类型是动态的;因此,我们可以定义相应的函数为:
def square(x):
return x * x
这个特性确实给我们带来了巨大的力量和便利。例如,从 scikit-learn 中,我们有一个做交叉验证的函数:
# evaluate a perceptron model on the dataset
from numpy import mean
from numpy import std
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.linear_model import Perceptron
# define dataset
X, y = make_classification(n_samples=1000, n_features=10, n_informative=10, n_redundant=0, random_state=1)
# define model
model = Perceptron()
# define model evaluation method
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# evaluate model
scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1)
# summarize result
print('Mean Accuracy: %.3f (%.3f)' % (mean(scores), std(scores)))
但是在上述示例中,model
是 scikit-learn 模型对象的一个变量。无论它是像上述中的感知器模型,决策树,还是支持向量机模型,都无关紧要。重要的是在 cross_val_score()
函数内部,数据将通过其 fit()
函数传递给模型。因此,模型必须实现 fit()
成员函数,并且 fit()
函数的行为相同。其结果是 cross_val_score()
函数不需要特定的模型类型,只要它看起来像一个模型即可。如果我们使用 Keras 构建神经网络模型,我们可以通过包装使 Keras 模型看起来像一个 scikit-learn 模型:
# MLP for Pima Indians Dataset with 10-fold cross validation via sklearn
from keras.models import Sequential
from keras.layers import Dense
from keras.wrappers.scikit_learn import KerasClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_diabetes
import numpy
# Function to create model, required for KerasClassifier
def create_model():
# create model
model = Sequential()
model.add(Dense(12, input_dim=8, activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# fix random seed for reproducibility
seed = 7
numpy.random.seed(seed)
# load pima indians dataset
dataset = numpy.loadtxt("https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(build_fn=create_model, epochs=150, batch_size=10, verbose=0)
# evaluate using 10-fold cross validation
kfold = StratifiedKFold(n_splits=10, shuffle=True, random_state=seed)
results = cross_val_score(model, X, Y, cv=kfold)
print(results.mean())
在上述中,我们使用了来自 Keras 的包装器。还有其他的包装器,比如 scikeras。它所做的就是确保 Keras 模型的接口看起来像一个 scikit-learn 分类器,这样你就可以利用 cross_val_score()
函数。如果我们用以下内容替换上述的 model
:
model = create_model()
那么 scikit-learn 函数会抱怨找不到 model.score()
函数。
同样地,由于鸭子类型,我们可以重用一个期望列表的函数来处理 NumPy 数组或 pandas series,因为它们都支持相同的索引和切片操作。例如,我们可以如下拟合时间序列与 ARIMA 模型:
from statsmodels.tsa.statespace.sarimax import SARIMAX
import numpy as np
import pandas as pd
data = [266.0,145.9,183.1,119.3,180.3,168.5,231.8,224.5,192.8,122.9,336.5,185.9,
194.3,149.5,210.1,273.3,191.4,287.0,226.0,303.6,289.9,421.6,264.5,342.3,
339.7,440.4,315.9,439.3,401.3,437.4,575.5,407.6,682.0,475.3,581.3,646.9]
model = SARIMAX(y, order=(5,1,0))
res = model.fit(disp=False)
print("AIC = ", res.aic)
data = np.array(data)
model = SARIMAX(y, order=(5,1,0))
res = model.fit(disp=False)
print("AIC = ", res.aic)
data = pd.Series(data)
model = SARIMAX(y, order=(5,1,0))
res = model.fit(disp=False)
print("AIC = ", res.aic)
上述应该为每次拟合生成相同的 AIC 分数。
Python 中的作用域和命名空间
在大多数语言中,变量是在有限的作用域内定义的。例如,函数内部定义的变量只能在该函数内部访问:
from math import sqrt
def quadratic(a,b,c):
discrim = b*b - 4*a*c
x = -b/(2*a)
y = sqrt(discrim)/(2*a)
return x-y, x+y
局部变量 discrim
如果不在函数 quadratic()
内部,是无法访问的。这对某些人来说可能会有所惊讶:
a = 1
def f(x):
a = 2 * x
return a
b = f(3)
print(a, b)
1 6
我们在函数 f
外部定义了变量 a
,但是在函数 f
内部,变量 a
被赋值为 2 * x
。然而,函数内部的 a
和外部的 a
是无关的,除了名称。因此,当我们退出函数时,变量 a
的值没有改变。为了在函数 f
内部使其可修改,我们需要声明名称 a
是 global
的,以明确指出这个名称应该来自全局作用域,而不是局部作用域:
a = 1
def f(x):
global a
a = 2 * x
return a
b = f(3)
print(a, b)
6 6
然而,当在函数中引入嵌套作用域时,问题可能进一步复杂化。考虑以下示例:
a = 1
def f(x):
a = x
def g(x):
return a * x
return g(3)
b = f(2)
print(b)
6
函数 f
内部的变量 a
与全局的变量 a
是不同的。然而,在函数 g
中,由于没有对 a
进行写入操作,而只是读取,Python 将从最近的作用域即函数 f
中找到相同的 a
。变量 x
则是作为函数 g
的参数定义,并在调用 g(3)
时取值为 3
,而不是假定来自函数 f
中的 x
的值。
注意: 如果变量在函数的任何地方有值被赋予,它就被定义在局部作用域中。如果在赋值之前从中读取变量的值,则会引发错误,而不是使用外部或全局作用域中同名变量的值。
此属性有多种用途。Python 中许多记忆化装饰器的实现巧妙地利用了函数作用域。另一个例子是以下内容:
import numpy as np
def datagen(X, y, batch_size, sampling_rate=0.7):
"""A generator to produce samples from input numpy arrays X and y
"""
# Select rows from arrays X and y randomly
indexing = np.random.random(len(X)) < sampling_rate
Xsam, ysam = X[indexing], y[indexing]
# Actual logic to generate batches
def _gen(batch_size):
while True:
Xbatch, ybatch = [], []
for _ in range(batch_size):
i = np.random.randint(len(Xsam))
Xbatch.append(Xsam[i])
ybatch.append(ysam[i])
yield np.array(Xbatch), np.array(ybatch)
# Create and return a generator
return _gen(batch_size)
这是一个创建从输入 NumPy 数组 X
和 y
中批量样本的生成器函数。这样的生成器在 Keras 模型的训练中是可接受的。然而,出于诸如交叉验证等原因,我们不希望从整个输入数组 X
和 y
中采样,而是从它们的一个固定子集中随机选择行。我们通过在 datagen()
函数的开头随机选择一部分行并将它们保留在 Xsam
、ysam
中来实现这一点。然后在内部函数 _gen()
中,从 Xsam
和 ysam
中对行进行采样,直到创建一个批次。虽然列表 Xbatch
和 ybatch
在函数 _gen()
内部被定义和创建,但数组 Xsam
和 ysam
不是 _gen()
的局部变量。更有趣的是生成器被创建时的情况:
X = np.random.random((100,3))
y = np.random.random(100)
gen1 = datagen(X, y, 3)
gen2 = datagen(X, y, 4)
print(next(gen1))
print(next(gen2))
(array([[0.89702235, 0.97516228, 0.08893787],
[0.26395301, 0.37674529, 0.1439478 ],
[0.24859104, 0.17448628, 0.41182877]]), array([0.2821138 , 0.87590954, 0.96646776]))
(array([[0.62199772, 0.01442743, 0.4897467 ],
[0.41129379, 0.24600387, 0.53640666],
[0.02417213, 0.27637708, 0.65571031],
[0.15107433, 0.11331674, 0.67000849]]), array([0.91559533, 0.84886957, 0.30451455, 0.5144225 ]))
函数 datagen()
被调用两次,因此创建了两组不同的 Xsam
、yam
。但由于内部函数 _gen()
依赖于它们,这两组 Xsam
、ysam
同时存在于内存中。技术上来说,我们称当调用 datagen()
时,会创建一个具有特定 Xsam
、ysam
的闭包,并且调用 _gen()
会访问该闭包。换句话说,两次调用 datagen()
的作用域是共存的。
总结一下,每当一行代码引用一个名称(无论是变量、函数还是模块),名称都按照 LEGB 规则的顺序解析:
-
首先是局部作用域,即在同一函数中定义的名称
-
闭包或“非局部”作用域。如果我们在嵌套函数内部,这是上一级函数。
-
全局作用域,即在同一脚本顶层定义的名称(但不跨不同程序文件)
-
内置作用域,即由 Python 自动创建的作用域,例如变量
__name__
或函数list()
想要开始使用 Python 进行机器学习吗?
立即注册我的免费 7 天电子邮件速成课程(附有示例代码)。
点击注册并获得课程的免费 PDF 电子书版本。
调查类型和作用域
因为 Python 中类型不是静态的,有时我们想知道我们在处理什么,但从代码中并不容易看出。一种方法是使用 type()
或 isinstance()
函数。例如:
import numpy as np
X = np.random.random((100,3))
print(type(X))
print(isinstance(X, np.ndarray))
<class 'numpy.ndarray'>
True
type()
函数返回一个类型对象。isinstance()
函数返回一个布尔值,允许我们检查某个对象是否匹配特定类型。这在我们需要知道变量的类型时非常有用。如果我们将 pandas 数据框传递给我们上面定义的 datagen()
函数:
import pandas as pd
import numpy as np
def datagen(X, y, batch_size, sampling_rate=0.7):
"""A generator to produce samples from input numpy arrays X and y
"""
# Select rows from arrays X and y randomly
indexing = np.random.random(len(X)) < sampling_rate
Xsam, ysam = X[indexing], y[indexing]
# Actual logic to generate batches
def _gen(batch_size):
while True:
Xbatch, ybatch = [], []
for _ in range(batch_size):
i = np.random.randint(len(Xsam))
Xbatch.append(Xsam[i])
ybatch.append(ysam[i])
yield np.array(Xbatch), np.array(ybatch)
# Create and return a generator
return _gen(batch_size)
X = pd.DataFrame(np.random.random((100,3)))
y = pd.DataFrame(np.random.random(100))
gen3 = datagen(X, y, 3)
print(next(gen3))
在 Python 的调试器 pdb
下运行上述代码将得到如下结果:
> /Users/MLM/ducktype.py(1)<module>()
-> import pandas as pd
(Pdb) c
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/pandas/core/indexes/range.py", line 385, in get_loc
return self._range.index(new_key)
ValueError: 1 is not in range
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/pdb.py", line 1723, in main
pdb._runscript(mainpyfile)
File "/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/pdb.py", line 1583, in _runscript
self.run(statement)
File "/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/bdb.py", line 580, in run
exec(cmd, globals, locals)
File "<string>", line 1, in <module>
File "/Users/MLM/ducktype.py", line 1, in <module>
import pandas as pd
File "/Users/MLM/ducktype.py", line 18, in _gen
ybatch.append(ysam[i])
File "/usr/local/lib/python3.9/site-packages/pandas/core/frame.py", line 3458, in __getitem__
indexer = self.columns.get_loc(key)
File "/usr/local/lib/python3.9/site-packages/pandas/core/indexes/range.py", line 387, in get_loc
raise KeyError(key) from err
KeyError: 1
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> /usr/local/lib/python3.9/site-packages/pandas/core/indexes/range.py(387)get_loc()
-> raise KeyError(key) from err
(Pdb)
从追踪信息中我们看到出了问题,因为我们无法获取 ysam[i]
。我们可以使用以下方法来验证 ysam
确实是一个 Pandas DataFrame 而不是一个 NumPy 数组:
(Pdb) up
> /usr/local/lib/python3.9/site-packages/pandas/core/frame.py(3458)__getitem__()
-> indexer = self.columns.get_loc(key)
(Pdb) up
> /Users/MLM/ducktype.py(18)_gen()
-> ybatch.append(ysam[i])
(Pdb) type(ysam)
<class 'pandas.core.frame.DataFrame'>
因此,我们不能使用 ysam[i]
从 ysam
中选择行 i
。我们在调试器中可以做什么来验证我们应该如何修改代码?有几个有用的函数可以用来调查变量和作用域:
-
dir()
用于查看作用域中定义的名称或对象中定义的属性 -
locals()
和globals()
用于查看本地和全局定义的名称和值。
例如,我们可以使用 dir(ysam)
来查看 ysam
内部定义了哪些属性或函数:
(Pdb) dir(ysam)
['T', '_AXIS_LEN', '_AXIS_ORDERS', '_AXIS_REVERSED', '_AXIS_TO_AXIS_NUMBER',
...
'iat', 'idxmax', 'idxmin', 'iloc', 'index', 'infer_objects', 'info', 'insert',
'interpolate', 'isin', 'isna', 'isnull', 'items', 'iteritems', 'iterrows',
'itertuples', 'join', 'keys', 'kurt', 'kurtosis', 'last', 'last_valid_index',
...
'transform', 'transpose', 'truediv', 'truncate', 'tz_convert', 'tz_localize',
'unstack', 'update', 'value_counts', 'values', 'var', 'where', 'xs']
(Pdb)
其中一些是属性,如 shape
,还有一些是函数,如 describe()
。你可以在 pdb
中读取属性或调用函数。通过仔细阅读这个输出,我们回忆起从 DataFrame 中读取行 i
的方法是通过 iloc
,因此我们可以用以下语法进行验证:
(Pdb) ysam.iloc[i]
0 0.83794
Name: 2, dtype: float64
(Pdb)
如果我们调用 dir()
而不带任何参数,它将给出当前作用域中定义的所有名称,例如,
(Pdb) dir()
['Xbatch', 'Xsam', '_', 'batch_size', 'i', 'ybatch', 'ysam']
(Pdb) up
> /Users/MLM/ducktype.py(1)<module>()
-> import pandas as pd
(Pdb) dir()
['X', '__builtins__', '__file__', '__name__', 'datagen', 'gen3', 'np', 'pd', 'y']
(Pdb)
由于作用域会随着你在调用栈中移动而变化。类似于没有参数的 dir()
,我们可以调用 locals()
来显示所有本地定义的变量,例如,
(Pdb) locals()
{'batch_size': 3, 'Xbatch': ...,
'ybatch': ..., '_': 0, 'i': 1, 'Xsam': ...,
'ysam': ...}
(Pdb)
确实,locals()
返回一个 dict
,允许你查看所有的名称和值。因此,如果我们需要读取变量 Xbatch
,可以通过 locals()["Xbatch"]
来获取相同的内容。类似地,我们可以使用 globals()
来获取全局作用域中定义的名称字典。
这种技术有时是有益的。例如,我们可以通过使用 dir(model)
来检查一个 Keras 模型是否“编译”了。在 Keras 中,编译模型是为训练设置损失函数,并建立前向和反向传播的流程。因此,已编译的模型将具有额外定义的属性 loss
:
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential([
Dense(5, input_shape=(3,)),
Dense(1)
])
has_loss = "loss" in dir(model)
print("Before compile, loss function defined:", has_loss)
model.compile()
has_loss = "loss" in dir(model)
print("After compile, loss function defined:", has_loss)
Before compile, loss function defined: False
After compile, loss function defined: True
这使我们在代码运行之前添加了额外的保护,以防止出现错误。
进一步阅读
本节提供了更多关于该主题的资源,如果你想深入了解。
文章
-
Python 术语表(鸭子类型),
docs.python.org/3/glossary.html#term-duck-typing
-
Python 内置函数,
docs.python.org/3/library/functions.html
书籍
- 流畅的 Python,第二版,作者 Luciano Ramalho,
www.amazon.com/dp/1492056359/
概述
在本教程中,您已经看到了 Python 如何组织命名空间以及变量如何与代码交互。具体来说,您学到了:
-
Python 代码通过它们的接口使用变量;因此,变量的数据类型通常不重要。
-
Python 变量是在它们的命名空间或闭包中定义的,同名变量可以在不同的作用域中共存,因此它们不会互相干扰。
-
我们有一些来自 Python 的内置函数,允许我们检查当前作用域中定义的名称或变量的数据类型。
在 Python 中更容易进行实验
原文:
machinelearningmastery.com/easier-experimenting-in-python/
当我们在进行机器学习项目时,经常需要尝试多种替代方案。Python 中的一些特性允许我们尝试不同的选项而不需要太多的努力。在本教程中,我们将看到一些加速实验的技巧。
完成本教程后,您将学到:
-
如何利用鸭子类型特性轻松交换函数和对象
-
如何将组件变成彼此的插拔替换以帮助实验更快地运行
使用我的新书Python for Machine Learning 启动您的项目,包括逐步教程和所有示例的Python 源代码文件。
在 Python 中更容易进行实验。由Jake Givens拍摄。部分权利保留
概述
本教程分为三个部分;它们是:
-
机器学习项目的工作流程
-
函数作为对象
-
注意事项
机器学习项目的工作流程
考虑一个非常简单的机器学习项目如下:
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
# Load dataset
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/iris.csv"
names = ['sepal-length', 'sepal-width', 'petal-length', 'petal-width', 'class']
dataset = read_csv(url, names=names)
# Split-out validation dataset
array = dataset.values
X = array[:,0:4]
y = array[:,4]
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.20, random_state=1, shuffle=True)
# Train
clf = SVC()
clf.fit(X_train, y_train)
# Test
score = clf.score(X_val, y_val)
print("Validation accuracy", score)
这是一个典型的机器学习项目工作流程。我们有数据预处理阶段,然后是模型训练,之后是评估我们的结果。但在每个步骤中,我们可能想尝试一些不同的东西。例如,我们可能会想知道是否归一化数据会使其更好。因此,我们可以将上面的代码重写为以下内容:
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# Load dataset
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/iris.csv"
names = ['sepal-length', 'sepal-width', 'petal-length', 'petal-width', 'class']
dataset = read_csv(url, names=names)
# Split-out validation dataset
array = dataset.values
X = array[:,0:4]
y = array[:,4]
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.20, random_state=1, shuffle=True)
# Train
clf = Pipeline([('scaler',StandardScaler()), ('classifier',SVC())])
clf.fit(X_train, y_train)
# Test
score = clf.score(X_val, y_val)
print("Validation accuracy", score)
到目前为止一切顺利。但是如果我们继续用不同的数据集、不同的模型或不同的评分函数进行实验,每次在使用缩放器和不使用之间进行切换将意味着大量的代码更改,并且很容易出错。
因为 Python 支持鸭子类型,我们可以看到以下两个分类器模型实现了相同的接口:
clf = SVC()
clf = Pipeline([('scaler',StandardScaler()), ('classifier',SVC())])
因此,我们可以简单地在这两个版本之间选择并保持一切完整。我们可以说这两个模型是插拔替换。
利用此属性,我们可以创建一个切换变量来控制我们所做的设计选择:
USE_SCALER = True
if USE_SCALER:
clf = Pipeline([('scaler',StandardScaler()), ('classifier',SVC())])
else:
clf = SVC()
通过在USE_SCALER
变量之间切换True
和False
,我们可以选择是否应用缩放器。更复杂的例子是在不同的缩放器和分类器模型之间进行选择,例如:
SCALER = "standard"
CLASSIFIER = "svc"
if CLASSIFIER == "svc":
model = SVC()
elif CLASSIFIER == "cart":
model = DecisionTreeClassifier()
else:
raise NotImplementedError
if SCALER == "standard":
clf = Pipeline([('scaler',StandardScaler()), ('classifier',model)])
elif SCALER == "maxmin":
clf = Pipeline([('scaler',MaxMinScaler()), ('classifier',model)])
elif SCALER == None:
clf = model
else:
raise NotImplementedError
一个完整的示例如下:
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# toggle between options
SCALER = "maxmin" # "standard", "maxmin", or None
CLASSIFIER = "cart" # "svc" or "cart"
# Load dataset
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/iris.csv"
names = ['sepal-length', 'sepal-width', 'petal-length', 'petal-width', 'class']
dataset = read_csv(url, names=names)
# Split-out validation dataset
array = dataset.values
X = array[:,0:4]
y = array[:,4]
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.20, random_state=1, shuffle=True)
# Create model
if CLASSIFIER == "svc":
model = SVC()
elif CLASSIFIER == "cart":
model = DecisionTreeClassifier()
else:
raise NotImplementedError
if SCALER == "standard":
clf = Pipeline([('scaler',StandardScaler()), ('classifier',model)])
elif SCALER == "maxmin":
clf = Pipeline([('scaler',MinMaxScaler()), ('classifier',model)])
elif SCALER == None:
clf = model
else:
raise NotImplementedError
# Train
clf.fit(X_train, y_train)
# Test
score = clf.score(X_val, y_val)
print("Validation accuracy", score)
如果您进一步走一步,甚至可以跳过切换变量,直接使用字符串进行快速实验:
import numpy as np
import scipy.stats as stats
# Covariance matrix and Cholesky decomposition
cov = np.array([[1, 0.8], [0.8, 1]])
L = np.linalg.cholesky(cov)
# Generate 100 pairs of bi-variate Gaussian random numbers
if not "USE SCIPY":
z = np.random.randn(100,2)
x = z @ L.T
else:
x = stats.multivariate_normal(mean=[0, 0], cov=cov).rvs(100)
...
函数作为对象
在 Python 中,函数是一等公民。您可以将函数分配给变量。事实上,在 Python 中,函数是对象,类也是(类本身,不仅仅是类的具体实例)。因此,我们可以使用上述相似函数的相同技术进行实验。
import numpy as np
DIST = "normal"
if DIST == "normal":
rangen = np.random.normal
elif DIST == "uniform":
rangen = np.random.uniform
else:
raise NotImplementedError
random_data = rangen(size=(10,5))
print(random_data)
以上类似于调用np.random.normal(size=(10,5))
,但我们将函数保存在变量中,以便于随时替换一个函数。请注意,由于我们使用相同的参数调用函数,我们必须确保所有变体都能接受它。如果不能,我们可能需要一些额外的代码行来创建一个包装器。例如,在生成学生 t 分布的情况下,我们需要一个额外的自由度参数:
import numpy as np
DIST = "t"
if DIST == "normal":
rangen = np.random.normal
elif DIST == "uniform":
rangen = np.random.uniform
elif DIST == "t":
def t_wrapper(size):
# Student's t distribution with 3 degree of freedom
return np.random.standard_t(df=3, size=size)
rangen = t_wrapper
else:
raise NotImplementedError
random_data = rangen(size=(10,5))
print(random_data)
这是因为在上述情况中,np.random.normal
、np.random.uniform
和我们定义的t_wrapper
都可以互换使用。
想要开始学习 Python 进行机器学习吗?
现在免费参加我的 7 天电子邮件快速课程(附有示例代码)。
点击注册,还可以免费获得课程的 PDF 电子书版本。
注意事项
机器学习与其他编程项目不同,因为工作流程中存在更多的不确定性。当您构建网页或游戏时,您心中有一个目标。但在机器学习项目中,有一些探索性工作。
在其他项目中,您可能会使用像 git 或 Mercurial 这样的源代码控制系统来管理您的源代码开发历史。然而,在机器学习项目中,我们试验许多步骤的不同组合。使用 git 管理这些不同的变化可能并不合适,更不用说有时可能会过度。因此,使用切换变量来控制流程应该能让我们更快地尝试不同的方法。当我们在 Jupyter 笔记本上工作时,这特别方便。
然而,当我们将多个版本的代码放在一起时,程序变得笨拙且不易读。确认决策后最好进行一些清理工作。这将有助于我们将来的维护工作。
进一步阅读
本节提供更多关于该主题的资源,如果您希望深入了解。
书籍
- 流畅的 Python,第二版,作者 Luciano Ramalho,
www.amazon.com/dp/1492056359/
总结
在本教程中,您已经看到 Python 中的鸭子类型属性如何帮助我们创建可互换的替代品。具体而言,您学到了:
-
鸭子类型可以帮助我们在机器学习工作流中轻松切换替代方案。
-
我们可以利用切换变量来在替代方案之间进行实验。
探索 Python 生态系统
原文:
machinelearningmastery.com/exploring-the-python-ecosystem/
Python 是一种优雅的编程语言,因为其语法简单、清晰且简洁。但是,Python 如果没有其丰富的第三方库支持,将不会如此成功。Python 因数据科学和机器学习而闻名,因此它已经成为事实上的通用语言,仅仅是因为我们为这些任务提供了如此多的库。如果没有这些库,Python 就不会如此强大。
完成本教程后,您将学到:
-
-
Python 库安装在您的系统中的位置
-
什么是 PyPI,以及库代码库如何帮助您的项目
-
如何使用
pip
命令从代码库使用库
-
用我的新书 Python 机器学习 启动您的项目,包括逐步教程和所有示例的Python 源代码文件。
让我们开始吧!
探索 Python 生态系统
照片由 Vinit Srivastava 拍摄。部分权利保留。
概述
本教程分为五部分,它们是:
-
Python 生态系统
-
Python 库位置
-
pip
命令 -
搜索包
-
托管您自己的代码库
Python 生态系统
在没有互联网的旧时代,语言和库是分开的。当你从教科书学习 C 时,你看不到任何帮助你读取 CSV 文件或打开 PNG 图像的内容。Java 的旧时代也是如此。如果你需要任何官方库中不包括的东西,你需要从各种地方搜索。如何下载或安装库将取决于库的供应商。
如果我们有一个中央代码库来托管许多库,并让我们使用统一接口安装库,那会更加方便。这样一来,我们可以不时地检查新版本。更好的是,我们还可以通过关键词在代码库中搜索,以发现可以帮助我们项目的库。CPAN 是 Perl 的库示例。类似地,R 有 CRAN,Ruby 有 RubyGems,Node.js 有 npm,Java 有 Maven。对于 Python,我们有 PyPI(Python 包索引),pypi.org/
。
PyPI 是平台无关的。如果您通过从 python.org 下载安装程序在 Windows 上安装 Python,则可以使用 pip
命令访问 PyPI。如果您在 Mac 上使用 homebrew 安装 Python,则同样可以使用 pip
命令。即使您使用 Ubuntu Linux 的内置 Python,情况也是相同的。
作为一个仓库,你几乎可以在 PyPI 上找到任何东西。从大型库如 Tensorflow 和 PyTorch 到小型库如 minimal。由于 PyPI 上可用的库数量庞大,你可以轻松找到实现你项目中某些重要组件的工具。因此,我们拥有一个强大且不断增长的 Python 库生态系统,使其更加强大。
想开始使用 Python 进行机器学习吗?
现在立即领取我的免费 7 天电子邮件速成课程(附示例代码)。
点击注册并同时获取课程的免费 PDF 电子书版本。
Python 库位置
当我们在 Python 脚本中需要一个库时,我们使用:
import module_name
但 Python 如何知道在哪里读取模块的内容并将其加载到我们的脚本中?就像 Linux 的 bash shell 或 Windows 的命令提示符寻找要执行的命令一样,Python 依赖于一系列 路径 来定位要加载的模块。随时,我们可以通过打印列表 sys.path
来检查路径(在导入 sys
模块之后)。例如,在通过 homebrew 安装的 Mac 上的 Python:
import sys
print(sys.path)
它打印以下内容:
['',
'/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python39.zip',
'/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9',
'/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload',
'/usr/local/lib/python3.9/site-packages']
这意味着如果你运行 import my_module
,Python 会首先在与你当前位置相同的目录中查找 my_module
(第一个元素,空字符串)。如果未找到,Python 将检查第二个元素中 zip 文件内的模块。然后是在第三个元素下的目录中,依此类推。最终路径 /usr/local/lib/python3.9/site-packages
通常是你安装第三方库的地方。上面的第二、第三和第四元素是内置标准库的位置。
如果你在其他地方安装了一些额外的库,你可以设置环境变量 PYTHONPATH
并指向它。例如,在 Linux 和 Mac 上,我们可以在终端中运行如下命令:
Shell
$ PYTHONPATH="/tmp:/var/tmp" python print_path.py
其中 print_path.py
是上面的两行代码。运行此命令将打印以下内容:
['', '/tmp', '/var/tmp',
'/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python39.zip',
'/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9',
'/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload',
'/usr/local/lib/python3.9/site-packages']
我们看到 Python 会从 /tmp
开始搜索,然后是 /var/tmp
,最后检查内置库和已安装的第三方库。当我们设置 PYTHONPATH
环境变量时,我们使用冒号 “:
” 来分隔多个路径以搜索我们的 import
。如果你不熟悉终端语法,上面的命令行定义了环境变量并运行 Python 脚本,可以分成两条命令:
Shell
$ export PYTHONPATH="/tmp:/var/tmp"
$ python print_path.py
如果你使用的是 Windows,你需要改为这样做:
Shell
C:\> set PYTHONPATH="C:\temp;D:\temp"
C:\> python print_path.py
即,我们需要使用分号 “;
” 来分隔路径。
注意: 不推荐这样做,但你可以在 import
语句之前修改 sys.path
。Python 将在之后搜索新的位置,但这意味着将你的脚本绑定到特定路径。换句话说,你的脚本可能无法在另一台计算机上运行。
Pip 命令
上述sys.path
中打印的最后一个路径是通常安装第三方库的位置。pip
命令是从互联网获取库并将其安装到该位置的方法。最简单的语法是:
Shell
pip install scikit-learn pandas
这将安装两个包:scikit-learn 和 pandas。稍后,您可能需要在新版本发布时升级包。语法是:
Shell
pip install -U scikit-learn
其中-U
表示升级。要知道哪些包已过时,我们可以使用以下命令:
Shell
pip list --outdated
它将打印所有在 PyPI 中比您的系统新的包的列表,例如以下内容:
Package Version Latest Type
---------------------------- ---------- -------- -----
absl-py 0.14.0 1.0.0 wheel
anyio 3.4.0 3.5.0 wheel
...
xgboost 1.5.1 1.5.2 wheel
yfinance 0.1.69 0.1.70 wheel
不加--outdated
参数,pip
命令会显示所有已安装的包及其版本。你可以选择使用-V
选项显示每个包的安装位置,例如以下内容:
Shell
$ pip list -v
Package Version Location Installer
---------------------------- ---------- -------------------------------------- ---------
absl-py 0.14.0 /usr/local/lib/python3.9/site-packages pip
aiohttp 3.8.1 /usr/local/lib/python3.9/site-packages pip
aiosignal 1.2.0 /usr/local/lib/python3.9/site-packages pip
anyio 3.4.0 /usr/local/lib/python3.9/site-packages pip
...
word2number 1.1 /usr/local/lib/python3.9/site-packages pip
wrapt 1.12.1 /usr/local/lib/python3.9/site-packages pip
xgboost 1.5.1 /usr/local/lib/python3.9/site-packages pip
yfinance 0.1.69 /usr/local/lib/python3.9/site-packages pip
如果您需要检查包的摘要,可以使用pip show
命令,例如,
Shell
$ pip show pandas
Name: pandas
Version: 1.3.4
Summary: Powerful data structures for data analysis, time series, and statistics
Home-page: https://pandas.pydata.org
Author: The Pandas Development Team
Author-email: pandas-dev@python.org
License: BSD-3-Clause
Location: /usr/local/lib/python3.9/site-packages
Requires: numpy, python-dateutil, pytz
Required-by: bert-score, copulae, datasets, pandas-datareader, seaborn, statsmodels, ta, textattack, yfinance
这会为您提供一些信息,例如主页、安装位置以及它依赖的其他包以及依赖它的包。
当您需要移除一个包(例如为了释放磁盘空间),您可以简单地运行:
Shell
pip uninstall tensorflow
使用pip
命令的最后一点提示:pip 有两种类型的包。一种是作为源代码分发的包,另一种是作为二进制分发的包。它们仅在模块的某些部分不是用 Python 编写(例如 C 或 Cython)并且需要在使用前编译时才有所不同。源包将在您的机器上编译,而二进制分发已经编译,特定于平台(例如 64 位 Windows)。通常后者作为wheel
包分发,您需要先安装wheel
以享受全部好处:
pip install wheel
一个像 Tensorflow 这样的大型包将需要很多小时才能从头编译。因此,建议先安装wheel
并在可能时使用 wheel 包。
搜索包
较新版本的pip
命令已禁用了搜索功能,因为它给 PyPI 系统带来了太大的工作负担。
我们可以通过 PyPI 网页上的搜索框来查找包。
当您输入关键字,例如“梯度提升”,它将显示包含该关键字的许多包:
您可以点击每个包以获取更多详情(通常包括代码示例),以确定哪一个符合您的需求。
如果您更喜欢命令行,可以安装pip-search
包:
Shell
pip install pip-search
然后可以运行pip_search
命令来使用关键字搜索:
Shell
pip_search gradient boosting
它不会提供 PyPI 上的所有内容,因为那将有数千个。但它会提供最相关的结果。以下是来自 Mac 终端的结果:
托管自己的仓库
PyPI 是互联网上的一个仓库。但pip
命令并不只使用它。如果你有某些原因需要自己的 PyPI 服务器(例如,在你的公司网络内部托管,以便你的pip
不会超出你的防火墙),你可以尝试pypiserver
包:
Shell
pip install pypiserver
根据包的文档,你可以使用pypi-server
命令设置你的服务器。然后,你可以上传包并开始提供服务。如何配置和设置自己的服务器的细节在这里描述起来太长了。但它的作用是提供一个pip
命令可以理解的可用包的索引,并在pip
请求某个特定包时提供下载。
如果你有自己的服务器,你可以通过以下步骤在pip
中安装包:
Shell
pip install pandas --index-url https://192.168.0.234:8080
这里,--index-url
后的地址是你自己服务器的主机和端口号。
PyPI 不是唯一的仓库。如果你用 Anaconda 安装了 Python,你还有一个替代系统,conda
,来安装包。语法类似(几乎总是将pip
替换为conda
会按预期工作)。但你应该记住,它们是两个独立工作的不同系统。
进一步阅读
本节提供了更多关于该主题的资源,如果你希望深入了解。
-
pip 文档,
pip.pypa.io/en/stable/
-
Python 包索引,
pypi.org/
-
pypiserver 包,
pypi.org/project/pypiserver/
总结
在本教程中,你已经了解了pip
命令及其如何从 Python 生态系统中为你的项目提供丰富的包。具体来说,你学到了:
-
如何从 PyPI 查找包
-
Python 如何在你的系统中管理其库
-
如何安装、升级和移除系统中的包
-
如何在我们的网络中托管自己的 PyPI 版本
Python 中的函数式编程
原文:
machinelearningmastery.com/functional-programming-in-python/
Python 是一个出色的编程语言。它可能是你开发机器学习或数据科学应用程序的首选。Python 有趣的地方在于,它是一种多范式编程语言,可以用于面向对象编程和命令式编程。它具有简单的语法,易于阅读和理解。
在计算机科学和数学中,许多问题的解决方案可以通过函数式编程风格更容易和自然地表达。在本教程中,我们将讨论 Python 对函数式编程范式的支持以及帮助你以这种风格编程的 Python 类和模块。
完成本教程后,你将了解:
-
函数式编程的基本概念
-
itertools
库 -
functools
库 -
Map-reduce 设计模式及其在 Python 中的可能实现
通过我的新书 《Python 与机器学习》,启动你的项目,包括逐步教程和所有示例的Python 源代码文件。
让我们开始吧。
Python 中的函数式编程
图片由 Abdullah_Shakoor 提供,部分版权保留
教程概述
本教程分为五个部分;它们是:
-
函数式编程的思想
-
高阶函数:过滤、映射和归约
-
Itertools
-
Functools
-
Map-reduce 模式
函数式编程的思想
如果你有编程经验,你很可能学习了命令式编程。它由语句和变量操作构成。函数式编程是一种声明式范式。它不同于命令式范式,程序通过应用和组合函数来构建。这里的函数应更接近数学函数的定义,其中没有副作用,即没有对外部变量的访问。当你用相同的参数调用它们时,它们总是给出相同的结果。
函数式编程的好处是使你的程序更少出错。没有副作用,它更可预测且更容易查看结果。我们也不需要担心程序的某个部分会干扰到另一个部分。
许多库采用了函数式编程范式。例如,以下使用 pandas 和 pandas-datareader:
import pandas_datareader as pdr
import pandas_datareader.wb
df = (
pdr.wb
.download(indicator="SP.POP.TOTL", country="all", start=2000, end=2020)
.reset_index()
.filter(["country", "SP.POP.TOTL"])
.groupby("country")
.mean()
)
print(df)
这将给你以下输出:
SP.POP.TOTL
country
Afghanistan 2.976380e+07
Africa Eastern and Southern 5.257466e+08
Africa Western and Central 3.550782e+08
Albania 2.943192e+06
Algeria 3.658167e+07
... ...
West Bank and Gaza 3.806576e+06
World 6.930446e+09
Yemen, Rep. 2.334172e+07
Zambia 1.393321e+07
Zimbabwe 1.299188e+07
pandas-datareader 是一个有用的库,帮助您实时从互联网下载数据。上述示例是从世界银行下载人口数据。结果是一个带有国家和年份作为索引的 pandas dataframe,并且一个名为“SP.POP.TOTL”的列表示人口数量。然后我们逐步操作数据帧,并最终找出所有国家在多年间的平均人口数量。
我们可以这样写是因为,在 pandas 中,大多数对数据帧的函数不会改变数据帧本身,而是生成一个新的数据帧以反映函数的结果。我们称这种行为为不可变,因为输入数据帧从未改变。其结果是我们可以逐步链式调用函数来操作数据帧。如果我们必须使用命令式编程的风格来打破它,上面的程序等同于以下内容:
import pandas_datareader as pdr
import pandas_datareader.wb
df = pdr.wb.download(indicator="SP.POP.TOTL", country="all", start=2000, end=2020)
df = df.reset_index()
df = df.filter(["country", "SP.POP.TOTL"])
groups = df.groupby("country")
df = groups.mean()
print(df)
高阶函数:过滤(Filter)、映射(Map)和减少(Reduce)
Python 不是严格的函数式编程语言。但以函数式风格编写 Python 非常简单。有三个基本的迭代函数允许我们以非常简单的方式编写一个功能强大的程序:filter、map 和 reduce。
过滤(Filter)是从可迭代对象中选择一些元素,比如一个列表。映射(Map)是逐个转换元素。最后,减少(Reducing)是将整个可迭代对象转换为不同的形式,比如所有元素的总和或将列表中的子字符串连接成更长的字符串。为了说明它们的使用,让我们考虑一个简单的任务:给定来自 Apache Web 服务器的日志文件,找到发送了最多 404 错误请求的 IP 地址。如果你不知道 Apache Web 服务器的日志文件是什么样的,以下是一个例子:
89.170.74.95 - - [17/May/2015:16:05:27 +0000] "HEAD /projects/xdotool/ HTTP/1.1" 200 - "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0"
95.82.59.254 - - [19/May/2015:03:05:19 +0000] "GET /images/jordan-80.png HTTP/1.1" 200 6146 "http://www.semicomplete.com/articles/dynamic-dns-with-dhcp/" "Mozilla/5.0 (Windows NT 6.1; rv:27.0) Gecko/20100101 Firefox/27.0"
155.140.133.248 - - [19/May/2015:06:05:34 +0000] "GET /images/jordan-80.png HTTP/1.1" 200 6146 "http://www.semicomplete.com/blog/geekery/debugging-java-performance.html" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"
68.180.224.225 - - [20/May/2015:20:05:02 +0000] "GET /blog/tags/documentation HTTP/1.1" 200 12091 "-" "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)"
上述内容来自一个更大的文件,位于这里。这些是日志中的几行内容。每行以客户端(即浏览器)的 IP 地址开头,“HTTP/1.1”后面的代码是响应状态码。通常情况下,如果请求被满足,则状态码为 200。但如果浏览器请求了服务器上不存在的内容,则代码将为 404。要找到对应于最多 404 请求的 IP 地址,我们可以简单地逐行扫描日志文件,找到其中的 404 请求,计算 IP 地址以识别出现最多次数的那一个。
在 Python 代码中,我们可以这样做。首先,我们看看如何读取日志文件并从一行中提取 IP 地址和状态码:
import urllib.request
import re
# Read the log file, split into lines
logurl = "https://raw.githubusercontent.com/elastic/examples/master/Common%20Data%20Formats/apache_logs/apache_logs"
logfile = urllib.request.urlopen(logurl).read().decode("utf8")
lines = logfile.splitlines()
# using regular expression to extract IP address and status code from a line
def ip_and_code(logline):
m = re.match(r'([\d\.]+) .*? \[.*?\] ".*?" (\d+) ', logline)
return (m.group(1), m.group(2))
print(ip_and_code(lines[0]))
然后我们可以使用一些 map()和 filter()以及其他一些函数来查找 IP 地址:
...
import collections
def is404(pair):
return pair[1] == "404"
def getIP(pair):
return pair[0]
def count_ip(count_item):
ip, count = count_item
return (count, ip)
# transform each line into (IP address, status code) pair
ipcodepairs = map(ip_and_code, lines)
# keep only those with status code 404
pairs404 = filter(is404, ipcodepairs)
# extract the IP address part from each pair
ip404 = map(getIP, pairs404)
# count the occurrences, the result is a dictionary of IP addresses map to the count
ipcount = collections.Counter(ip404)
# convert the (IP address, count) tuple into (count, IP address) order
countip = map(count_ip, ipcount.items())
# find the tuple with the maximum on the count
print(max(countip))
这里,我们没有使用 reduce()函数,因为我们有一些专门的 reduce 操作内置,比如max()
。但事实上,我们可以使用列表推导符号来编写一个更简单的程序:
...
ipcodepairs = [ip_and_code(x) for x in lines]
ip404 = [ip for ip,code in ipcodepairs if code=="404"]
ipcount = collections.Counter(ip404)
countip = [(count,ip) for ip,count in ipcount.items()]
print(max(countip))
或者甚至可以将它写成一个单一语句(但可读性较差):
import urllib.request
import re
import collections
logurl = "https://raw.githubusercontent.com/elastic/examples/master/Common%20Data%20Formats/apache_logs/apache_logs"
print(
max(
[(count,ip) for ip,count in
collections.Counter([
ip for ip, code in
[ip_and_code(x) for x in
urllib.request.urlopen(logurl)
.read()
.decode("utf8")
.splitlines()
]
if code=="404"
]).items()
]
)
)
想要开始使用 Python 进行机器学习吗?
现在就参加我的免费 7 天电子邮件速成课程(包含示例代码)。
点击注册并免费获得课程的 PDF 电子书版本。
Python 中的迭代工具
上述关于过滤器、映射器和归约器的示例说明了迭代对象在 Python 中的普遍性。这包括列表、元组、字典、集合,甚至生成器,所有这些都可以使用 for 循环进行迭代。在 Python 中,我们有一个名为itertools
的模块,它提供了更多的函数来操作(但不改变)迭代对象。来自Python 官方文档:
该模块标准化了一组核心的快速、内存高效的工具,这些工具本身或与其他工具结合使用都很有用。它们共同形成了一种“迭代器代数”,使得在纯 Python 中简洁高效地构造专用工具成为可能。
我们将在本教程中讨论itertools
的一些函数。在尝试下面给出的示例时,请确保导入itertools
和operator
,如:
import itertools
import operator
无限迭代器
无限迭代器帮助你创建无限长度的序列,如下所示。
构造 + 示例 | 输出 |
---|---|
count() ```py | |
start = 0 | |
step = 100 | |
for i in itertools.count(start, step): | |
print(i) | |
if i>=1000: | |
break | |
``` | ```py 0 |
100 | |
200 | |
300 | |
400 | |
500 | |
600 | |
700 | |
800 | |
900 | |
1000 |
| `cycle()` ```py
counter = 0
cyclic_list = [1, 2, 3, 4, 5]
for i in itertools.cycle(cyclic_list):
print(i)
counter = counter+1
if counter>10:
break
```| ```py 1
2
3
4
5
1
2
3
4
5
1
```|
| `repeat()` ```py
for i in itertools.repeat(3,5):
print(i)
```| ```py 3
3
3
3
3
```|
### 组合迭代器
你可以使用这些迭代器创建排列、组合等。
| 构造 + 示例 | 输出 |
| --- | --- |
| `product()` ```py
x = [1, 2, 3]
y = ['A', 'B']
print(list(itertools.product(x, y)))
```| ```py [(1, 'A'), (1, 'B'), (2, 'A'), (2, 'B'),
(3, 'A'), (3, 'B')]
```|
| `permutations()` ```py
x = [1, 2, 3]
print(list(itertools.permutations(x)))
```| ```py [(1, 2, 3), (1, 3, 2), (2, 1, 3),
(2, 3, 1), (3, 1, 2), (3, 2, 1)]
```|
| `combinations()` ```py
y = ['A', 'B', 'C', 'D']
print(list(itertools.combinations(y, 3)))
```| ```py [('A', 'B', 'C'), ('A', 'B', 'D'),
('A', 'C', 'D'), ('B', 'C', 'D')]
```|
| `combinations_with_replacement()` ```py
z = ['A', 'B', 'C']
print(list(itertools.combinations_with_replacement(z, 2)))
```| ```py [('A', 'A'), ('A', 'B'), ('A', 'C'),
('B', 'B'), ('B', 'C'), ('C', 'C')]
```|
### 更多有用的迭代器
还有其他迭代器会在传入的两个列表中较短的那个结束时停止。其中一些在下面有所描述。这不是一个详尽的列表,你可以在[这里查看完整列表](https://docs.python.org/3/library/itertools.html#itertool-functions)。
#### 累积()
自动创建一个迭代器,该迭代器累计给定操作符或函数的结果并返回结果。你可以从 Python 的`operator`库中选择一个操作符,或编写自定义操作符。
```py
# Custom operator
def my_operator(a, b):
return a+b if a>5 else a-b
x = [2, 3, 4, -6]
mul_result = itertools.accumulate(x, operator.mul)
print("After mul operator", list(mul_result))
pow_result = itertools.accumulate(x, operator.pow)
print("After pow operator", list(pow_result))
my_operator_result = itertools.accumulate(x, my_operator)
print("After customized my_operator", list(my_operator_result))
After mul operator [2, 6, 24, -144]
After pow operator [2, 8, 4096, 2.117582368135751e-22]
After customized my_operator [2, -1, -5, 1]
Starmap()
将相同的操作符应用于项对。
pair_list = [(1, 2), (4, 0.5), (5, 7), (100, 10)]
starmap_add_result = itertools.starmap(operator.add, pair_list)
print("Starmap add result: ", list(starmap_add_result))
x1 = [2, 3, 4, -6]
x2 = [4, 3, 2, 1]
starmap_mul_result = itertools.starmap(operator.mul, zip(x1, x2))
print("Starmap mul result: ", list(starmap_mul_result))
Starmap add result: [3, 4.5, 12, 110]
Starmap mul result: [8, 9, 8, -6]
filterfalse()
根据特定标准筛选数据。
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_result = itertools.filterfalse(lambda x: x%2, my_list)
small_terms = itertools.filterfalse(lambda x: x>=5, my_list)
print('Even result:', list(even_result))
print('Less than 5:', list(small_terms))
Even result: [2, 4, 6, 8, 10]
Less than 5: [1, 2, 3, 4]
Python 中的 Functools
在大多数编程语言中,将函数作为参数传递或函数返回另一个函数可能会令人困惑或难以处理。Python 包含了functools
库,使得处理这些函数变得容易。来自 Python 官方functools
文档:
functools
模块用于高阶函数:作用于或返回其他函数的函数。一般来说,任何可调用对象都可以被视为函数。
在这里,我们解释了这个库的一些有趣功能。你可以在这里查看functools
函数的完整列表。
使用lru_cache
在命令式编程语言中,递归是非常昂贵的。每次调用函数时,它都会被评估,即使它被相同的参数集调用。在 Python 中,lru_cache
是一个装饰器,可以用来缓存函数评估的结果。当函数再次用相同的参数集调用时,会使用存储的结果,从而避免了与递归相关的额外开销。
我们来看以下示例。我们有相同的计算第 n 个斐波那契数的实现,有和没有 lru_cache
。我们可以看到 fib(30)
有 31 次函数评估,这正如我们预期的那样,因为 lru_cache
。fib()
函数仅对 n=0,1,2…30 被调用,并且结果存储在内存中,稍后使用。这明显少于 fib_slow(30)
,它有 2692537 次评估。
import functools
@functools.lru_cache
def fib(n):
global count
count = count + 1
return fib(n-2) + fib(n-1) if n>1 else 1
def fib_slow(n):
global slow_count
slow_count = slow_count + 1
return fib_slow(n-2) + fib_slow(n-1) if n>1 else 1
count = 0
slow_count = 0
fib(30)
fib_slow(30)
print('With lru_cache total function evaluations: ', count)
print('Without lru_cache total function evaluations: ', slow_count)
With lru_cache total function evaluations: 31
Without lru_cache total function evaluations: 2692537
值得注意的是,lru_cache
装饰器在你在 Jupyter notebooks 中尝试机器学习问题时特别有用。如果你有一个从互联网上下载数据的函数,将其用 lru_cache
装饰可以将下载的内容保存在内存中,并避免即使你多次调用下载函数也重复下载相同的文件。
使用 reduce()
Reduce 类似于 itertools.accumulate()
。它将一个函数重复应用于列表的元素,并返回结果。以下是一些带有注释的示例,以解释这些函数的工作原理。
# Evaluates ((1+2)+3)+4
list_sum = functools.reduce(operator.add, [1, 2, 3, 4])
print(list_sum)
# Evaluates (2³)⁴
list_pow = functools.reduce(operator.pow, [2, 3, 4])
print(list_pow)
10
4096
reduce()
函数可以接受任何“操作符”,并可以选择性地指定初始值。例如,前面示例中的 collections.Counter
函数可以如下实现:
import functools
def addcount(counter, element):
if element not in counter:
counter[element] = 1
else:
counter[element] += 1
return counter
items = ["a", "b", "a", "c", "d", "c", "b", "a"]
counts = functools.reduce(addcount, items, {})
print(counts)
{'a': 3, 'b': 2, 'c': 2, 'd': 1}
使用 partial()
有时你会有一个接受多个参数的函数,其中一些参数被反复使用。partial()
函数返回一个具有较少参数的新版本的相同函数。
例如,如果你需要重复计算 2 的幂,你可以创建一个新的 numpy 的 power()
函数,如下所示:
import numpy
power_2 = functools.partial(np.power, 2)
print('2⁴ =', power_2(4))
print('2⁶ =', power_2(6))
2⁴ = 16
2⁶ = 64
Map-Reduce 模式
在前面的章节中,我们提到了 filter、map 和 reduce 函数作为高阶函数。使用 map-reduce 设计模式确实是帮助我们轻松创建高可扩展性程序的一种方法。map-reduce 模式是对许多类型的计算的抽象表示,这些计算操作列表或对象集合。map
阶段将输入集合映射到一个中间表示。reduce
步骤从这个中间表示中计算出一个单一的输出。这个设计模式在函数式编程语言中非常流行。Python 也提供了构造来高效地实现这一设计模式。
在 Python 中的 Map-Reduce
作为 map-reduce 设计模式的一个例子,假设我们要统计列表中能被 3 整除的数字。我们将使用 lambda
定义一个匿名函数,并利用它来 map()
列表中的所有项,判断它们是否通过我们的可整除性测试,然后将它们映射为 1 或 0。map()
函数接受一个函数和一个可迭代对象作为参数。接下来,我们将使用 reduce()
来累积最终结果。
# All numbers from 1 to 20
input_list = list(range(20))
# Use map to see which numbers are divisible by 3
bool_list = map(lambda x: 1 if x%3==0 else 0, input_list)
# Convert map object to list
bool_list = list(bool_list)
print('bool_list =', bool_list)
total_divisible_3 = functools.reduce(operator.add, bool_list)
print('Total items divisible by 3 = ', total_divisible_3)
bool_list = [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]
Total items divisible by 3 = 7
尽管非常简单,但之前的示例说明了在 Python 中实现 map-reduce
设计模式是多么容易。您可以使用 Python 中出乎意料的简单易用的构造来解决复杂且漫长的问题。
进一步阅读
本节提供了更多关于该主题的资源,以便您深入了解。
书籍
-
《思考 Python:如何像计算机科学家一样思考》 由 Allen B. Downey 编写
-
《Python 3 编程:Python 语言完全介绍》 由 Mark Summerfield 编写
-
《Python 编程:计算机科学导论》 由 John Zelle 编写
Python 官方文档
总结
在本教程中,您了解了支持函数式编程的 Python 特性。
具体来说,您学到了:
-
使用
itertools
在 Python 中返回有限或无限序列的可迭代对象 -
functools
支持的高阶函数 -
map-reduce 设计模式在 Python 中的实现
对于这篇文章中讨论的 Python,您有任何问题吗?请在下面的评论中提问,我会尽力回答。
Google Colab 机器学习项目
原文:
machinelearningmastery.com/google-colab-for-machine-learning-projects/
您是否曾想要一个易于配置的交互环境来运行您的机器学习代码,并且可以免费访问 GPU?Google Colab 是您一直在寻找的答案。它是一种方便且易于使用的方式来在云端运行 Jupyter notebooks,其免费版本还提供了一些有限的 GPU 访问权限。
如果您熟悉 Jupyter notebooks,学习 Colab 将会非常简单,我们甚至可以导入 Jupyter notebooks 以在 Google Colab 上运行。但 Colab 还有很多巧妙的功能,我们将在本文中探索。让我们深入了解吧!
完成教程后,您将学习如何:
-
使用 Google Colab 的免费 GPU 额度加速训练
-
使用 Google Colab 的扩展程序保存到 Google Drive,为 pandas DataFrame 提供交互式显示等。
-
在使用 Google Colab 训练时保存模型进度
使用我的新书Kick-start your project 《Python 机器学习》,包括逐步教程和所有示例的Python 源代码文件。
Google Colab 机器学习项目
由 NASA 拍摄并由 托马斯·托莫普洛斯 处理。保留部分权利。
概述
本教程分为五个部分,它们是:
-
什么是 Google Colab?
-
Google Colab 快速入门指南
-
探索您的 Colab 环境
-
实用的 Google Colab 扩展
-
示例:将模型进度保存到 Google Drive
什么是 Google Colab?
来自 “欢迎使用 Colab” notebook:
Colab notebooks 允许您在一个文档中结合可执行代码和富文本,以及图像、HTML、LaTeX 等。当您创建自己的 Colab notebooks 时,它们会存储在您的 Google Drive 帐户中。您可以轻松地与同事或朋友分享您的 Colab notebooks,允许他们对您的 notebooks 进行评论或甚至编辑它们。
我们可以像使用 Jupyter notebooks 一样使用 Google Colabs。它们非常方便,因为 Google Colab 托管它们,所以我们不使用任何自己的计算机资源来运行 notebook。我们还可以分享这些 notebooks,使其他人能够轻松运行我们的代码,所有这些都在标准环境中,因为它不依赖于我们自己的本地机器。然而,在初始化期间,我们可能需要在我们的环境中安装一些库。
想要开始使用 Python 进行机器学习?
现在参加我的免费 7 天电子邮件速成课程(附示例代码)。
点击注册并免费获取课程的 PDF 电子书版。
Google Colab 快速入门指南
要创建你的 Google Colab 文件并开始使用 Google Colab,你可以前往 Google Drive 并创建一个 Google Drive 帐户(如果你还没有)。现在,点击 Google Drive 页面左上角的“新建”按钮,然后点击“更多” ▷ “Google Colaboratory”。
创建一个新的 Google Colab 笔记本
你将进入你的新 Google Colab 文件页面:
新 Google Colab 笔记本
从这里,你可以使用右上角的共享按钮与他人分享你的 Google Colab 文件,或者开始编程!
Colab 上的快捷键与 Jupyter 笔记本上的类似。以下是一些有用的快捷键:
-
运行单元格:Ctrl + Enter
-
运行单元格并在下面添加新单元格:Alt + Enter
-
运行单元格并转到下一个单元格:Shift + Enter
-
增加两格缩进:Ctrl + ]
-
减少两格缩进:Ctrl +
但还有一个非常有用的功能,可以让你只运行单元格中的特定选定部分代码:
- 运行单元格的选定部分:Ctrl + Shift + Enter
就像 Jupyter 笔记本一样,你也可以使用 Markdown 单元格编写文本。但 Colab 还有一个额外的功能,可以根据你的 Markdown 内容自动生成目录,你还可以根据 Markdown 单元格中的标题隐藏部分代码。
[
Google Colab 使用 Markdown 和目录
如果你在自己的电脑上运行 Jupyter,你只能使用电脑的 CPU。但在 Colab 中,你可以将运行时更改为包括 GPU 和 TPU,除了 CPU,因为它是在 Google 的云端执行的。你可以通过访问 Runtime ▷ Change runtime type 来切换到不同的运行时:
更改 Google Colab 的运行时类型
然后你可以从不同的硬件加速器中选择,以装备你的环境。
将 GPU/TPU 添加到 Google Colab 笔记本环境
与自己的电脑不同,Google Colab 不提供终端来输入命令以管理你的 Python 环境。要安装 Python 库和其他程序,我们可以使用 !
字符来运行 shell 命令,就像在 Jupyter 笔记本中一样,例如 !pip install numpy
(但正如我们稍后看到的,Colab 已经预装了很多我们需要的库,例如 NumPy)
现在我们知道如何设置 Colab 环境并开始运行一些代码,让我们来探索一下这个环境吧!
探索你的 Colab 环境
由于我们可以使用 !
运行一些 shell 命令,wget
命令可能是获取数据的最简单方法。例如,运行此命令将把一个 CSV 文件带到 Colab 环境中:
! wget https://raw.githubusercontent.com/jbrownlee/Datasets/master/shampoo.csv
要探索你在虚拟机上 Colab 文件的当前工作目录,请点击屏幕左侧的文件图标。默认情况下,Colab 为你提供一个名为 sample_data
的目录,其中包含一些文件:
Google Colab 笔记本的文件选项卡
这是我们 Colab 笔记本的当前工作目录。你可以在笔记本中使用类似这样的代码读取其中的一个文件:
file = open("sample_data/mnist_test.csv")
稍后我们将探讨如何使用 Colab 扩展将我们的 Google Drive 挂载到这个目录,以便存储和访问我们 Google Drive 帐户中的文件。
通过使用 !
运行 shell 命令,我们还可以查看 Colab 环境的硬件配置。要查看 CPU,我们可以使用:
!cat /proc/cpuinfo
这给出了我的环境的输出:
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 63
model name : Intel(R) Xeon(R) CPU @ 2.30GHz
stepping : 0
microcode : 0x1
cpu MHz : 2299.998
cache size : 46080 KB
…
processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 63
model name : Intel(R) Xeon(R) CPU @ 2.30GHz
stepping : 0
microcode : 0x1
cpu MHz : 2299.998
cache size : 46080 KB
…
我们还可以通过使用以下命令检查是否附加了 GPU:
!nvidia-smi
如果你有一个,这将给出输出:
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.32.03 Driver Version: 460.32.03 CUDA Version: 11.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 Tesla K80 Off | 00000000:00:04.0 Off | 0 |
| N/A 57C P8 31W / 149W | 0MiB / 11441MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
这些只是一些我们可以用来探索 Colab 环境的 shell 命令示例。还有许多其他命令,如 !pip list
用于查看 Colab 环境访问的库,标准的 !ls
用于探索工作目录中的文件等。
有用的 Colab 扩展
Colab 还配备了许多非常有用的扩展。其中一个扩展允许我们将 Google Drive 挂载到我们的工作目录。我们可以通过以下方式完成这项操作:
import os
from google.colab import drive
MOUNTPOINT = "/content/gdrive"
DATADIR = os.path.join(MOUNTPOINT, "MyDrive")
drive.mount(MOUNTPOINT)
然后,Colab 将请求访问你的 Google Drive 文件的权限,你可以在选择要授权的 Google 帐户后完成。授予所需权限后,我们可以在左侧的文件选项卡中看到我们的 Google Drive 已挂载。
Google Drive 挂载到 Google Colab 笔记本的当前工作目录
然后,要将文件写入我们的 Google Drive,我们可以执行以下操作:
...
# writes directly to google drive
with open(f"{DATADIR}/test.txt", "w") as outfile:
outfile.write("Hello World!")
这段代码将 Hello World!
写入到 Google Drive 顶层的 test.txt
文件中。同样,我们也可以通过使用以下代码从 Google Drive 文件中读取内容:
...
with open(f"{DATADIR}/test.txt", "r") as infile:
file_data = infile.read()
print(file_data)
这输出:
Hello World!
这基于我们之前的示例。
此外,Google Colab 还提供了一些扩展,以创造更好的笔记本体验。如果我们经常使用 pandas DataFrame,有一个扩展可以显示交互式表格。要使用此功能,我们可以使用魔法函数:
%load_ext google.colab.data_table
这启用了 DataFrames 的交互式显示,然后当我们运行:
from sklearn.datasets import fetch_openml
X = fetch_openml("diabetes", version=1, as_frame=True, return_X_y=False)["frame"]
X
这将把 DataFrame 显示为一个交互式表格,我们可以根据列进行筛选,查看表格中的不同行等。
Google Colab 中 pandas DataFrame 的交互式接口
要稍后禁用此功能,我们可以运行:
%unload_ext google.colab.data_table
当我们再次显示相同的 DataFrame X
时,我们得到标准的 Pandas DataFrame 接口:
pandas DataFrame 的标准接口
示例:在 Google Drive 上保存模型进度
Google Colab 可能是为您的机器学习项目提供强大 GPU 资源的最简单方法。但是在 Colab 的免费版本中,Google 限制了我们每个会话中使用 Colab 笔记本的时间。我们的内核可能会无缘无故终止。我们可以重新启动笔记本并继续工作,但可能会丢失内存中的所有内容。如果我们需要长时间训练模型,这是一个问题。我们的 Colab 实例可能会在训练完成之前终止。
使用 Google Colab 扩展来挂载我们的 Google Drive 和 Keras ModelCheckpoint 回调,我们可以将模型进度保存到 Google Drive。这对于绕过 Colab 超时特别有用。对于付费的 Pro 和 Pro+ 用户,限制较宽松,但始终有可能在随机时间中途终止模型训练。如果我们不想丢失部分训练的模型,这非常有价值。
在这个演示中,我们将使用 LeNet-5 模型对 MNIST 数据集进行训练。
import tensorflow as tf
from tensorflow import keras
from keras.layers import Input, Dense, Conv2D, Flatten, MaxPool2D
from keras.models import Model
class LeNet5(tf.keras.Model):
def __init__(self):
super(LeNet5, self).__init__()
#creating layers in initializer
self.conv1 = Conv2D(filters=6, kernel_size=(5,5), padding="same", activation="relu")
self.max_pool2x2 = MaxPool2D(pool_size=(2,2))
self.conv2 = Conv2D(filters=16, kernel_size=(5,5), padding="same", activation="relu")
self.flatten = Flatten()
self.fc1 = Dense(units=120, activation="relu")
self.fc2 = Dense(units=84, activation="relu")
self.fc3=Dense(units=10, activation="softmax")
def call(self, input_tensor):
conv1 = self.conv1(input_tensor)
maxpool1 = self.max_pool2x2(conv1)
conv2 = self.conv2(maxpool1)
maxpool2 = self.max_pool2x2(conv2)
flatten = self.flatten(maxpool2)
fc1 = self.fc1(flatten)
fc2 = self.fc2(fc1)
fc3 = self.fc3(fc2)
return fc3
然后,为了在训练期间将模型进度保存到 Google Drive,我们首先需要将 Google Drive 挂载到 Colab 环境中。
import os
from google.colab import drive
MOUNTPOINT = "/content/gdrive"
DATADIR = os.path.join(MOUNTPOINT, "MyDrive")
drive.mount(MOUNTPOINT)
之后,我们声明回调以将检查点模型保存到 Google Drive。
import tensorflow as tf
checkpoint_path = DATADIR + "/checkpoints/cp-epoch-{epoch}.ckpt"
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
save_weights_only=True,
verbose=1)
接下来,我们开始在 MNIST 数据集上训练,并使用检查点回调,以确保在 Colab 会话超时时可以从最后一个周期恢复:
import tensorflow as tf
from tensorflow import keras
from keras.layers import Input, Dense, Conv2D, Flatten, MaxPool2D
from keras.models import Model
mnist_digits = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist_digits.load_data()
input_layer = Input(shape=(28,28,1))
model = LeNet5()(input_layer)
model = Model(inputs=input_layer, outputs=model)
model.compile(optimizer="adam", loss=tf.keras.losses.SparseCategoricalCrossentropy(), metrics="acc")
model.fit(x=train_images, y=train_labels, batch_size=256, validation_data = [test_images, test_labels], epochs=5, callbacks=[cp_callback])
这将训练我们的模型并给出输出:
Epoch 1/5
235/235 [==============================] - ETA: 0s - loss: 0.9580 - acc: 0.8367
Epoch 1: saving model to /content/gdrive/MyDrive/checkpoints/cp-epoch-1.ckpt
235/235 [==============================] - 11s 7ms/step - loss: 0.9580 - acc: 0.8367 - val_loss: 0.1672 - val_acc: 0.9492
Epoch 2/5
229/235 [============================>.] - ETA: 0s - loss: 0.1303 - acc: 0.9605
Epoch 2: saving model to /content/gdrive/MyDrive/checkpoints/cp-epoch-2.ckpt
235/235 [==============================] - 1s 5ms/step - loss: 0.1298 - acc: 0.9607 - val_loss: 0.0951 - val_acc: 0.9707
Epoch 3/5
234/235 [============================>.] - ETA: 0s - loss: 0.0810 - acc: 0.9746
Epoch 3: saving model to /content/gdrive/MyDrive/checkpoints/cp-epoch-3.ckpt
235/235 [==============================] - 1s 6ms/step - loss: 0.0811 - acc: 0.9746 - val_loss: 0.0800 - val_acc: 0.9749
Epoch 4/5
230/235 [============================>.] - ETA: 0s - loss: 0.0582 - acc: 0.9818
Epoch 4: saving model to /content/gdrive/MyDrive/checkpoints/cp-epoch-4.ckpt
235/235 [==============================] - 1s 6ms/step - loss: 0.0580 - acc: 0.9819 - val_loss: 0.0653 - val_acc: 0.9806
Epoch 5/5
222/235 [===========================>..] - ETA: 0s - loss: 0.0446 - acc: 0.9858
Epoch 5: saving model to /content/gdrive/MyDrive/checkpoints/cp-epoch-5.ckpt
235/235 [==============================] - 1s 6ms/step - loss: 0.0445 - acc: 0.9859 - val_loss: 0.0583 - val_acc: 0.9825
从输出中,我们可以看到检查点已经被保存。查看我的 Google Drive 文件夹,我们还可以看到检查点存储在那里。
存储在 Google Drive 的检查点
Colab 实例在 Google 的云环境中运行。运行的机器有一些存储空间,因此我们可以安装软件包或下载一些文件。然而,我们不应将检查点保存到那里,因为我们不能保证会在会话终止后重新获得它。因此,我们在上面将 Google Drive 挂载到实例中,并将检查点保存在 Google Drive 中。这是确保检查点文件可访问的方式。
这里附上模型训练和保存到 Google Drive 的完整代码:
import os
from google.colab import drive
import tensorflow as tf
from tensorflow import keras
from keras.layers import Input, Dense, Conv2D, Flatten, MaxPool2D
from keras.models import Model
MOUNTPOINT = "/content/gdrive"
DATADIR = os.path.join(MOUNTPOINT, "MyDrive")
drive.mount(MOUNTPOINT)
class LeNet5(tf.keras.Model):
def __init__(self):
super(LeNet5, self).__init__()
self.conv1 = Conv2D(filters=6, kernel_size=(5,5), padding="same", activation="relu")
self.max_pool2x2 = MaxPool2D(pool_size=(2,2))
self.conv2 = Conv2D(filters=16, kernel_size=(5,5), padding="same", activation="relu")
self.flatten = Flatten()
self.fc1 = Dense(units=120, activation="relu")
self.fc2 = Dense(units=84, activation="relu")
self.fc3=Dense(units=10, activation="softmax")
def call(self, input_tensor):
conv1 = self.conv1(input_tensor)
maxpool1 = self.max_pool2x2(conv1)
conv2 = self.conv2(maxpool1)
maxpool2 = self.max_pool2x2(conv2)
flatten = self.flatten(maxpool2)
fc1 = self.fc1(flatten)
fc2 = self.fc2(fc1)
fc3 = self.fc3(fc2)
return fc3
mnist_digits = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist_digits.load_data()
# saving checkpoints
checkpoint_path = DATADIR + "/checkpoints/cp-epoch-{epoch}.ckpt"
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
save_weights_only=True,
verbose=1)
input_layer = Input(shape=(28,28,1))
model = LeNet5()(input_layer)
model = Model(inputs=input_layer, outputs=model)
model.compile(optimizer="adam", loss=tf.keras.losses.SparseCategoricalCrossentropy(), metrics="acc")
model.fit(x=train_images, y=train_labels, batch_size=256, validation_data = [test_images, test_labels],
epochs=5, callbacks=[cp_callback])
如果模型训练中途停止,我们只需重新编译模型并加载权重,然后可以继续训练:
checkpoint_path = DATADIR + "/checkpoints/cp-epoch-{epoch}.ckpt"
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
save_weights_only=True,
verbose=1)
input_layer = Input(shape=(28,28,1))
model = LeNet5()(input_layer)
model = Model(inputs=input_layer, outputs=model)
model.compile(optimizer="adam", loss=tf.keras.losses.SparseCategoricalCrossentropy(), metrics="acc")
# to resume from epoch 5 checkpoints
model.load_weights(DATADIR + "/checkpoints/cp-epoch-5.ckpt")
# continue training
model.fit(x=train_images, y=train_labels, batch_size=256, validation_data = [test_images, test_labels],
epochs=5, callbacks=[cp_callback])
进一步阅读
本节提供了更多关于该主题的资源,如果你想深入了解。
文章
-
“欢迎使用 Colab”笔记本:
colab.research.google.com/
-
Jupyter Notebook 文档:
docs.jupyter.org/en/latest/
总结
在本教程中,你已经了解了 Google Colab 是什么,如何利用 Google Colab 的免费层获得免费的 GPU 访问,如何将 Google Colab 与 Google Drive 帐户配合使用,以及如何将模型保存到 Google Drive 以存储训练过程中的模型进度。
具体而言,你学到了:
-
什么是 Google Colab,以及如何开始使用它
-
如何使用
!
和 bash 命令探索你的 Google Colab 笔记本环境 -
Google Colab 附带的有用扩展
-
在训练过程中将模型进度保存到 Google Drive