gradiend 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. gradiend/__init__.py +103 -0
  2. gradiend/data/__init__.py +23 -0
  3. gradiend/data/core/__init__.py +7 -0
  4. gradiend/data/core/base_loader.py +121 -0
  5. gradiend/data/core/spacy_util.py +63 -0
  6. gradiend/data/text/__init__.py +16 -0
  7. gradiend/data/text/filter_config.py +93 -0
  8. gradiend/data/text/prediction/__init__.py +10 -0
  9. gradiend/data/text/prediction/creator.py +771 -0
  10. gradiend/data/text/prediction/filter_engine.py +359 -0
  11. gradiend/data/text/preprocess.py +241 -0
  12. gradiend/evaluator/__init__.py +22 -0
  13. gradiend/evaluator/config.py +30 -0
  14. gradiend/evaluator/decoder.py +951 -0
  15. gradiend/evaluator/decoder_eval_utils.py +55 -0
  16. gradiend/evaluator/encoder.py +243 -0
  17. gradiend/evaluator/encoder_metrics.py +398 -0
  18. gradiend/evaluator/evaluator.py +280 -0
  19. gradiend/model/__init__.py +14 -0
  20. gradiend/model/core/__init__.py +17 -0
  21. gradiend/model/core/backbone.py +236 -0
  22. gradiend/model/layers.py +129 -0
  23. gradiend/model/model.py +791 -0
  24. gradiend/model/model_with_gradiend.py +890 -0
  25. gradiend/model/param_mapped.py +916 -0
  26. gradiend/model/utils.py +376 -0
  27. gradiend/py.typed +0 -0
  28. gradiend/trainer/__init__.py +70 -0
  29. gradiend/trainer/config.py +25 -0
  30. gradiend/trainer/core/__init__.py +23 -0
  31. gradiend/trainer/core/arguments.py +335 -0
  32. gradiend/trainer/core/callbacks.py +473 -0
  33. gradiend/trainer/core/config.py +15 -0
  34. gradiend/trainer/core/dataset.py +215 -0
  35. gradiend/trainer/core/feature_definition.py +672 -0
  36. gradiend/trainer/core/protocols.py +65 -0
  37. gradiend/trainer/core/pruning.py +385 -0
  38. gradiend/trainer/core/stats.py +152 -0
  39. gradiend/trainer/core/training.py +549 -0
  40. gradiend/trainer/factory.py +80 -0
  41. gradiend/trainer/text/__init__.py +36 -0
  42. gradiend/trainer/text/common/__init__.py +21 -0
  43. gradiend/trainer/text/common/dataset.py +51 -0
  44. gradiend/trainer/text/common/dataset_base.py +148 -0
  45. gradiend/trainer/text/common/lm_eval.py +505 -0
  46. gradiend/trainer/text/common/loading.py +140 -0
  47. gradiend/trainer/text/common/model_base.py +97 -0
  48. gradiend/trainer/text/prediction/__init__.py +19 -0
  49. gradiend/trainer/text/prediction/dataset.py +237 -0
  50. gradiend/trainer/text/prediction/decoder_eval_utils.py +392 -0
  51. gradiend/trainer/text/prediction/decoder_only_mlm.py +405 -0
  52. gradiend/trainer/text/prediction/model_with_gradiend.py +184 -0
  53. gradiend/trainer/text/prediction/trainer.py +2199 -0
  54. gradiend/trainer/text/prediction/unified_data.py +680 -0
  55. gradiend/trainer/text/prediction/unified_schema.py +26 -0
  56. gradiend/trainer/trainer.py +1668 -0
  57. gradiend/util/__init__.py +2 -0
  58. gradiend/util/logging.py +91 -0
  59. gradiend/util/metrics.py +191 -0
  60. gradiend/util/paths.py +373 -0
  61. gradiend/util/util.py +121 -0
  62. gradiend/visualizer/__init__.py +30 -0
  63. gradiend/visualizer/convergence.py +559 -0
  64. gradiend/visualizer/encoder_distributions.py +535 -0
  65. gradiend/visualizer/encoder_scatter.py +248 -0
  66. gradiend/visualizer/plot_optional.py +35 -0
  67. gradiend/visualizer/probability_shifts.py +373 -0
  68. gradiend/visualizer/topk/__init__.py +26 -0
  69. gradiend/visualizer/topk/pairwise_heatmap.py +621 -0
  70. gradiend/visualizer/topk/venn_.py +298 -0
  71. gradiend/visualizer/visualizer.py +99 -0
  72. gradiend-0.1.0.dist-info/METADATA +130 -0
  73. gradiend-0.1.0.dist-info/RECORD +76 -0
  74. gradiend-0.1.0.dist-info/WHEEL +5 -0
  75. gradiend-0.1.0.dist-info/licenses/LICENSE +201 -0
  76. gradiend-0.1.0.dist-info/top_level.txt +1 -0
