使用JSON反序列化sklearn模型时,无法为只读属性赋值的问题
解决DecisionTreeRegressor JSON序列化/反序列化的只读属性问题
你遇到的这个问题很典型——pickle和常规属性赋值的逻辑完全不同,这也是它能绕过只读属性限制的核心原因。下面我分两部分给你解释和解决:
Pickle是怎么实现的?
Pickle的工作机制和你用setattr赋值完全不一样:它不会走常规的属性设置流程,而是直接操作对象的底层内存结构和内部字典。具体来说:
- 当pickle加载对象时,它会先创建一个未初始化的空对象(不会调用类的
__init__方法) - 然后直接把之前保存的属性数据写入对象的
__dict__(实例属性字典)或者对应的内存区域 - 这种方式完全绕过了属性的访问控制(比如只读属性的setter限制),所以像
feature_importances_这类属性能被直接恢复
而你用setattr的时候,是在调用属性的设置逻辑,一旦属性被定义为只读(比如通过@property装饰器且没有setter),就会抛出AttributeError。
如何用JSON实现相同效果?
要解决这个问题,我们需要模仿pickle的思路,绕过setattr的限制,直接操作对象的内部字典。针对你的代码,这里有几个具体的修改方案:
1. 修改_set_params方法,直接操作__dict__
把原来的setattr替换为“先尝试常规赋值,失败则直接写入__dict__”的逻辑,这样就能处理只读属性:
def _set_params(self, obj, **param_dict): for k, v in param_dict.items(): print(k, v) # 转换numpy数组 value = np.array(v) if isinstance(v, list) else v try: # 先尝试常规赋值 setattr(obj, k, value) except AttributeError: # 只读属性直接写入__dict__ obj.__dict__[k] = value
2. 确保关键属性被正确保存
检查你在save_json里的排除列表,比如list_main_exclude里不要包含feature_importances_,确保这个属性被加入到保存的attrs_main字典中。如果之前不小心排除了,把它从排除列表里移除。
3. 处理Tree对象的特殊属性
对于tree_对象的属性,同样可以用上述逻辑。另外,创建Tree实例时,要确保初始化参数和原模型完全一致,比如n_features、n_classes、n_outputs的类型和值都要匹配。
额外提醒
- 有些sklearn模型的属性可能存在于
__slots__中(而不是__dict__),这种情况可以尝试用object.__setattr__(obj, k, value)来强制赋值,这是最底层的属性设置方法,能绕过几乎所有限制 - 测试时一定要对比加载后的模型和原模型的预测结果、
feature_importances_、tree_的关键属性,确保两者完全一致
内容的提问来源于stack exchange,提问作者vlad
相关产品推荐
相关产品推荐

