Repository: microsoft/torchgeo

Issue:
SeCo weights issue warning when loaded
### Description

It seems like the SeCo weights still have the final layer from when they were trained, meaning they always issue a warning when loaded into a trainer. We should remove the last layer and make sure the only keys in the weight are the ones needed to load the model.

### Steps to reproduce

Any of the following commands will reproduce the warning:
```console
$ pytest -m slow tests/trainers/test_regression.py::TestRegressionTask::test_weight_enum_download[ResNet50_Weights.SENTINEL2_RGB_SECO]
$ pytest -m slow tests/trainers/test_regression.py::TestRegressionTask::test_weight_str_download[ResNet50_Weights.SENTINEL2_RGB_SECO]
$ pytest -m slow tests/trainers/test_classification.py::TestClassificationTask::test_weight_enum_download[ResNet50_Weights.SENTINEL2_RGB_SECO]
$ pytest -m slow tests/trainers/test_classification.py::TestClassificationTask::test_weight_str_download[ResNet50_Weights.SENTINEL2_RGB_SECO]
```
No other weights issue this warning. We could ignore it with `with pytest.warns():`, but this will cause all other weights to fail the tests since they don't issue a warning.

### Version

0.5.0.dev0 (35525b2eb9cd53bf1910f2f6bd7714584205d927)


Discussion:
The current SeCo weights are almost entirely incorrect as the only key that matches between our uploaded weights and the author's weights is "conv1.weight".
Here's how you can extract just the encoder weights and rename them into a format that timm/torchvision understands:

```
from lightning.pytorch.utilities.migration import pl_legacy_patch
import timm
from src.moco import MocoV2  # from the SeCo repo
import torch
from copy import deepcopy

with pl_legacy_patch():
    backbone = MocoV2.load_from_checkpoint("../../seasonal-contrast/seco_resnet50_1m.ckpt")
    model = deepcopy(backbone.encoder_q).eval()
    state_dict_original = model.state_dict()

    resnet18 = timm.create_model("resnet50")
    key_correct_name_list = list(resnet18.state_dict().keys())

    state_dict_new = {}
    for i, (k, v) in enumerate(state_dict_original.items()):
         state_dict_new[key_correct_name_list[i]] = v

    torch.save(state_dict_new, "seco_resnet50_1m.ckpt")
``` 
@nilsleh if you have time after you finish #1482 can you take a look at this?

Resolving patch:
diff --git a/torchgeo/models/resnet.py b/torchgeo/models/resnet.py
--- a/torchgeo/models/resnet.py
+++ b/torchgeo/models/resnet.py
@@ -83,7 +83,7 @@ class ResNet18_Weights(WeightsEnum):  # type: ignore[misc]
     )
 
     SENTINEL2_RGB_SECO = Weights(
-        url="https://huggingface.co/torchgeo/resnet18_sentinel2_rgb_seco/resolve/main/resnet18_sentinel2_rgb_seco-9976a9cb.pth",  # noqa: E501
+        url="https://huggingface.co/torchgeo/resnet18_sentinel2_rgb_seco/resolve/main/resnet18_sentinel2_rgb_seco-cefca942.pth",  # noqa: E501
         transforms=_seco_transforms,
         meta={
             "dataset": "SeCo Dataset",
@@ -158,7 +158,7 @@ class ResNet50_Weights(WeightsEnum):  # type: ignore[misc]
     )
 
     SENTINEL2_RGB_SECO = Weights(
-        url="https://huggingface.co/torchgeo/resnet50_sentinel2_rgb_seco/resolve/main/resnet50_sentinel2_rgb_seco-584035db.pth",  # noqa: E501
+        url="https://huggingface.co/torchgeo/resnet50_sentinel2_rgb_seco/resolve/main/resnet50_sentinel2_rgb_seco-018bf397.pth",  # noqa: E501
         transforms=_seco_transforms,
         meta={
             "dataset": "SeCo Dataset",
@@ -196,7 +196,11 @@ def resnet18(
     model: ResNet = timm.create_model("resnet18", *args, **kwargs)
 
     if weights:
-        model.load_state_dict(weights.get_state_dict(progress=True), strict=False)
+        missing_keys, unexpected_keys = model.load_state_dict(
+            weights.get_state_dict(progress=True), strict=False
+        )
+        assert set(missing_keys) <= {"fc.weight", "fc.bias"}
+        assert not unexpected_keys
 
     return model
 
@@ -227,6 +231,10 @@ def resnet50(
     model: ResNet = timm.create_model("resnet50", *args, **kwargs)
 
     if weights:
-        model.load_state_dict(weights.get_state_dict(progress=True), strict=False)
+        missing_keys, unexpected_keys = model.load_state_dict(
+            weights.get_state_dict(progress=True), strict=False
+        )
+        assert set(missing_keys) <= {"fc.weight", "fc.bias"}
+        assert not unexpected_keys
 
     return model
diff --git a/torchgeo/models/vit.py b/torchgeo/models/vit.py
--- a/torchgeo/models/vit.py
+++ b/torchgeo/models/vit.py
@@ -94,6 +94,10 @@ def vit_small_patch16_224(
     )
 
     if weights:
-        model.load_state_dict(weights.get_state_dict(progress=True), strict=False)
+        missing_keys, unexpected_keys = model.load_state_dict(
+            weights.get_state_dict(progress=True), strict=False
+        )
+        assert set(missing_keys) <= {"head.weight", "head.bias"}
+        assert not unexpected_keys
 
     return model


Test patch: