fix(uc3-uhi): unblock the Guatemala UHI use case on develop - #148
fix(uc3-uhi): unblock the Guatemala UHI use case on develop#148BachirNILU wants to merge 9 commits into
Conversation
Four defects prevented `experiment=heat_alignment` and every
`heat_guatemala_*` prediction experiment from running. Two of them are
not use-case specific.
base_datamodule: in the `from_file` split, a MissingDataError was built
rather than raised, so exception objects ended up inside the index array
and surfaced later as "list indices must be integers or slices, not
MissingDataError". Unmatched name_locs are now dropped with a warning
(matching what the train split already did) and an empty split raises.
train.py: `cfg.get("test") and wandb_logger is not None` silently skipped
the test and validate stages whenever LOGGER=none. Only the wandb
log_metrics call needs the logger.
base_model: loss_fn.setup() was called unconditionally, but the
predictive models are configured with torch losses (nn.MSELoss), which
have no setup hook.
heat_guatemala_caption_builder: implements return_aux_ids. Templates use
expert-legend label columns while the soft loss scores on the numeric
ones, so an explicit label -> numeric map is needed. aux_lst is excluded
so the target cannot leak into a caption.
configs: CNNEncoder no longer takes freezing_strategy or output_dim, but
both CNN model configs still passed them. Restores the heat_guatemala
experiment configs, which were dropped from develop.
scripts/infer_aligned_embeddings.py: fetches Tessera through the
GeoTessera API and projects it into the aligned text space using a
trained checkpoint.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LnFiUYCJ445jKSqHKaNbXt
…ence script The csv output now carries name_loc, lat, lon, the raw pooled Tessera vector (tessera_000..127) and the aligned one (aligned_000..511), so the two can be compared directly downstream. Pooling moved into the per-tile load. Stacking full tiles first held ~1.7 GB across the 13k Guatemala locations; the pooled vectors are ~7 MB. Verified on all 13,038 locations, none skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LnFiUYCJ445jKSqHKaNbXt
The csv now has five columns: name_loc, lat, lon, tessera_embedding, aligned_embedding, with the whole vector per cell as a bracketed list (json.loads reads it back). --wide-columns keeps the previous one-column-per-dimension layout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LnFiUYCJ445jKSqHKaNbXt
plot_concept_maps.py takes the aligned-embedding csv plus a json of concepts, encodes each concept's captions with the trained text branch, and writes a per-location score table and one map per concept. Scoring is a single matmul: the aligned embeddings are already L2-normalised. Maps are coloured by z-score. Raw cosines occupy a narrow band, so a map of them reads as flat; the ranking is unchanged. The run also prints the correlation between concepts, since near-duplicate concepts otherwise produce near-identical maps. demo_concepts.json holds the vocabulary, limited to the concepts with a positive skill index on the test split. Both scripts now ignore the legacy text_model.embeddings.position_ids buffer, which older transformers versions wrote into checkpoints and which was being rejected as an unexpected key. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LnFiUYCJ445jKSqHKaNbXt
| # set up loss if needed. Only BaseLossFn subclasses define setup(); the | ||
| # predictive models are configured with torch losses directly (nn.MSELoss, | ||
| # nn.BCEWithLogitsLoss, ...), which have no such hook. | ||
| if self.loss_fn is not None and hasattr(self.loss_fn, "setup"): |
There was a problem hiding this comment.
As discussed, I am not sure if we should keep it?
| train_metrics = trainer.callback_metrics | ||
|
|
||
| if cfg.get("validate") and wandb_logger is not None: | ||
| # NB: only the wandb bookkeeping needs a wandb_logger; trainer.validate/test |
There was a problem hiding this comment.
I agree! Could we remove these comments?
| records_name_to_idx[n] | ||
| if n in records_name_to_idx | ||
| else MissingDataError("Validation split is missing data") | ||
| def _to_record_indices(name_locs, split_name): |
There was a problem hiding this comment.
Before hand we had a MissingData error specifically for validation and test splits, which must remain identical. The split was already processed so that validation and test splits only contain locations that have all modalities available. Meanwhile train split can use as many locations as there are available for a specific location.
Looking at this code, I don't think it functions the intended way.
There was a problem hiding this comment.
I agree it should be raise MissingDataError(f"{mode} split is missing data")
|
The configurations were not removed but rather moved to |
base_datamodule: restore the original contract. Validation and test splits are already built to contain only locations with every modality available, so a name_loc missing from the records means the split and the data have diverged and the run must stop -- raise MissingDataError. The train split may legitimately be shorter, so unmatched entries there are dropped with a warning. The bug being fixed remains the same one: the error was constructed rather than raised, which put exception objects into the index array. Note this makes a saved split a hard contract: a split file predating the current model-ready csv now stops the run instead of warning. base_model: use isinstance(self.loss_fn, BaseLossFn) rather than a hasattr probe. Only BaseLossFn subclasses define setup(); the predictive models are configured with torch losses directly. train.py: drop the explanatory comment, keep the fix. configs: the guatemala experiment configs were moved to configs/experiment/guatemala/, not removed -- my earlier check missed the subfolder and restored duplicates at the top level. Those are removed. Only the two configs genuinely absent upstream are added to the subfolder: heat_guatemala_full_fusion_avg_reg and heat_guatemala_tessera_tabular_avg_best. All 11 guatemala experiments smoke-tested after the change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Hi @gabrieletijunaityte , all four comments addressed (with support from Claude code) please feel free to take another look. |
Adds the Guatemala use case to src/inference/, following the layout the s2bms habitat-similarity script established, each with a config under configs/. inference_heat_guatemala_embeddings.py produces the aligned Tessera embeddings. inference_heat_guatemala_concept_similarity.py is the Guatemala counterpart of inference_s2bms_habitat_similarity.py: it collects the aligned embedding, the predictive model's LST estimate and the measured LST for every location in a split, then correlates each concept's text similarity against both. The prediction head consumes the pooled 128-d Tessera vector while the aligned embedding is that same vector projected into the 512-d text space, so the two branches share an input but not a representation and neither output can be fed to the other. Both checkpoints are therefore needed, loaded separately. Shared loaders live in src/utils/heat_guatemala_inference.py rather than beside the scripts: src/inference.py and src/inference/ collide as import targets, so src.inference cannot be made a package without shadowing the existing module. First result on the test split (3368 locations), which removed a concept from the demo vocabulary: predictive model r=0.853, RMSE=1.078 degC vegetation greenness r=-0.595 vs measured LST forest cover r=-0.646 established urban r=+0.431 cool area r=+0.217 <- wrong sign, dropped "Cool area" similarity correlates positively with measured LST, i.e. it points at hotter places. It tracks established urban fabric rather than coolness, so it is moved to the excluded list in demo_concepts.json. scripts/infer_aligned_embeddings.py is left untouched; it is the standalone argparse version already shared for the demo UI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s normalisation epoch_093 is the tessera+average model the experiment config documents (R2=0.733, RMSE=1.011, MAE=0.814); this checkout reproduces R2=0.7330, MAE=0.8147 on the test split. It replaces epoch_059, a develop-side retrain that reaches only R2=0.7103. epoch_093 cannot simply be loaded by develop's PredictiveModel. Feature normalisation changed from a plain L2 F.normalize to a trainable nn.LayerNorm, so every checkpoint trained before that change lacks the LayerNorm weights. Loading one anyway raises nothing and silently scores R2 = -8230 on this split. load_prediction_branch now infers the era from the weights -- LayerNorm when normalizer.* is present, L2 otherwise -- and logs which it picked. Validation on the test split (3368 locations) is unchanged in sign and slightly stronger against predictions: predictive model r=0.862, RMSE=1.035 degC vegetation greenness r=-0.595 vs measured, -0.633 vs predicted forest cover r=-0.646 / -0.716 established urban r=+0.431 / +0.458 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Testing the new dataset/caption refactor on the Guatemala UHI use case (#125). Four things blocked it. Two are not use-case specific.
Up front: this branch was produced with AI assistance (Claude Code). I've reviewed and run everything, but please read it as a proposal rather than a finished patch — a few calls below are choices, not obvious fixes, and I'd rather you decide.
Bugs
base_datamodule.py—from_filesplit builds aMissingDataErrorinstead of raising it, so exception objects land in the index array and surface much later as:This fires for any
name_locin the split file that isn't inrecords. Affects every UC that loads a saved split, not just this one. Also:trainsilently dropped unmatched entries whileval/testdid not, and the test branch used the message"Validation split is missing data".train.py—cfg.get("test") and wandb_logger is not Nonesilently skips the test and validate stages whenLOGGER=none(the.env.exampledefault), even withtest: True. Only thewandb_logger.log_metricscall needs the logger.base_model.py—loss_fn.setup()is called unconditionally, but the predictive models are configured with torch losses directly. Everyheat_guatemala_*prediction experiment died on'MSELoss' object has no attribute 'setup'.configs/model/heat_*_cnn_best.yaml—CNNEncoderno longer acceptsfreezing_strategyoroutput_dim, but both configs still passed them. These ship on develop and are incompatible with develop's own code.New
return_aux_idsforHeatGuatemalaCaptionBuilder. OnlyButterflyCaptionBuilderreturns the tuple the base class unpacks, so the feature raised for us.YieldAfricaCaptionBuilderhas the same gap — not touched here.scripts/infer_aligned_embeddings.py— coordinates in, GeoTessera API fetch, aligned embeddings out, using a trained checkpoint.--captionsprints a cosine-similarity matrix as a quick alignment check.Please review — these are choices
name_locswith a warning to match whattrainalready did. TheMissingDataErrorsuggests you may have wanted a hard failure on any unmatched validation row. Both are defensible; if you prefer hard-fail it just needs to be an actualraise, and the split file becomes a strict contract. Your call.configs/experiment/heat_guatemala_*.yamlandheat_alignment.yaml, which were removed from develop. If that deletion was deliberate, drop this part of the diff and I'll relocate them.output_dim: 256from the CNN configs changes the encoder output to the resnet default. Correct w.r.t. the new API, but it does change the tuned model.Not fixed here — worth separate issues
setup_tessera()is unreachable:download_missing_tilesis a hardcoded localFalse. Inside it: an undefinedavail_filesin the full-download branch;get_tessera_embeds()called without its requiredversion;except NoTileError or PartialTileError(only catches the first); and a mask recomputed against a staleavail_files, so freshly downloaded tiles get dropped anyway. Fixing it means deciding how downloads should be requested, which felt like your call.versiondefaults tov1.1, which doesn't cover Guatemala City — 11 tiles for 2024 in the 1°×1° box, vs the 13,038 we hold from v1. A live fetch fails under v1.1 and succeeds under v1. Worth reconsidering the default.logging.infoinstead oflog.infoinsetup_tesseraandstore_concept_thresholds(lands onroot);random()rebindstemplate_idsinside the loop iterating it;assert getattr(torch, dtype), KeyError(...)raisesAttributeErrorfirst.Verified
All 10
heat_guatemala_*experiments plusheat_alignmentsmoke-test clean. Full 5-epoch alignment run:val_loss3.432 → 3.252,val_pos_sim0.597 vsval_neg_sim0.430,test_loss3.602.n_captions_for_validationandreturn_aux_idsboth confirmed working.🤖 Generated with Claude Code