From e250bce339c3d7bb20739a73ccf20577fb2f73fd Mon Sep 17 00:00:00 2001 From: Valterf Date: Thu, 24 Feb 2022 17:10:12 +0100 Subject: [PATCH 01/17] Converting metnet to pytorch-lightning setup() method is ugly and needs fixing before running on default parameters. I still haven't figured out how to do this properly. We want the train_dataset to have a set of day and val_dataset to have the other days. Furthermore training should be done by sampling random lead_time and random day. However validation should not be random. Link to datafiles in README. --- .bumpversion.cfg | 2 +- .gitignore | 1 + MetNet_lightning.py | 29 +++ README.md | 2 - Scripts/Activate.ps1 | 247 +++++++++++++++++++ Scripts/activate | 69 ++++++ Scripts/activate.bat | 34 +++ Scripts/deactivate.bat | 22 ++ Scripts/pip.exe | Bin 0 -> 106402 bytes Scripts/pip3.10.exe | Bin 0 -> 106402 bytes Scripts/pip3.exe | Bin 0 -> 106402 bytes Scripts/python.exe | Bin 0 -> 229888 bytes Scripts/pythonw.exe | Bin 0 -> 220672 bytes a.txt | 0 data_prep/__init__.py | 1 + data_prep/metnet_dataloader.py | 54 ++++ data_prep/prepare_data_MetNet.py | 409 +++++++++++++++++++++++++++++++ metnet/__init__.py | 3 - metnet/layers/ConditionTime.py | 5 +- metnet/layers/ConvGRU.py | 6 +- metnet/layers/ConvLSTM.py | 10 +- metnet/layers/DownSampler.py | 14 +- metnet/layers/__init__.py | 1 + metnet/models/__init__.py | 2 - metnet/models/metnet.py | 25 +- metnet/models/metnet_pylight.py | 248 +++++++++++++++++++ pyvenv.cfg | 3 + setup.py | 2 +- tests/test_model.py | 14 +- 29 files changed, 1159 insertions(+), 44 deletions(-) create mode 100644 MetNet_lightning.py create mode 100644 Scripts/Activate.ps1 create mode 100644 Scripts/activate create mode 100644 Scripts/activate.bat create mode 100644 Scripts/deactivate.bat create mode 100644 Scripts/pip.exe create mode 100644 Scripts/pip3.10.exe create mode 100644 Scripts/pip3.exe create mode 100644 Scripts/python.exe create mode 100644 Scripts/pythonw.exe create mode 100644 a.txt create mode 100644 data_prep/__init__.py create mode 100644 data_prep/metnet_dataloader.py create mode 100644 data_prep/prepare_data_MetNet.py create mode 100644 metnet/models/metnet_pylight.py create mode 100644 pyvenv.cfg diff --git a/.bumpversion.cfg b/.bumpversion.cfg index eef065f..11269f0 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,7 +1,7 @@ [bumpversion] commit = True tag = True -current_version = 2.0.5 +current_version = 2.0.4 [bumpversion:file:setup.py] search = version="{current_version}" diff --git a/.gitignore b/.gitignore index 863634c..bd6404c 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,4 @@ dmypy.json # Pyre type checker .pyre/ +*.h5 diff --git a/MetNet_lightning.py b/MetNet_lightning.py new file mode 100644 index 0000000..6507205 --- /dev/null +++ b/MetNet_lightning.py @@ -0,0 +1,29 @@ +from metnet.models.metnet_pylight import MetNetPylight +import torch +import torch.nn.functional as F +from data_prep.prepare_data_MetNet import load_data +import pytorch_lightning as pl + + +model = MetNetPylight( + hidden_dim=128, #384 original paper + forecast_steps=6, #240 original paper + input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 + output_channels=51, #512 + input_size=112, # 112 + n_samples = 500, + ) +# MetNet expects original HxW to be 4x the input size +#x = torch.randn((1, 7, 16, 128, 128)) +print(model) + +trainer = pl.Trainer(fast_dev_run= True) +input("train? press enter to continue...") +trainer.fit(model) +'''x = torch.randn((1, 7, 15, 112, 112)) +#x = torch.cat([torch.zeros(1, 7, 10, 32, 32),torch.randn(1,7,8,32,32)], dim=2) +out = model(x) +# MetNet creates predictions for the center 1/4th +y = torch.randn((1, 6, 51, 28, 28)) +print(F.mse_loss(out, y)) +F.mse_loss(out, y).backward()''' diff --git a/README.md b/README.md index f1955b2..7fc7ab1 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,6 @@ pip install -e . Alternatively, you can also install a usually older version through ```pip install metnet``` -Please ensure that you're using Python version 3.9 or above. - ## Data While the exact training data used for both MetNet and MetNet-2 haven't been released, the papers do go into some detail as to the inputs, which were GOES-16 and MRMS precipitation data, as well as the time period covered. We will be making those splits available, as well as a larger dataset that covers a longer time period, with [HuggingFace Datasets](https://huggingface.co/datasets/openclimatefix/goes-mrms)! diff --git a/Scripts/Activate.ps1 b/Scripts/Activate.ps1 new file mode 100644 index 0000000..51fc55c --- /dev/null +++ b/Scripts/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virutal environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/Scripts/activate b/Scripts/activate new file mode 100644 index 0000000..41bfb5c --- /dev/null +++ b/Scripts/activate @@ -0,0 +1,69 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="C:\Users\valte\Desktop\Teoretisk Fysik\SMHI master\Network\metnet-main" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/Scripts:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1="(metnet-main) ${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT="(metnet-main) " + export VIRTUAL_ENV_PROMPT +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null +fi diff --git a/Scripts/activate.bat b/Scripts/activate.bat new file mode 100644 index 0000000..1b32d26 --- /dev/null +++ b/Scripts/activate.bat @@ -0,0 +1,34 @@ +@echo off + +rem This file is UTF-8 encoded, so we need to update the current code page while executing it +for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do ( + set _OLD_CODEPAGE=%%a +) +if defined _OLD_CODEPAGE ( + "%SystemRoot%\System32\chcp.com" 65001 > nul +) + +set VIRTUAL_ENV=C:\Users\valte\Desktop\Teoretisk Fysik\SMHI master\Network\metnet-main + +if not defined PROMPT set PROMPT=$P$G + +if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT% +if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME% + +set _OLD_VIRTUAL_PROMPT=%PROMPT% +set PROMPT=(metnet-main) %PROMPT% + +if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% +set PYTHONHOME= + +if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH% +if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH% + +set PATH=%VIRTUAL_ENV%\Scripts;%PATH% +set VIRTUAL_ENV_PROMPT=(metnet-main) + +:END +if defined _OLD_CODEPAGE ( + "%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul + set _OLD_CODEPAGE= +) diff --git a/Scripts/deactivate.bat b/Scripts/deactivate.bat new file mode 100644 index 0000000..62a39a7 --- /dev/null +++ b/Scripts/deactivate.bat @@ -0,0 +1,22 @@ +@echo off + +if defined _OLD_VIRTUAL_PROMPT ( + set "PROMPT=%_OLD_VIRTUAL_PROMPT%" +) +set _OLD_VIRTUAL_PROMPT= + +if defined _OLD_VIRTUAL_PYTHONHOME ( + set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%" + set _OLD_VIRTUAL_PYTHONHOME= +) + +if defined _OLD_VIRTUAL_PATH ( + set "PATH=%_OLD_VIRTUAL_PATH%" +) + +set _OLD_VIRTUAL_PATH= + +set VIRTUAL_ENV= +set VIRTUAL_ENV_PROMPT= + +:END diff --git a/Scripts/pip.exe b/Scripts/pip.exe new file mode 100644 index 0000000000000000000000000000000000000000..a21b20dc724c07ec822546b569f9abae13c12813 GIT binary patch literal 106402 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

