├── 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 | --------------------------------------------------------------------------------