Python 是一种解释型语言。这意味着有一个解释器来运行我们的程序,而不是编译代码并本地运行。在 Python 中,REPL(读取-评估-打印循环)可以逐行运行命令。结合 Python 提供的一些检查工具,有助于开发代码。
接下来,您将看到如何利用 Python 解释器来检查对象和开发程序。
完成本教程后,你将学到:
- 如何在 Python 解释器中工作
- 如何在 Python 中使用检查函数
- 如何在检查函数的帮助下逐步开发解决方案
通过我的新书 Python for Machine Learning 启动您的项目,其中包括分步教程以及所有示例的Python 源代码文件。
让我们开始吧!

使用检查工具开发 Python 程序。
图片由 Tekton 拍摄。部分权利保留。
教程概述
本教程分为四个部分;它们是:
- PyTorch 和 TensorFlow
- 寻找线索
- 从权重中学习
- 制作一个复制器
PyTorch 和 TensorFlow
PyTorch 和 TensorFlow 是 Python 中两个最大的神经网络库。它们的代码不同,但它们能做的事情是相似的。
考虑经典的 MNIST 手写数字识别问题,您可以构建一个 LeNet-5 模型来分类数字,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import numpy as np import torch import torch.nn as nn import torch.optim as optim import torchvision # 加载 MNIST 训练数据 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 模型 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) ) # 训练循环 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() # 运行训练 optimizer = optim.Adam(torch_model.parameters()) loss_fn = nn.CrossEntropyLoss() training_loop(torch_model, optimizer, loss_fn, train_loader, n_epochs=20) # 保存模型 torch.save(torch_model, "lenet5.pt") |
这是一个不需要任何验证或测试的简化代码。 TensorFlow 中的对应代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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 模型 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") ]) # 将数据重塑为 (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') # 训练 keras_model.compile(loss="sparse_categorical_crossentropy", optimizer="adam", metrics=["accuracy"]) keras_model.fit(X_train, y_train, epochs=20, batch_size=32) # 保存 keras_model.save("lenet5.h5") |
运行此程序将从 PyTorch 代码中获得文件 lenet5.pt
,从 TensorFlow 代码中获得文件 lenet5.h5
。
寻找线索
如果您理解上述神经网络的作用,您应该可以指出,除了每个层中的许多乘法和加法计算之外,别无他物。在数学上,输入和每个全连接层的核之间有一个矩阵乘法,然后在结果中加上偏置。在卷积层中,将核与输入矩阵的一部分进行逐元素乘法,然后将结果相加,并将偏置添加为一个特征映射的输出元素。
想开始学习机器学习 Python 吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
在使用两种不同的框架开发相同的 LeNet-5 模型时,如果它们的权重相同,就应该能够使它们工作得完全一样。鉴于它们的架构相同,如何将一个模型的权重复制到另一个模型?
您可以按如下方式加载已保存的模型:
1 2 3 4 |
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)。
1 2 3 4 5 6 7 |
Python 3.9.13 (main, May 19 2022, 13:48:47) [Clang 13.1.6 (clang-1316.0.21.2)] on darwin 键入“help”、“copyright”、“credits”或“license”以获取更多信息。 >>> import torch >>> import tensorflow as tf >>> torch_model = torch.load("lenet5.pt") >>> keras_model = tf.keras.models.load_model("lenet5.h5") |
以上不会打印任何内容。但是您可以使用内置的 type()
命令检查加载的两个模型。
1 2 3 4 |
>>> type(torch_model) <class 'torch.nn.modules.container.Sequential'> >>> type(keras_model) <class 'keras.engine.sequential.Sequential'> |
因此,您在这里知道它们分别是 PyTorch 和 Keras 的神经网络模型。由于它们是经过训练的模型,权重必须存储在其中。那么,如何才能在这些模型中找到权重呢?由于它们是对象,最简单的方法是使用内置函数 dir()
来检查它们的成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
>>> 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()
函数。
1 2 3 4 5 6 7 |
>>> 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”的成员。为了精简它们的名称,您可以在解释器中执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 |
>>> [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'] |
这可能需要一些试验和错误。但这并不太难,您可能会发现可以使用 PyTorch 模型中的 state_dict
查看权重。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
>>> 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()
找到权重。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
>>> 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
属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> 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
类型。
您可以通过检查每个张量或数组的形状来了解更多信息。
1 2 3 4 5 6 7 8 9 |
>>> [(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 模型中没有看到层名称,但实际上,您可以使用类似的推理来查找层并获取它们的名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> 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 张量并检查它来操作它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
>>> 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 数组。您对此可以相当确定,因为您看到数字匹配并且形状也匹配。事实上,通过查看文档,您可以更加确定。
1 |
>>> help(torch_states["0.weight"].numpy) |
help()
函数将显示函数的文档字符串,这通常是它的文档。
由于这是第一个卷积层的核,通过比较该核的形状与 Keras 模型的形状,您可能会注意到它们的形状不同。
1 2 3 |
>>> keras_weights = keras_model.get_weights() >>> keras_weights[0].shape (5, 5, 1, 6) |
请注意,第一层的输入是一个 28×28×1 的图像数组,而输出是 6 个特征图。将核形状中的 1 和 6 分别对应输入和输出的通道数是很自然的。同样,根据我们对卷积层机制的理解,核应该是 5×5 的矩阵。
此时,您可能已经猜到,在 PyTorch 卷积层中,核表示为(输出 × 输入 × 高度 × 宽度),而在 Keras 中,它表示为(高度 × 宽度 × 输入 × 输出)。
同样,您还可以看到在全连接层中,PyTorch 将核表示为(输出 × 输入),而 Keras 是(输入 × 输出)。
1 2 3 4 |
>>> keras_weights[6].shape (120, 84) >>> list(torch_states.values())[6].shape torch.Size([84, 120]) |
将权重和张量进行匹配并将它们的形状并排放置应该能使这些更加清晰。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> 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 张量的名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> 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()
函数来进一步确认。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
>>> 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()
退出)。
通过研究如何重塑权重以及如何从一种数据类型转换到另一种数据类型,你会得到以下程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import torch import tensorflow as tf # 加载模型 torch_model = torch.load("lenet5.pt") keras_model = tf.keras.models.load_model("lenet5.h5") # 从 Keras 模型中提取权重 keras_weights = keras_model.get_weights() # 将形状从 Keras 转换为 PyTorch for idx in [0, 2, 4]: # conv 层: (out, in, height, width) keras_weights[idx] = keras_weights[idx].transpose([3, 2, 0, 1]) for idx in [6, 8]: # dense 层: (out, in) keras_weights[idx] = keras_weights[idx].transpose() # 设置权重 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) # 保存新模型 torch.save(torch_model, "lenet5-keras.pt") |
反之,将 PyTorch 模型中的权重复制到 Keras 模型中,操作也类似,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import torch import tensorflow as tf # 加载模型 torch_model = torch.load("lenet5.pt") keras_model = tf.keras.models.load_model("lenet5.h5") # 从 PyTorch 模型中提取权重 torch_states = torch_model.state_dict() weights = list(torch_states.values()) # 将张量转换为 numpy 数组 weights = [w.numpy() for w in weights] # 将形状从 PyTorch 转换为 Keras for idx in [0, 2, 4]: # conv 层: (height, width, in, out) weights[idx] = weights[idx].transpose([2, 3, 1, 0]) for idx in [6, 8]: # dense 层: (in, out) weights[idx] = weights[idx].transpose() # 设置权重 keras_model.set_weights(weights) # 保存新模型 keras_model.save("lenet5-torch.h5") |
然后,你可以通过将随机数组作为输入来验证它们是否工作相同,此时你预期输出结果会完全一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import numpy as np import torch import tensorflow as tf # 加载模型 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") # 创建一个随机输入 sample = np.random.random((28,28)) # 将样本转换为 torch 输入形状 torch_sample = torch.Tensor(sample.reshape(1,1,28,28)) # 将样本转换为 keras 输入形状 keras_sample = sample.reshape(1,28,28,1) # 检查输出 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) |
在我们的例子中,输出是
1 2 3 4 5 6 7 8 9 |
[[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 标准库中的 inspect 模块
- 内建函数 dir
- PyTorch 中的
state_dict
是什么 - TensorFlow 的
get_weights
方法
总结
在本教程中,你学习了如何在 Python REPL 下进行操作,并使用内省函数来开发解决方案。具体来说,
- 你学习了如何在 REPL 中使用内省函数来了解对象的内部成员。
- 你学习了如何使用 REPL 来试验 Python 代码。
- 因此,你开发了一个在 PyTorch 和 Keras 模型之间进行转换的程序。
暂无评论。