项目文件夹

文件
wehub-resource-sync 593b94c120
pytest / Unit Tests (push) Has been cancelled
pytest / Integration (integration_tests_a) (push) Has been cancelled
pytest / Integration (integration_tests_b) (push) Has been cancelled
pytest / Integration (integration_tests_c) (push) Has been cancelled
pytest / Integration (integration_tests_d) (push) Has been cancelled
pytest / Integration (integration_tests_e) (push) Has been cancelled
pytest / Integration (integration_tests_f) (push) Has been cancelled
pytest / Integration (integration_tests_g) (push) Has been cancelled
pytest / Integration (integration_tests_h) (push) Has been cancelled
pytest / Integration (integration_tests_i) (push) Has been cancelled
pytest / Integration (integration_tests_j) (push) Has been cancelled
pytest / Distributed (distributed_a) (push) Has been cancelled
pytest / Distributed (distributed_b) (push) Has been cancelled
pytest / Distributed (distributed_c) (push) Has been cancelled
pytest / Distributed (distributed_d) (push) Has been cancelled
pytest / Distributed (distributed_e) (push) Has been cancelled
pytest / Distributed (distributed_f) (push) Has been cancelled
pytest / Minimal Install (push) Has been cancelled
pytest / Event File (push) Has been cancelled
pytest (slow) / py-slow (push) Has been cancelled
Publish JSON Schema / publish-schema (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:49:20 +08:00

112 行
5.2 KiB
Python

from copy import deepcopy
import pandas as pd
import torch
from ludwig.api import LudwigModel
from ludwig.constants import COLUMN, INPUT_FEATURES, PREPROCESSING, SPLIT
from ludwig.data.split import get_splitter
from ludwig.features.base_feature import BaseFeature
def filter_cols(df, cols):
cols = {c.lower() for c in cols}
retain_cols = [c for c in df.columns if c.lower() in cols]
return df[retain_cols]
def prepare_data(model: LudwigModel, inputs_df: pd.DataFrame, sample_df: pd.DataFrame, target: str):
config = model.config
feature_cols = [feature[COLUMN] for feature in config[INPUT_FEATURES]]
if SPLIT in config.get(PREPROCESSING, {}):
# Keep columns required for Ludwig preprocessing
splitter = get_splitter(**config[PREPROCESSING][SPLIT])
feature_cols += splitter.required_columns
target_feature_name = get_feature_name(model, target)
inputs_df = filter_cols(inputs_df, feature_cols)
if sample_df is not None:
sample_df = filter_cols(sample_df, feature_cols)
return inputs_df, sample_df, feature_cols, target_feature_name
def get_pred_col(preds, target):
t = target.lower()
for c in preds:
if c.lower() == t:
if "probabilities" in preds[c]:
return preds[c]["probabilities"]
else:
return preds[c]["predictions"]
raise ValueError(f"Unable to find target column {t} in {preds.keys()}")
def get_feature_name(model: LudwigModel, target: str) -> str:
t = target.lower()
for c in model.training_set_metadata:
if c.lower() == t:
return c
raise ValueError(f"Unable to find target column {t} in {model.training_set_metadata.keys()}")
def get_absolute_module_key_from_submodule(module: torch.nn.Module, submodule: torch.nn.Module):
"""Get the absolute module key for each param in the target layer.
Assumes that the keys in the submodule are relative to the module.
We find the params from the submodule in the module by comparing the data
pointers, since the data returned by named_parameters is by reference.
More information on checking if tensors point to the same place in storage can be found here:
https://discuss.pytorch.org/t/any-way-to-check-if-two-tensors-have-the-same-base/44310/2
"""
absolute_keys = []
for module_key, module_param in module.named_parameters():
for _, submodule_param in submodule.named_parameters():
if submodule_param.data_ptr() == module_param.data_ptr():
absolute_keys.append(module_key)
break
return absolute_keys
def replace_layer_with_copy(feat: BaseFeature, target_layer: torch.nn.Module):
"""Replaces a layer in a feature with a copy of the layer in-place.
This is useful in a tied weights scenario, where a single encoder may be used by multiple features. If we leave
as-is, Captum complains about the resulting computation graph. The solution is to create an identical
(deep) copy of the layer fed into Captum: https://github.com/pytorch/captum/issues/794#issuecomment-1093021638
This is safe to do during the explain step because we are essentially running inference, and no model artifacts are
being saved during the explain step.
TODO(geoffrey): if a user ever wants to train immediately after explain (i.e. w/o loading weights from the disk),
we might want to implement this as a context so that we can restore the original encoder object at the end.
Will defer this implementation for now because that scenario seems unlikely.
At a high-level the approach is the following:
1. Create a deep-copy of the entire encoder object and set it as the feature's encoder object
2. Replace the tensors in the copied encoder object with the tensors from the original encoder object, except for
the tensors in the target layer. We want to explain these tensors, so we want to keep them as deep copies.
This approach ensures that at most 2 copies of the encoder object are in memory at any given time.
"""
with torch.no_grad():
# Get the original encoder object and a mapping from param names to the params themselves.
orig_encoder_obj = feat.encoder_obj
orig_encoder_obj_state_dict = orig_encoder_obj.state_dict()
# Deep copy the original encoder object and set the copy as this feature's encoder object.
copy_encoder_obj = deepcopy(orig_encoder_obj)
feat.encoder_obj = copy_encoder_obj
# We have to get the absolute module key in order to do string matching because the target_layer keys are
# relative to itself. If we were to leave it as-is and attempt to suffix match, we may get duplicates for
# common layers i.e. "LayerNorm.weight" and "LayerNorm.bias". Getting the absolute module key ensures we
# use values like "transformer.module.embedding.LayerNorm.weight" instead.
keys_to_keep_copy = get_absolute_module_key_from_submodule(orig_encoder_obj, target_layer)
# Get the tensors to keep from the copied encoder object. These are the tensors in the target layer.
for key, param in copy_encoder_obj.named_parameters():
if key not in keys_to_keep_copy:
param.data = orig_encoder_obj_state_dict[key].data