如何散列 class 或函数定义?
How to hash a class or function definition?
背景
在试验机器学习时,我经常重用以前通过 pickling/unpickling 训练的模型。
然而,在处理特征提取部分时,不混淆不同的模型是一个挑战。
因此,我想添加一项检查,以确保模型是使用与测试数据完全相同的特征提取过程进行训练的。
问题
我的想法如下:
与模型一起,我将在泡菜转储中包含一个哈希值,该哈希值对特征提取过程进行指纹识别。
在训练模型或将其用于 prediction/testing 时,模型包装器被赋予符合特定协议的特征提取 class。
当然,在 class 上使用 hash()
是行不通的,因为它不是跨调用持久的。
所以我想我也许可以找到定义 class 的源文件,并从该文件中获取哈希值。
但是,可能有一种方法可以直接从 class 的内存内容中获取稳定的哈希值。
这会有两个好处:
如果找不到源文件,它也可以工作。
而且它可能会忽略对源文件的不相关更改(例如,修复模块文档字符串中的拼写错误)。
classes 有可以在这里使用的代码对象吗?
您要找的只是一个散列过程,其中包含 class 定义的所有重要细节。 (可以通过递归地包含 它们的 定义来包含基本 classes。)为了最大限度地减少错误匹配,基本思想是对 序列化 你的class。所以从 pickle
开始:它支持比 hash
更多的类型,并且当它使用身份时,它使用基于名称的 可重现的 身份。这使其成为递归策略基本情况的良好候选者:处理函数和 classes 其 contents 很重要,并让它处理任何引用的辅助对象。
所以按案例定义序列化。调用一个对象 special 如果它属于下面的任何情况,但最后一个。
- 对于被视为包含特殊对象的
tuple
:
- 人物
t
- 其
len
的序列化
- 每个元素的序列化,顺序
- 对于被视为包含特殊对象的
dict
:
- 字符
d
- 其
len
的序列化
- 每个名称和值的序列化,按排序顺序
- 对于定义显着的 class:
- 字符
C
- 其
__bases__
的序列化
- 其
vars
的序列化
- 对于定义显着的函数:
- 字符
f
- 其
__defaults__
的序列化
- 其
__kwdefaults__
的序列化(在 Python 3 中)
- 其
__closure__
的序列化(但使用单元格值而不是单元格本身)
- 其
vars
的序列化
- 其
__code__
的序列化
- 对于代码对象(因为
pickle
根本不支持它们):
- 字符
c
- 其
co_argcount
、co_nlocals
、co_flags
、co_code
、co_consts
、co_names
、co_freevars
的连载, 和 co_cellvars
, 按此顺序; none 其中非常特别
- 对于静态或 class 方法对象:
- 字符
s
或m
- 其
__func__
的序列化
- 对于 属性:
- 字符
p
- 其
fget
、fset
和 fdel
的序列化顺序
- 对于任何其他对象:
pickle.dumps(x,-1)
(你实际上从来没有 store 所有这些:只需在顶级函数中创建一个你选择的 hashlib
对象,并在递归部分 update
它与序列化的每一部分依次进行。)
类型标签是为了避免冲突,特别是没有前缀。二元泡菜已经没有前缀了。您可以根据对其内容的确定性分析(即使是启发式分析)或上下文来决定是否使用容器,只要您保持一致即可。
一如既往,平衡误报与漏报是一门艺术:对于一个函数,您可以包含 __globals__
(修剪已经序列化的对象以避免大量序列化,即使不是无限序列化)或只是其中找到的任何 __name__
。省略 co_varnames
会忽略重命名局部变量,这很好,除非自省很重要; co_filename
和 co_name
.
类似
您可能需要支持更多类型:寻找不正确 pickle
的静态属性和默认参数(因为它们 包含 对特殊类型的引用)或根本。当然,请注意某些类型(如文件对象)是不可修改的 因为 很难或不可能序列化它们(尽管与 pickle
不同,一旦你可以像处理任何其他函数一样处理 lambda已完成 code
个对象)。冒着错误匹配的风险,您可以选择仅序列化此类对象的 type(一如既往,以字符 ?
为前缀,以区别于实际具有的类型位置)。
背景
在试验机器学习时,我经常重用以前通过 pickling/unpickling 训练的模型。 然而,在处理特征提取部分时,不混淆不同的模型是一个挑战。 因此,我想添加一项检查,以确保模型是使用与测试数据完全相同的特征提取过程进行训练的。
问题
我的想法如下: 与模型一起,我将在泡菜转储中包含一个哈希值,该哈希值对特征提取过程进行指纹识别。
在训练模型或将其用于 prediction/testing 时,模型包装器被赋予符合特定协议的特征提取 class。
当然,在 class 上使用 hash()
是行不通的,因为它不是跨调用持久的。
所以我想我也许可以找到定义 class 的源文件,并从该文件中获取哈希值。
但是,可能有一种方法可以直接从 class 的内存内容中获取稳定的哈希值。 这会有两个好处: 如果找不到源文件,它也可以工作。 而且它可能会忽略对源文件的不相关更改(例如,修复模块文档字符串中的拼写错误)。 classes 有可以在这里使用的代码对象吗?
您要找的只是一个散列过程,其中包含 class 定义的所有重要细节。 (可以通过递归地包含 它们的 定义来包含基本 classes。)为了最大限度地减少错误匹配,基本思想是对 序列化 你的class。所以从 pickle
开始:它支持比 hash
更多的类型,并且当它使用身份时,它使用基于名称的 可重现的 身份。这使其成为递归策略基本情况的良好候选者:处理函数和 classes 其 contents 很重要,并让它处理任何引用的辅助对象。
所以按案例定义序列化。调用一个对象 special 如果它属于下面的任何情况,但最后一个。
- 对于被视为包含特殊对象的
tuple
:- 人物
t
- 其
len
的序列化
- 每个元素的序列化,顺序
- 人物
- 对于被视为包含特殊对象的
dict
:- 字符
d
- 其
len
的序列化
- 每个名称和值的序列化,按排序顺序
- 字符
- 对于定义显着的 class:
- 字符
C
- 其
__bases__
的序列化
- 其
vars
的序列化
- 字符
- 对于定义显着的函数:
- 字符
f
- 其
__defaults__
的序列化
- 其
__kwdefaults__
的序列化(在 Python 3 中) - 其
__closure__
的序列化(但使用单元格值而不是单元格本身) - 其
vars
的序列化
- 其
__code__
的序列化
- 字符
- 对于代码对象(因为
pickle
根本不支持它们):- 字符
c
- 其
co_argcount
、co_nlocals
、co_flags
、co_code
、co_consts
、co_names
、co_freevars
的连载, 和co_cellvars
, 按此顺序; none 其中非常特别
- 字符
- 对于静态或 class 方法对象:
- 字符
s
或m
- 其
__func__
的序列化
- 字符
- 对于 属性:
- 字符
p
- 其
fget
、fset
和fdel
的序列化顺序
- 字符
- 对于任何其他对象:
pickle.dumps(x,-1)
(你实际上从来没有 store 所有这些:只需在顶级函数中创建一个你选择的 hashlib
对象,并在递归部分 update
它与序列化的每一部分依次进行。)
类型标签是为了避免冲突,特别是没有前缀。二元泡菜已经没有前缀了。您可以根据对其内容的确定性分析(即使是启发式分析)或上下文来决定是否使用容器,只要您保持一致即可。
一如既往,平衡误报与漏报是一门艺术:对于一个函数,您可以包含 __globals__
(修剪已经序列化的对象以避免大量序列化,即使不是无限序列化)或只是其中找到的任何 __name__
。省略 co_varnames
会忽略重命名局部变量,这很好,除非自省很重要; co_filename
和 co_name
.
您可能需要支持更多类型:寻找不正确 pickle
的静态属性和默认参数(因为它们 包含 对特殊类型的引用)或根本。当然,请注意某些类型(如文件对象)是不可修改的 因为 很难或不可能序列化它们(尽管与 pickle
不同,一旦你可以像处理任何其他函数一样处理 lambda已完成 code
个对象)。冒着错误匹配的风险,您可以选择仅序列化此类对象的 type(一如既往,以字符 ?
为前缀,以区别于实际具有的类型位置)。