File size: 5,184 Bytes
ff5b2f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import numpy as np
import torch
import torch.nn as nn
from timm.models.layers import trunc_normal_
from einops import rearrange

from onescience.modules.embedding.fourcastnetembedding import FourCastNetEmbedding
from onescience.modules.fuser.fourcastnetfuser import FourCastNetFuser


class FourCastNet(nn.Module):
    """
    FourCastNet 的主模型实现。

    该模型使用以下组件完成输入编码与主干特征提取:

    - `OneEmbedding(style="FourCastNetEmbedding")`
      - 将二维气象场切分为二维 patch token 序列
    - `OneFuser(style="FourCastNetFuser")`
      - 在二维 patch 网格上重复执行 AFNO 频域混合与 MLP 通道混合

    在当前实现中:

    - 输入为二维单时刻气象场 `(Batch, Channels, Height, Width)`
    - patch embedding 输出会加上可学习位置编码
    - token 序列随后恢复成 `(PatchGridHeight, PatchGridWidth)` 二维网格
    - 多层 `FourCastNetFuser` 在 patch 网格上完成主干特征提取
    - 最终通过线性头恢复回目标变量场

    Args:
        img_size (tuple[int, int]):
            输入空间尺寸 `(Height, Width)`。
        patch_size (tuple[int, int]):
            patch 切分尺寸 `(PatchHeight, PatchWidth)`。
        in_chans (int):
            输入变量通道数。
        out_chans (int):
            输出变量通道数。
        embed_dim (int):
            patch embedding 特征维度。
        depth (int):
            主干 `FourCastNetFuser` 堆叠层数。
        mlp_ratio (float):
            每层 MLP 隐层放大倍数。
        drop_rate (float):
            dropout 比例。
        drop_path_rate (float):
            按层递增的 Stochastic Depth 最大比例。
        num_blocks (int):
            AFNO 的通道分块数。
        sparsity_threshold (float):
            AFNO 的 soft shrink 阈值。
        hard_thresholding_fraction (float):
            AFNO 保留的频率模式比例。
    """

    def __init__(
        self,
        img_size=(720, 1440),
        patch_size=(8, 8),
        in_chans=19,
        out_chans=19,
        embed_dim=768,
        depth=12,
        mlp_ratio=4.0,
        drop_rate=0.0,
        drop_path_rate=0.0,
        num_blocks=8,
        sparsity_threshold=0.01,
        hard_thresholding_fraction=1.0,
    ):
        super().__init__()
        self.img_size = img_size
        self.patch_size = patch_size
        self.in_chans = in_chans
        self.out_chans = out_chans
        self.num_features = self.embed_dim = embed_dim
        self.num_blocks = num_blocks

        num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0])
        drop_path = np.linspace(0, drop_path_rate, depth).tolist()

        self.pos_embed = nn.Parameter(torch.zeros(1, num_patches, embed_dim))
        self.pos_drop = nn.Dropout(p=drop_rate)
        self.patch_grid_height = img_size[0] // self.patch_size[0]
        self.patch_grid_width = img_size[1] // self.patch_size[1]

        self.patch_embed = FourCastNetEmbedding(
            img_size=img_size,
            patch_size=patch_size,
            in_chans=in_chans,
            embed_dim=embed_dim,
        )

        self.blocks = nn.ModuleList([
            FourCastNetFuser(
                dim=embed_dim,
                mlp_ratio=mlp_ratio,
                drop=drop_rate,
                drop_path=drop_path[i],
                num_blocks=num_blocks,
                sparsity_threshold=sparsity_threshold,
                hard_thresholding_fraction=hard_thresholding_fraction,
            )
            for i in range(depth)
        ])

        self.head = nn.Linear(
            embed_dim,
            self.out_chans * self.patch_size[0] * self.patch_size[1],
            bias=False,
        )

        trunc_normal_(self.pos_embed, std=0.02)
        self.apply(self._init_weights)

    def _init_weights(self, m):
        if isinstance(m, nn.Linear):
            trunc_normal_(m.weight, std=0.02)
            if isinstance(m, nn.Linear) and m.bias is not None:
                nn.init.constant_(m.bias, 0)
        elif isinstance(m, nn.LayerNorm):
            nn.init.constant_(m.bias, 0)
            nn.init.constant_(m.weight, 1.0)

    @torch.jit.ignore
    def no_weight_decay(self):
        return {'pos_embed', 'cls_token'}


    def forward(self, x):
        """
        Args:
            x (torch.Tensor):
                输入张量,形状为 `(Batch, Channels, Height, Width)`。

        Returns:
            torch.Tensor:
                输出张量,形状为 `(Batch, out_chans, Height, Width)`。
        """
        Batch = x.shape[0]

        x = self.patch_embed(x)
        x = x + self.pos_embed
        x = self.pos_drop(x)

        x = x.reshape(Batch, self.patch_grid_height, self.patch_grid_width, self.embed_dim)
        for blk in self.blocks:
            x = blk(x)

        x = self.head(x)
        x = rearrange(
            x,
            "b h w (p1 p2 c_out) -> b c_out (h p1) (w p2)",
            p1=self.patch_size[0],
            p2=self.patch_size[1],
            h=self.patch_grid_height,
            w=self.patch_grid_width,
        )
        return x