|L 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�_E7=1!BZQi;?j>R$V~89~IwJLeG}$sx;p6 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%0D*fQyP2pV53F{Xge?4v+fPdZb2~NrZYukIQybt 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

{)N%ceI!HM9KD7~gCbd+eUaD#GP{)Z$NrrW^ zc^RYl1W&6c+Pv}enVfZEy5l=}g?{$gE06R>SMLV%p}iT;*=tt1REtU1Ocu4}br+M~ zwL7ebZI5~l)xNw*8YLr}-Y^{4vU ze0pK7wt*?O{M7v&OX3Ff94zm8D0NHbqz@DipKE4$hErA^6qa1f&!70n&?o5NqAPP& ze0;n2kjrKT)6y$OVdbSauND@|=u~;W$uK?ghvOJ_mUXk^>hne8b|{n|*v#_hpE_a; z(C|RMmf@aWdbaP?dJFAw`KXV)SHs(@1Kc$>9?uEdbY#c=Y!5NsQ!R1Ug#JPq@%2Y$ z+@>?KLi|gsbwzzG&2mpU?hO7Q1r;|!-uS^#-5p!TJ{c;SVmSMb&Jh3e<15AD^`-`n z7jszMEc(G`#eSJW6fF|4SmlsKk7bJ;==nFjih7Qj?7%Fo9)zdnxap_>NjObuoKp~S1#?Ds=_ZSDDd*L&>QH2Lj_iX2wc#3u=R(hs=JnKw6t$ML>Nu3MF~;Iow>T74d?^9wofE?-@o z>$YPaO09*Zt5aejC|foE-@Y;HBfJzkyH3By1@FT!o38$VsDe#KIrfg zldsM@`N=$|)N_FStl14>(J6Y>IVX=gyeh4IIjY{3?@&Zd=yeBr_H|3Iq*5tA(Eyug zmcw6`z1TO1y)@)OlmD_we*6;+?$|!uttKkH{iR)#iEitaOzYLP*PHGFQ@$S}G1;`(Bf5 zn#@?A0@dhzJ<5wfZYtz&>>Q!cE?~Jh%P!fIA zzszC!lv^X%j-I=;jV5a4tjQP?IDVJF`?qUyk|#QZGj?e=Y7?^5dsQ!X>W`Z8-tfk< z>0^?;Z5ZO04EmcSY@3i_>XCzPzD>F8V`cBqG+dvLa$UDB>*ua*f`-@oxS*`Rl`0H|LJBBMh&NUV=V_S?1YGS&o-yNNcNI*6>4a`)qdUIYvBQ-f(m?xm`E+h~vK$Iw4p%n@J~1=eSP`5j`VgZ=;Oh`l0JRw(j;=PxPs@ zXwtnGC#It9u?jNPvU=51*KYliIUDLj3>Hroa?4>{RUBwPH+s~L$I**}1qL08l~jve zofNEF7Bw!YSg(#Y$irP^(E_{0{@H!Ym5HO)xi6Nli4c<7ZQ4X{R&2&Iz{aMgEkOe6 z<}XG#&O3TbRj6>txe+mCcMnF#o_fDby`B9+Jo7O7&2}fGmn=E0U#5GeNF=OzB|&_2 zg2n8bxYsTUyAl-wZVtMsSvtKiq?g8+)^qH1y*nlmqA5*xOayKW5=`Y4=B;0_E@i3u zwgqPPf2^Lga_sPalR_80T$NIcw3^1O&M-<-ow4y(Dc z|6oJ?!6ZiwDg6Nv@`WBg@;$1X`m=qVv2uDh(qNL>`l0Ncl=v&_>K)6D=kOeulO;dN z&@N(bUb5<&9x!7wD@h%drBe{CqOY47GC{e;)BOHJScR=E8GT^jywFu4b}lzMHHB3Q z{GRmJAV8bo;Me9Fg15=t(dBKl~p{n0Se~|ixrttc7kQF zOQUcy8-;G)@J#U8o^{j9vxi-eFWWzKfnoIcqQ`135}4k3{i&WKZ-)wp*oaA)->>_~ za=yNB&S_yuSVm2po_#yT=6Jbp&*`QWk1k&9F|xqI!`-9ybpE$%gMReMCQ7Y>dxCoN zv57Ku2YABoZjn$~XVzTpVjoqXrM-J{?X)tttqIr0$l2ILjca{#aLcp!8JDl!aW5Mf z(b#fG>vW&RH+9SQ?k&kw2^e#H^>{Vydc(}x++yR>w18rRoGtF3=1}iEX;5{(eqsI4 z8N5nS_(`EIdz(-q1Y zZ#=|wRZC>!385&zpa)J#muKH~n|I)$uuc7o^&mrrQI&e(s+*JFn;NN@=2Z&wGAJGk zR#s@MImn;(ovQOeTJ7$z1|&9=-5EOxZFUYPw!E;gEFxviouX2pvrNzs-`SLPk?9lUGjqRT~&)7?w^ zB`6!6T1zasbYtWN%gs%b1B=fv-`_NvH(%|g;0$g4#Bg!8LPGfFXIJH@AEjdwr!j~H z{r#mHU_bvzXy~lTnnl)AW)nM)7KHa6T!^9k5TDh%fJNEYk=TckBcrNIi62K z!Nf~7hmW8IYFNs@+h?$Ez~#|NstfdHQoJ*Quie`t#G|-z)moPuDL1H=wzD$mp8miq zWlr+lN^l5G;lF)y{J4C5=a!pUj|TEd^%v89O-vQ2DpUM8c4b=6#+u-yvzH}!!=9%I z!2VU)l$lB|{N*Ut;#+Ow63iA%)e~R1c>R{AvI425ebveu2VV*)>4-dy6-EUulo@eybbxv53*%!RxGlVOE)f4{`9=>r}2*m z*?xK@O+`ISVa48AGD;*>P-o3Uv7Fuodx_iT$p$=f0+APq!9)pfDnD(t_90apiTasF zbGF#fVQT(zAQdbdSpHOAj; zJ@MBUJa3#>cv#ljbhwYQk=}aBrYX{Ugq5Ra)?aad1Pz)}a?#j9ej0PlhMqHQ3B!AF z*50#VBS4+Gnvt5$E%gMRA949Qf9|g13SgtX#)KXdr;Ux3e$r2jnum@oMED5n>1JJR844#n*SK) zLTwQ_HJLng`_Yp#B9fM#_a2_R{BdVf3+$7QP-EVB@@CG>6wmOSwC7?<1x}XphaKHj z7s>O-(i=_sVF_sx!TN*iMkEEkYQ}8!5ZBJ=bGO0ulktGd8y&)(M-sQ#_xT$wOiq^9 zpnw%HleROzSTlk0b{VsOPRr~o+Hr*A#*jG~DhnLNDi+n-^k@uWK63CZrXC++Me#Nyrk4i1&DSdVq@Ff|?}^fsS?3-3ta29}>Ul(%7PZw`oTut=DCPBmfT28jtFuO_ zg*o>ylNOFG8I|ESIw@0$P$ur~<~cQg`xHM%iJp7ydE`C_lxLmRZyf(>w8zQlR%ub{ z$lj}nf-BkrWe#U|c-iyywOhL~^afu(~#!YI(lkebAlrje@%GSrz984VEQ%;|&BWcxWCC6=<4H!K-J(O6d=# zIXIOK%}l%GG%;yO!$P&Rz7{Q_jw@!KFBOnJA>;P8S+|w8mKa?x{BYjMBg`$mSImjk zvzo^2mGI=%f*W#E)ckc-dS8U_ipjI{qC|-NE5d~WAT@uyx{?)n>Gn0BY3Csblz>>X z`Hm?cPg8UB!k^4gU=7)}U{rr2{vw;+FwLBFM?6t;f|qIus{BIM@(VmB zvw4%U1eHuF_UHD^^D^JMc+|ZQ-dGQ@enH5 zL@cN{5=(7uqUuKQSdCb|TJ!vibZ5mZ|Ff$Sj#cqIyC#&_;6bUk3EF8aU^-XVSTahh zXJ4?)q?$hz}ql}c-;~>tI#&``Y{Iw1)}599Bma^vG|p0$tSte3p|zy)T8@&`V{l^ z3ks^bnI=%MCPw|*%ghn;!4|S#@+TYhanjT73dJ};AXkr)6OkpFQtGrIFL8IXxoUTX z-OZMUdZ}LT896sbJuSaxC$Pr!i1|yw0wqG;)m*Qd?$*!w9*?te&!xA=PK)T%z}DW- zXPqTaMlJQlT7UZeX1S{OcZNqAi0M9FxLzIGqNSK7EA%bss~$8+#Ic8T=n%2|p3ap5 z+nOAzZ7KdD%EFpOu8zFDUVUQt6jzS%j#Q|Oyhv2p658&@={7z330~<{G=A2ekkp@TM$Ip^(+_y%LY9wR9}L%ha&KXPsg&R zc#EI~|ENh?hw24G6dqa|XPthumlX^Zy#J87R$rYt)bqqDmDb6J>qkG5&K4=qBc_eD zSBMr{8zIP84A}??-5$C4hFByov4xn+6Y|(;%#3mNr33wzZryTBfha3qDxk9<(|A19 zgwWD+78kyY&R#_#Csl4PbAD+{`q#CGs3ILGu%8(PfC|7vNWy8{Xh7>NC4;q zcmiYsGyrf@4SYxifG8J82jFYxk$*dZ-QWkbC;;3C_>t5C4~GKG=_cEM{nvmyJ^&e_ zUT_k`g+dU!3PBt!v^5W*71;pud@su=AzjR@(%5fc#Kjii6Y3kWU&Jb<$g5S-P76agr}hXo)5T-rNcwctI7DaK8! z|Mi5oAqWWH!y;_;7NLzV5rT845ct(1HIRXo04l%-exRT?rXuA7=($%2X#s2oK5WY8$ilidS-|=L&L<<=1D1s) zz=xBd|F{Wa|FyS?qDIcQ|0DE^Vr)}DhPQ+4ZYxAc8+d?aK_j0d3%EV3#qFU@7I1su z>PCo_h#*cPf>@D=s~i6p=m(O805I(m04NpcH+%#74|t#pJkY_i(AFNXENp-_fo0)) zbR(C3$QI|1LHM7R1;7FK?>Ybx0HC$LOizLSoluvU2l~JREDKxPWC6DaEDKmSYQq)I z<3k$2i;VyqKql5g+kkWX&?;ybc$@&SCA&(s@MR1DMCL*rPYwF*tby`y(1-f~-6l-` zZjfVR;K2?6gEm>f?E%XIZV!5J{Y5Dp@@YF-^=tCs5NPzPKwn=UWo2cNXhUpI1Tj(( z#F9nKee)2+u|-~Hvk}BpMG#XI!I_C@H|R7I-~;9XR~B%4_)*=6`$;@j!2JQ%3#&jb z@K_Sgf#4>euPNtz^Q&mb`7oJGvOI`?iXe6_g0naf#Oy_^y>k(qiHRVdFN$UzB4$jEWHEK+ZxEcf zf&y(75X5N)9xOl*v$>rI5J&in`sd04zM8hl!uROL?;SU;2VL39VL%I9hIi({ud06r zgF%*(7vqbrQx$;+lrMQ;#o<8=lZvun{MV80j-PRR_%q$8ng9LcMg`Cd$#onC!2g}U zi+%_gKo$b=p%I)1hpyW%L~!;F3WRom<-ixq0q_7~X`^bW35ZACEe|^C#*VV^S9K%A zjpio5-$g&fbtcPzc;N_Q!=M`uitRkWx`5;X)`tK!WrYahR(DH3R~~+qZiLw2+?4w} z=!ZD(Wd0DZ8r?p!6x}+s1bDEBnkGw0tZ``PzPojtaReVW3fzLE2$G<-__QCH$+#&?$ zQKAPYR7f7&Ii>`1z~Mm%mVel8zab}LC9 zoCG=evvK1dXe(_g`FGKuoehI8Nb%Q<5S-19?m8+Xk_Vst z9d!YggSyHR`aJeG{oVA>x;zWTzlkS# z@HXWJdgQvgod>_7UxXMN?foLRPw0w%%m>^aXwW~l)%{aIivLIG|Ev8W9t*=c(evtMqf<+p%K@g791D z&6_vm*nnh6+rQ?<2K1q-gae?zAa>%HP*IG^3ew2=;Hk&zV(tGZ{oJuZ z^wk}xBfe)PGV+#GU@_|R32oF~LyHAF?}cTx5|cNE5* zCw%98F(K$A$vcQY{LAU@EKk4co<^gQI2R@akYhwVU+!+M^sV_~IE)XPK7ByAuhZ4l z{hy&9&l7OpjMMwPc$DI^pPVmo^UPHWbSOq=1dj;V!1WvB5u3yoP>+0Xw z|8ZpqPEAD#2?-?r_+NA5M^yDD1Lh6)(aRVXdJ%CAWrkls8F$W;{X%ZE4|@A75>>u^ zj#^tdn|jsN)#%{CgI&`8Z>OKj2Z-T{*lae+$jCq+K7dikpCmZ%6eT4kq4Vd@BP>VU ze7d^+o9n+TeDQY>K?~tAVpo9Y72Unp)%QP3|6lF(8`Zo2y zZ~gP{q_wNE{-ggt{r8{#`*Z!DXicF1B}ldtM9(i%Q1-?7B;F|pMag^o9sZnnfCnX0 zdy|0a`ZN9azeGF6wKM&g?!pjL67c6_`(Ef>%o!5!=l^T;cdoDh-|7EZ`TsNd`0w}s zIPf0_{_k)AFEKX|_T)-)0U<$Nsf556R|wrs-gmmTw%v2D4Zsz9c#sNLoB?JMUlsoU z`O4FF&%F|D_rL4PEvNI9i?xx@`i2diW&ouTM=lV-ypK*MC1ojhO3ZIu}ar^@xaBN|rrjXbd8UD>TQ2sj> z*sN*ffA;;xKF?ggZ+sR6_G?suYX!81k3cK-&0D(Kn|ls{0eo))G-7$e{-td`*MA%SfGRJ5GQa@Y_vz<6)kv?+VViWPA` z0^pPI2K%H$UN$Cuak2j)>0$c7HImZ11 z1)u=x1`~30?z=^c7NPR;a`Y}Ip7f*S_!Dz{C@~FLkJu#tx%hK! zp0OVU*Qfq_+BVj+*k>5m!%^_PZc80Qu9G&xKZ^ghZQIDKQm!8-eV?!|Xn+mJ_lphn zkbd_nx&mW`j`-uxTpNCTHo%XzZDpY46Q^yQ>nKBih5wF@jwJpMFKqklw*&rbTwm6Q zSItP@z0UY^@3Aj)cWrx9K1a`X)o-WPlf|b0EdJORzqO@_^u;6n@W7@J`ypZen58=zOgUpZ)fzBv4z!}B(oj}pPPAD3#=?|Ewu%-qbgE0s8d+e@l`*HaH;b-yp z^zI`L}a)l03&wzwLa3jy_A+U*}Q>-$l>8im0gQC9(tA>aK0y z<%~}YeinZR2M4kq%=QMoQtJt=B62@zaa$h&%q=x&M+^mq(a}?}2mI z&!JYZ5if}LMllyQx7*!wXaIl2F7zrsvxMt+!|^rX_+gUm8EEK?zdh(Atb-xp);sCX z@b7NS)7|&R#>VL3!-oi;vC#CX9_+{KNT_>Xh^k9+QFSqf98`s&C_p5OaD*AFOxVc9{iw)x4Ev9D8^$K>A&(>Cw-=Yqm#ILbtb|-D?f$* zcj}++Y*>|7D&x8a+jDEuH?z9vIeDLc%a!y$`D|~_IkyD+<#yDo^;M-mh5z^JAFfSl zS6A$BpBcjG8>-&I03OoQo6ev8N7~Mu;EwgUdezrFMd{ zT~-9CS0i25qBxJ8U-bg_5NC~-aMv?a^r)^cg~(Y+m1WN;qPw0T)J&+Y|!i1(CI^Js3}9`1u5W*a}T{r z2uAtw0jMzLHY(1J1|6FRGSh-8Dk>26bLYO#z5Y}9S0?-&ZSk*^p}JD#>9^)Doo(L! ziTkHcBT>G;4oW*FhBD7elb`YR>2YzCdUznhzjrtP`X})HR+*R%{5!s4+&>hhlGpzg z{C|}H-O=^m_y2LAI}X5K^5>Jouvdv~SFp_owp+n_!Z-%YHn3MQe2I_^-~}oESC;ACeOvjdv175GAS#meJ=;^ zg<$&yyf=vVH?eJ%Mt;|_c^CF{AzA-O`2__9=<#JE@?2(oE;T-r`f198FUrR_;5{X5 z&w}@gyOYiRoHh~nz2)PzrxrN7Bkb5Ra-AHnZQ}jY$Sb=M-k-+hb_84>c#jeDqAS^4 z2XzAJWv+A>KZ+gk0z5;2;lY(o@lsckj@Hs)nz?7jQELJh-x{na{CZ zz_B?%20(Lj(x278va&L2Y5GL2lj6N6yqAp6x54M6<27n*n}g4Q#e1*#>{q1GY=SZGwvf-Z#bj zb$H)8xVn{;O>7^8Ws?f^1F3zl?$7AQ<%fobqUU!`lV>90^KS5-I=0Qg=Og3up>ezb za_Bo6reExad=alrdF z_{@H8yTtq8c<&OQLyXT_AsOE4=@S&tS!8Q*+6Gzzq|VH>ias_%fkEfc-=oU>`a&KGU(7sSo<&k zZu+@=`kw2ZJ9p6QC;sU1IdyU`4xj&o&yVM}OB_e!{i}3z=FFMjQa;v8crWrL*ecY$ ze?{(ryn7yv-WR08{cDs9=aqHd^Z1$i{}xa1UPi~Wf4Y12&&uzrp1I%siuL?IE5AFu zf7SQ@mH%!1^Q-vWT|WOt{(oz|>t}vTKlulGdP1_@$2!Yx7Rqv)`IY|vnKHiTUF_$J zp}X=s_ecM~+`~vr0Y;$`B(%b{8GP}o2%bn_z;^(Cj_LO;<`HoEJ^J4mNH=HHcGj);gr_fKyk=eBq*^DsN5 zJ%2oA$M)!WjRDUk@mdRxv540=@Vp-C`_-T2pPiXb+UH<d#y%RejQDLD?tb0QoogN&bLw-|+<+Kg^GZ$`mRn^3?}7$dTd zp$qSuzo`362s`j+`QveB>_u&IjEUzw*nSqz`|x}RkFoKb56?5OoovLT;4kVP;xlJ- z&VPoJ9O?*`S1r-2I2JjU#`7vXpTP55Y;TX}5_n#T=Tq1YG&1mXd)=JD(u2^U&iP{- zht6>J+G+GMF$CpCTt-=U&Z4v%PADbF5hVs3Lw*M+WEptA6cyx(f@=^8!|P|S-(m^s z(>edHWQhA69)sdI7U7OyuX}tYIXA>>66Ug}|$P_sThc%(p+wKV4e10qc&b;Y-|132gCDC`Gh*N50{0*+$-f`W6 z3SOimJnsKi{#;&kcU`-7Ey6M1lH(tt%)|(k@#H>AiGPUj8W%p_6~Fhb{JY}G!X-a; zcI3D6@5~Q;uej*P_Nl*>e|LQOk?;Sni3N0e&m_7V?!@+S{FgUdxw7H-S2re z*}k_kH~-&xMO7jMsuCbj0RWYUph98a2K>zT0q8dZkHhfxkR$+i97gE@H-90)dbb*^ zbyH#A0mBdNIziv3gZo$9@8NM3_y$J)NFLBvN8)}O#sXCk7d#Ti0g*5^!^6w=)Yfje z`h~oDA5gZyec8VJ%2eFn<1qyux8U&v-q(S#MC$xDTJZaw+G!;2uW-M9mhDoA-^YOa zay-V;0($WH3jAoP7>Tal$9_V1O&9mu*Ie|9&be8np1e$bW?tMv-MwG% z&}CIMUK7QA0bUEm>!5fI^z31cTF9dYugL>%O*@v)UDLqVwQCmDh8$fH1#!2k@c03* z?_(bZ+#l{w&+|9St-gle#iYyEmjjD^|3^y%F)!(=%mHW(}mCt>yyK7hP zclT^p-*bLLtj{E-4ToJ68WA9uHRLJNHfcK*6XZQ$7$+kN|g6Khr!Bi3d76*HXJqc0JEcni_ zCqZyF6|v@Og!cv7@{}Z`6bX2OlcyxX9!n6t+VX@ucweY3Pf5ZNW}Us;@{}YDLHA)i z*?cqL{{~_$p$-3+5NqJmGD4MD1^3I~|5Esj)BPTmV8Flw`SB6;d~WSm1@c#nJO1^{ z#1A~=f&458Gs1}2MHoPCMuah;2RZK|^oiZ@cp)!-M!NlP2Ck z*b2|@2W+;&ogP_;8r)kDhVc6?;K^FJ>cjm$c)|>xkc5(U6PEDXcKF5uO2yCNGz)mg z(~ietc-EZICGj);;<=9b@L8= zxB~?p^Oq!6z^@ovU7!f(4C@6vN8$YyaA!cii8)~kG-FO0z*83R%-7#Z569ht67cP5R= zW(G6En32plW-2qAS->n|Rxulx1WS-5$`WHqvZPqjEIAg1rOZ-esk3xghAdN-6^qJp zWVy23Su_@#70e1_MY7^psjO^P0jq>n#UflmYw-|-DUC{VrMW}c7B(%I7DkJt#nG~9 z1+)@c6|I3rcnW%odP;f9c`AFVd8&Krcp7>Zc$IYElNepHjaL-9GF^?XPLHI=(NpQ! z^a6Sby^7vIC%gr{MZLwmCB3D*rM>06Dc;K7YToMJI^KrfrruWGRBuObS8sQ3nm5}! z*gMQSvP(Wo{w6L{e3gCGeARt*d<}g~eXV?{zK*`GzV5y>U$$?sZe~w=6UTm*0QhG{2dIafWzz;d#g*wQN708V{olOq|o@4_* z8t8(+2`S)$GH}2UsCNX)*+6w1NLC3*l^{rz6iAaYNRlB)kt0YD8>A-=B&P(VMi3-M z3Zz9DB*hS<#1SNf4bl+@l2HOuAqWy71=64ll3)l@;0O}HhT4yVnlE8Bumss+K!F@v znXS$?WLvQv+3pacB#a%$&Ssae8`y$AVm?wnaz4sF>OO`(Rz8kC?mldvFrPS|Y@ZUJ z1|LCRF<&WPIpCT)aLfw0Xh~yz0p}h;z#As49Ihry}on}b0 zqB%kvV1xXp`WN_D`4a)60g?gI0h9o>0G$BS0BV4104*RmATl5|pdg?sfCv-~lnj&( zqy(x3>I9kwQUhHBX@S9kk%6g!1%Xw8M388ZWRP?aB}gquC&)C28sr*83knX33`z|u z2&xJK1X6hjV~`($CQ6f}Nz*7aHJT2M(;8{e5+iA;&GsF$Rdv=_xo%}dA2)Qjrn>P7Pk_KNgM^(ugNN`Nkt1YJgf zwy6WUj7oQ<)9As_N>ibQRzd3&g_bD|tx^qIq$#vUS7?dB&YlaULUg#GILdTQs8RuEzDd;8Tr4GEa0`7$Y=fvnbAp52u{Zx>DS7-q= evJFH+D@cWQPyj8V$_JOn(HW4{f9Zde1OE?;Tx?qa literal 0 HcmV?d00001 diff --git a/Scripts/pythonw.exe b/Scripts/pythonw.exe new file mode 100644 index 0000000000000000000000000000000000000000..3b5c0bd9c1425c9f5463a751266134596231e369 GIT binary patch literal 220672 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 Lt8eLF%7Om_M`u|h literal 0 HcmV?d00001 diff --git a/a.txt b/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/data_prep/__init__.py b/data_prep/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data_prep/__init__.py @@ -0,0 +1 @@ + diff --git a/data_prep/metnet_dataloader.py b/data_prep/metnet_dataloader.py new file mode 100644 index 0000000..6561c1b --- /dev/null +++ b/data_prep/metnet_dataloader.py @@ -0,0 +1,54 @@ +import torch +from .prepare_data_MetNet import load_data +from torch.utils.data import Dataset, DataLoader +import math +import numpy as np + +class MetNetDataset(Dataset): + def __init__(self,X,Y): + """ + Input: + file_name: path to hdf5-file. + N: time-window in 5minut increments. N=1000 means 5000 minutes of data + lead_times: Lead times + Output: + X: numpy array shape (None, time, channels, width, height), + Y: numpy array shape (None, lead_times, channels, width, height) + """ + + + + self.x = X + self.y = Y + + self.n_samples=X.shape[0] + + def __getitem__(self, index): + # allows indexing dataset[0] + return self.x[index], self.y[index] + def __len__(self): + # Will allow len(data) + return self.n_samples + +if __name__=="__main__": + dataset = MetNetDataset("combination_all_pn157.h5", N = 1000) + dataloader = DataLoader(dataset=dataset,batch_size=4,shuffle=True) + + eps = 2 + tot_samps = len(dataset) + n_iterations = math.ceil(tot_samps/4) + print(tot_samps, n_iterations) + for epoch in range(eps): + for i, (inputs,labels) in enumerate(dataloader): + if (i+1)%5 == 0: + print(f"epoch {epoch}/{eps}, step {i+1}/{n_iterations}, input {inputs.shape}") + + + + print("DONE NOW") + input() + dataiter = iter(dataloader) + + data = dataiter.next() + features, labels = data + print(features.shape, labels.shape) diff --git a/data_prep/prepare_data_MetNet.py b/data_prep/prepare_data_MetNet.py new file mode 100644 index 0000000..1e137cd --- /dev/null +++ b/data_prep/prepare_data_MetNet.py @@ -0,0 +1,409 @@ +import numpy as np +import h5py as h5 +import matplotlib +import matplotlib.pyplot as plt +import matplotlib.animation as animation +from pathlib import Path +import sys, os +from datetime import datetime, timedelta,date + +''' +Output: 5D tensor of shape (N_samples, time_steps, channels, width, height) = (None, 7, 75, 112, 112) + +''' + + +def space_to_depth(x, block_size): + x = np.asarray(x) + batch, height, width, depth = x.shape + reduced_height = height // block_size + reduced_width = width // block_size + y = x.reshape(batch, reduced_height, block_size, + reduced_width, block_size, depth) + z = np.swapaxes(y, 2, 3).reshape(batch, reduced_height, reduced_width, -1) + return z + +def date_assertion(dates,expected_delta = 5): + for date1,date2 in zip(dates[0:-1],dates[1:]): + list1 = date1.split("_") + list1 = list1[0:3] + list1[3].split(":") + #print(date1, date2) + y1, m1, d1, hour1, minute1 = [int(a) for a in list1] + datetime1 = datetime(y1, m1, d1, hour=hour1, minute=minute1) + list2 = date2.split("_") + list2 = list2[0:3] + list2[3].split(":") + y2, m2, d2, hour2, minute2 = [int(a) for a in list2] + + datetime2 = datetime(y2, m2, d2, hour=hour2, minute=minute2) + delta = datetime2-datetime1 + minutes = delta.total_seconds()/60 + + #print(datetime1) + #print(datetime2) + #print("DELTA ", minutes, "delta ", expected_delta) + assert int(minutes) == expected_delta + +def h5_iterator(h5_file,maxN = 100,spaced = 1): + """Iterates through the desired datafile and returns index, array and datetime""" + + months = {"01":"January", "02":"February", "03":"March", + "04":"April", "05":"May", "06":"June", + "07":"July", "08":"August", "09":"September", + "10":"October", "11":"November", "12":"December"} + with h5.File(h5_file,"r") as f: + + keys = list(f["data/pn157"].keys()) + for i,name in enumerate(keys): + + if i%spaced!=0: + continue + j = i//spaced + obj = f["data/pn157/"+name] + + #print(name, obj) + if maxN: + if i//spaced>=maxN: break + #print(name) + typ, date = name.split("-") + y, m, d, t = date.split("_") + #title = f"Year: {y} month: {months[m]} day: {d} time: {t}" + array = np.array(obj) #flip array + + + #print(array.shape) + yield j, array, date + +def down_sampler(data, rate = 2): + """Spatial downsampling with vertical and horizontal downsampling rate = rate.""" + + down_sampled=data[:,0::rate,0::rate] + + return down_sampled + + + +def temporal_concatenation(data,dates,targets,target_dates,concat = 7, overlap = 0,spaced = 3,lead_times = 60): + """Takes the spatial 2D arrays and concatenates temporal aspect to 3D-vector (T-120min, T-105min, ..., T-0min) + concat = number of frames to encode in temporal dimension + overlap = how many of the spatial arrays are allowed to overlap in another datasample""" + n,x_size,y_size,channels = data.shape + n_y,x_y,y_y = targets.shape + + seq_length = spaced*concat + lead_times #5 minute increments + x_limit = n - seq_length//spaced + #concecutive time + X = [] + X_dates=[] + Y = [] + Y_dates = [] + for i,j in zip(range(0,x_limit,concat-overlap),range(concat*spaced-2,n_y,(concat-overlap)*spaced)): + + if (i+1)%1000==0: + print(f"\nTemporal concatenated samples: ",i+1) + temp_input = data[i:i+concat,:,:] + temp_target = targets[j:j+lead_times,:,:] + temp_dates = dates[i:i+concat] + temp_dates_target = target_dates[j:j+lead_times] + try: + date_assertion(temp_dates,expected_delta = 5*spaced) + date_assertion(temp_dates_target,expected_delta = 5) + fiver = [temp_dates[-1],temp_dates_target[0]] #final X date and first Y date should be 5 spaced minutes + date_assertion(fiver,expected_delta = 5) + except AssertionError: + print(f"Warning, dates are not alligned! Skipping: {i}:{i+seq_length}") + print(temp_dates) + print(temp_dates_target) + input() + continue + X.append(temp_input) + X_dates.append(temp_dates) + Y.append(temp_target) + Y_dates.append(temp_dates_target) + X = np.array(X) + Y = np.array(Y) + + return X,Y,X_dates,Y_dates + +def extract_centercrop(data,factor_smaller=2): + + x0 = 0 + y0 = 0 + x1 = data.shape[2] + y1 = data.shape[1] + + try: + assert x1 == y1 + except AssertionError: + print(f"\nWarning: centercrop shapes ({x1}, {y1}) are not the same.") + centercrop_x_lim = slice(x0+x1//(2*factor_smaller),x1-x1//(2*factor_smaller)) + centercrop_y_lim = slice(y0+y1//(2*factor_smaller),y1-y1//(2*factor_smaller)) + + return data[:,centercrop_y_lim,centercrop_x_lim] + +def datetime_encoder(data,dates,plotter = False): + data_shape = data.shape + data_type = data.dtype + year_days = [] + day_minutes=[] + for i,date_string in enumerate(dates): + if (i+1)%1000==0: + print("Dates loaded for encoding: ",i+1) + list1 = date_string.split("_") + list1 = list1[0:3] + list1[3].split(":") + + year,month,day, hour, minute = [int(a) for a in list1] + date_object = date(year,month,day) + day_of_the_year = date_object.timetuple().tm_yday + minute_of_the_day = hour*60 + minute + year_days.append(day_of_the_year) + day_minutes.append(minute_of_the_day) + year_days = np.array(year_days) + day_minutes = np.array(day_minutes) + year_days = np.repeat(year_days[:,np.newaxis],data_shape[1],axis=1) + year_days = np.repeat(year_days[:,:,np.newaxis],data_shape[2],axis=2) + day_minutes = np.repeat(day_minutes[:,np.newaxis],data_shape[1],axis=1) + day_minutes = np.repeat(day_minutes[:,:,np.newaxis],data_shape[2],axis=2) + + + + periodicals = [np.sin(2*np.pi*year_days/365,dtype =data_type), + np.cos(2*np.pi*year_days/365,dtype =data_type), + np.sin(2*np.pi*day_minutes/(60*24),dtype =data_type), + np.cos(2*np.pi*day_minutes/(60*24),dtype =data_type)] + periodicals = [np.expand_dims(a, axis=3) for a in periodicals] + date_array = np.concatenate(periodicals,axis=3) + + try: + assert date_array.shape[0:2] == data_shape[0:2] + except AssertionError: + print("Datetime dimensions seem wrong!") + raise + '''for i,(day,min) in enumerate(zip(year_days,day_minutes)): + date_array[i,:,:,0] = np.sin(2*np.pi*day/365,dtype =data_type) + date_array[i,:,:,1] = np.cos(2*np.pi*day/365,dtype =data_type) + date_array[i,:,:,2] = np.sin(2*np.pi*min/(60*24),dtype =data_type) + date_array[i,:,:,3] = np.cos(2*np.pi*min/(60*24),dtype =data_type)''' + if plotter: + fig,ax = plt.subplots(1,2) + ax[0].scatter(date_array[:,0,0,0],date_array[:,0,0,1]) + fig.suptitle(f"Periodical year and days from {dates[0]} to {dates[-1]}") + ax[1].scatter(date_array[:,0,0,2],date_array[:,0,0,3]) + ax[0].set_title("Year") + ax[1].set_title("Day") + + plt.show() + data = np.concatenate((data,date_array),axis=3) + return data + +def longlatencoding(data): + print(f"\nExtracting longitude, latitude and elevation data ...", end="") + + with h5.File("lonlatelev.h5","r") as FF: + lonlatelev = FF["lonlatelev"] + lonlatelev = np.array(lonlatelev)[:112,:112,:] + + lon = lonlatelev[:,:,0] + lat = lonlatelev[:,:,1] + + elev = lonlatelev[:,:,2] + + + lon_mean, lon_std = np.mean(lon), np.std(lon) + lat_mean, lat_std = np.mean(lat), np.std(lat) + elev_mean, elev_std = np.mean(elev), np.std(elev) + + lon = (lon-lon_mean)/lon_std + lat = (lat-lat_mean)/lat_std + elev = (elev-elev_mean)/elev_std + elev = np.log(elev-np.min(elev)+0.1) + elev /= np.max(elev) + lon = np.tanh(lon) + lat = np.tanh(lat) + elev = np.tanh(elev) + print("MINMAX lon: ", np.min(lon), np.max(lon)) + print("MINMAX lat: ", np.min(lat), np.max(lat)) + print("MINMAX elev: ", np.min(elev), np.max(elev)) + + lonlatelev[:,:,0] = lon + lonlatelev[:,:,1] = lat + lonlatelev[:,:,2] = elev + + lonlatelev = np.expand_dims(lonlatelev, axis=0) + lonlatelev = np.repeat(lonlatelev,data.shape[0],axis=0) + + print(f"\ndone! it has shape {lonlatelev.shape}") + + return np.concatenate((data,lonlatelev), axis=3) + + + +def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True): + #15 minutes between datapoints is default --> spaced = 3 + snapshots = [] + dates = [] + all_snapshots = [] + Y_dates = [] + + + + + if not printer: + sys.stdout = open(os.devnull, 'w') + for i, array,date in h5_iterator(h5_path, N): + if (i+1)%1000==0: + print("Loaded samples: ",i+1) + + if i%spaced==0: + snapshots.append(array) + dates.append(date) + all_snapshots.append(array) + Y_dates.append(date) + print("Done loading samples! \n") + + data = np.array(snapshots) + all_data = np.array(all_snapshots) + + print("\nDatatype data: ", data.dtype) + print("\nInput data shape: ", data.shape) + + + x0,x1,y0,y1 = square + print(f"\nInput patch by index: xmin = {x0}, xmax = {x1}, ymin = {y0}, ymax = {y1}") + x_lim = slice(x0,x1) + y_lim = slice(y0,y1) + + center_x = (x0+x1)//2 + center_y = (y0+y1)//2 + length_x = (x1-x0)//16 #size of Y is 16 times smaller + length_y = (y1-y0)//16 #size of Y is 16 times smaller + Y_lim_x = slice(center_x-length_x//2,center_x+length_x//2) + Y_lim_y = slice(center_y-length_y//2,center_y+length_y//2) + Y = all_data[:,Y_lim_y,Y_lim_x] + print(f"\nY shape here (not ready): {Y.shape}") + data = data[:,y_lim,x_lim] + print(f"\nSliced data to dimensions {data.shape}") + + if centercrop: #extract centercrop before downsampling, since it's high resolution + + center = extract_centercrop(data) + print(f"\nCopying centercrop with shape {center.shape}") + if downsample == True: + print("\nDownsampling with rate: ", downsampling_rate) + data = down_sampler(data) + print("\nDone downsampling!") + + + print("\nDatatype downsampled: ", data.dtype) + print("\nDownsampled data shape: ",data.shape) + if len(data.shape)<4: + data = np.expand_dims(data, axis=3) + print(f"\nAdding channel dimension to data, new shape: {data.shape}") + if centercrop: + if len(center.shape)<4: + center = np.expand_dims(center, axis=3) + print(f"\nAdding channel dimension to centercrop, new shape: {center.shape}") + if spacedepth==True: + #print(f"\nStarting space-to-depth transform on data") + + data = space_to_depth(data,box) + + print(f"\nSpace-to-depth done! Data shape: {data.shape}") + if centercrop: + center = space_to_depth(center,box) + print(f"\nSpace-to-depth done! Centercrop shape: {center.shape}") + + if centercrop: + data = np.concatenate((data,center), axis=3) + print(f"\nConcatenating data and centercrop to dimenison: {data.shape} with shape [:,:,:,downsampled + centercrop]") + + + + gain = 0.4 + offset = -30 + data = data*gain + offset + maxx = np.max(data) + print("\nMAX (should be 255): ", maxx) + data = np.log(data+0.01)/4 + data = np.nan_to_num(data) #there are negative values in the np.log + + print(f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}") + + + data = longlatencoding(data) + print(f"\nConcatenating data with long, lat and elevation. New shape: {data.shape}, dtype: {data.dtype}") + + data = datetime_encoder(data,dates,plotter=False) + print(f"\nEncoding datetime periodical variables (seasonally,hourly) and concatenating with data. New shape: {data.shape}, dtype: {data.dtype}") + ''' + lead_time_spacing = 5 + hours_ahead = 5 # center square of 28km is, 434 to edge, ~300km to dataedge,60km/h gives ~7h + lead_times = (hours_ahead*60//lead_time_spacing) leadtimes''' + + + + + data = np.swapaxes(np.swapaxes(data,3,1),2,3) + print(f"\nData swapping axes to get channel first, now shape: {data.shape}") + X,Y, X_dates,Y_dates = temporal_concatenation(data,dates,Y,Y_dates,concat = concat, overlap = overlap, spaced = spaced,lead_times = lead_times) + print(f"\nDone with temporal concatenation and target_split! Data shape: {X.shape}, target shape: {Y.shape}") + #X[:,:,0:8,:,:] = X[:,:,0:8,:,:]*0.4 - 30 + '''for channel in range(8,X.shape[2]): + + plt.imshow(X[0,0,channel,:,:]) + plt.show()''' + Y = Y*0.4 -30 + print("MINMAX Y AFTER GAIN + OFFSET", np.min(Y), np.max(Y)) + + Y = rain_binned(Y) + + print(f"\nDone with binning targets into bins, target shape: {Y.shape}") + + + + if not printer: + sys.stdout = sys.__stdout__ + + + + return X,Y, X_dates,Y_dates + +def rain_binned(Y, n_bins = 51,increment = 2): + SHAPE = Y.shape + n,leads,w,h = SHAPE + max_fall = n_bins*increment + min_dbz = np.min(Y) + max_dbz = np.max(Y) + Y[np.where(Y>70)] = 0 + + rain = (10**(Y / 10.0) / 200.0)**(1.0 / 1.6) + print("RAIN MINMAX: ", np.min(rain), np.max(rain)) + + rain_bins = np.zeros((n,leads,n_bins,w,h)) + for i in range(n_bins-1): + bin_min = i*increment + bin_max = (i+1)*increment + rain_bin = np.zeros((n,leads,w,h)) #Y.shape = (None,lead_times, bin_channel, width/4,heigth/4) + idx = np.where(np.logical_and(rain>=bin_min, rain=n_bins*increment) + rain_bin[idx] = 1 + rain_bins[:,:,n_bins-1,:,:] = rain_bin + + return rain_bins + + + +if __name__=="__main__": + + + data,dates = load_data("combination_all_pn157.h5",N =500,downsample=True,spacedepth=True,printer=True) + print(data.nbytes) + + + print(np.max(data)) diff --git a/metnet/__init__.py b/metnet/__init__.py index 7eaaddc..8b13789 100644 --- a/metnet/__init__.py +++ b/metnet/__init__.py @@ -1,4 +1 @@ -from metnet.models.metnet import MetNet -from metnet.models.metnet2 import MetNet2 -from .layers import * diff --git a/metnet/layers/ConditionTime.py b/metnet/layers/ConditionTime.py index 97695ae..f1593b1 100644 --- a/metnet/layers/ConditionTime.py +++ b/metnet/layers/ConditionTime.py @@ -7,11 +7,12 @@ def condition_time(x, i=0, size=(12, 16), seq_len=15): assert i < seq_len times = (torch.eye(seq_len, dtype=x.dtype, device=x.device)[i]).unsqueeze(-1).unsqueeze(-1) ones = torch.ones(1, *size, dtype=x.dtype, device=x.device) + #print((times*ones).shape) return times * ones class ConditionTime(nn.Module): - "Condition Time on a stack of images, adds `horizon` channels to image" + """Condition Time on a stack of images, adds `horizon` channels to image""" def __init__(self, horizon, ch_dim=2, num_dims=5): super().__init__() @@ -20,7 +21,7 @@ def __init__(self, horizon, ch_dim=2, num_dims=5): self.num_dims = num_dims def forward(self, x, fstep=0): - "x stack of images, fsteps" + """x stack of images, fsteps""" if self.num_dims == 5: bs, seq_len, ch, h, w = x.shape ct = condition_time(x, fstep, (h, w), seq_len=self.horizon).repeat(bs, seq_len, 1, 1, 1) diff --git a/metnet/layers/ConvGRU.py b/metnet/layers/ConvGRU.py index 9699c9f..c829d2a 100644 --- a/metnet/layers/ConvGRU.py +++ b/metnet/layers/ConvGRU.py @@ -10,7 +10,7 @@ def __init__( hidden_dim, kernel_size=(3, 3), bias=True, - activation=F.tanh, + activation=torch.tanh, batchnorm=False, ): """ @@ -71,7 +71,7 @@ def forward(self, input, h_prev=None): combined = torch.cat((input, h_prev), dim=1) # concatenate along channel axis - combined_conv = F.sigmoid(self.conv_zr(combined)) + combined_conv = torch.sigmoid(self.conv_zr(combined)) z, r = torch.split(combined_conv, self.hidden_dim, dim=1) @@ -131,7 +131,7 @@ def __init__( n_layers, batch_first=True, bias=True, - activation=F.tanh, + activation=torch.tanh, input_p=0.2, hidden_p=0.1, batchnorm=False, diff --git a/metnet/layers/ConvLSTM.py b/metnet/layers/ConvLSTM.py index f78b305..9993832 100644 --- a/metnet/layers/ConvLSTM.py +++ b/metnet/layers/ConvLSTM.py @@ -16,7 +16,7 @@ def __init__( hidden_dim: int, kernel_size: int, bias=True, - activation=F.tanh, + activation=torch.tanh, batchnorm=False, ): """ @@ -69,13 +69,13 @@ def forward(self, x: torch.Tensor, prev_state: list) -> tuple[torch.Tensor, torc cc_i, cc_f, cc_o, cc_g = torch.split(combined_conv, self.hidden_dim, dim=1) - i = F.sigmoid(cc_i) - f = F.sigmoid(cc_f) + i = torch.sigmoid(cc_i) + f = torch.sigmoid(cc_f) g = self.activation(cc_g) c_cur = f * c_prev + i * g - o = F.sigmoid(cc_o) + o = torch.sigmoid(cc_o) h_cur = o * self.activation(c_cur) @@ -115,7 +115,7 @@ def __init__( kernel_size: int, num_layers: int, bias=True, - activation=F.tanh, + activation=torch.tanh, batchnorm=False, ): """ diff --git a/metnet/layers/DownSampler.py b/metnet/layers/DownSampler.py index d7c349d..da88d8e 100644 --- a/metnet/layers/DownSampler.py +++ b/metnet/layers/DownSampler.py @@ -9,24 +9,22 @@ def __init__(self, in_channels, output_channels: int = 256, conv_type: str = "st super().__init__() conv2d = get_conv_layer(conv_type=conv_type) self.output_channels = output_channels - if conv_type == "antialiased": - antialiased = True - else: - antialiased = False + self.module = nn.Sequential( conv2d(in_channels, 160, 3, padding=1), - nn.MaxPool2d((2, 2), stride=1 if antialiased else 2), - antialiased_cnns.BlurPool(160, stride=2) if antialiased else nn.Identity(), + nn.MaxPool2d((2, 2), stride=2), + #antialiased_cnns.BlurPool(160, stride=2) if antialiased else nn.Identity(), nn.BatchNorm2d(160), conv2d(160, output_channels, 3, padding=1), nn.BatchNorm2d(output_channels), conv2d(output_channels, output_channels, 3, padding=1), nn.BatchNorm2d(output_channels), conv2d(output_channels, output_channels, 3, padding=1), - nn.MaxPool2d((2, 2), stride=1 if antialiased else 2), - antialiased_cnns.BlurPool(output_channels, stride=2) if antialiased else nn.Identity(), + nn.MaxPool2d((2, 2), stride=2), + #antialiased_cnns.BlurPool(output_channels, stride=2) if antialiased else nn.Identity(), ) def forward(self, x): + return self.module.forward(x) diff --git a/metnet/layers/__init__.py b/metnet/layers/__init__.py index aec8617..04011d0 100644 --- a/metnet/layers/__init__.py +++ b/metnet/layers/__init__.py @@ -3,3 +3,4 @@ from .DownSampler import DownSampler from .Preprocessor import MetNetPreprocessor from .TimeDistributed import TimeDistributed +from .ConvLSTM import ConvLSTM diff --git a/metnet/models/__init__.py b/metnet/models/__init__.py index 04d0750..e69de29 100644 --- a/metnet/models/__init__.py +++ b/metnet/models/__init__.py @@ -1,2 +0,0 @@ -from .metnet import MetNet -from .metnet2 import MetNet2 diff --git a/metnet/models/metnet.py b/metnet/models/metnet.py index a1d33d0..4e6c9e5 100644 --- a/metnet/models/metnet.py +++ b/metnet/models/metnet.py @@ -9,7 +9,7 @@ class MetNet(torch.nn.Module, PyTorchModelHubMixin): def __init__( self, - image_encoder: str = "downsampler", + image_encoder: str = "downsampler", #4 CNN layers input_channels: int = 12, sat_channels: int = 12, input_size: int = 256, @@ -42,15 +42,15 @@ def __init__( self.forecast_steps = forecast_steps self.input_channels = input_channels self.output_channels = output_channels - + ''' self.preprocessor = MetNetPreprocessor( sat_channels=sat_channels, crop_size=input_size, use_space2depth=True, split_input=True ) # Update number of input_channels with output from MetNetPreprocessor new_channels = sat_channels * 4 # Space2Depth new_channels *= 2 # Concatenate two of them together - input_channels = input_channels - sat_channels + new_channels - self.drop = nn.Dropout(temporal_dropout) + input_channels = input_channels - sat_channels + new_channels''' + #self.drop = nn.Dropout(temporal_dropout) if image_encoder in ["downsampler", "default"]: image_encoder = DownSampler(input_channels + forecast_steps) else: @@ -70,19 +70,26 @@ def __init__( self.head = nn.Conv2d(hidden_dim, output_channels, kernel_size=(1, 1)) # Reduces to mask def encode_timestep(self, x, fstep=1): - + print("\n shape before preprocess: ", x.shape) # Preprocess Tensor - x = self.preprocessor(x) - + #x = self.preprocessor(x) + print("\n shape after preprocess: ", x.shape) # Condition Time + x = self.ct(x, fstep) + print("\n shape after ct: ", x.shape) ##CNN x = self.image_encoder(x) + print("\n shape after image_encoder: ", x.shape) # Temporal Encoder - _, state = self.temporal_enc(self.drop(x)) - return self.temporal_agg(state) + #_, state = self.temporal_enc(self.drop(x)) + _, state = self.temporal_enc(x) + print("\n shape after temp enc: ", state.shape) + dummy = self.temporal_agg(state) + print("\n shape after temporal_agg: ", dummy.shape) + return dummy def forward(self, imgs): """It takes a rank 5 tensor diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py new file mode 100644 index 0000000..118aee7 --- /dev/null +++ b/metnet/models/metnet_pylight.py @@ -0,0 +1,248 @@ +import torch +import torch.nn as nn +from axial_attention import AxialAttention +from huggingface_hub import PyTorchModelHubMixin +import pytorch_lightning as pl +import torch.nn.functional as F +from torch import optim +from data_prep import metnet_dataloader +from data_prep.prepare_data_MetNet import load_data +from metnet.layers import ConditionTime, ConvGRU, DownSampler, MetNetPreprocessor, TimeDistributed +from metnet.layers.ConditionTime import condition_time +from torch.utils.data import DataLoader, random_split +from random import shuffle +import math + +class MetNetPylight(pl.LightningModule, PyTorchModelHubMixin): + def __init__( + self, + image_encoder: str = "downsampler", #4 CNN layers + file_name: str = "combination_all_pn157.h5", + input_channels: int = 12, + n_samples: int = 1000, + sat_channels: int = 12, + input_size: int = 256, + output_channels: int = 12, + hidden_dim: int = 384, + kernel_size: int = 3, + num_layers: int = 1, + num_att_layers: int = 4, + forecast_steps: int = 48, + temporal_dropout: float = 0.2, + **kwargs, + ): + super(MetNetPylight, self).__init__() + config = locals() + config.pop("self") + config.pop("__class__") + self.config = kwargs.pop("config", config) + sat_channels = self.config["sat_channels"] + input_size = self.config["input_size"] + input_channels = self.config["input_channels"] + temporal_dropout = self.config["temporal_dropout"] + image_encoder = self.config["image_encoder"] + forecast_steps = self.config["forecast_steps"] + hidden_dim = self.config["hidden_dim"] + kernel_size = self.config["kernel_size"] + num_layers = self.config["num_layers"] + num_att_layers = self.config["num_att_layers"] + output_channels = self.config["output_channels"] + self.n_samples = n_samples + self.file_name = file_name + + self.forecast_steps = forecast_steps + self.input_channels = input_channels + self.output_channels = output_channels + ''' + self.preprocessor = MetNetPreprocessor( + sat_channels=sat_channels, crop_size=input_size, use_space2depth=True, split_input=True + ) + # Update number of input_channels with output from MetNetPreprocessor + new_channels = sat_channels * 4 # Space2Depth + new_channels *= 2 # Concatenate two of them together + input_channels = input_channels - sat_channels + new_channels''' + #self.drop = nn.Dropout(temporal_dropout) + if image_encoder in ["downsampler", "default"]: + image_encoder = DownSampler(input_channels + forecast_steps) + else: + raise ValueError(f"Image_encoder {image_encoder} is not recognized") + self.image_encoder = TimeDistributed(image_encoder) + #self.ct = ConditionTime(forecast_steps) + self.temporal_enc = TemporalEncoder( + image_encoder.output_channels, hidden_dim, ks=kernel_size, n_layers=num_layers + ) + self.temporal_agg = nn.Sequential( + *[ + AxialAttention(dim=hidden_dim, dim_index=1, heads=8, num_dimensions=2) + for _ in range(num_att_layers) + ] + ) + + self.head = nn.Conv2d(hidden_dim, output_channels, kernel_size=(1, 1)) # Reduces to mask + self.double() + + def encode_timestep(self, x, fstep=1): + #print("\n shape before preprocess: ", x.shape) + # Preprocess Tensor + #x = self.preprocessor(x) + #print("\n shape after preprocess: ", x.shape) + # Condition Time + + #x = self.ct(x, fstep) + x = x.double() + + #print("\n shape after ct: ", x.shape) + + ##CNN + x = self.image_encoder(x) + #print("\n shape after image_encoder: ", x.shape) + + # Temporal Encoder + #_, state = self.temporal_enc(self.drop(x)) + _, x = self.temporal_enc(x) + #print("\n shape after temp enc: ", state.shape) + + x = self.temporal_agg(x) + #print("\n shape after temporal_agg: ", agg.shape) + return x + + def forward(self, imgs): + """It takes a rank 5 tensor + - imgs [bs, seq_len, channels, h, w] + """ + + # Compute all timesteps, probably can be parallelized + res = [] + for i in range(self.forecast_steps): + x_i = self.encode_timestep(imgs, i) + + out = self.head(x_i) + res.append(out) + res = torch.stack(res, dim=1) + return res + + def training_step(self, batch, batch_idx): + x, y = batch + y_hat = self(x.float()) + loss = F.mse_loss(y_hat, y) + pbar = {"training_loss": loss} + return {"loss": loss, "progress_bar": pbar} + + def validation_step(self, batch, batch_idx): + results = self.training_step(batch, batch_idx) + return results + + def validation_epoch_end(self, val_step_outputs): + avg_val_loss = torch.tensor([x["loss"] for x in val_step_outputs]).mean() + pbar = {"avg_val_loss":avg_val_loss} + + return {"val_loss":avg_val_loss, "progress_bar":pbar} + + + def configure_optimizers(self): + optimizer = optim.SGD(self.parameters(),lr=1e-2) + return optimizer + + + def setup(self, stage = None): + + X,Y,X_dates,Y_dates = load_data(self.file_name, N = self.n_samples, lead_times = self.forecast_steps) + X = torch.from_numpy(X) + Y = torch.from_numpy(Y) + loaded_samples = X.shape[0] + split_list = list(range(loaded_samples)) + shuffle(split_list) + split_idx = int(loaded_samples*0.7) + + idx_training = split_list[:split_idx] + idx_val = split_list[split_idx:] + + X_train = [] + Y_train = [] + first = True + + for index in idx_training: + print(index) + for lead in range(self.forecast_steps): + x, y = X[index], Y[index] + + seq_len, ch, h, w = x.shape + ct = condition_time(x, lead, (h, w), seq_len=self.forecast_steps).repeat(seq_len, 1, 1, 1) + x = torch.cat([x, ct], dim=1) #HARDCODED + y = y[lead] + x = torch.unsqueeze(x,dim = 0) + y = torch.unsqueeze(y,dim = 0) + if first: + + X_train = x + Y_train = y + first = False + else: + + X_train = torch.cat((X_train,x), 0) + Y_train = torch.cat((Y_train,y), 0) + + + + + X_val = [] + Y_val = [] + first = True + + for index in idx_val: + for lead in range(self.forecast_steps): + x, y = X[index], Y[index] + + seq_len, ch, h, w = x.shape + ct = condition_time(x, lead, (h, w), seq_len=self.forecast_steps).repeat(seq_len, 1, 1, 1) + x = torch.cat([x, ct], dim=1) #HARDCODED + y = y[lead] + x = torch.unsqueeze(x,dim = 0) + y = torch.unsqueeze(y,dim = 0) + if first: + X_val = x + Y_val = y + first = False + else: + + X_val = torch.cat((X_val,x), 0) + Y_val = torch.cat((Y_val,y), 0) + + print("Train X shape: ", X_train.shape) + print("Train Y shape: ", Y_train.shape) + print("Validation X shape: ", X_val.shape) + print("Validation Y shape: ", Y_val.shape) + input() + + self.train_dataset = metnet_dataloader.MetNetDataset(X_train, Y_train) + self.val_dataset = metnet_dataloader.MetNetDataset(X_val, Y_val) + + + + def train_dataloader(self): + + train_loader = DataLoader(dataset=self.train_dataset,batch_size=4, shuffle = True) + + #val_loader = DataLoader(dataset=val,batch_size=4) + #test_loader = DataLoader(dataset=test,batch_size=4) + return train_loader + + def val_dataloader(self): + val_loader = DataLoader(self.val_dataset) + return val_loader + + +class TemporalEncoder(nn.Module): + def __init__(self, in_channels, out_channels=384, ks=3, n_layers=1): + super().__init__() + self.rnn = ConvGRU(in_channels, out_channels, (ks, ks), n_layers, batch_first=True) + + def forward(self, x): + x, h = self.rnn(x) + return (x, h[-1]) + +''' +def feat2image(x, target_size=(128, 128)): + "This idea comes from MetNet" + x = x.transpose(1, 2) + return x.unsqueeze(-1).unsqueeze(-1) * x.new_ones(1, 1, 1, *target_size)''' diff --git a/pyvenv.cfg b/pyvenv.cfg new file mode 100644 index 0000000..479ee9f --- /dev/null +++ b/pyvenv.cfg @@ -0,0 +1,3 @@ +home = C:\Users\valte\.conda\envs\MetNet +include-system-site-packages = false +version = 3.10.0 diff --git a/setup.py b/setup.py index 4888920..28393c0 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name="metnet", - version="2.0.5", + version="2.0.4", packages=find_packages(), url="https://github.com/openclimatefix/metnet", license="MIT License", diff --git a/tests/test_model.py b/tests/test_model.py index c22cad8..5f2114d 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -28,7 +28,6 @@ def test_metnet_creation(): ) assert not torch.isnan(out).any(), "Output included NaNs" - def test_metnet_backwards(): model = MetNet( hidden_dim=32, @@ -37,7 +36,7 @@ def test_metnet_backwards(): output_channels=12, sat_channels=12, input_size=32, - ) + ) # MetNet expects original HxW to be 4x the input size x = torch.randn((2, 12, 16, 128, 128)) out = model(x) @@ -48,7 +47,7 @@ def test_metnet_backwards(): 12, 8, 8, - ) + ) y = torch.randn((2, 24, 12, 8, 8)) F.mse_loss(out, y).backward() assert not torch.isnan(out).any(), "Output included NaNs" @@ -87,7 +86,6 @@ def test_metnet2_creation(): ) assert not torch.isnan(out).any(), "Output included NaNs" - def test_metnet2_backward(): model = MetNet2( forecast_steps=8, @@ -97,7 +95,7 @@ def test_metnet2_backward(): lstm_channels=32, encoder_channels=64, center_crop_size=16, - ) + ) # MetNet expects original HxW to be 4x the input size x = torch.randn((2, 6, 12, 256, 256)) out = model(x) @@ -108,7 +106,7 @@ def test_metnet2_backward(): 12, 64, 64, - ) - y = torch.rand((2, 8, 12, 64, 64)) + ) + y = torch.rand((2,8,12,64,64)) F.mse_loss(out, y).backward() - assert not torch.isnan(out).any(), "Output included NaNs" + assert not torch.isnan(out).any(), "Output included NaNs" \ No newline at end of file From e8de4bdc03d8a1003de0508aff7b6d4b1b04ffd3 Mon Sep 17 00:00:00 2001 From: Valterf Date: Thu, 24 Feb 2022 17:11:04 +0100 Subject: [PATCH 02/17] adding links to swedish radar dataset --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7fc7ab1..4343ece 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +Pytorch lightning: + +Swedish dataset (the ugly and small one, I might publish the nice and big one too at some point): +https://we.tl/t-OfkV7ZnGWC + # MetNet and MetNet-2 PyTorch Implementation of Google Research's MetNet for short term weather forecasting (https://arxiv.org/abs/2003.12140), inspired from https://github.com/tcapelle/metnet_pytorch/tree/master/metnet_pytorch From db05b09bbe496ee45ea9636cf197664a5e78275c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 25 Feb 2022 09:35:59 +0000 Subject: [PATCH 03/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- MetNet_lightning.py | 28 +-- Scripts/Activate.ps1 | 2 +- Scripts/activate.bat | 2 +- data_prep/__init__.py | 1 - data_prep/metnet_dataloader.py | 31 +-- data_prep/prepare_data_MetNet.py | 420 +++++++++++++++++-------------- metnet/__init__.py | 1 - metnet/layers/ConditionTime.py | 2 +- metnet/layers/DownSampler.py | 7 +- metnet/layers/__init__.py | 2 +- metnet/models/metnet.py | 12 +- metnet/models/metnet_pylight.py | 105 ++++---- tests/test_model.py | 14 +- 13 files changed, 336 insertions(+), 291 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index 6507205..3d014e3 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -1,29 +1,29 @@ -from metnet.models.metnet_pylight import MetNetPylight +import pytorch_lightning as pl import torch import torch.nn.functional as F -from data_prep.prepare_data_MetNet import load_data -import pytorch_lightning as pl +from data_prep.prepare_data_MetNet import load_data +from metnet.models.metnet_pylight import MetNetPylight model = MetNetPylight( - hidden_dim=128, #384 original paper - forecast_steps=6, #240 original paper - input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 - output_channels=51, #512 - input_size=112, # 112 - n_samples = 500, - ) + hidden_dim=128, # 384 original paper + forecast_steps=6, # 240 original paper + input_channels=15, # 46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 + output_channels=51, # 512 + input_size=112, # 112 + n_samples=500, +) # MetNet expects original HxW to be 4x the input size -#x = torch.randn((1, 7, 16, 128, 128)) +# x = torch.randn((1, 7, 16, 128, 128)) print(model) -trainer = pl.Trainer(fast_dev_run= True) +trainer = pl.Trainer(fast_dev_run=True) input("train? press enter to continue...") trainer.fit(model) -'''x = torch.randn((1, 7, 15, 112, 112)) +"""x = torch.randn((1, 7, 15, 112, 112)) #x = torch.cat([torch.zeros(1, 7, 10, 32, 32),torch.randn(1,7,8,32,32)], dim=2) out = model(x) # MetNet creates predictions for the center 1/4th y = torch.randn((1, 6, 51, 28, 28)) print(F.mse_loss(out, y)) -F.mse_loss(out, y).backward()''' +F.mse_loss(out, y).backward()""" diff --git a/Scripts/Activate.ps1 b/Scripts/Activate.ps1 index 51fc55c..b547581 100644 --- a/Scripts/Activate.ps1 +++ b/Scripts/Activate.ps1 @@ -44,7 +44,7 @@ command: PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -For more information on Execution Policies: +For more information on Execution Policies: https://go.microsoft.com/fwlink/?LinkID=135170 #> diff --git a/Scripts/activate.bat b/Scripts/activate.bat index 1b32d26..8e6d34e 100644 --- a/Scripts/activate.bat +++ b/Scripts/activate.bat @@ -25,7 +25,7 @@ if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH% if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH% set PATH=%VIRTUAL_ENV%\Scripts;%PATH% -set VIRTUAL_ENV_PROMPT=(metnet-main) +set VIRTUAL_ENV_PROMPT=(metnet-main) :END if defined _OLD_CODEPAGE ( diff --git a/data_prep/__init__.py b/data_prep/__init__.py index 8b13789..e69de29 100644 --- a/data_prep/__init__.py +++ b/data_prep/__init__.py @@ -1 +0,0 @@ - diff --git a/data_prep/metnet_dataloader.py b/data_prep/metnet_dataloader.py index 6561c1b..83e8350 100644 --- a/data_prep/metnet_dataloader.py +++ b/data_prep/metnet_dataloader.py @@ -1,11 +1,14 @@ -import torch -from .prepare_data_MetNet import load_data -from torch.utils.data import Dataset, DataLoader import math + import numpy as np +import torch +from torch.utils.data import DataLoader, Dataset + +from .prepare_data_MetNet import load_data + class MetNetDataset(Dataset): - def __init__(self,X,Y): + def __init__(self, X, Y): """ Input: file_name: path to hdf5-file. @@ -16,35 +19,33 @@ def __init__(self,X,Y): Y: numpy array shape (None, lead_times, channels, width, height) """ - - self.x = X self.y = Y - self.n_samples=X.shape[0] + self.n_samples = X.shape[0] def __getitem__(self, index): # allows indexing dataset[0] return self.x[index], self.y[index] + def __len__(self): # Will allow len(data) return self.n_samples -if __name__=="__main__": - dataset = MetNetDataset("combination_all_pn157.h5", N = 1000) - dataloader = DataLoader(dataset=dataset,batch_size=4,shuffle=True) + +if __name__ == "__main__": + dataset = MetNetDataset("combination_all_pn157.h5", N=1000) + dataloader = DataLoader(dataset=dataset, batch_size=4, shuffle=True) eps = 2 tot_samps = len(dataset) - n_iterations = math.ceil(tot_samps/4) + n_iterations = math.ceil(tot_samps / 4) print(tot_samps, n_iterations) for epoch in range(eps): - for i, (inputs,labels) in enumerate(dataloader): - if (i+1)%5 == 0: + for i, (inputs, labels) in enumerate(dataloader): + if (i + 1) % 5 == 0: print(f"epoch {epoch}/{eps}, step {i+1}/{n_iterations}, input {inputs.shape}") - - print("DONE NOW") input() dataiter = iter(dataloader) diff --git a/data_prep/prepare_data_MetNet.py b/data_prep/prepare_data_MetNet.py index 1e137cd..79bd7a7 100644 --- a/data_prep/prepare_data_MetNet.py +++ b/data_prep/prepare_data_MetNet.py @@ -1,16 +1,18 @@ -import numpy as np +import os +import sys +from datetime import date, datetime, timedelta +from pathlib import Path + import h5py as h5 import matplotlib -import matplotlib.pyplot as plt import matplotlib.animation as animation -from pathlib import Path -import sys, os -from datetime import datetime, timedelta,date +import matplotlib.pyplot as plt +import numpy as np -''' +""" Output: 5D tensor of shape (N_samples, time_steps, channels, width, height) = (None, 7, 75, 112, 112) -''' +""" def space_to_depth(x, block_size): @@ -18,97 +20,116 @@ def space_to_depth(x, block_size): batch, height, width, depth = x.shape reduced_height = height // block_size reduced_width = width // block_size - y = x.reshape(batch, reduced_height, block_size, - reduced_width, block_size, depth) + y = x.reshape(batch, reduced_height, block_size, reduced_width, block_size, depth) z = np.swapaxes(y, 2, 3).reshape(batch, reduced_height, reduced_width, -1) return z -def date_assertion(dates,expected_delta = 5): - for date1,date2 in zip(dates[0:-1],dates[1:]): + +def date_assertion(dates, expected_delta=5): + for date1, date2 in zip(dates[0:-1], dates[1:]): list1 = date1.split("_") list1 = list1[0:3] + list1[3].split(":") - #print(date1, date2) - y1, m1, d1, hour1, minute1 = [int(a) for a in list1] + # print(date1, date2) + y1, m1, d1, hour1, minute1 = [int(a) for a in list1] datetime1 = datetime(y1, m1, d1, hour=hour1, minute=minute1) list2 = date2.split("_") list2 = list2[0:3] + list2[3].split(":") - y2, m2, d2, hour2, minute2 = [int(a) for a in list2] + y2, m2, d2, hour2, minute2 = [int(a) for a in list2] datetime2 = datetime(y2, m2, d2, hour=hour2, minute=minute2) - delta = datetime2-datetime1 - minutes = delta.total_seconds()/60 + delta = datetime2 - datetime1 + minutes = delta.total_seconds() / 60 - #print(datetime1) - #print(datetime2) - #print("DELTA ", minutes, "delta ", expected_delta) + # print(datetime1) + # print(datetime2) + # print("DELTA ", minutes, "delta ", expected_delta) assert int(minutes) == expected_delta -def h5_iterator(h5_file,maxN = 100,spaced = 1): + +def h5_iterator(h5_file, maxN=100, spaced=1): """Iterates through the desired datafile and returns index, array and datetime""" - months = {"01":"January", "02":"February", "03":"March", - "04":"April", "05":"May", "06":"June", - "07":"July", "08":"August", "09":"September", - "10":"October", "11":"November", "12":"December"} - with h5.File(h5_file,"r") as f: + months = { + "01": "January", + "02": "February", + "03": "March", + "04": "April", + "05": "May", + "06": "June", + "07": "July", + "08": "August", + "09": "September", + "10": "October", + "11": "November", + "12": "December", + } + with h5.File(h5_file, "r") as f: keys = list(f["data/pn157"].keys()) - for i,name in enumerate(keys): + for i, name in enumerate(keys): - if i%spaced!=0: + if i % spaced != 0: continue - j = i//spaced - obj = f["data/pn157/"+name] + j = i // spaced + obj = f["data/pn157/" + name] - #print(name, obj) + # print(name, obj) if maxN: - if i//spaced>=maxN: break - #print(name) + if i // spaced >= maxN: + break + # print(name) typ, date = name.split("-") y, m, d, t = date.split("_") - #title = f"Year: {y} month: {months[m]} day: {d} time: {t}" - array = np.array(obj) #flip array - + # title = f"Year: {y} month: {months[m]} day: {d} time: {t}" + array = np.array(obj) # flip array - #print(array.shape) + # print(array.shape) yield j, array, date -def down_sampler(data, rate = 2): + +def down_sampler(data, rate=2): """Spatial downsampling with vertical and horizontal downsampling rate = rate.""" - down_sampled=data[:,0::rate,0::rate] + down_sampled = data[:, 0::rate, 0::rate] return down_sampled - -def temporal_concatenation(data,dates,targets,target_dates,concat = 7, overlap = 0,spaced = 3,lead_times = 60): +def temporal_concatenation( + data, dates, targets, target_dates, concat=7, overlap=0, spaced=3, lead_times=60 +): """Takes the spatial 2D arrays and concatenates temporal aspect to 3D-vector (T-120min, T-105min, ..., T-0min) concat = number of frames to encode in temporal dimension overlap = how many of the spatial arrays are allowed to overlap in another datasample""" - n,x_size,y_size,channels = data.shape - n_y,x_y,y_y = targets.shape + n, x_size, y_size, channels = data.shape + n_y, x_y, y_y = targets.shape - seq_length = spaced*concat + lead_times #5 minute increments - x_limit = n - seq_length//spaced - #concecutive time + seq_length = spaced * concat + lead_times # 5 minute increments + x_limit = n - seq_length // spaced + # concecutive time X = [] - X_dates=[] + X_dates = [] Y = [] Y_dates = [] - for i,j in zip(range(0,x_limit,concat-overlap),range(concat*spaced-2,n_y,(concat-overlap)*spaced)): - - if (i+1)%1000==0: - print(f"\nTemporal concatenated samples: ",i+1) - temp_input = data[i:i+concat,:,:] - temp_target = targets[j:j+lead_times,:,:] - temp_dates = dates[i:i+concat] - temp_dates_target = target_dates[j:j+lead_times] + for i, j in zip( + range(0, x_limit, concat - overlap), + range(concat * spaced - 2, n_y, (concat - overlap) * spaced), + ): + + if (i + 1) % 1000 == 0: + print(f"\nTemporal concatenated samples: ", i + 1) + temp_input = data[i : i + concat, :, :] + temp_target = targets[j : j + lead_times, :, :] + temp_dates = dates[i : i + concat] + temp_dates_target = target_dates[j : j + lead_times] try: - date_assertion(temp_dates,expected_delta = 5*spaced) - date_assertion(temp_dates_target,expected_delta = 5) - fiver = [temp_dates[-1],temp_dates_target[0]] #final X date and first Y date should be 5 spaced minutes - date_assertion(fiver,expected_delta = 5) + date_assertion(temp_dates, expected_delta=5 * spaced) + date_assertion(temp_dates_target, expected_delta=5) + fiver = [ + temp_dates[-1], + temp_dates_target[0], + ] # final X date and first Y date should be 5 spaced minutes + date_assertion(fiver, expected_delta=5) except AssertionError: print(f"Warning, dates are not alligned! Skipping: {i}:{i+seq_length}") print(temp_dates) @@ -122,9 +143,10 @@ def temporal_concatenation(data,dates,targets,target_dates,concat = 7, overlap = X = np.array(X) Y = np.array(Y) - return X,Y,X_dates,Y_dates + return X, Y, X_dates, Y_dates + -def extract_centercrop(data,factor_smaller=2): +def extract_centercrop(data, factor_smaller=2): x0 = 0 y0 = 0 @@ -135,87 +157,88 @@ def extract_centercrop(data,factor_smaller=2): assert x1 == y1 except AssertionError: print(f"\nWarning: centercrop shapes ({x1}, {y1}) are not the same.") - centercrop_x_lim = slice(x0+x1//(2*factor_smaller),x1-x1//(2*factor_smaller)) - centercrop_y_lim = slice(y0+y1//(2*factor_smaller),y1-y1//(2*factor_smaller)) + centercrop_x_lim = slice(x0 + x1 // (2 * factor_smaller), x1 - x1 // (2 * factor_smaller)) + centercrop_y_lim = slice(y0 + y1 // (2 * factor_smaller), y1 - y1 // (2 * factor_smaller)) - return data[:,centercrop_y_lim,centercrop_x_lim] + return data[:, centercrop_y_lim, centercrop_x_lim] -def datetime_encoder(data,dates,plotter = False): + +def datetime_encoder(data, dates, plotter=False): data_shape = data.shape data_type = data.dtype year_days = [] - day_minutes=[] - for i,date_string in enumerate(dates): - if (i+1)%1000==0: - print("Dates loaded for encoding: ",i+1) + day_minutes = [] + for i, date_string in enumerate(dates): + if (i + 1) % 1000 == 0: + print("Dates loaded for encoding: ", i + 1) list1 = date_string.split("_") list1 = list1[0:3] + list1[3].split(":") - year,month,day, hour, minute = [int(a) for a in list1] - date_object = date(year,month,day) + year, month, day, hour, minute = [int(a) for a in list1] + date_object = date(year, month, day) day_of_the_year = date_object.timetuple().tm_yday - minute_of_the_day = hour*60 + minute + minute_of_the_day = hour * 60 + minute year_days.append(day_of_the_year) day_minutes.append(minute_of_the_day) year_days = np.array(year_days) day_minutes = np.array(day_minutes) - year_days = np.repeat(year_days[:,np.newaxis],data_shape[1],axis=1) - year_days = np.repeat(year_days[:,:,np.newaxis],data_shape[2],axis=2) - day_minutes = np.repeat(day_minutes[:,np.newaxis],data_shape[1],axis=1) - day_minutes = np.repeat(day_minutes[:,:,np.newaxis],data_shape[2],axis=2) - - - - periodicals = [np.sin(2*np.pi*year_days/365,dtype =data_type), - np.cos(2*np.pi*year_days/365,dtype =data_type), - np.sin(2*np.pi*day_minutes/(60*24),dtype =data_type), - np.cos(2*np.pi*day_minutes/(60*24),dtype =data_type)] + year_days = np.repeat(year_days[:, np.newaxis], data_shape[1], axis=1) + year_days = np.repeat(year_days[:, :, np.newaxis], data_shape[2], axis=2) + day_minutes = np.repeat(day_minutes[:, np.newaxis], data_shape[1], axis=1) + day_minutes = np.repeat(day_minutes[:, :, np.newaxis], data_shape[2], axis=2) + + periodicals = [ + np.sin(2 * np.pi * year_days / 365, dtype=data_type), + np.cos(2 * np.pi * year_days / 365, dtype=data_type), + np.sin(2 * np.pi * day_minutes / (60 * 24), dtype=data_type), + np.cos(2 * np.pi * day_minutes / (60 * 24), dtype=data_type), + ] periodicals = [np.expand_dims(a, axis=3) for a in periodicals] - date_array = np.concatenate(periodicals,axis=3) + date_array = np.concatenate(periodicals, axis=3) try: assert date_array.shape[0:2] == data_shape[0:2] except AssertionError: print("Datetime dimensions seem wrong!") raise - '''for i,(day,min) in enumerate(zip(year_days,day_minutes)): + """for i,(day,min) in enumerate(zip(year_days,day_minutes)): date_array[i,:,:,0] = np.sin(2*np.pi*day/365,dtype =data_type) date_array[i,:,:,1] = np.cos(2*np.pi*day/365,dtype =data_type) date_array[i,:,:,2] = np.sin(2*np.pi*min/(60*24),dtype =data_type) - date_array[i,:,:,3] = np.cos(2*np.pi*min/(60*24),dtype =data_type)''' + date_array[i,:,:,3] = np.cos(2*np.pi*min/(60*24),dtype =data_type)""" if plotter: - fig,ax = plt.subplots(1,2) - ax[0].scatter(date_array[:,0,0,0],date_array[:,0,0,1]) + fig, ax = plt.subplots(1, 2) + ax[0].scatter(date_array[:, 0, 0, 0], date_array[:, 0, 0, 1]) fig.suptitle(f"Periodical year and days from {dates[0]} to {dates[-1]}") - ax[1].scatter(date_array[:,0,0,2],date_array[:,0,0,3]) + ax[1].scatter(date_array[:, 0, 0, 2], date_array[:, 0, 0, 3]) ax[0].set_title("Year") ax[1].set_title("Day") plt.show() - data = np.concatenate((data,date_array),axis=3) + data = np.concatenate((data, date_array), axis=3) return data + def longlatencoding(data): print(f"\nExtracting longitude, latitude and elevation data ...", end="") - with h5.File("lonlatelev.h5","r") as FF: + with h5.File("lonlatelev.h5", "r") as FF: lonlatelev = FF["lonlatelev"] - lonlatelev = np.array(lonlatelev)[:112,:112,:] + lonlatelev = np.array(lonlatelev)[:112, :112, :] - lon = lonlatelev[:,:,0] - lat = lonlatelev[:,:,1] - - elev = lonlatelev[:,:,2] + lon = lonlatelev[:, :, 0] + lat = lonlatelev[:, :, 1] + elev = lonlatelev[:, :, 2] lon_mean, lon_std = np.mean(lon), np.std(lon) lat_mean, lat_std = np.mean(lat), np.std(lat) elev_mean, elev_std = np.mean(elev), np.std(elev) - lon = (lon-lon_mean)/lon_std - lat = (lat-lat_mean)/lat_std - elev = (elev-elev_mean)/elev_std - elev = np.log(elev-np.min(elev)+0.1) + lon = (lon - lon_mean) / lon_std + lat = (lat - lat_mean) / lat_std + elev = (elev - elev_mean) / elev_std + elev = np.log(elev - np.min(elev) + 0.1) elev /= np.max(elev) lon = np.tanh(lon) lat = np.tanh(lat) @@ -224,36 +247,46 @@ def longlatencoding(data): print("MINMAX lat: ", np.min(lat), np.max(lat)) print("MINMAX elev: ", np.min(elev), np.max(elev)) - lonlatelev[:,:,0] = lon - lonlatelev[:,:,1] = lat - lonlatelev[:,:,2] = elev + lonlatelev[:, :, 0] = lon + lonlatelev[:, :, 1] = lat + lonlatelev[:, :, 2] = elev lonlatelev = np.expand_dims(lonlatelev, axis=0) - lonlatelev = np.repeat(lonlatelev,data.shape[0],axis=0) + lonlatelev = np.repeat(lonlatelev, data.shape[0], axis=0) print(f"\ndone! it has shape {lonlatelev.shape}") - return np.concatenate((data,lonlatelev), axis=3) - - - -def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True): - #15 minutes between datapoints is default --> spaced = 3 + return np.concatenate((data, lonlatelev), axis=3) + + +def load_data( + h5_path, + N=3000, + lead_times=60, + concat=7, + square=(0, 448, 881 - 448, 881), + downsampling_rate=2, + overlap=0, + spaced=3, + downsample=True, + spacedepth=True, + centercrop=True, + box=2, + printer=True, +): + # 15 minutes between datapoints is default --> spaced = 3 snapshots = [] dates = [] all_snapshots = [] Y_dates = [] - - - if not printer: - sys.stdout = open(os.devnull, 'w') - for i, array,date in h5_iterator(h5_path, N): - if (i+1)%1000==0: - print("Loaded samples: ",i+1) + sys.stdout = open(os.devnull, "w") + for i, array, date in h5_iterator(h5_path, N): + if (i + 1) % 1000 == 0: + print("Loaded samples: ", i + 1) - if i%spaced==0: + if i % spaced == 0: snapshots.append(array) dates.append(date) all_snapshots.append(array) @@ -266,24 +299,23 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 print("\nDatatype data: ", data.dtype) print("\nInput data shape: ", data.shape) - - x0,x1,y0,y1 = square + x0, x1, y0, y1 = square print(f"\nInput patch by index: xmin = {x0}, xmax = {x1}, ymin = {y0}, ymax = {y1}") - x_lim = slice(x0,x1) - y_lim = slice(y0,y1) - - center_x = (x0+x1)//2 - center_y = (y0+y1)//2 - length_x = (x1-x0)//16 #size of Y is 16 times smaller - length_y = (y1-y0)//16 #size of Y is 16 times smaller - Y_lim_x = slice(center_x-length_x//2,center_x+length_x//2) - Y_lim_y = slice(center_y-length_y//2,center_y+length_y//2) - Y = all_data[:,Y_lim_y,Y_lim_x] + x_lim = slice(x0, x1) + y_lim = slice(y0, y1) + + center_x = (x0 + x1) // 2 + center_y = (y0 + y1) // 2 + length_x = (x1 - x0) // 16 # size of Y is 16 times smaller + length_y = (y1 - y0) // 16 # size of Y is 16 times smaller + Y_lim_x = slice(center_x - length_x // 2, center_x + length_x // 2) + Y_lim_y = slice(center_y - length_y // 2, center_y + length_y // 2) + Y = all_data[:, Y_lim_y, Y_lim_x] print(f"\nY shape here (not ready): {Y.shape}") - data = data[:,y_lim,x_lim] + data = data[:, y_lim, x_lim] print(f"\nSliced data to dimensions {data.shape}") - if centercrop: #extract centercrop before downsampling, since it's high resolution + if centercrop: # extract centercrop before downsampling, since it's high resolution center = extract_centercrop(data) print(f"\nCopying centercrop with shape {center.shape}") @@ -292,118 +324,128 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 data = down_sampler(data) print("\nDone downsampling!") - print("\nDatatype downsampled: ", data.dtype) - print("\nDownsampled data shape: ",data.shape) - if len(data.shape)<4: + print("\nDownsampled data shape: ", data.shape) + if len(data.shape) < 4: data = np.expand_dims(data, axis=3) print(f"\nAdding channel dimension to data, new shape: {data.shape}") if centercrop: - if len(center.shape)<4: + if len(center.shape) < 4: center = np.expand_dims(center, axis=3) print(f"\nAdding channel dimension to centercrop, new shape: {center.shape}") - if spacedepth==True: - #print(f"\nStarting space-to-depth transform on data") + if spacedepth == True: + # print(f"\nStarting space-to-depth transform on data") - data = space_to_depth(data,box) + data = space_to_depth(data, box) print(f"\nSpace-to-depth done! Data shape: {data.shape}") if centercrop: - center = space_to_depth(center,box) + center = space_to_depth(center, box) print(f"\nSpace-to-depth done! Centercrop shape: {center.shape}") if centercrop: - data = np.concatenate((data,center), axis=3) - print(f"\nConcatenating data and centercrop to dimenison: {data.shape} with shape [:,:,:,downsampled + centercrop]") - - + data = np.concatenate((data, center), axis=3) + print( + f"\nConcatenating data and centercrop to dimenison: {data.shape} with shape [:,:,:,downsampled + centercrop]" + ) gain = 0.4 offset = -30 - data = data*gain + offset + data = data * gain + offset maxx = np.max(data) print("\nMAX (should be 255): ", maxx) - data = np.log(data+0.01)/4 - data = np.nan_to_num(data) #there are negative values in the np.log - - print(f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}") + data = np.log(data + 0.01) / 4 + data = np.nan_to_num(data) # there are negative values in the np.log + print( + f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}" + ) data = longlatencoding(data) - print(f"\nConcatenating data with long, lat and elevation. New shape: {data.shape}, dtype: {data.dtype}") - - data = datetime_encoder(data,dates,plotter=False) - print(f"\nEncoding datetime periodical variables (seasonally,hourly) and concatenating with data. New shape: {data.shape}, dtype: {data.dtype}") - ''' + print( + f"\nConcatenating data with long, lat and elevation. New shape: {data.shape}, dtype: {data.dtype}" + ) + + data = datetime_encoder(data, dates, plotter=False) + print( + f"\nEncoding datetime periodical variables (seasonally,hourly) and concatenating with data. New shape: {data.shape}, dtype: {data.dtype}" + ) + """ lead_time_spacing = 5 hours_ahead = 5 # center square of 28km is, 434 to edge, ~300km to dataedge,60km/h gives ~7h - lead_times = (hours_ahead*60//lead_time_spacing) leadtimes''' - - + lead_times = (hours_ahead*60//lead_time_spacing) leadtimes""" - - data = np.swapaxes(np.swapaxes(data,3,1),2,3) + data = np.swapaxes(np.swapaxes(data, 3, 1), 2, 3) print(f"\nData swapping axes to get channel first, now shape: {data.shape}") - X,Y, X_dates,Y_dates = temporal_concatenation(data,dates,Y,Y_dates,concat = concat, overlap = overlap, spaced = spaced,lead_times = lead_times) - print(f"\nDone with temporal concatenation and target_split! Data shape: {X.shape}, target shape: {Y.shape}") - #X[:,:,0:8,:,:] = X[:,:,0:8,:,:]*0.4 - 30 - '''for channel in range(8,X.shape[2]): + X, Y, X_dates, Y_dates = temporal_concatenation( + data, + dates, + Y, + Y_dates, + concat=concat, + overlap=overlap, + spaced=spaced, + lead_times=lead_times, + ) + print( + f"\nDone with temporal concatenation and target_split! Data shape: {X.shape}, target shape: {Y.shape}" + ) + # X[:,:,0:8,:,:] = X[:,:,0:8,:,:]*0.4 - 30 + """for channel in range(8,X.shape[2]): plt.imshow(X[0,0,channel,:,:]) - plt.show()''' - Y = Y*0.4 -30 + plt.show()""" + Y = Y * 0.4 - 30 print("MINMAX Y AFTER GAIN + OFFSET", np.min(Y), np.max(Y)) Y = rain_binned(Y) print(f"\nDone with binning targets into bins, target shape: {Y.shape}") - - if not printer: sys.stdout = sys.__stdout__ + return X, Y, X_dates, Y_dates - return X,Y, X_dates,Y_dates - -def rain_binned(Y, n_bins = 51,increment = 2): +def rain_binned(Y, n_bins=51, increment=2): SHAPE = Y.shape - n,leads,w,h = SHAPE - max_fall = n_bins*increment + n, leads, w, h = SHAPE + max_fall = n_bins * increment min_dbz = np.min(Y) max_dbz = np.max(Y) - Y[np.where(Y>70)] = 0 + Y[np.where(Y > 70)] = 0 - rain = (10**(Y / 10.0) / 200.0)**(1.0 / 1.6) + rain = (10 ** (Y / 10.0) / 200.0) ** (1.0 / 1.6) print("RAIN MINMAX: ", np.min(rain), np.max(rain)) - rain_bins = np.zeros((n,leads,n_bins,w,h)) - for i in range(n_bins-1): - bin_min = i*increment - bin_max = (i+1)*increment - rain_bin = np.zeros((n,leads,w,h)) #Y.shape = (None,lead_times, bin_channel, width/4,heigth/4) - idx = np.where(np.logical_and(rain>=bin_min, rain= bin_min, rain < bin_max)) rain_bin[idx] = 1 - #rain_bin = np.expand_dims(rain_bin,axis=2) + # rain_bin = np.expand_dims(rain_bin,axis=2) - rain_bins[:,:,i,:,:] = rain_bin + rain_bins[:, :, i, :, :] = rain_bin - rain_bin = np.zeros((n,leads,w,h)) - idx = np.where(rain>=n_bins*increment) + rain_bin = np.zeros((n, leads, w, h)) + idx = np.where(rain >= n_bins * increment) rain_bin[idx] = 1 - rain_bins[:,:,n_bins-1,:,:] = rain_bin + rain_bins[:, :, n_bins - 1, :, :] = rain_bin return rain_bins +if __name__ == "__main__": -if __name__=="__main__": - - - data,dates = load_data("combination_all_pn157.h5",N =500,downsample=True,spacedepth=True,printer=True) + data, dates = load_data( + "combination_all_pn157.h5", N=500, downsample=True, spacedepth=True, printer=True + ) print(data.nbytes) - print(np.max(data)) diff --git a/metnet/__init__.py b/metnet/__init__.py index 8b13789..e69de29 100644 --- a/metnet/__init__.py +++ b/metnet/__init__.py @@ -1 +0,0 @@ - diff --git a/metnet/layers/ConditionTime.py b/metnet/layers/ConditionTime.py index f1593b1..e752274 100644 --- a/metnet/layers/ConditionTime.py +++ b/metnet/layers/ConditionTime.py @@ -7,7 +7,7 @@ def condition_time(x, i=0, size=(12, 16), seq_len=15): assert i < seq_len times = (torch.eye(seq_len, dtype=x.dtype, device=x.device)[i]).unsqueeze(-1).unsqueeze(-1) ones = torch.ones(1, *size, dtype=x.dtype, device=x.device) - #print((times*ones).shape) + # print((times*ones).shape) return times * ones diff --git a/metnet/layers/DownSampler.py b/metnet/layers/DownSampler.py index da88d8e..1efa199 100644 --- a/metnet/layers/DownSampler.py +++ b/metnet/layers/DownSampler.py @@ -10,11 +10,10 @@ def __init__(self, in_channels, output_channels: int = 256, conv_type: str = "st conv2d = get_conv_layer(conv_type=conv_type) self.output_channels = output_channels - self.module = nn.Sequential( conv2d(in_channels, 160, 3, padding=1), nn.MaxPool2d((2, 2), stride=2), - #antialiased_cnns.BlurPool(160, stride=2) if antialiased else nn.Identity(), + # antialiased_cnns.BlurPool(160, stride=2) if antialiased else nn.Identity(), nn.BatchNorm2d(160), conv2d(160, output_channels, 3, padding=1), nn.BatchNorm2d(output_channels), @@ -22,9 +21,9 @@ def __init__(self, in_channels, output_channels: int = 256, conv_type: str = "st nn.BatchNorm2d(output_channels), conv2d(output_channels, output_channels, 3, padding=1), nn.MaxPool2d((2, 2), stride=2), - #antialiased_cnns.BlurPool(output_channels, stride=2) if antialiased else nn.Identity(), + # antialiased_cnns.BlurPool(output_channels, stride=2) if antialiased else nn.Identity(), ) def forward(self, x): - + return self.module.forward(x) diff --git a/metnet/layers/__init__.py b/metnet/layers/__init__.py index 04011d0..b93b5e3 100644 --- a/metnet/layers/__init__.py +++ b/metnet/layers/__init__.py @@ -1,6 +1,6 @@ from .ConditionTime import ConditionTime from .ConvGRU import ConvGRU +from .ConvLSTM import ConvLSTM from .DownSampler import DownSampler from .Preprocessor import MetNetPreprocessor from .TimeDistributed import TimeDistributed -from .ConvLSTM import ConvLSTM diff --git a/metnet/models/metnet.py b/metnet/models/metnet.py index 4e6c9e5..8475f31 100644 --- a/metnet/models/metnet.py +++ b/metnet/models/metnet.py @@ -9,7 +9,7 @@ class MetNet(torch.nn.Module, PyTorchModelHubMixin): def __init__( self, - image_encoder: str = "downsampler", #4 CNN layers + image_encoder: str = "downsampler", # 4 CNN layers input_channels: int = 12, sat_channels: int = 12, input_size: int = 256, @@ -42,15 +42,15 @@ def __init__( self.forecast_steps = forecast_steps self.input_channels = input_channels self.output_channels = output_channels - ''' + """ self.preprocessor = MetNetPreprocessor( sat_channels=sat_channels, crop_size=input_size, use_space2depth=True, split_input=True ) # Update number of input_channels with output from MetNetPreprocessor new_channels = sat_channels * 4 # Space2Depth new_channels *= 2 # Concatenate two of them together - input_channels = input_channels - sat_channels + new_channels''' - #self.drop = nn.Dropout(temporal_dropout) + input_channels = input_channels - sat_channels + new_channels""" + # self.drop = nn.Dropout(temporal_dropout) if image_encoder in ["downsampler", "default"]: image_encoder = DownSampler(input_channels + forecast_steps) else: @@ -72,7 +72,7 @@ def __init__( def encode_timestep(self, x, fstep=1): print("\n shape before preprocess: ", x.shape) # Preprocess Tensor - #x = self.preprocessor(x) + # x = self.preprocessor(x) print("\n shape after preprocess: ", x.shape) # Condition Time @@ -84,7 +84,7 @@ def encode_timestep(self, x, fstep=1): print("\n shape after image_encoder: ", x.shape) # Temporal Encoder - #_, state = self.temporal_enc(self.drop(x)) + # _, state = self.temporal_enc(self.drop(x)) _, state = self.temporal_enc(x) print("\n shape after temp enc: ", state.shape) dummy = self.temporal_agg(state) diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index 118aee7..f03e8f6 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -1,22 +1,25 @@ +import math +from random import shuffle + +import pytorch_lightning as pl import torch import torch.nn as nn +import torch.nn.functional as F from axial_attention import AxialAttention from huggingface_hub import PyTorchModelHubMixin -import pytorch_lightning as pl -import torch.nn.functional as F from torch import optim +from torch.utils.data import DataLoader, random_split + from data_prep import metnet_dataloader from data_prep.prepare_data_MetNet import load_data from metnet.layers import ConditionTime, ConvGRU, DownSampler, MetNetPreprocessor, TimeDistributed from metnet.layers.ConditionTime import condition_time -from torch.utils.data import DataLoader, random_split -from random import shuffle -import math + class MetNetPylight(pl.LightningModule, PyTorchModelHubMixin): def __init__( self, - image_encoder: str = "downsampler", #4 CNN layers + image_encoder: str = "downsampler", # 4 CNN layers file_name: str = "combination_all_pn157.h5", input_channels: int = 12, n_samples: int = 1000, @@ -53,21 +56,21 @@ def __init__( self.forecast_steps = forecast_steps self.input_channels = input_channels self.output_channels = output_channels - ''' + """ self.preprocessor = MetNetPreprocessor( sat_channels=sat_channels, crop_size=input_size, use_space2depth=True, split_input=True ) # Update number of input_channels with output from MetNetPreprocessor new_channels = sat_channels * 4 # Space2Depth new_channels *= 2 # Concatenate two of them together - input_channels = input_channels - sat_channels + new_channels''' - #self.drop = nn.Dropout(temporal_dropout) + input_channels = input_channels - sat_channels + new_channels""" + # self.drop = nn.Dropout(temporal_dropout) if image_encoder in ["downsampler", "default"]: image_encoder = DownSampler(input_channels + forecast_steps) else: raise ValueError(f"Image_encoder {image_encoder} is not recognized") self.image_encoder = TimeDistributed(image_encoder) - #self.ct = ConditionTime(forecast_steps) + # self.ct = ConditionTime(forecast_steps) self.temporal_enc = TemporalEncoder( image_encoder.output_channels, hidden_dim, ks=kernel_size, n_layers=num_layers ) @@ -82,28 +85,28 @@ def __init__( self.double() def encode_timestep(self, x, fstep=1): - #print("\n shape before preprocess: ", x.shape) + # print("\n shape before preprocess: ", x.shape) # Preprocess Tensor - #x = self.preprocessor(x) - #print("\n shape after preprocess: ", x.shape) + # x = self.preprocessor(x) + # print("\n shape after preprocess: ", x.shape) # Condition Time - #x = self.ct(x, fstep) + # x = self.ct(x, fstep) x = x.double() - #print("\n shape after ct: ", x.shape) + # print("\n shape after ct: ", x.shape) ##CNN x = self.image_encoder(x) - #print("\n shape after image_encoder: ", x.shape) + # print("\n shape after image_encoder: ", x.shape) # Temporal Encoder - #_, state = self.temporal_enc(self.drop(x)) + # _, state = self.temporal_enc(self.drop(x)) _, x = self.temporal_enc(x) - #print("\n shape after temp enc: ", state.shape) + # print("\n shape after temp enc: ", state.shape) x = self.temporal_agg(x) - #print("\n shape after temporal_agg: ", agg.shape) + # print("\n shape after temporal_agg: ", agg.shape) return x def forward(self, imgs): @@ -134,25 +137,25 @@ def validation_step(self, batch, batch_idx): def validation_epoch_end(self, val_step_outputs): avg_val_loss = torch.tensor([x["loss"] for x in val_step_outputs]).mean() - pbar = {"avg_val_loss":avg_val_loss} - - return {"val_loss":avg_val_loss, "progress_bar":pbar} + pbar = {"avg_val_loss": avg_val_loss} + return {"val_loss": avg_val_loss, "progress_bar": pbar} def configure_optimizers(self): - optimizer = optim.SGD(self.parameters(),lr=1e-2) + optimizer = optim.SGD(self.parameters(), lr=1e-2) return optimizer + def setup(self, stage=None): - def setup(self, stage = None): - - X,Y,X_dates,Y_dates = load_data(self.file_name, N = self.n_samples, lead_times = self.forecast_steps) + X, Y, X_dates, Y_dates = load_data( + self.file_name, N=self.n_samples, lead_times=self.forecast_steps + ) X = torch.from_numpy(X) Y = torch.from_numpy(Y) loaded_samples = X.shape[0] split_list = list(range(loaded_samples)) shuffle(split_list) - split_idx = int(loaded_samples*0.7) + split_idx = int(loaded_samples * 0.7) idx_training = split_list[:split_idx] idx_val = split_list[split_idx:] @@ -164,14 +167,16 @@ def setup(self, stage = None): for index in idx_training: print(index) for lead in range(self.forecast_steps): - x, y = X[index], Y[index] + x, y = X[index], Y[index] seq_len, ch, h, w = x.shape - ct = condition_time(x, lead, (h, w), seq_len=self.forecast_steps).repeat(seq_len, 1, 1, 1) - x = torch.cat([x, ct], dim=1) #HARDCODED + ct = condition_time(x, lead, (h, w), seq_len=self.forecast_steps).repeat( + seq_len, 1, 1, 1 + ) + x = torch.cat([x, ct], dim=1) # HARDCODED y = y[lead] - x = torch.unsqueeze(x,dim = 0) - y = torch.unsqueeze(y,dim = 0) + x = torch.unsqueeze(x, dim=0) + y = torch.unsqueeze(y, dim=0) if first: X_train = x @@ -179,11 +184,8 @@ def setup(self, stage = None): first = False else: - X_train = torch.cat((X_train,x), 0) - Y_train = torch.cat((Y_train,y), 0) - - - + X_train = torch.cat((X_train, x), 0) + Y_train = torch.cat((Y_train, y), 0) X_val = [] Y_val = [] @@ -191,22 +193,24 @@ def setup(self, stage = None): for index in idx_val: for lead in range(self.forecast_steps): - x, y = X[index], Y[index] + x, y = X[index], Y[index] seq_len, ch, h, w = x.shape - ct = condition_time(x, lead, (h, w), seq_len=self.forecast_steps).repeat(seq_len, 1, 1, 1) - x = torch.cat([x, ct], dim=1) #HARDCODED + ct = condition_time(x, lead, (h, w), seq_len=self.forecast_steps).repeat( + seq_len, 1, 1, 1 + ) + x = torch.cat([x, ct], dim=1) # HARDCODED y = y[lead] - x = torch.unsqueeze(x,dim = 0) - y = torch.unsqueeze(y,dim = 0) + x = torch.unsqueeze(x, dim=0) + y = torch.unsqueeze(y, dim=0) if first: X_val = x Y_val = y first = False else: - X_val = torch.cat((X_val,x), 0) - Y_val = torch.cat((Y_val,y), 0) + X_val = torch.cat((X_val, x), 0) + Y_val = torch.cat((Y_val, y), 0) print("Train X shape: ", X_train.shape) print("Train Y shape: ", Y_train.shape) @@ -217,14 +221,12 @@ def setup(self, stage = None): self.train_dataset = metnet_dataloader.MetNetDataset(X_train, Y_train) self.val_dataset = metnet_dataloader.MetNetDataset(X_val, Y_val) - - def train_dataloader(self): - train_loader = DataLoader(dataset=self.train_dataset,batch_size=4, shuffle = True) + train_loader = DataLoader(dataset=self.train_dataset, batch_size=4, shuffle=True) - #val_loader = DataLoader(dataset=val,batch_size=4) - #test_loader = DataLoader(dataset=test,batch_size=4) + # val_loader = DataLoader(dataset=val,batch_size=4) + # test_loader = DataLoader(dataset=test,batch_size=4) return train_loader def val_dataloader(self): @@ -241,8 +243,9 @@ def forward(self, x): x, h = self.rnn(x) return (x, h[-1]) -''' + +""" def feat2image(x, target_size=(128, 128)): "This idea comes from MetNet" x = x.transpose(1, 2) - return x.unsqueeze(-1).unsqueeze(-1) * x.new_ones(1, 1, 1, *target_size)''' + return x.unsqueeze(-1).unsqueeze(-1) * x.new_ones(1, 1, 1, *target_size)""" diff --git a/tests/test_model.py b/tests/test_model.py index 5f2114d..c22cad8 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -28,6 +28,7 @@ def test_metnet_creation(): ) assert not torch.isnan(out).any(), "Output included NaNs" + def test_metnet_backwards(): model = MetNet( hidden_dim=32, @@ -36,7 +37,7 @@ def test_metnet_backwards(): output_channels=12, sat_channels=12, input_size=32, - ) + ) # MetNet expects original HxW to be 4x the input size x = torch.randn((2, 12, 16, 128, 128)) out = model(x) @@ -47,7 +48,7 @@ def test_metnet_backwards(): 12, 8, 8, - ) + ) y = torch.randn((2, 24, 12, 8, 8)) F.mse_loss(out, y).backward() assert not torch.isnan(out).any(), "Output included NaNs" @@ -86,6 +87,7 @@ def test_metnet2_creation(): ) assert not torch.isnan(out).any(), "Output included NaNs" + def test_metnet2_backward(): model = MetNet2( forecast_steps=8, @@ -95,7 +97,7 @@ def test_metnet2_backward(): lstm_channels=32, encoder_channels=64, center_crop_size=16, - ) + ) # MetNet expects original HxW to be 4x the input size x = torch.randn((2, 6, 12, 256, 256)) out = model(x) @@ -106,7 +108,7 @@ def test_metnet2_backward(): 12, 64, 64, - ) - y = torch.rand((2,8,12,64,64)) + ) + y = torch.rand((2, 8, 12, 64, 64)) F.mse_loss(out, y).backward() - assert not torch.isnan(out).any(), "Output included NaNs" \ No newline at end of file + assert not torch.isnan(out).any(), "Output included NaNs" From 3d3ea48faabae2c3abfc146823e7c0484e2d42a3 Mon Sep 17 00:00:00 2001 From: Valterf Date: Fri, 25 Feb 2022 11:58:30 +0100 Subject: [PATCH 04/17] Added conditional lead time solution --- MetNet_lightning.py | 10 +-- data_prep/metnet_dataloader.py | 9 +- data_prep/prepare_data_MetNet.py | 18 ++-- metnet/models/metnet_pylight.py | 137 ++++++++----------------------- 4 files changed, 54 insertions(+), 120 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index 6507205..bc12721 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -13,17 +13,9 @@ input_size=112, # 112 n_samples = 500, ) -# MetNet expects original HxW to be 4x the input size -#x = torch.randn((1, 7, 16, 128, 128)) +#MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. print(model) trainer = pl.Trainer(fast_dev_run= True) input("train? press enter to continue...") trainer.fit(model) -'''x = torch.randn((1, 7, 15, 112, 112)) -#x = torch.cat([torch.zeros(1, 7, 10, 32, 32),torch.randn(1,7,8,32,32)], dim=2) -out = model(x) -# MetNet creates predictions for the center 1/4th -y = torch.randn((1, 6, 51, 28, 28)) -print(F.mse_loss(out, y)) -F.mse_loss(out, y).backward()''' diff --git a/data_prep/metnet_dataloader.py b/data_prep/metnet_dataloader.py index 6561c1b..3fba6ac 100644 --- a/data_prep/metnet_dataloader.py +++ b/data_prep/metnet_dataloader.py @@ -5,7 +5,7 @@ import numpy as np class MetNetDataset(Dataset): - def __init__(self,X,Y): + def __init__(self,file_name,N, lead_times = 60): """ Input: file_name: path to hdf5-file. @@ -15,14 +15,13 @@ def __init__(self,X,Y): X: numpy array shape (None, time, channels, width, height), Y: numpy array shape (None, lead_times, channels, width, height) """ + X,Y,X_dates,Y_dates = load_data(file_name, N = N, lead_times = lead_times) - - self.x = X - self.y = Y + self.x = torch.from_numpy(X) + self.y = torch.from_numpy(Y) self.n_samples=X.shape[0] - def __getitem__(self, index): # allows indexing dataset[0] return self.x[index], self.y[index] diff --git a/data_prep/prepare_data_MetNet.py b/data_prep/prepare_data_MetNet.py index 1e137cd..c8960f1 100644 --- a/data_prep/prepare_data_MetNet.py +++ b/data_prep/prepare_data_MetNet.py @@ -8,10 +8,20 @@ from datetime import datetime, timedelta,date ''' -Output: 5D tensor of shape (N_samples, time_steps, channels, width, height) = (None, 7, 75, 112, 112) +Output: 3D tensor of shape (N,128,128,240+46): ''' +#from grid_with_projection import GridWithProjection +def h5_writer(directory,data,dates): + with h5.File(f'network_data.h5', 'w') as target: + for date in dates: + target.create_dataset(target_name,data = array,compression="gzip") + +def do_with_items(name): + return name + + def space_to_depth(x, block_size): x = np.asarray(x) @@ -318,13 +328,11 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 - gain = 0.4 - offset = -30 - data = data*gain + offset + maxx = np.max(data) print("\nMAX (should be 255): ", maxx) data = np.log(data+0.01)/4 - data = np.nan_to_num(data) #there are negative values in the np.log + data = np.nan_to_num(data) print(f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}") diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index 118aee7..cbdc7fd 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -5,12 +5,10 @@ import pytorch_lightning as pl import torch.nn.functional as F from torch import optim -from data_prep import metnet_dataloader -from data_prep.prepare_data_MetNet import load_data +from data_prep import metnet_dataloader, prepare_data_MetNet from metnet.layers import ConditionTime, ConvGRU, DownSampler, MetNetPreprocessor, TimeDistributed -from metnet.layers.ConditionTime import condition_time from torch.utils.data import DataLoader, random_split -from random import shuffle +import numpy as np import math class MetNetPylight(pl.LightningModule, PyTorchModelHubMixin): @@ -47,12 +45,12 @@ def __init__( num_layers = self.config["num_layers"] num_att_layers = self.config["num_att_layers"] output_channels = self.config["output_channels"] - self.n_samples = n_samples - self.file_name = file_name self.forecast_steps = forecast_steps self.input_channels = input_channels self.output_channels = output_channels + self.n_samples = n_samples + self.file_name = file_name ''' self.preprocessor = MetNetPreprocessor( sat_channels=sat_channels, crop_size=input_size, use_space2depth=True, split_input=True @@ -67,7 +65,7 @@ def __init__( else: raise ValueError(f"Image_encoder {image_encoder} is not recognized") self.image_encoder = TimeDistributed(image_encoder) - #self.ct = ConditionTime(forecast_steps) + self.ct = ConditionTime(forecast_steps) self.temporal_enc = TemporalEncoder( image_encoder.output_channels, hidden_dim, ks=kernel_size, n_layers=num_layers ) @@ -88,7 +86,7 @@ def encode_timestep(self, x, fstep=1): #print("\n shape after preprocess: ", x.shape) # Condition Time - #x = self.ct(x, fstep) + x = self.ct(x, fstep) x = x.double() #print("\n shape after ct: ", x.shape) @@ -99,38 +97,49 @@ def encode_timestep(self, x, fstep=1): # Temporal Encoder #_, state = self.temporal_enc(self.drop(x)) - _, x = self.temporal_enc(x) + _, state = self.temporal_enc(x) #print("\n shape after temp enc: ", state.shape) - x = self.temporal_agg(x) + agg = self.temporal_agg(state) #print("\n shape after temporal_agg: ", agg.shape) - return x + return agg - def forward(self, imgs): + def forward(self, imgs,lead_time): """It takes a rank 5 tensor - imgs [bs, seq_len, channels, h, w] + - lead_time #random int between 0,self.forecast_steps """ # Compute all timesteps, probably can be parallelized res = [] - for i in range(self.forecast_steps): - x_i = self.encode_timestep(imgs, i) - out = self.head(x_i) - res.append(out) + x_i = self.encode_timestep(imgs, lead_time) + + out = self.head(x_i) + res.append(out) res = torch.stack(res, dim=1) return res def training_step(self, batch, batch_idx): x, y = batch - y_hat = self(x.float()) - loss = F.mse_loss(y_hat, y) + + + lead_time = np.random.randint(0,self.forecast_steps) + + y_hat = self(x.float(),lead_time) + loss = F.mse_loss(y_hat, y[:,lead_time]) pbar = {"training_loss": loss} return {"loss": loss, "progress_bar": pbar} def validation_step(self, batch, batch_idx): - results = self.training_step(batch, batch_idx) - return results + x, y = batch + lead_times = list(range(self.forecast_steps)) + loss = 0 + for lead_time in lead_times: + y_hat = self(x.float(),lead_time) + loss += F.mse_loss(y_hat, y[:,lead_time]) + + return {"loss": loss} def validation_epoch_end(self, val_step_outputs): avg_val_loss = torch.tensor([x["loss"] for x in val_step_outputs]).mean() @@ -138,97 +147,23 @@ def validation_epoch_end(self, val_step_outputs): return {"val_loss":avg_val_loss, "progress_bar":pbar} - def configure_optimizers(self): optimizer = optim.SGD(self.parameters(),lr=1e-2) return optimizer - def setup(self, stage = None): - - X,Y,X_dates,Y_dates = load_data(self.file_name, N = self.n_samples, lead_times = self.forecast_steps) - X = torch.from_numpy(X) - Y = torch.from_numpy(Y) - loaded_samples = X.shape[0] - split_list = list(range(loaded_samples)) - shuffle(split_list) - split_idx = int(loaded_samples*0.7) - - idx_training = split_list[:split_idx] - idx_val = split_list[split_idx:] - - X_train = [] - Y_train = [] - first = True - - for index in idx_training: - print(index) - for lead in range(self.forecast_steps): - x, y = X[index], Y[index] - - seq_len, ch, h, w = x.shape - ct = condition_time(x, lead, (h, w), seq_len=self.forecast_steps).repeat(seq_len, 1, 1, 1) - x = torch.cat([x, ct], dim=1) #HARDCODED - y = y[lead] - x = torch.unsqueeze(x,dim = 0) - y = torch.unsqueeze(y,dim = 0) - if first: - - X_train = x - Y_train = y - first = False - else: - - X_train = torch.cat((X_train,x), 0) - Y_train = torch.cat((Y_train,y), 0) - - - - - X_val = [] - Y_val = [] - first = True - - for index in idx_val: - for lead in range(self.forecast_steps): - x, y = X[index], Y[index] - - seq_len, ch, h, w = x.shape - ct = condition_time(x, lead, (h, w), seq_len=self.forecast_steps).repeat(seq_len, 1, 1, 1) - x = torch.cat([x, ct], dim=1) #HARDCODED - y = y[lead] - x = torch.unsqueeze(x,dim = 0) - y = torch.unsqueeze(y,dim = 0) - if first: - X_val = x - Y_val = y - first = False - else: - - X_val = torch.cat((X_val,x), 0) - Y_val = torch.cat((Y_val,y), 0) - - print("Train X shape: ", X_train.shape) - print("Train Y shape: ", Y_train.shape) - print("Validation X shape: ", X_val.shape) - print("Validation Y shape: ", Y_val.shape) - input() - - self.train_dataset = metnet_dataloader.MetNetDataset(X_train, Y_train) - self.val_dataset = metnet_dataloader.MetNetDataset(X_val, Y_val) - - - + data = metnet_dataloader.MetNetDataset(self.file_name, N = self.n_samples, lead_times = self.forecast_steps) + nsamples = len(data) + split_list = [math.floor(nsamples*0.7),math.floor(nsamples*0.3)] + split_list[0] += nsamples-sum(split_list) + self.train_data, self.val_data = random_split(data, split_list) def train_dataloader(self): - train_loader = DataLoader(dataset=self.train_dataset,batch_size=4, shuffle = True) - #val_loader = DataLoader(dataset=val,batch_size=4) - #test_loader = DataLoader(dataset=test,batch_size=4) + train_loader = DataLoader(dataset=self.train_data,batch_size=4) return train_loader - def val_dataloader(self): - val_loader = DataLoader(self.val_dataset) + val_loader = DataLoader(self.val_data) return val_loader From bf31068a5d22b3acec85300d5954317317131a90 Mon Sep 17 00:00:00 2001 From: Valterf Date: Sat, 26 Feb 2022 15:07:53 +0100 Subject: [PATCH 05/17] adding LSTM --- data_prep/prepare_data_MetNet.py | 20 +++++++------------- metnet/layers/ConvLSTM.py | 10 +++++----- metnet/models/metnet_pylight.py | 5 +++-- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/data_prep/prepare_data_MetNet.py b/data_prep/prepare_data_MetNet.py index c8960f1..a7dc608 100644 --- a/data_prep/prepare_data_MetNet.py +++ b/data_prep/prepare_data_MetNet.py @@ -8,7 +8,7 @@ from datetime import datetime, timedelta,date ''' -Output: 3D tensor of shape (N,128,128,240+46): +Output: 5D tensor of shape (n_samples, time_dim, channels, width, height): ''' @@ -56,10 +56,6 @@ def date_assertion(dates,expected_delta = 5): def h5_iterator(h5_file,maxN = 100,spaced = 1): """Iterates through the desired datafile and returns index, array and datetime""" - months = {"01":"January", "02":"February", "03":"March", - "04":"April", "05":"May", "06":"June", - "07":"July", "08":"August", "09":"September", - "10":"October", "11":"November", "12":"December"} with h5.File(h5_file,"r") as f: keys = list(f["data/pn157"].keys()) @@ -188,11 +184,7 @@ def datetime_encoder(data,dates,plotter = False): except AssertionError: print("Datetime dimensions seem wrong!") raise - '''for i,(day,min) in enumerate(zip(year_days,day_minutes)): - date_array[i,:,:,0] = np.sin(2*np.pi*day/365,dtype =data_type) - date_array[i,:,:,1] = np.cos(2*np.pi*day/365,dtype =data_type) - date_array[i,:,:,2] = np.sin(2*np.pi*min/(60*24),dtype =data_type) - date_array[i,:,:,3] = np.cos(2*np.pi*min/(60*24),dtype =data_type)''' + if plotter: fig,ax = plt.subplots(1,2) ax[0].scatter(date_array[:,0,0,0],date_array[:,0,0,1]) @@ -327,12 +319,14 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 print(f"\nConcatenating data and centercrop to dimenison: {data.shape} with shape [:,:,:,downsampled + centercrop]") - - + GAIN = 0.4 + OFFSET = -30 + data = data*GAIN + OFFSET maxx = np.max(data) - print("\nMAX (should be 255): ", maxx) + print("\nMAX DBZ data(should be 72): ", maxx) data = np.log(data+0.01)/4 data = np.nan_to_num(data) + data = np.tanh(data) print(f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}") diff --git a/metnet/layers/ConvLSTM.py b/metnet/layers/ConvLSTM.py index 9993832..f78b305 100644 --- a/metnet/layers/ConvLSTM.py +++ b/metnet/layers/ConvLSTM.py @@ -16,7 +16,7 @@ def __init__( hidden_dim: int, kernel_size: int, bias=True, - activation=torch.tanh, + activation=F.tanh, batchnorm=False, ): """ @@ -69,13 +69,13 @@ def forward(self, x: torch.Tensor, prev_state: list) -> tuple[torch.Tensor, torc cc_i, cc_f, cc_o, cc_g = torch.split(combined_conv, self.hidden_dim, dim=1) - i = torch.sigmoid(cc_i) - f = torch.sigmoid(cc_f) + i = F.sigmoid(cc_i) + f = F.sigmoid(cc_f) g = self.activation(cc_g) c_cur = f * c_prev + i * g - o = torch.sigmoid(cc_o) + o = F.sigmoid(cc_o) h_cur = o * self.activation(c_cur) @@ -115,7 +115,7 @@ def __init__( kernel_size: int, num_layers: int, bias=True, - activation=torch.tanh, + activation=F.tanh, batchnorm=False, ): """ diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index cbdc7fd..f3e23b7 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -6,7 +6,7 @@ import torch.nn.functional as F from torch import optim from data_prep import metnet_dataloader, prepare_data_MetNet -from metnet.layers import ConditionTime, ConvGRU, DownSampler, MetNetPreprocessor, TimeDistributed +from metnet.layers import ConditionTime, ConvGRU, DownSampler, MetNetPreprocessor, TimeDistributed, ConvLSTM from torch.utils.data import DataLoader, random_split import numpy as np import math @@ -170,7 +170,8 @@ def val_dataloader(self): class TemporalEncoder(nn.Module): def __init__(self, in_channels, out_channels=384, ks=3, n_layers=1): super().__init__() - self.rnn = ConvGRU(in_channels, out_channels, (ks, ks), n_layers, batch_first=True) + #self.rnn = ConvGRU(in_channels, out_channels, (ks, ks), n_layers, batch_first=True) + self.rnn = ConvLSTM(in_channels, out_channels, ks, n_layers) def forward(self, x): x, h = self.rnn(x) From d40557b93cc5d5e30d59db4725e89dda76bba0c0 Mon Sep 17 00:00:00 2001 From: Valter Fallenius Date: Tue, 1 Mar 2022 09:54:53 +0100 Subject: [PATCH 06/17] Adding wandb module --- MetNet_lightning.py | 14 +++++-- data_prep/prepare_data_MetNet.py | 30 +++++--------- metnet/models/metnet_pylight.py | 70 ++++++++++++++++++++++++-------- 3 files changed, 75 insertions(+), 39 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index bc12721..69558b7 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -3,19 +3,25 @@ import torch.nn.functional as F from data_prep.prepare_data_MetNet import load_data import pytorch_lightning as pl +from pytorch_lightning.loggers import WandbLogger +import wandb +wandb.login() model = MetNetPylight( - hidden_dim=128, #384 original paper - forecast_steps=6, #240 original paper + hidden_dim=256, #384 original paper + forecast_steps=60, #240 original paper input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 output_channels=51, #512 input_size=112, # 112 - n_samples = 500, + n_samples = 1000, ) #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. print(model) +wandb_logger = WandbLogger(project="lit-wandb") + +trainer = pl.Trainer(max_epochs=10, gpus=-1,log_every_n_steps=20, logger = wandb_logger) -trainer = pl.Trainer(fast_dev_run= True) input("train? press enter to continue...") trainer.fit(model) +wandb.finish() diff --git a/data_prep/prepare_data_MetNet.py b/data_prep/prepare_data_MetNet.py index a7dc608..b732290 100644 --- a/data_prep/prepare_data_MetNet.py +++ b/data_prep/prepare_data_MetNet.py @@ -12,14 +12,6 @@ ''' -#from grid_with_projection import GridWithProjection -def h5_writer(directory,data,dates): - with h5.File(f'network_data.h5', 'w') as target: - for date in dates: - target.create_dataset(target_name,data = array,compression="gzip") - -def do_with_items(name): - return name @@ -36,12 +28,13 @@ def space_to_depth(x, block_size): def date_assertion(dates,expected_delta = 5): for date1,date2 in zip(dates[0:-1],dates[1:]): list1 = date1.split("_") - list1 = list1[0:3] + list1[3].split(":") - #print(date1, date2) + #print(list1) + y1, m1, d1, hour1, minute1 = [int(a) for a in list1] + datetime1 = datetime(y1, m1, d1, hour=hour1, minute=minute1) list2 = date2.split("_") - list2 = list2[0:3] + list2[3].split(":") + y2, m2, d2, hour2, minute2 = [int(a) for a in list2] datetime2 = datetime(y2, m2, d2, hour=hour2, minute=minute2) @@ -58,20 +51,20 @@ def h5_iterator(h5_file,maxN = 100,spaced = 1): with h5.File(h5_file,"r") as f: - keys = list(f["data/pn157"].keys()) + keys = list(f.keys()) for i,name in enumerate(keys): if i%spaced!=0: continue j = i//spaced - obj = f["data/pn157/"+name] + obj = f[name] #print(name, obj) if maxN: if i//spaced>=maxN: break #print(name) - typ, date = name.split("-") - y, m, d, t = date.split("_") + date = name + y, m, d, hh, mm = date.split("_") #title = f"Year: {y} month: {months[m]} day: {d} time: {t}" array = np.array(obj) #flip array @@ -119,7 +112,6 @@ def temporal_concatenation(data,dates,targets,target_dates,concat = 7, overlap = print(f"Warning, dates are not alligned! Skipping: {i}:{i+seq_length}") print(temp_dates) print(temp_dates_target) - input() continue X.append(temp_input) X_dates.append(temp_dates) @@ -155,7 +147,7 @@ def datetime_encoder(data,dates,plotter = False): if (i+1)%1000==0: print("Dates loaded for encoding: ",i+1) list1 = date_string.split("_") - list1 = list1[0:3] + list1[3].split(":") + year,month,day, hour, minute = [int(a) for a in list1] date_object = date(year,month,day) @@ -239,7 +231,7 @@ def longlatencoding(data): -def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True): +def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True, rain_step = 2): #15 minutes between datapoints is default --> spaced = 3 snapshots = [] dates = [] @@ -253,7 +245,7 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 sys.stdout = open(os.devnull, 'w') for i, array,date in h5_iterator(h5_path, N): if (i+1)%1000==0: - print("Loaded samples: ",i+1) + print("Loaded samples: ",(i+1)//spaced) if i%spaced==0: snapshots.append(array) diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index f3e23b7..ba7854d 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -15,7 +15,7 @@ class MetNetPylight(pl.LightningModule, PyTorchModelHubMixin): def __init__( self, image_encoder: str = "downsampler", #4 CNN layers - file_name: str = "combination_all_pn157.h5", + file_name: str = "data3_500k.h5", input_channels: int = 12, n_samples: int = 1000, sat_channels: int = 12, @@ -25,8 +25,10 @@ def __init__( kernel_size: int = 3, num_layers: int = 1, num_att_layers: int = 4, - forecast_steps: int = 48, + forecast_steps: int = 240, temporal_dropout: float = 0.2, + num_workers: int = 32, + rain_step: int = 2, #in millimeters **kwargs, ): super(MetNetPylight, self).__init__() @@ -51,6 +53,8 @@ def __init__( self.output_channels = output_channels self.n_samples = n_samples self.file_name = file_name + self.workers = num_workers + self.rain_step = rain_step ''' self.preprocessor = MetNetPreprocessor( sat_channels=sat_channels, crop_size=input_size, use_space2depth=True, split_input=True @@ -78,6 +82,8 @@ def __init__( self.head = nn.Conv2d(hidden_dim, output_channels, kernel_size=(1, 1)) # Reduces to mask self.double() + + self.save_hyperparameters() def encode_timestep(self, x, fstep=1): #print("\n shape before preprocess: ", x.shape) @@ -111,14 +117,15 @@ def forward(self, imgs,lead_time): """ # Compute all timesteps, probably can be parallelized - res = [] + x_i = self.encode_timestep(imgs, lead_time) out = self.head(x_i) - res.append(out) - res = torch.stack(res, dim=1) - return res + + + + return out def training_step(self, batch, batch_idx): x, y = batch @@ -128,8 +135,8 @@ def training_step(self, batch, batch_idx): y_hat = self(x.float(),lead_time) loss = F.mse_loss(y_hat, y[:,lead_time]) - pbar = {"training_loss": loss} - return {"loss": loss, "progress_bar": pbar} + self.log("train/loss", loss, on_epoch=True) + return {"loss": loss} def validation_step(self, batch, batch_idx): x, y = batch @@ -142,10 +149,15 @@ def validation_step(self, batch, batch_idx): return {"loss": loss} def validation_epoch_end(self, val_step_outputs): - avg_val_loss = torch.tensor([x["loss"] for x in val_step_outputs]).mean() - pbar = {"avg_val_loss":avg_val_loss} + outs = [x["loss"] for x in val_step_outputs] + print(outs) + + avg_val_loss = torch.tensor(outs).mean() + flattened = torch.flatten(torch.cat(outs)) + self.logger.experiment.log({"validation/losses": wandb.Histogram(flattened.to("cpu")),"global step": self.global_step}) + - return {"val_loss":avg_val_loss, "progress_bar":pbar} + return {"val_loss":avg_val_loss} def configure_optimizers(self): optimizer = optim.SGD(self.parameters(),lr=1e-2) @@ -160,22 +172,48 @@ def setup(self, stage = None): def train_dataloader(self): - train_loader = DataLoader(dataset=self.train_data,batch_size=4) + train_loader = DataLoader(dataset=self.train_data,batch_size=10, num_workers = self.workers) return train_loader def val_dataloader(self): - val_loader = DataLoader(self.val_data) + val_loader = DataLoader(self.val_data, num_workers = self.workers) return val_loader - + def threshhold_loss(self, pred, target): + thresh_bin = thresh//self.rain_step + class TemporalEncoder(nn.Module): def __init__(self, in_channels, out_channels=384, ks=3, n_layers=1): super().__init__() - #self.rnn = ConvGRU(in_channels, out_channels, (ks, ks), n_layers, batch_first=True) - self.rnn = ConvLSTM(in_channels, out_channels, ks, n_layers) + self.rnn = ConvGRU(in_channels, out_channels, (ks, ks), n_layers, batch_first=True) + #self.rnn = ConvLSTM(in_channels, out_channels, ks, n_layers) def forward(self, x): x, h = self.rnn(x) return (x, h[-1]) + +def rain_transform(x): + rain = (10**(x / 10.0) / 200.0)**(1.0 / 1.6) + return rain + +class RainfieldCallback(pl.Callback): + def __init__(self, val_samples, num_samples=1): + super().__init__() + self.val_imgs, self.val_Y = val_samples + self.val_imgs = self.val_imgs[:num_samples] + self.val_Y = self.val_Y[:num_samples] + + + def on_validation_epoch_end(self, trainer, pl_module, lead_times = 60): + val_imgs = self.val_imgs.to(device=pl_module.device) + for lead_time in range(lead_times): + y_hat = pl_module(val_imgs) + preds = torch.argmax(logits, 1) + + trainer.logger.experiment.log({ + "examples": [wandb.Image(x, caption=f"Pred:{pred}, Label:{y}") + for x, pred, y in zip(val_imgs, preds, self.val_labels)], + "global_step": trainer.global_step + }) ''' def feat2image(x, target_size=(128, 128)): From d4b5e5ae11f665551653e9625c08afc6455be042 Mon Sep 17 00:00:00 2001 From: ValterFallenius Date: Wed, 2 Mar 2022 15:04:21 +0100 Subject: [PATCH 07/17] trying to train with smallest possible network --- MetNet_lightning.py | 13 ++-- data_prep/metnet_dataloader.py | 4 +- data_prep/prepare_data_MetNet.py | 43 +++++++++---- metnet/models/metnet_pylight.py | 104 ++++++++++++++++++++++--------- 4 files changed, 116 insertions(+), 48 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index 69558b7..ac33e29 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -5,23 +5,26 @@ import pytorch_lightning as pl from pytorch_lightning.loggers import WandbLogger import wandb +from pytorch_lightning.callbacks import DeviceStatsMonitor wandb.login() model = MetNetPylight( - hidden_dim=256, #384 original paper - forecast_steps=60, #240 original paper + hidden_dim=8, #384 original paper + forecast_steps=1, #240 original paper input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 output_channels=51, #512 input_size=112, # 112 - n_samples = 1000, + n_samples = 100, + num_workers = 8, + batch_size = 1, + learning_rate = 10 ) #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. print(model) wandb_logger = WandbLogger(project="lit-wandb") -trainer = pl.Trainer(max_epochs=10, gpus=-1,log_every_n_steps=20, logger = wandb_logger) +trainer = pl.Trainer(max_epochs=5000, gpus=-1,log_every_n_steps=1, logger = wandb_logger,strategy="ddp_find_unused_parameters_false") -input("train? press enter to continue...") trainer.fit(model) wandb.finish() diff --git a/data_prep/metnet_dataloader.py b/data_prep/metnet_dataloader.py index 3fba6ac..c7fb98e 100644 --- a/data_prep/metnet_dataloader.py +++ b/data_prep/metnet_dataloader.py @@ -5,7 +5,7 @@ import numpy as np class MetNetDataset(Dataset): - def __init__(self,file_name,N, lead_times = 60): + def __init__(self,file_name,N, lead_times = 60, rain_step = 0.2, n_bins = 512): """ Input: file_name: path to hdf5-file. @@ -15,7 +15,7 @@ def __init__(self,file_name,N, lead_times = 60): X: numpy array shape (None, time, channels, width, height), Y: numpy array shape (None, lead_times, channels, width, height) """ - X,Y,X_dates,Y_dates = load_data(file_name, N = N, lead_times = lead_times) + X,Y,X_dates,Y_dates = load_data(file_name, N = N, lead_times = lead_times, rain_step = rain_step, n_bins = n_bins) self.x = torch.from_numpy(X) diff --git a/data_prep/prepare_data_MetNet.py b/data_prep/prepare_data_MetNet.py index b732290..c5a1ed0 100644 --- a/data_prep/prepare_data_MetNet.py +++ b/data_prep/prepare_data_MetNet.py @@ -110,8 +110,8 @@ def temporal_concatenation(data,dates,targets,target_dates,concat = 7, overlap = date_assertion(fiver,expected_delta = 5) except AssertionError: print(f"Warning, dates are not alligned! Skipping: {i}:{i+seq_length}") - print(temp_dates) - print(temp_dates_target) + #print(temp_dates) + #print(temp_dates_target) continue X.append(temp_input) X_dates.append(temp_dates) @@ -231,7 +231,7 @@ def longlatencoding(data): -def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True, rain_step = 2): +def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True, rain_step = 0.2, n_bins=512): #15 minutes between datapoints is default --> spaced = 3 snapshots = [] dates = [] @@ -258,7 +258,7 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 all_data = np.array(all_snapshots) print("\nDatatype data: ", data.dtype) - print("\nInput data shape: ", data.shape) + print("\nInput data shape: ", data.shape, " size: ", sys.getsizeof(data)) x0,x1,y0,y1 = square @@ -316,14 +316,22 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 data = data*GAIN + OFFSET maxx = np.max(data) print("\nMAX DBZ data(should be 72): ", maxx) - data = np.log(data+0.01)/4 - data = np.nan_to_num(data) - data = np.tanh(data) + data_new = np.empty(data.shape) + runs = data_new.shape[0]//5000 + for run in range(runs): + data_new[run:run+5000] = np.log(data[run:run+5000]+0.01)/4 + data_new[run:run+5000] = np.nan_to_num(data_new[run:run+5000]) + data_new[run:run+5000] = np.tanh(data_new[run:run+5000]) + + data_new[runs:] = np.log(data[runs:]+0.01)/4 + data_new[runs:] = np.nan_to_num(data_new[runs:]) + data_new[runs:] = np.tanh(data_new[runs:]) + print(f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}") - data = longlatencoding(data) + data = longlatencoding(data_new) print(f"\nConcatenating data with long, lat and elevation. New shape: {data.shape}, dtype: {data.dtype}") data = datetime_encoder(data,dates,plotter=False) @@ -345,10 +353,10 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 plt.imshow(X[0,0,channel,:,:]) plt.show()''' - Y = Y*0.4 -30 + Y = Y*GAIN + OFFSET print("MINMAX Y AFTER GAIN + OFFSET", np.min(Y), np.max(Y)) - Y = rain_binned(Y) + Y = rain_binned(Y, n_bins = n_bins, increment = rain_step) print(f"\nDone with binning targets into bins, target shape: {Y.shape}") @@ -371,23 +379,32 @@ def rain_binned(Y, n_bins = 51,increment = 2): rain = (10**(Y / 10.0) / 200.0)**(1.0 / 1.6) print("RAIN MINMAX: ", np.min(rain), np.max(rain)) - + '''for lead_time in range(leads): + plt.imshow(rain[10,lead_time,:,:]) + plt.show()''' rain_bins = np.zeros((n,leads,n_bins,w,h)) + counter = [] for i in range(n_bins-1): bin_min = i*increment bin_max = (i+1)*increment rain_bin = np.zeros((n,leads,w,h)) #Y.shape = (None,lead_times, bin_channel, width/4,heigth/4) idx = np.where(np.logical_and(rain>=bin_min, rain=n_bins*increment) rain_bin[idx] = 1 rain_bins[:,:,n_bins-1,:,:] = rain_bin + counter.append(len(idx)) + print("RAINBINS: ", rain_bins.size, " counter size: ", sum(counter)) + + print(counter) + '''plt.plot(counter) + plt.show()''' return rain_bins diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index ba7854d..8212362 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -4,12 +4,14 @@ from huggingface_hub import PyTorchModelHubMixin import pytorch_lightning as pl import torch.nn.functional as F +from torch.nn import CrossEntropyLoss from torch import optim from data_prep import metnet_dataloader, prepare_data_MetNet from metnet.layers import ConditionTime, ConvGRU, DownSampler, MetNetPreprocessor, TimeDistributed, ConvLSTM from torch.utils.data import DataLoader, random_split import numpy as np import math +import matplotlib.pyplot as plt class MetNetPylight(pl.LightningModule, PyTorchModelHubMixin): def __init__( @@ -20,15 +22,17 @@ def __init__( n_samples: int = 1000, sat_channels: int = 12, input_size: int = 256, - output_channels: int = 12, + output_channels: int = 512, hidden_dim: int = 384, kernel_size: int = 3, num_layers: int = 1, - num_att_layers: int = 4, + num_att_layers: int = 1, forecast_steps: int = 240, temporal_dropout: float = 0.2, num_workers: int = 32, - rain_step: int = 2, #in millimeters + batch_size: int = 8, + rain_step: int = 0.2, #in millimeters + learning_rate: int = 1e-2, **kwargs, ): super(MetNetPylight, self).__init__() @@ -55,6 +59,8 @@ def __init__( self.file_name = file_name self.workers = num_workers self.rain_step = rain_step + self.learning_rate = learning_rate + self.batch_size = batch_size ''' self.preprocessor = MetNetPreprocessor( sat_channels=sat_channels, crop_size=input_size, use_space2depth=True, split_input=True @@ -63,7 +69,7 @@ def __init__( new_channels = sat_channels * 4 # Space2Depth new_channels *= 2 # Concatenate two of them together input_channels = input_channels - sat_channels + new_channels''' - #self.drop = nn.Dropout(temporal_dropout) + self.drop = nn.Dropout(temporal_dropout) if image_encoder in ["downsampler", "default"]: image_encoder = DownSampler(input_channels + forecast_steps) else: @@ -91,7 +97,7 @@ def encode_timestep(self, x, fstep=1): #x = self.preprocessor(x) #print("\n shape after preprocess: ", x.shape) # Condition Time - + x = self.ct(x, fstep) x = x.double() @@ -118,68 +124,68 @@ def forward(self, imgs,lead_time): # Compute all timesteps, probably can be parallelized + #plot_channels(imgs) + x = self.encode_timestep(imgs, lead_time) + #plot_channels(x) - x_i = self.encode_timestep(imgs, lead_time) - - out = self.head(x_i) + out = self.head(x) - return out + return torch.softmax(out,dim=1) def training_step(self, batch, batch_idx): x, y = batch lead_time = np.random.randint(0,self.forecast_steps) - + L = CrossEntropyLoss() y_hat = self(x.float(),lead_time) - loss = F.mse_loss(y_hat, y[:,lead_time]) + loss = L(y_hat, y[:,lead_time]) self.log("train/loss", loss, on_epoch=True) return {"loss": loss} def validation_step(self, batch, batch_idx): x, y = batch lead_times = list(range(self.forecast_steps)) + loss = 0 + L = CrossEntropyLoss() for lead_time in lead_times: y_hat = self(x.float(),lead_time) - loss += F.mse_loss(y_hat, y[:,lead_time]) - + + loss += L(y_hat, y[:,lead_time]) + self.log("validation/loss_epoch", loss, on_step=False, on_epoch=True) + if self.global_step%100 == 0: + plot_category(y[0,0],y_hat[0],self.output_channels,self.rain_step,self.device) return {"loss": loss} def validation_epoch_end(self, val_step_outputs): - outs = [x["loss"] for x in val_step_outputs] - print(outs) - + outs = tuple([x["loss"] for x in val_step_outputs]) avg_val_loss = torch.tensor(outs).mean() - flattened = torch.flatten(torch.cat(outs)) - self.logger.experiment.log({"validation/losses": wandb.Histogram(flattened.to("cpu")),"global step": self.global_step}) - - return {"val_loss":avg_val_loss} def configure_optimizers(self): - optimizer = optim.SGD(self.parameters(),lr=1e-2) + optimizer = optim.SGD(self.parameters(),lr=self.learning_rate) return optimizer def setup(self, stage = None): - data = metnet_dataloader.MetNetDataset(self.file_name, N = self.n_samples, lead_times = self.forecast_steps) + data = metnet_dataloader.MetNetDataset(self.file_name, N = self.n_samples, lead_times = self.forecast_steps, rain_step = self.rain_step, n_bins = self.output_channels) nsamples = len(data) split_list = [math.floor(nsamples*0.7),math.floor(nsamples*0.3)] split_list[0] += nsamples-sum(split_list) self.train_data, self.val_data = random_split(data, split_list) + def train_dataloader(self): - - - train_loader = DataLoader(dataset=self.train_data,batch_size=10, num_workers = self.workers) + train_loader = DataLoader(dataset=self.train_data,batch_size=self.batch_size, num_workers = self.workers) return train_loader def val_dataloader(self): - val_loader = DataLoader(self.val_data, num_workers = self.workers) + val_loader = DataLoader(self.val_data,batch_size=self.batch_size, num_workers = self.workers) return val_loader - def threshhold_loss(self, pred, target): - thresh_bin = thresh//self.rain_step + def thresh_F1(self, pred, target,thresh = 0.2): + thresh_bin = thresh//self.rain_step + norms = torch.sum(pred, dim=1) class TemporalEncoder(nn.Module): def __init__(self, in_channels, out_channels=384, ks=3, n_layers=1): @@ -194,7 +200,49 @@ def forward(self, x): def rain_transform(x): rain = (10**(x / 10.0) / 200.0)**(1.0 / 1.6) return rain + +def plot_category(y,y_hat,bins,increment,title,thresh_bin = 2): + #accepts y.shape = (bins,w,h) + print(title, y.shape) + print(title, y_hat.shape) + _, w, h = y.shape + a1 = torch.empty((w,h)) + a2 = torch.empty((w,h)) + part_1_a = torch.sum(y[0:thresh_bin], dim = 0) + part_1_b = torch.sum(y[thresh_bin:], dim = 0) + part_2_a = torch.sum(y_hat[0:thresh_bin], dim = 0) + part_2_b = torch.sum(y_hat[thresh_bin:], dim = 0) + idx_1_below = torch.where(part_1_a==1) + idx_1_above = torch.where(part_1_b==1) + idx_2_below = torch.where(part_2_a<=part_2_b) + idx_2_above = torch.where(part_2_a>part_2_b) + a1[idx_1_below] = 0 + a1[idx_1_above] = 1 + a2[idx_2_below] = 0 + a2[idx_2_above] = 1 + y = a1.cpu().detach().numpy() + y_hat = a2.cpu().detach().numpy() + fig, ax = plt.subplots(1,2) + fig.suptitle(str(title)) + ax[0].imshow(y) + ax[1].imshow(y_hat) + plt.show() + +def plot_channels(x): + print("CHANNEL SHAPE: ", x.shape) + if len(x.shape)==5: + for channel in range(x.shape[2]): + + plt.imshow(x[0,0,channel,:,:].cpu().detach().numpy()) + plt.title(str(channel)) + plt.show() + else: + for channel in range(x.shape[1]): + + plt.imshow(x[0,channel,:,:].cpu().detach().numpy()) + plt.title(str(channel)) + plt.show() class RainfieldCallback(pl.Callback): def __init__(self, val_samples, num_samples=1): super().__init__() From ffbb020ac81058b189a3320cc9a5d186c8be6bf7 Mon Sep 17 00:00:00 2001 From: ValterFallenius Date: Fri, 4 Mar 2022 16:14:34 +0100 Subject: [PATCH 08/17] searching for bugs --- MetNet_lightning.py | 2 +- metnet/models/metnet_pylight.py | 62 +++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index ac33e29..6aef262 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -13,7 +13,7 @@ hidden_dim=8, #384 original paper forecast_steps=1, #240 original paper input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 - output_channels=51, #512 + output_channels=6, #512 input_size=112, # 112 n_samples = 100, num_workers = 8, diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index 8212362..0ab0d8b 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -97,22 +97,28 @@ def encode_timestep(self, x, fstep=1): #x = self.preprocessor(x) #print("\n shape after preprocess: ", x.shape) # Condition Time - + plot_channels(x, 1, tit_add = "before ct") x = self.ct(x, fstep) + x = x.double() + + #print("\n shape after ct: ", x.shape) ##CNN x = self.image_encoder(x) + #plot_channels(x, 1, tit_add = "after downsampler") #print("\n shape after image_encoder: ", x.shape) # Temporal Encoder #_, state = self.temporal_enc(self.drop(x)) _, state = self.temporal_enc(x) + #plot_channels(x, 1, tit_add = "after temporal_enc") #print("\n shape after temp enc: ", state.shape) agg = self.temporal_agg(state) + plot_channels(x, 1, tit_add = "after agg") #print("\n shape after temporal_agg: ", agg.shape) return agg @@ -123,30 +129,39 @@ def forward(self, imgs,lead_time): """ # Compute all timesteps, probably can be parallelized - - #plot_channels(imgs) + print("in forward") + #plot_channels(imgs, 2, tit_add = "input") x = self.encode_timestep(imgs, lead_time) - #plot_channels(x) - + #plot_channels(x, 2, tit_add = "after encode") + print("shape before head: ", x.shape) out = self.head(x) + print("shape after head: ", out.shape) + plot_channels(x, 1, tit_add = "after head") + out = torch.softmax(out,dim=1) + print("shape after softmax: ", out.shape) + plot_channels(out, 1, tit_add = "after softmax") - - - return torch.softmax(out,dim=1) + return out def training_step(self, batch, batch_idx): + x, y = batch - + print("training_step len: ", x.shape[0]) lead_time = np.random.randint(0,self.forecast_steps) L = CrossEntropyLoss() y_hat = self(x.float(),lead_time) + for i in range(x.shape[0]): + plot_bins(y_hat[i], "y_hat") + plot_bins(y[i,0], "y") loss = L(y_hat, y[:,lead_time]) self.log("train/loss", loss, on_epoch=True) return {"loss": loss} def validation_step(self, batch, batch_idx): + x, y = batch + print("validation_step len: ", x.shape[0]) lead_times = list(range(self.forecast_steps)) loss = 0 @@ -156,7 +171,7 @@ def validation_step(self, batch, batch_idx): loss += L(y_hat, y[:,lead_time]) self.log("validation/loss_epoch", loss, on_step=False, on_epoch=True) - if self.global_step%100 == 0: + if self.global_step%100 == 1: plot_category(y[0,0],y_hat[0],self.output_channels,self.rain_step,self.device) return {"loss": loss} @@ -228,20 +243,37 @@ def plot_category(y,y_hat,bins,increment,title,thresh_bin = 2): ax[0].imshow(y) ax[1].imshow(y_hat) plt.show() + +def plot_bins(y, title=""): + N = y.shape[0] + side = int(N**0.5) + if side**2x.shape[2]: maxN = x.shape[2] + channels = np.random.choice(x.shape[2],maxN, replace = False) + for channel in channels: plt.imshow(x[0,0,channel,:,:].cpu().detach().numpy()) - plt.title(str(channel)) + plt.title(str(channel)+" " + tit_add) plt.show() else: - for channel in range(x.shape[1]): + if not maxN or maxN>x.shape[1]: maxN = x.shape[1] + channels = np.random.choice(x.shape[1],maxN, replace = False) + for channel in channels: plt.imshow(x[0,channel,:,:].cpu().detach().numpy()) - plt.title(str(channel)) + plt.title(str(channel)+" " + tit_add) plt.show() class RainfieldCallback(pl.Callback): def __init__(self, val_samples, num_samples=1): From ad9117f236dda91deadd79d3da17231eb6916c7c Mon Sep 17 00:00:00 2001 From: ValterFallenius Date: Sat, 5 Mar 2022 14:16:27 +0100 Subject: [PATCH 09/17] bug searching with plots --- MetNet_lightning.py | 4 +-- metnet/models/metnet_pylight.py | 58 +++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index 6aef262..aa09678 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -18,13 +18,13 @@ n_samples = 100, num_workers = 8, batch_size = 1, - learning_rate = 10 + learning_rate = 1e-2 ) #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. print(model) wandb_logger = WandbLogger(project="lit-wandb") -trainer = pl.Trainer(max_epochs=5000, gpus=-1,log_every_n_steps=1, logger = wandb_logger,strategy="ddp_find_unused_parameters_false") +trainer = pl.Trainer(track_grad_norm = 2, max_epochs=5000, gpus=-1,log_every_n_steps=1, logger = wandb_logger,strategy="ddp_find_unused_parameters_false") trainer.fit(model) wandb.finish() diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index 0ab0d8b..6c76012 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -97,7 +97,7 @@ def encode_timestep(self, x, fstep=1): #x = self.preprocessor(x) #print("\n shape after preprocess: ", x.shape) # Condition Time - plot_channels(x, 1, tit_add = "before ct") + #plot_channels(x, 1, tit_add = "INPUT") x = self.ct(x, fstep) x = x.double() @@ -108,17 +108,17 @@ def encode_timestep(self, x, fstep=1): ##CNN x = self.image_encoder(x) - #plot_channels(x, 1, tit_add = "after downsampler") - #print("\n shape after image_encoder: ", x.shape) + #plot_channels(x, 1, tit_add = "after image_encoder") + print("\n shape after image_encoder: ", x.shape) # Temporal Encoder #_, state = self.temporal_enc(self.drop(x)) _, state = self.temporal_enc(x) - #plot_channels(x, 1, tit_add = "after temporal_enc") - #print("\n shape after temp enc: ", state.shape) + plot_channels(state, 1, tit_add = "after temporal_enc") + print("\n shape after temp enc: ", state.shape) agg = self.temporal_agg(state) - plot_channels(x, 1, tit_add = "after agg") + #plot_channels(x, 1, tit_add = "after agg") #print("\n shape after temporal_agg: ", agg.shape) return agg @@ -136,12 +136,12 @@ def forward(self, imgs,lead_time): print("shape before head: ", x.shape) out = self.head(x) print("shape after head: ", out.shape) - plot_channels(x, 1, tit_add = "after head") - out = torch.softmax(out,dim=1) - print("shape after softmax: ", out.shape) - plot_channels(out, 1, tit_add = "after softmax") - - return out + #plot_channels(out, 1, tit_add = "after head") + soft = torch.softmax(out,dim=1) + print("shape after softmax: ", soft.shape) + #plot_channels(out, 1, tit_add = "after softmax") + plot_bins(out[0], " after head",soft[0], "after softmax") + return soft def training_step(self, batch, batch_idx): @@ -151,9 +151,10 @@ def training_step(self, batch, batch_idx): lead_time = np.random.randint(0,self.forecast_steps) L = CrossEntropyLoss() y_hat = self(x.float(),lead_time) - for i in range(x.shape[0]): - plot_bins(y_hat[i], "y_hat") - plot_bins(y[i,0], "y") + if self.global_step%100 == 0: + for i in range(x.shape[0]): + plot_bins(y_hat[i], " y_hat",y[i,0], "y") + loss = L(y_hat, y[:,lead_time]) self.log("train/loss", loss, on_epoch=True) return {"loss": loss} @@ -244,16 +245,21 @@ def plot_category(y,y_hat,bins,increment,title,thresh_bin = 2): ax[1].imshow(y_hat) plt.show() -def plot_bins(y, title=""): - N = y.shape[0] +def plot_bins(y1, title1="", y2 = 0, title2=""): + N = y1.shape[0] side = int(N**0.5) if side**2 Date: Fri, 18 Mar 2022 08:33:40 +0100 Subject: [PATCH 10/17] Bug fix positional encoding --- MetNet_lightning.py | 20 +-- data_prep/prepare_data_MetNet.py | 210 +++++++++++++++++++++++++------ metnet/models/metnet_pylight.py | 100 +++++++++------ 3 files changed, 246 insertions(+), 84 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index aa09678..d86405b 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -10,21 +10,25 @@ wandb.login() model = MetNetPylight( - hidden_dim=8, #384 original paper - forecast_steps=1, #240 original paper + hidden_dim=128, #384 original paper + forecast_steps=60, #240 original paper input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 - output_channels=6, #512 + output_channels=30, #512 input_size=112, # 112 - n_samples = 100, - num_workers = 8, - batch_size = 1, - learning_rate = 1e-2 + n_samples = 50000, + num_workers = 32, + batch_size = 5, + learning_rate = 1e-2, + num_att_layers = 4, + plot_every = 100, #Plot every global_step + rain_step = 0.1, + momentum = 0.9, ) #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. print(model) wandb_logger = WandbLogger(project="lit-wandb") -trainer = pl.Trainer(track_grad_norm = 2, max_epochs=5000, gpus=-1,log_every_n_steps=1, logger = wandb_logger,strategy="ddp_find_unused_parameters_false") +trainer = pl.Trainer(track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=1, logger = wandb_logger,strategy="ddp") trainer.fit(model) wandb.finish() diff --git a/data_prep/prepare_data_MetNet.py b/data_prep/prepare_data_MetNet.py index c5a1ed0..130f353 100644 --- a/data_prep/prepare_data_MetNet.py +++ b/data_prep/prepare_data_MetNet.py @@ -204,16 +204,22 @@ def longlatencoding(data): lon_mean, lon_std = np.mean(lon), np.std(lon) lat_mean, lat_std = np.mean(lat), np.std(lat) + elev /= np.max(np.abs(elev)) elev_mean, elev_std = np.mean(elev), np.std(elev) - + try: + assert lon_std != 0 + assert lat_std != 0 + assert elev_std != 0 + except AssertionError: + print("WARNING: LON LAT OR ELEV STD == 0") lon = (lon-lon_mean)/lon_std lat = (lat-lat_mean)/lat_std elev = (elev-elev_mean)/elev_std elev = np.log(elev-np.min(elev)+0.1) - elev /= np.max(elev) - lon = np.tanh(lon) - lat = np.tanh(lat) - elev = np.tanh(elev) + + #lon = np.tanh(lon) + #lat = np.tanh(lat) + #elev = np.tanh(elev) print("MINMAX lon: ", np.min(lon), np.max(lon)) print("MINMAX lat: ", np.min(lat), np.max(lat)) print("MINMAX elev: ", np.min(elev), np.max(elev)) @@ -237,26 +243,31 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 dates = [] all_snapshots = [] Y_dates = [] - - - - + array_mean = 0 + n = 0 if not printer: sys.stdout = open(os.devnull, 'w') for i, array,date in h5_iterator(h5_path, N): + if (i+1)%1000==0: print("Loaded samples: ",(i+1)//spaced) - + if i%spaced==0: snapshots.append(array) dates.append(date) all_snapshots.append(array) + array_mean += np.mean(array) + n+=1 + Y_dates.append(date) + print("MEAN", array_mean/n) print("Done loading samples! \n") - + data = np.array(snapshots) all_data = np.array(all_snapshots) - + + del(snapshots)#MANAGE MEMORY + del(all_snapshots)#MANAGE MEMORY print("\nDatatype data: ", data.dtype) print("\nInput data shape: ", data.shape, " size: ", sys.getsizeof(data)) @@ -272,8 +283,12 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 length_y = (y1-y0)//16 #size of Y is 16 times smaller Y_lim_x = slice(center_x-length_x//2,center_x+length_x//2) Y_lim_y = slice(center_y-length_y//2,center_y+length_y//2) + print("SLICED x: ",Y_lim_x) + print("SLICED y: ",Y_lim_y) Y = all_data[:,Y_lim_y,Y_lim_x] + del(all_data) #MANAGE MEMORY print(f"\nY shape here (not ready): {Y.shape}") + data = data[:,y_lim,x_lim] print(f"\nSliced data to dimensions {data.shape}") @@ -297,8 +312,6 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 center = np.expand_dims(center, axis=3) print(f"\nAdding channel dimension to centercrop, new shape: {center.shape}") if spacedepth==True: - #print(f"\nStarting space-to-depth transform on data") - data = space_to_depth(data,box) print(f"\nSpace-to-depth done! Data shape: {data.shape}") @@ -314,18 +327,34 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 GAIN = 0.4 OFFSET = -30 data = data*GAIN + OFFSET + + data_after_gained = np.copy(data) maxx = np.max(data) print("\nMAX DBZ data(should be 72): ", maxx) data_new = np.empty(data.shape) - runs = data_new.shape[0]//5000 - for run in range(runs): + N = data_new.shape[0] + runs = N//5000 + for run in range(0,N,5000): data_new[run:run+5000] = np.log(data[run:run+5000]+0.01)/4 data_new[run:run+5000] = np.nan_to_num(data_new[run:run+5000]) data_new[run:run+5000] = np.tanh(data_new[run:run+5000]) - data_new[runs:] = np.log(data[runs:]+0.01)/4 - data_new[runs:] = np.nan_to_num(data_new[runs:]) - data_new[runs:] = np.tanh(data_new[runs:]) + data_new[runs*5000:] = np.log(data[runs*5000:]+0.01)/4 + data_new[runs*5000:] = np.nan_to_num(data_new[runs*5000:]) + data_new[runs*5000:] = np.tanh(data_new[runs*5000:]) + #data[np.where(data<0)] = 0 + '''data_new = np.log(data+0.01)/4 + data_new = np.nan_to_num(data_new) + data_new = np.tanh(data_new)''' + + + + for i in range(8): + try: + assert np.std(data_new[:,:,:,i]) != 0 + except AssertionError: + print(f"WARNING: CHANNEL {i} STD == 0") + data_new[:,:,:,i] = (data_new[:,:,:,i]- np.mean(data_new[:,:,:,i]))/np.std(data_new[:,:,:,i]) print(f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}") @@ -336,10 +365,7 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 data = datetime_encoder(data,dates,plotter=False) print(f"\nEncoding datetime periodical variables (seasonally,hourly) and concatenating with data. New shape: {data.shape}, dtype: {data.dtype}") - ''' - lead_time_spacing = 5 - hours_ahead = 5 # center square of 28km is, 434 to edge, ~300km to dataedge,60km/h gives ~7h - lead_times = (hours_ahead*60//lead_time_spacing) leadtimes''' + @@ -347,29 +373,90 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 data = np.swapaxes(np.swapaxes(data,3,1),2,3) print(f"\nData swapping axes to get channel first, now shape: {data.shape}") X,Y, X_dates,Y_dates = temporal_concatenation(data,dates,Y,Y_dates,concat = concat, overlap = overlap, spaced = spaced,lead_times = lead_times) - print(f"\nDone with temporal concatenation and target_split! Data shape: {X.shape}, target shape: {Y.shape}") - #X[:,:,0:8,:,:] = X[:,:,0:8,:,:]*0.4 - 30 - '''for channel in range(8,X.shape[2]): - plt.imshow(X[0,0,channel,:,:]) - plt.show()''' + print(f"\nDone with temporal concatenation and target_split! Data shape: {X.shape}, target shape: {Y.shape}") Y = Y*GAIN + OFFSET - print("MINMAX Y AFTER GAIN + OFFSET", np.min(Y), np.max(Y)) + '''for i in range(0,5): + fig, ax = plt.subplots(1,2) + ax[0].imshow(X[i,0,0,:,:]) + #ax[0].imshow(np.mean(data_after_gained[i*7,42:70,42:70,4:8],axis=2)) + ax[0].set_title(X_dates[i][6]) + ax[1].imshow(Y[i,0,:,:]) + ax[1].set_title(Y_dates[i][0]) + plt.show()''' + + print("comparing X and Y after gain:", np.mean(data_after_gained[:,:,4:8]), np.mean(Y)) + + Y_gained = np.copy(Y) - Y = rain_binned(Y, n_bins = n_bins, increment = rain_step) + print("MINMAX Y AFTER GAIN + OFFSET", np.min(Y), np.max(Y)) + + passer = np.mean(X[:,6,4:8,:,:],axis=1) + + Y = rain_binned(Y, n_bins = n_bins, increment = rain_step, x = passer) print(f"\nDone with binning targets into bins, target shape: {Y.shape}") - - if not printer: sys.stdout = sys.__stdout__ - - + + #Remove low-rainfall data: + meaned = np.mean(X[:,:,0:4,:,:], axis=(1,2,3,4)) + idx_sorted = np.argsort(meaned) + + N = len(meaned) + '''for j in range(N-1,0,-1): + fig, ax = plt.subplots(7,1) + for k in range(7): + i = idx_sorted[j] + + im = ax[k].imshow(X[i,k,0,:,:]) + ax[k].set_title(f"MEAN: {meaned[i]:.2f}") + + + fig.subplots_adjust(right=0.8) + cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) + fig.colorbar(im, cax=cbar_ax) + plt.show()''' + to_keep = int(N*0.2) + idx_to_keep = idx_sorted[-to_keep:] + #print(meaned) + #print(meaned[idx_to_keep]) + + X = X[idx_to_keep] + Y = Y[idx_to_keep] + X_dates = [X_dates[i] for i in idx_to_keep] + Y_dates = [Y_dates[i] for i in idx_to_keep] + print(f"\nOnly keeping {to_keep} out of {N} samples to reduce low rainfall events. New X shape: {X.shape}") + N = X.shape[0] + '''for j in range(N-1,0,-1): + fig, ax = plt.subplots(7,2) + for k in range(7): + + + im = ax[k,0].imshow(X[j,k,0,:,:]) + ax[k,0].set_title(f"MEAN: {np.mean(X[j,k,0,:,:]):.2f}") + ax[k,1].imshow(Y[j,k,0,:,:]) + + + fig.subplots_adjust(right=0.8) + cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) + fig.colorbar(im, cax=cbar_ax) + plt.show() + ''' + + #print(f"\nOnly keeping {to_keep} out of {N} samples to reduce low rainfall events. New X shape: {X.shape}") + + Y_thresh = np.ones(Y[:,0,:,:].shape)*-1 + + Y_thresh[np.where(Y[:,0,:,:]==1)] = 1 + + + #rain_check(X, Y_gained[idx_to_keep], Y_thresh,X_dates,Y_dates,X_dict,Y_dict, meaned) return X,Y, X_dates,Y_dates -def rain_binned(Y, n_bins = 51,increment = 2): +def rain_binned(Y, n_bins = 51,increment = 2, x = None): SHAPE = Y.shape n,leads,w,h = SHAPE max_fall = n_bins*increment @@ -378,9 +465,16 @@ def rain_binned(Y, n_bins = 51,increment = 2): Y[np.where(Y>70)] = 0 rain = (10**(Y / 10.0) / 200.0)**(1.0 / 1.6) + print("RAIN MINMAX: ", np.min(rain), np.max(rain)) - '''for lead_time in range(leads): - plt.imshow(rain[10,lead_time,:,:]) + + '''for i in range(n): + fig,ax = plt.subplots(1,2) + rain_x = (10**(x[i] / 10.0) / 200.0)**(1.0 / 1.6) + ax[0].imshow(x[i][(112//2)-14:(112//2)+14, (112//2)-14:(112//2)+14]) + ax[0].set_title("x zoomed") + ax[1].imshow(rain[i,0,:,:]) + ax[1].set_title("y") plt.show()''' rain_bins = np.zeros((n,leads,n_bins,w,h)) counter = [] @@ -403,12 +497,46 @@ def rain_binned(Y, n_bins = 51,increment = 2): print("RAINBINS: ", rain_bins.size, " counter size: ", sum(counter)) print(counter) - '''plt.plot(counter) - plt.show()''' + return rain_bins - - +''' +def rain_check(X, Y,Y_thresh,x_dates,y_dates,X_dict,Y_dict,meaned): + X_mid = np.mean(X[:,:,4:8], axis = 2) #,42:70, 42:70 + N = min(X.shape[0], Y.shape[0]) + temps = X_mid.shape[1] + leads = min(Y.shape[1],temps) + + for n in range(N): + fig, axs = plt.subplots(5,temps) + axs = axs.reshape(-1) + #print(X_mid[n,0,:,:].reshape(-1)) + for i in range(temps): + #print("i: ", i, "\n", X_mid[n,i,:,:].reshape(-1)) + + print("MINMAX", np.min(X_mid[n,i,:,:]), np.max(X_mid[n,i,:,:])) + #print(X_mid[np.where(np.isnan(X_mid))] + np.random.random(X_mid[np.where(np.isnan(X_mid))].shape)) + im = axs[i].imshow(X_mid[n,i,:,:]) + + #axs[i].set_title(x_dates[n][i][-5:]) + for i in range(temps): + im = axs[i+temps].imshow(X_dict[x_dates[n][i]]) + axs[i+temps].set_title(x_dates[n][i][-5:]) + for j in range(leads): + im = axs[j+2*temps].imshow(Y[n,j,:,:]) + axs[j+2*temps].set_title(y_dates[n][j][-5:]) + for j in range(leads): + im = axs[j+3*temps].imshow(Y_thresh[n,j,:,:]) + axs[j+3*temps].set_title(y_dates[n][j][-5:]) + for j in range(leads): + im = axs[j+4*temps].imshow(Y_dict[y_dates[n][i]]) + axs[j+4*temps].set_title(x_dates[n][i][-5:]) + fig.suptitle("mean : " + str(meaned[n])) + fig.subplots_adjust(right=0.8) + cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) + fig.colorbar(im, cax=cbar_ax) + plt.show()''' + if __name__=="__main__": diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index 6c76012..2c28ffb 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -1,6 +1,6 @@ import torch import torch.nn as nn -from axial_attention import AxialAttention +from axial_attention import AxialAttention, AxialPositionalEmbedding from huggingface_hub import PyTorchModelHubMixin import pytorch_lightning as pl import torch.nn.functional as F @@ -13,25 +13,29 @@ import math import matplotlib.pyplot as plt + class MetNetPylight(pl.LightningModule, PyTorchModelHubMixin): def __init__( self, image_encoder: str = "downsampler", #4 CNN layers file_name: str = "data3_500k.h5", - input_channels: int = 12, - n_samples: int = 1000, - sat_channels: int = 12, - input_size: int = 256, - output_channels: int = 512, - hidden_dim: int = 384, - kernel_size: int = 3, - num_layers: int = 1, - num_att_layers: int = 1, - forecast_steps: int = 240, - temporal_dropout: float = 0.2, + input_channels: int = 12, # radar channels + longitude channels + time encoding channels = 15 (excluding lead time encoding) + n_samples: int = 1000, # number of radar snapshots to preprocess + sat_channels: int = 0, # ignore + input_size: int = 256, # height = width = input_size = 112 + output_channels: int = 512, # number of rain bins + rain_step: int = 0.2, # size of each rain bin in millimeters + hidden_dim: int = 384, # hidden dimensions in RNN layer + kernel_size: int = 3, # Kernel sizes in Downsampler + num_layers: int = 1, # ignore + num_att_layers: int = 4, #Number of attention layers, 8 original paper. + forecast_steps: int = 240, # Number of lead times + temporal_dropout: float = 0.2, # Dropout num_workers: int = 32, batch_size: int = 8, - rain_step: int = 0.2, #in millimeters + momentum: float = 0.9, + plot_every: int = 10, + learning_rate: int = 1e-2, **kwargs, ): @@ -61,6 +65,8 @@ def __init__( self.rain_step = rain_step self.learning_rate = learning_rate self.batch_size = batch_size + self.plot_every = plot_every + self.momentum = momentum ''' self.preprocessor = MetNetPreprocessor( sat_channels=sat_channels, crop_size=input_size, use_space2depth=True, split_input=True @@ -69,7 +75,7 @@ def __init__( new_channels = sat_channels * 4 # Space2Depth new_channels *= 2 # Concatenate two of them together input_channels = input_channels - sat_channels + new_channels''' - self.drop = nn.Dropout(temporal_dropout) + #self.drop = nn.Dropout(temporal_dropout) if image_encoder in ["downsampler", "default"]: image_encoder = DownSampler(input_channels + forecast_steps) else: @@ -79,6 +85,7 @@ def __init__( self.temporal_enc = TemporalEncoder( image_encoder.output_channels, hidden_dim, ks=kernel_size, n_layers=num_layers ) + self.position_embedding = AxialPositionalEmbedding(dim=self.temporal_enc.out_channels, shape = (input_size // 4, input_size // 4)) self.temporal_agg = nn.Sequential( *[ AxialAttention(dim=hidden_dim, dim_index=1, heads=8, num_dimensions=2) @@ -109,18 +116,19 @@ def encode_timestep(self, x, fstep=1): ##CNN x = self.image_encoder(x) #plot_channels(x, 1, tit_add = "after image_encoder") - print("\n shape after image_encoder: ", x.shape) + #print("\n shape after image_encoder: ", x.shape) # Temporal Encoder #_, state = self.temporal_enc(self.drop(x)) _, state = self.temporal_enc(x) - plot_channels(state, 1, tit_add = "after temporal_enc") - print("\n shape after temp enc: ", state.shape) + embedded = self.position_embedding(state) + #plot_channels(state, 1, tit_add = "after temporal_enc") + #print("\n shape after temp enc: ", state.shape) agg = self.temporal_agg(state) #plot_channels(x, 1, tit_add = "after agg") #print("\n shape after temporal_agg: ", agg.shape) - return agg + return state def forward(self, imgs,lead_time): """It takes a rank 5 tensor @@ -129,40 +137,44 @@ def forward(self, imgs,lead_time): """ # Compute all timesteps, probably can be parallelized - print("in forward") + #print("in forward") + #print_channels(imgs[0,0]) #plot_channels(imgs, 2, tit_add = "input") x = self.encode_timestep(imgs, lead_time) #plot_channels(x, 2, tit_add = "after encode") - print("shape before head: ", x.shape) + #print("shape before head: ", x.shape) out = self.head(x) - print("shape after head: ", out.shape) + #print("shape after head: ", out.shape) #plot_channels(out, 1, tit_add = "after head") soft = torch.softmax(out,dim=1) - print("shape after softmax: ", soft.shape) + #print("shape after softmax: ", soft.shape) #plot_channels(out, 1, tit_add = "after softmax") - plot_bins(out[0], " after head",soft[0], "after softmax") + #plot_bins(out[0], " after head",soft[0], "after softmax") return soft def training_step(self, batch, batch_idx): x, y = batch - print("training_step len: ", x.shape[0]) + #print("training_step len: ", x.shape[0]) lead_time = np.random.randint(0,self.forecast_steps) L = CrossEntropyLoss() y_hat = self(x.float(),lead_time) - if self.global_step%100 == 0: + + '''if (self.global_step+1)%self.plot_every == 0 and str(self.device)== "cuda:0": for i in range(x.shape[0]): - plot_bins(y_hat[i], " y_hat",y[i,0], "y") - + + plot_bins(x[i], y_hat[i], " y_hat global step: "+str(self.global_step),y[i,0], "y global step: "+str(self.global_step)) + ''' loss = L(y_hat, y[:,lead_time]) + self.log("train/loss", loss, on_epoch=True) return {"loss": loss} def validation_step(self, batch, batch_idx): x, y = batch - print("validation_step len: ", x.shape[0]) + #print("validation_step len: ", x.shape[0]) lead_times = list(range(self.forecast_steps)) loss = 0 @@ -172,8 +184,8 @@ def validation_step(self, batch, batch_idx): loss += L(y_hat, y[:,lead_time]) self.log("validation/loss_epoch", loss, on_step=False, on_epoch=True) - if self.global_step%100 == 1: - plot_category(y[0,0],y_hat[0],self.output_channels,self.rain_step,self.device) + #if self.global_step%100 == 1: + #plot_category(y[0,0],y_hat[0],self.output_channels,self.rain_step,self.device) return {"loss": loss} def validation_epoch_end(self, val_step_outputs): @@ -182,7 +194,7 @@ def validation_epoch_end(self, val_step_outputs): return {"val_loss":avg_val_loss} def configure_optimizers(self): - optimizer = optim.SGD(self.parameters(),lr=self.learning_rate) + optimizer = optim.SGD(self.parameters(),lr=self.learning_rate, momentum = self.momentum) return optimizer def setup(self, stage = None): @@ -206,6 +218,7 @@ def thresh_F1(self, pred, target,thresh = 0.2): class TemporalEncoder(nn.Module): def __init__(self, in_channels, out_channels=384, ks=3, n_layers=1): super().__init__() + self.out_channels = out_channels self.rnn = ConvGRU(in_channels, out_channels, (ks, ks), n_layers, batch_first=True) #self.rnn = ConvLSTM(in_channels, out_channels, ks, n_layers) @@ -245,17 +258,27 @@ def plot_category(y,y_hat,bins,increment,title,thresh_bin = 2): ax[1].imshow(y_hat) plt.show() -def plot_bins(y1, title1="", y2 = 0, title2=""): +def plot_bins(x, y1, title1="", y2 = 0, title2=""): N = y1.shape[0] side = int(N**0.5) if side**2 Date: Thu, 24 Mar 2022 10:23:41 +0100 Subject: [PATCH 11/17] new dataloader --- MetNet_lightning.py | 22 +++-- data_prep/metnet_dataloader.py | 68 ++++++++++++++- data_prep/prepare_data_MetNet.py | 137 +++++++++++++++++++++---------- metnet/models/metnet_pylight.py | 136 +++++++++++++++++++++++------- 4 files changed, 283 insertions(+), 80 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index d86405b..8728ebd 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -6,26 +6,36 @@ from pytorch_lightning.loggers import WandbLogger import wandb from pytorch_lightning.callbacks import DeviceStatsMonitor - +from pytorch_lightning import seed_everything wandb.login() model = MetNetPylight( - hidden_dim=128, #384 original paper + hidden_dim=256, #384 original paper forecast_steps=60, #240 original paper input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 - output_channels=30, #512 + output_channels=128, #512 input_size=112, # 112 - n_samples = 50000, + n_samples = 20000, num_workers = 32, batch_size = 5, learning_rate = 1e-2, - num_att_layers = 4, - plot_every = 100, #Plot every global_step + num_att_layers = 8, + plot_every = None, #Plot every global_step rain_step = 0.1, momentum = 0.9, + att_heads=16, + keep_biggest = 0.15, ) +#PATH_cp = "lit-wandb/xpyues72/checkpoints/epoch=440-step=3086.ckpt" +#model = MetNetPylight.load_from_checkpoint(PATH_cp) + +#model.n_samples = 60000 +#model.printer = True +#model.plot_every = None #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. print(model) + + wandb_logger = WandbLogger(project="lit-wandb") trainer = pl.Trainer(track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=1, logger = wandb_logger,strategy="ddp") diff --git a/data_prep/metnet_dataloader.py b/data_prep/metnet_dataloader.py index c7fb98e..ce11ecb 100644 --- a/data_prep/metnet_dataloader.py +++ b/data_prep/metnet_dataloader.py @@ -3,9 +3,11 @@ from torch.utils.data import Dataset, DataLoader import math import numpy as np +import os +import matplotlib.pyplot as plt class MetNetDataset(Dataset): - def __init__(self,file_name,N, lead_times = 60, rain_step = 0.2, n_bins = 512): + def __init__(self,file_name,N, lead_times = 60, rain_step = 0.2, n_bins = 512, skip_data = 0, keep_biggest = 0.8, printer = True): """ Input: file_name: path to hdf5-file. @@ -15,16 +17,78 @@ def __init__(self,file_name,N, lead_times = 60, rain_step = 0.2, n_bins = 512): X: numpy array shape (None, time, channels, width, height), Y: numpy array shape (None, lead_times, channels, width, height) """ - X,Y,X_dates,Y_dates = load_data(file_name, N = N, lead_times = lead_times, rain_step = rain_step, n_bins = n_bins) + data_path = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/data_exclusive/" + file_list = os.listdir(data_path) + means = [] + dates = [] + names = [] + for name in file_list: + if not name[-4:] == ".npy": continue + if name[-5:] == "Y.npy": continue + names.append(name) + name = name[:-4] #remove .npy + name = name.split("/")[-1] #remove directories + #print(name) + mean = name.split("_")[0] + date = "_".join(name.split("_")[1:-1]) + print(mean, date) + means.append(float(mean)) + dates.append(date) + means = np.array(means) + idx_sorted = np.argsort(means) + print("MEANS: ", means[idx_sorted]) + print(len(means)) + N = min(N,len(means)) + + '''bins = np.arange(-35, 40, 5) # fixed bin size + plt.xlim([min(means)-5, max(means)+5]) + plt.hist(means, bins=bins, alpha=0.5) + plt.title('Mean rainfall input') + plt.xlabel('mean DBZ') + plt.ylabel('count') + plt.show()''' + X = np.empty((N, 7, 15, 112,112)) + Y = np.empty((N, lead_times, n_bins, 28,28)) + for i in range(N): + if (i+1)%100==0: + print(f"Loaded samples: {i+1}/{N}") + idx = idx_sorted[-(i+1)] + date = dates[idx] + name_X = data_path + names[idx] + name_Y = data_path + names[idx].replace("X", "Y") + X_here = np.load(name_X) + #X_here = np.expand_dims(X_here,axis = 0) + #print(f"X SHAPE: {X_here.shape}") + Y_here = np.load(name_Y) + #print(f"Y SHAPE: {Y_here.shape}") + #Y_here = np.expand_dims(Y_here,axis = 0) + X[i] = X_here + Y[i] = Y_here + '''to_plot = np.mean(X_here[:,0:4], axis = 1) + fig, axs = plt.subplots(1,7) + for i in range(7): + axs[i].imshow(to_plot[i]) + fig.suptitle(name_X) + plt.show()''' + + + '''if skip_data == 0: + X,Y,X_dates,Y_dates = load_data(file_name, N = N, lead_times = lead_times, rain_step = rain_step, n_bins = n_bins, keep_biggest = keep_biggest, printer = printer) + else: + X,Y = skip_data''' + #print("X SHAPE: ", X.shape) + #print("Y SHAPE: ", Y.shape) self.x = torch.from_numpy(X) self.y = torch.from_numpy(Y) self.n_samples=X.shape[0] + def __getitem__(self, index): # allows indexing dataset[0] return self.x[index], self.y[index] + def __len__(self): # Will allow len(data) return self.n_samples diff --git a/data_prep/prepare_data_MetNet.py b/data_prep/prepare_data_MetNet.py index 130f353..4b7c528 100644 --- a/data_prep/prepare_data_MetNet.py +++ b/data_prep/prepare_data_MetNet.py @@ -46,14 +46,15 @@ def date_assertion(dates,expected_delta = 5): #print("DELTA ", minutes, "delta ", expected_delta) assert int(minutes) == expected_delta -def h5_iterator(h5_file,maxN = 100,spaced = 1): +def h5_iterator(h5_file,maxN = 100,spaced = 1, starter = 0): """Iterates through the desired datafile and returns index, array and datetime""" with h5.File(h5_file,"r") as f: keys = list(f.keys()) for i,name in enumerate(keys): - + if i=maxN: break + if (i-starter)//spaced>=maxN: break #print(name) date = name y, m, d, hh, mm = date.split("_") @@ -233,41 +234,74 @@ def longlatencoding(data): print(f"\ndone! it has shape {lonlatelev.shape}") - return np.concatenate((data,lonlatelev), axis=3) + return np.concatenate((data,lonlatelev), axis=3, dtype = np.float32) -def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True, rain_step = 0.2, n_bins=512): +def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True, rain_step = 0.2, n_bins=512, keep_biggest = 0.8): #15 minutes between datapoints is default --> spaced = 3 snapshots = [] dates = [] all_snapshots = [] Y_dates = [] array_mean = 0 + means = [] n = 0 if not printer: sys.stdout = open(os.devnull, 'w') for i, array,date in h5_iterator(h5_path, N): if (i+1)%1000==0: - print("Loaded samples: ",(i+1)//spaced) + print("Loaded samples: ",n) if i%spaced==0: snapshots.append(array) dates.append(date) + means.append(np.mean(array)) + n+=1 all_snapshots.append(array) array_mean += np.mean(array) - n+=1 + Y_dates.append(date) - print("MEAN", array_mean/n) + + '''print("MEAN", array_mean/n) print("Done loading samples! \n") + n_snap = len(snapshots) + n_all_snaps = len(all_snapshots) + means = np.array(means)[0:(n_snap//concat)*concat].reshape(n_snap//concat, concat) + running_means = np.mean(means,axis=1) + n_runs = len(running_means) + n_to_keep = int(n_runs*keep_biggest) + idx_to_keep = np.argsort(running_means)[-n_to_keep:] + print(idx_to_keep) + temp_s = [] + temp_s_all = [] + temp_s_dates = [] + temp_s_all_dates = [] + print("LEN BEFORE", len(snapshots), len(all_snapshots)) + for i in idx_to_keep: + temp_s.extend(snapshots[i*concat:i*concat+concat]) + temp_s_all.extend(all_snapshots[i*concat*spaced:i*concat*spaced+concat*spaced]) + temp_s_dates.extend(dates[i*concat:i*concat+concat]) + temp_s_all_dates.extend(Y_dates[i*concat*spaced:i*concat*spaced+concat*spaced]) + snapshots = temp_s + all_snapshots = temp_s_all + dates = temp_s_dates + Y_dates = temp_s_all_dates + print("LEN AFTER", len(snapshots), len(all_snapshots)) + print(dates) + print(Y_dates) + input()''' + + data = np.array(snapshots) + del(snapshots) # MANAGE MEMORY all_data = np.array(all_snapshots) - del(snapshots)#MANAGE MEMORY - del(all_snapshots)#MANAGE MEMORY + + del(all_snapshots) # MANAGE MEMORY print("\nDatatype data: ", data.dtype) print("\nInput data shape: ", data.shape, " size: ", sys.getsizeof(data)) @@ -324,24 +358,42 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 print(f"\nConcatenating data and centercrop to dimenison: {data.shape} with shape [:,:,:,downsampled + centercrop]") + + + data = longlatencoding(data) + print(f"\nConcatenating data with long, lat and elevation. New shape: {data.shape}, dtype: {data.dtype}") + + data = datetime_encoder(data,dates,plotter=False) + print(f"\nEncoding datetime periodical variables (seasonally,hourly) and concatenating with data. New shape: {data.shape}, dtype: {data.dtype}") + + + + + + data = np.swapaxes(np.swapaxes(data,3,1),2,3) + print(f"\nData swapping axes to get channel first, now shape: {data.shape}") + X,Y, X_dates,Y_dates = temporal_concatenation(data,dates,Y,Y_dates,concat = concat, overlap = overlap, spaced = spaced,lead_times = lead_times) + + print(f"\nDone with temporal concatenation and target_split! Data shape: {X.shape}, target shape: {Y.shape}") + GAIN = 0.4 OFFSET = -30 - data = data*GAIN + OFFSET + X[:,:,0:8] = X[:,:,0:8]*GAIN + OFFSET - data_after_gained = np.copy(data) - maxx = np.max(data) + + maxx = np.max(X[:,:,0:8]) print("\nMAX DBZ data(should be 72): ", maxx) - data_new = np.empty(data.shape) + data_new = np.empty(X[:,:,0:8].shape) N = data_new.shape[0] runs = N//5000 for run in range(0,N,5000): - data_new[run:run+5000] = np.log(data[run:run+5000]+0.01)/4 - data_new[run:run+5000] = np.nan_to_num(data_new[run:run+5000]) - data_new[run:run+5000] = np.tanh(data_new[run:run+5000]) + data_new[run:run+5000,:,0:8] = np.log(X[run:run+5000,:,0:8]+0.01, dtype = np.float32)/4 + data_new[run:run+5000,:,0:8] = np.nan_to_num(data_new[run:run+5000,:,0:8]) + data_new[run:run+5000,:,0:8] = np.tanh(data_new[run:run+5000,:,0:8], dtype = np.float32) - data_new[runs*5000:] = np.log(data[runs*5000:]+0.01)/4 - data_new[runs*5000:] = np.nan_to_num(data_new[runs*5000:]) - data_new[runs*5000:] = np.tanh(data_new[runs*5000:]) + data_new[runs*5000:,:,0:8] = np.log(X[runs*5000:,:,0:8]+0.01, dtype = np.float32)/4 + data_new[runs*5000:,:,0:8] = np.nan_to_num(data_new[runs*5000:,:,0:8]) + data_new[runs*5000:,:,0:8] = np.tanh(data_new[runs*5000:,:,0:8], dtype = np.float32) #data[np.where(data<0)] = 0 '''data_new = np.log(data+0.01)/4 data_new = np.nan_to_num(data_new) @@ -354,27 +406,12 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 assert np.std(data_new[:,:,:,i]) != 0 except AssertionError: print(f"WARNING: CHANNEL {i} STD == 0") - data_new[:,:,:,i] = (data_new[:,:,:,i]- np.mean(data_new[:,:,:,i]))/np.std(data_new[:,:,:,i]) + data_new[:,:,i] = (data_new[:,:,i] - np.mean(data_new[:,:,i] ))/np.std(data_new[:,:,i] ) print(f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}") - - data = longlatencoding(data_new) - print(f"\nConcatenating data with long, lat and elevation. New shape: {data.shape}, dtype: {data.dtype}") - - data = datetime_encoder(data,dates,plotter=False) - print(f"\nEncoding datetime periodical variables (seasonally,hourly) and concatenating with data. New shape: {data.shape}, dtype: {data.dtype}") - - - - - data = np.swapaxes(np.swapaxes(data,3,1),2,3) - print(f"\nData swapping axes to get channel first, now shape: {data.shape}") - X,Y, X_dates,Y_dates = temporal_concatenation(data,dates,Y,Y_dates,concat = concat, overlap = overlap, spaced = spaced,lead_times = lead_times) - - print(f"\nDone with temporal concatenation and target_split! Data shape: {X.shape}, target shape: {Y.shape}") Y = Y*GAIN + OFFSET '''for i in range(0,5): fig, ax = plt.subplots(1,2) @@ -385,7 +422,7 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 ax[1].set_title(Y_dates[i][0]) plt.show()''' - print("comparing X and Y after gain:", np.mean(data_after_gained[:,:,4:8]), np.mean(Y)) + #print("comparing X and Y after gain:", np.mean(data_after_gained[:,:,4:8]), np.mean(Y)) Y_gained = np.copy(Y) @@ -397,8 +434,7 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 print(f"\nDone with binning targets into bins, target shape: {Y.shape}") - if not printer: - sys.stdout = sys.__stdout__ + #Remove low-rainfall data: meaned = np.mean(X[:,:,0:4,:,:], axis=(1,2,3,4)) @@ -418,12 +454,16 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) fig.colorbar(im, cax=cbar_ax) plt.show()''' - to_keep = int(N*0.2) + to_keep = int(N*keep_biggest) idx_to_keep = idx_sorted[-to_keep:] #print(meaned) #print(meaned[idx_to_keep]) X = X[idx_to_keep] + '''print(meaned[idx_sorted]) + print(meaned[idx_to_keep]) + print(np.mean(X[:,:,0:4,:,:], axis=(1,2,3,4))) + input()''' Y = Y[idx_to_keep] X_dates = [X_dates[i] for i in idx_to_keep] Y_dates = [Y_dates[i] for i in idx_to_keep] @@ -442,8 +482,16 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 fig.subplots_adjust(right=0.8) cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) fig.colorbar(im, cax=cbar_ax) - plt.show() - ''' + plt.show()''' + + #plots all channels seperately: + '''channels = X.shape[2] + for i in range(N): + fig, axs = plt.subplots(4,4) + axs = axs.reshape(-1) + for c in range(channels): + axs[c].imshow(X[i,0,c]) + plt.show()''' #print(f"\nOnly keeping {to_keep} out of {N} samples to reduce low rainfall events. New X shape: {X.shape}") @@ -453,7 +501,8 @@ def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881 #rain_check(X, Y_gained[idx_to_keep], Y_thresh,X_dates,Y_dates,X_dict,Y_dict, meaned) - + if not printer: + sys.stdout = sys.__stdout__ return X,Y, X_dates,Y_dates def rain_binned(Y, n_bins = 51,increment = 2, x = None): @@ -541,7 +590,7 @@ def rain_check(X, Y,Y_thresh,x_dates,y_dates,X_dict,Y_dict,meaned): if __name__=="__main__": - data,dates = load_data("combination_all_pn157.h5",N =500,downsample=True,spacedepth=True,printer=True) + data,dates = load_data("combination_all_pn157.h5",N =500,downsample=True,spacedepth=True,printer=True,) print(data.nbytes) diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index 2c28ffb..4849501 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -12,6 +12,9 @@ import numpy as np import math import matplotlib.pyplot as plt +import wandb +from sklearn.metrics import f1_score + class MetNetPylight(pl.LightningModule, PyTorchModelHubMixin): @@ -34,12 +37,15 @@ def __init__( num_workers: int = 32, batch_size: int = 8, momentum: float = 0.9, + att_heads: int = 8, plot_every: int = 10, + keep_biggest: float = 0.8, learning_rate: int = 1e-2, **kwargs, ): super(MetNetPylight, self).__init__() + pl.seed_everything(42, workers = True) config = locals() config.pop("self") config.pop("__class__") @@ -67,6 +73,16 @@ def __init__( self.batch_size = batch_size self.plot_every = plot_every self.momentum = momentum + self.att_heads = att_heads + lead_time_keys = list(range(forecast_steps)) + lead_time_counts = [0 for i in range(forecast_steps)] + self.lead_time_histogram = dict(zip(lead_time_keys,lead_time_counts)) + self.keep_biggest = keep_biggest + + if str(self.device)== "cuda:0": + self.printer = True + else: + self.printer = False ''' self.preprocessor = MetNetPreprocessor( sat_channels=sat_channels, crop_size=input_size, use_space2depth=True, split_input=True @@ -76,10 +92,11 @@ def __init__( new_channels *= 2 # Concatenate two of them together input_channels = input_channels - sat_channels + new_channels''' #self.drop = nn.Dropout(temporal_dropout) - if image_encoder in ["downsampler", "default"]: + '''if image_encoder in ["downsampler", "default"]: image_encoder = DownSampler(input_channels + forecast_steps) else: - raise ValueError(f"Image_encoder {image_encoder} is not recognized") + raise ValueError(f"Image_encoder {image_encoder} is not recognized")''' + image_encoder = DownSampler(input_channels + forecast_steps) self.image_encoder = TimeDistributed(image_encoder) self.ct = ConditionTime(forecast_steps) self.temporal_enc = TemporalEncoder( @@ -88,7 +105,7 @@ def __init__( self.position_embedding = AxialPositionalEmbedding(dim=self.temporal_enc.out_channels, shape = (input_size // 4, input_size // 4)) self.temporal_agg = nn.Sequential( *[ - AxialAttention(dim=hidden_dim, dim_index=1, heads=8, num_dimensions=2) + AxialAttention(dim=hidden_dim, dim_index=1, heads=self.att_heads, num_dimensions=2) for _ in range(num_att_layers) ] ) @@ -98,16 +115,23 @@ def __init__( self.save_hyperparameters() - def encode_timestep(self, x, fstep=1): + def encode_timestep(self, x, fstep=1, lead_times = []): #print("\n shape before preprocess: ", x.shape) # Preprocess Tensor #x = self.preprocessor(x) #print("\n shape after preprocess: ", x.shape) # Condition Time #plot_channels(x, 1, tit_add = "INPUT") - x = self.ct(x, fstep) - - x = x.double() + if lead_times: + bs, t, c, w, h = x.shape + x_temp = torch.empty((bs, t, c+self.forecast_steps, w, h), device = self.device) + for i,lead_time in enumerate(lead_times): + + x_temp[i] = self.ct(x[i:i+1], lead_time) + else: + x_temp = self.ct(x, fstep) + + x = x_temp.double() @@ -130,7 +154,7 @@ def encode_timestep(self, x, fstep=1): #print("\n shape after temporal_agg: ", agg.shape) return state - def forward(self, imgs,lead_time): + def forward(self, imgs,lead_time = 0, lead_times = []): """It takes a rank 5 tensor - imgs [bs, seq_len, channels, h, w] - lead_time #random int between 0,self.forecast_steps @@ -140,33 +164,39 @@ def forward(self, imgs,lead_time): #print("in forward") #print_channels(imgs[0,0]) #plot_channels(imgs, 2, tit_add = "input") - x = self.encode_timestep(imgs, lead_time) + #print(imgs.shape) + x = self.encode_timestep(imgs, lead_time, lead_times) #plot_channels(x, 2, tit_add = "after encode") #print("shape before head: ", x.shape) out = self.head(x) #print("shape after head: ", out.shape) #plot_channels(out, 1, tit_add = "after head") - soft = torch.softmax(out,dim=1) + #soft = torch.softmax(out,dim=1) #print("shape after softmax: ", soft.shape) #plot_channels(out, 1, tit_add = "after softmax") #plot_bins(out[0], " after head",soft[0], "after softmax") - return soft + return out def training_step(self, batch, batch_idx): x, y = batch #print("training_step len: ", x.shape[0]) - - lead_time = np.random.randint(0,self.forecast_steps) + + bs = x.shape[0] + lead_times = [np.random.randint(0,self.forecast_steps) for _ in range(bs)] + L = CrossEntropyLoss() - y_hat = self(x.float(),lead_time) + y_hat = self(x.float(),lead_times=lead_times) + #y_leads = torch.empty(y[:,lead_time].shape, device = self.device) + #y_leads = torch.tensor([y[i,lead_times[i]] for i in range(self.batch_size)], device = self.device) + loss = L(y_hat, y[torch.arange(bs), lead_times]) + if self.plot_every: + if (self.global_step)%self.plot_every == 0 and str(self.device)== "cuda:0": + for lead_time in range(self.forecast_steps): + y_hat = self(x.float(),lead_time = lead_time) + plot_bins(x[0], y_hat[0], " y_hat leadtime: "+str(lead_time),y[0,lead_time], " y leadtime: "+str(lead_time)) - '''if (self.global_step+1)%self.plot_every == 0 and str(self.device)== "cuda:0": - for i in range(x.shape[0]): - - plot_bins(x[i], y_hat[i], " y_hat global step: "+str(self.global_step),y[i,0], "y global step: "+str(self.global_step)) - ''' - loss = L(y_hat, y[:,lead_time]) + #loss = L(y_hat, y[:,lead_time]) self.log("train/loss", loss, on_epoch=True) return {"loss": loss} @@ -183,14 +213,44 @@ def validation_step(self, batch, batch_idx): y_hat = self(x.float(),lead_time) loss += L(y_hat, y[:,lead_time]) + loss /= self.forecast_steps self.log("validation/loss_epoch", loss, on_step=False, on_epoch=True) #if self.global_step%100 == 1: #plot_category(y[0,0],y_hat[0],self.output_channels,self.rain_step,self.device) return {"loss": loss} + + def test_step(self, batch, batch_idx): + x,y = batch + # ---------- calculate test_loss ---------- + loss = 0 + L = CrossEntropyLoss() + for lead_time in range(self.forecast_steps): + y_hat= self(x,lead_time) + loss += L(y_hat, y[:,lead_time]) + loss /= self.forecast_steps + self.log("test/loss_epoch", loss, on_step=False, on_epoch=True) + # ---------- calculate test_loss ---------- + + # ---------- get all lead times ---------- + y_hat = torch.empty(y.shape) + for lead_time in range(self.forecast_steps): + y_hat[:,lead_time] = self(x,lead_time) + # ---------- get all lead times ---------- + + #plot bins: + for i in range(x.shape[0]): + plot_bins(x[i], y_hat[i,0], "x" ,y[i,0], "y") + #plot category: + for i in range(x.shape[0]): + plot_category(y[i,0],y_hat[i,0],self.output_channels,self.rain_step,self.device) def validation_epoch_end(self, val_step_outputs): + '''wombo = self.logger.experiment + hist_scores = [[s] for s in self.lead_time_histogram.values()] + table = wandb.Table(data = hist_scores, columns = ["lead_times"]) + wombo.log({"leadtimes_histogram": wombo.plot.histogram(table, "Lead times", title="Lead times histogram")})''' outs = tuple([x["loss"] for x in val_step_outputs]) - avg_val_loss = torch.tensor(outs).mean() + avg_val_loss = torch.tensor(outs, device = self.device).mean() return {"val_loss":avg_val_loss} def configure_optimizers(self): @@ -198,11 +258,15 @@ def configure_optimizers(self): return optimizer def setup(self, stage = None): - data = metnet_dataloader.MetNetDataset(self.file_name, N = self.n_samples, lead_times = self.forecast_steps, rain_step = self.rain_step, n_bins = self.output_channels) + data = metnet_dataloader.MetNetDataset(self.file_name, N = self.n_samples, lead_times = self.forecast_steps, rain_step = self.rain_step, n_bins = self.output_channels, keep_biggest = self.keep_biggest, printer = self.printer) nsamples = len(data) - split_list = [math.floor(nsamples*0.7),math.floor(nsamples*0.3)] + split_list = [math.floor(nsamples*0.7),math.floor(nsamples*0.15),math.floor(nsamples*0.15)] + split_list[1] = max(1,split_list[1]) + split_list[2] = max(1,split_list[2]) split_list[0] += nsamples-sum(split_list) - self.train_data, self.val_data = random_split(data, split_list) + self.train_data, self.val_data, self.test_data= random_split(data, split_list) + #print("SHAPES: ", self.train_data.shape, self.val_data.shape, self.test_data.shape) + def train_dataloader(self): train_loader = DataLoader(dataset=self.train_data,batch_size=self.batch_size, num_workers = self.workers) @@ -210,11 +274,24 @@ def train_dataloader(self): def val_dataloader(self): val_loader = DataLoader(self.val_data,batch_size=self.batch_size, num_workers = self.workers) return val_loader + def test_dataloader(self): + test_loader = DataLoader(self.test_data,batch_size=self.batch_size, num_workers = self.workers) + return test_loader - def thresh_F1(self, pred, target,thresh = 0.2): + def thresh_F1(self, y_hat, y,thresh = 0.2): + #input: pred shape (None, lead_times, bins, 28, 28), y shape (None, lead_times, bins, 28, 28) + n, leads, bins, w, h = y_hat.shape thresh_bin = thresh//self.rain_step - norms = torch.sum(pred, dim=1) - + y_thresh = torch.zeros(n, leads, 2, w, h) #binary threshhold + y_thresh[:,:,1,:,:] = 1 + y_hat_thresh = torch.copy(y_thresh) + idx_y = torch.where(y[:,:,:thresh_bin,:,:] == 1) + idx_y_hat = torch.where(y_hat[:,:,:thresh_bin,:,:] == 1) + y_thresh[:,:,0,:,:][idx_y] = 1 + y_thresh[:,:,1,:,:][idx_y] = 0 + + + class TemporalEncoder(nn.Module): def __init__(self, in_channels, out_channels=384, ks=3, n_layers=1): super().__init__() @@ -259,6 +336,7 @@ def plot_category(y,y_hat,bins,increment,title,thresh_bin = 2): plt.show() def plot_bins(x, y1, title1="", y2 = 0, title2=""): + N = y1.shape[0] side = int(N**0.5) if side**2 Date: Mon, 11 Apr 2022 15:31:25 +0200 Subject: [PATCH 12/17] weekly backup, new sampling techinque, probabillity plots etc --- MetNet_lightning.py | 36 +-- metnet/models/metnet_pylight.py | 400 ++++++++++++++++++++++++++++---- test_metnet_lightning.py | 71 ++++++ 3 files changed, 446 insertions(+), 61 deletions(-) create mode 100644 test_metnet_lightning.py diff --git a/MetNet_lightning.py b/MetNet_lightning.py index 8728ebd..4880ea3 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -7,38 +7,48 @@ import wandb from pytorch_lightning.callbacks import DeviceStatsMonitor from pytorch_lightning import seed_everything +import time + wandb.login() -model = MetNetPylight( +'''model = MetNetPylight( hidden_dim=256, #384 original paper - forecast_steps=60, #240 original paper + forecast_steps=8, #240 original paper input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 output_channels=128, #512 input_size=112, # 112 - n_samples = 20000, - num_workers = 32, - batch_size = 5, + n_samples = None, #None = All ~ 23000 + num_workers = 4, + batch_size = 8, learning_rate = 1e-2, num_att_layers = 8, plot_every = None, #Plot every global_step - rain_step = 0.1, + rain_step = 0.2, momentum = 0.9, att_heads=16, keep_biggest = 0.15, - ) -#PATH_cp = "lit-wandb/xpyues72/checkpoints/epoch=440-step=3086.ckpt" -#model = MetNetPylight.load_from_checkpoint(PATH_cp) + leadtime_spacing = 3, #1: 5 minutes, 3: 15 minutes + )''' +#PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/epoch=430-step=22842.ckpt" +PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/lit-wandb/jbd0j048/checkpoints/epoch=210-step=14558.ckpt" +model = MetNetPylight.load_from_checkpoint(PATH_cp) +print(model) -#model.n_samples = 60000 +#model.n_samples = 2000 #model.printer = True #model.plot_every = None #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. -print(model) - +#print(model) +#wandb.restore("/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/wandb/run-20220331_162434-21o2u2sj" +#wandb.init(run_id = "21o2u2sj",resume="must") wandb_logger = WandbLogger(project="lit-wandb") -trainer = pl.Trainer(track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=1, logger = wandb_logger,strategy="ddp") + + +trainer = pl.Trainer(num_sanity_val_steps=2, track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=50, logger = wandb_logger,strategy="ddp", early_stop_callback=True) +start_time = time.time() trainer.fit(model) +print("--- %s seconds ---" % (time.time() - start_time)) wandb.finish() diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index 4849501..e4af5df 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -14,8 +14,9 @@ import matplotlib.pyplot as plt import wandb from sklearn.metrics import f1_score - - +import PIL +import matplotlib as mpl +from matplotlib import colors class MetNetPylight(pl.LightningModule, PyTorchModelHubMixin): def __init__( @@ -42,6 +43,7 @@ def __init__( keep_biggest: float = 0.8, learning_rate: int = 1e-2, + leadtime_spacing: int = 1, **kwargs, ): super(MetNetPylight, self).__init__() @@ -78,6 +80,8 @@ def __init__( lead_time_counts = [0 for i in range(forecast_steps)] self.lead_time_histogram = dict(zip(lead_time_keys,lead_time_counts)) self.keep_biggest = keep_biggest + self.weights = None + self.leadtime_spacing = leadtime_spacing if str(self.device)== "cuda:0": self.printer = True @@ -91,7 +95,7 @@ def __init__( new_channels = sat_channels * 4 # Space2Depth new_channels *= 2 # Concatenate two of them together input_channels = input_channels - sat_channels + new_channels''' - #self.drop = nn.Dropout(temporal_dropout) + self.drop = nn.Dropout(temporal_dropout) '''if image_encoder in ["downsampler", "default"]: image_encoder = DownSampler(input_channels + forecast_steps) else: @@ -144,7 +148,8 @@ def encode_timestep(self, x, fstep=1, lead_times = []): # Temporal Encoder #_, state = self.temporal_enc(self.drop(x)) - _, state = self.temporal_enc(x) + dropped = self.drop(x) + _, state = self.temporal_enc(dropped) embedded = self.position_embedding(state) #plot_channels(state, 1, tit_add = "after temporal_enc") #print("\n shape after temp enc: ", state.shape) @@ -153,7 +158,7 @@ def encode_timestep(self, x, fstep=1, lead_times = []): #plot_channels(x, 1, tit_add = "after agg") #print("\n shape after temporal_agg: ", agg.shape) return state - + def forward(self, imgs,lead_time = 0, lead_times = []): """It takes a rank 5 tensor - imgs [bs, seq_len, channels, h, w] @@ -179,97 +184,243 @@ def forward(self, imgs,lead_time = 0, lead_times = []): def training_step(self, batch, batch_idx): - x, y = batch + x, y, rainy_leads = batch #print("training_step len: ", x.shape[0]) bs = x.shape[0] - lead_times = [np.random.randint(0,self.forecast_steps) for _ in range(bs)] + lead_times = [int(np.random.choice(leads.cpu().detach().numpy())) for leads in rainy_leads] + #w = torch.tensor(self.weights,device = self.device) L = CrossEntropyLoss() y_hat = self(x.float(),lead_times=lead_times) + #y_leads = torch.empty(y[:,lead_time].shape, device = self.device) #y_leads = torch.tensor([y[i,lead_times[i]] for i in range(self.batch_size)], device = self.device) - loss = L(y_hat, y[torch.arange(bs), lead_times]) - if self.plot_every: - if (self.global_step)%self.plot_every == 0 and str(self.device)== "cuda:0": - for lead_time in range(self.forecast_steps): - y_hat = self(x.float(),lead_time = lead_time) - plot_bins(x[0], y_hat[0], " y_hat leadtime: "+str(lead_time),y[0,lead_time], " y leadtime: "+str(lead_time)) + loss = L(y_hat, y[torch.arange(bs), lead_times], ) - #loss = L(y_hat, y[:,lead_time]) - self.log("train/loss", loss, on_epoch=True) + self.log("train/loss", loss, on_step=False, on_epoch=True) + '''if batch_idx == 0: + + self.train_batch = (x[0:1],y[0:1])''' + return {"loss": loss} def validation_step(self, batch, batch_idx): x, y = batch #print("validation_step len: ", x.shape[0]) + #log_img = (batch_idx == 0 and str(self.device)== "cuda:0") lead_times = list(range(self.forecast_steps)) loss = 0 + L = CrossEntropyLoss() for lead_time in lead_times: y_hat = self(x.float(),lead_time) + loss += L(y_hat, y[:,lead_time]) + + '''y_img, y_hat_img = thresh_imgs(y[0,lead_time], y_hat[0], thresh_bin = 1) + if log_img: + self.logger.experiment.log({f"val_{lead_time}":[wandb.Image(y_img.cpu(), caption=f"y leadtime {lead_time}"), wandb.Image(y_hat_img.cpu(), caption=f"y_hat leadtime {lead_time}")]})''' loss /= self.forecast_steps + self.log("validation/loss_epoch", loss, on_step=False, on_epoch=True) - #if self.global_step%100 == 1: - #plot_category(y[0,0],y_hat[0],self.output_channels,self.rain_step,self.device) + return {"loss": loss} def test_step(self, batch, batch_idx): x,y = batch + + + # ---------- calculate test_loss ---------- loss = 0 L = CrossEntropyLoss() + y_hat = torch.empty(y.shape) for lead_time in range(self.forecast_steps): - y_hat= self(x,lead_time) - loss += L(y_hat, y[:,lead_time]) + y_hat_here= self(x,lead_time) + loss += L(y_hat_here, y[:,lead_time]) + y_hat[:,lead_time] = y_hat_here loss /= self.forecast_steps self.log("test/loss_epoch", loss, on_step=False, on_epoch=True) # ---------- calculate test_loss ---------- - # ---------- get all lead times ---------- - y_hat = torch.empty(y.shape) - for lead_time in range(self.forecast_steps): - y_hat[:,lead_time] = self(x,lead_time) - # ---------- get all lead times ---------- + - #plot bins: + thresh = 1 + no_rain = torch.zeros(y[0,0].shape, device = self.device) + no_rain[0] = 1 + + + + + ''' + #for i in range(y.shape[0]): + for i in range(1): + temp3 = x[i,-1,0:4].cpu().numpy() + temp3 = np.mean(temp3, axis = 0) + temp3 = (temp3 + np.min(temp3))/(np.max(temp3)-np.min(temp3)) + temp3 = temp3*255 + pil_im_x = PIL.Image.fromarray(np.uint8(temp3)) + self.logger.log_image(key=f"val_{lead_time}", images=[pil_im_x], caption = ["x"]) + + self.f1_count += 1 + imgs = [] + capts = [] + for lead_time in range(0,self.forecast_steps,10): + + y_img, y_hat_img, f1,f1_control = thresh_imgs(y[i,lead_time], y_hat[i,lead_time], no_rain, thresh_bin = thresh) + self.avg_y_img[lead_time]+=(torch.mean(y_img.cpu())) + self.avg_y_hat_img[lead_time]+=(torch.mean(y_hat_img.cpu())) + self.f1s[lead_time] += f1 + self.f1s_control[lead_time] += f1_control + + if lead_time in list(range(0,60,10)): + + temp1 = y_img.cpu().numpy() + pil_im_y = PIL.Image.fromarray(np.uint8(temp1)*255) + imgs.append(pil_im_y) + mean_y = str(np.mean(temp1)) + + capts.append(f"y {lead_time}, mean={mean_y[0:4]}") + temp2 = y_hat_img.cpu().numpy() + pil_im_y = PIL.Image.fromarray(np.uint8(temp2)*255) + imgs.append(pil_im_y) + capts.append(f"y_hat {lead_time}, f1={round(f1,4)}") + + + + fig,axs = plt.subplots(1,2) + im0 = axs[0].imshow(y_img, vmin=0, vmax=1) + axs[0].set_title("y") + im0 = axs[1].imshow(y_hat_img, vmin=0, vmax=1) + axs[1].set_title("y_hat") + fig.suptitle(f"Threshhold: {thresh}, lead time: {lead_time}, with f1-score: {f1}") + fig.subplots_adjust(right=0.8) + + fig.colorbar(im0, ax=axs.ravel().tolist()) + plt.show() + self.logger.log_image(key=f"val_{lead_time}", images=imgs, caption = capts) + ''' for i in range(x.shape[0]): - plot_bins(x[i], y_hat[i,0], "x" ,y[i,0], "y") + softed = torch.softmax(y_hat[i],dim=1) + plot_probabillity(y[i],softed,[kk for kk in range(0,self.forecast_steps,self.forecast_steps//3)],increment = 0.2, spacing = self.leadtime_spacing) + #plot_categories(y[i,0],softed,increment = 0.2) + + #plot bins: + '''for i in range(x.shape[0]): + softed = torch.softmax(y_hat[i,0],dim=0) + plot_bins(x[i], softed[0:9], "x" ,y[i,0,0:9], "y") #plot category: for i in range(x.shape[0]): - plot_category(y[i,0],y_hat[i,0],self.output_channels,self.rain_step,self.device) - + plot_category(y[i,0],y_hat[i,0],self.output_channels,self.rain_step,self.device)''' + def on_train_epoch_end(self): + imgs = [] + capts = [] + thresh = 1 + + '''for lead_time in range(0,self.forecast_steps,10): + y_hat = self(self.train_batch[0],lead_time) + y = self.train_batch[1] + no_rain = torch.zeros(y[0,0].shape, device = self.device) + no_rain[0] = 1 + y_img, y_hat_img, f1,f1_control = thresh_imgs(y[0,lead_time], y_hat[0], no_rain, thresh_bin = thresh) + + + + temp1 = y_img.cpu().numpy() + pil_im_y = PIL.Image.fromarray(np.uint8(temp1)*255) + imgs.append(pil_im_y) + capts.append(f"y {lead_time}") + temp2 = y_hat_img.cpu().numpy() + pil_im_y = PIL.Image.fromarray(np.uint8(temp2)*255) + imgs.append(pil_im_y) + capts.append(f"y_hat {lead_time}") + self.logger.log_image(key=f"train_end_{lead_time}", images=imgs, caption = capts)''' + def validation_epoch_end(self, val_step_outputs): '''wombo = self.logger.experiment hist_scores = [[s] for s in self.lead_time_histogram.values()] table = wandb.Table(data = hist_scores, columns = ["lead_times"]) wombo.log({"leadtimes_histogram": wombo.plot.histogram(table, "Lead times", title="Lead times histogram")})''' outs = tuple([x["loss"] for x in val_step_outputs]) + avg_val_loss = torch.tensor(outs, device = self.device).mean() - return {"val_loss":avg_val_loss} + return {"val_loss":avg_val_loss} + def test_epoch_end(self,test_step_outputs): + f1mean = np.array(self.f1s)/self.f1_count + f1_control_mean = np.array(self.f1s_control)/self.f1_count + plt.plot(f1mean,"b", label="f1 meaned") + plt.plot(f1_control_mean,"--g", label="no rain controll") + plt.legend() + plt.title(f"f1-scores over leadtimes") + plt.xlabel("lead_time") + plt.ylabel("f1") + plt.show() + + y_means = np.array(self.avg_y_img)/self.f1_count + y_hat_means = np.array(self.avg_y_hat_img)/self.f1_count + plt.plot(y_means,"b", label="y means") + plt.plot(y_hat_means,"g", label="y_hat means") + plt.legend() + plt.title(f"y and yhat above threshhold at different leadtimes") + plt.xlabel("lead_time") + plt.ylabel("mean") + plt.show() + def configure_optimizers(self): optimizer = optim.SGD(self.parameters(),lr=self.learning_rate, momentum = self.momentum) return optimizer def setup(self, stage = None): - data = metnet_dataloader.MetNetDataset(self.file_name, N = self.n_samples, lead_times = self.forecast_steps, rain_step = self.rain_step, n_bins = self.output_channels, keep_biggest = self.keep_biggest, printer = self.printer) - nsamples = len(data) - split_list = [math.floor(nsamples*0.7),math.floor(nsamples*0.15),math.floor(nsamples*0.15)] - split_list[1] = max(1,split_list[1]) - split_list[2] = max(1,split_list[2]) - split_list[0] += nsamples-sum(split_list) - self.train_data, self.val_data, self.test_data= random_split(data, split_list) + self.train_data = metnet_dataloader.MetNetDataset("train", N = self.n_samples , keep_biggest = self.keep_biggest, leadtime_spacing = self.leadtime_spacing, lead_times = self.forecast_steps) + self.val_data = metnet_dataloader.MetNetDataset("val", N = None, keep_biggest = 1, leadtime_spacing = self.leadtime_spacing) + self.test_data = metnet_dataloader.MetNetDataset("test", N = self.n_samples, keep_biggest = 1, leadtime_spacing = self.leadtime_spacing) + '''if self.train_data.weights is not None: + + self.weights = self.train_data.weights''' + + + + print(f"Training data samples = {len(self.train_data)}") + + print(f"Validation data samples = {len(self.val_data)}") + print(f"Test data samples = {len(self.test_data)}") #print("SHAPES: ", self.train_data.shape, self.val_data.shape, self.test_data.shape) + #print([i.split("/")[-1][0:6] for i in self.train_data.file_names]) + print("FIXING IMBALANCE") + self.class_balancing = torch.from_numpy(np.load("/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/class_imbalance.npy")).to(self.device) + self.class_balancing_half = torch.from_numpy(np.load("/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/class_imbalance0.15.npy")).to(self.device) + a = torch.divide(self.class_balancing, self.class_balancing_half).cpu().numpy() + a = np.nan_to_num(a) + b = np.arange(0,len(a)) + idx = np.where(a!=0) + #print(len(a)) + #print(a) + #plt.plot(self.class_balancing.cpu(), "--r") + '''plt.scatter(b[idx],a[idx]) + plt.title("") + #plt.yscale("log") + plt.show()''' + self.rain_bins = torch.zeros((self.output_channels,)) + '''for i, batch in enumerate(self.train_data): + x,y = batch + + self.rain_bins += torch.sum(y.cpu(),dim = [0,1,3,4]) + if i%50==0: + print(f"{i}/{len(self.train_data)}") + np.save(f"/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/class_imbalance{self.keep_biggest}.npy",self.rain_bins.numpy()) + plt.plot(self.rain_bins) + plt.yscale("log") + plt.show()''' def train_dataloader(self): - train_loader = DataLoader(dataset=self.train_data,batch_size=self.batch_size, num_workers = self.workers) + train_loader = DataLoader(dataset=self.train_data,batch_size=self.batch_size, num_workers = self.workers, shuffle = True) return train_loader def val_dataloader(self): val_loader = DataLoader(self.val_data,batch_size=self.batch_size, num_workers = self.workers) @@ -278,18 +429,83 @@ def test_dataloader(self): test_loader = DataLoader(self.test_data,batch_size=self.batch_size, num_workers = self.workers) return test_loader - def thresh_F1(self, y_hat, y,thresh = 0.2): - #input: pred shape (None, lead_times, bins, 28, 28), y shape (None, lead_times, bins, 28, 28) - n, leads, bins, w, h = y_hat.shape - thresh_bin = thresh//self.rain_step - y_thresh = torch.zeros(n, leads, 2, w, h) #binary threshhold - y_thresh[:,:,1,:,:] = 1 - y_hat_thresh = torch.copy(y_thresh) - idx_y = torch.where(y[:,:,:thresh_bin,:,:] == 1) - idx_y_hat = torch.where(y_hat[:,:,:thresh_bin,:,:] == 1) - y_thresh[:,:,0,:,:][idx_y] = 1 - y_thresh[:,:,1,:,:][idx_y] = 0 + '''def on_train_epoch_start(self): + print("INSIDE NOW") + self.rain_bins = torch.zeros((self.output_channels,)) + for i, batch in enumerate(self.train_data): + x,y = batch + self.rain_bins += torch.sum(y.cpu(),dim = [0,1,3,4]) + if i%50==0: + print(f"{i}/{len(self.train_data)}") + plt.plot(self.rain_bins) + plt.yscale("log") + plt.show()''' + +def thresh_imgs(y, y_hat, after_five, thresh_bin = 1): + bins, w, h = y_hat.shape + + y_below = torch.sum(y[0:thresh_bin], dim=0) + y_above = torch.sum(y[thresh_bin:], dim=0) + y_outcome = torch.ones((w, h)) + y_idx_less_rain = torch.where(y_belowpart_y_hat_probs) + part_y_hat_probs[idx_y_hat] = part_y_hat[idx_y_hat] + rain_img_y[idx_y] = i + rain_img_y_hat[idx_y_hat] = i + + fig, ax = plt.subplots(1,2) + rain_img_y = rain_img_y/torch.max(rain_img_y) + rain_img_y_hat = rain_img_y_hat/torch.max(rain_img_y_hat) + ax[0].imshow(rain_img_y, cmap = "hot") + ax[0].set_title("Ground truth rain") + ax[1].imshow(rain_img_y_hat, cmap = "hot") + ax[1].set_title("Predicted rain") + plt.show() + +def plot_probabillity(y,y_hat,lead_times ,increment = 0.2,spacing = 1): + #accepts y.shape = (leads, bins,w,h) + + _, _, w, h = y.shape + fig, ax = plt.subplots(len(lead_times),2) + + + bounds = [0, 0.2, 1,3] + prob_bounds = [0, 0.2, 0.4, 0.6, 0.8, 1] + + + ii = np.arange(0,28) + jj = np.arange(0,28) + xx, yy = np.meshgrid(ii, jj) + + divnorm_bounds = colors.TwoSlopeNorm(vmin=0, vcenter=0.1, vmax=3) + + for j, lead_time in enumerate(lead_times): + rain_img_y = torch.zeros((w,h)) + + for i in range(y.shape[1]): + idx_y_rain = torch.where( y[lead_time,i] == 1) + rain_img_y[idx_y_rain] = i*increment + + #zz = rain_img_y[xx,yy] + + rain_img_y_hat = torch.sum(y_hat[lead_time,1:], dim = 0) + + #np.save("att_visualisera.npy", rain_img_y.cpu().detach().numpy()) + zz = rain_img_y.cpu().detach().numpy() + + zz_hat = rain_img_y_hat.cpu().detach().numpy() + im1 = ax[j,0].contourf(xx,yy,zz,bounds,cmap = "Reds", extend="both", norm = divnorm_bounds) + + ax[j,0].set_title(f"Lead time:{(lead_time*5+5)*spacing} min") + im2 = ax[j,1].contourf(xx,yy,zz_hat,prob_bounds,cmap = "Greens") + ax[j,1].set_title(f"Lead time:{(lead_time*5+5)*spacing} min") + ax[j,0].get_xaxis().set_visible(False) + ax[j,0].get_yaxis().set_visible(False) + ax[j,1].get_xaxis().set_visible(False) + ax[j,1].get_yaxis().set_visible(False) + + #fig.subplots_adjust(left=0.2) + + + cb1 = fig.colorbar(im1, ax=ax[:,0]) + cb1.set_label("Rain [mm/h]") + cb2 = fig.colorbar(im2, ax=ax[:,1]) + cb2.set_label("Probabillity of rain>0.2mm/h") + fig.suptitle("Ground truth rainfall (left) vs. Prediction probabillity (right)") + plt.show() def plot_bins(x, y1, title1="", y2 = 0, title2=""): N = y1.shape[0] diff --git a/test_metnet_lightning.py b/test_metnet_lightning.py new file mode 100644 index 0000000..d6a019b --- /dev/null +++ b/test_metnet_lightning.py @@ -0,0 +1,71 @@ +from metnet.models.metnet_pylight import MetNetPylight +import torch +import torch.nn.functional as F +from data_prep.prepare_data_MetNet import load_data +import pytorch_lightning as pl +from pytorch_lightning.loggers import WandbLogger +import wandb +from pytorch_lightning.callbacks import DeviceStatsMonitor +from pytorch_lightning import seed_everything +import time + +wandb.login() + +'''model = MetNetPylight( + hidden_dim=256, #384 original paper + forecast_steps=60, #240 original paper + input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 + output_channels=128, #512 + input_size=112, # 112 + n_samples = None, #None = All ~ 23000 + num_workers = 4, + batch_size = None, #None = 8 + learning_rate = 1e-2, + num_att_layers = 8, + plot_every = None, #Plot every global_step + rain_step = 0.2, + momentum = 0.9, + att_heads=16, + keep_biggest = 0.3, + )''' +#PATH_cp = "epoch=653-step=90251.ckpt" +#PATH_cp = "epoch=429-step=59339.ckpt" +#PATH_cp = "epoch=276-step=14680.ckpt" +#PATH_cp = "epoch=61-step=3285.ckpt" +#PATH_cp = "epoch=464-step=24644.ckpt" + +#PATH_cp = "epoch=471-step=25015.ckpt" +#PATH_cp = "epoch=33-step=3569.ckpt" +#PATH_cp = "epoch=430-step=22842.ckpt" +# +PATH_cp = "8leadtimessecond8h.ckpt" +PATH_cp = "epoch=242-step=16766.ckpt" + +model = MetNetPylight.load_from_checkpoint(PATH_cp) +model.keep_biggest = 0.1 +#model.batch_size = 8 +model.n_samples = None + +model.plot_every = None +#MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. +#print(model) +model.f1s = [0 for _ in range(model.forecast_steps)] +model.f1s_control = [0 for _ in range(model.forecast_steps)] +model.f1_count = 0 +model.avg_y_img = [0 for _ in range(model.forecast_steps)] +model.avg_y_hat_img = [0 for _ in range(model.forecast_steps)] + + + +wandb_logger = WandbLogger(project="lit-wandb") + + + +trainer = pl.Trainer(track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=10, logger = wandb_logger,strategy="ddp") + + + +start_time = time.time() +trainer.test(model) +print("--- %s seconds ---" % (time.time() - start_time)) +wandb.finish() From e13c39ef9f1f0d03515856de061e1ff8bf3ea27e Mon Sep 17 00:00:00 2001 From: ValterFallenius Date: Tue, 12 Apr 2022 15:34:06 +0200 Subject: [PATCH 13/17] Bugfix with spatial aggregation, drop early return --- MetNet_lightning.py | 11 +- data_prep/metnet_dataloader.py | 248 +++++++++++++++++++------------- metnet/models/metnet_pylight.py | 20 ++- test_metnet_lightning.py | 11 +- 4 files changed, 182 insertions(+), 108 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index 4880ea3..ed69b60 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -7,11 +7,12 @@ import wandb from pytorch_lightning.callbacks import DeviceStatsMonitor from pytorch_lightning import seed_everything +from pytorch_lightning.callbacks.early_stopping import EarlyStopping import time wandb.login() -'''model = MetNetPylight( +model = MetNetPylight( hidden_dim=256, #384 original paper forecast_steps=8, #240 original paper input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 @@ -28,10 +29,10 @@ att_heads=16, keep_biggest = 0.15, leadtime_spacing = 3, #1: 5 minutes, 3: 15 minutes - )''' + ) #PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/epoch=430-step=22842.ckpt" -PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/lit-wandb/jbd0j048/checkpoints/epoch=210-step=14558.ckpt" -model = MetNetPylight.load_from_checkpoint(PATH_cp) +#PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/lit-wandb/jbd0j048/checkpoints/epoch=210-step=14558.ckpt" +#model = MetNetPylight.load_from_checkpoint(PATH_cp) print(model) #model.n_samples = 2000 @@ -46,7 +47,7 @@ -trainer = pl.Trainer(num_sanity_val_steps=2, track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=50, logger = wandb_logger,strategy="ddp", early_stop_callback=True) +trainer = pl.Trainer(num_sanity_val_steps=2, track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=50, logger = wandb_logger,strategy="ddp", callbacks=[EarlyStopping(monitor="val_loss", mode="min")]) start_time = time.time() trainer.fit(model) diff --git a/data_prep/metnet_dataloader.py b/data_prep/metnet_dataloader.py index ce11ecb..49693a0 100644 --- a/data_prep/metnet_dataloader.py +++ b/data_prep/metnet_dataloader.py @@ -1,5 +1,5 @@ import torch -from .prepare_data_MetNet import load_data +#from .prepare_data_MetNet import load_data from torch.utils.data import Dataset, DataLoader import math import numpy as np @@ -7,111 +7,167 @@ import matplotlib.pyplot as plt class MetNetDataset(Dataset): - def __init__(self,file_name,N, lead_times = 60, rain_step = 0.2, n_bins = 512, skip_data = 0, keep_biggest = 0.8, printer = True): - """ - Input: - file_name: path to hdf5-file. - N: time-window in 5minut increments. N=1000 means 5000 minutes of data - lead_times: Lead times - Output: - X: numpy array shape (None, time, channels, width, height), - Y: numpy array shape (None, lead_times, channels, width, height) - """ - data_path = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/data_exclusive/" + def __init__(self,ID, N=None, keep_biggest = 0.5, leadtime_spacing = 1, lead_times = 60): + + data_path = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/bin_sorted_data/"+ID+"/" + self.leadtime_spacing = leadtime_spacing + + self.file_names = [] + self.ID = ID + self.means = [] + self.weights = None + file_list = os.listdir(data_path) - means = [] - dates = [] - names = [] - for name in file_list: - if not name[-4:] == ".npy": continue - if name[-5:] == "Y.npy": continue - names.append(name) - name = name[:-4] #remove .npy - name = name.split("/")[-1] #remove directories - #print(name) - mean = name.split("_")[0] - date = "_".join(name.split("_")[1:-1]) - print(mean, date) - means.append(float(mean)) - dates.append(date) - means = np.array(means) - idx_sorted = np.argsort(means) - print("MEANS: ", means[idx_sorted]) - print(len(means)) - N = min(N,len(means)) + if not N: + N = len(file_list) - '''bins = np.arange(-35, 40, 5) # fixed bin size - plt.xlim([min(means)-5, max(means)+5]) - - plt.hist(means, bins=bins, alpha=0.5) - plt.title('Mean rainfall input') - plt.xlabel('mean DBZ') - plt.ylabel('count') - - plt.show()''' - X = np.empty((N, 7, 15, 112,112)) - Y = np.empty((N, lead_times, n_bins, 28,28)) - for i in range(N): - if (i+1)%100==0: - print(f"Loaded samples: {i+1}/{N}") - idx = idx_sorted[-(i+1)] - date = dates[idx] - name_X = data_path + names[idx] - name_Y = data_path + names[idx].replace("X", "Y") - X_here = np.load(name_X) - #X_here = np.expand_dims(X_here,axis = 0) - #print(f"X SHAPE: {X_here.shape}") - Y_here = np.load(name_Y) - #print(f"Y SHAPE: {Y_here.shape}") - #Y_here = np.expand_dims(Y_here,axis = 0) - X[i] = X_here - Y[i] = Y_here - '''to_plot = np.mean(X_here[:,0:4], axis = 1) - fig, axs = plt.subplots(1,7) - for i in range(7): - axs[i].imshow(to_plot[i]) - fig.suptitle(name_X) - plt.show()''' + for file_name in file_list: + if file_name[-5] == "X": + self.file_names.append(data_path+file_name) + mean = float(file_name.split("_")[0]) + + self.means.append(mean) + + + self.means = np.array(self.means) + idx_sorted = np.argsort(-self.means) + to_keep = int(N*keep_biggest) + + + + idx_to_keep = idx_sorted[:to_keep] + self.file_names = [self.file_names[idx] for idx in idx_sorted[:to_keep]] + + if ID == "train": + minimum_rain = 5 + skipped = 0 + try: + self.rainy_leadtimes = np.load(f"leadtime_sampling_N_{N}_leads_{lead_times}_spacing_{leadtime_spacing}_minimum_{minimum_rain}.npy") + with open(f"leadtime_sampling_N_{N}_leads_{lead_times}_spacing_{leadtime_spacing}_minimum_{minimum_rain}.txt", 'r') as f: + self.file_names = [a.replace("\n","") for a in list(f.readlines())] + + + except FileNotFoundError: + self.rainy_leadtimes = [] + copy_to_it = self.file_names[:] + for j, file_name in enumerate(copy_to_it): + if j%100==0: + print(f"Progress {j} / {len(self.file_names)}") + + y = np.load(file_name.replace("X","Y")) + rainy_leads = [] + + for i, y_here in enumerate(y[self.leadtime_spacing-1::self.leadtime_spacing]): + if i>=lead_times: + break + if np.sum(y_here[1:])>minimum_rain: + rainy_leads.append(i) + else: + #print("skipping one") + skipped += 1 + + if not rainy_leads: + len_before = len(self.file_names) + self.file_names.remove(file_name) + assert len_before-len(self.file_names) == 1 - '''if skip_data == 0: - X,Y,X_dates,Y_dates = load_data(file_name, N = N, lead_times = lead_times, rain_step = rain_step, n_bins = n_bins, keep_biggest = keep_biggest, printer = printer) - else: - X,Y = skip_data''' - #print("X SHAPE: ", X.shape) - #print("Y SHAPE: ", Y.shape) - self.x = torch.from_numpy(X) - self.y = torch.from_numpy(Y) - - self.n_samples=X.shape[0] + print("SKIPPING ", file_name) + + else: + for i in np.random.choice(rainy_leads,(lead_times-len(rainy_leads))): + rainy_leads.append(i) + rainy_leads = np.array(rainy_leads) + assert len(rainy_leads) == lead_times + self.rainy_leadtimes.append(rainy_leads) + + assert len(self.rainy_leadtimes) == len(self.file_names) + np.save(f"leadtime_sampling_N_{N}_leads_{lead_times}_spacing_{leadtime_spacing}_minimum_{minimum_rain}.npy",np.array(self.rainy_leadtimes)) + with open(f"leadtime_sampling_N_{N}_leads_{lead_times}_spacing_{leadtime_spacing}_minimum_{minimum_rain}.txt", 'w') as f: + for item in self.file_names: + f.write(f"{item}\n") + '''n_uniques = [] + for leads in self.rainy_leadtimes: + unique = np.unique(leads) + n_uniques.append(len(unique)) + plt.hist(n_uniques, bins = lead_times) + plt.title("Number of unique leadtimes") + plt.show() + a = {} + for lead in range(lead_times): + a[lead] = len(np.where(self.rainy_leadtimes==lead)[0]) + print(a) + plt.hist(self.rainy_leadtimes.reshape(-1), bins = lead_times) + plt.title("resampling of leadtimes") + plt.show()''' + self.n_samples = len(self.file_names) + def __getitem__(self, index): # allows indexing dataset[0] - return self.x[index], self.y[index] + name_x = self.file_names[index] + name_y = name_x.replace("X","Y") + x = np.load(name_x) + + y = np.load(name_y) + + y = y[self.leadtime_spacing-1::self.leadtime_spacing] + + x = torch.from_numpy(x) + y = torch.from_numpy(y) + if self.ID == "train": + + return x, y, self.rainy_leadtimes[index] + return x, y def __len__(self): # Will allow len(data) return self.n_samples if __name__=="__main__": - dataset = MetNetDataset("combination_all_pn157.h5", N = 1000) - dataloader = DataLoader(dataset=dataset,batch_size=4,shuffle=True) - - eps = 2 - tot_samps = len(dataset) - n_iterations = math.ceil(tot_samps/4) - print(tot_samps, n_iterations) - for epoch in range(eps): - for i, (inputs,labels) in enumerate(dataloader): - if (i+1)%5 == 0: - print(f"epoch {epoch}/{eps}, step {i+1}/{n_iterations}, input {inputs.shape}") - - - - print("DONE NOW") - input() - dataiter = iter(dataloader) - - data = dataiter.next() - features, labels = data - print(features.shape, labels.shape) + test_data = MetNetDataset("train", N = None, keep_biggest = 1) + n_to_plot = 128 + '''BINS = np.zeros((n_to_plot,)) + + for j,(x,y) in enumerate(test_data): + if j%100==0: print(f"Progress {j}/{len(test_data)}") + for i in range(n_to_plot): + BINS[i] += np.sum(y[0,i].numpy()) + np.save(f"bin_count_no_keep_biggest.npy",BINS)''' + + BINS1 = np.load("bin_count.npy") + BINS2 = np.load("bin_count_no_keep_biggest.npy") + N_1 = np.sum(BINS1) + N_2 = np.sum(BINS2) + BINS1 /= N_1 + BINS2 /= N_2 + for i,a in enumerate(BINS1): + if a==0: + BINS1[i] = BINS1[i-1] + for i,a in enumerate(BINS2): + if a==0: + BINS2[i] = BINS2[i-1] + + + + #BINS = np.load("bin_count_no_keep_biggest.npy") + + rain_mm = np.arange(128)*0.2 + plt.plot(rain_mm,100*BINS1, label =r"$Y_{15\%}$") + plt.plot(rain_mm,100*BINS2, label =r"$Y_{100\%}$") + plt.legend() + plt.yscale("log") + plt.xlabel("Rain rate [mm/h]") + plt.ylabel("Percentage [%]") + plt.title("Percentage of pixels per rain rate") + plt.show() + + plt.plot(rain_mm, 100*BINS1/BINS2, label =r"$\frac{bin_{15}}{bin_{100}}$") + plt.legend() + #plt.yscale("log") + plt.xlabel("Rain rate [mm/h]") + plt.ylabel("Percentage difference [%]") + plt.title("Impact of keeping 15% of data") + plt.show() + + diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index e4af5df..111bf07 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -17,6 +17,7 @@ import PIL import matplotlib as mpl from matplotlib import colors +from metnet.layers.utils import get_conv_layer class MetNetPylight(pl.LightningModule, PyTorchModelHubMixin): def __init__( @@ -113,7 +114,20 @@ def __init__( for _ in range(num_att_layers) ] ) - + '''conv2d = get_conv_layer(conv_type="standard") + self.conv_agg = nn.Sequential( + conv2d(hidden_dim, hidden_dim, kernel_size=(28,1), padding="same"), + conv2d(hidden_dim, hidden_dim, kernel_size=(1,28), padding="same"), + conv2d(hidden_dim, hidden_dim, kernel_size=(3,3), padding=1), + #nn.MaxPool2d((2, 2), stride=2), + # antialiased_cnns.BlurPool(160, stride=2) if antialiased else nn.Identity(), + nn.BatchNorm2d(hidden_dim), + conv2d(hidden_dim, hidden_dim, kernel_size=(28,1)), + conv2d(hidden_dim, hidden_dim, kernel_size=(1,28)), + conv2d(hidden_dim, hidden_dim, kernel_size=(3,3), padding=1), + #nn.MaxPool2d((2, 2), stride=2), + # antialiased_cnns.BlurPool(output_channels, stride=2) if antialiased else nn.Identity(), + )''' self.head = nn.Conv2d(hidden_dim, output_channels, kernel_size=(1, 1)) # Reduces to mask self.double() @@ -155,9 +169,10 @@ def encode_timestep(self, x, fstep=1, lead_times = []): #print("\n shape after temp enc: ", state.shape) agg = self.temporal_agg(state) + #agg = self.conv_agg(state) #plot_channels(x, 1, tit_add = "after agg") #print("\n shape after temporal_agg: ", agg.shape) - return state + return agg def forward(self, imgs,lead_time = 0, lead_times = []): """It takes a rank 5 tensor @@ -181,6 +196,7 @@ def forward(self, imgs,lead_time = 0, lead_times = []): #plot_channels(out, 1, tit_add = "after softmax") #plot_bins(out[0], " after head",soft[0], "after softmax") return out + def training_step(self, batch, batch_idx): diff --git a/test_metnet_lightning.py b/test_metnet_lightning.py index d6a019b..d79064a 100644 --- a/test_metnet_lightning.py +++ b/test_metnet_lightning.py @@ -39,14 +39,14 @@ #PATH_cp = "epoch=430-step=22842.ckpt" # PATH_cp = "8leadtimessecond8h.ckpt" -PATH_cp = "epoch=242-step=16766.ckpt" +#PATH_cp = "epoch=242-step=16766.ckpt" model = MetNetPylight.load_from_checkpoint(PATH_cp) -model.keep_biggest = 0.1 +#model.keep_biggest = 0.1 #model.batch_size = 8 -model.n_samples = None +#model.n_samples = None -model.plot_every = None +#model.plot_every = None #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. #print(model) model.f1s = [0 for _ in range(model.forecast_steps)] @@ -54,7 +54,8 @@ model.f1_count = 0 model.avg_y_img = [0 for _ in range(model.forecast_steps)] model.avg_y_hat_img = [0 for _ in range(model.forecast_steps)] - +model.skipped = 0 +model.not_skipped = 0 wandb_logger = WandbLogger(project="lit-wandb") From 89bcfd986cbd3ad591fd23fbc53943e994afd96c Mon Sep 17 00:00:00 2001 From: ValterFallenius Date: Thu, 21 Apr 2022 11:43:41 +0200 Subject: [PATCH 14/17] Weekly backup --- MetNet_lightning.py | 34 ++++-- data_prep/metnet_dataloader.py | 5 +- metnet/models/metnet_pylight.py | 189 +++++++++++++++++++------------- test_metnet_lightning.py | 37 +++++-- 4 files changed, 171 insertions(+), 94 deletions(-) diff --git a/MetNet_lightning.py b/MetNet_lightning.py index ed69b60..3232bee 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -8,13 +8,14 @@ from pytorch_lightning.callbacks import DeviceStatsMonitor from pytorch_lightning import seed_everything from pytorch_lightning.callbacks.early_stopping import EarlyStopping +from pytorch_lightning.callbacks import ModelCheckpoint import time wandb.login() model = MetNetPylight( hidden_dim=256, #384 original paper - forecast_steps=8, #240 original paper + forecast_steps=1, #240 original paper input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 output_channels=128, #512 input_size=112, # 112 @@ -22,20 +23,29 @@ num_workers = 4, batch_size = 8, learning_rate = 1e-2, - num_att_layers = 8, + num_att_layers = 4, plot_every = None, #Plot every global_step rain_step = 0.2, momentum = 0.9, att_heads=16, - keep_biggest = 0.15, - leadtime_spacing = 3, #1: 5 minutes, 3: 15 minutes + keep_biggest = 1, + leadtime_spacing = 12, #1: 5 minutes, 3: 15 minutes ) -#PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/epoch=430-step=22842.ckpt" -#PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/lit-wandb/jbd0j048/checkpoints/epoch=210-step=14558.ckpt" +#PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/8leads with agg.ckpt" +#PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/full_run_continue.ckpt" #model = MetNetPylight.load_from_checkpoint(PATH_cp) -print(model) - -#model.n_samples = 2000 +model.testing = False +model.learning_rate = 1e-2 +'''print(model) +print(model.forecast_steps) +print(model.input_channels) +print(model.output_channels) +print(model.n_samples) +print(model.file_name) +print(model.keep_biggest) +input() +model.keep_biggest = 0.9 +model.n_samples = None''' #model.printer = True #model.plot_every = None #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. @@ -43,11 +53,11 @@ #wandb.restore("/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/wandb/run-20220331_162434-21o2u2sj" #wandb.init(run_id = "21o2u2sj",resume="must") -wandb_logger = WandbLogger(project="lit-wandb") - +wandb_logger = WandbLogger(project="lit-wandb", log_model="all") +checkpoint_callback = ModelCheckpoint(monitor="validation/loss_epoch", mode="min", save_top_k=5) -trainer = pl.Trainer(num_sanity_val_steps=2, track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=50, logger = wandb_logger,strategy="ddp", callbacks=[EarlyStopping(monitor="val_loss", mode="min")]) +trainer = pl.Trainer(num_sanity_val_steps=2, track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=50, logger = wandb_logger,strategy="ddp", callbacks=[checkpoint_callback]) start_time = time.time() trainer.fit(model) diff --git a/data_prep/metnet_dataloader.py b/data_prep/metnet_dataloader.py index 49693a0..4481de8 100644 --- a/data_prep/metnet_dataloader.py +++ b/data_prep/metnet_dataloader.py @@ -101,6 +101,7 @@ def __init__(self,ID, N=None, keep_biggest = 0.5, leadtime_spacing = 1, lead_tim plt.title("resampling of leadtimes") plt.show()''' + self.n_samples = len(self.file_names) def __getitem__(self, index): @@ -110,7 +111,7 @@ def __getitem__(self, index): x = np.load(name_x) y = np.load(name_y) - + persistence = y[0] y = y[self.leadtime_spacing-1::self.leadtime_spacing] x = torch.from_numpy(x) @@ -118,6 +119,8 @@ def __getitem__(self, index): if self.ID == "train": return x, y, self.rainy_leadtimes[index] + if self.ID == "test": + return x, y, persistence return x, y def __len__(self): diff --git a/metnet/models/metnet_pylight.py b/metnet/models/metnet_pylight.py index 111bf07..ea528d3 100644 --- a/metnet/models/metnet_pylight.py +++ b/metnet/models/metnet_pylight.py @@ -83,7 +83,7 @@ def __init__( self.keep_biggest = keep_biggest self.weights = None self.leadtime_spacing = leadtime_spacing - + self.testing = False if str(self.device)== "cuda:0": self.printer = True else: @@ -162,12 +162,13 @@ def encode_timestep(self, x, fstep=1, lead_times = []): # Temporal Encoder #_, state = self.temporal_enc(self.drop(x)) - dropped = self.drop(x) - _, state = self.temporal_enc(dropped) + if not self.testing: + x = self.drop(x) + _, state = self.temporal_enc(x) embedded = self.position_embedding(state) #plot_channels(state, 1, tit_add = "after temporal_enc") #print("\n shape after temp enc: ", state.shape) - + #return state #REMOVEMOMROEMREOMREOMREORMERRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR agg = self.temporal_agg(state) #agg = self.conv_agg(state) #plot_channels(x, 1, tit_add = "after agg") @@ -230,25 +231,50 @@ def validation_step(self, batch, batch_idx): lead_times = list(range(self.forecast_steps)) loss = 0 - + f1_val = 0 + f1_count = 0 L = CrossEntropyLoss() for lead_time in lead_times: y_hat = self(x.float(),lead_time) - loss += L(y_hat, y[:,lead_time]) - + ''' + # F1 score: + softed = torch.softmax(y_hat, dim=1) + n_soft = softed.shape[0] + rainy = torch.sum(y_hat[:,1:], dim=1) + idx_rain = [torch.where(rainy[j]>y_hat[j,0]) for j in range(n_soft)] + for i, soft in enumerate(softed): + if len(idx_rain[i])==0: + continue + else: + f1_count +=1 + truth = torch.sum(y[i,lead_time,1:],dim = 0) + pred = torch.zeros(truth.shape) + idx_here = idx_rain[i] + pred[idx_here] = 1 + + truth_flat = torch.flatten(truth) + pred_flat = torch.flatten(pred) + print("SUM FLAT: ", torch.sum(truth_flat), torch.sum(pred_flat)) + + f1_here = f1_score(truth_flat.cpu().detach().numpy(),pred_flat.cpu().detach().numpy(), zero_divison=0) + + f1_val += f1_here''' + '''y_img, y_hat_img = thresh_imgs(y[0,lead_time], y_hat[0], thresh_bin = 1) if log_img: self.logger.experiment.log({f"val_{lead_time}":[wandb.Image(y_img.cpu(), caption=f"y leadtime {lead_time}"), wandb.Image(y_hat_img.cpu(), caption=f"y_hat leadtime {lead_time}")]})''' - loss /= self.forecast_steps - self.log("validation/loss_epoch", loss, on_step=False, on_epoch=True) - return {"loss": loss} + loss /= self.forecast_steps + #f1_val /= f1_count + self.log("validation/loss_epoch", loss, on_step=False, on_epoch=True) + #self.log("validation/f1_score", f1_val, on_step=False, on_epoch=True) + return {"loss": loss} #, "f1_val": f1_val def test_step(self, batch, batch_idx): - x,y = batch + x,y, persistence = batch @@ -261,21 +287,57 @@ def test_step(self, batch, batch_idx): loss += L(y_hat_here, y[:,lead_time]) y_hat[:,lead_time] = y_hat_here loss /= self.forecast_steps - self.log("test/loss_epoch", loss, on_step=False, on_epoch=True) + self.log("test/loss_epoch", loss, on_step=True, on_epoch=True) # ---------- calculate test_loss ---------- - - thresh = 1 - no_rain = torch.zeros(y[0,0].shape, device = self.device) - no_rain[0] = 1 + # ---------- calculate f1_score ---------- + self.probabillity_thresh = 0 + thresh = self.thresh + rain_y = torch.sum(y[:,:,thresh:], dim=2) + softed = torch.softmax(y_hat,dim=2) + no_rain_y_hat = torch.sum(softed[:,:, 0:thresh], dim=2) + rain_y_hat = torch.sum(softed[:,:,thresh:], dim=2) + threshed_y_hat = torch.zeros(rain_y_hat.shape) + idx_above = torch.where(rain_y_hat>self.probabillity_thresh) + threshed_y_hat[idx_above] = 1 + + + + + for sample in range(y.shape[0]): + + after_five = persistence[sample] + after_five = torch.sum(after_five[thresh:],dim=0) + after_five = torch.flatten(after_five).cpu().detach().numpy() + f1_here = [] + for lead_time in range(self.forecast_steps): + if torch.sum(rain_y[sample,lead_time])<5: + self.skipped += 1 + continue + truth = torch.flatten(rain_y[sample,lead_time]).cpu().detach().numpy() + pred = torch.flatten(threshed_y_hat[sample,lead_time]).cpu().detach().numpy() + + f1 = f1_score(truth, pred) + f1_after_five = f1_score(truth, after_five) + f1_here.append((f1,f1_after_five)) + self.f1s[lead_time].append(f1) + self.f1s_control[lead_time].append(f1_after_five) + + #plot_probabillity(y[sample],softed[sample],[kk for kk in range(0,self.forecast_steps,self.forecast_steps//3)],increment = 0.2, spacing = self.leadtime_spacing, f1_scores = f1_here) + + + + + + # ---------- calculate f1_score ---------- - ''' - #for i in range(y.shape[0]): - for i in range(1): + + #for i in range(y.shape[0]): #REWRITE + '''for i in range(1): temp3 = x[i,-1,0:4].cpu().numpy() temp3 = np.mean(temp3, axis = 0) temp3 = (temp3 + np.min(temp3))/(np.max(temp3)-np.min(temp3)) @@ -287,14 +349,15 @@ def test_step(self, batch, batch_idx): imgs = [] capts = [] for lead_time in range(0,self.forecast_steps,10): - + if torch.sum(y[i,lead_time,0])>28*28-5: + continue y_img, y_hat_img, f1,f1_control = thresh_imgs(y[i,lead_time], y_hat[i,lead_time], no_rain, thresh_bin = thresh) self.avg_y_img[lead_time]+=(torch.mean(y_img.cpu())) self.avg_y_hat_img[lead_time]+=(torch.mean(y_hat_img.cpu())) self.f1s[lead_time] += f1 self.f1s_control[lead_time] += f1_control - if lead_time in list(range(0,60,10)): + if lead_time in list(range(0,self.forecast_steps,self.forecast_steps//3)): temp1 = y_img.cpu().numpy() pil_im_y = PIL.Image.fromarray(np.uint8(temp1)*255) @@ -321,10 +384,10 @@ def test_step(self, batch, batch_idx): plt.show() self.logger.log_image(key=f"val_{lead_time}", images=imgs, caption = capts) ''' - for i in range(x.shape[0]): + '''for i in range(x.shape[0]): softed = torch.softmax(y_hat[i],dim=1) - plot_probabillity(y[i],softed,[kk for kk in range(0,self.forecast_steps,self.forecast_steps//3)],increment = 0.2, spacing = self.leadtime_spacing) - #plot_categories(y[i,0],softed,increment = 0.2) + plot_probabillity(y[i],softed,[kk for kk in range(0,self.forecast_steps,self.forecast_steps//3)],increment = 0.2, spacing = self.leadtime_spacing)''' + #plot_categories(y[i,0],softed,increment = 0.2) #plot bins: '''for i in range(x.shape[0]): @@ -363,22 +426,27 @@ def validation_epoch_end(self, val_step_outputs): table = wandb.Table(data = hist_scores, columns = ["lead_times"]) wombo.log({"leadtimes_histogram": wombo.plot.histogram(table, "Lead times", title="Lead times histogram")})''' outs = tuple([x["loss"] for x in val_step_outputs]) - + #outs_f1 = tuple([x["f1_val"] for x in val_step_outputs]) avg_val_loss = torch.tensor(outs, device = self.device).mean() - - return {"val_loss":avg_val_loss} + #avg_val_f1 = torch.tensor(outs, device = self.device).mean() + return {"val_loss":avg_val_loss} #, "val_f1_avg": avg_val_f1} def test_epoch_end(self,test_step_outputs): - f1mean = np.array(self.f1s)/self.f1_count - f1_control_mean = np.array(self.f1s_control)/self.f1_count - plt.plot(f1mean,"b", label="f1 meaned") - plt.plot(f1_control_mean,"--g", label="no rain controll") + f1_mean = [ np.mean(f1s) for f1s in self.f1s] + lens = [len(f1s) for f1s in self.f1s] + print(f"Skipped {self.skipped} / {self.skipped + sum(lens)}") + f1_mean = np.array(f1_mean) + f1_control_mean = np.array([ np.mean(f1s) for f1s in self.f1s_control]) + np.save(f"f1_threshed_{self.probabillity_thresh}_N_{sum(lens)}_thresh_{self.thresh}.npy",f1_mean) + np.save(f"f1_control_N_{sum(lens)}_thresh_{self.thresh}.npy",f1_control_mean) + plt.plot(f1_mean,"b", label="f1 meaned") + plt.plot(f1_control_mean,"--g", label="persistence") plt.legend() - plt.title(f"f1-scores over leadtimes") + plt.title(f"f1-scores, P(rate>0.2)>{self.probabillity_thresh}") plt.xlabel("lead_time") plt.ylabel("f1") plt.show() - y_means = np.array(self.avg_y_img)/self.f1_count + '''y_means = np.array(self.avg_y_img)/self.f1_count y_hat_means = np.array(self.avg_y_hat_img)/self.f1_count plt.plot(y_means,"b", label="y means") plt.plot(y_hat_means,"g", label="y_hat means") @@ -386,7 +454,7 @@ def test_epoch_end(self,test_step_outputs): plt.title(f"y and yhat above threshhold at different leadtimes") plt.xlabel("lead_time") plt.ylabel("mean") - plt.show() + plt.show()''' def configure_optimizers(self): optimizer = optim.SGD(self.parameters(),lr=self.learning_rate, momentum = self.momentum) @@ -395,7 +463,7 @@ def configure_optimizers(self): def setup(self, stage = None): self.train_data = metnet_dataloader.MetNetDataset("train", N = self.n_samples , keep_biggest = self.keep_biggest, leadtime_spacing = self.leadtime_spacing, lead_times = self.forecast_steps) self.val_data = metnet_dataloader.MetNetDataset("val", N = None, keep_biggest = 1, leadtime_spacing = self.leadtime_spacing) - self.test_data = metnet_dataloader.MetNetDataset("test", N = self.n_samples, keep_biggest = 1, leadtime_spacing = self.leadtime_spacing) + self.test_data = metnet_dataloader.MetNetDataset("test", N = self.n_samples, keep_biggest = 0.1, leadtime_spacing = self.leadtime_spacing) '''if self.train_data.weights is not None: self.weights = self.train_data.weights''' @@ -406,34 +474,7 @@ def setup(self, stage = None): print(f"Validation data samples = {len(self.val_data)}") print(f"Test data samples = {len(self.test_data)}") - #print("SHAPES: ", self.train_data.shape, self.val_data.shape, self.test_data.shape) - #print([i.split("/")[-1][0:6] for i in self.train_data.file_names]) - print("FIXING IMBALANCE") - self.class_balancing = torch.from_numpy(np.load("/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/class_imbalance.npy")).to(self.device) - self.class_balancing_half = torch.from_numpy(np.load("/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/class_imbalance0.15.npy")).to(self.device) - - a = torch.divide(self.class_balancing, self.class_balancing_half).cpu().numpy() - a = np.nan_to_num(a) - b = np.arange(0,len(a)) - idx = np.where(a!=0) - #print(len(a)) - #print(a) - #plt.plot(self.class_balancing.cpu(), "--r") - '''plt.scatter(b[idx],a[idx]) - plt.title("") - #plt.yscale("log") - plt.show()''' - self.rain_bins = torch.zeros((self.output_channels,)) - '''for i, batch in enumerate(self.train_data): - x,y = batch - - self.rain_bins += torch.sum(y.cpu(),dim = [0,1,3,4]) - if i%50==0: - print(f"{i}/{len(self.train_data)}") - np.save(f"/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/class_imbalance{self.keep_biggest}.npy",self.rain_bins.numpy()) - plt.plot(self.rain_bins) - plt.yscale("log") - plt.show()''' + def train_dataloader(self): train_loader = DataLoader(dataset=self.train_data,batch_size=self.batch_size, num_workers = self.workers, shuffle = True) @@ -463,23 +504,23 @@ def thresh_imgs(y, y_hat, after_five, thresh_bin = 1): y_below = torch.sum(y[0:thresh_bin], dim=0) y_above = torch.sum(y[thresh_bin:], dim=0) - y_outcome = torch.ones((w, h)) + y_outcome = torch.zeros((w, h)) y_idx_less_rain = torch.where(y_below=0.2)>{p}") + + fig.suptitle("Different probabillity thresholds for precipitation") + ax.set_xlabel("Lead time") + ax.set_ylabel("F1") + ax.legend() + plt.show() + wandb.login() @@ -38,20 +58,23 @@ #PATH_cp = "epoch=33-step=3569.ckpt" #PATH_cp = "epoch=430-step=22842.ckpt" # -PATH_cp = "8leadtimessecond8h.ckpt" +#PATH_cp = "8leadtimessecond8h.ckpt" #PATH_cp = "epoch=242-step=16766.ckpt" +PATH_cp = "fullrun_1.ckpt" model = MetNetPylight.load_from_checkpoint(PATH_cp) -#model.keep_biggest = 0.1 -#model.batch_size = 8 -#model.n_samples = None +model.keep_biggest = 0.15 +model.thresh = 1 +model.batch_size = 8 +model.n_samples = None +model.testing = True #model.plot_every = None #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. #print(model) -model.f1s = [0 for _ in range(model.forecast_steps)] -model.f1s_control = [0 for _ in range(model.forecast_steps)] -model.f1_count = 0 +model.f1s = [[] for _ in range(model.forecast_steps)] +model.f1s_control = [[] for _ in range(model.forecast_steps)] +model.f1_count = [0 for _ in range(model.forecast_steps)] model.avg_y_img = [0 for _ in range(model.forecast_steps)] model.avg_y_hat_img = [0 for _ in range(model.forecast_steps)] model.skipped = 0 From 598fa96c60403cda883db43ec5d0f7faac4c9fd2 Mon Sep 17 00:00:00 2001 From: ValterFallenius Date: Tue, 26 Apr 2022 10:16:30 +0200 Subject: [PATCH 15/17] Implementing moving geographic point of reference --- MetNet_lightning.py | 23 +- prepare_files.py | 1062 ++++++++++++++++++++++++++++++++++++++ test_metnet_lightning.py | 33 +- 3 files changed, 1104 insertions(+), 14 deletions(-) create mode 100644 prepare_files.py diff --git a/MetNet_lightning.py b/MetNet_lightning.py index 3232bee..a64143e 100644 --- a/MetNet_lightning.py +++ b/MetNet_lightning.py @@ -13,9 +13,9 @@ wandb.login() -model = MetNetPylight( +'''model = MetNetPylight( hidden_dim=256, #384 original paper - forecast_steps=1, #240 original paper + forecast_steps=60, #240 original paper input_channels=15, #46 original paper, hour/day/month = 3, lat/long/elevation = 3, GOES+MRMS = 40 output_channels=128, #512 input_size=112, # 112 @@ -29,23 +29,22 @@ momentum = 0.9, att_heads=16, keep_biggest = 1, - leadtime_spacing = 12, #1: 5 minutes, 3: 15 minutes - ) + leadtime_spacing = 1, #1: 5 minutes, 3: 15 minutes + )''' #PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/8leads with agg.ckpt" #PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/full_run_continue.ckpt" -#model = MetNetPylight.load_from_checkpoint(PATH_cp) -model.testing = False -model.learning_rate = 1e-2 -'''print(model) + +PATH_cp = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/lit-wandb/2yap8c0s/checkpoints/epoch=754-step=52094_0.2231.ckpt" +model = MetNetPylight.load_from_checkpoint(PATH_cp) +model.learning_rate = 1e-3 +print(model) print(model.forecast_steps) print(model.input_channels) print(model.output_channels) print(model.n_samples) print(model.file_name) print(model.keep_biggest) -input() -model.keep_biggest = 0.9 -model.n_samples = None''' + #model.printer = True #model.plot_every = None #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. @@ -57,7 +56,7 @@ checkpoint_callback = ModelCheckpoint(monitor="validation/loss_epoch", mode="min", save_top_k=5) -trainer = pl.Trainer(num_sanity_val_steps=2, track_grad_norm = 2, max_epochs=1000, gpus=-1,log_every_n_steps=50, logger = wandb_logger,strategy="ddp", callbacks=[checkpoint_callback]) +trainer = pl.Trainer(num_sanity_val_steps=2, track_grad_norm = 2, max_epochs=2000, gpus=-1,log_every_n_steps=50, logger = wandb_logger,strategy="ddp", callbacks=[checkpoint_callback]) start_time = time.time() trainer.fit(model) diff --git a/prepare_files.py b/prepare_files.py new file mode 100644 index 0000000..e9701ec --- /dev/null +++ b/prepare_files.py @@ -0,0 +1,1062 @@ +import numpy as np +import h5py as h5 +import matplotlib +import matplotlib.pyplot as plt +import matplotlib.animation as animation +from pathlib import Path +import sys, os +from datetime import datetime, timedelta,date + +''' +Output: 5D tensor of shape (n_samples, time_dim, channels, width, height): + +''' + + + + +def space_to_depth(x, block_size): + x = np.asarray(x) + batch, height, width, depth = x.shape + reduced_height = height // block_size + reduced_width = width // block_size + y = x.reshape(batch, reduced_height, block_size, + reduced_width, block_size, depth) + z = np.swapaxes(y, 2, 3).reshape(batch, reduced_height, reduced_width, -1) + return z + +def date_assertion(dates,expected_delta = 5): + for date1,date2 in zip(dates[0:-1],dates[1:]): + list1 = date1.split("_") + #print(list1) + + y1, m1, d1, hour1, minute1 = [int(a) for a in list1] + + datetime1 = datetime(y1, m1, d1, hour=hour1, minute=minute1) + list2 = date2.split("_") + + y2, m2, d2, hour2, minute2 = [int(a) for a in list2] + + datetime2 = datetime(y2, m2, d2, hour=hour2, minute=minute2) + delta = datetime2-datetime1 + minutes = delta.total_seconds()/60 + + #print(datetime1) + #print(datetime2) + #print("DELTA ", minutes, "delta ", expected_delta) + assert int(minutes) == expected_delta + +def h5_iterator(h5_file,maxN = 100,spaced = 1, starter = 0): + """Iterates through the desired datafile and returns index, array and datetime""" + + with h5.File(h5_file,"r") as f: + + keys = list(f.keys())[starter:] + for i,name in enumerate(keys): + + if i%spaced!=0: + continue + j = i//spaced + obj = f[name] + + #print(name, obj) + if maxN: + if i//spaced>=maxN: break + #print(name) + date = name + y, m, d, hh, mm = date.split("_") + #title = f"Year: {y} month: {months[m]} day: {d} time: {t}" + array = np.array(obj) #flip array + + + #print(array.shape) + yield j, array, date + +def down_sampler(data, rate = 2): + """Spatial downsampling with vertical and horizontal downsampling rate = rate.""" + + down_sampled=data[:,0::rate,0::rate] + + return down_sampled + + + +def temporal_concatenation(data,dates,targets,target_dates,concat = 7, overlap = 0,spaced = 3,lead_times = 60): + """Takes the spatial 2D arrays and concatenates temporal aspect to 3D-vector (T-120min, T-105min, ..., T-0min) + concat = number of frames to encode in temporal dimension + overlap = how many of the spatial arrays are allowed to overlap in another datasample""" + n,x_size,y_size,channels = data.shape + n_y,x_y,y_y = targets.shape + + seq_length = spaced*concat + lead_times #5 minute increments + x_limit = n - seq_length//spaced + #concecutive time + X = [] + X_dates=[] + Y = [] + Y_dates = [] + for i,j in zip(range(0,x_limit,concat-overlap),range(concat*spaced-2,n_y,(concat-overlap)*spaced)): + + if (i+1)%1000==0: + print(f"\nTemporal concatenated samples: ",i+1) + temp_input = data[i:i+concat,:,:] + temp_target = targets[j:j+lead_times,:,:] + temp_dates = dates[i:i+concat] + temp_dates_target = target_dates[j:j+lead_times] + try: + date_assertion(temp_dates,expected_delta = 5*spaced) + date_assertion(temp_dates_target,expected_delta = 5) + fiver = [temp_dates[-1],temp_dates_target[0]] #final X date and first Y date should be 5 spaced minutes + date_assertion(fiver,expected_delta = 5) + except AssertionError: + print(f"Warning, dates are not alligned! Skipping: {i}:{i+seq_length}") + #print(temp_dates) + #print(temp_dates_target) + continue + X.append(temp_input) + X_dates.append(temp_dates) + Y.append(temp_target) + Y_dates.append(temp_dates_target) + X = np.array(X) + Y = np.array(Y) + + return X,Y,X_dates,Y_dates + +def extract_centercrop(data,factor_smaller=2): + + x0 = 0 + y0 = 0 + x1 = data.shape[2] + y1 = data.shape[1] + + try: + assert x1 == y1 + except AssertionError: + print(f"\nWarning: centercrop shapes ({x1}, {y1}) are not the same.") + centercrop_x_lim = slice(x0+x1//(2*factor_smaller),x1-x1//(2*factor_smaller)) + centercrop_y_lim = slice(y0+y1//(2*factor_smaller),y1-y1//(2*factor_smaller)) + + return data[:,centercrop_y_lim,centercrop_x_lim] + +def datetime_encoder(data,dates,plotter = False): + data_shape = data.shape + data_type = data.dtype + year_days = [] + day_minutes=[] + for i,date_string in enumerate(dates): + if (i+1)%1000==0: + print("Dates loaded for encoding: ",i+1) + list1 = date_string.split("_") + + + year,month,day, hour, minute = [int(a) for a in list1] + date_object = date(year,month,day) + day_of_the_year = date_object.timetuple().tm_yday + minute_of_the_day = hour*60 + minute + year_days.append(day_of_the_year) + day_minutes.append(minute_of_the_day) + year_days = np.array(year_days) + day_minutes = np.array(day_minutes) + year_days = np.repeat(year_days[:,np.newaxis],data_shape[1],axis=1) + year_days = np.repeat(year_days[:,:,np.newaxis],data_shape[2],axis=2) + day_minutes = np.repeat(day_minutes[:,np.newaxis],data_shape[1],axis=1) + day_minutes = np.repeat(day_minutes[:,:,np.newaxis],data_shape[2],axis=2) + + + + periodicals = [np.sin(2*np.pi*year_days/365,dtype =data_type), + np.cos(2*np.pi*year_days/365,dtype =data_type), + np.sin(2*np.pi*day_minutes/(60*24),dtype =data_type), + np.cos(2*np.pi*day_minutes/(60*24),dtype =data_type)] + periodicals = [np.expand_dims(a, axis=3) for a in periodicals] + date_array = np.concatenate(periodicals,axis=3) + + try: + assert date_array.shape[0:2] == data_shape[0:2] + except AssertionError: + print("Datetime dimensions seem wrong!") + raise + + if plotter: + fig,ax = plt.subplots(1,2) + ax[0].scatter(date_array[:,0,0,0],date_array[:,0,0,1]) + fig.suptitle(f"Periodical year and days from {dates[0]} to {dates[-1]}") + ax[1].scatter(date_array[:,0,0,2],date_array[:,0,0,3]) + ax[0].set_title("Year") + ax[1].set_title("Day") + + plt.show() + data = np.concatenate((data,date_array),axis=3) + return data + +def longlatencoding(data): + print(f"\nExtracting longitude, latitude and elevation data ...", end="") + + with h5.File("lonlatelev_full.h5","r") as FF: + lonlatelev = FF["lonlatelev"] + lonlatelev = np.array(lonlatelev) + print("LONLAT SHAPE:", lonlatelev.shape) + + lon = lonlatelev[:,:,0] + lat = lonlatelev[:,:,1] + + elev = lonlatelev[:,:,2] + + + lon_mean, lon_std = np.mean(lon), np.std(lon) + lat_mean, lat_std = np.mean(lat), np.std(lat) + elev /= np.max(np.abs(elev)) + elev_mean, elev_std = np.mean(elev), np.std(elev) + try: + assert lon_std != 0 + assert lat_std != 0 + assert elev_std != 0 + except AssertionError: + print("WARNING: LON LAT OR ELEV STD == 0") + lon = (lon-lon_mean)/lon_std + lat = (lat-lat_mean)/lat_std + elev = (elev-elev_mean)/elev_std + elev = np.log(elev-np.min(elev)+0.1) + + #lon = np.tanh(lon) + #lat = np.tanh(lat) + #elev = np.tanh(elev) + print("MINMAX lon: ", np.min(lon), np.max(lon)) + print("MINMAX lat: ", np.min(lat), np.max(lat)) + print("MINMAX elev: ", np.min(elev), np.max(elev)) + + lonlatelev[:,:,0] = lon + lonlatelev[:,:,1] = lat + lonlatelev[:,:,2] = elev + + lonlatelev = np.expand_dims(lonlatelev, axis=0) + lonlatelev = np.repeat(lonlatelev,data.shape[0],axis=0) + + print(f"\ndone! it has shape {lonlatelev.shape}") + + return np.concatenate((data,lonlatelev), axis=3, dtype = np.float32) + + + +def load_data(h5_path,N = 3000,lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True, rain_step = 0.2, n_bins=512, keep_biggest = 0.8): + #15 minutes between datapoints is default --> spaced = 3 + snapshots = [] + dates = [] + all_snapshots = [] + Y_dates = [] + array_mean = 0 + means = [] + n = 0 + if not printer: + sys.stdout = open(os.devnull, 'w') + for i, array,date in h5_iterator(h5_path, N): + + if (i+1)%1000==0: + print("Loaded samples: ",n) + + if i%spaced==0: + snapshots.append(array) + dates.append(date) + means.append(np.mean(array)) + n+=1 + all_snapshots.append(array) + array_mean += np.mean(array) + + + Y_dates.append(date) + + '''print("MEAN", array_mean/n) + print("Done loading samples! \n") + n_snap = len(snapshots) + n_all_snaps = len(all_snapshots) + means = np.array(means)[0:(n_snap//concat)*concat].reshape(n_snap//concat, concat) + running_means = np.mean(means,axis=1) + n_runs = len(running_means) + n_to_keep = int(n_runs*keep_biggest) + idx_to_keep = np.argsort(running_means)[-n_to_keep:] + print(idx_to_keep) + temp_s = [] + temp_s_all = [] + temp_s_dates = [] + temp_s_all_dates = [] + print("LEN BEFORE", len(snapshots), len(all_snapshots)) + for i in idx_to_keep: + temp_s.extend(snapshots[i*concat:i*concat+concat]) + temp_s_all.extend(all_snapshots[i*concat*spaced:i*concat*spaced+concat*spaced]) + temp_s_dates.extend(dates[i*concat:i*concat+concat]) + temp_s_all_dates.extend(Y_dates[i*concat*spaced:i*concat*spaced+concat*spaced]) + snapshots = temp_s + all_snapshots = temp_s_all + dates = temp_s_dates + Y_dates = temp_s_all_dates + print("LEN AFTER", len(snapshots), len(all_snapshots)) + print(dates) + print(Y_dates) + input()''' + + + + data = np.array(snapshots) + del(snapshots) # MANAGE MEMORY + all_data = np.array(all_snapshots) + + + del(all_snapshots) # MANAGE MEMORY + print("\nDatatype data: ", data.dtype) + print("\nInput data shape: ", data.shape, " size: ", sys.getsizeof(data)) + + + x0,x1,y0,y1 = square + print(f"\nInput patch by index: xmin = {x0}, xmax = {x1}, ymin = {y0}, ymax = {y1}") + x_lim = slice(x0,x1) + y_lim = slice(y0,y1) + + center_x = (x0+x1)//2 + center_y = (y0+y1)//2 + length_x = (x1-x0)//16 #size of Y is 16 times smaller + length_y = (y1-y0)//16 #size of Y is 16 times smaller + Y_lim_x = slice(center_x-length_x//2,center_x+length_x//2) + Y_lim_y = slice(center_y-length_y//2,center_y+length_y//2) + print("SLICED x: ",Y_lim_x) + print("SLICED y: ",Y_lim_y) + Y = all_data[:,Y_lim_y,Y_lim_x] + del(all_data) #MANAGE MEMORY + print(f"\nY shape here (not ready): {Y.shape}") + + data = data[:,y_lim,x_lim] + print(f"\nSliced data to dimensions {data.shape}") + + if centercrop: #extract centercrop before downsampling, since it's high resolution + + center = extract_centercrop(data) + print(f"\nCopying centercrop with shape {center.shape}") + if downsample == True: + print("\nDownsampling with rate: ", downsampling_rate) + data = down_sampler(data) + print("\nDone downsampling!") + + + print("\nDatatype downsampled: ", data.dtype) + print("\nDownsampled data shape: ",data.shape) + if len(data.shape)<4: + data = np.expand_dims(data, axis=3) + print(f"\nAdding channel dimension to data, new shape: {data.shape}") + if centercrop: + if len(center.shape)<4: + center = np.expand_dims(center, axis=3) + print(f"\nAdding channel dimension to centercrop, new shape: {center.shape}") + if spacedepth==True: + data = space_to_depth(data,box) + + print(f"\nSpace-to-depth done! Data shape: {data.shape}") + if centercrop: + center = space_to_depth(center,box) + print(f"\nSpace-to-depth done! Centercrop shape: {center.shape}") + + if centercrop: + data = np.concatenate((data,center), axis=3) + print(f"\nConcatenating data and centercrop to dimenison: {data.shape} with shape [:,:,:,downsampled + centercrop]") + + + + + data = longlatencoding(data) + print(f"\nConcatenating data with long, lat and elevation. New shape: {data.shape}, dtype: {data.dtype}") + + data = datetime_encoder(data,dates,plotter=False) + print(f"\nEncoding datetime periodical variables (seasonally,hourly) and concatenating with data. New shape: {data.shape}, dtype: {data.dtype}") + + + + + + data = np.swapaxes(np.swapaxes(data,3,1),2,3) + print(f"\nData swapping axes to get channel first, now shape: {data.shape}") + X,Y, X_dates,Y_dates = temporal_concatenation(data,dates,Y,Y_dates,concat = concat, overlap = overlap, spaced = spaced,lead_times = lead_times) + + print(f"\nDone with temporal concatenation and target_split! Data shape: {X.shape}, target shape: {Y.shape}") + + GAIN = 0.4 + OFFSET = -30 + X[:,:,0:8] = X[:,:,0:8]*GAIN + OFFSET + + + maxx = np.max(X[:,:,0:8]) + print("\nMAX DBZ data(should be 72): ", maxx) + data_new = np.empty(X[:,:,0:8].shape) + N = data_new.shape[0] + runs = N//5000 + for run in range(0,N,5000): + data_new[run:run+5000,:,0:8] = np.log(X[run:run+5000,:,0:8]+0.01, dtype = np.float32)/4 + data_new[run:run+5000,:,0:8] = np.nan_to_num(data_new[run:run+5000,:,0:8]) + data_new[run:run+5000,:,0:8] = np.tanh(data_new[run:run+5000,:,0:8], dtype = np.float32) + + data_new[runs*5000:,:,0:8] = np.log(X[runs*5000:,:,0:8]+0.01, dtype = np.float32)/4 + data_new[runs*5000:,:,0:8] = np.nan_to_num(data_new[runs*5000:,:,0:8]) + data_new[runs*5000:,:,0:8] = np.tanh(data_new[runs*5000:,:,0:8], dtype = np.float32) + + '''data_new = np.log(data+0.01)/4 + data_new = np.nan_to_num(data_new) + data_new = np.tanh(data_new)''' + + + + for i in range(8): + try: + assert np.std(data_new[:,:,:,i]) != 0 + except AssertionError: + print(f"WARNING: CHANNEL {i} STD == 0") + data_new[:,:,i] = (data_new[:,:,i] - np.mean(data_new[:,:,i] ))/np.std(data_new[:,:,i] ) + + + print(f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}") + + + Y = Y*GAIN + OFFSET + '''for i in range(0,5): + fig, ax = plt.subplots(1,2) + ax[0].imshow(X[i,0,0,:,:]) + #ax[0].imshow(np.mean(data_after_gained[i*7,42:70,42:70,4:8],axis=2)) + ax[0].set_title(X_dates[i][6]) + ax[1].imshow(Y[i,0,:,:]) + ax[1].set_title(Y_dates[i][0]) + plt.show()''' + + #print("comparing X and Y after gain:", np.mean(data_after_gained[:,:,4:8]), np.mean(Y)) + + Y_gained = np.copy(Y) + + print("MINMAX Y AFTER GAIN + OFFSET", np.min(Y), np.max(Y)) + + passer = np.mean(X[:,6,4:8,:,:],axis=1) + + Y = rain_binned(Y, n_bins = n_bins, increment = rain_step, x = passer) + + print(f"\nDone with binning targets into bins, target shape: {Y.shape}") + + + + #Remove low-rainfall data: + meaned = np.mean(X[:,:,0:4,:,:], axis=(1,2,3,4)) + idx_sorted = np.argsort(meaned) + + N = len(meaned) + '''for j in range(N-1,0,-1): + fig, ax = plt.subplots(7,1) + for k in range(7): + i = idx_sorted[j] + + im = ax[k].imshow(X[i,k,0,:,:]) + ax[k].set_title(f"MEAN: {meaned[i]:.2f}") + + + fig.subplots_adjust(right=0.8) + cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) + fig.colorbar(im, cax=cbar_ax) + plt.show()''' + to_keep = int(N*keep_biggest) + idx_to_keep = idx_sorted[-to_keep:] + #print(meaned) + #print(meaned[idx_to_keep]) + + X = X[idx_to_keep] + '''print(meaned[idx_sorted]) + print(meaned[idx_to_keep]) + print(np.mean(X[:,:,0:4,:,:], axis=(1,2,3,4))) + input()''' + Y = Y[idx_to_keep] + X_dates = [X_dates[i] for i in idx_to_keep] + Y_dates = [Y_dates[i] for i in idx_to_keep] + print(f"\nOnly keeping {to_keep} out of {N} samples to reduce low rainfall events. New X shape: {X.shape}") + N = X.shape[0] + '''for j in range(N-1,0,-1): + fig, ax = plt.subplots(7,2) + for k in range(7): + + + im = ax[k,0].imshow(X[j,k,0,:,:]) + ax[k,0].set_title(f"MEAN: {np.mean(X[j,k,0,:,:]):.2f}") + ax[k,1].imshow(Y[j,k,0,:,:]) + + + fig.subplots_adjust(right=0.8) + cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) + fig.colorbar(im, cax=cbar_ax) + plt.show()''' + + #plots all channels seperately: + '''channels = X.shape[2] + for i in range(N): + fig, axs = plt.subplots(4,4) + axs = axs.reshape(-1) + for c in range(channels): + axs[c].imshow(X[i,0,c]) + plt.show()''' + + #print(f"\nOnly keeping {to_keep} out of {N} samples to reduce low rainfall events. New X shape: {X.shape}") + + Y_thresh = np.ones(Y[:,0,:,:].shape)*-1 + + Y_thresh[np.where(Y[:,0,:,:]==1)] = 1 + + + #rain_check(X, Y_gained[idx_to_keep], Y_thresh,X_dates,Y_dates,X_dict,Y_dict, meaned) + if not printer: + sys.stdout = sys.__stdout__ + return X,Y, X_dates,Y_dates + +def rain_binned(Y, n_bins = 51,increment = 0.2, x = None): + SHAPE = Y.shape + n,leads,w,h = SHAPE + max_fall = n_bins*increment + min_dbz = np.min(Y) + max_dbz = np.max(Y) + Y[np.where(Y>70)] = 0 + + rain = (10**(Y / 10.0) / 200.0)**(1.0 / 1.6) + + print("RAIN MINMAX: ", np.min(rain), np.max(rain)) + + '''for i in range(n): + fig,ax = plt.subplots(1,2) + rain_x = (10**(x[i] / 10.0) / 200.0)**(1.0 / 1.6) + ax[0].imshow(x[i][(112//2)-14:(112//2)+14, (112//2)-14:(112//2)+14]) + ax[0].set_title("x zoomed") + ax[1].imshow(rain[i,0,:,:]) + ax[1].set_title("y") + plt.show()''' + rain_bins = np.zeros((n,leads,n_bins,w,h), dtype = np.int8) + counter = [] + for i in range(n_bins-1): + if i%100==0: print(f"Bin progress:",i) + bin_min = i*increment + bin_max = (i+1)*increment + rain_bin = np.zeros((n,leads,w,h)) #Y.shape = (None,lead_times, bin_channel, width/4,heigth/4) + idx = np.where(np.logical_and(rain>=bin_min, rain=n_bins*increment) + rain_bin[idx] = 1 + rain_bins[:,:,n_bins-1,:,:] = rain_bin + counter.append(len(idx)) + print("RAINBINS: ", rain_bins.size, " counter size: ", sum(counter)) + + print(counter) + + + return rain_bins +''' +def rain_check(X, Y,Y_thresh,x_dates,y_dates,X_dict,Y_dict,meaned): + X_mid = np.mean(X[:,:,4:8], axis = 2) #,42:70, 42:70 + N = min(X.shape[0], Y.shape[0]) + temps = X_mid.shape[1] + leads = min(Y.shape[1],temps) + + for n in range(N): + fig, axs = plt.subplots(5,temps) + axs = axs.reshape(-1) + #print(X_mid[n,0,:,:].reshape(-1)) + for i in range(temps): + #print("i: ", i, "\n", X_mid[n,i,:,:].reshape(-1)) + + print("MINMAX", np.min(X_mid[n,i,:,:]), np.max(X_mid[n,i,:,:])) + #print(X_mid[np.where(np.isnan(X_mid))] + np.random.random(X_mid[np.where(np.isnan(X_mid))].shape)) + im = axs[i].imshow(X_mid[n,i,:,:]) + + #axs[i].set_title(x_dates[n][i][-5:]) + for i in range(temps): + im = axs[i+temps].imshow(X_dict[x_dates[n][i]]) + axs[i+temps].set_title(x_dates[n][i][-5:]) + for j in range(leads): + im = axs[j+2*temps].imshow(Y[n,j,:,:]) + axs[j+2*temps].set_title(y_dates[n][j][-5:]) + for j in range(leads): + im = axs[j+3*temps].imshow(Y_thresh[n,j,:,:]) + axs[j+3*temps].set_title(y_dates[n][j][-5:]) + for j in range(leads): + im = axs[j+4*temps].imshow(Y_dict[y_dates[n][i]]) + axs[j+4*temps].set_title(x_dates[n][i][-5:]) + fig.suptitle("mean : " + str(meaned[n])) + fig.subplots_adjust(right=0.8) + cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) + fig.colorbar(im, cax=cbar_ax) + plt.show()''' + + +def prepare_files(h5_path = "data3_500k.h5",lead_times = 60, concat = 7, square = (0,448,881-448,881), downsampling_rate = 2, overlap = 0, spaced=3,downsample = True, spacedepth =True,centercrop=True,box=2,printer=True, rain_step = 0.2, n_bins=512): + #15 minutes between datapoints is default --> spaced = 3 + + + + batch_size = 5000 + N = 500000 + partial = 400000 + for start in range(partial,partial+100000,batch_size): + print(f"-------------------- STARTING AT {start} --------------------") + snapshots = [] + dates = [] + all_snapshots = [] + all_dates = [] + for i, array,date in h5_iterator(h5_path, batch_size, starter=start): + + if (i+1)%1000==0: + print("Loaded samples: ", i+1) + + if i%spaced==0: + snapshots.append(array) + dates.append(date) + + all_snapshots.append(array) + all_dates.append(date) + + data = np.array(snapshots) + del(snapshots) # MANAGE MEMORY + all_data = np.array(all_snapshots) + + + del(all_snapshots) # MANAGE MEMORY + print("\nDatatype data: ", data.dtype) + print("\nInput data shape: ", data.shape, " size: ", sys.getsizeof(data)) + + + x0,x1,y0,y1 = square + print(f"\nInput patch by index: xmin = {x0}, xmax = {x1}, ymin = {y0}, ymax = {y1}") + x_lim = slice(x0,x1) + y_lim = slice(y0,y1) + + center_x = (x0+x1)//2 + center_y = (y0+y1)//2 + length_x = (x1-x0)//16 #size of Y is 16 times smaller + length_y = (y1-y0)//16 #size of Y is 16 times smaller + if length_x==length_y: + Y_lim_x = slice(center_x-length_x//2,center_x+length_x//2) + Y_lim_y = slice(center_y-length_y//2,center_y+length_y//2) + else: + Y_lim_x = slice(center_x-length_x//2,center_x+length_x//2) + Y_lim_y = slice(center_x-length_x//2,y1-(center_x-length_x//2)) + print("SLICED Y in x: ",Y_lim_x) + print("SLICED Y in y: ",Y_lim_y) + Y = all_data[:,Y_lim_y,Y_lim_x] + del(all_data) #MANAGE MEMORY + print(f"\nY shape here (not ready): {Y.shape}") + + data = data[:,y_lim,x_lim] + print(f"\nSliced data to dimensions {data.shape}") + + if centercrop: #extract centercrop before downsampling, since it's high resolution + + center = extract_centercrop(data) + print(f"\nCopying centercrop with shape {center.shape}") + if downsample == True: + print("\nDownsampling with rate: ", downsampling_rate) + data = down_sampler(data) + print("\nDone downsampling!") + + + print("\nDatatype downsampled: ", data.dtype) + print("\nDownsampled data shape: ",data.shape) + if len(data.shape)<4: + data = np.expand_dims(data, axis=3) + print(f"\nAdding channel dimension to data, new shape: {data.shape}") + if centercrop: + if len(center.shape)<4: + center = np.expand_dims(center, axis=3) + print(f"\nAdding channel dimension to centercrop, new shape: {center.shape}") + if spacedepth==True: + data = space_to_depth(data,box) + + print(f"\nSpace-to-depth done! Data shape: {data.shape}") + if centercrop: + center = space_to_depth(center,box) + print(f"\nSpace-to-depth done! Centercrop shape: {center.shape}") + + if centercrop: + data = np.concatenate((data,center), axis=3) + print(f"\nConcatenating data and centercrop to dimenison: {data.shape} with shape [:,:,:,downsampled + centercrop]") + + + + + data = longlatencoding(data) + print(f"\nConcatenating data with long, lat and elevation. New shape: {data.shape}, dtype: {data.dtype}") + + data = datetime_encoder(data,dates,plotter=False) + print(f"\nEncoding datetime periodical variables (seasonally,hourly) and concatenating with data. New shape: {data.shape}, dtype: {data.dtype}") + + + + + + data = np.swapaxes(np.swapaxes(data,3,1),2,3) + print(f"\nData swapping axes to get channel first, now shape: {data.shape}") + X,Y, X_dates,Y_dates = temporal_concatenation(data,dates,Y,all_dates,concat = concat, overlap = overlap, spaced = spaced,lead_times = lead_times) + + print(f"\nDone with temporal concatenation and target_split! Data shape: {X.shape}, target shape: {Y.shape}") + + GAIN = 0.4 + OFFSET = -30 + X[:,:,0:8] = X[:,:,0:8]*GAIN + OFFSET + + + maxx = np.max(X[:,:,0:8]) + print("\nMAX DBZ data(should be 72): ", maxx) + data_new = np.empty(X[:,:,0:8].shape) + N = data_new.shape[0] + runs = N//5000 + for run in range(0,N,5000): + data_new[run:run+5000,:,0:8] = np.log(X[run:run+5000,:,0:8]+0.01, dtype = np.float32)/4 + data_new[run:run+5000,:,0:8] = np.nan_to_num(data_new[run:run+5000,:,0:8]) + data_new[run:run+5000,:,0:8] = np.tanh(data_new[run:run+5000,:,0:8], dtype = np.float32) + + data_new[runs*5000:,:,0:8] = np.log(X[runs*5000:,:,0:8]+0.01, dtype = np.float32)/4 + data_new[runs*5000:,:,0:8] = np.nan_to_num(data_new[runs*5000:,:,0:8]) + data_new[runs*5000:,:,0:8] = np.tanh(data_new[runs*5000:,:,0:8], dtype = np.float32) + #data[np.where(data<0)] = 0 + '''data_new = np.log(data+0.01)/4 + data_new = np.nan_to_num(data_new) + data_new = np.tanh(data_new)''' + + + + for i in range(8): + try: + assert np.std(data_new[:,:,:,i]) != 0 + except AssertionError: + print(f"WARNING: CHANNEL {i} STD == 0") + data_new[:,:,i] = (data_new[:,:,i] - np.mean(data_new[:,:,i] ))/np.std(data_new[:,:,i] ) + + + print(f"\nScaling data with log(x+0.01)/4, replace NaN with 0 and apply tanh(x) and convert to data type: {data.dtype}, nbytes: {data.nbytes}, size: {data.size}") + + Y = Y*GAIN + OFFSET + '''for i in range(0,5): + fig, ax = plt.subplots(1,2) + ax[0].imshow(X[i,0,0,:,:]) + #ax[0].imshow(np.mean(data_after_gained[i*7,42:70,42:70,4:8],axis=2)) + ax[0].set_title(X_dates[i][6]) + ax[1].imshow(Y[i,0,:,:]) + ax[1].set_title(Y_dates[i][0]) + plt.show()''' + + #print("comparing X and Y after gain:", np.mean(data_after_gained[:,:,4:8]), np.mean(Y)) + + Y_gained = np.copy(Y) + + print("MINMAX Y AFTER GAIN + OFFSET", np.min(Y), np.max(Y)) + + passer = np.mean(X[:,6,4:8,:,:],axis=1) + + Y = rain_binned(Y, n_bins = n_bins, increment = rain_step, x = passer) + + print(f"\nDone with binning targets into bins, target shape: {Y.shape}") + + + + #Remove low-rainfall data: + to_mean = X[:,:,0:4,:,:] + + idx_to_remove = np.where(to_mean > 70) + to_mean[idx_to_remove] = 0 + means = np.mean(to_mean, axis=(1,2,3,4)) + + + '''for j in range(N-1,0,-1): + fig, ax = plt.subplots(7,1) + for k in range(7): + i = idx_sorted[j] + + im = ax[k].imshow(X[i,k,0,:,:]) + ax[k].set_title(f"MEAN: {meaned[i]:.2f}") + + + fig.subplots_adjust(right=0.8) + cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) + fig.colorbar(im, cax=cbar_ax) + plt.show()''' + + '''print(meaned[idx_sorted]) + print(meaned[idx_to_keep]) + print(np.mean(X[:,:,0:4,:,:], axis=(1,2,3,4))) + input()''' + + + '''for j in range(N-1,0,-1): + fig, ax = plt.subplots(7,2) + for k in range(7): + + + im = ax[k,0].imshow(X[j,k,0,:,:]) + ax[k,0].set_title(f"MEAN: {np.mean(X[j,k,0,:,:]):.2f}") + ax[k,1].imshow(Y[j,k,0,:,:]) + + + fig.subplots_adjust(right=0.8) + cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) + fig.colorbar(im, cax=cbar_ax) + plt.show()''' + + #plots all channels seperately: + '''channels = X.shape[2] + for i in range(N): + fig, axs = plt.subplots(4,4) + axs = axs.reshape(-1) + for c in range(channels): + axs[c].imshow(X[i,0,c]) + plt.show()''' + + #print(f"\nOnly keeping {to_keep} out of {N} samples to reduce low rainfall events. New X shape: {X.shape}") + path_loc = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/data_moving_frame/" + for i in range(X.shape[0]): + mean_str = str(means[i]) + mean_str = mean_str[0:max(5,len(mean_str))] + start_date = X_dates[i][0] + full_path_X = path_loc + mean_str +"_" + start_date +"_"+"X"+ ".npy" + full_path_Y = path_loc + mean_str +"_" + start_date +"_"+"Y"+ ".npy" + np.save(full_path_X,X[i]) + np.save(full_path_Y,Y[i]) + + + #rain_check(X, Y_gained[idx_to_keep], Y_thresh,X_dates,Y_dates,X_dict,Y_dict, meaned) + if not printer: + sys.stdout = sys.__stdout__ + +def prepare_batches(batch_size = 8): + data_path = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/data_exclusive/" + file_list = os.listdir(data_path) + means = [] + dates = [] + names = [] + for name in file_list: + if not name[-4:] == ".npy": continue + if name[-5:] == "Y.npy": continue + names.append(name) + name = name[:-4] #remove .npy + name = name.split("/")[-1] #remove directories + #print(name) + mean = name.split("_")[0] + date = "_".join(name.split("_")[1:-1]) + print(mean, date) + means.append(float(mean)) + dates.append(date) + + means = np.array(means) + idx_sorted = np.argsort(-means) + names_sorted = [names[idx] for idx in idx_sorted] + means_sorted = means[idx_sorted] + dates_sorted = [dates[idx] for idx in idx_sorted] + print("MEANS: ", means[idx_sorted]) + print(len(means)) + N = len(means) + + batches_path = f"/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/batch_{batch_size}/" + paths = {} + for ID in ["train", "val", "test"]: + #os.makedirs(batches_path+ID + "/") + paths[ID] = batches_path + ID + "/" + train, val, test = (10, 2, 2) + leng = sum([train, val, test]) + + for i in range(0,N//batch_size): + if i%10==0: + print(f"batch {i} / {N//batch_size}") + X = np.empty((batch_size, 7, 15, 112,112)) + Y = np.empty((batch_size, 60, 128, 28,28)) + + if 0 <= i%leng < train: ID = "train" + elif train <= i%leng < train+val: ID = "val" + elif train+val <= i%leng: ID = "test" + + batch_mean = np.mean(means_sorted[i*batch_size:(i+1)*batch_size]) + batch_names_X = [data_path + file_name for file_name in names_sorted[i*batch_size:(i+1)*batch_size]] + batch_names_Y = [file_name.replace("X","Y") for file_name in batch_names_X] + + for k,(x,y) in enumerate(zip(batch_names_X,batch_names_Y)): + X[k] = np.load(x) + Y[k] = np.load(y) + + np.save(paths[ID]+str(batch_mean)[0:5]+ "_" +dates_sorted[i*batch_size]+"_X.npy",X) + np.save(paths[ID]+str(batch_mean)[0:5]+ "_" +dates_sorted[i*batch_size]+"_Y.npy",Y) + +def prepare_new_mean_split(): + data_path = "/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/data_exclusive/" + file_list = os.listdir(data_path) + non_zeros = np.zeros((128,)) + dates = [] + names = [] + for k,name in enumerate(file_list): + if k%100 == 0: print(f"{k} / {len(file_list)} samples meaned!") + if not name[-4:] == ".npy": continue + if name[-5:] == "Y.npy": continue + Y_name = name.replace("X","Y") + Y_here = np.load(data_path+Y_name) #Y.shape = (60, 128, 28, 28) + + non_zero = np.sum(Y_here[:,1:,:,:], axis = (0,1,2,3)) + + names.append(name) + name = name[:-4] #remove .npy + + + date = "_".join(name.split("_")[1:-1]) + + non_zeros.append(non_zero) + dates.append(date) + + non_zeros = np.array(non_zeros) + idx_sorted = np.argsort(-non_zeros) + np.save("/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/no_zeros.npy",non_zeros) + + names_sorted = [names[idx] for idx in idx_sorted] + non_zeros_sorted = non_zeros[idx_sorted] + dates_sorted = [dates[idx] for idx in idx_sorted] + + print("MEANS: ", non_zeros[idx_sorted]) + print(len(non_zeros)) + N = len(non_zeros) + + y_sort_path = f"/proj/berzelius-2022-18/users/sm_valfa/metnet_pylight/metnet/bin_sorted_data/" + paths = {} + for ID in ["train", "val", "test"]: + #os.makedirs(y_sort_path+ID + "/") + paths[ID] = y_sort_path + ID + "/" + train, val, test = (10, 2, 2) + leng = sum([train, val, test]) + + for i in range(0,N): + if i%100==0: + print(f" {i} / {N} samples done!") + + + if 0 <= i%leng < train: ID = "train" + elif train <= i%leng < train+val: ID = "val" + elif train+val <= i%leng: ID = "test" + + + X_name = data_path + names_sorted[i] + Y_name = X_name.replace("X","Y") + + X = np.load(X_name) + Y = np.load(Y_name) + + + + np.save(paths[ID]+str(non_zeros_sorted[i])+ "_" +dates_sorted[i]+"_X.npy",X) + np.save(paths[ID]+str(non_zeros_sorted[i])+ "_" +dates_sorted[i]+"_Y.npy",Y) + +def plot_rain_distribution(h5_path = "data3_500k.h5", maxN = 1000): + mean_rain_month = [[] for i in range(12)] + + mean_rain_hour = [[] for i in range(24)] + + for i, array,date in h5_iterator(h5_path, maxN = maxN): + + if (i+1)%1000==0: + print("Counted samples: ", i+1) + monthly_mean = np.array([np.mean(month) for month in mean_rain_month]) + hourly_mean = np.array([np.mean(hour) for hour in mean_rain_hour]) + print(monthly_mean) + print(hourly_mean) + #np.save("monthly_mean.npy", monthly_mean) + #np.save("hourly_mean.npy", hourly_mean) + + fig, axs = plt.subplots(1,2, subplot_kw={'projection': 'polar'}) + equals = np.linspace(0, 360, 24, endpoint=False) + + axs[0].scatter(np.deg2rad(equals), hourly_mean) + + # Set the circumference labels + axs[0].set_xticks(np.linspace(0, 2*np.pi, 24, endpoint=False)) + axs[0].set_xticklabels(range(24)) + + # Make the labels go clockwise + axs[0].set_theta_direction(-1) + + # Place 0 at the top + axs[0].set_theta_offset(np.pi/2.0) + + #plt.show() + + + + equals = np.linspace(0, 360, 12, endpoint=False) #np.arange(24) + #ones = np.ones(24) + axs[1].scatter(np.deg2rad(equals), monthly_mean) + + # Set the circumference labels + axs[1].set_xticks(np.linspace(0, 2*np.pi, 12, endpoint=False)) + axs[1].set_xticklabels(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]) + + # Make the labels go clockwise + axs[1].set_theta_direction(-1) + + # Place 0 at the top + axs[1].set_theta_offset(np.pi/2.0) + + axs[0].set_title("Hourly rain rates") + axs[1].set_title("Monthly rain rates") + plt.savefig("yearly_rates_plots") + plt.show() + listed = date.split("_") + y, m, d, hour, minute = [int(a) for a in listed] + + array = 0.4*array - 30 + rain = (10**(array / 10.0) / 200.0)**(1.0 / 1.6) + + idx = np.where(array<70) + + if len(idx)==0: continue + mean_rain = np.mean(rain[idx]) + mean_rain_month[m].append(mean_rain) + mean_rain_hour[hour].append(mean_rain) + + monthly_mean = [np.mean(month) for month in mean_rain_month] + hourly_mean = [np.mean(hour) for hour in mean_rain_hour] + print(hourly_mean) + np.save("monthly_mean.npy", monthly_mean) + np.save("hourly_mean.npy", hourly_mean) + ax = plt.subplot(111, polar=True) + equals = np.linspace(0, 360, 24, endpoint=False) #np.arange(24) + ones = np.ones(24) + ax.scatter(np.deg2rad(equals), hourly_mean) + + # Set the circumference labels + ax.set_xticks(np.linspace(0, 2*np.pi, 24, endpoint=False)) + ax.set_xticklabels(range(24)) + + # Make the labels go clockwise + ax.set_theta_direction(-1) + + # Place 0 at the top + ax.set_theta_offset(np.pi/2.0) + + plt.show() + + + ax = plt.subplot(111, polar=True) + equals = np.linspace(0, 360, 12, endpoint=False) #np.arange(24) + #ones = np.ones(24) + ax.scatter(np.deg2rad(equals), monthly) + + # Set the circumference labels + ax.set_xticks(np.linspace(0, 2*np.pi, 12, endpoint=False)) + ax.set_xticklabels(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]) + + # Make the labels go clockwise + ax.set_theta_direction(-1) + + # Place 0 at the top + ax.set_theta_offset(np.pi/2.0) + + plt.show() + +if __name__=="__main__": + plot_rain_distribution(maxN = None) + square = (0,448,0,880) + #prepare_files(square=square) + #prepare_new_mean_split() + + diff --git a/test_metnet_lightning.py b/test_metnet_lightning.py index 93a817e..26c6ae1 100644 --- a/test_metnet_lightning.py +++ b/test_metnet_lightning.py @@ -11,6 +11,22 @@ import numpy as np import matplotlib.pyplot as plt +if False: + + thresh = 1 + control = np.load(f"f1_control_thresh_{thresh}.npy") + fig, ax = plt.subplots(1,1) + ax.plot(control, label="persistence") + print(control) + f1s = np.load(f"f1_threshed_0.5_thresh_{thresh}.npy") + ax.plot(f1s, label=f"No aggregation model") + print(f1s) + fig.suptitle(f"F1-score for rainfall threshed at {round(thresh*0.2,3)} mm/h") + + ax.set_xlabel("Lead time") + ax.set_ylabel("F1") + ax.legend() + plt.show() if False: print("hej") N = 3606 @@ -61,17 +77,30 @@ #PATH_cp = "8leadtimessecond8h.ckpt" #PATH_cp = "epoch=242-step=16766.ckpt" PATH_cp = "fullrun_1.ckpt" +#PATH_cp = "best_60_leadtime.ckpt" +#PATH_cp = "best_single_leadtime.ckpt" +#PATH_cp = "no agg network.ckpt" + model = MetNetPylight.load_from_checkpoint(PATH_cp) -model.keep_biggest = 0.15 +#model.forecast_steps=8 +#model.leadtime_spacing = 3 +#model.keep_biggest = 0.15 model.thresh = 1 model.batch_size = 8 model.n_samples = None model.testing = True - +print(model.forecast_steps) #model.plot_every = None #MetNetPylight expects already preprocessed data. Can be change by uncommenting the preprocessing step. #print(model) +model.TPs = np.zeros(model.forecast_steps) +model.FNs = np.zeros(model.forecast_steps) +model.FPs = np.zeros(model.forecast_steps) +model.TPs_control = np.zeros(model.forecast_steps) +model.FNs_control = np.zeros(model.forecast_steps) +model.FPs_control = np.zeros(model.forecast_steps) + model.f1s = [[] for _ in range(model.forecast_steps)] model.f1s_control = [[] for _ in range(model.forecast_steps)] model.f1_count = [0 for _ in range(model.forecast_steps)] From ec771897ad998f44904c367d06408fbdbca10815 Mon Sep 17 00:00:00 2001 From: Valter Fallenius Date: Thu, 14 Mar 2024 11:45:14 +0100 Subject: [PATCH 16/17] Adding blog post about my MetNet implementation --- MetNet blog post, Valter Fallenius.pdf | Bin 0 -> 456759 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 MetNet blog post, Valter Fallenius.pdf diff --git a/MetNet blog post, Valter Fallenius.pdf b/MetNet blog post, Valter Fallenius.pdf new file mode 100644 index 0000000000000000000000000000000000000000..45d1a4dd76ddc6b82d08c10f5ae4edf0184f03db GIT binary patch literal 456759 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