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