├── LICENSE
├── README.md
└── model.py
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Agon Serifi, Tobias Günther and Nikolina Ban
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spatio-Temporal Downscaling of Climate Data using Convolutional and Error-Predicting Neural Networks
2 |
3 |
4 | This folder contains a tensorflow implementation for the DCN and RPN architectures of the paper:
5 |
6 | * Agon Serifi, Tobias Günther, Nikolina Ban
7 | [Spatio-Temporal Downscaling of Climate Data using Convolutional and Error-Predicting Neural Networks](https://doi.org/10.3389/fclim.2021.656479)
8 |
9 | The file model.py contains functions to construct the proposed architectures.
10 | Use 'get_model(residual = False)' for the DCN and 'get_model(residual = True)' for the RPN architecture.
11 |
12 | ### Dependencies
13 | - Tensorflow
14 |
15 | ### Citation
16 | If you find our work useful to your research, please consider citing:
17 | ```
18 | @article{serifi2021spatio,
19 | title={Spatio-Temporal Downscaling of Climate Data using Convolutional and Error-Predicting Neural Networks},
20 | author={Serifi, Agon and G{\"u}nther, Tobias and Ban, Nikolina},
21 | journal={Frontiers in Climate},
22 | volume={3},
23 | pages={26},
24 | year={2021},
25 | publisher={Frontiers}
26 | }
27 | ```
28 |
29 | ### License
30 | By downloading and using the code you agree to the terms in the [LICENSE](https://github.com/aserifi/convolutional-downscaling/blob/main/LICENSE).
31 |
--------------------------------------------------------------------------------
/model.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 |
3 | from tensorflow.keras.layers import *
4 | from tensorflow.keras.models import Model
5 |
6 | print(tf.__version__)
7 |
8 | def grad_loss(v_gt, v):
9 | # Gradient loss
10 | loss = tf.reduce_mean(tf.abs(v - v_gt), axis=[1,2,3])
11 | jy = v[:,:,1:,:,:] - v[:,:,:-1,:,:]
12 | jx = v[:,:,:,1:,:] - v[:,:,:,:-1,:]
13 | jy_ = v_gt[:,:,1:,:,:] - v_gt[:,:,:-1,:,:]
14 | jx_ = v_gt[:,:,:,1:,:] - v_gt[:,:,:,:-1,:]
15 | loss += tf.reduce_mean(tf.abs(jy - jy_), axis=[1,2,3])
16 | loss += tf.reduce_mean(tf.abs(jx - jx_), axis=[1,2,3])
17 | return loss
18 |
19 | def uNet(input, time, lat, lon, height, kernel = [5, 3, 3], nodes = [72, 144, 288, 576]):
20 | '''
21 | This function defines a U-Net architecture
22 | :param input: the main-input layer
23 | :param time: the time input layer
24 | :param lat, lon, height: additional fields
25 | :param kernel: Kernel-sizes (default = [5, 3, 3])
26 | :param nodes: different neuron-sizes if needed (default = [72, 144, 288, 576])
27 | :return: last layer of constructed model
28 | '''
29 |
30 | # set Timesteps
31 | TS = 3
32 | ##################################################### 1st Block ####################################################
33 | conv1 = Conv3D(filters = nodes[0],
34 | kernel_size = (TS, kernel[0], kernel[0]),
35 | activation = 'relu',
36 | padding = 'same',
37 | data_format = 'channels_last')(input)
38 | mergetime = Concatenate(axis=4)([conv1, lat, lon, height])
39 | conv1 = Conv3D(filters = nodes[0],
40 | kernel_size = kernel[0],
41 | activation = 'relu',
42 | padding = 'same',
43 | data_format = 'channels_last')(mergetime)
44 |
45 | pool1 = MaxPooling3D(pool_size = (1, 2, 2))(conv1)
46 |
47 | ##################################################### 2nd Block ####################################################
48 | conv2 = Conv3D(filters = nodes[1],
49 | kernel_size = (TS, kernel[1], kernel[1]),
50 | activation = 'relu',
51 | padding = 'same',
52 | data_format = 'channels_last')(pool1)
53 | conv2 = Conv3D(filters = nodes[1],
54 | kernel_size = (TS, kernel[1], kernel[1]),
55 | activation = 'relu',
56 | padding = 'same',
57 | data_format = 'channels_last')(conv2)
58 |
59 | pool2 = MaxPooling3D(pool_size = (1, 2, 2))(conv2)
60 |
61 | ##################################################### 3rd Block ####################################################
62 | conv3 = Conv3D(filters = nodes[2],
63 | kernel_size = (TS, kernel[2], kernel[2]),
64 | activation = 'relu',
65 | padding = 'same',
66 | data_format = 'channels_last')(pool2)
67 | conv3 = Conv3D(filters = nodes[2],
68 | kernel_size = (TS, kernel[2], kernel[2]),
69 | activation='relu',
70 | padding = 'same',
71 | data_format = 'channels_last')(conv3)
72 |
73 | pool3 = MaxPooling3D(pool_size = (1, 2, 2))(conv3)
74 |
75 | ##################################################### 4th Block ####################################################
76 | conv4 = Conv3D(filters = nodes[3],
77 | kernel_size = (TS, kernel[2], kernel[2]),
78 | activation='relu',
79 | padding = 'same',
80 | data_format = 'channels_last')(pool3)
81 | conv4 = Conv3D(filters = nodes[3],
82 | kernel_size = (TS, kernel[2], kernel[2]),
83 | activation='relu',
84 | padding = 'same',
85 | data_format = 'channels_last')(conv4)
86 |
87 | ####################################################### TIME #######################################################
88 | # Merge time-layer at this point
89 | mergetime = Concatenate(axis=4)([conv4, time])
90 |
91 | ################################################### UP 3rd Block ###################################################
92 | # Up-Size again
93 | up3 = UpSampling3D(size = (1, 2, 2))(mergetime)
94 | up3 = Conv3D(filters = nodes[2],
95 | kernel_size = (TS, kernel[1], kernel[1]),
96 | activation = 'relu',
97 | padding = 'same',
98 | kernel_initializer = 'he_normal')(up3)
99 |
100 | # Skip connection
101 | merge3 = Concatenate(axis=4)([conv3, up3])
102 |
103 | conv3 = Conv3D(filters = nodes[2],
104 | kernel_size = (TS, kernel[1], kernel[1]),
105 | activation = 'relu',
106 | padding = 'same',
107 | data_format = 'channels_last')(merge3)
108 | conv3 = Conv3D(filters = nodes[2],
109 | kernel_size = (TS, kernel[1], kernel[1]),
110 | activation = 'relu',
111 | padding = 'same',
112 | data_format = 'channels_last')(conv3)
113 |
114 | ################################################### UP 2nd Block ###################################################
115 | up2 = UpSampling3D(size = (1, 2, 2))(conv3)
116 | up2 = Conv3D(filters = nodes[1],
117 | kernel_size = (TS, kernel[1], kernel[1]),
118 | activation = 'relu',
119 | padding = 'same',
120 | kernel_initializer = 'he_normal')(up2)
121 |
122 | # Skip connection
123 | merge2 = Concatenate(axis=4)([conv2, up2])
124 |
125 | conv2 = Conv3D(filters = nodes[1],
126 | kernel_size = (TS, kernel[1], kernel[1]),
127 | activation = 'relu',
128 | padding = 'same',
129 | data_format = 'channels_last')(merge2)
130 | conv2 = Conv3D(filters = nodes[1],
131 | kernel_size = (TS, kernel[1], kernel[1]),
132 | activation = 'relu',
133 | padding = 'same',
134 | data_format = 'channels_last')(conv2)
135 |
136 | ################################################### UP 1st Block ###################################################
137 | up1 = UpSampling3D(size = (1, 2, 2))(conv2)
138 | up1 = Conv3D(filters = nodes[0],
139 | kernel_size = (TS, kernel[0], kernel[0]),
140 | activation = 'relu',
141 | padding = 'same',
142 | kernel_initializer = 'he_normal')(up1)
143 |
144 | merge1 = Concatenate(axis=4)([conv1, up1])
145 |
146 | conv1 = Conv3D(filters = nodes[0],
147 | kernel_size = (TS, kernel[0], kernel[0]),
148 | activation = 'relu',
149 | padding = 'same',
150 | data_format = 'channels_last')(merge1)
151 | conv1 = Conv3D(filters = nodes[0],
152 | kernel_size = (TS, kernel[0], kernel[0]),
153 | activation = 'relu',
154 | padding = 'same',
155 | data_format = 'channels_last')(conv1)
156 |
157 | # last layer is the output
158 | output = conv1
159 |
160 | return output
161 |
162 | def get_model(PS=32, loss = grad_loss, optimizer = 'adam', nodes = [72, 144, 288, 576], residual = False):
163 | '''
164 | This function creates the DCN-architecture (residual = False) or RPN-architecture (residual = True).
165 | :param PS: Patch size
166 | :param loss: loss function (default = grad_loss)
167 | :param optimizer: optimizer (default = 'adam')
168 | :param nodes: different neuron-sizes if needed (default = [72, 144, 288, 576])
169 | :param residual: boolean toggeling between RPN (True) and DCN (False)
170 | :return: Model
171 | '''
172 |
173 | # Input layers
174 | main_input = Input(shape = (3, PS, PS, 1))
175 | time = Input(shape = (3, PS/8, PS/8, 1))
176 | lat = Input(shape = (3, PS, PS, 1))
177 | lon = Input(shape = (3, PS, PS, 1))
178 | height = Input(shape = (3, PS, PS, 1))
179 |
180 | # Load U-Net
181 | unet = uNet(main_input, time, lat, lon, height, nodes = nodes)
182 |
183 | # Define output layer after U-Net
184 | temp_out = Conv3D(filters = 1,
185 | kernel_size = (3, 1, 1),
186 | activation = 'linear',
187 | padding = 'valid',
188 | data_format = "channels_last")(unet)
189 |
190 | # residual layer
191 | if residual:
192 | temp_out = Add()([main_input[:,1,:,:], temp_out])
193 |
194 | # create model with the defined Layers
195 | model = Model(inputs = [main_input, time, lat, lon, height],
196 | outputs = temp_out)
197 |
198 | # compile with defined loss and optimizer
199 | model.compile(loss = loss,
200 | optimizer = optimizer,
201 | metrics = ['mse', 'mae', 'mape'])
202 |
203 | return model
204 |
205 | def main():
206 | model = get_model() # DCN
207 | # model = get_model(residual=True) # RPN
208 | # model.summary()
209 |
210 |
211 | if __name__ == '__main__':
212 | main()
213 |
--------------------------------------------------------------------------------