gradiend/__init__.py ADDED
@@ -0,0 +1,103 @@
1
+ """
2
+ GRADIEND: Feature Learning within Neural Networks
3
+
4
+ GRADIEND is a method for learning features within neural networks
5
+ by training an encoder-decoder architecture on gradients.
6
+
7
+ Public API (from gradiend):
8
+ - Model: GradiendModel, ParamMappedGradiendModel, ModelWithGradiend
9
+ - Data: TextFilterConfig, TextPredictionDataCreator, TextPreprocessConfig,
10
+ SpacyTagSpec, preprocess_texts, resolve_base_data
11
+ - Trainer: TextPredictionTrainer, TextPredictionConfig, TrainingArguments,
12
+ load_training_stats, GradientTrainingDataset, TextGradientTrainingDataset,
13
+ create_model_with_gradiend
14
+ - Logging: setup_logging, get_logger
15
+
16
+ Sub-packages (use when you need modality-specific or internal APIs):
17
+ - gradiend.trainer: Trainer, PrePruneConfig, PostPruneConfig, callbacks, etc.
18
+ - gradiend.trainer.text: TextModelWithGradiend, TextBatchedDataset, etc.
19
+ - gradiend.evaluator: EncoderEvaluator, DecoderEvaluator, Evaluator
20
+ - gradiend.visualizer: Visualizer, plot_encoder_distributions, plot_topk_overlap_heatmap, plot_topk_overlap_venn, etc.
21
+ - gradiend.data: same as top-level data API (alternative import path)
22
+
23
+ For experimental features (analysis, plotting, LaTeX export), install with:
24
+ pip install gradiend[recommended]
25
+ """
26
+
27
+ from importlib.metadata import PackageNotFoundError, version
28
+
29
+ try:
30
+ __version__ = version("gradiend")
31
+ except PackageNotFoundError:
32
+ __version__ = "0.1.0" # editable install before package is installed
33
+
34
+ # Core model classes
35
+ from gradiend.model import GradiendModel, ParamMappedGradiendModel, ModelWithGradiend
36
+
37
+ # High-level data API (filter config, data creator, preprocess)
38
+ from gradiend.data import (
39
+ TextFilterConfig,
40
+ TextPredictionDataCreator,
41
+ TextPreprocessConfig,
42
+ SpacyTagSpec,
43
+ preprocess_texts,
44
+ resolve_base_data,
45
+ )
46
+
47
+ # Text prediction trainer (high-level)
48
+ from gradiend.trainer.text.prediction.trainer import TextPredictionTrainer, TextPredictionConfig
49
+
50
+ # Logging
51
+ from gradiend.util.logging import setup_logging, get_logger
52
+
53
+ # Visualization (top-k overlap plots)
54
+ from gradiend.visualizer import (
55
+ plot_topk_overlap_heatmap,
56
+ plot_topk_overlap_venn,
57
+ )
58
+
59
+ # Training
60
+ from gradiend.trainer import (
61
+ load_training_stats,
62
+ set_seed,
63
+ TrainingArguments,
64
+ TrainerConfig,
65
+ GradientTrainingDataset,
66
+ TextGradientTrainingDataset,
67
+ create_model_with_gradiend,
68
+ PrePruneConfig,
69
+ PostPruneConfig
70
+ )
71
+
72
+ __all__ = [
73
+ # Core model
74
+ "GradiendModel",
75
+ "ParamMappedGradiendModel",
76
+ "ModelWithGradiend",
77
+ # Data (high-level)
78
+ "TextFilterConfig",
79
+ "TextPredictionDataCreator",
80
+ "TextPreprocessConfig",
81
+ "SpacyTagSpec",
82
+ "preprocess_texts",
83
+ "resolve_base_data",
84
+ # Trainers and configs
85
+ "TextPredictionTrainer",
86
+ "TextPredictionConfig",
87
+ "TrainerConfig",
88
+ "PrePruneConfig",
89
+ "PostPruneConfig",
90
+ # Logging
91
+ "setup_logging",
92
+ "get_logger",
93
+ # Training
94
+ "load_training_stats",
95
+ "set_seed",
96
+ "TrainingArguments",
97
+ "GradientTrainingDataset",
98
+ "TextGradientTrainingDataset",
99
+ "create_model_with_gradiend",
100
+ # Visualization
101
+ "plot_topk_overlap_heatmap",
102
+ "plot_topk_overlap_venn",
103
+ ]
@@ -0,0 +1,23 @@
1
+ """
2
+ Data module: create training and neutral datasets from base corpora.
3
+
4
+ Use TextPredictionDataCreator to filter and mask base texts based on TextFilterConfig.
5
+ """
6
+
7
+ from gradiend.data.core import resolve_base_data
8
+ from gradiend.data.text import (
9
+ SpacyTagSpec,
10
+ TextFilterConfig,
11
+ TextPreprocessConfig,
12
+ preprocess_texts,
13
+ )
14
+ from gradiend.data.text.prediction import TextPredictionDataCreator
15
+
16
+ __all__ = [
17
+ "SpacyTagSpec",
18
+ "TextFilterConfig",
19
+ "TextPreprocessConfig",
20
+ "TextPredictionDataCreator",
21
+ "preprocess_texts",
22
+ "resolve_base_data",
23
+ ]
@@ -0,0 +1,7 @@
1
+ """Core data loading (modality-agnostic)."""
2
+
3
+ from gradiend.data.core.base_loader import resolve_base_data
4
+
5
+ __all__ = [
6
+ "resolve_base_data",
7
+ ]
@@ -0,0 +1,121 @@
1
+ """
2
+ Base data loader: resolve from HF, DataFrame, CSV, or list of strings.
3
+
4
+ Modality-agnostic; used by both text prediction and (future) classification.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import random
10
+ from pathlib import Path
11
+ from typing import List, Optional, Union
12
+
13
+ import pandas as pd
14
+
15
+ from gradiend.util.logging import get_logger
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ def resolve_base_data(
21
+ source: Union[str, pd.DataFrame, List[str]],
22
+ text_column: str = "text",
23
+ max_size: Optional[int] = None,
24
+ split: str = "train",
25
+ seed: int = 42,
26
+ hf_config: Optional[str] = None,
27
+ trust_remote_code: bool = False,
28
+ ) -> List[str]:
29
+ """Resolve input to a list of text strings.
30
+
31
+ Supports HuggingFace dataset ID, pandas DataFrame, CSV path, or list of strings.
32
+ Data is shuffled with `seed` before applying `max_size` to avoid bias from
33
+ ordered sources (e.g. chronological, single-author).
34
+
35
+ Args:
36
+ source: HF dataset ID (str), CSV path (str ending in .csv or path exists),
37
+ pandas DataFrame, or List[str].
38
+ text_column: Column name for text (default "text"). Ignored for List[str].
39
+ max_size: Cap on number of items (applied after shuffle). None = no cap.
40
+ split: Dataset split for HF (default "train").
41
+ seed: Random seed for shuffle.
42
+ hf_config: HuggingFace dataset config/subset (e.g. "20220301.en" for wikipedia).
43
+ Only used when source is an HF dataset ID.
44
+ trust_remote_code: Passed to load_dataset when loading from HF. Default False.
45
+
46
+ Returns:
47
+ List of strings (texts).
48
+ """
49
+ if not isinstance(text_column, str):
50
+ raise TypeError(f"text_column must be str, got {type(text_column).__name__}")
51
+ if max_size is not None and not isinstance(max_size, int):
52
+ raise TypeError(f"max_size must be int or None, got {type(max_size).__name__}")
53
+ if not isinstance(split, str):
54
+ raise TypeError(f"split must be str, got {type(split).__name__}")
55
+ if not isinstance(seed, int):
56
+ raise TypeError(f"seed must be int, got {type(seed).__name__}")
57
+ if hf_config is not None and not isinstance(hf_config, str):
58
+ raise TypeError(f"hf_config must be str or None, got {type(hf_config).__name__}")
59
+ if not isinstance(trust_remote_code, bool):
60
+ raise TypeError(f"trust_remote_code must be bool, got {type(trust_remote_code).__name__}")
61
+
62
+ texts: List[str]
63
+ if isinstance(source, list):
64
+ if not all(isinstance(x, str) for x in source):
65
+ raise TypeError("source as list must contain only strings")
66
+ texts = [str(x).strip() for x in source if str(x).strip()]
67
+ elif isinstance(source, pd.DataFrame):
68
+ if text_column not in source.columns:
69
+ raise ValueError(f"DataFrame missing column '{text_column}'. Columns: {list(source.columns)}")
70
+ texts = source[text_column].dropna().astype(str).str.strip().tolist()
71
+ texts = [x for x in texts if x]
72
+ elif isinstance(source, str):
73
+ texts = _load_from_string_source(
74
+ source, text_column, split, hf_config, trust_remote_code
75
+ )
76
+ else:
77
+ raise TypeError(f"source must be str, DataFrame, or List[str]; got {type(source)}")
78
+
79
+ rng = random.Random(seed)
80
+ rng.shuffle(texts)
81
+ if max_size is not None and len(texts) > max_size:
82
+ texts = texts[:max_size]
83
+ logger.debug(f"resolve_base_data: {len(texts)} texts")
84
+ return texts
85
+
86
+
87
+ def _load_from_string_source(
88
+ source: str,
89
+ text_column: str,
90
+ split: str,
91
+ hf_config: Optional[str] = None,
92
+ trust_remote_code: bool = False,
93
+ ) -> List[str]:
94
+ """Load from HF dataset or CSV path."""
95
+ path = Path(source)
96
+ if path.suffix.lower() == ".csv" and path.exists():
97
+ df = pd.read_csv(source)
98
+ if text_column not in df.columns:
99
+ raise ValueError(f"CSV missing column '{text_column}'. Columns: {list(df.columns)}")
100
+ texts = df[text_column].dropna().astype(str).str.strip().tolist()
101
+ return [x for x in texts if x]
102
+
103
+ try:
104
+ from datasets import load_dataset
105
+ except ImportError as e:
106
+ raise ImportError("HuggingFace datasets required for HF source. pip install datasets") from e
107
+
108
+ if hf_config is not None:
109
+ ds = load_dataset(
110
+ source, hf_config, split=split, trust_remote_code=trust_remote_code
111
+ )
112
+ else:
113
+ ds = load_dataset(source, split=split, trust_remote_code=trust_remote_code)
114
+ if hasattr(ds, "to_pandas"):
115
+ df = ds.to_pandas()
116
+ else:
117
+ df = pd.DataFrame(list(ds))
118
+ if text_column not in df.columns:
119
+ raise ValueError(f"HF dataset missing column '{text_column}'. Columns: {list(df.columns)}")
120
+ texts = df[text_column].dropna().astype(str).str.strip().tolist()
121
+ return [x for x in texts if x]
@@ -0,0 +1,63 @@
1
+ """
2
+ Spacy model loading with clearer errors and optional auto-download.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any
8
+
9
+ # Spacy model-not-found error code
10
+ _SPACY_E050 = "E050"
11
+
12
+
13
+ def load_spacy_model(
14
+ model_name: str,
15
+ download_if_missing: bool = False,
16
+ ) -> Any:
17
+ """Load a spacy model with improved error handling.
18
+
19
+ Args:
20
+ model_name: Spacy model name (e.g. "de_core_news_sm").
21
+ download_if_missing: If True and the model is not found, attempt to
22
+ download it via `python -m spacy download`. Default False.
23
+
24
+ Returns:
25
+ Loaded spacy Language model.
26
+
27
+ Raises:
28
+ ImportError: If spacy is not installed.
29
+ OSError: If the model is not found, with a message including the
30
+ install command: "python -m spacy download {model_name}".
31
+ """
32
+ try:
33
+ import spacy
34
+ except ImportError as e:
35
+ raise ImportError(
36
+ "spacy is required for morphological filtering. "
37
+ "Install with: pip install spacy "
38
+ "Or: pip install gradiend[data]"
39
+ ) from e
40
+
41
+ try:
42
+ return spacy.load(model_name)
43
+ except OSError as e:
44
+ err_str = str(e)
45
+ is_model_not_found = _SPACY_E050 in err_str or "Can't find model" in err_str
46
+
47
+ if is_model_not_found and download_if_missing:
48
+ try:
49
+ from spacy.cli import download
50
+ download(model_name)
51
+ return spacy.load(model_name)
52
+ except Exception as download_err:
53
+ raise OSError(
54
+ f"Spacy model '{model_name}' not found and auto-download failed: {download_err}. "
55
+ f"Install manually with: python -m spacy download {model_name}"
56
+ ) from e
57
+
58
+ if is_model_not_found:
59
+ raise OSError(
60
+ f"Spacy model '{model_name}' not found. "
61
+ f"Install it with: python -m spacy download {model_name}"
62
+ ) from e
63
+ raise
@@ -0,0 +1,16 @@
1
+ """Text-specific data modules."""
2
+
3
+ from gradiend.data.text.filter_config import SpacyTagSpec, TextFilterConfig
4
+ from gradiend.data.text.preprocess import (
5
+ TextPreprocessConfig,
6
+ iter_sentences_from_texts,
7
+ preprocess_texts,
8
+ )
9
+
10
+ __all__ = [
11
+ "SpacyTagSpec",
12
+ "TextFilterConfig",
13
+ "TextPreprocessConfig",
14
+ "iter_sentences_from_texts",
15
+ "preprocess_texts",
16
+ ]
@@ -0,0 +1,93 @@
1
+ """
2
+ Filter configuration for text: targets, spacy tags, lemma matching.
3
+
4
+ Not prediction-specific; used by text filtering in general.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Dict, List, Optional, Union
11
+
12
+ # Type alias for spacy morph/tag constraints (e.g. {"pos": "DET", "Case": "Nom"})
13
+ SpacyTagSpec = Dict[str, str]
14
+
15
+
16
+ def _merge_spacy_tags(parent: Optional[SpacyTagSpec], child: Optional[SpacyTagSpec]) -> Optional[SpacyTagSpec]:
17
+ """Merge child tags into parent; child overrides parent for same keys."""
18
+ if parent is None and child is None:
19
+ return None
20
+ if parent is None:
21
+ return dict(child) if child else None
22
+ if child is None:
23
+ return dict(parent)
24
+ out = dict(parent)
25
+ out.update(child)
26
+ return out
27
+
28
+
29
+ def _first_target_string(targets: List[Union["TextFilterConfig", str]]) -> Optional[str]:
30
+ """Extract first target string from targets for default id."""
31
+ for t in targets:
32
+ if isinstance(t, str):
33
+ return t
34
+ if isinstance(t, TextFilterConfig):
35
+ s = _first_target_string(t.targets)
36
+ if s is not None:
37
+ return s
38
+ return None
39
+
40
+
41
+ @dataclass
42
+ class TextFilterConfig:
43
+ """Configuration for filtering and masking target tokens in text.
44
+
45
+ Simple use: TextFilterConfig(target="das") or TextFilterConfig(targets=["der", "die"], spacy_tags={...})
46
+ Advanced: TextFilterConfig(targets=[TextFilterConfig(["das"], spacy_tags={...}), "der"], spacy_tags={...})
47
+ Nested entries inherit parent spacy_tags and may add/override their own.
48
+
49
+ When used in TextPredictionDataCreator.feature_targets (a list), id names the output class.
50
+ If id is None, it is inferred from the first target string.
51
+ """
52
+
53
+ targets: Optional[List[Union["TextFilterConfig", str]]] = None
54
+ target: Optional[str] = None
55
+ spacy_tags: Optional[SpacyTagSpec] = None
56
+ use_lemma: bool = False
57
+ id: Optional[str] = None
58
+ mask: str = "[MASK]"
59
+ weight: float = 1.0
60
+
61
+ def __post_init__(self) -> None:
62
+ if self.target is not None and self.targets is not None:
63
+ raise ValueError("TextFilterConfig: provide either target or targets, not both")
64
+ if self.target is not None:
65
+ self.targets = [self.target]
66
+ elif isinstance(self.targets, str):
67
+ self.targets = [self.targets]
68
+ if self.targets is None:
69
+ self.targets = []
70
+ if not self.targets:
71
+ raise ValueError("TextFilterConfig.targets must be non-empty")
72
+ if self.id is None:
73
+ self.id = _first_target_string(self.targets)
74
+
75
+ def get_effective_tags(self, parent_tags: Optional[SpacyTagSpec] = None) -> Optional[SpacyTagSpec]:
76
+ """Get merged spacy_tags (self + parent)."""
77
+ return _merge_spacy_tags(parent_tags, self.spacy_tags)
78
+
79
+ def flatten_targets_with_tags(
80
+ self,
81
+ parent_tags: Optional[SpacyTagSpec] = None,
82
+ ) -> List[tuple[str, Optional[SpacyTagSpec]]]:
83
+ """Flatten nested config to list of (target_str, effective_spacy_tags)."""
84
+ effective = self.get_effective_tags(parent_tags)
85
+ out: List[tuple[str, Optional[SpacyTagSpec]]] = []
86
+ for t in self.targets:
87
+ if isinstance(t, str):
88
+ out.append((t, effective))
89
+ elif isinstance(t, TextFilterConfig):
90
+ out.extend(t.flatten_targets_with_tags(effective))
91
+ else:
92
+ raise TypeError(f"target must be str or TextFilterConfig; got {type(t)}")
93
+ return out
@@ -0,0 +1,10 @@
1
+ """Text prediction data: filter engine, TextPredictionDataCreator."""
2
+
3
+ from gradiend.data.text.prediction.creator import TextPredictionDataCreator
4
+ from gradiend.data.text.prediction.filter_engine import filter_sentences, mask_sentence
5
+
6
+ __all__ = [
7
+ "filter_sentences",
8
+ "mask_sentence",
9
+ "TextPredictionDataCreator",
10
+ ]