diff --git a/CHANGELOG.md b/CHANGELOG.md index 065a7c44..12319011 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,8 @@ * `senkin_tmp_train`, `senkin_tmp_predict`: Store the model bundle (the test predictions, as the solution is transductive) as an h5ad file in a directory instead of a pickle. The train image is built from `nvidia/cuda` with the current pandas 3, the predict image `openproblems/base_pytorch_nvidia:1` ships pandas 2, and unpickling the bundled `var` DataFrame raised `NotImplementedError` in `NDArrayBacked.__setstate__`, so senkin_tmp finished both CITE training runs of run_2026-09-23 but never produced a prediction. The predict step still reads the old pickle until the test resources are regenerated (PR #73). +* `novel`: Guard the TF-IDF and LSI of the ATAC input against cells without counts in the selected peaks. These turned into NaN, so the validation loss never improved, no model was saved and `novel_predict` failed with a missing `tensor.pt` on the multiome swap datasets (PR #76). + * `senkin_tmp_predict`: Compare the cell and protein names of the stored predictions as lists. The train and predict images run different pandas versions, so the same names were read back with a different index dtype and `Index.equals()` failed on every dataset (PR #75). * `scipenn`: Exit 99 (non-applicable) instead of raising a `ValueError` on anything other than GEX -> ADT (PR #77). diff --git a/src/methods/novel/helper_functions.py b/src/methods/novel/helper_functions.py index 0ebe2009..dbb3a3e8 100644 --- a/src/methods/novel/helper_functions.py +++ b/src/methods/novel/helper_functions.py @@ -29,11 +29,13 @@ def fit(self, X): def transform(self, X): if not self.fitted: raise RuntimeError('Transformer was not fitted on any data') + row_sums = np.asarray(X.sum(axis=1)).reshape(-1, 1) + row_sums[row_sums == 0] = 1 if scipy.sparse.issparse(X): - tf = X.multiply(1 / X.sum(axis=1)) + tf = X.multiply(1 / row_sums) return tf.multiply(self.idf) else: - tf = X / X.sum(axis=1, keepdims=True) + tf = X / row_sums return tf * self.idf def fit_transform(self, X): @@ -75,7 +77,9 @@ def transform(self, adata): X_norm = np.log1p(X_norm * 1e4) X_lsi = self.pcaTransformer.transform(X_norm) X_lsi -= X_lsi.mean(axis=1, keepdims=True) - X_lsi /= X_lsi.std(axis=1, ddof=1, keepdims=True) + X_lsi_std = X_lsi.std(axis=1, ddof=1, keepdims=True) + X_lsi_std[X_lsi_std == 0] = 1 + X_lsi /= X_lsi_std lsi_df = pd.DataFrame(X_lsi, index = adata_use.obs_names) return lsi_df