dietnetwork 0.3.0__tar.gz
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.
- dietnetwork-0.3.0/.flake8 +6 -0
- dietnetwork-0.3.0/.gitignore +36 -0
- dietnetwork-0.3.0/CHANGELOG.md +103 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/__init__.py +0 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/compute_input_features_statistics.py +221 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/generate_embedding.py +216 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/helpers/__init__.py +0 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/helpers/dataset_utils.py +200 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/helpers/log_utils.py +148 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/helpers/mainloop_utils.py +395 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/helpers/model.py +489 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/helpers/model_handlers.py +89 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/helpers/task_handlers.py +361 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/partition_data.py +331 -0
- dietnetwork-0.3.0/Dietnet_train_and_evaluate/train.py +959 -0
- dietnetwork-0.3.0/LICENSE +28 -0
- dietnetwork-0.3.0/NOTICE +15 -0
- dietnetwork-0.3.0/PKG-INFO +249 -0
- dietnetwork-0.3.0/README.md +191 -0
- dietnetwork-0.3.0/pyproject.toml +149 -0
- dietnetwork-0.3.0/reproduction/1kgp_partition_seed124.npz +0 -0
- dietnetwork-0.3.0/reproduction/README.md +128 -0
- dietnetwork-0.3.0/reproduction/prep.sbatch +43 -0
- dietnetwork-0.3.0/reproduction/retrain.sbatch +31 -0
- dietnetwork-0.3.0/reproduction/submit_retrain.sh +16 -0
- dietnetwork-0.3.0/scripts/__init__.py +0 -0
- dietnetwork-0.3.0/scripts/check_sensitive_files.py +294 -0
- dietnetwork-0.3.0/scripts/upstream_manifest.py +79 -0
- dietnetwork-0.3.0/src/dietnet/__init__.py +11 -0
- dietnetwork-0.3.0/src/dietnet/_launch.py +40 -0
- dietnetwork-0.3.0/src/dietnet/align.py +188 -0
- dietnetwork-0.3.0/src/dietnet/alphagenome.py +208 -0
- dietnetwork-0.3.0/src/dietnet/cli.py +479 -0
- dietnetwork-0.3.0/src/dietnet/dataset.py +181 -0
- dietnetwork-0.3.0/src/dietnet/features.py +267 -0
- dietnetwork-0.3.0/src/dietnet/package.py +187 -0
- dietnetwork-0.3.0/src/dietnet/plink.py +163 -0
- dietnetwork-0.3.0/src/dietnet/predict.py +123 -0
- dietnetwork-0.3.0/src/dietnet/published.py +87 -0
- dietnetwork-0.3.0/src/dietnet/py.typed +0 -0
- dietnetwork-0.3.0/src/dietnet/training.py +176 -0
- dietnetwork-0.3.0/src/dietnet/upstream.py +161 -0
- dietnetwork-0.3.0/src/dietnet/upstream_manifest.json +53 -0
- dietnetwork-0.3.0/tests/README.md +42 -0
- dietnetwork-0.3.0/tests/conftest.py +56 -0
- dietnetwork-0.3.0/tests/fixtures/build_fixtures.py +210 -0
- dietnetwork-0.3.0/tests/fixtures/kgp_mini.bed +0 -0
- dietnetwork-0.3.0/tests/fixtures/kgp_mini.bim +2000 -0
- dietnetwork-0.3.0/tests/fixtures/kgp_mini.fam +480 -0
- dietnetwork-0.3.0/tests/fixtures/kgp_mini_labels.tsv +481 -0
- dietnetwork-0.3.0/tests/fixtures/kgp_mini_upstream.hdf5 +0 -0
- dietnetwork-0.3.0/tests/fixtures/synthetic.bed +0 -0
- dietnetwork-0.3.0/tests/fixtures/synthetic.bim +500 -0
- dietnetwork-0.3.0/tests/fixtures/synthetic.fam +200 -0
- dietnetwork-0.3.0/tests/fixtures/synthetic_labels.tsv +201 -0
- dietnetwork-0.3.0/tests/fixtures/synthetic_trait.tsv +201 -0
- dietnetwork-0.3.0/tests/fixtures/synthetic_upstream.hdf5 +0 -0
- dietnetwork-0.3.0/tests/integration/test_cli_pipeline.py +263 -0
- dietnetwork-0.3.0/tests/integration/test_sdist_contents.py +153 -0
- dietnetwork-0.3.0/tests/parity/test_dataset.py +72 -0
- dietnetwork-0.3.0/tests/parity/test_predict.py +125 -0
- dietnetwork-0.3.0/tests/parity/test_published.py +58 -0
- dietnetwork-0.3.0/tests/support.py +113 -0
- dietnetwork-0.3.0/tests/unit/test_align.py +179 -0
- dietnetwork-0.3.0/tests/unit/test_alphagenome.py +203 -0
- dietnetwork-0.3.0/tests/unit/test_docs_match_cli.py +62 -0
- dietnetwork-0.3.0/tests/unit/test_egress_guard.py +121 -0
- dietnetwork-0.3.0/tests/unit/test_features.py +134 -0
- dietnetwork-0.3.0/tests/unit/test_package_metadata.py +143 -0
- dietnetwork-0.3.0/tests/unit/test_sensitive_file_check.py +279 -0
- dietnetwork-0.3.0/tests/unit/test_upstream_unchanged.py +45 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
[flake8]
|
|
2
|
+
# Matches black's line length, so a line black accepts is never a lint error.
|
|
3
|
+
max-line-length = 100
|
|
4
|
+
extend-ignore = E203,W503,E501,F841,F541
|
|
5
|
+
# HussinLab's code is kept identical to upstream and never linted here.
|
|
6
|
+
extend-exclude = Dietnet_train_and_evaluate,.venv
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
|
|
5
|
+
# Private exp files
|
|
6
|
+
.ipynb_checkpoints
|
|
7
|
+
#*.ipynb
|
|
8
|
+
*.csv
|
|
9
|
+
run.sh
|
|
10
|
+
make_attributions_run.sh
|
|
11
|
+
Dietnet/Interpretability/experiment_scripts/*.sh
|
|
12
|
+
|
|
13
|
+
# Debugging and testing
|
|
14
|
+
DATA/
|
|
15
|
+
EXPERIMENT_01/
|
|
16
|
+
Test_dev/
|
|
17
|
+
Dietnet/tests/filenames.py
|
|
18
|
+
|
|
19
|
+
# Model weights
|
|
20
|
+
Models_1KGP_24populations/
|
|
21
|
+
|
|
22
|
+
# Local environments and tool state
|
|
23
|
+
.venv/
|
|
24
|
+
.claude/*
|
|
25
|
+
# The project's Claude Code settings and the data-egress hook they run are
|
|
26
|
+
# shared; everything else under .claude/ (local settings, worktrees) is not.
|
|
27
|
+
!.claude/settings.json
|
|
28
|
+
!.claude/hooks/
|
|
29
|
+
.coverage
|
|
30
|
+
coverage.xml
|
|
31
|
+
# Written by the CI install step
|
|
32
|
+
requirements-ci.txt
|
|
33
|
+
|
|
34
|
+
# The documentation site (uv run mkdocs build)
|
|
35
|
+
site/
|
|
36
|
+
.cache/
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
6
|
+
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.3.0] - 2026-10-26
|
|
9
|
+
|
|
10
|
+
Rebuilt on HussinLab/DIETNETWORK `main` (`5481556e`). The training code is
|
|
11
|
+
upstream's, unmodified. The `dietnet` command line, model packages and
|
|
12
|
+
inference are re-applied around it, replacing this repository's 2022 copy of
|
|
13
|
+
the core, which trained differently from the published model (its main
|
|
14
|
+
dropout was fixed at 0.5; the published config uses 0.1).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `dietnet build-embedding`: the auxiliary network's SNP features from several
|
|
19
|
+
sources: the published genotype frequencies, any precomputed per-SNP table
|
|
20
|
+
(oriented to the counted allele), and random or shuffled controls, with
|
|
21
|
+
per-block centring and PCA. Packages record which blocks a model used.
|
|
22
|
+
- `dietnet match`: prepare a cohort on CPU; `predict` with the same `--work-dir`
|
|
23
|
+
goes straight to inference (for a GPU job).
|
|
24
|
+
- `generate-embedding` and `train` refuse classes that have no training
|
|
25
|
+
samples in some fold: upstream's embedding is NaN for them (or silently
|
|
26
|
+
drops their columns).
|
|
27
|
+
- `reproduction/`: the published split (seed 124, verified against Zenodo), the
|
|
28
|
+
published models' held-out accuracy (0.9214) pinned by a slow test, and the
|
|
29
|
+
SLURM jobs that retrained all 15 (0.9282).
|
|
30
|
+
- `dietnet train --model mlp`: upstream's MLP baseline (no auxiliary network).
|
|
31
|
+
- `dietnet alphagenome`: AlphaGenome variant effects for every SNP through the
|
|
32
|
+
hosted API, resumable, as a feature table for `build-embedding`
|
|
33
|
+
(`pip install 'dietnetwork[alphagenome]'`).
|
|
34
|
+
- `dietnet create-dataset`: PLINK straight to upstream's HDF5 schema, identical
|
|
35
|
+
to upstream's `create_dataset.py` output and without the text round-trip.
|
|
36
|
+
- `dietnet published`: the 15 published models (Zenodo
|
|
37
|
+
doi:10.5281/zenodo.16943452) as model packages.
|
|
38
|
+
- Allele-aware SNP matching. Dosages that count the other allele are flipped,
|
|
39
|
+
the other strand is recognised, and A/T and C/G SNPs are kept only on an
|
|
40
|
+
exact match. The published D/(D−|C|) missing-SNP scale is applied.
|
|
41
|
+
- Biobank-scale prediction: the cohort is matched once into a contiguous HDF5
|
|
42
|
+
under `--work-dir`, memory-mapped, and shared by every model.
|
|
43
|
+
- `dietnet info`, which reports the upstream commit and whether its files are
|
|
44
|
+
unmodified.
|
|
45
|
+
- Parity tests against upstream: dataset builder, `predict` vs
|
|
46
|
+
`test_independent_dataset.py`, and the published models vs
|
|
47
|
+
`Dietnet_inference/inference.py`.
|
|
48
|
+
- Tooling as in manifold_genetics: uv lock, black/isort/flake8, pre-commit with
|
|
49
|
+
a sensitive-files guard and gitleaks, CI on Python 3.10–3.13 and macOS.
|
|
50
|
+
- Released as `dietnetwork` on PyPI: `release.yml` publishes on a version tag
|
|
51
|
+
through trusted publishing, after checking the tag, a dated CHANGELOG entry,
|
|
52
|
+
the artefacts' contents and a clean-room install (`dietnet info` must find
|
|
53
|
+
the bundled upstream code byte for byte the pinned commit).
|
|
54
|
+
- Tests of what would be published: package metadata
|
|
55
|
+
(`tests/unit/test_package_metadata.py`) and the sdist and wheel themselves
|
|
56
|
+
(`tests/integration/test_sdist_contents.py`: no untracked file, nothing the
|
|
57
|
+
sensitive-files check would flag, upstream code identical), run in a CI
|
|
58
|
+
`package` job.
|
|
59
|
+
- A Claude Code data-egress hook (`.claude/hooks/egress_guard.py`, run by the
|
|
60
|
+
project's `.claude/settings.json`): agents are asked for approval before any
|
|
61
|
+
copy from a remote host, any dump of a genotype or data file over ssh, or any
|
|
62
|
+
upload of one to a host that is not an Alliance cluster, and cannot commit or
|
|
63
|
+
push with `--no-verify`.
|
|
64
|
+
- `LICENSE` (BSD 3-Clause) and `NOTICE`, which keeps HussinLab's upstream code
|
|
65
|
+
outside that licence; `py.typed`.
|
|
66
|
+
- A documentation site (mkdocs-material, `docs/`, the `docs` extra), built
|
|
67
|
+
strictly on every pull request and deployed to GitHub Pages from `master`.
|
|
68
|
+
Its tutorial runs the whole pipeline on the `kgp_mini` fixture on every
|
|
69
|
+
build, and a test keeps `docs/cli.md` naming exactly the CLI's commands and
|
|
70
|
+
options.
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
|
|
74
|
+
- `build-embedding` with a `class_genotype_freq` block failed when
|
|
75
|
+
`--dataset` or `--partition` was a relative path: upstream's script runs from
|
|
76
|
+
its own directory and could not find them.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- `partition`, `generate-embedding`, `compute-input-stats` and `train` now run
|
|
81
|
+
upstream's scripts. Partitions are label-balanced by default, as in the paper.
|
|
82
|
+
- Model packages (format 2) hold upstream's checkpoint, the fold's embedding and
|
|
83
|
+
statistics, a training header and metadata (task, normalization, seed, fold,
|
|
84
|
+
upstream commit). Weights load strictly.
|
|
85
|
+
- The predictions TSV has a header: `sample`, `prediction`, `probability`,
|
|
86
|
+
`model_agreement` (regression: `prediction`, `sd_across_models`).
|
|
87
|
+
- `check` takes `--packages` (held-out accuracy per model) or `--predictions`
|
|
88
|
+
with `--labels`.
|
|
89
|
+
- Commands use `--plink` and `--out`. The old CLI's `--exp-path`-relative file
|
|
90
|
+
names are gone.
|
|
91
|
+
- `dietnet alphagenome` sends one request per SNP and retries transient API
|
|
92
|
+
errors with backoff. About 2% of requests fail at random with gRPC
|
|
93
|
+
`INTERNAL`, and the client's batch call failed a whole 200-SNP chunk on any
|
|
94
|
+
one of them, so a run never got past its first chunk.
|
|
95
|
+
|
|
96
|
+
### Removed
|
|
97
|
+
|
|
98
|
+
- The 2022 core (`Dietnet/`): model, training loop and dataset code, replaced by
|
|
99
|
+
upstream's.
|
|
100
|
+
- Upstream files the CLI does not run: deprecated code, experiments, upstream's
|
|
101
|
+
inference scripts and `create_dataset.py` (listed in the manifest).
|
|
102
|
+
- `preprocess-plink`, `analyze-population` and the Dropbox model presets.
|
|
103
|
+
Allele-aware matching covers the first; the others return if needed.
|
|
File without changes
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import multiprocessing as mp
|
|
4
|
+
import time
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
import h5py
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
start_time = time.time()
|
|
14
|
+
args = parse_args()
|
|
15
|
+
|
|
16
|
+
# Flag to compute only mean (True or False)
|
|
17
|
+
mean_only = args.mean_only
|
|
18
|
+
|
|
19
|
+
# Load indices of every fold
|
|
20
|
+
partition_data = np.load(args.partition, allow_pickle=True)
|
|
21
|
+
indices_byfold = partition_data['folds_indexes']
|
|
22
|
+
|
|
23
|
+
# Dataset
|
|
24
|
+
dataset_file = h5py.File(args.dataset, 'r')
|
|
25
|
+
|
|
26
|
+
# Compute inp feat stats by fold
|
|
27
|
+
means_by_fold = []
|
|
28
|
+
sds_by_fold = []
|
|
29
|
+
for fold in range(len(indices_byfold)):
|
|
30
|
+
print('\n---\nComputing input features statistic of fold', str(fold))
|
|
31
|
+
fold_start_time = time.time()
|
|
32
|
+
|
|
33
|
+
# Indices of the fold
|
|
34
|
+
fold_indices = indices_byfold[fold]
|
|
35
|
+
|
|
36
|
+
# Genotypes of training samples
|
|
37
|
+
train_indices = np.sort(fold_indices[0]) # sort is a hdf5 requirement
|
|
38
|
+
x_train = dataset_file['inputs'][train_indices]
|
|
39
|
+
|
|
40
|
+
# Compute statistics of every features in parallel
|
|
41
|
+
ncpus = args.ncpus
|
|
42
|
+
print('Computing input features statistics of {} SNPs for {} '
|
|
43
|
+
'samples using {} cpu(s)'.format(
|
|
44
|
+
x_train.shape[1], x_train.shape[0], ncpus))
|
|
45
|
+
|
|
46
|
+
pool = mp.Pool(processes=ncpus) #this starts ncpus nb of processes
|
|
47
|
+
|
|
48
|
+
# Results that will be returned by processes
|
|
49
|
+
results = []
|
|
50
|
+
|
|
51
|
+
# The statistics of every snp feature is computed by a different process
|
|
52
|
+
nb_snps = x_train.shape[1]
|
|
53
|
+
for i in range(nb_snps):
|
|
54
|
+
# Compute snps mean
|
|
55
|
+
if mean_only :
|
|
56
|
+
results.append(
|
|
57
|
+
pool.apply_async(
|
|
58
|
+
compute_feature_mean, args=(i, x_train[:,i])))
|
|
59
|
+
|
|
60
|
+
# Compute mean and standard deviation (sd)
|
|
61
|
+
else:
|
|
62
|
+
results.append(
|
|
63
|
+
pool.apply_async(
|
|
64
|
+
compute_feature_mean_and_sd, args=(i, x_train[:,i])))
|
|
65
|
+
|
|
66
|
+
# Get order (snp nb) and mean of features from multiprocess results
|
|
67
|
+
print('Computed statistics of all input features, getting the results')
|
|
68
|
+
results_values = np.array([np.array(p.get()) for p in results])
|
|
69
|
+
|
|
70
|
+
# Order stats based on snp nb
|
|
71
|
+
print('Ordering {} means returned by multiprocessing'.format(
|
|
72
|
+
len(results_values[:,1])))
|
|
73
|
+
idx = results_values[:,0].astype(int)
|
|
74
|
+
ordered_idx_pos = idx.argsort()
|
|
75
|
+
|
|
76
|
+
# Ordered means
|
|
77
|
+
means = results_values[:,1][ordered_idx_pos]
|
|
78
|
+
|
|
79
|
+
# Ordered sd
|
|
80
|
+
if not mean_only:
|
|
81
|
+
print('Ordering {} standard deviations returned by multiprocessing'.format(
|
|
82
|
+
len(results_values[:,2])))
|
|
83
|
+
sds = results_values[:,2][ordered_idx_pos]
|
|
84
|
+
|
|
85
|
+
# Append fold statistics
|
|
86
|
+
means_by_fold.append(means)
|
|
87
|
+
sds_by_fold.append(sds)
|
|
88
|
+
|
|
89
|
+
print('Computed fold input features stats in {} seconds\n---\n'.format(
|
|
90
|
+
time.time()-fold_start_time))
|
|
91
|
+
|
|
92
|
+
# Close access to h5py dataset file
|
|
93
|
+
dataset_file.close()
|
|
94
|
+
|
|
95
|
+
# Write input features stats to file
|
|
96
|
+
if args.out is not None:
|
|
97
|
+
stats_filename = args.out
|
|
98
|
+
else:
|
|
99
|
+
stats_filename = 'input_features_stats_' \
|
|
100
|
+
+ args.dataset.split('/')[-1][0:-5] \
|
|
101
|
+
+ '_' \
|
|
102
|
+
+ datetime.now().strftime("%Y_%m_%d_%Hh%Mmin%Ssec")
|
|
103
|
+
|
|
104
|
+
stats_fullpath = os.path.join(args.exp_path, stats_filename)
|
|
105
|
+
|
|
106
|
+
if mean_only:
|
|
107
|
+
np.savez(stats_fullpath,
|
|
108
|
+
means_by_fold=means_by_fold)
|
|
109
|
+
|
|
110
|
+
else:
|
|
111
|
+
np.savez(stats_fullpath,
|
|
112
|
+
means_by_fold=means_by_fold,
|
|
113
|
+
sd_by_fold=sds_by_fold)
|
|
114
|
+
|
|
115
|
+
print('\n---')
|
|
116
|
+
print('Input features stats were saved to {}'.format(stats_fullpath))
|
|
117
|
+
print('Execution time {} seconds\n---\n'.format(
|
|
118
|
+
time.time() - start_time))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def compute_feature_mean(i, x):
|
|
122
|
+
"""
|
|
123
|
+
i is the snp nb (from 0 to nb_snps-1)
|
|
124
|
+
x is the vector of genotypes for the snp (dim : nb_samples x 1)
|
|
125
|
+
"""
|
|
126
|
+
# Set to false missing value (missing values are encoded with -1)
|
|
127
|
+
mask = (x >= 0)
|
|
128
|
+
|
|
129
|
+
# Compute mean :
|
|
130
|
+
# in numerator, missing values are 0
|
|
131
|
+
# in the denominator we divise by the nb of non missing values
|
|
132
|
+
mean = np.sum(x*mask) / np.sum(mask)
|
|
133
|
+
|
|
134
|
+
return (i,mean)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def compute_feature_mean_and_sd(i, x):
|
|
138
|
+
"""
|
|
139
|
+
i is the snp nb (from 0 to nb_snps-1)
|
|
140
|
+
x is the vector of genotypes for the snp (dim : nb_samples x 1)
|
|
141
|
+
"""
|
|
142
|
+
# Compute mean
|
|
143
|
+
_,mean = compute_feature_mean(i,x)
|
|
144
|
+
|
|
145
|
+
# --- Compute sd ---
|
|
146
|
+
mask = (x >= 0) #set missing values (-1) to False
|
|
147
|
+
|
|
148
|
+
numerator = np.sum((x*mask - mean*mask)**2)
|
|
149
|
+
|
|
150
|
+
denominator = np.sum(mask) - 1
|
|
151
|
+
|
|
152
|
+
sd = np.sqrt(numerator/denominator)
|
|
153
|
+
|
|
154
|
+
sd += 1e-6
|
|
155
|
+
|
|
156
|
+
return (i,mean,sd)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def parse_args():
|
|
160
|
+
parser = argparse.ArgumentParser(
|
|
161
|
+
description=('Compute features means and standard deviations '
|
|
162
|
+
'for missing values filing and input normalization '
|
|
163
|
+
'at training time')
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Path
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
'--exp-path',
|
|
169
|
+
type=str,
|
|
170
|
+
required=True,
|
|
171
|
+
help=('Path to directory where input features statistics will '
|
|
172
|
+
'be written')
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Files
|
|
176
|
+
parser.add_argument(
|
|
177
|
+
'--dataset',
|
|
178
|
+
type=str,
|
|
179
|
+
required=True,
|
|
180
|
+
help=('Hdf5 dataset created with create_dataset.py. '
|
|
181
|
+
'Provide full path')
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
'--partition',
|
|
186
|
+
type=str,
|
|
187
|
+
required=True,
|
|
188
|
+
help=('Npz dataset partition returned by partition_data.py '
|
|
189
|
+
'Provide full path')
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Which statistic to compute (mean or mean+sd)
|
|
193
|
+
parser.add_argument(
|
|
194
|
+
'--mean-only',
|
|
195
|
+
action='store_true',
|
|
196
|
+
help=('Use this flag to compute only input features means '
|
|
197
|
+
'and not the standard deviations')
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
parser.add_argument(
|
|
201
|
+
'--ncpus',
|
|
202
|
+
type=int,
|
|
203
|
+
default=1,
|
|
204
|
+
help=('Number of cpus for parallel computation of means '
|
|
205
|
+
'and of standard deviations. Default: %(default)i')
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Output
|
|
209
|
+
parser.add_argument(
|
|
210
|
+
'--out',
|
|
211
|
+
type=str,
|
|
212
|
+
help=('Optional filename for input features statistics file. '
|
|
213
|
+
'If not provided the file will be named '
|
|
214
|
+
'input_features_stats_datasetFilename_date')
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return parser.parse_args()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == '__main__':
|
|
221
|
+
main()
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import multiprocessing as mp
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
import h5py
|
|
10
|
+
|
|
11
|
+
import helpers.dataset_utils as du
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Additive encoding : genotypes 0, 1 and 2
|
|
15
|
+
NB_POSSIBLE_GENOTYPES = 3
|
|
16
|
+
|
|
17
|
+
# Class label key in dataset file
|
|
18
|
+
CLASS_LABEL = 'class_labels'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
start_time = time.time()
|
|
23
|
+
args = parse_args()
|
|
24
|
+
|
|
25
|
+
# Hdf5 dataset
|
|
26
|
+
dataset_file = h5py.File(args.dataset, 'r')
|
|
27
|
+
|
|
28
|
+
# Indices by fold
|
|
29
|
+
partition_data = np.load(args.partition, allow_pickle=True)
|
|
30
|
+
indices_byfold = partition_data['folds_indexes']
|
|
31
|
+
|
|
32
|
+
print('\n---')
|
|
33
|
+
print('Computing embedding by fold using {} cpu(s)'.format(args.ncpus))
|
|
34
|
+
if args.ncpus > 1:
|
|
35
|
+
pool = mp.Pool(processes=args.ncpus) #this starts ncpus nb of processes
|
|
36
|
+
|
|
37
|
+
# Results that will be returned by processes (embedding for a given fold)
|
|
38
|
+
results = []
|
|
39
|
+
|
|
40
|
+
for fold in range(len(indices_byfold)):
|
|
41
|
+
# Fold indices
|
|
42
|
+
fold_indices = indices_byfold[fold]
|
|
43
|
+
# Get fold data
|
|
44
|
+
(_,_,_,
|
|
45
|
+
x_train, y_train, _,
|
|
46
|
+
x_valid, y_valid, _,
|
|
47
|
+
x_test, y_test,_) = du.get_fold_data(fold, indices_byfold, dataset_file, label=CLASS_LABEL)
|
|
48
|
+
|
|
49
|
+
# Embedding on train+valid sets
|
|
50
|
+
if args.include_valid:
|
|
51
|
+
print('Including valid samples in emb computation')
|
|
52
|
+
x = np.concatenate((x_train, x_valid))
|
|
53
|
+
y = np.concatenate((y_train, y_valid))
|
|
54
|
+
# Embedding on valid set
|
|
55
|
+
elif args.only_valid:
|
|
56
|
+
x = x_valid
|
|
57
|
+
y = y_valid
|
|
58
|
+
# Embedding on test set
|
|
59
|
+
elif args.only_test:
|
|
60
|
+
x = x_test
|
|
61
|
+
y = y_test
|
|
62
|
+
# Embedding on training set
|
|
63
|
+
else:
|
|
64
|
+
x = x_train
|
|
65
|
+
y = y_train
|
|
66
|
+
|
|
67
|
+
# Compute embedding
|
|
68
|
+
#emb = compute_fold_embedding(x, y)
|
|
69
|
+
|
|
70
|
+
print('Computing embedding using {} samples'.format(len(y)))
|
|
71
|
+
# One cpu
|
|
72
|
+
if args.ncpus == 1:
|
|
73
|
+
results.append(compute_fold_embedding(x, y, fold))
|
|
74
|
+
else:
|
|
75
|
+
results.append(pool.apply_async(
|
|
76
|
+
compute_fold_embedding, args=(x,y,fold)))
|
|
77
|
+
|
|
78
|
+
# Close access to dataset file
|
|
79
|
+
dataset_file.close()
|
|
80
|
+
|
|
81
|
+
# Results values: [(fold0, embedding0), (fold1, embedding1) ...]
|
|
82
|
+
print('Computed embedding of every fold, getting the results')
|
|
83
|
+
if args.ncpus > 1:
|
|
84
|
+
results_values = [p.get() for p in results]
|
|
85
|
+
else:
|
|
86
|
+
results_values = results
|
|
87
|
+
|
|
88
|
+
# Order results by fold
|
|
89
|
+
print('Putting embeddings by fold order')
|
|
90
|
+
folds_order = np.array([i[0] for i in results_values])
|
|
91
|
+
|
|
92
|
+
# Order counts (nb samples) per class
|
|
93
|
+
classes_counts = np.array([i[1] for i in results_values])
|
|
94
|
+
classes_counts_ordered = classes_counts[folds_order]
|
|
95
|
+
|
|
96
|
+
# Order embeddings
|
|
97
|
+
embeddings = np.array([i[2] for i in results_values])
|
|
98
|
+
embeddings_ordered = embeddings[folds_order]
|
|
99
|
+
|
|
100
|
+
print('Computed embedding of {} folds with following '
|
|
101
|
+
'number of samples per class for each fold: {}'.format(
|
|
102
|
+
len(folds_order), classes_counts_ordered))
|
|
103
|
+
print('---')
|
|
104
|
+
|
|
105
|
+
# Write embedding to file
|
|
106
|
+
if args.out is not None:
|
|
107
|
+
emb_filename = args.out
|
|
108
|
+
else:
|
|
109
|
+
emb_filename = 'genotype_class_freq_embedding_' \
|
|
110
|
+
+ args.dataset.split('/')[-1][0:-5] \
|
|
111
|
+
+ '_' \
|
|
112
|
+
+ datetime.now().strftime("%Y_%m_%d_%Hh%Mmin%Ssec")
|
|
113
|
+
|
|
114
|
+
emb_fullpath = os.path.join(args.exp_path, emb_filename)
|
|
115
|
+
|
|
116
|
+
print('\n---\nEmbeddings generated in {} seconds'.format(
|
|
117
|
+
time.time()-start_time))
|
|
118
|
+
|
|
119
|
+
print('Saving results to {}'.format(emb_fullpath))
|
|
120
|
+
np.savez(emb_fullpath, emb=embeddings_ordered)
|
|
121
|
+
|
|
122
|
+
print('---\n')
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def compute_fold_embedding(xs, ys, fold):
|
|
126
|
+
# Total number of classes
|
|
127
|
+
nb_class = ys.max() + 1 #class 0
|
|
128
|
+
class_to_count = {}
|
|
129
|
+
|
|
130
|
+
# Compute sum of genotypes (0-1-2) per class
|
|
131
|
+
xs = xs.transpose() # rows are snps, col are inds
|
|
132
|
+
embedding = np.zeros((xs.shape[0],nb_class*NB_POSSIBLE_GENOTYPES))
|
|
133
|
+
for c in range(nb_class):
|
|
134
|
+
# Select genotypes for samples of same class
|
|
135
|
+
class_genotypes = xs[:,ys==c]
|
|
136
|
+
nb = class_genotypes.shape[1] #nb of samples in that class
|
|
137
|
+
#print('Class:', c, 'NB:', nb)
|
|
138
|
+
class_to_count[c] = nb
|
|
139
|
+
for genotype in range(NB_POSSIBLE_GENOTYPES):
|
|
140
|
+
col = NB_POSSIBLE_GENOTYPES*c+genotype
|
|
141
|
+
embedding[:,col] = (class_genotypes == genotype).sum(axis=1)/nb
|
|
142
|
+
|
|
143
|
+
return fold, class_to_count, embedding
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def parse_args():
|
|
148
|
+
parser = argparse.ArgumentParser(
|
|
149
|
+
description='Generate embedding'
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
parser.add_argument(
|
|
153
|
+
'--exp-path',
|
|
154
|
+
type=str,
|
|
155
|
+
required=True,
|
|
156
|
+
help='Path to directory where embedding will be saved. '
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
parser.add_argument(
|
|
160
|
+
'--dataset',
|
|
161
|
+
type=str,
|
|
162
|
+
required=True,
|
|
163
|
+
help=('Hdf5 dataset created with create_dataset.py '
|
|
164
|
+
'Provide full path')
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
'--partition',
|
|
169
|
+
type=str,
|
|
170
|
+
required=True,
|
|
171
|
+
help=('Npz dataset partition returned by partition_data.py '
|
|
172
|
+
'Provide full path')
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
parser.add_argument(
|
|
176
|
+
'--ncpus',
|
|
177
|
+
type=int,
|
|
178
|
+
default=1,
|
|
179
|
+
help=('Number of cpus available to compute folds embedding in '
|
|
180
|
+
'parallel. Default:%(default)i')
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
'--include-valid',
|
|
185
|
+
action='store_true',
|
|
186
|
+
help=('Use this flag to include samples from the validation set '
|
|
187
|
+
'in the embedding computation.')
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
'--only-valid',
|
|
192
|
+
action='store_true',
|
|
193
|
+
help=('Use this flag to compute embedding only on samples '
|
|
194
|
+
' in the validation set')
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
parser.add_argument(
|
|
198
|
+
'--only-test',
|
|
199
|
+
action='store_true',
|
|
200
|
+
help=('Use this flag to compute embedding only on samples '
|
|
201
|
+
' in the test set')
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
parser.add_argument(
|
|
205
|
+
'--out',
|
|
206
|
+
type=str,
|
|
207
|
+
help=('Optional filename for embedding file. If not provided '
|
|
208
|
+
'the file will be named '
|
|
209
|
+
'genotype_class_freq_embedding_datasetFilename_date')
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return parser.parse_args()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
if __name__ == '__main__':
|
|
216
|
+
main()
|
|
File without changes
|