Compare commits

2 Commits

Author SHA1 Message Date
Victor Mylle
b3f05f386f Finished intermediate february report + next steps from meeting 2024-02-19 15:48:45 +01:00
Victor Mylle
76a597af28 Started February Report 2024-02-17 17:53:07 +01:00
22 changed files with 4335 additions and 211 deletions

Binary file not shown.

BIN
Reports/February/ea-en.pdf Normal file

Binary file not shown.

BIN
Reports/February/ea-nl.pdf Normal file

Binary file not shown.

BIN
Reports/February/eb-en.pdf Normal file

Binary file not shown.

View File

@@ -0,0 +1,185 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Ghent University document class
% Created by DF Benoit, December 15, 2022
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{ugent-doc}
% Required packages
\RequirePackage{kvoptions}
\RequirePackage{geometry}
\RequirePackage{calc}
\RequirePackage{graphicx}
\RequirePackage{xcolor}
% ugent-doc specific options (kvoptions)
\SetupKeyvalOptions{family=ugd,prefix=ugd@} %UGentArticle
% Declare the class specific options
\DeclareStringOption[eb]{faculty}[eb]
\DeclareStringOption[en]{language}[en]
\DeclareStringOption[article]{doctype}[article]
\DeclareBoolOption[true]{sftitles} % Default: true
\ProcessKeyvalOptions*
% Pass options not specified above to the parent class
% \@unusedoptionlist is a macro in kvoptions
\LoadClass[\@unusedoptionlist]{\ugd@doctype}
% All sections, subsections and subsubsections in sans serif
\ifugd@sftitles
\RequirePackage[sf]{titlesec}
\fi
% Define UGent colors
%= = = = = = = = = = =
% Base colors
% UGent blue
\definecolor{ugentblue}{RGB}{30,100,200}
% UGent yellow
\definecolor{ugentyellow}{RGB}{255,210,0}
% UGent white
\definecolor{ugentwhite}{RGB}{255,255,255}
% UGent black
\definecolor{ugentblack}{RGB}{0,0,0}
% Faculty specific colors
% Faculty of Literature & Philosophy
\definecolor{ugent-lw}{RGB}{241,164,43}
% Faculty of Law
\definecolor{ugent-re}{RGB}{220,78,40}
% Faculty of Science
\definecolor{ugent-we}{RGB}{45,140,168}
% Faculty of Medicine and Health Sciences
\definecolor{ugent-ge}{RGB}{232,94,113}
% Faculty of Engineering and Architecture
\definecolor{ugent-ea}{RGB}{139,190,232}
% Faculty of Economics and Business Administration
\definecolor{ugent-eb}{RGB}{174,176,80}
% Faculty of Veterinary Medicine
\definecolor{ugent-di}{RGB}{130,84,145}
% Faculty of Psychology and Educational Sciences
\definecolor{ugent-pp}{RGB}{251,126,58}
% Faculty of Bioscience Engineering
\definecolor{ugent-bw}{RGB}{39,171,173}
% Faculty of Pharmaceutical Sciences
\definecolor{ugent-fw}{RGB}{190,81,144}
% Faculty of Political and Social Sciences
\definecolor{ugent-ps}{RGB}{113,168,96}
% Define new commands
\def\thetitle#1{\def\@thetitle{#1}}
\def\thesubtitle#1{\def\@thesubtitle{#1}}
\def\infoboxa#1{\def\@infoboxa{#1}}
\def\infoboxb#1{\def\@infoboxb{#1}}
\def\infoboxc#1{\def\@infoboxc{#1}}
\def\infoboxd#1{\def\@infoboxd{#1}}
% Initialize new commands as 'empty'
\def\@thetitle{}
\def\@thesubtitle{}
\def\@infoboxa{}
\def\@infoboxb{}
\def\@infoboxc{}
\def\@infoboxd{}
% Define lengths based on UGent document grid
% See: https://styleguide.ugent.be/basic-principles/grid-and-layout.html
\newlength{\longedge}
\setlength{\longedge}{\maxof{\paperheight}{\paperwidth}}
\newlength{\gridunit}
\setlength{\gridunit}{\longedge/28} %Divide long edge by 7 and next by 4
\newlength{\subpaperheight}
\setlength{\subpaperheight}{\paperheight-7\gridunit} %Type area: 3 units for faculty logo, 4 units for UGent logo
\newlength{\subpaperwidth}
\setlength{\subpaperwidth}{\paperwidth-\gridunit} %Left margin of 1 gridunit
% Define strut based on \gridunit
\newcommand{\mystrut}[1][-.5]{\rule[#1\gridunit]{0pt}{0pt}}
% Set default page layout
% Can be overwritten in preamble of document
\renewcommand{\baselinestretch}{1.15} % line spacing
\geometry{bottom=2.5cm,top=2.5cm,left=3cm,right=2cm} % margins
% Redefine the titlepage in accordance with UGent styleguide
\renewcommand\maketitle{\begin{titlepage}%
\thispagestyle{empty} % by default, the pagestyle of title page is plain
\newgeometry{top=0cm, bottom=0cm, left=0cm, right=0cm} % set special margins
\setlength{\parindent}{0cm} % necessary to put minipages/boxes at extreme left of page
\setlength{\parsep}{0cm} % necessary to stack minipages/boxes without space
\setlength{\fboxsep}{0cm} % no border around minipages/boxes
\setlength{\parskip}{0cm}
\setlength{\lineskip}{0cm}
\ifugd@sftitles
\sffamily % Titlepage in sans serif font
\fi
\includegraphics[height=3\gridunit]{\ugd@faculty-\ugd@language.pdf}%
\makebox[\gridunit]{}% Left margin of 1 gridunit
\colorbox{ugent-\ugd@faculty!30}{%
%\colorbox{ugentwhite}{%
\begin{minipage}[c][\subpaperheight][t]{\subpaperwidth}%
\vskip 5\gridunit % top margin within minipage
\hskip \gridunit % left margin of 1 within the colorbox
%\fbox{%
\begin{minipage}{\subpaperwidth-2\gridunit} % tile minipage, right margin of 1
\raggedright\bfseries\huge
\textcolor{ugentblue}{\mystrut\@thetitle}\newline
\Large\textcolor{ugentblue}{\@thesubtitle}
\mystrut[1]
\end{minipage}%}
\vskip\fill % Push down to bottom of minipage
\ifx\@infoboxa\empty\else % ony put box if not empty
\hskip\gridunit % left margin of infobox
%\fbox{%
\begin{minipage}[b]{\subpaperwidth-3\gridunit} % right margin of 1
\@infoboxa
\end{minipage}%}
\baselineskip0pt\mystrut
\fi
\ifx\@infoboxb\empty\else % ony put box if not empty
\hskip\gridunit % left margin of infobox
%\fbox{%
\begin{minipage}[b]{\subpaperwidth-3\gridunit} % right margin of 1
\@infoboxb
\end{minipage}%}
\baselineskip0pt\mystrut
\fi
\ifx\@infoboxc\empty\else % ony put box if not empty
\hskip\gridunit % left margin of infobox
%\fbox{%
\begin{minipage}[b]{\subpaperwidth-3\gridunit} % right margin of 1
\@infoboxc
\end{minipage}%}
\baselineskip0pt\mystrut
\fi
\ifx\@infoboxd\empty\else % ony put box if not empty
\hskip\gridunit % left margin of infobox
%\fbox{%
\begin{minipage}[b]{\subpaperwidth-3\gridunit} % right margin of 1
\@infoboxd
\end{minipage}%}
\fi
\baselineskip0pt\mystrut[-1]
\end{minipage}
}%
\includegraphics[height=4\gridunit]{ugent-\ugd@language.pdf}%
\end{titlepage}
\restoregeometry
}

Binary file not shown.

View File

@@ -0,0 +1,34 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@nameuse{bbl@beforestart}
\abx@aux@refcontext{nyt/global//global/global}
\providecommand\HyperFirstAtBeginDocument{\AtBeginDocument}
\HyperFirstAtBeginDocument{\ifx\hyper@anchor\@undefined
\global\let\oldcontentsline\contentsline
\gdef\contentsline#1#2#3#4{\oldcontentsline{#1}{#2}{#3}}
\global\let\oldnewlabel\newlabel
\gdef\newlabel#1#2{\newlabelxx{#1}#2}
\gdef\newlabelxx#1#2#3#4#5#6{\oldnewlabel{#1}{{#2}{#3}}}
\AtEndDocument{\ifx\hyper@anchor\@undefined
\let\contentsline\oldcontentsline
\let\newlabel\oldnewlabel
\fi}
\fi}
\global\let\hyper@last\relax
\gdef\HyperFirstAtBeginDocument#1{#1}
\providecommand\HyField@AuxAddToFields[1]{}
\providecommand\HyField@AuxAddToCoFields[2]{}
\@writefile{toc}{\boolfalse {citerequest}\boolfalse {citetracker}\boolfalse {pagetracker}\boolfalse {backtracker}\relax }
\@writefile{lof}{\boolfalse {citerequest}\boolfalse {citetracker}\boolfalse {pagetracker}\boolfalse {backtracker}\relax }
\@writefile{lot}{\boolfalse {citerequest}\boolfalse {citetracker}\boolfalse {pagetracker}\boolfalse {backtracker}\relax }
\babel@aux{english}{}
\@writefile{toc}{\defcounter {refsection}{0}\relax }\@writefile{toc}{\contentsline {section}{\numberline {1}Intermediate Results}{1}{section.1}\protected@file@percent }
\@writefile{toc}{\defcounter {refsection}{0}\relax }\@writefile{toc}{\contentsline {subsection}{\numberline {1.1}Net Regulation Volume Modeling}{1}{subsection.1.1}\protected@file@percent }
\@writefile{toc}{\defcounter {refsection}{0}\relax }\@writefile{toc}{\contentsline {subsubsection}{\numberline {1.1.1}Input Features}{1}{subsubsection.1.1.1}\protected@file@percent }
\@writefile{toc}{\defcounter {refsection}{0}\relax }\@writefile{toc}{\contentsline {subsubsection}{\numberline {1.1.2}Models}{1}{subsubsection.1.1.2}\protected@file@percent }
\@writefile{lot}{\defcounter {refsection}{0}\relax }\@writefile{lot}{\contentsline {table}{\numberline {1}{\ignorespaces Performance of Autoregressive Models}}{1}{table.1}\protected@file@percent }
\newlabel{tab:general_models}{{1}{1}{Performance of Autoregressive Models}{table.1}{}}
\@writefile{toc}{\defcounter {refsection}{0}\relax }\@writefile{toc}{\contentsline {subsubsection}{\numberline {1.1.3}Charging Policy}{2}{subsubsection.1.1.3}\protected@file@percent }
\@writefile{lot}{\defcounter {refsection}{0}\relax }\@writefile{lot}{\contentsline {table}{\numberline {2}{\ignorespaces Comparison of Energy Storage Policies Using Predicted NRV. Battery of 2MWh with 1MW charge/discharge power. Evaluated on data from 01-01-2023 until 08-10-2023.}}{2}{table.2}\protected@file@percent }
\newlabel{table:energy_storage_policies}{{2}{2}{Comparison of Energy Storage Policies Using Predicted NRV. Battery of 2MWh with 1MW charge/discharge power. Evaluated on data from 01-01-2023 until 08-10-2023}{table.2}{}}
\@writefile{toc}{\defcounter {refsection}{0}\relax }\@writefile{toc}{\contentsline {section}{\numberline {2}Schedule next months}{3}{section.2}\protected@file@percent }

2592
Reports/February/verslag.bcf Normal file

File diff suppressed because it is too large Load Diff

1000
Reports/February/verslag.log Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
\BOOKMARK [1][-]{section.1}{Intermediate Results}{}% 1
\BOOKMARK [2][-]{subsection.1.1}{Net Regulation Volume Modeling}{section.1}% 2
\BOOKMARK [3][-]{subsubsection.1.1.1}{Input Features}{subsection.1.1}% 3
\BOOKMARK [3][-]{subsubsection.1.1.2}{Models}{subsection.1.1}% 4
\BOOKMARK [3][-]{subsubsection.1.1.3}{Charging Policy}{subsection.1.1}% 5
\BOOKMARK [1][-]{section.2}{Schedule next months}{}% 6

Binary file not shown.

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" standalone="yes"?>
<!-- logreq request file -->
<!-- logreq version 1.0 / dtd version 1.0 -->
<!-- Do not edit this file! -->
<!DOCTYPE requests [
<!ELEMENT requests (internal | external)*>
<!ELEMENT internal (generic, (provides | requires)*)>
<!ELEMENT external (generic, cmdline?, input?, output?, (provides | requires)*)>
<!ELEMENT cmdline (binary, (option | infile | outfile)*)>
<!ELEMENT input (file)+>
<!ELEMENT output (file)+>
<!ELEMENT provides (file)+>
<!ELEMENT requires (file)+>
<!ELEMENT generic (#PCDATA)>
<!ELEMENT binary (#PCDATA)>
<!ELEMENT option (#PCDATA)>
<!ELEMENT infile (#PCDATA)>
<!ELEMENT outfile (#PCDATA)>
<!ELEMENT file (#PCDATA)>
<!ATTLIST requests
version CDATA #REQUIRED
>
<!ATTLIST internal
package CDATA #REQUIRED
priority (9) #REQUIRED
active (0 | 1) #REQUIRED
>
<!ATTLIST external
package CDATA #REQUIRED
priority (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8) #REQUIRED
active (0 | 1) #REQUIRED
>
<!ATTLIST provides
type (static | dynamic | editable) #REQUIRED
>
<!ATTLIST requires
type (static | dynamic | editable) #REQUIRED
>
<!ATTLIST file
type CDATA #IMPLIED
>
]>
<requests version="1.0">
<internal package="biblatex" priority="9" active="0">
<generic>latex</generic>
<provides type="dynamic">
<file>verslag.bcf</file>
</provides>
<requires type="dynamic">
<file>verslag.bbl</file>
</requires>
<requires type="static">
<file>blx-dm.def</file>
<file>apa.dbx</file>
<file>blx-compat.def</file>
<file>biblatex.def</file>
<file>standard.bbx</file>
<file>apa.bbx</file>
<file>apa.cbx</file>
<file>biblatex.cfg</file>
<file>english.lbx</file>
<file>american.lbx</file>
<file>american-apa.lbx</file>
<file>english-apa.lbx</file>
</requires>
</internal>
<external package="biblatex" priority="5" active="0">
<generic>biber</generic>
<cmdline>
<binary>biber</binary>
<infile>verslag</infile>
</cmdline>
<input>
<file>verslag.bcf</file>
</input>
<output>
<file>verslag.bbl</file>
</output>
<provides type="dynamic">
<file>verslag.bbl</file>
</provides>
<requires type="dynamic">
<file>verslag.bcf</file>
</requires>
<requires type="editable">
<file>./references.bib</file>
</requires>
</external>
</requests>

Binary file not shown.

View File

@@ -0,0 +1,213 @@
\documentclass[12pt,a4paper,faculty=ea,language=en,doctype=article]{ugent-doc}
% Optional: margins and spacing
%-------------------------------
% Uncomment and adjust to change the default values set by the template
% Note: the defaults are suggested values by Ghent University
%\geometry{bottom=2.5cm,top=2.5cm,left=3cm,right=2cm}
%\renewcommand{\baselinestretch}{1.15} % line spacing
% Font
%------
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc} % allows non-ascii input characters
% Comment or remove the two lines below to use the default Computer Modern font
\usepackage{libertine}
\usepackage{libertinust1math}
\usepackage{enumitem}
% NOTE: because the UGent font Panno is proprietary, it is not possible to use it
% in Overleaf. But UGent does not suggest to use Panno for documents (or maybe only for
% the titlepage). For the body, the UGent suggestion is to use a good serif font (for
% LaTeX this could be libertine or Computer Modern).
% Proper word splitting
%-----------------------
\usepackage[english]{babel}
% Mathematics
%-------------
\usepackage{amsmath}
% Figures
%---------
\usepackage{graphicx} % optional: the package is already loaded by the template
\graphicspath{{./figures/}}
% Bibliography settings
%-----------------------
\usepackage[backend=biber, style=apa, sorting=nyt, hyperref=true]{biblatex}
\addbibresource{./references.bib}
\usepackage{csquotes} % Suggested when using babel+biblatex
% Hyperreferences
%-----------------
\usepackage[colorlinks=true, allcolors=ugentblue]{hyperref}
% Whitespace between paragraphs and no indentation
%--------------------------------------------------
\usepackage[parfill]{parskip}
% Input for title page
%----------------------
% The title
\thesubtitle{February Intermediate Report}
%% Note: a stricter UGent style could be achieved with, e.g.:
\usepackage{ulem} % for colored underline
\renewcommand{\ULthickness}{2pt} % adjust thickness of underline
\thetitle{Forecasting and generative modeling of the Belgian electricity market}
% Note: do not forget to reset the \ULthickness to 1pt after invoking \maketitle
% (otherwise all underlines in the rest of your document will be too thick):
%\renewcommand{\ULthickness}{1pt}
% The first (top) infobox at bottom of titlepage
\infoboxa{\bfseries\large Master Thesis}
% The second infobox at bottom of titlepage
\infoboxb{Name:
\begin{tabular}[t]{l}
Victor Mylle
\end{tabular}
}
% The third infobox at bottom of titlepage
\infoboxc{
Promotors:
\begin{tabular}[t]{l}
prof. dr. ir. Chris Develder \\
prof. Bert Claessens
\end{tabular}
\\\\
Supervisor:
\begin{tabular}[t]{l}
Jonas Van Gompel
\end{tabular}
}
% The last (bottom) infobox at bottom of titlepage
\infoboxd{Academic year: 2023--2024} % note dash, not hyphen
\begin{document}
% =====================================================================
% Cover
% =====================================================================
% ------------ TITLE PAGE ---------
\maketitle
\renewcommand{\ULthickness}{1pt}
% =====================================================================
% Front matter
% =====================================================================
% ------------ TABLE OF CONTENTS ---------
% {\hypersetup{hidelinks}\tableofcontents} % hide link color in toc
% \newpage
% \begin{titlepage}
% \centering % Centers everything on the page
% % Logo or Image (Optional)
% % \includegraphics[width=0.5\textwidth]{path_to_logo.jpg}
% \vspace*{2cm} % Add vertical space
% {\large Title: Forecasting and generative modeling of the Belgian electricity market\par}
% \vspace{2cm}
% {\Large Victor Mylle\par}
% \vspace{1cm}
% {\large Period of Internship: 3 July 2023 - 31 August 2023\par}
% \vspace{1cm}
% {\large Mentor: dr. ir. Femke De Backere\par}
% {\large TechWolf supervisor: ir. Jens-Joris Decorte}
% \end{titlepage}
\newpage
\section{Intermediate Results}
\subsection{Net Regulation Volume Modeling}
Using a generative model, we try to predict the NRV for the next day. The model is trained on historical data and uses multiple input features to model the NRV. The data for the input features can all be downloaded from \href{https://www.elia.be/en/grid-data/open-data}{Elia Open Data}.
\subsubsection{Input Features}
The generative model uses multiple input features to predict the NRV.
\begin{itemize}[noitemsep]
\item NRV History (NRV of yesterday)
\item Load Forecast (Forecasted load of tomorrow)
\item Load History (Load of yesterday)
\item Wind Forecast (Forecasted wind of tomorrow)
\item Wind History (Wind of yesterday)
\item Implicit net position (Nominal net position of tomorrow)
\item Time features (Day of the week + quarter of the day)
\item Photovoltaic Forecast\textsuperscript{*}
\item Photovoltaic History\textsuperscript{*}
\end{itemize}
\textsuperscript{*} These features are not used currently, the data was not available. These features can easily be added without changing any code.
\subsubsection{Models}
In the intermediate report of November, baselines were discussed. Now, other more advanced models are used. Samples must be generated using the model, this means the model can't just output one value but a distribution is needed. Quantile Regression can be used for this task. The model then outputs the values of multiple quantiles. For example, the model outputs the value for which 10\% of the data is lower, the value for which 50\% of the data is lower, etc. This way, the model outputs a distribution which can be used to sample from. The NRV predicitons are done in a quarter-hourly resolution. To predict the NRV for the next day, 96 values need to be sampled. This can be done in an autoregressive manner. The model outputs the quantiles for the first quarter-hour, a sample is drawn from this distribution and this sample is used as input for the next quarter-hour. This process is repeated 96 times.
\begin{table}[h]
\centering
\begin{tabular}{lcc}
\hline
\textbf{Model} & \textbf{test\_L1Loss} & \textbf{test\_CRPSLoss} \\
\hline
Linear Model & 101.639 & 68.485 \\
Non Linear Model & 102.031 & 68.968 \\
LSTM/GRU Model & 104.261 & 66.052 \\
\hline
\end{tabular}
\caption{Performance of Autoregressive Models}
\label{tab:general_models}
\end{table}
At the moment, I am experimenting with a diffusion model to generatively model the NRV but more research and expermimenting needs to be done.
\subsubsection{Charging Policy}
Using the predicted NRV, a policy can be implemented to charge and discharge a battery. The goal of the policy is to maximize the profit made by selling the stored electricity. A simple policy is implemented to charge and discharge the battery based on 2 thresholds determined by the predicted NRV. The policy is evaluated on historical data and the profit is calculated. To determine the charge and discharge threshold, 1000 full NRV predictions are done for the next day and for each of these predicitions, the thresholds are determined. Next, the mean of these thresholds is used as the final threshold.
\begin{table}[h]
\centering
\begin{tabular}{lccc}
\hline
\textbf{Policy} & \textbf{Total Profit (€)} & \textbf{Charge Cycles} \\
\hline
Baseline (charge: €150, discharge: €175) & 251,202.59 & 725 \\
Baseline (yesterday imbalance price) & 342,980.09 & 903 \\
GRU Predicted NRV (mean thresholds) & 339,846.91 & 842 \\
Diffusion Predicted NRV (mean thresholds) & 338,168.03 & 886 \\
\hline
\end{tabular}
\caption{Comparison of Energy Storage Policies Using Predicted NRV. Battery of 2MWh with 1MW charge/discharge power. Evaluated on data from 01-01-2023 until 08-10-2023.}
\label{table:energy_storage_policies}
\end{table}
The recommended charge cycles for a battery is <400 cycles per year. The policy also needs to take this into account. A penalty parameter can be introduced and determined so that the policy is penalized for every charge cycle above 400. The policy can then be optimized using this penalty parameter. I am currenlty experimenting with this.
\newpage
\section{Schedule next months}
\begin{itemize}
\item Baselines with penalties for charge cycles above 400
\item Better visualizations of the policy profit results.
\item Case studies of days with extreme thresholds
\item Finetuning of models and hyperparametres based on model errors and profits of the policy
\item Ablation study of input features
\item Experiment further with diffusion models
\item During the experimenting, I will write my thesis and update the results and conclusions chapters.
\end{itemize}
\end{document}

View File

@@ -155,3 +155,50 @@ Test data: 01-01-2023 until 08-102023
- [ ] time steps reducing for diffusion model (UNet activation functions?)
- [ ] (State space model? S4)
TODO:
- [ ] diffusion model oefening generative models vragen
- [ ] Non autoregressive models policy testen (Non Linear eerst) -> als dit al slect, niet verder kijken, wel vermelden
- [ ] Policy in test set -> over charge cycles (stop trading electricity)
- [ ] penalty bepalen op training data
- [ ] cycles en profit herschalen naar per jaar
baseline -> NRV van gisteren gebruiken om thresholds te bepalen voor vandaag
andere policies -> NRV van vandaag voorspellen met model en thresholds bepalen voor vandaag
Eerste baseline -> thresholds bepalen op training data maar ook stoppen als 400 cycles (herschalen) per jaar bereikt zijn -> thresholds zouden anders moeten zijn (Ook met penalty parameter)
-> deze toepassen op test set (ook stoppen als 400/jaar bereikt zijn)
Visualizatie van thresholds over test set voor baselines en complexere modellen -> zonder penalties tonen
1 a 2 Case studies (extreme gevallen, thresholds 150, -5, normale mss)
- Generatie van NRV (echte NRV)
- Thresholds die eruit komen
- Profit en charge cycles
Policy volledig fixen en later training script met policy direct erachter (tijdens schrijven door laten runnen)
1) Policy
2) Finetuning van modellen (+ vergelijken met elkaar opbv profit en error)
3) Ablation Study (input features weghalen en kijken wat er gebeurt)
( 4) Diffusion tussendoor )
Inleiding +
Literatuurstudie +
Tabellen die we gaan bespreken -> updaten met nieuwe data dan
Nog eens 3e meeting opbrengen voor 2e deel maart.

View File

@@ -25,19 +25,12 @@ class NrvDataset(Dataset):
self.sequence_length = sequence_length
self.predict_sequence_length = predict_sequence_length
self.samples_to_skip = self.skip_samples(dataframe=dataframe, full_day_skip=self.full_day_skip)
self.samples_to_skip = self.skip_samples(dataframe=dataframe)
total_indices = set(
range(len(dataframe) - self.sequence_length - self.predict_sequence_length)
)
self.valid_indices = sorted(list(total_indices - set(self.samples_to_skip)))
# full day indices
full_day_skipped_samples = self.skip_samples(dataframe=dataframe, full_day_skip=True)
full_day_total_indices = set(
range(len(dataframe) - self.sequence_length - self.predict_sequence_length)
)
self.full_day_valid_indices = sorted(list(full_day_total_indices - set(full_day_skipped_samples)))
self.history_features = []
if self.data_config.LOAD_HISTORY:
self.history_features.append("total_load")
@@ -80,7 +73,7 @@ class NrvDataset(Dataset):
self.history_features, self.forecast_features = self.preprocess_data(dataframe)
def skip_samples(self, dataframe, full_day_skip):
def skip_samples(self, dataframe):
nan_rows = dataframe[dataframe.isnull().any(axis=1)]
nan_indices = nan_rows.index
skip_indices = [
@@ -98,7 +91,7 @@ class NrvDataset(Dataset):
# add indices that are not the start of a day (00:15) to the skip indices (use datetime column)
# get indices of all 00:15 timestamps
if full_day_skip:
if self.full_day_skip:
start_of_day_indices = dataframe[
dataframe["datetime"].dt.time != pd.Timestamp("00:00:00").time()
].index

View File

@@ -8,8 +8,7 @@ import pandas as pd
import datetime
from tqdm import tqdm
from src.utils.imbalance_price_calculator import ImbalancePriceCalculator
import seaborn as sns
import matplotlib.pyplot as plt
import time
import plotly.express as px
### import functions ###
@@ -17,7 +16,7 @@ from src.trainers.quantile_trainer import auto_regressive as quantile_auto_regre
from src.trainers.diffusion_trainer import sample_diffusion
from src.utils.clearml import ClearMLHelper
### Arguments ###
# argparse to parse task id and model type
parser = argparse.ArgumentParser()
parser.add_argument('--task_id', type=str, default=None)
parser.add_argument('--model_type', type=str, default=None)
@@ -28,7 +27,6 @@ assert args.task_id is not None, "Please specify task id"
assert args.model_type is not None, "Please specify model type"
assert args.model_name is not None, "Please specify model name"
### Baseline Policy ###
battery = Battery(2, 1)
baseline_policy = BaselinePolicy(battery, data_path="")
@@ -165,17 +163,20 @@ def get_next_day_profits_for_date(model, data_processor, test_loader, date, ipc,
return predicted_nrv_profits_cycles, baseline_profits_cycles, _charge_thresholds, _discharge_thresholds
def next_day_test_set(model, data_processor, test_loader, ipc, predict_NRV: callable):
penalties = [0, 50, 250, 500, 1000, 1500]
penalties = [0, 10, 50, 150, 300, 500, 600, 800, 1000, 1500, 2000, 2500]
predicted_nrv_profits_cycles = {i: [0, 0] for i in penalties}
baseline_profits_cycles = {i: [0, 0] for i in penalties}
charge_thresholds = {}
discharge_thresholds = {}
# get all dates in test set
dates = baseline_policy.test_data["DateTime"].dt.date.unique()
# dates back to datetime
dates = pd.to_datetime(dates)
for date in tqdm(dates):
for date in tqdm(dates[:10]):
try:
new_predicted_nrv_profits_cycles, new_baseline_profits_cycles, new_charge_thresholds, new_discharge_thresholds = get_next_day_profits_for_date(model, data_processor, test_loader, date, ipc, predict_NRV, penalties)
@@ -190,7 +191,8 @@ def next_day_test_set(model, data_processor, test_loader, ipc, predict_NRV: call
baseline_profits_cycles[penalty][1] += new_baseline_profits_cycles[penalty][1]
except Exception as e:
print(f"Error for date {date}")
# print(f"Error for date {date}")
raise e
return predicted_nrv_profits_cycles, baseline_profits_cycles, charge_thresholds, discharge_thresholds
@@ -220,6 +222,9 @@ def main():
# the charge_thresholds is a dictionary with date as key. The values of the dictionary is another dictionary with keys as penalties and values as the charge thresholds
# create density plot that shows a density plot of the charge thresholds for each penalty (use seaborn displot) (One plot with a different color for each penalty)
import seaborn as sns
import matplotlib.pyplot as plt
charge_thresholds_for_penalty = {}
for d in charge_thresholds.values():
for penalty, thresholds in d.items():
@@ -234,73 +239,47 @@ def main():
discharge_thresholds_for_penalty[penalty] = []
discharge_thresholds_for_penalty[penalty].extend(thresholds)
def plot_threshold_distribution(thresholds: dict, title: str):
data_to_plot = []
for penalty, values in thresholds.items():
for value in values:
data_to_plot.append({'Penalty': penalty, 'Value': value.item()})
df = pd.DataFrame(data_to_plot)
palette = sns.color_palette("bright", len(thresholds.keys()))
fig = sns.displot(data=df, x="Value", hue="Penalty", kind="kde", palette=palette)
plt.title('Density of Charge Thresholds by Penalty')
plt.xlabel('Charge Threshold')
plt.ylabel('Density')
plt.legend(title='Penalty')
task.get_logger().report_matplotlib_figure(
"Policy Results",
title,
iteration=0,
figure=fig
)
plt.close()
### Plot charge thresholds distribution ###
plot_threshold_distribution(charge_thresholds_for_penalty, "Charge Thresholds")
data_to_plot = []
for penalty, values in charge_thresholds_for_penalty.items():
for value in values:
data_to_plot.append({'Penalty': penalty, 'Value': value.item()})
df = pd.DataFrame(data_to_plot)
print(df.head())
palette = sns.color_palette("bright", len(charge_thresholds.keys()))
fig = sns.displot(data=df, x="Value", hue="Penalty", kind="kde", palette=palette)
plt.title('Density of Charge Thresholds by Penalty')
plt.xlabel('Charge Threshold')
plt.ylabel('Density')
plt.legend(title='Penalty')
task.get_logger().report_matplotlib_figure(
"Policy Results",
"Charge Thresholds",
iteration=0,
figure=fig
)
plt.close()
### Plot discharge thresholds distribution ###
plot_threshold_distribution(discharge_thresholds_for_penalty, "Discharge Thresholds")
data_to_plot = []
for penalty, values in discharge_thresholds_for_penalty.items():
for value in values:
data_to_plot.append({'Penalty': penalty, 'Value': value.item()})
df = pd.DataFrame(data_to_plot)
palette = sns.color_palette("bright", len(discharge_thresholds.keys()))
fig = sns.displot(data=df, x="Value", hue="Penalty", kind="kde", palette=palette)
plt.title('Density of Charge Thresholds by Penalty')
plt.xlabel('Charge Threshold')
plt.ylabel('Density')
plt.legend(title='Penalty')
task.get_logger().report_matplotlib_figure(
"Policy Results",
"Discharge Thresholds",
iteration=0,
figure=fig
)
plt.close()
def plot_thresholds_per_day(thresholds: dict, title: str):
# plot mean charge threshold per day (per penalty (other color))
data_to_plot = []
for date, values in thresholds.items():
for penalty, value in values.items():
mean_val = value.mean().item()
std_val = value.std().item() # Calculate standard deviation
data_to_plot.append({'Date': date, 'Penalty': penalty, 'Mean': mean_val, 'StdDev': std_val})
print(f"Date: {date}, Penalty: {penalty}, Mean: {mean_val}, StdDev: {std_val}")
df = pd.DataFrame(data_to_plot)
df["Date"] = pd.to_datetime(df["Date"])
fig = px.line(
df,
x="Date",
y="Mean",
color="Penalty",
title=title,
labels={"Mean": "Threshold", "Date": "Date"},
markers=True, # Adds markers to the lines
hover_data=["Penalty"], # Adds additional hover information
)
fig.update_layout(
width=1000, # Set the width of the figure
height=600, # Set the height of the figure
title_x=0.5, # Center the title horizontally
)
task.get_logger().report_plotly(
"Thresholds per Day",
title,
iteration=0,
figure=fig
)
### Plot mean charge thresholds per day ###
plot_thresholds_per_day(charge_thresholds, "Mean Charge Thresholds per Day")
### Plot mean discharge thresholds per day ###
plot_thresholds_per_day(discharge_thresholds, "Mean Discharge Thresholds per Day")
# create dataframe with columns "name", "penalty", "profit", "cycles"

View File

@@ -33,29 +33,67 @@ class AutoRegressiveTrainer(Trainer):
self.model.output_size = 1
def debug_plots(self, task, train: bool, data_loader, sample_indices, epoch):
for actual_idx, idx in sample_indices.items():
auto_regressive_output = self.auto_regressive(data_loader.dataset, [idx]*1000)
num_samples = len(sample_indices)
rows = num_samples # One row per sample since we only want one column
# check if self has get_plot_error
if hasattr(self, "get_plot_error"):
cols = 2
print("Using get_plot_error")
else:
cols = 1
print("Using get_plot")
fig = make_subplots(
rows=rows,
cols=cols,
subplot_titles=[f"Sample {i+1}" for i in range(num_samples)],
)
for i, idx in enumerate(sample_indices):
auto_regressive_output = self.auto_regressive(data_loader.dataset, [idx])
if len(auto_regressive_output) == 3:
initial, predictions, target = auto_regressive_output
else:
initial, _, predictions, target = auto_regressive_output
initial, predictions, _, target = auto_regressive_output
initial = initial.squeeze(0)
predictions = predictions.squeeze(0)
target = target.squeeze(0)
# keep one initial
initial = initial[0]
target = target[0]
sub_fig = self.get_plot(initial, target, predictions, show_legend=(i == 0))
predictions = predictions
row = i + 1
col = 1
fig = self.get_plot(initial, target, predictions, show_legend=(0 == 0))
for trace in sub_fig.data:
fig.add_trace(trace, row=row, col=col)
task.get_logger().report_matplotlib_figure(
title="Training" if train else "Testing",
series=f'Sample {actual_idx}',
iteration=epoch,
figure=fig,
if cols == 2:
error_sub_fig = self.get_plot_error(
target, predictions
)
for trace in error_sub_fig.data:
fig.add_trace(trace, row=row, col=col + 1)
loss = self.criterion(
predictions.to(self.device), target.to(self.device)
).item()
fig["layout"]["annotations"][i].update(
text=f"{self.criterion.__class__.__name__}: {loss:.6f}"
)
# y axis same for all plots
# fig.update_yaxes(range=[-1, 1], col=1)
fig.update_layout(height=1000 * rows)
task.get_logger().report_plotly(
title=f"{'Training' if train else 'Test'} Samples",
series="full_day",
iteration=epoch,
figure=fig,
)
def auto_regressive(self, data_loader, idx, sequence_length: int = 96):
self.model.eval()

View File

@@ -19,6 +19,8 @@ def sample_diffusion(model: DiffusionModel, n: int, inputs: torch.tensor, noise_
alpha = 1. - beta
alpha_hat = torch.cumprod(alpha, dim=0)
# inputs: (num_features) -> (batch_size, num_features)
# inputs: (time_steps, num_features) -> (batch_size, time_steps, num_features)
if len(inputs.shape) == 2:
inputs = inputs.repeat(n, 1)
elif len(inputs.shape) == 3:
@@ -40,17 +42,17 @@ def sample_diffusion(model: DiffusionModel, n: int, inputs: torch.tensor, noise_
noise = torch.zeros_like(x)
x = 1/torch.sqrt(_alpha) * (x-((1-_alpha) / (torch.sqrt(1 - _alpha_hat))) * predicted_noise) + torch.sqrt(_beta) * noise
x = torch.clamp(x, -1.0, 1.0)
return x
class DiffusionTrainer:
def __init__(self, model: nn.Module, data_processor: DataProcessor, device: torch.device):
self.model = model
self.device = device
self.noise_steps = 30
self.beta_start = 0.0001
self.noise_steps = 20
self.beta_start = 1e-4
self.beta_end = 0.02
self.ts_length = 96
@@ -96,16 +98,7 @@ class DiffusionTrainer:
else:
loader = test_loader
# set seed
np.random.seed(42)
actual_indices = np.random.choice(loader.dataset.full_day_valid_indices, num_samples, replace=False)
indices = {}
for i in actual_indices:
indices[i] = loader.dataset.valid_indices.index(i)
print(actual_indices)
indices = np.random.randint(0, len(loader.dataset) - 1, size=num_samples)
return indices
def init_clearml_task(self, task):
@@ -180,7 +173,7 @@ class DiffusionTrainer:
def debug_plots(self, task, training: bool, data_loader, sample_indices, epoch):
for actual_idx, idx in sample_indices.items():
for i, idx in enumerate(sample_indices):
features, target, _ = data_loader.dataset[idx]
features = features.to(self.device)
@@ -189,21 +182,18 @@ class DiffusionTrainer:
self.model.eval()
with torch.no_grad():
samples = self.sample(self.model, 100, features).cpu().numpy()
samples = self.data_processor.inverse_transform(samples)
target = self.data_processor.inverse_transform(target)
ci_99_upper = np.quantile(samples, 0.995, axis=0)
ci_99_lower = np.quantile(samples, 0.005, axis=0)
ci_99_upper = np.quantile(samples, 0.99, axis=0)
ci_99_lower = np.quantile(samples, 0.01, axis=0)
ci_95_upper = np.quantile(samples, 0.975, axis=0)
ci_95_lower = np.quantile(samples, 0.025, axis=0)
ci_95_upper = np.quantile(samples, 0.95, axis=0)
ci_95_lower = np.quantile(samples, 0.05, axis=0)
ci_90_upper = np.quantile(samples, 0.95, axis=0)
ci_90_lower = np.quantile(samples, 0.05, axis=0)
ci_50_lower = np.quantile(samples, 0.25, axis=0)
ci_50_upper = np.quantile(samples, 0.75, axis=0)
ci_90_upper = np.quantile(samples, 0.9, axis=0)
ci_90_lower = np.quantile(samples, 0.1, axis=0)
ci_50_upper = np.quantile(samples, 0.5, axis=0)
ci_50_lower = np.quantile(samples, 0.5, axis=0)
sns.set_theme()
time_steps = np.arange(0, 96)
@@ -229,7 +219,7 @@ class DiffusionTrainer:
task.get_logger().report_matplotlib_figure(
title="Training" if training else "Testing",
series=f'Sample {actual_idx}',
series=f'Sample {i}',
iteration=epoch,
figure=fig,
)

View File

@@ -10,9 +10,7 @@ import plotly.graph_objects as go
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.patches as mpatches
def sample_from_dist(quantiles, preds):
if isinstance(preds, torch.Tensor):
@@ -263,35 +261,35 @@ class AutoRegressiveQuantileTrainer(AutoRegressiveTrainer):
name="test_CRPS_from_samples_transformed", value=np.mean(crps_from_samples_metric)
)
# def get_plot_error(
# self,
# next_day,
# predictions,
# ):
# metric = PinballLoss(quantiles=self.quantiles)
# fig = go.Figure()
def get_plot_error(
self,
next_day,
predictions,
):
metric = PinballLoss(quantiles=self.quantiles)
fig = go.Figure()
# next_day_np = next_day.view(-1).cpu().numpy()
# predictions_np = predictions.cpu().numpy()
next_day_np = next_day.view(-1).cpu().numpy()
predictions_np = predictions.cpu().numpy()
# if True:
# next_day_np = self.data_processor.inverse_transform(next_day_np)
# predictions_np = self.data_processor.inverse_transform(predictions_np)
if True:
next_day_np = self.data_processor.inverse_transform(next_day_np)
predictions_np = self.data_processor.inverse_transform(predictions_np)
# # for each time step, calculate the error using the metric
# errors = []
# for i in range(96):
# for each time step, calculate the error using the metric
errors = []
for i in range(96):
# target_tensor = torch.tensor(next_day_np[i]).unsqueeze(0)
# prediction_tensor = torch.tensor(predictions_np[i]).unsqueeze(0)
target_tensor = torch.tensor(next_day_np[i]).unsqueeze(0)
prediction_tensor = torch.tensor(predictions_np[i]).unsqueeze(0)
# errors.append(metric(prediction_tensor, target_tensor))
errors.append(metric(prediction_tensor, target_tensor))
# # plot the error
# fig.add_trace(go.Scatter(x=np.arange(96), y=errors, name=metric.__class__.__name__))
# fig.update_layout(title=f"Error of {metric.__class__.__name__} for each time step")
# plot the error
fig.add_trace(go.Scatter(x=np.arange(96), y=errors, name=metric.__class__.__name__))
fig.update_layout(title=f"Error of {metric.__class__.__name__} for each time step")
# return fig
return fig
def get_plot(
@@ -314,59 +312,26 @@ class AutoRegressiveQuantileTrainer(AutoRegressiveTrainer):
next_day_np = self.data_processor.inverse_transform(next_day_np)
predictions_np = self.data_processor.inverse_transform(predictions_np)
ci_99_upper = np.quantile(predictions_np, 0.995, axis=0)
ci_99_lower = np.quantile(predictions_np, 0.005, axis=0)
ci_95_upper = np.quantile(predictions_np, 0.975, axis=0)
ci_95_lower = np.quantile(predictions_np, 0.025, axis=0)
ci_90_upper = np.quantile(predictions_np, 0.95, axis=0)
ci_90_lower = np.quantile(predictions_np, 0.05, axis=0)
ci_50_lower = np.quantile(predictions_np, 0.25, axis=0)
ci_50_upper = np.quantile(predictions_np, 0.75, axis=0)
# Add traces for current and next day
# fig.add_trace(go.Scatter(x=np.arange(96), y=current_day_np, name="Current Day"))
# fig.add_trace(go.Scatter(x=96 + np.arange(96), y=next_day_np, name="Next Day"))
fig.add_trace(go.Scatter(x=np.arange(96), y=current_day_np, name="Current Day"))
fig.add_trace(go.Scatter(x=96 + np.arange(96), y=next_day_np, name="Next Day"))
# for i, q in enumerate(self.quantiles):
# fig.add_trace(
# go.Scatter(
# x=96 + np.arange(96),
# y=predictions_np[:, i],
# name=f"Prediction (Q={q})",
# line=dict(dash="dash"),
# )
# )
for i, q in enumerate(self.quantiles):
fig.add_trace(
go.Scatter(
x=96 + np.arange(96),
y=predictions_np[:, i],
name=f"Prediction (Q={q})",
line=dict(dash="dash"),
)
)
# # Update the layout
# fig.update_layout(
# title="Predictions and Quantiles of the Linear Model",
# showlegend=show_legend,
# )
# Update the layout
fig.update_layout(
title="Predictions and Quantiles of the Linear Model",
showlegend=show_legend,
)
sns.set_theme()
time_steps = np.arange(0, 96)
fig, ax = plt.subplots(figsize=(20, 10))
ax.plot(time_steps, predictions_np.mean(axis=0), label="Mean of NRV samples", linewidth=3)
# ax.fill_between(time_steps, ci_lower, ci_upper, color='b', alpha=0.2, label='Full Interval')
ax.fill_between(time_steps, ci_99_lower, ci_99_upper, color='b', alpha=0.2, label='99% Interval')
ax.fill_between(time_steps, ci_95_lower, ci_95_upper, color='b', alpha=0.2, label='95% Interval')
ax.fill_between(time_steps, ci_90_lower, ci_90_upper, color='b', alpha=0.2, label='90% Interval')
ax.fill_between(time_steps, ci_50_lower, ci_50_upper, color='b', alpha=0.2, label='50% Interval')
ax.plot(next_day_np, label="Real NRV", linewidth=3)
# full_interval_patch = mpatches.Patch(color='b', alpha=0.2, label='Full Interval')
ci_99_patch = mpatches.Patch(color='b', alpha=0.3, label='99% Interval')
ci_95_patch = mpatches.Patch(color='b', alpha=0.4, label='95% Interval')
ci_90_patch = mpatches.Patch(color='b', alpha=0.5, label='90% Interval')
ci_50_patch = mpatches.Patch(color='b', alpha=0.6, label='50% Interval')
ax.legend(handles=[ci_99_patch, ci_95_patch, ci_90_patch, ci_50_patch, ax.lines[0], ax.lines[1]])
return fig
def auto_regressive(self, dataset, idx_batch, sequence_length: int = 96):

View File

@@ -86,7 +86,7 @@ class Trainer:
def random_samples(self, train: bool = True, num_samples: int = 10):
train_loader, test_loader = self.data_processor.get_dataloaders(
predict_sequence_length=96
predict_sequence_length=self.model.output_size
)
if train:
@@ -94,14 +94,7 @@ class Trainer:
else:
loader = test_loader
np.random.seed(42)
actual_indices = np.random.choice(loader.dataset.full_day_valid_indices, num_samples, replace=False)
indices = {}
for i in actual_indices:
indices[i] = loader.dataset.valid_indices.index(i)
print(actual_indices)
indices = np.random.randint(0, len(loader.dataset) - 1, size=num_samples)
return indices
def train(self, epochs: int, remotely: bool = False, task: Task = None):
@@ -114,8 +107,8 @@ class Trainer:
predict_sequence_length=self.model.output_size
)
train_samples = self.random_samples(train=True, num_samples=5)
test_samples = self.random_samples(train=False, num_samples=5)
train_samples = self.random_samples(train=True)
test_samples = self.random_samples(train=False)
self.init_clearml_task(task)

View File

@@ -38,7 +38,7 @@ data_config.NOMINAL_NET_POSITION = True
data_config = task.connect(data_config, name="data_features")
data_processor = DataProcessor(data_config, path="", lstm=False)
data_processor.set_batch_size(64)
data_processor.set_batch_size(128)
data_processor.set_full_day_skip(True)
inputDim = data_processor.get_input_size()
@@ -47,15 +47,15 @@ print("Input dim: ", inputDim)
model_parameters = {
"epochs": 5000,
"learning_rate": 0.0001,
"hidden_sizes": [128, 128],
"time_dim": 8,
"hidden_sizes": [512, 512, 512],
"time_dim": 64,
}
model_parameters = task.connect(model_parameters, name="model_parameters")
#### Model ####
model = SimpleDiffusionModel(96, model_parameters["hidden_sizes"], other_inputs_dim=inputDim[1], time_dim=model_parameters["time_dim"])
# model = GRUDiffusionModel(96, model_parameters["hidden_sizes"], other_inputs_dim=inputDim[2], time_dim=model_parameters["time_dim"], gru_hidden_size=128)
# model = SimpleDiffusionModel(96, model_parameters["hidden_sizes"], other_inputs_dim=inputDim[1], time_dim=model_parameters["time_dim"])
model = GRUDiffusionModel(96, model_parameters["hidden_sizes"], other_inputs_dim=inputDim[2], time_dim=model_parameters["time_dim"], gru_hidden_size=256)
print("Starting training ...")