Describe the bug
Documented behaviour that does not match the code. Each was checked against the installed package.
deeptab.NotFittedError is exported and documented but no reachable code path ever raises it
Where: deeptab/core/exceptions.py (79-81 and 218-223; exported at deeptab/init.py:9,21)
NotFittedError is part of deeptab.__all__ and its docstring says "A method was called before fit() completed." Every user-facing entry point instead goes through _PredictMixin._validate_predict_input (deeptab/models/_mixins/predict.py:88), which calls sklearn's check_is_fitted and raises sklearn.exceptions.NotFittedError (a ValueError/AttributeError subclass, unrelated to DeepTabError). Every raise not_fitted_error(...) in classifier_base.py (348/354/362/409/413/421), regressor_base.py:243 and lss_base.py:357 sits after that call and is therefore dead. Code written against the public exception hierarchy never catches anything.
Observed: MLPClassifier -> sklearn.exceptions.NotFittedError | caught by deeptab.NotFittedError? False
MLPRegressor -> sklearn.exceptions.NotFittedError | caught by deeptab.NotFittedError? False
MLPLSS -> sklearn.exceptions.NotFittedError | caught by deeptab.NotFittedError? False
(same for predict_proba / evaluate / score)
deeptab.NotFittedError mro: NotFittedError -> ModelError -> DeepTabError -> Exception
sklearn NotFittedError mro: NotFittedError -> ValueError -> AttributeError -> Exception
Expected: Either _validate_predict_input should raise deeptab.core.exceptions.NotFittedError (or DeepTab's class should subclass sklearn's so both catch), or the exception should not be advertised in deeptab.__all__ and the exception-hierarchy docstring. As it stands the public error type is unraisable and except deeptab.NotFittedError is a silent no-op.
Repro
import warnings; warnings.simplefilter("ignore")
import numpy as np, pandas as pd
from deeptab import NotFittedError as DeepTabNotFitted
from deeptab.models import MLPClassifier, MLPRegressor, MLPLSS
rng = np.random.default_rng(0)
X = pd.DataFrame(rng.normal(size=(20, 3)), columns=list("abc"))
y = rng.integers(0, 2, 20)
for cls in (MLPClassifier, MLPRegressor, MLPLSS):
try:
cls().predict(X)
except Exception as e:
print(cls.__name__, "->", type(e).__module__ + "." + type(e).__name__,
"| caught by deeptab.NotFittedError?", isinstance(e, DeepTabNotFitted))
# the documented pattern simply does not work:
try:
MLPClassifier().predict(X)
except DeepTabNotFitted:
print("handled") # never reached — the exception escapes
get_hardware_info is documented as from deeptab import get_hardware_info but is not exported
Where: deeptab/__init__.py (11, 15-32)
deeptab/__init__.py imports only print_hardware_info, so the import shown in get_hardware_info's own (unskipped) docstring example raises ImportError; the function is reachable only via deeptab.core.hardware.
Observed: ImportError: cannot import name 'get_hardware_info' from 'deeptab'. The docstring at deeptab/core/hardware.py:107-112 is an unskipped doctest (>>> from deeptab import get_hardware_info / >>> info = get_hardware_info() / >>> sorted(info) / ['cpu', 'cuda', 'mps', 'platform', 'recommended_accelerator']), and deeptab/core/hardware.py:10 lists the name in __all__. The sibling print_hardware_info is exported, so the omission looks accidental rather than deliberate.
Expected: Add get_hardware_info to the from .core.hardware import ... line and to deeptab.__all__, so the documented example works and the machine-readable counterpart of print_hardware_info (the only way to programmatically read recommended_accelerator) is reachable from the public namespace.
Repro
from deeptab import get_hardware_info
FAQ tells users to subclass BaseTaskModel, a class that does not exist anywhere in the package
Where: docs/getting_started/faq.md (512-514)
"### Can I use my own custom architecture? — Yes, but it requires subclassing BaseTaskModel. See the source code for examples of how to extend the base classes." There is no BaseTaskModel in DeepTab; a repo-wide grep finds the identifier only in this one FAQ line. The real extension point is BaseModel + SklearnBase*, as docs/core_concepts/custom_models.md correctly documents, so the FAQ actively sends readers looking for a nonexistent symbol.
Observed: deeptab False
deeptab.core False
deeptab.training False
deeptab.models False
(and from deeptab.core import BaseTaskModel -> ImportError)
Expected: The FAQ answer should point at BaseModel / SklearnBaseClassifier / SklearnBaseRegressor / SklearnBaseLSS and link to core_concepts/custom_models.md, whose recipe I ran verbatim and confirmed works.
Repro
# repo-wide: the name exists only in the FAQ
# $ grep -rn "BaseTaskModel" . --exclude-dir=.git
# docs/getting_started/faq.md:514: ... subclassing `BaseTaskModel` ...
import deeptab, deeptab.core, deeptab.training, deeptab.models
for mod in (deeptab, deeptab.core, deeptab.training, deeptab.models):
print(mod.__name__, hasattr(mod, "BaseTaskModel"))
fit()/build_model() numpydoc documents a nonexistent 'factor' parameter and four wrong defaults on all three estimator bases
Where: deeptab/models/classifier_base.py (229, 237, 243, 245, 247, 249 (fit); mirrored in deeptab/models/regressor_base.py:73,175 and deeptab/models/lss_base.py:235)
The public fit docstrings of SklearnBaseClassifier, SklearnBaseRegressor and SklearnBaseLSS (and both regressor build_model docstrings) document a parameter factor : float, default=0.1 — the real name is lr_factor. Unknown fit kwargs are swept into **trainer_kwargs and forwarded to pl.Trainer, so fit(..., factor=0.5) fails there rather than being ignored. The same docstrings also state defaults that contradict the signature: batch_size default=64 (real 128), patience default=10 (real 15), lr default=1e-3 (real None → 1e-4), weight_decay default=0.025 (real None → 1e-6). A 10x learning rate and a 25000x weight decay are material misstatements for anyone reading the API reference rather than the source.
Observed: 'factor' in signature: False
real batch_size default = 128
real patience default = 15
real lr default = None
real weight_decay default = None
docstring says: ['batch_size : int, default=64', 'patience : int, default=10', 'factor : float, default=0.1',
'lr : float, default=1e-3', 'weight_decay : float, default=0.025', ...]
TypeError: Trainer.init() got an unexpected keyword argument 'factor'
Expected: Rename the documented factor entry to lr_factor and correct the four defaults to match the signature (128 / 15 / None→TrainerConfig.lr=1e-4 / None→TrainerConfig.weight_decay=1e-6). These docstrings are what docs/api/models/*.rst renders as the public reference.
Repro
import inspect, warnings; warnings.simplefilter("ignore")
import numpy as np, pandas as pd
from deeptab.models import MLPClassifier
from deeptab.models.classifier_base import SklearnBaseClassifier
sig = inspect.signature(SklearnBaseClassifier.fit).parameters
print("'factor' in signature:", "factor" in sig)
for p in ("batch_size", "patience", "lr", "weight_decay"):
print(f" real {p} default = {sig[p].default}")
print([l.strip() for l in SklearnBaseClassifier.fit.__doc__.splitlines()
if l.strip().startswith(("factor ", "batch_size ", "patience ", "lr ", "weight_decay "))])
rng = np.random.default_rng(0)
X = pd.DataFrame(rng.normal(size=(40, 3)), columns=list("abc"))
MLPClassifier().fit(X, rng.integers(0, 2, 40), factor=0.5, accelerator="cpu")
MLPConfig documents a 'skip_layers' constructor parameter that does not exist
Where: deeptab/configs/models/mlp_config.py (24-25 (docstring) vs 34-38 (fields))
The MLPConfig numpydoc lists skip_layers : bool, default=False — Whether to include skip layers. alongside the real skip_connections field. skip_layers is not a dataclass field (the fields are layer_sizes, dropout, use_glu, skip_connections plus BaseModelConfig's), so a user copying it from the API reference gets a TypeError. This is the only ghost field across all 21 config classes (I diffed every one).
Observed: ['use_embeddings', ..., 'cat_encoding', 'layer_sizes', 'dropout', 'use_glu', 'skip_connections']
TypeError: MLPConfig.init() got an unexpected keyword argument 'skip_layers'
Expected: The docstring should drop skip_layers (or the field should exist). The Parameters block of a dataclass config is the documented constructor signature, and this entry is the only one in the package that cannot be passed.
Repro
from deeptab.configs import MLPConfig
import dataclasses
print([f.name for f in dataclasses.fields(MLPConfig)])
MLPConfig(skip_layers=True)
InferenceModel.predict() on an LSS model returns the parameter matrix, not the documented distribution mean/mode, and predict_params(raw=False) is documented backwards
Where: deeptab/core/inference.py (344-373 (docstring at 347-350) and 407-439; doc mirror at docs/core_concepts/inference.md:257-266)
InferenceModel.predict's docstring says "For distributional regression (LSS) returns the distribution mean / mode as a float array" and the guide labels it "Distribution mean / mode (default)". In fact predict calls self._estimator.predict(X), which for SklearnBaseLSS returns the transformed distribution parameters of shape (n_samples, n_params) — byte-for-byte identical to predict_params(X, raw=False). Separately, inference.md:263 annotates model.predict_params(X_new, raw=False) as "Raw distribution parameters (before inverse-link transform)", which is exactly inverted: raw=False applies the family transform, raw=True is the pre-transform output.
Observed: predict shape: (60, 2) [-0.00286126 0.76246905]
predict == predict_params(raw=False)? True
raw=True (pre-transform): [-0.00286126 0.13414809]
So predict returns an (n, 2) [loc, scale] matrix, not an (n,) mean array, and raw=False is the transformed output, not the raw one.
Expected: Either InferenceModel.predict should reduce LSS output to the distribution mean/mode (making it genuinely different from predict_params), or both the docstring at inference.py:347-350 and inference.md:257-266 should say it returns the parameter matrix — and the raw=False comment at inference.md:263 must be corrected. Serving code following the docstring (float(preds[i]) for a point forecast) gets a length-2 array instead.
Repro
import warnings; warnings.simplefilter("ignore")
import numpy as np, pandas as pd
from deeptab.models import MLPLSS
from deeptab.configs import TrainerConfig
from deeptab import InferenceModel
rng = np.random.default_rng(0)
X = pd.DataFrame(rng.normal(size=(60, 5)), columns=[f"f{i}" for i in range(5)])
y = X.values @ rng.normal(size=5)
m = MLPLSS(trainer_config=TrainerConfig(max_epochs=1, batch_size=16,
patience=1, lr_patience=1))
m.fit(X, y, family="normal", accelerator="cpu",
enable_progress_bar=False, enable_model_summary=False)
m.save("lss.deeptab")
im = InferenceModel.from_path("lss.deeptab")
p = np.asarray(im.predict(X))
params = im.predict_params(X, raw=False)
raw = im.predict_params(X, raw=True)
print("predict shape:", p.shape, p[0])
print("predict == predict_params(raw=False)?", np.allclose(p, params))
print("raw=True (pre-transform):", raw[0])
InferenceModel.feature_names returns positional integer labels instead of None for array-fitted artifacts, making the documented count-only validation path unreachable
Where: deeptab/core/inference.py (221-229 (feature_names) and 302-338 (validate_input); doc claim at docs/core_concepts/inference.md:196-207)
docs/core_concepts/inference.md ("No column names in the artifact") states that for a model fitted on a NumPy array model.feature_names is None and only a feature-count check runs, producing ValueError: Expected 10 feature(s) (no column names available for detailed validation), got 7.. In reality ensure_dataframe gives the array integer column labels 0..k-1, set_input_feature_attributes stores them in input_columns_, and feature_names returns [0, 1, 2, 3]. The expected_names is None branch at inference.py:306-313 is therefore dead for such artifacts, and a serving payload built as pd.DataFrame([payload]) with real key names is rejected with a misleading "missing column(s) [0, 1, 2, 3]" message.
Observed: feature_names (docs say None): [0, 1, 2, 3]
named df -> ValueError | Input is missing 4 column(s) that were present during training: [0, 1, 2, 3].
wrong count -> ValueError | Input has 3 unexpected column(s) not seen during training: [4, 5, 6]. To drop them automatically, pass allow_extra_columns=True.
The documented "Expected 4 feature(s) (no column names available for detailed validation), got 7." message never appears. The bare estimator is looser but then leaks a raw pandas error: m.predict(named_df) dies inside pretab with KeyError: "None of [Index([0], dtype='int64')] are in the [columns]" (via deeptab/data/datamodule.py:306), because validate_input_features skips the name check when feature_names_in_ was deleted for non-string columns.
Expected: feature_names should return None when input_columns_ are auto-generated positional integers (i.e. not all strings), matching set_input_feature_attributes' own all(isinstance(c, str)) test at deeptab/core/sklearn_compat.py:98 and the documented behaviour — so array-trained artifacts fall into the count-only branch and accept a same-width DataFrame instead of demanding columns literally named 0..k-1.
Repro
import warnings; warnings.simplefilter("ignore")
import numpy as np, pandas as pd
from deeptab.models import MLPClassifier
from deeptab.configs import TrainerConfig
from deeptab import InferenceModel
rng = np.random.default_rng(0)
X = rng.normal(size=(48, 4)); y = rng.integers(0, 2, 48) # fitted on a NumPy array
m = MLPClassifier(trainer_config=TrainerConfig(max_epochs=1, batch_size=16,
patience=1, lr_patience=1))
m.fit(X, y, accelerator="cpu", enable_progress_bar=False, enable_model_summary=False)
im = InferenceModel.from_estimator(m)
print("feature_names (docs say None):", im.feature_names)
for bad, label in [(pd.DataFrame(X, columns=["age", "income", "score", "tenure"]), "named df"),
(rng.normal(size=(5, 7)), "wrong count")]:
try:
im.validate_input(bad)
except Exception as e:
print(label, "->", type(e).__name__, "|", e)
InferenceModel cannot pass external embeddings, so deployment inference on an embedding-trained model dies with a raw torch shape error
Where: deeptab/core/inference.py (324-352)
InferenceModel.predict/predict_proba call self._estimator.predict(X_validated) with no embeddings argument, but estimators fitted with fit(..., embeddings=...) require them at inference. The documented deployment wrapper is therefore unusable for those models and fails inside torch instead of reporting the missing input.
Observed: RuntimeError: linear(): input and weight.T shapes cannot be multiplied (8x128 and 160x8). validate_input() passes cleanly (the embedding columns are not part of the tabular schema), and L2.predict(X.iloc[:8], embeddings=emb[:8]) works and matches the pre-save predictions exactly — so only the InferenceModel path is broken.
Expected: InferenceModel should accept and forward embeddings (the artifact records embedding_feature_info), or raise a clear error such as "this model was trained with external embeddings; pass embeddings=..." instead of a torch shape mismatch.
Repro
import warnings; warnings.simplefilter('ignore')
import numpy as np, pandas as pd
from deeptab.models.mlp import MLPClassifier
from deeptab.configs import MLPConfig, TrainerConfig
from deeptab import InferenceModel
rng = np.random.default_rng(0)
X = pd.DataFrame({'a': rng.normal(size=64), 'b': rng.normal(size=64)})
y = (X['a']>0).astype(int).values
emb = rng.normal(size=(64,6)).astype('float32')
m = MLPClassifier(model_config=MLPConfig(layer_sizes=[8], use_embeddings=True),
trainer_config=TrainerConfig(max_epochs=1, batch_size=16, patience=3), random_state=0)
m.fit(X, y, embeddings=emb, accelerator='cpu', devices=1)
m.save('emb.deeptab')
InferenceModel.from_path('emb.deeptab').predict(X.iloc[:8])
Expected behavior
Documented names should exist and be exported, documented parameters should exist with the documented
defaults, and documented return values should match what is returned.
Screenshots
n/a
Desktop (please complete the following information):
- OS: macOS (Darwin 25.5.0, arm64)
- Python version: 3.11.15
- deeptab Version: 2.0.0 (main @ 4e6a359)
Additional context
torch 2.9.1, lightning 2.6.5, scikit-learn 1.9.0, numpy 2.4.6. Found in a second-pass review of v2.0.0
(seven independent lenses, each finding adversarially re-verified by a second reviewer, then re-run by
hand). Distinct from the already-filed #409-#426.
Describe the bug
Documented behaviour that does not match the code. Each was checked against the installed package.
deeptab.NotFittedError is exported and documented but no reachable code path ever raises it
Where:
deeptab/core/exceptions.py(79-81 and 218-223; exported at deeptab/init.py:9,21)NotFittedErroris part ofdeeptab.__all__and its docstring says "A method was called before fit() completed." Every user-facing entry point instead goes through_PredictMixin._validate_predict_input(deeptab/models/_mixins/predict.py:88), which calls sklearn'scheck_is_fittedand raisessklearn.exceptions.NotFittedError(aValueError/AttributeErrorsubclass, unrelated toDeepTabError). Everyraise not_fitted_error(...)in classifier_base.py (348/354/362/409/413/421), regressor_base.py:243 and lss_base.py:357 sits after that call and is therefore dead. Code written against the public exception hierarchy never catches anything.Observed: MLPClassifier -> sklearn.exceptions.NotFittedError | caught by deeptab.NotFittedError? False
MLPRegressor -> sklearn.exceptions.NotFittedError | caught by deeptab.NotFittedError? False
MLPLSS -> sklearn.exceptions.NotFittedError | caught by deeptab.NotFittedError? False
(same for predict_proba / evaluate / score)
deeptab.NotFittedError mro: NotFittedError -> ModelError -> DeepTabError -> Exception
sklearn NotFittedError mro: NotFittedError -> ValueError -> AttributeError -> Exception
Expected: Either
_validate_predict_inputshould raisedeeptab.core.exceptions.NotFittedError(or DeepTab's class should subclass sklearn's so both catch), or the exception should not be advertised indeeptab.__all__and the exception-hierarchy docstring. As it stands the public error type is unraisable andexcept deeptab.NotFittedErroris a silent no-op.Repro
get_hardware_info is documented as
from deeptab import get_hardware_infobut is not exportedWhere:
deeptab/__init__.py(11, 15-32)deeptab/__init__.pyimports onlyprint_hardware_info, so the import shown inget_hardware_info's own (unskipped) docstring example raises ImportError; the function is reachable only viadeeptab.core.hardware.Observed:
ImportError: cannot import name 'get_hardware_info' from 'deeptab'. The docstring at deeptab/core/hardware.py:107-112 is an unskipped doctest (>>> from deeptab import get_hardware_info/>>> info = get_hardware_info()/>>> sorted(info)/['cpu', 'cuda', 'mps', 'platform', 'recommended_accelerator']), anddeeptab/core/hardware.py:10lists the name in__all__. The siblingprint_hardware_infois exported, so the omission looks accidental rather than deliberate.Expected: Add
get_hardware_infoto thefrom .core.hardware import ...line and todeeptab.__all__, so the documented example works and the machine-readable counterpart ofprint_hardware_info(the only way to programmatically readrecommended_accelerator) is reachable from the public namespace.Repro
FAQ tells users to subclass BaseTaskModel, a class that does not exist anywhere in the package
Where:
docs/getting_started/faq.md(512-514)"### Can I use my own custom architecture? — Yes, but it requires subclassing
BaseTaskModel. See the source code for examples of how to extend the base classes." There is noBaseTaskModelin DeepTab; a repo-wide grep finds the identifier only in this one FAQ line. The real extension point isBaseModel+SklearnBase*, asdocs/core_concepts/custom_models.mdcorrectly documents, so the FAQ actively sends readers looking for a nonexistent symbol.Observed: deeptab False
deeptab.core False
deeptab.training False
deeptab.models False
(and
from deeptab.core import BaseTaskModel-> ImportError)Expected: The FAQ answer should point at
BaseModel/SklearnBaseClassifier/SklearnBaseRegressor/SklearnBaseLSSand link tocore_concepts/custom_models.md, whose recipe I ran verbatim and confirmed works.Repro
fit()/build_model() numpydoc documents a nonexistent 'factor' parameter and four wrong defaults on all three estimator bases
Where:
deeptab/models/classifier_base.py(229, 237, 243, 245, 247, 249 (fit); mirrored in deeptab/models/regressor_base.py:73,175 and deeptab/models/lss_base.py:235)The public
fitdocstrings ofSklearnBaseClassifier,SklearnBaseRegressorandSklearnBaseLSS(and both regressorbuild_modeldocstrings) document a parameterfactor : float, default=0.1— the real name islr_factor. Unknown fit kwargs are swept into**trainer_kwargsand forwarded topl.Trainer, sofit(..., factor=0.5)fails there rather than being ignored. The same docstrings also state defaults that contradict the signature:batch_size default=64(real 128),patience default=10(real 15),lr default=1e-3(real None → 1e-4),weight_decay default=0.025(real None → 1e-6). A 10x learning rate and a 25000x weight decay are material misstatements for anyone reading the API reference rather than the source.Observed: 'factor' in signature: False
real batch_size default = 128
real patience default = 15
real lr default = None
real weight_decay default = None
docstring says: ['batch_size : int, default=64', 'patience : int, default=10', 'factor : float, default=0.1',
'lr : float, default=1e-3', 'weight_decay : float, default=0.025', ...]
TypeError: Trainer.init() got an unexpected keyword argument 'factor'
Expected: Rename the documented
factorentry tolr_factorand correct the four defaults to match the signature (128 / 15 / None→TrainerConfig.lr=1e-4 / None→TrainerConfig.weight_decay=1e-6). These docstrings are whatdocs/api/models/*.rstrenders as the public reference.Repro
MLPConfig documents a 'skip_layers' constructor parameter that does not exist
Where:
deeptab/configs/models/mlp_config.py(24-25 (docstring) vs 34-38 (fields))The
MLPConfignumpydoc listsskip_layers : bool, default=False — Whether to include skip layers.alongside the realskip_connectionsfield.skip_layersis not a dataclass field (the fields arelayer_sizes,dropout,use_glu,skip_connectionsplusBaseModelConfig's), so a user copying it from the API reference gets a TypeError. This is the only ghost field across all 21 config classes (I diffed every one).Observed: ['use_embeddings', ..., 'cat_encoding', 'layer_sizes', 'dropout', 'use_glu', 'skip_connections']
TypeError: MLPConfig.init() got an unexpected keyword argument 'skip_layers'
Expected: The docstring should drop
skip_layers(or the field should exist). TheParametersblock of a dataclass config is the documented constructor signature, and this entry is the only one in the package that cannot be passed.Repro
InferenceModel.predict() on an LSS model returns the parameter matrix, not the documented distribution mean/mode, and predict_params(raw=False) is documented backwards
Where:
deeptab/core/inference.py(344-373 (docstring at 347-350) and 407-439; doc mirror at docs/core_concepts/inference.md:257-266)InferenceModel.predict's docstring says "For distributional regression (LSS) returns the distribution mean / mode as a float array" and the guide labels it "Distribution mean / mode (default)". In factpredictcallsself._estimator.predict(X), which forSklearnBaseLSSreturns the transformed distribution parameters of shape(n_samples, n_params)— byte-for-byte identical topredict_params(X, raw=False). Separately, inference.md:263 annotatesmodel.predict_params(X_new, raw=False)as "Raw distribution parameters (before inverse-link transform)", which is exactly inverted:raw=Falseapplies the family transform,raw=Trueis the pre-transform output.Observed: predict shape: (60, 2) [-0.00286126 0.76246905]
predict == predict_params(raw=False)? True
raw=True (pre-transform): [-0.00286126 0.13414809]
So
predictreturns an (n, 2) [loc, scale] matrix, not an (n,) mean array, andraw=Falseis the transformed output, not the raw one.Expected: Either
InferenceModel.predictshould reduce LSS output to the distribution mean/mode (making it genuinely different frompredict_params), or both the docstring at inference.py:347-350 and inference.md:257-266 should say it returns the parameter matrix — and theraw=Falsecomment at inference.md:263 must be corrected. Serving code following the docstring (float(preds[i])for a point forecast) gets a length-2 array instead.Repro
InferenceModel.feature_names returns positional integer labels instead of None for array-fitted artifacts, making the documented count-only validation path unreachable
Where:
deeptab/core/inference.py(221-229 (feature_names) and 302-338 (validate_input); doc claim at docs/core_concepts/inference.md:196-207)docs/core_concepts/inference.md("No column names in the artifact") states that for a model fitted on a NumPy arraymodel.feature_namesisNoneand only a feature-count check runs, producingValueError: Expected 10 feature(s) (no column names available for detailed validation), got 7.. In realityensure_dataframegives the array integer column labels0..k-1,set_input_feature_attributesstores them ininput_columns_, andfeature_namesreturns[0, 1, 2, 3]. Theexpected_names is Nonebranch at inference.py:306-313 is therefore dead for such artifacts, and a serving payload built aspd.DataFrame([payload])with real key names is rejected with a misleading "missing column(s) [0, 1, 2, 3]" message.Observed: feature_names (docs say None): [0, 1, 2, 3]
named df -> ValueError | Input is missing 4 column(s) that were present during training: [0, 1, 2, 3].
wrong count -> ValueError | Input has 3 unexpected column(s) not seen during training: [4, 5, 6]. To drop them automatically, pass allow_extra_columns=True.
The documented "Expected 4 feature(s) (no column names available for detailed validation), got 7." message never appears. The bare estimator is looser but then leaks a raw pandas error:
m.predict(named_df)dies inside pretab withKeyError: "None of [Index([0], dtype='int64')] are in the [columns]"(via deeptab/data/datamodule.py:306), becausevalidate_input_featuresskips the name check whenfeature_names_in_was deleted for non-string columns.Expected:
feature_namesshould returnNonewheninput_columns_are auto-generated positional integers (i.e. not all strings), matchingset_input_feature_attributes' ownall(isinstance(c, str))test at deeptab/core/sklearn_compat.py:98 and the documented behaviour — so array-trained artifacts fall into the count-only branch and accept a same-width DataFrame instead of demanding columns literally named 0..k-1.Repro
InferenceModel cannot pass external embeddings, so deployment inference on an embedding-trained model dies with a raw torch shape error
Where:
deeptab/core/inference.py(324-352)InferenceModel.predict/predict_probacallself._estimator.predict(X_validated)with noembeddingsargument, but estimators fitted withfit(..., embeddings=...)require them at inference. The documented deployment wrapper is therefore unusable for those models and fails inside torch instead of reporting the missing input.Observed: RuntimeError: linear(): input and weight.T shapes cannot be multiplied (8x128 and 160x8).
validate_input()passes cleanly (the embedding columns are not part of the tabular schema), andL2.predict(X.iloc[:8], embeddings=emb[:8])works and matches the pre-save predictions exactly — so only the InferenceModel path is broken.Expected: InferenceModel should accept and forward embeddings (the artifact records
embedding_feature_info), or raise a clear error such as "this model was trained with external embeddings; pass embeddings=..." instead of a torch shape mismatch.Repro
Expected behavior
Documented names should exist and be exported, documented parameters should exist with the documented
defaults, and documented return values should match what is returned.
Screenshots
n/a
Desktop (please complete the following information):
Additional context
torch 2.9.1, lightning 2.6.5, scikit-learn 1.9.0, numpy 2.4.6. Found in a second-pass review of v2.0.0
(seven independent lenses, each finding adversarially re-verified by a second reviewer, then re-run by
hand). Distinct from the already-filed #409-#426.