diff --git a/.gitignore b/.gitignore index b0452612a7becab1caebb2f77e4b454813a4c9f3..0fd481a5848990b0d291057d41bf247214afbd7a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ models/__pycache__/ interactive/jsd/ interactive/plots/ interactive/*.png - +interactive/inference/__pycache__/ +interactive/physics/__pycache__/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c5315a814d59689ec3f0e988af8aecd32af08d1..0bf3e8f3bda6a7774980fc6093459f23ba37ff46 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ build_kaniko_command: variables: # To push to a specific docker tag other than latest(the default), amend the --destination parameter, e.g. --destination $CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME # See https://docs.gitlab.com/ee/ci/variables/predefined_variables.html#variables-reference for available variables - IMAGE_DESTINATION: ${CI_REGISTRY_IMAGE}:ddp + IMAGE_DESTINATION: ${CI_REGISTRY_IMAGE}:Peter_image image: # We recommend using the CERN version of the Kaniko image: gitlab-registry.cern.ch/ci-tools/docker-image-builder name: gitlab-registry.cern.ch/ci-tools/docker-image-builder @@ -21,5 +21,4 @@ check_python_run: image: pytorch/pytorch:1.9.0-cuda10.2-cudnn7-runtime script: - pip install pyflakes - - pyflakes $CI_PROJECT_DIR/regressor.py - - pyflakes $CI_PROJECT_DIR/wgan.py \ No newline at end of file + - pyflakes $CI_PROJECT_DIR/wgan_ECAL_HCAL_3crit.py diff --git a/Dockerfile b/Dockerfile index 23e5c4abe6f43a16aece4479d92e140577db9d42..f31165b449627cd83e471d7ad02b8a25accc2f9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,9 @@ FROM pytorch/pytorch:1.9.0-cuda10.2-cudnn7-runtime +ENV NB_USER jovyan +ENV NB_UID 1000 +ENV HOME /home/$NB_USER + RUN apt-get -qq update && \ apt-get -yqq install libpam-krb5 krb5-user && \ apt-get -yqq clean && \ @@ -9,22 +13,28 @@ RUN apt-get -qq update && \ python3-pip python3-wheel && \ rm -rf /var/lib/apt/lists/* +# create user and set required ownership +RUN useradd -M -s /bin/bash -N -u ${NB_UID} ${NB_USER} \ + && mkdir -p ${HOME} \ + && chown -R ${NB_USER}:users ${HOME} \ + && chown -R ${NB_USER}:users /usr/local/bin -RUN mkdir -p /opt/regressor && \ - mkdir -p /opt/regressor/src/models \ +RUN mkdir -p ${HOME}/models \ && pip install h5py pyflakes comet_ml && export MKL_SERVICE_FORCE_INTEL=1 -WORKDIR /opt/regressor/src +WORKDIR ${HOME} -ADD wgan.py /opt/regressor/src/wgan.py -ADD wganHCAL.py /opt/regressor/src/wganHCAL.py +ADD wgan.py ${HOME}/wgan.py +ADD wganHCAL.py ${HOME}/wganHCAL.py +ADD wgan_ECAL_HCAL_3crit.py ${HOME}/wgan_ECAL_HCAL_3crit.py -COPY ./models/* /opt/regressor/src/models/ +COPY ./models/* ${HOME}/models/ COPY docker/krb5.conf /etc/krb5.conf +RUN mkdir -p /etc/sudoers.d && echo "jovyan ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers.d/jovyan +RUN echo 'PS1="${debian_chroot:+($debian_chroot)}@\h:\w\$ "' >> /etc/bash.bashrc -RUN chgrp -R 0 /opt/regressor \ - && chmod -R g+rwX /opt/regressor +USER $NB_USER ENTRYPOINT ["/bin/bash"] diff --git a/interactive/control.ipynb b/interactive/control.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..50e79376b2905cff38a7dda5d4266c583e608938 --- /dev/null +++ b/interactive/control.ipynb @@ -0,0 +1,257 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import torch.nn as nn\n", + "import sys\n", + "sys.path.append('/home/jovyan/pytorchjob')\n", + "\n", + "import importlib\n", + "\n", + "from models.generator import DCGAN_G\n", + "from models.generatorFull import Hcal_ecalEMB\n", + "from matplotlib.colors import LogNorm\n", + "\n", + "import random\n", + "from torch.autograd import Variable\n", + "import interactive.physics.plotting as phys\n", + "import interactive.physics.basics as B\n", + "import interactive.inference.generate as G\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib as mpl\n", + "import h5py\n", + "import matplotlib.patches as mpatches" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "f50 = h5py.File('/eos/user/e/eneren/scratch/50GeV75k.hdf5', 'r')\n", + "s50E = f50['ecal/layers'][:3000]\n", + "s50H = f50['hcal/layers'][:3000]\n", + "\n", + "cReal50 = np.concatenate((s50E , s50H ),1)\n", + "\n", + "esumRealECAL50 = B.getTotE(s50E, 30, 30, 30)\n", + "esumRealHCAL50 = B.getTotE(s50H, 30, 30, 48)\n", + "esumReal50 = B.getTotE(cReal50, 30, 30, 78)\n", + "\n", + "showers = {\n", + " '50F': esumReal50,\n", + " '50E': esumRealECAL50,\n", + " '50H': esumRealHCAL50\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "importlib.reload(B)\n", + "importlib.reload(G)\n", + "G.fid_scan_rECAL(showers, 1000, 1600)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Generating of showers: current batch 1000 .......\n", + "Generating of showers: current batch 2000 .......\n", + "Generating of showers: current batch 3000 .......\n" + ] + } + ], + "source": [ + "importlib.reload(G)\n", + "eph=1586\n", + "fakeShower, fE, fH = G.make_shower_rECAL(eph, 50, 3000)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "### MIP CUT\n", + "##MIPcut\n", + "cReal50[ cReal50 < 0.25] = 0.0\n", + "fakeShower[ fakeShower < 0.25] = 0.0\n", + "\n", + "s50E[ s50E < 0.25] = 0.0\n", + "fE[ fE < 0.25] = 0.0\n", + "\n", + "s50H[ s50H < 0.25] = 0.0\n", + "fH[ fH < 0.25] = 0.0\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning: Cannot change to a different GUI toolkit: notebook. Using widget instead.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ba3a4d1fced340a58a92e75c70e63c4c", + "version_major": 2, + "version_minor": 0 + }, + "image/png": "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PCQAAFpFq0xB5+TkKCMjQ5GRkerevbvmzJmjuXPn6tSpU3rjjTe87Vwul5544gm5XC6tXbtW/fr1U1RUlF544QVlZmbq+eef1/79+322ffz4cT3zzDMaNmyYlixZoltuuUWJiYl6++23FRMToyeeeEJff/11axwWAABBE9QAjo6O1tatWzV8+HC/uv79+0uSzp075y3bsmWL9u3bp8zMTPXo0cOn/ezZs+VyubRkyRKf8uXLl8vhcGjWrFmyWCze8oiICE2dOlVlZWVat25dMA8LAICgC2oAh4aG6p577lFIiP9mCwoKJEkZGRnesg0bNkiSRowY4dfeU+Zpcz19AABob1r1OWCHw6Fjx47prbfe0po1a/Tiiy/qoYce8tYXFxdLkpKTk/36JiQkKCwsTOXl5aqqqlJsbKwaGxt14MCBJvt4yjzbBQCgvWq1AN60aZPGjRsnSbr11lu1atUqTZ061adNRUWFJCkmJibgNqKjo+VwOFRZWanY2FhVV1fL6XTKYrEoOjrar323bt0kSZWVlcE8FAAAgq7VngN+4IEH1NjY6L0L+vvf/74eeOABVVVVedvU19dLck9dB2K1WiVJFy5caFF7AADaq1ZdiCMkJES33367FixYoP/4j/9Qfn6+5s+f763v0qWLJAVcR1eSnE6nJCk8PLxF7QEAaK/abCWs73//+5Kk1atXq66uTpL7Oq8kVVdXB+xTU1Mjyf1GGck9VW21WmUYhrfuUp47rD3tAQBor9rsZQzh4eHq3r27Tp8+rZKSEqWmpiolJUWbN29WaWmpX/uKigo5HA717NlTsbGxkqROnTpp4MCBKiwsVGlpqYYOHerT5+jRo5KklJSUK45l4cKF3unqsWPHauzYsUE4QgBAa8rLy1NeXp6kizOeN7KgBvAvf/lLffbZZ3rvvff86pxOp86ePStJioqKkiSNHz9eixcv9j6idKmdO3d621xq/PjxKiwsVEFBgV8AN9XncllZWd4xAABuDJeeMNntdi1btszkEV2foE5Bf/PNN9q+fbvPYhseq1evVmNjowYOHOh9XCgjI0MpKSnKzc3VqVOnfNpnZ2crJCRE8+bN8ymfO3euwsLCtGLFChmG4S2vq6vT2rVrlZiYqEmTJgXzsAAACLqgBrDFYtGZM2eUmZmp7du36/z58yovL9err76qefPmKSIiQq+//vrF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", + "text/html": [ + "\n", + " <div style=\"display: inline-block;\">\n", + " <div class=\"jupyter-widgets widget-label\" style=\"text-align: center;\">\n", + " Figure\n", + " </div>\n", + " <img src='data:image/png;base64,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PCQAAFpFq0xB5+TkKCMjQ5GRkerevbvmzJmjuXPn6tSpU3rjjTe87Vwul5544gm5XC6tXbtW/fr1U1RUlF544QVlZmbq+eef1/79+322ffz4cT3zzDMaNmyYlixZoltuuUWJiYl6++23FRMToyeeeEJff/11axwWAABBE9QAjo6O1tatWzV8+HC/uv79+0uSzp075y3bsmWL9u3bp8zMTPXo0cOn/ezZs+VyubRkyRKf8uXLl8vhcGjWrFmyWCze8oiICE2dOlVlZWVat25dMA8LAICgC2oAh4aG6p577lFIiP9mCwoKJEkZGRnesg0bNkiSRowY4dfeU+Zpcz19AABob1r1OWCHw6Fjx47prbfe0po1a/Tiiy/qoYce8tYXFxdLkpKTk/36JiQkKCwsTOXl5aqqqlJsbKwaGxt14MCBJvt4yjzbBQCgvWq1AN60aZPGjRsnSbr11lu1atUqTZ061adNRUWFJCkmJibgNqKjo+VwOFRZWanY2FhVV1fL6XTKYrEoOjrar323bt0kSZWVlcE8FAAAgq7VngN+4IEH1NjY6L0L+vvf/74eeOABVVVVedvU19dLck9dB2K1WiVJFy5caFF7AADaq1ZdiCMkJES33367FixYoP/4j/9Qfn6+5s+f763v0qWLJAVcR1eSnE6nJCk8PLxF7QEAaK/abCWs73//+5Kk1atXq66uTpL7Oq8kVVdXB+xTU1Mjyf1GGck9VW21WmUYhrfuUp47rD3tAQBor9rsZQzh4eHq3r27Tp8+rZKSEqWmpiolJUWbN29WaWmpX/uKigo5HA717NlTsbGxkqROnTpp4MCBKiwsVGlpqYYOHerT5+jRo5KklJSUK45l4cKF3unqsWPHauzYsUE4QgBAa8rLy1NeXp6kizOeN7KgBvAvf/lLffbZZ3rvvff86pxOp86ePStJioqKkiSNHz9eixcv9j6idKmdO3d621xq/PjxKiwsVEFBgV8AN9XncllZWd4xAABuDJeeMNntdi1btszkEV2foE5Bf/PNN9q+fbvPYhseq1evVmNjowYOHOh9XCgjI0MpKSnKzc3VqVOnfNpnZ2crJCRE8+bN8ymfO3euwsLCtGLFChmG4S2vq6vT2rVrlZiYqEmTJgXzsAAACLqgBrDFYtGZM2eUmZmp7du36/z58yovL9err76qefPmKSIiQq+//vrF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' width=480.0/>\n", + " </div>\n", + " " + ], + "text/plain": [ + "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib notebook\n", + "importlib.reload(phys)\n", + "importlib.reload(B)\n", + "phys.plot_esum(esumReal50, B.getTotE(fakeShower, 30, 30, 78), 50, 0, 2500, 700, '50_GeV_rECAL_eph'+str(eph))\n", + "\n", + "#phys.plot_esum(esumRealHCAL50, B.getTotE(fH, 30, 30, 48), 50, 0, 2000, 700, '50_GeV_HCAL_rECAL_only_eph'+str(eph))\n", + "#phys.plot_esum(esumRealECAL50, B.getTotE(fE, 30, 30, 30), 50, 0, 2000, 1400, '50_GeV_ECAL_rECAL_only_eph'+str(eph))\n", + "\n", + "#phys.esum_2D(esumRealECAL50, esumRealHCAL50, \n", + "# B.getTotE(fE, 30, 30, 30), B.getTotE(fH, 30, 30, 48),\n", + "# nbinsX=50, nbinsY=50, name='eph'+str(eph))\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "importlib.reload(phys)\n", + "R50 = B.getLongProfile(cReal50, 30, 30, 78)\n", + "F50 = B.getLongProfile(fakeShower, 30, 30, 78)\n", + "\n", + "phys.plt_Profile(R50, F50, save_title='Eph_rECAL'+str(eph))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "importlib.reload(phys)\n", + "\n", + "\n", + "nhitR = B.getOcc(cReal50, 30, 30, 78)\n", + "nhitF = B.getOcc(fakeShower, 30, 30, 78)\n", + "\n", + "phys.plt_nhits(nhitR, nhitF, 'WGAN', save_title='50GeV_eph_rECAL'+str(eph))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for b in range(1000, 3000 + 1 , 1000):\n", + " print(b)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "kubeflow_notebook": { + "autosnapshot": false, + "docker_image": "gitlab-registry.cern.ch/ai-ml/kubeflow_images/pytorch-notebook-gpu-1.8.1:v0.6.1-30", + "experiment": { + "id": "", + "name": "" + }, + "experiment_name": "", + "katib_metadata": { + "algorithm": { + "algorithmName": "grid" + }, + "maxFailedTrialCount": 3, + "maxTrialCount": 12, + "objective": { + "objectiveMetricName": "", + "type": "minimize" + }, + "parallelTrialCount": 3, + "parameters": [] + }, + "katib_run": false, + "pipeline_description": "", + "pipeline_name": "", + "snapshot_volumes": false, + "steps_defaults": [], + "volume_access_mode": "rwm", + "volumes": [] + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/interactive/dataLoad_chunck.ipynb b/interactive/dataLoad_chunck.ipynb deleted file mode 100644 index d0c064f2006f6beb4063717d046886360499e13d..0000000000000000000000000000000000000000 --- a/interactive/dataLoad_chunck.ipynb +++ /dev/null @@ -1,317 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "import torch.nn as nn\n", - "import torch.nn.parallel\n", - "import torch.nn.functional as F\n", - "\n", - "import h5py\n", - "import torch\n", - "from torch.utils import data\n", - "import numpy as np\n", - "\n", - "class HDF5Dataset(data.Dataset):\n", - " def __init__(self, file_path, train_size, transform=None):\n", - " super().__init__()\n", - " self.file_path = file_path\n", - " self.transform = transform\n", - " self.hdf5file = h5py.File(self.file_path, 'r')\n", - " \n", - " if train_size > self.hdf5file['ecal']['layers'].shape[0]-1:\n", - " self.train_size = self.hdf5file['ecal']['layers'].shape[0]-1\n", - " else:\n", - " self.train_size = train_size\n", - " \n", - " \n", - " def __len__(self):\n", - " return self.hdf5file['ecal']['layers'][0:self.train_size].shape[0]\n", - " \n", - " def __getitem__(self, index):\n", - " # get data\n", - " x = self.get_data(index)\n", - " if self.transform:\n", - " x = torch.from_numpy(self.transform(x)).float()\n", - " else:\n", - " x = torch.from_numpy(x).float()\n", - " e = torch.from_numpy(self.get_energy(index))\n", - " if torch.sum(x) != torch.sum(x): #checks for NANs\n", - " return self.__getitem__(int(np.random.rand()*self.__len__()))\n", - " else:\n", - " return x, e\n", - " \n", - " def get_data(self, i):\n", - " return self.hdf5file['ecal']['layers'][i]\n", - " \n", - " def get_energy(self, i):\n", - " return self.hdf5file['ecal']['energy'][i]\n", - " \n", - "\n", - "\n", - "class DataSingle(torch.utils.data.Dataset):\n", - " 'Characterizes a dataset for PyTorch'\n", - " def __init__(self, list_IDs, chunk):\n", - " 'Initialization'\n", - " self.list_IDs = list_IDs\n", - " self.chunk = chunk\n", - " \n", - " def __len__(self):\n", - " 'Denotes the total number of samples'\n", - " return len(self.list_IDs)\n", - "\n", - " def __getitem__(self, index):\n", - " 'Loads one sample of data'\n", - " # Select sample\n", - " ID = self.list_IDs[index]\n", - " chunk_size = self.chunk\n", - " \n", - " # Load data and get energy\n", - " d = HDF5Dataset('/eos/user/e/eneren/run_prod75k/hdf5/pion-shower_'+ ID +'.hdf5', transform=None, train_size=500)\n", - " for m in range(chunk_size,len(d)+1, chunk_size):\n", - " X = d.get_data(m)\n", - " y = d.get_energy(m)\n", - " print(X.shape, y.shape)\n", - " \n", - " return X, y\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "metadata": {}, - "outputs": [], - "source": [ - "params = {'batch_size': 10,\n", - " 'shuffle': True,\n", - " 'num_workers': 2}\n", - "\n", - "chunk_size = 100\n", - "partition = {\n", - " 'train': ['14', '11', '17'], \n", - " 'validation': ['18']\n", - "}\n", - "\n", - "\n", - "training_set = DataSingle(partition['train'], chunk_size)\n", - "training_generator = torch.utils.data.DataLoader(training_set, **params)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": {}, - "outputs": [], - "source": [ - "#training_set[0].shape" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "(30, 30, 30) (1,)\n", - "torch.Size([3, 30, 30, 30]) torch.Size([3, 1])\n" - ] - } - ], - "source": [ - "for epoch in range(1):\n", - " # Training\n", - " for data, energy in training_generator:\n", - " print (data.shape, energy.shape)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "<torch.utils.data.dataloader.DataLoader at 0x7fdf44db92e8>" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "training_generator" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "ID=15\n", - "a = HDF5Dataset('/eos/user/e/eneren/run_prod75k/hdf5/pion-shower_'+ str(ID) +'.hdf5', transform=None, train_size=5000)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "b = a.get_data()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "t = np.array(b)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "numpy.ndarray" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "type(t)" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "100\n", - "200\n", - "300\n", - "400\n", - "500\n" - ] - } - ], - "source": [ - "for i in range(100,500+1, 100):\n", - " print (i)" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "range(0, 5000)" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "a" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "kubeflow_notebook": { - "autosnapshot": false, - "docker_image": "gitlab-registry.cern.ch/ai-ml/kubeflow_images/pytorch-notebook-gpu-1.8.1:v0.6.1-30", - "experiment": { - "id": "", - "name": "" - }, - "experiment_name": "", - "katib_metadata": { - "algorithm": { - "algorithmName": "grid" - }, - "maxFailedTrialCount": 3, - "maxTrialCount": 12, - "objective": { - "objectiveMetricName": "", - "type": "minimize" - }, - "parallelTrialCount": 3, - "parameters": [] - }, - "katib_run": false, - "pipeline_description": "", - "pipeline_name": "", - "snapshot_volumes": false, - "steps_defaults": [], - "volume_access_mode": "rwm", - "volumes": [] - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/interactive/evalGPU.ipynb b/interactive/eval-ecal-GPU.ipynb similarity index 100% rename from interactive/evalGPU.ipynb rename to interactive/eval-ecal-GPU.ipynb diff --git a/interactive/eval-hcal-GPU.ipynb b/interactive/eval-hcal-GPU.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..4292b65d92ca0e975b64170fdccdc267f890dae4 --- /dev/null +++ b/interactive/eval-hcal-GPU.ipynb @@ -0,0 +1,449 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import torch.nn as nn\n", + "import sys\n", + "sys.path.append('/home/jovyan/pytorchjob')\n", + "\n", + "from models.generator import DCGAN_G\n", + "from models.generatorFull import Hcal_ecalEMB\n", + "from matplotlib.colors import LogNorm\n", + "\n", + "\n", + "import random\n", + "from torch.autograd import Variable\n", + "import interactive.functions as F\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib as mpl\n", + "import h5py\n", + "import matplotlib.patches as mpatches\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "## G4 \n", + "f40 = h5py.File('/eos/user/e/eneren/scratch/single/40GeVData20k.hdf5', 'r')\n", + "f50 = h5py.File('/eos/user/e/eneren/scratch/50GeV75k.hdf5', 'r')\n", + "f60 = h5py.File('/eos/user/e/eneren/scratch/single/60GeV20k.hdf5', 'r')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s40E = f40['ecal/layers'][:10]\n", + "s50E = f50['ecal/layers'][:1000]\n", + "s60E = f60['ecal/layers'][:10]\n", + "\n", + "\n", + "s40H = f40['hcal/layers'][:10]\n", + "s50H = f50['hcal/layers'][:1000]\n", + "s60H = f60['hcal/layers'][:10]\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cReal40 = np.concatenate((s40E , s40H ),1)\n", + "cReal50 = np.concatenate((s50E , s50H ),1)\n", + "cReal60 = np.concatenate((s60E , s60H ),1)\n", + "\n", + "esumReal40 = F.getTotE(cReal40, 30, 30, 78)\n", + "esumReal50 = F.getTotE(cReal50, 30, 30, 78)\n", + "esumReal60 = F.getTotE(cReal60, 30, 30, 78)\n", + "\n", + "\n", + "esumRealECAL40 = F.getTotE(s40E, 30, 30, 30)\n", + "esumRealHCAL40 = F.getTotE(s40H, 30, 30, 48)\n", + "\n", + "esumRealECAL60 = F.getTotE(s60E, 30, 30, 30)\n", + "esumRealHCAL60 = F.getTotE(s60H, 30, 30, 48)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "showers = {\n", + " '40F': esumReal40,\n", + " '40E': esumRealECAL40,\n", + " '40H': esumRealHCAL40,\n", + " '50F': esumReal50,\n", + " '60F': esumReal60,\n", + " '60E': esumRealECAL60,\n", + " '60F': esumReal60,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "showers['50F'].shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def esum_plot(real, fake, nbins, minE, maxE, name):\n", + " \n", + " figSE = plt.figure(figsize=(6,6*0.77/0.67))\n", + " axSE = figSE.add_subplot(1,1,1)\n", + "\n", + "\n", + " pSEa = axSE.hist(real, bins=nbins, \n", + " #weights=np.ones_like(real)/(float(len(real))), \n", + " histtype='step', color='black',\n", + " range=[minE, maxE])\n", + " pSEb = axSE.hist(fake, bins=nbins, \n", + " #weights=np.ones_like(fake)/(float(len(fake))),\n", + " histtype='step', color='red',\n", + " range=[minE, maxE])\n", + "\n", + " plt.title(name)\n", + " \n", + " plt.xlabel('MeV', fontsize=18)\n", + " #axSE.set_xscale('log')\n", + " #axSE.set_yscale('log')\n", + " \n", + " figSE.patch.set_facecolor('white') \n", + " \n", + " red_patch = mpatches.Patch(color='red', label='WGAN')\n", + " grey_patch = mpatches.Patch(color='black', label='G4')\n", + " \n", + " axSE.legend(handles=[red_patch, grey_patch])\n", + " \n", + " \n", + " \n", + " plt.savefig('./esum'+str(name)+'.png')\n", + "\n", + "\n", + "\n", + " \n", + " \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def fid_plot(eph_start, eph_end):\n", + " \n", + " ngf = 32\n", + " nz = 100\n", + " batch_size = 1000\n", + " device = torch.device(\"cuda\")\n", + " ## LOAD ECAL GENERATOR\n", + " mGenE = DCGAN_G(ngf, nz)\n", + " mGenE = nn.parallel.DataParallel(mGenE)\n", + " exp='wganv1'\n", + " eph = 694\n", + "\n", + " gen_checkpointECAL = torch.load('/eos/user/e/eneren/experiments/' + exp + \"_generator_\"+ str(eph) + \".pt\", map_location=torch.device('cuda'))\n", + " mGenE.load_state_dict(gen_checkpointECAL['model_state_dict'])\n", + " mGenE.eval()\n", + " #####\n", + "\n", + " ## LOAD HCAL GENERATOR\n", + " mGenH = Hcal_ecalEMB(ngf, 32, nz, emb_size=32).to(device)\n", + " mGenH = nn.parallel.DataParallel(mGenH)\n", + " expH = 'wganHCAL50GeV_v2'\n", + " \n", + " Tensor = torch.cuda.FloatTensor \n", + " eph_list = list(range(eph_start,eph_end,1))\n", + " \n", + " esum_mean = []\n", + " esum_down = []\n", + " esum_up = []\n", + " for eph in eph_list:\n", + " gen_checkpointHCAL = torch.load('/eos/user/e/eneren/experiments/' + expH + \"_generator_\"+ str(eph) + \".pt\", map_location=torch.device('cuda'))\n", + " mGenH.load_state_dict(gen_checkpointHCAL['model_state_dict'])\n", + " mGenH.eval()\n", + " \n", + " print (\"Epoch: \" , eph)\n", + " jsd = []\n", + " for Etrue in [50]:\n", + "\n", + " ##ECAL generate\n", + " z = Variable(Tensor(np.random.uniform(-1, 1, (batch_size, nz, 1, 1, 1))))\n", + " E = torch.from_numpy(np.random.uniform(Etrue, Etrue, (batch_size,1))).float()\n", + "\n", + " ecal_shower = mGenE(z, E.view(-1, 1, 1, 1, 1)).detach()\n", + " ecal_shower = ecal_shower.unsqueeze(1) \n", + " ####\n", + " \n", + " ## HCAL generate\n", + " zH = Variable(Tensor(np.random.uniform(-1, 1, (batch_size, nz))))\n", + " hcal_shower = mGenH(zH, E, ecal_shower).detach()\n", + "\n", + " ecal_shower = ecal_shower.squeeze(1)\n", + " comb = torch.cat((ecal_shower, hcal_shower),1)\n", + "\n", + " esumFake = F.getTotE(comb.cpu().numpy(), 30, 30, 78)\n", + " esumReal = showers[str(Etrue)+'F']\n", + "\n", + " \n", + "\n", + " jsd.append(F.jsdHist(esumReal, esumFake, 50, 0, 2500, eph, debug=False))\n", + " \n", + " #m = (jsd[0] + jsd[1] + jsd[2]) / 3.0\n", + " m = jsd[0]\n", + " esum_mean.append(m)\n", + " esum_down.append(m - min(jsd))\n", + " esum_up.append(max(jsd) - m)\n", + " \n", + " \n", + " print (\"Epoch: \", jsd)\n", + " \n", + "\n", + " # Plotting\n", + " # Energy sum with average and spread\n", + " plt.figure(figsize=(12,4), facecolor='none', dpi=400)\n", + " plt.scatter(eph_list, esum_mean, marker = 'x', color='red', label='Average Energy sum')\n", + " #plt.scatter(epoch_list, epoch_matrix_median[:,0], marker = '+', color='blue', label='Median Energy sum area difference')\n", + " plt.errorbar(eph_list, esum_mean, yerr=[esum_down, esum_up], color='black', label='Min-Max interval', linestyle='None')\n", + " #plt.errorbar(epoch_list, epoch_matrix_mean[:,0], yerr=epoch_matrix_std[:,0], color='green', label='Standard deviation', linestyle='None', capsize=3.0)\n", + "\n", + "\n", + " plt.legend(loc='upper right', fontsize=10)\n", + " plt.xlabel('epoch', fontsize=14)\n", + " plt.ylabel('JS difference', fontsize=16)\n", + " plt.ylim(0.1,1.0)\n", + " \n", + " plt.savefig('/eos/user/e/eneren/plots/sweep_min_max__'+str(eph_start)+'__'+str(eph_end)+'.png')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def esum_highStat(eph, Etrue): \n", + " \n", + " ngf = 32\n", + " nz = 100\n", + " batch_size = 1000\n", + " device = torch.device(\"cuda\")\n", + " ## LOAD ECAL GENERATOR\n", + " mGenE = DCGAN_G(ngf, nz)\n", + " mGenE = nn.parallel.DataParallel(mGenE)\n", + " exp='wganv1'\n", + " eph_ecal = 694\n", + "\n", + " gen_checkpointECAL = torch.load('/eos/user/e/eneren/experiments/' + exp + \"_generator_\"+ str(eph_ecal) + \".pt\", map_location=torch.device('cuda'))\n", + " mGenE.load_state_dict(gen_checkpointECAL['model_state_dict'])\n", + " mGenE.eval()\n", + " #####\n", + "\n", + " ## LOAD HCAL GENERATOR\n", + " mGenH = Hcal_ecalEMB(ngf, 32, nz, emb_size=32).to(device)\n", + " mGenH = nn.parallel.DataParallel(mGenH)\n", + " expH = 'wganHCAL50GeV_v2'\n", + " #expH = 'wganHCALv1'\n", + " \n", + " Tensor = torch.cuda.FloatTensor \n", + " \n", + " fakeL = []\n", + " fakeIm = []\n", + " for b in range(batch_size, 1001, batch_size):\n", + " gen_checkpointHCAL = torch.load('/eos/user/e/eneren/experiments/' + expH + \"_generator_\"+ str(eph) + \".pt\", map_location=torch.device('cuda'))\n", + " mGenH.load_state_dict(gen_checkpointHCAL['model_state_dict'])\n", + " mGenH.eval()\n", + " \n", + "\n", + " ##ECAL generate\n", + " z = Variable(Tensor(np.random.uniform(-1, 1, (batch_size, nz, 1, 1, 1))))\n", + " E = torch.from_numpy(np.random.uniform(Etrue, Etrue, (batch_size,1))).float()\n", + "\n", + " ecal_shower = mGenE(z, E.view(-1, 1, 1, 1, 1)).detach()\n", + " ecal_shower = ecal_shower.unsqueeze(1) \n", + " ####\n", + "\n", + " ## HCAL generate\n", + " zH = Variable(Tensor(np.random.uniform(-1, 1, (batch_size, nz))))\n", + " hcal_shower = mGenH(zH, E, ecal_shower).detach()\n", + "\n", + " #print (F.getTotE(hcal_shower.cpu().numpy(), 48, 30, 30))\n", + " \n", + " ecal_shower = ecal_shower.squeeze(1)\n", + " comb = torch.cat((ecal_shower, hcal_shower),1)\n", + " esumFake = F.getTotE(comb.cpu().numpy(), 78, 30, 30)\n", + " fakeL.append(esumFake)\n", + " fakeIm.append(comb.cpu().numpy())\n", + " \n", + " esum = np.asarray(fakeL)\n", + " im = np.asarray(fakeIm)\n", + " \n", + " return esum, im" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "EPH = 723\n", + "E = 50\n", + "eFake, imFake = esum_highStat(EPH, E)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "esum_plot(showers[str(E)+'F'], eFake.flatten(), 50, 0, 2500, 'epoch_stat1k_'+str(EPH)+'_E='+str(E))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fid_plot(1450, 1601)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_combinedSingleXZ(imageReal, imageFake, r):\n", + " \n", + " cmap = mpl.cm.viridis\n", + " cmap.set_bad('white',1.)\n", + " \n", + " figExIm, (axExIm1, axExIm2) = plt.subplots(1, 2, figsize=(20, 6))\n", + " \n", + " \n", + " \n", + " # Y-Z Real\n", + " imsumReal = np.sum(imageReal[r], axis=1)#+1.0\n", + " im1 = axExIm1.imshow(imsumReal, filternorm=False, interpolation='none', cmap = cmap, vmin=0.01, vmax=100,\n", + " norm=mpl.colors.LogNorm(), origin='lower')\n", + " figExIm.patch.set_facecolor('white') \n", + " axExIm1.set_xlabel('y [cells]', family='serif')\n", + " axExIm1.set_ylabel('z [layers]', family='serif')\n", + " figExIm.colorbar(im1)\n", + " \n", + " # Y-Z Fake\n", + " imsumFake = np.sum(imageFake[0][r], axis=1)#+1.0\n", + " im2 = axExIm2.imshow(imsumFake, filternorm=False, interpolation='none', cmap = cmap, vmin=0.01, vmax=100,\n", + " norm=mpl.colors.LogNorm(), origin='lower')\n", + " figExIm.patch.set_facecolor('white') \n", + " axExIm2.set_xlabel('y [cells]', family='serif')\n", + " axExIm2.set_ylabel('z [layers]', family='serif')\n", + " figExIm.colorbar(im2)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "r = random.randrange(1, 1000)\n", + "for i in range(1,20):\n", + " plot_combinedSingleXZ(cReal50, imFake, i)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "imFake.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "kubeflow_notebook": { + "autosnapshot": false, + "docker_image": "gitlab-registry.cern.ch/ai-ml/kubeflow_images/pytorch-notebook-gpu-1.8.1:v0.6.1-30", + "experiment": { + "id": "", + "name": "" + }, + "experiment_name": "", + "katib_metadata": { + "algorithm": { + "algorithmName": "grid" + }, + "maxFailedTrialCount": 3, + "maxTrialCount": 12, + "objective": { + "objectiveMetricName": "", + "type": "minimize" + }, + "parallelTrialCount": 3, + "parameters": [] + }, + "katib_run": false, + "pipeline_description": "", + "pipeline_name": "", + "snapshot_volumes": false, + "steps_defaults": [], + "volume_access_mode": "rwm", + "volumes": [] + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/interactive/generateECAL_HCAL.ipynb b/interactive/generateECAL_HCAL.ipynb index f700917140a03d20cd8bdbcb799fd0886f77412f..a4329cbc5d26f7b6858cd85aa49f8689c2b570c7 100644 --- a/interactive/generateECAL_HCAL.ipynb +++ b/interactive/generateECAL_HCAL.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -21,6 +21,7 @@ "sys.path.append('/home/jovyan/pytorchjob')\n", "from models.generator import DCGAN_G\n", "from models.generatorFull import Hcal_ecalEMB\n", + "from matplotlib.colors import LogNorm\n", "\n", "from torch.autograd import Variable\n", "import interactive.functions as F\n", @@ -34,7 +35,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -43,19 +44,19 @@ "#f50 = h5py.File('/eos/user/e/eneren/scratch/50GeV75k.hdf5', 'r')\n", "f60 = h5py.File('/eos/user/e/eneren/scratch/single/60GeV20k.hdf5', 'r')\n", "\n", - "#showers40E = f40['ecal/layers'][:1000]\n", - "#showers40H = f40['hcal/layers'][:1000]\n", + "#showers40E = f40['ecal/layers'][:1500]\n", + "#showers40H = f40['hcal/layers'][:1500]\n", "\n", "#showers50E = f50['ecal/layers'][:1000]\n", "#showers50H = f50['hcal/layers'][:1000]\n", "\n", - "showers60E = f60['ecal/layers'][:1000]\n", - "showers60H = f60['hcal/layers'][:1000]\n" + "showers60E = f60['ecal/layers'][:1500]\n", + "showers60H = f60['hcal/layers'][:1500]\n" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -102,24 +103,15 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "torch.Size([1000, 30, 30, 30]) torch.Size([1000, 48, 30, 30])\n", - "torch.Size([1000, 78, 30, 30])\n" - ] - } - ], + "outputs": [], "source": [ "ngf = 32\n", "nz = 100\n", - "batch_size = 1000\n", + "batch_size = 1500\n", "device = torch.device(\"cuda\")\n", - "Etrue = 60\n", + "Etrue = 40\n", "## LOAD ECAL GENERATOR\n", "mGenE = DCGAN_G(ngf, nz)\n", "mGenE = nn.parallel.DataParallel(mGenE)\n", @@ -135,7 +127,7 @@ "mGenH = Hcal_ecalEMB(ngf, 32, nz, emb_size=32).to(device)\n", "mGenH = nn.parallel.DataParallel(mGenH)\n", "expH = 'wganHCALv1'\n", - "ephH = 63\n", + "ephH = 103\n", "gen_checkpointHCAL = torch.load('/eos/user/e/eneren/experiments/' + expH + \"_generator_\"+ str(ephH) + \".pt\", map_location=torch.device('cuda'))\n", "mGenH.load_state_dict(gen_checkpointHCAL['model_state_dict'])\n", "mGenH.eval()\n", @@ -161,20 +153,9 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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tXmde8s4M+ekqa41yd4/9yaXxlUFNvvFR2mP4YoPP/xQY8aMUU5OToljMzMzdcstt+jUqVNasmSJOnXqpFWrVmnkyJHasmWLli9fLn9/8++Zp1T5xYp/H/1fhYWFuTy+X+iD1k8S08LS8LbPpVg+xa9DG1ueE3Ta+r1CHca59obvcrvmuPYLqzScvckuSeKuuDLIxHTj78039iWpt+pny3OyazWyND4vw/o/f/O/t5IFnnZjjhu5BWWU8je8C5q/Yv1neeDpivshxpUfzLjC1Q9vypuV15LncO1KAAC+a/HixVq4cKHef/99tW/fvshzISEhknTVq4YKCvzQ0NCyTRIAAMAHUYcB/+WJbZxcmD9v3jzFxcVp/vz5WrZsmeLj44sdP2XKFP3www9auXKlbr31VknS0KFDNX36dD311FN66623NGbMmFImfnVVfrECAAAAVceaNWv0yCOP6O2339Y999xjPB8VFaXExESdOnXK6fyMjAxJUr169Yo9z+TJkwuv/uvXr5/69etXysxRFaxevVqrV6+WJJeufAMAoDKhDkNF5qt1WExMjBITExUZGally4q/APXMmTP65z//qfr16+uuu+4q8tyoUaM0ceJEvf7664WLFatWrdLLL79cOGbJkiWKiooqVb4sVgAAAKBKWLt2rYYOHao33nhDsbGxTsfExMRo3bp1OnTokPFcamqqsrOzVb9+fdWqVavYc82cOdPS3buAVPQDlczMTL3xxhtezggAAM+gDkNFV951mM3hdKdUy8coScHdEa5Yv369srOz1a1btyJbtElSrVq11LJlS+3bt09JSUlq2bKl+vfvX6TvjCfQYBsAAAA+b926dRoyZIjmzp1b5A1yYmKili5dWvj1gAEDJEnbtm0zjrF169YiYwAAAFAy6jDACYfDMw8P2r17tyQpOjra6fMF8YJxZYE7KwAAAODT1q9fr7vvvltz5szRww8/XOS5hIQELVy4UMOHD5ck9e7dWzExMVqxYoWOHTumunXrFo6dP3++/Pz8NHbs2HLNv6yld440Yll1zDc+zppJt/n3dCNW85DZR+pUjBkLC802Ys1Cjxux/5xuZ8TaTzT7qP3wqtknbec/zFjPtRON2OmxNxsxV7R6wcxj37Sy79cGAEBlUdnrMFebaTuT/NuiDbUv1M0zxgQdMz+a9b/7hBFrFGzWTe1q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1VWKiIhQXFycbr/9ds2ePVtnz54tUzcvL0+TJ09WmzZtFBkZqcaNG2vAgAHatWuXX2I5ffq0xo0bp8TERNWtW1exsbHq16+fUlNTlZeX55drIHgxWQEEiDFGQ4YMkSStWrWqzB8B0tPTtXbtWsXGxqqoqKjMHw+q4vjx4+rUqZPefvttvfnmmzp79qw++ugjtW7dWhMmTNC0adNK6xYXF+vee+/V6NGjlZKSom+//Vaff/65YmNjdeedd2rRokU+xSJJDz/8sKZNm6bf//73OnHihD755BN1795dTz31lFasWOHz+QEAQMXIRchFAACAO7Zs2aIdO3aoVq1aiomJ0ccffyzp+y+MvPvuu0pISNC5c+eUnp7u87W++OIL3XLLLdq2bZuWL1+unJwcrVu3TvXq1dOoUaPK7PGVl5en2267TdOnT9ekSZOUnZ2tHTt2qLCwUF27dtXGjRt9isUYo969e+utt95SamqqsrKytHXrVsXGxiolJUU7duzwtbsIckxWAAE0fPhwSdL8+fMtx+bNm6eUlBSFhfn+z/CJJ57QwYMHlZqaql69eikyMlLx8fGaN2+e2rdvX6bunDlztGLFCj300EN68sknVb9+fTVv3lzz589XXFycRo4c6dM3DrOzs7V8+XLdeeeduuuuu1SvXj01adJEEydOVLdu3XztKgAAsIFchFwEAAC4o2PHjnruueeUmZmpRx99VJJ09OhRPf7440pLS1O9evX8cp1f/epXysrK0rvvvquOHTsqIiJCbdq00X//93/ryiuvLFP3+eef144dOzRmzBgNHjxYkZGRSkhI0FtvvaU6depo2LBhKioqqnIse/bs0ccff6zBgwerW7duioqK0lVXXaW5c+cqNjbW164iBDBZAQRQ165d1a5dO61evVrHjh0rLT9+/LjWrFmjlJQUn69x/PhxLV++XI0bN9btt99e5lhYWJjGjx+vLl26lJbNnTtXkkoHyhK1atXSfffdp9zcXC1btqzK8YSFhckYo+3bt+vw4cNlji1btkz33ntvlc8NAADsIRchFwEAAO6ZOHGibrzxRr3//vv685//rKFDh+qhhx5SUlKSX86/Y8cO7dq1SzfddJNat25d5ljdunX1wgsv6Prrr5f0/TKcb775piRrHtawYUP17dtX33zzjTZt2lTleDwejyRpw4YNysrKKlO+detW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\n", - "text/plain": [ - "<Figure size 2000x480 with 4 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "r = random.randrange(1, 1000)\n", "plot_combinedSingle(combined.cpu(), r)" @@ -182,47 +163,42 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "esumFake = F.getTotE(combined.cpu().numpy(), 78, 30, 30)" + "esumFake = F.getTotE(combined.cpu().numpy(), 78, 30, 30)\n", + "esumFakeECAL = F.getTotE(ecal_shower.cpu().numpy(), 30, 30, 30)\n", + "esumFakeHCAL = F.getTotE(hcal_shower.cpu().numpy(), 48, 30, 30)\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 51, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "combinedReal = np.concatenate((showers60E , showers60H ),1)\n", "combinedReal.shape\n", - "esumReal = F.getTotE(combinedReal, 78, 30, 30)" + "esumReal = F.getTotE(combinedReal, 78, 30, 30)\n", + "\n", + "esumRealECAL = F.getTotE(showers60E, 30, 30, 30)\n", + "esumRealHCAL = F.getTotE(showers60H, 48, 30, 30)" ] }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(1000,)" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "esumReal.shape" ] }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -254,27 +230,133 @@ " \n", " \n", " \n", - " plt.savefig('./esum'+str(name)+'.png')" + " plt.savefig('./esum'+str(name)+'.png')\n", + " \n", + "\n", + "def esum_plotECAL(real, fake, nbins, minE, maxE, name):\n", + " \n", + " figSE = plt.figure(figsize=(6,6*0.77/0.67))\n", + " axSE = figSE.add_subplot(1,1,1)\n", + "\n", + "\n", + " pSEa = axSE.hist(real, bins=nbins, \n", + " #weights=np.ones_like(real)/(float(len(real))), \n", + " histtype='step', color='black',\n", + " range=[minE, maxE])\n", + " pSEb = axSE.hist(fake, bins=nbins, \n", + " #weights=np.ones_like(fake)/(float(len(fake))),\n", + " histtype='step', color='red',\n", + " range=[minE, maxE])\n", + "\n", + " plt.title(name)\n", + " \n", + " plt.xlabel('MeV', fontsize=18)\n", + " #axSE.set_xscale('log')\n", + " #axSE.set_yscale('log')\n", + " \n", + " red_patch = mpatches.Patch(color='red', label='WGAN')\n", + " grey_patch = mpatches.Patch(color='black', label='G4')\n", + " \n", + " axSE.legend(handles=[red_patch, grey_patch])\n", + " \n", + " \n", + " \n", + " plt.savefig('./esumECAL'+str(name)+'.png')\n", + "\n", + "def esum_plotHCAL(real, fake, nbins, minE, maxE, name):\n", + " \n", + " figSE = plt.figure(figsize=(6,6*0.77/0.67))\n", + " axSE = figSE.add_subplot(1,1,1)\n", + "\n", + "\n", + " pSEa = axSE.hist(real, bins=nbins, \n", + " #weights=np.ones_like(real)/(float(len(real))), \n", + " histtype='step', color='black',\n", + " range=[minE, maxE])\n", + " pSEb = axSE.hist(fake, bins=nbins, \n", + " #weights=np.ones_like(fake)/(float(len(fake))),\n", + " histtype='step', color='red',\n", + " range=[minE, maxE])\n", + "\n", + " plt.title(name)\n", + " \n", + " plt.xlabel('MeV', fontsize=18)\n", + " #axSE.set_xscale('log')\n", + " #axSE.set_yscale('log')\n", + " \n", + " red_patch = mpatches.Patch(color='red', label='WGAN')\n", + " grey_patch = mpatches.Patch(color='black', label='G4')\n", + " \n", + " axSE.legend(handles=[red_patch, grey_patch])\n", + " \n", + " \n", + " \n", + " plt.savefig('./esumHCAL'+str(name)+'.png')\n", + " \n", + " \n", + "\n", + "def esum_2D(ecal, hcal, nbinsX, nbinsY, name):\n", + " \n", + "\n", + " plt.hist2d(hcal, ecal,bins=(nbinsX, nbinsY), \n", + " range = [[0,1000], [0,1000]], norm=LogNorm(),\n", + " cmap=plt.cm.Reds)\n", + " \n", + "\n", + " plt.title(name)\n", + " \n", + " plt.xlabel('HCAL E-Sum [MeV]', fontsize=18)\n", + " plt.ylabel('ECAL E-Sum [MeV]', fontsize=18)\n", + " #axSE.set_xscale('log')\n", + " #axSE.set_yscale('log')\n", + " \n", + " plt.colorbar()\n", + " plt.savefig('./esumHCAL'+str(name)+'.png')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "esum_plot(esumReal, esumFake, 50, 0, 2600, 'epoch_stat1k_'+str(103)+'_E='+str(60))" ] }, { "cell_type": "code", - "execution_count": 52, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "<Figure size 480x551.642 with 1 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], + "source": [ + "esum_plotECAL(esumRealECAL, esumFakeECAL, 50, -100, 1200, 'epoch_stat1k_'+str(103)+'_E='+str(60))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "esum_plotHCAL(esumRealHCAL, esumFakeHCAL, 50, 0, 3000, 'epoch_stat1k_'+str(103)+'_E='+str(60))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "esum_2D(esumRealECAL, esumRealHCAL, 50, 50, 'Real2D_E='+str(60))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ - "esum_plot(esumReal, esumFake, 50, 0, 2600, 'epoch_stat1k_'+str(63)+'_E='+str(60))" + "esum_2D(esumFakeECAL, esumFakeHCAL, 50, 50, 'Fake2D_E='+str(60))" ] }, { diff --git a/interactive/inference/generate.py b/interactive/inference/generate.py new file mode 100644 index 0000000000000000000000000000000000000000..51e1e63c02f6ab61807d53fb4774009062451e1e --- /dev/null +++ b/interactive/inference/generate.py @@ -0,0 +1,218 @@ +import torch +import random +import sys +from torch.autograd import Variable +import numpy as np +import h5py +import torch.nn as nn +import matplotlib.pyplot as plt +sys.path.append('/home/jovyan/pytorchjob') +from models.generator import DCGAN_G +from models.generatorFull import Hcal_ecalEMB +import interactive.physics.basics as B +import scipy.spatial.distance as dist + + +def make_shower(eph, Etrue, nEvents): + + + ngf = 32 + nz = 100 + batch_size = 1000 + device = torch.device("cuda") + ## LOAD ECAL GENERATOR + mGenE = DCGAN_G(ngf, nz) + mGenE = torch.nn.parallel.DataParallel(mGenE) + exp='wganv1' + eph_ecal = 694 + + gen_checkpointECAL = torch.load('/eos/user/e/eneren/experiments/' + exp + "_generator_"+ str(eph_ecal) + ".pt", map_location=torch.device('cuda')) + mGenE.load_state_dict(gen_checkpointECAL['model_state_dict']) + mGenE.eval() + ##### + + ## LOAD HCAL GENERATOR + mGenH = Hcal_ecalEMB(ngf, 32, nz, emb_size=32).to(device) + mGenH = torch.nn.parallel.DataParallel(mGenH) + expH = 'wganHCAL50GeV_v2' + #expH = 'wganHCALv1' + + Tensor = torch.cuda.FloatTensor + + + gen_checkpointHCAL = torch.load('/eos/user/e/eneren/experiments/' + expH + "_generator_"+ str(eph) + ".pt", map_location=torch.device('cuda')) + mGenH.load_state_dict(gen_checkpointHCAL['model_state_dict']) + mGenH.eval() + + fakeList = [] + fE_list = [] + fH_list = [] + for b in range(batch_size, nEvents + 1 , batch_size): + + print ("Generating of showers: current batch {} .......".format(b)) + ##ECAL generate + z = Variable(Tensor(np.random.uniform(-1, 1, (batch_size, nz, 1, 1, 1)))) + E = torch.from_numpy(np.random.uniform(Etrue, Etrue, (batch_size,1))).float() + + ecal_shower = mGenE(z, E.view(-1, 1, 1, 1, 1)).detach() + ecal_shower = ecal_shower.unsqueeze(1) + #### + + ## HCAL generate + zH = Variable(Tensor(np.random.uniform(-1, 1, (batch_size, nz)))) + hcal_shower = mGenH(zH, E, ecal_shower).detach() + + ecal_shower = ecal_shower.squeeze(1) + comb = torch.cat((ecal_shower, hcal_shower),1) + + fakeList.append(comb.cpu().numpy()) + fE_list.append(ecal_shower.cpu().numpy()) + fH_list.append(hcal_shower.cpu().numpy()) + + + im = np.vstack(fakeList) + imE = np.vstack(fE_list) + imH = np.vstack(fH_list) + + return im, imE, imH + + + + +def make_shower_rECAL(eph, Etrue, nEvents): + + + ngf = 32 + nz = 100 + batch_size = 1000 + device = torch.device("cuda") + ## LOAD REAL ECAL DATA + f50 = h5py.File('/eos/user/e/eneren/scratch/50GeV75k.hdf5', 'r') + ##### + + + ## LOAD HCAL GENERATOR + mGenH = Hcal_ecalEMB(ngf, 32, nz, emb_size=32).to(device) + mGenH = torch.nn.parallel.DataParallel(mGenH) + expH = 'wganHCAL50GeV_v2' + #expH = 'wganHCALv1' + + Tensor = torch.cuda.FloatTensor + + + gen_checkpointHCAL = torch.load('/eos/user/e/eneren/experiments/' + expH + "_generator_"+ str(eph) + ".pt", map_location=torch.device('cuda')) + mGenH.load_state_dict(gen_checkpointHCAL['model_state_dict']) + mGenH.eval() + + fakeList = [] + fE_list = [] + fH_list = [] + for b in range(batch_size, nEvents + 1 , batch_size): + + print ("Generating of showers: current batch {} .......".format(b)) + ##ECAL REAL + s50E = f50['ecal/layers'][b-batch_size:b] + ecal_shower = torch.from_numpy(s50E).float() + ecal_shower = ecal_shower.unsqueeze(1) + ecal_shower = ecal_shower.cuda() + E = torch.from_numpy(np.random.uniform(Etrue, Etrue, (batch_size,1))).float() + #### + + ## HCAL generate + zH = Variable(Tensor(np.random.uniform(-1, 1, (batch_size, nz)))) + hcal_shower = mGenH(zH, E, ecal_shower).detach() + + ecal_shower = ecal_shower.squeeze(1) + comb = torch.cat((ecal_shower, hcal_shower),1) + + fakeList.append(comb.cpu().numpy()) + fE_list.append(ecal_shower.cpu().numpy()) + fH_list.append(hcal_shower.cpu().numpy()) + + + im = np.vstack(fakeList) + imE = np.vstack(fE_list) + imH = np.vstack(fH_list) + + return im, imE, imH + + +def fid_scan_rECAL(showers, eph_start, eph_end): + + ngf = 32 + nz = 100 + batch_size = 1000 + device = torch.device("cuda") + ## LOAD REAL ECAL DATA + f50 = h5py.File('/eos/user/e/eneren/scratch/50GeV75k.hdf5', 'r') + ##### + + ## LOAD HCAL GENERATOR + mGenH = Hcal_ecalEMB(ngf, 32, nz, emb_size=32).to(device) + mGenH = nn.parallel.DataParallel(mGenH) + expH = 'wganHCAL50GeV_v2' + + Tensor = torch.cuda.FloatTensor + eph_list = list(range(eph_start,eph_end,1)) + + esum_mean = [] + esum_down = [] + esum_up = [] + for eph in eph_list: + gen_checkpointHCAL = torch.load('/eos/user/e/eneren/experiments/' + expH + "_generator_"+ str(eph) + ".pt", map_location=torch.device('cuda')) + mGenH.load_state_dict(gen_checkpointHCAL['model_state_dict']) + mGenH.eval() + + print ("Epoch: " , eph) + jsd = [] + for Etrue in [50]: + + ##ECAL real + ##ECAL REAL + s50E = f50['ecal/layers'][:1000] + ecal_shower = torch.from_numpy(s50E).float() + ecal_shower = ecal_shower.unsqueeze(1) + ecal_shower = ecal_shower.cuda() + E = torch.from_numpy(np.random.uniform(Etrue, Etrue, (batch_size,1))).float() + #### + #### + + ## HCAL generate + zH = Variable(Tensor(np.random.uniform(-1, 1, (batch_size, nz)))) + hcal_shower = mGenH(zH, E, ecal_shower).detach() + + ecal_shower = ecal_shower.squeeze(1) + comb = torch.cat((ecal_shower, hcal_shower),1) + + esumFake = B.getTotE(comb.cpu().numpy(), 30, 30, 78) + esumReal = showers[str(Etrue)+'F'] + + + + jsd.append(B.jsdHist(esumReal, esumFake, 50, 0, 2500, eph, debug=False)) + + #m = (jsd[0] + jsd[1] + jsd[2]) / 3.0 + m = jsd[0] + esum_mean.append(m) + esum_down.append(m - min(jsd)) + esum_up.append(max(jsd) - m) + + + print ("Epoch: ", jsd) + + + # Plotting + # Energy sum with average and spread + plt.figure(figsize=(12,4), facecolor='none', dpi=400) + plt.scatter(eph_list, esum_mean, marker = 'x', color='red', label='Average Energy sum') + #plt.scatter(epoch_list, epoch_matrix_median[:,0], marker = '+', color='blue', label='Median Energy sum area difference') + plt.errorbar(eph_list, esum_mean, yerr=[esum_down, esum_up], color='black', label='Min-Max interval', linestyle='None') + #plt.errorbar(epoch_list, epoch_matrix_mean[:,0], yerr=epoch_matrix_std[:,0], color='green', label='Standard deviation', linestyle='None', capsize=3.0) + + + plt.legend(loc='upper right', fontsize=10) + plt.xlabel('epoch', fontsize=14) + plt.ylabel('JS difference', fontsize=16) + plt.ylim(0.1,1.0) + + plt.savefig('/eos/user/e/eneren/plots/sweep_min_max__'+str(eph_start)+'__'+str(eph_end)+'.png') \ No newline at end of file diff --git a/interactive/physics/basics.py b/interactive/physics/basics.py new file mode 100644 index 0000000000000000000000000000000000000000..5e65ee4a75732956230e73e88bfacdfcc770cac6 --- /dev/null +++ b/interactive/physics/basics.py @@ -0,0 +1,89 @@ +import numpy as np +import matplotlib.pyplot as plt +import scipy.spatial.distance as dist + +def getTotE(data, xbins, ybins, layers): + data = np.reshape(data,[-1, layers*xbins*ybins]) + etot_arr = np.sum(data, axis=(1)) + return etot_arr + + +def getOcc(data, xbins, ybins, layers): + data = np.reshape(data,[-1, layers*xbins*ybins]) + occ_arr = (data > 0.0).sum(axis=(1)) + return occ_arr + + +def getHitE(data, xbins, ybins, layers): + ehit_arr = np.reshape(data,[data.shape[0]*xbins*ybins*layers]) + ehit_arr = ehit_arr[ehit_arr != 0.0] + return ehit_arr + +# calculates the center of gravity (as in CALICE) (0st moment) +def get0Moment(x): + n, l = x.shape + tiles = np.tile(np.arange(l), (n,1)) + y = x * tiles + y = y.sum(1) + y = y/x.sum(1) + return y + +def getLongProfile(data, xbins, ybins, layers): + data = np.reshape(data,[-1, layers, xbins*ybins]) + etot_arr = np.sum(data, axis=(2)) + return etot_arr + +def getRadialDistribution(data, xbins, ybins, layers): + current = np.reshape(data,[-1, layers, xbins,ybins]) + current_sum = np.sum(current, axis=(0,1)) + + r_list=[] + phi_list=[] + e_list=[] + n_cent_x = (xbins-1)/2.0 + n_cent_y = (ybins-1)/2.0 + + for n_x in np.arange(0, xbins): + for n_y in np.arange(0, ybins): + if current_sum[n_x,n_y] != 0.0: + r = np.sqrt((n_x - n_cent_x)**2 + (n_y - n_cent_y)**2) + r_list.append(r) + phi = np.arctan((n_x - n_cent_x)/(n_y - n_cent_y)) + phi_list.append(phi) + e_list.append(current_sum[n_x,n_y]) + + r_arr = np.asarray(r_list) + phi_arr = np.asarray(phi_list) + e_arr = np.asarray(e_list) + + return r_arr, phi_arr, e_arr + + +def jsdHist(data_real, data_fake, nbins, minE, maxE, eph, debug=False): + + figSE = plt.figure(figsize=(6,6*0.77/0.67)) + axSE = figSE.add_subplot(1,1,1) + + + pSEa = axSE.hist(data_real, bins=nbins, + weights=np.ones_like(data_real)/(float(len(data_real))), + histtype='step', color='black', + range=[minE, maxE]) + pSEb = axSE.hist(data_fake, bins=nbins, + weights=np.ones_like(data_fake)/(float(len(data_fake))), + histtype='step', color='red', + range=[minE, maxE]) + + frq1 = pSEa[0] + frq2 = pSEb[0] + + JSD = dist.jensenshannon(frq1, frq2) + + if (debug): + plt.savefig('./jsd/esum_'+str(eph)+'.png') + + plt.close() + + if len(frq1) != len(frq2): + print('ERROR JSD: Histogram bins are not matching!!') + return JSD \ No newline at end of file diff --git a/interactive/physics/plotting.py b/interactive/physics/plotting.py new file mode 100644 index 0000000000000000000000000000000000000000..cd1ec55159ab773abfee8cca7056305c8c670525 --- /dev/null +++ b/interactive/physics/plotting.py @@ -0,0 +1,195 @@ +import matplotlib.pyplot as plt +import matplotlib as mpl +import matplotlib.patches as mpatches +import numpy as np +from matplotlib.colors import LogNorm + +font = {'family' : 'serif', + 'size' : 18} +mpl.rc('font', **font) +plt.style.use('classic') +mpl.rc('font', **font) + + +def plot_esum(real, fake, nbins, minE, maxE, ymax, name): + + + figSE = plt.figure(figsize=(6,6*0.77/0.67)) + axSE = figSE.add_subplot(1,1,1) + + + pSEa = axSE.hist(real, bins=nbins, + histtype='stepfilled', color='lightgrey', + range=[minE, maxE]) + pSEb = axSE.hist(fake, bins=nbins, + histtype='step', color='red', + range=[minE, maxE]) + + + + axSE.text(0.60, 0.87, 'GEANT 4', horizontalalignment='left',verticalalignment='top', + transform=axSE.transAxes, color = 'grey') + + axSE.text(0.60, 0.80, 'WGAN', horizontalalignment='left',verticalalignment='top', + transform=axSE.transAxes, color = 'red') + + axSE.text(0.6, + 0.95, + '50GeV', horizontalalignment='left',verticalalignment='top', + transform=axSE.transAxes) + + + figSE.patch.set_facecolor('white') + + axSE.set_xlim([0, maxE]) + axSE.set_ylim([0, ymax]) + axSE.set_xlabel("MeV", family='serif') + + plt.savefig('./esum'+str(name)+'.png') + + + +def plt_Profile(data_real, data_fake, model_title='ML Model', + model_title2='ML Model2', save_title='ML_model', + number=1, numberf=1, numberf2=1, + save_fig_name="_long_E_dist", y_title = 'layer'): + + figSpnE = plt.figure(figsize=(6,6)) + axSpnE = figSpnE.add_subplot(1,1,1) + lightblue = (0.1, 0.1, 0.9, 0.3) + n_layers = data_real.shape[1] + hits = np.arange(0, n_layers)+0.5 + + + pSpnEa = axSpnE.hist(hits, bins=n_layers, range=[0,n_layers], density=None, + weights=np.mean(data_real, 0), edgecolor='grey', + label = "orig", linewidth=1, color='lightgrey', + histtype='stepfilled') + + #axSpnE.plot((0.42, 0.48),(0.87-0.02, 0.87-0.02),linewidth=1, + # transform=axSpnE.transAxes, color = 'grey') + axSpnE.text(0.60, 0.87, 'GEANT 4', horizontalalignment='left',verticalalignment='top', + transform=axSpnE.transAxes, color = 'grey') + + + + pSpnEb = axSpnE.hist(hits, bins=pSpnEa[1], range=None, density=None, + weights=np.mean(data_fake, 0), edgecolor='red', + label = "orig", linewidth=1, + histtype='step') + + #axSpnE.plot((0.42, 0.42),(0.87-0.02, 0.87-0.02), + # transform=axSpnE.transAxes, color = 'red') + axSpnE.text(0.60, 0.80, 'WGAN', horizontalalignment='left',verticalalignment='top', + transform=axSpnE.transAxes, color = 'red') + + + axSpnE.set_xlabel(y_title, family='serif') + axSpnE.set_ylabel('energy [MeV]', family='serif') + axSpnE.set_xlim([0, n_layers+1.0]) + axSpnE.set_ylim([1, 100]) + #axSpnE.xaxis.set_ticks([0.5,1.0,1.5,2.0]) + + #axSpnE.xaxis.set_minor_locator(MultipleLocator(1)) + #axSpnE.xaxis.set_major_locator(MultipleLocator(5)) + + axSpnE.text(0.6, + 0.95, + '50GeV', horizontalalignment='left',verticalalignment='top', + transform=axSpnE.transAxes) + + plt.subplots_adjust(left=0.18, right=0.95, top=0.95, bottom=0.18) + plt.yscale('log') + figSpnE.patch.set_facecolor('white') + + plt.savefig('./plot_' + save_title + save_fig_name+ ".png") + + +def plt_nhits(data_real, data_fake, model_title='ML Model', model_title2='ML Model 2', save_title=' '): + #figOcc = plt.figure(figsize=(6,6)) + figOcc = plt.figure(figsize=(6,6*0.77/0.67)) + + axOcc = figOcc.add_subplot(1,1,1) + lightblue = (0.1, 0.1, 0.9, 0.3) + + xmin = 0 + xmax = 1000 + nbins= 50 + ymax = 0.15 + ymin = 0 + + + + pOcca = axOcc.hist(data_real, bins=nbins, range=[xmin,xmax], density=None, + weights=np.ones_like(data_real)/(float(len(data_real))), edgecolor='black', linewidth=1, + color='lightgrey', + histtype='stepfilled') + pOccb = axOcc.hist(data_fake, bins=pOcca[1], range=None, density=None, + weights=np.ones_like(data_fake)/(float(len(data_fake))), linewidth=1, edgecolor='red', + histtype='step') + + + axOcc.text(0.50, 0.81, model_title, horizontalalignment='left',verticalalignment='top', + transform=axOcc.transAxes, color = 'red') + axOcc.text(0.50, 0.87, 'GEANT 4', horizontalalignment='left',verticalalignment='top', + transform=axOcc.transAxes, color = 'grey') + + + + + + axOcc.set_xlabel('number of hits', family='serif') + axOcc.set_ylabel('a.u.', family='serif') + axOcc.set_xlim([xmin, xmax]) + axOcc.set_ylim([ymin, ymax]) + axOcc.text(0.5, 0.95,save_title, horizontalalignment='left',verticalalignment='top', + transform=axOcc.transAxes) + + #axOcc.xaxis.set_minor_locator(MultipleLocator(50)) + #axOcc.xaxis.set_major_locator(MultipleLocator(200)) + + + #plt.subplots_adjust(left=0.18, right=0.95, top=0.95, bottom=0.18) + plt.subplots_adjust(left=0.18, right=0.95, top=0.85, bottom=0.18) + figOcc.patch.set_facecolor('white') + + figOcc.savefig('./plots_' + save_title+"_nhits.png") + + +def esum_2D(ecal, hcal, fake_ecal, fake_hcal, nbinsX, nbinsY, name): + + + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(25, 6)) + + vmaxH = hcal.max() + vminH = hcal.min() + + i1 = ax1.hist2d(hcal, ecal,bins=(nbinsX, nbinsY), + range = [[0,1500], [0,1500]], norm=LogNorm(), vmax=vmaxH, vmin = vminH, + cmap=plt.cm.Reds) + + + i2 = ax2.hist2d(fake_hcal, fake_ecal,bins=(nbinsX, nbinsY), + range = [[0,1500], [0,1500]], norm=LogNorm(), vmax = vmaxH, vmin = vminH, + cmap=plt.cm.Reds) + + + ax1.set_xlabel('HCAL E-Sum [MeV]', fontsize=18) + ax1.set_ylabel('ECAL E-Sum [MeV]', fontsize=18) + ax1.set_title('Real') + + ax2.set_xlabel('HCAL E-Sum [MeV]', fontsize=18) + ax2.set_ylabel('ECAL E-Sum [MeV]', fontsize=18) + ax2.set_title('Fake') + + fig.patch.set_facecolor('white') + fig.colorbar(i1[3], ax=ax1) + fig.colorbar(i2[3], ax=ax2) + + plt.savefig('./esum2D'+str(name)+'.png') + + + + + + \ No newline at end of file diff --git a/interactive/random_batch_sampling.ipynb b/interactive/random_batch_sampling.ipynb deleted file mode 100644 index 2fa621dbee691d50ed055250bb828be628533bd7..0000000000000000000000000000000000000000 --- a/interactive/random_batch_sampling.ipynb +++ /dev/null @@ -1,206 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "from torch.utils.data import Dataset, DataLoader, Sampler, BatchSampler\n", - "import os\n", - "import h5py\n", - "from torch.utils import data\n", - "\n", - "\n", - "class HDF5Dataset(data.Dataset):\n", - " def __init__(self, file_path, train_size, transform=None):\n", - " super().__init__()\n", - " self.file_path = file_path\n", - " self.transform = transform\n", - " self.hdf5file = h5py.File(self.file_path, 'r')\n", - " \n", - " if train_size > self.hdf5file['ecal']['layers'].shape[0]-1:\n", - " self.train_size = self.hdf5file['ecal']['layers'].shape[0]-1\n", - " else:\n", - " self.train_size = train_size\n", - " \n", - " \n", - " def __len__(self):\n", - " return self.hdf5file['ecal']['layers'][0:self.train_size].shape[0]\n", - " \n", - " def __getitem__(self, index):\n", - " # get data\n", - " x = self.get_data(index)\n", - " if self.transform:\n", - " x = torch.from_numpy(self.transform(x)).float()\n", - " else:\n", - " x = torch.from_numpy(x).float()\n", - " e = torch.from_numpy(self.get_energy(index))\n", - " if torch.sum(x) != torch.sum(x): #checks for NANs\n", - " return self.__getitem__(int(np.random.rand()*self.__len__()))\n", - " else:\n", - " return x, e\n", - " \n", - " def get_data(self, i):\n", - " return self.hdf5file['ecal']['layers'][i]\n", - " \n", - " def get_energy(self, i):\n", - " return self.hdf5file['ecal']['energy'][i]\n", - "\n", - " \n", - " \n", - "class RandomBatchSampler(Sampler):\n", - " \"\"\"Sampling class to create random sequential batches from a given dataset\n", - " E.g. if data is [1,2,3,4] with bs=2. Then first batch, [[1,2], [3,4]] then shuffle batches -> [[3,4],[1,2]]\n", - " This is useful for cases when you are interested in 'weak shuffling'\n", - " :param dataset: dataset you want to batch\n", - " :type dataset: torch.utils.data.Dataset\n", - " :param batch_size: batch size\n", - " :type batch_size: int\n", - " :returns: generator object of shuffled batch indices\n", - " \"\"\"\n", - " def __init__(self, dataset, batch_size):\n", - " self.batch_size = batch_size\n", - " self.dataset_length = len(dataset)\n", - " self.n_batches = self.dataset_length / self.batch_size\n", - " self.batch_ids = torch.randperm(int(self.n_batches))\n", - "\n", - " def __len__(self):\n", - " return self.batch_size\n", - "\n", - " def __iter__(self):\n", - " for id in self.batch_ids:\n", - " idx = torch.arange(id * self.batch_size, (id + 1) * self.batch_size)\n", - " for index in idx:\n", - " yield int(index)\n", - " if int(self.n_batches) < self.n_batches:\n", - " idx = torch.arange(int(self.n_batches) * self.batch_size, self.dataset_length)\n", - " for index in idx:\n", - " yield int(index)\n", - " \n", - " \n", - "def fast_loader(dataset, batch_size=32, drop_last=False, transforms=None):\n", - " \"\"\"Implements fast loading by taking advantage of .h5 dataset\n", - " The .h5 dataset has a speed bottleneck that scales (roughly) linearly with the number\n", - " of calls made to it. This is because when queries are made to it, a search is made to find\n", - " the data item at that index. However, once the start index has been found, taking the next items\n", - " does not require any more significant computation. So indexing data[start_index: start_index+batch_size]\n", - " is almost the same as just data[start_index]. The fast loading scheme takes advantage of this. However,\n", - " because the goal is NOT to load the entirety of the data in memory at once, weak shuffling is used instead of\n", - " strong shuffling.\n", - " :param dataset: a dataset that loads data from .h5 files\n", - " :type dataset: torch.utils.data.Dataset\n", - " :param batch_size: size of data to batch\n", - " :type batch_size: int\n", - " :param drop_last: flag to indicate if last batch will be dropped (if size < batch_size)\n", - " :type drop_last: bool\n", - " :returns: dataloading that queries from data using shuffled batches\n", - " :rtype: torch.utils.data.DataLoader\n", - " \"\"\"\n", - " return DataLoader(\n", - " dataset, batch_size=None, # must be disabled when using samplers\n", - " sampler=BatchSampler(RandomBatchSampler(dataset, batch_size), batch_size=batch_size, drop_last=drop_last)\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "dataset = HDF5Dataset('/eos/user/e/eneren/run_prod3k/validation3k.hdf5', transform=None, train_size=3000)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "train_loader = fast_loader(dataset, batch_size=500)" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0 torch.Size([500, 30, 30, 30]) torch.Size([500, 1])\n", - "1 torch.Size([500, 30, 30, 30]) torch.Size([500, 1])\n", - "2 torch.Size([500, 30, 30, 30]) torch.Size([500, 1])\n", - "3 torch.Size([500, 30, 30, 30]) torch.Size([500, 1])\n", - "4 torch.Size([500, 30, 30, 30]) torch.Size([500, 1])\n", - "5 torch.Size([499, 30, 30, 30]) torch.Size([499, 1])\n" - ] - } - ], - "source": [ - "for batch_idx, (data, energy) in enumerate(train_loader):\n", - " print (batch_idx, data.shape, energy.shape)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "kubeflow_notebook": { - "autosnapshot": false, - "docker_image": "gitlab-registry.cern.ch/ai-ml/kubeflow_images/pytorch-notebook-gpu-1.8.1:v0.6.1-30", - "experiment": { - "id": "", - "name": "" - }, - "experiment_name": "", - "katib_metadata": { - "algorithm": { - "algorithmName": "grid" - }, - "maxFailedTrialCount": 3, - "maxTrialCount": 12, - "objective": { - "objectiveMetricName": "", - "type": "minimize" - }, - "parallelTrialCount": 3, - "parameters": [] - }, - "katib_run": false, - "pipeline_description": "", - "pipeline_name": "", - "snapshot_volumes": false, - "steps_defaults": [], - "volume_access_mode": "rwm", - "volumes": [] - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/models/criticFull.py b/models/criticFull.py index 16333c33a13a65beeda8b45ec3c5cfbdd1108150..382112db6c4b11eaa0321577de21e9ebe8a8c625 100644 --- a/models/criticFull.py +++ b/models/criticFull.py @@ -5,13 +5,13 @@ import torch.nn.functional as F class CriticEMB(nn.Module): - def __init__(self, isize_1=30, isize_2=48, nc=2, ndf=64): + def __init__(self, isize_1=30, isize_2=48, nc=2, ndf=64, size_embed=64): super(CriticEMB, self).__init__() self.ndf = ndf self.isize_1 = isize_1 self.isize_2 = isize_2 self.nc = nc - self.size_embed = 16 + self.size_embed = size_embed self.conv1_bias = False @@ -42,14 +42,14 @@ class CriticEMB(nn.Module): #self.conv_HCAL_3 = torch.nn.Conv3d(ndf, ndf, kernel_size=4, stride=(2,2,2), padding=(1,1,1), bias=False) - self.conv_lin_ECAL = torch.nn.Linear(7*7*7*ndf, 64) - self.conv_lin_HCAL = torch.nn.Linear(7*7*7*ndf, 64) + self.conv_lin_ECAL = torch.nn.Linear(7*7*7*ndf, size_embed) + self.conv_lin_HCAL = torch.nn.Linear(7*7*7*ndf, size_embed) - self.econd_lin = torch.nn.Linear(1, 64) # label embedding + self.econd_lin = torch.nn.Linear(1, size_embed) # label embedding - self.fc1 = torch.nn.Linear(64*3, 128) # 3 components after cat - self.fc2 = torch.nn.Linear(128, 64) - self.fc3 = torch.nn.Linear(64, 1) + self.fc1 = torch.nn.Linear(size_embed*3, size_embed*2) # 3 components after cat + self.fc2 = torch.nn.Linear(size_embed*2, size_embed) + self.fc3 = torch.nn.Linear(size_embed, 1) def forward(self, img_ECAL, img_HCAL, E_true): diff --git a/models/generatorFull.py b/models/generatorFull.py index 029b553f80091ce3edc29d105e183b44da07f409..d8c2d7d6a58da0ec2c8ded4130b528db0b97e9f2 100644 --- a/models/generatorFull.py +++ b/models/generatorFull.py @@ -9,7 +9,7 @@ class Hcal_ecalEMB(nn.Module): """ generator component of WGAN """ - def __init__(self, ngf, ndf, nz, emb_size): + def __init__(self, ngf, ndf, nz, emb_size=16): super(Hcal_ecalEMB, self).__init__() diff --git a/pytorch_job_wganHCAL_nccl.yaml b/pytorch_job_wganHCAL_nccl.yaml index fcdf0407205c813895c65c8207c729516fb1932b..b3618b45dda216c69c2dcead56605bab479b634e 100644 --- a/pytorch_job_wganHCAL_nccl.yaml +++ b/pytorch_job_wganHCAL_nccl.yaml @@ -1,7 +1,7 @@ apiVersion: "kubeflow.org/v1" kind: "PyTorchJob" metadata: - name: "pytorch-dist-wganHCAL-nccl" + name: "pytorch-dist-wganhcal-nccl" spec: pytorchReplicaSpecs: Master: @@ -9,42 +9,23 @@ spec: restartPolicy: OnFailure template: metadata: + labels: + mount-kerberos-secret: "true" + mount-eos: "true" + mount-nvidia-driver: "true" annotations: sidecar.istio.io/inject: "false" spec: - volumes: - - name: eos - hostPath: - path: /var/eos - - name: krb-secret-vol - secret: - secretName: krb-secret - - name: nvidia-driver - hostPath: - path: /opt/nvidia-driver - type: "" containers: - name: pytorch - image: gitlab-registry.cern.ch/eneren/pytorchjob:ddp + image: gitlab-registry.cern.ch/eneren/pytorchjob:ddpv3 imagePullPolicy: Always env: - - name: PATH - value: /opt/conda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/nvidia-driver/bin - - name: LD_LIBRARY_PATH - value: /opt/nvidia-driver/lib64 - name: PYTHONUNBUFFERED value: "1" command: [sh, -c] args: - - cp /secret/krb-secret-vol/krb5cc_1000 /tmp/krb5cc_0 && chmod 600 /tmp/krb5cc_0 - && python -u wganHCAL.py --backend nccl --epochs 50 --exp wganHCALv1 --lrCrit 0.0001 --lrGen 0.00001; - volumeMounts: - - name: eos - mountPath: /eos - - name: krb-secret-vol - mountPath: "/secret/krb-secret-vol" - - name: nvidia-driver - mountPath: /opt/nvidia-driver + - python -u wganHCAL.py --backend nccl --epochs 50 --exp wganHCALv1 --lrCrit 0.00001 --lrGen 0.00001; resources: limits: nvidia.com/gpu: 1 @@ -53,42 +34,23 @@ spec: restartPolicy: OnFailure template: metadata: + labels: + mount-kerberos-secret: "true" + mount-eos: "true" + mount-nvidia-driver: "true" annotations: sidecar.istio.io/inject: "false" spec: - volumes: - - name: eos - hostPath: - path: /var/eos - - name: krb-secret-vol - secret: - secretName: krb-secret - - name: nvidia-driver - hostPath: - path: /opt/nvidia-driver - type: "" containers: - name: pytorch - image: gitlab-registry.cern.ch/eneren/pytorchjob:ddp + image: gitlab-registry.cern.ch/eneren/pytorchjob:ddpv3 imagePullPolicy: Always env: - - name: PATH - value: /opt/conda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/nvidia-driver/bin - - name: LD_LIBRARY_PATH - value: /opt/nvidia-driver/lib64 - name: PYTHONUNBUFFERED value: "1" command: [sh, -c] args: - - cp /secret/krb-secret-vol/krb5cc_1000 /tmp/krb5cc_0 && chmod 600 /tmp/krb5cc_0 - && python -u wganHCAL.py --backend nccl --epochs 50 --exp wganHCALv1 --lrCrit 0.0001 --lrGen 0.00001; - volumeMounts: - - name: eos - mountPath: /eos - - name: krb-secret-vol - mountPath: "/secret/krb-secret-vol" - - name: nvidia-driver - mountPath: /opt/nvidia-driver + - python -u wganHCAL.py --backend nccl --epochs 50 --exp wganHCALv1 --lrCrit 0.00001 --lrGen 0.00001; resources: limits: nvidia.com/gpu: 1 diff --git a/wganHCAL.py b/wganHCAL.py index 822875f4b9792e50fac1438b9d1b44c4d9bfdf79..9835cc800dc25d2235414fba1dd116009ce7515e 100644 --- a/wganHCAL.py +++ b/wganHCAL.py @@ -21,25 +21,37 @@ sys.path.append('/opt/regressor/src') from models.generatorFull import Hcal_ecalEMB from models.data_loaderFull import HDF5Dataset from models.criticFull import CriticEMB +from models.generator import DCGAN_G -def calc_gradient_penalty(netD, real_dataECAL, real_data, fake_data, real_label, BATCH_SIZE, device, layer, xsize, ysize): +def calc_gradient_penalty(netD, real_ecal, real_hcal, fake_ecal, fake_hcal, real_label, BATCH_SIZE, device, layer, layer_hcal, xsize, ysize): - alpha = torch.rand(BATCH_SIZE, 1) - alpha = alpha.expand(BATCH_SIZE, int(real_data.nelement()/BATCH_SIZE)).contiguous() - alpha = alpha.view(BATCH_SIZE, 1, layer, xsize, ysize) - alpha = alpha.to(device) + alphaE = torch.rand(BATCH_SIZE, 1) + alphaE = alphaE.expand(BATCH_SIZE, int(real_ecal.nelement()/BATCH_SIZE)).contiguous() + alphaE = alphaE.view(BATCH_SIZE, 1, layer, xsize, ysize) + alphaE = alphaE.to(device) - fake_data = fake_data.view(BATCH_SIZE, 1, layer, xsize, ysize) - interpolates = alpha * real_data.detach() + ((1 - alpha) * fake_data.detach()) + alphaH = torch.rand(BATCH_SIZE, 1) + alphaH = alphaH.expand(BATCH_SIZE, int(real_hcal.nelement()/BATCH_SIZE)).contiguous() + alphaH = alphaH.view(BATCH_SIZE, 1, layer_hcal, xsize, ysize) + alphaH = alphaH.to(device) + + fake_hcal = fake_hcal.view(BATCH_SIZE, 1, layer_hcal, xsize, ysize) + fake_ecal = fake_ecal.view(BATCH_SIZE, 1, layer, xsize, ysize) + + interpolatesHCAL = alphaH * real_hcal.detach() + ((1 - alphaH) * fake_hcal.detach()) + interpolatesECAL = alphaE * real_ecal.detach() + ((1 - alphaE) * fake_ecal.detach()) - interpolates = interpolates.to(device) - interpolates.requires_grad_(True) - disc_interpolates = netD(real_dataECAL, interpolates.float(), real_label.float()) + interpolatesHCAL = interpolatesHCAL.to(device) + interpolatesHCAL.requires_grad_(True) + interpolatesECAL = interpolatesECAL.to(device) + interpolatesECAL.requires_grad_(True) - gradients = autograd.grad(outputs=disc_interpolates, inputs=interpolates, + disc_interpolates = netD(interpolatesECAL.float(), interpolatesHCAL.float(), real_label.float()) + + gradients = autograd.grad(outputs=disc_interpolates, inputs=[interpolatesECAL, interpolatesHCAL], grad_outputs=torch.ones(disc_interpolates.size()).to(device), create_graph=True, retain_graph=True, only_inputs=True)[0] @@ -48,39 +60,43 @@ def calc_gradient_penalty(netD, real_dataECAL, real_data, fake_data, real_label, return gradient_penalty -def train(args, aD, aG, device, train_loader, optimizer_d, optimizer_g, epoch, experiment): +def train(args, aD, aG, aGE, device, train_loader, optimizer_d, optimizer_g, epoch, experiment): ### CRITIC TRAINING aD.train() aG.eval() + aGE.eval() Tensor = torch.cuda.FloatTensor for batch_idx, (dataE, dataH, energy) in enumerate(train_loader): - real_dataECAL = dataE.to(device).unsqueeze(1).float() - - - real_dataHCAL = dataH.to(device).unsqueeze(1).float() - - - real_label = energy.to(device).float() - optimizer_d.zero_grad() - - z = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz)))) - ## Generate Fake data - fake_dataHCAL = aG(z, real_label, real_dataECAL).detach() ## 48 x 30 x 30 + ## Get Real data + real_dataECAL = dataE.to(device).unsqueeze(1).float() + real_dataHCAL = dataH.to(device).unsqueeze(1).float() + label = energy.to(device).float() + ### + + ## Generate Fake ECAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=False) + fake_ecal = aGE(zE, label.view(-1, 1, 1, 1, 1)).detach() + fake_ecal = fake_ecal.unsqueeze(1) + + ## Generate Fake HCAL + z = zE.view(args.batch_size, args.nz) + #z = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz))), requires_grad=False) + fake_dataHCAL = aG(z, label, fake_ecal).detach() ## 48 x 30 x 30 ## Critic fwd pass on Real - disc_real = aD(real_dataECAL, real_dataHCAL, real_label) + disc_real = aD(real_dataECAL, real_dataHCAL, label) ## Calculate Gradient Penalty Term - gradient_penalty = calc_gradient_penalty(aD, real_dataECAL, real_dataHCAL, fake_dataHCAL, real_label, args.batch_size, device, layer=48, xsize=30, ysize=30) + gradient_penalty = calc_gradient_penalty(aD, real_dataECAL, real_dataHCAL, fake_ecal, fake_dataHCAL, label, args.batch_size, device, layer=30, layer_hcal=48, xsize=30, ysize=30) - ## Critic fwd pass on Fake - disc_fake = aD(real_dataECAL, fake_dataHCAL.unsqueeze(1), real_label) + ## Critic fwd pass on Fake HCAL + disc_fake = aD(fake_ecal, fake_dataHCAL.unsqueeze(1), label) ## wasserstein-1 distace @@ -108,18 +124,26 @@ def train(args, aD, aG, device, train_loader, optimizer_d, optimizer_g, epoch, e ## GENERATOR TRAINING aD.eval() aG.train() - + aGE.train() + #print("Generator training started") optimizer_g.zero_grad() - + + ## Generate Fake ECAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=True) + fake_ecal = aGE(zE, label.view(-1, 1, 1, 1, 1)) + fake_ecal = fake_ecal.unsqueeze(1) + #### + + z = zE.view(args.batch_size, args.nz) + #z = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz))), requires_grad=True) ## generate fake data out of noise - fake_dataHCALG = aG(z, real_label, real_dataECAL) - + fake_dataHCALG = aG(z, label, fake_ecal) ## Total loss function for generator - gen_cost = aD(real_dataECAL, fake_dataHCALG.unsqueeze(1), real_label) + gen_cost = aD(fake_ecal, fake_dataHCALG.unsqueeze(1), label) g_cost = -torch.mean(gen_cost) g_cost.backward() optimizer_g.step() @@ -150,8 +174,8 @@ def parse_args(): parser.add_argument('--kappa', type=float, default=0.001, metavar='N', help='weight of label conditioning (default: 0.001)') - parser.add_argument('--ndf', type=int, default=64, metavar='N', - help='n-feature of critic (default: 64)') + parser.add_argument('--ndf', type=int, default=32, metavar='N', + help='n-feature of critic (default: 32)') parser.add_argument('--ngf', type=int, default=32, metavar='N', help='n-feature of generator (default: 32)') @@ -206,7 +230,7 @@ def parse_args(): # postprocess args - args.device = f'cuda:{args.local_rank}' # PytorchJob/launch.py + args.device = 'cuda:{}'.format(args.local_rank) # PytorchJob/launch.py args.batch_size = max(args.batch_size, args.world_size * 2) # min valid batchsize return args @@ -262,25 +286,33 @@ def run(args): train_loader = DataLoader(dataset, batch_size=args.batch_size, num_workers=args.nworkers, shuffle=True, drop_last=True, pin_memory=False) - + ## HCAL Generator and critic mCrit = CriticEMB().to(device) - mGen = Hcal_ecalEMB(args.ngf, 32, args.nz, emb_size=32).to(device) + mGen = Hcal_ecalEMB(args.ngf, 32, args.nz).to(device) + + ## ECAL GENERATOR + mGenE = DCGAN_G(args.ngf, args.nz).to(device) - if args.world_size > 1: Distributor = nn.parallel.DistributedDataParallel if use_cuda \ else nn.parallel.DistributedDataParallelCPU mCrit = Distributor(mCrit, device_ids=[args.local_rank], output_device=args.local_rank ) mGen = Distributor(mGen, device_ids=[args.local_rank], output_device=args.local_rank) + mGenE = Distributor(mGenE, device_ids=[args.local_rank], output_device=args.local_rank) else: mGen = nn.parallel.DataParallel(mGen) mCrit = nn.parallel.DataParallel(mCrit) + mGenE = nn.parallel.DataParallel(mGenE) - optimizerG = optim.Adam(mGen.parameters(), lr=args.lrGen, betas=(0.5, 0.9)) + optimizerG = optim.Adam(list(mGen.parameters())+list(mGenE.parameters()), lr=args.lrGen, betas=(0.5, 0.9)) optimizerD = optim.Adam(mCrit.parameters(), lr=args.lrCrit, betas=(0.5, 0.9)) + + gen_checkpointECAL = torch.load("/eos/user/e/eneren/experiments/wganv1_generator_694.pt", map_location=torch.device('cuda')) + mGenE.load_state_dict(gen_checkpointECAL['model_state_dict']) + if (args.chpt): critic_checkpoint = torch.load(args.chpt_base + args.exp + "_critic_"+ str(args.chpt_eph) + ".pt") gen_checkpoint = torch.load(args.chpt_base + args.exp + "_generator_"+ str(args.chpt_eph) + ".pt") @@ -307,9 +339,10 @@ def run(args): if args.world_size > 1: train_loader.sampler.set_epoch(epoch) - train(args, mCrit, mGen, device, train_loader, optimizerD, optimizerG, epoch, experiment) + train(args, mCrit, mGen, mGenE, device, train_loader, optimizerD, optimizerG, epoch, experiment) if args.rank == 0: gPATH = args.chpt_base + args.exp + "_generator_"+ str(epoch) + ".pt" + ePATH = args.chpt_base + args.exp + "_generatorECAL_"+ str(epoch) + ".pt" cPATH = args.chpt_base + args.exp + "_critic_"+ str(epoch) + ".pt" torch.save({ 'epoch': epoch, @@ -323,6 +356,12 @@ def run(args): 'optimizer_state_dict': optimizerD.state_dict() }, cPATH) + torch.save({ + 'epoch': epoch, + 'model_state_dict': mGenE.state_dict(), + 'optimizer_state_dict': optimizerG.state_dict() + }, ePATH) + print ("end training") diff --git a/wgan_ECAL_HCAL_2crit.py b/wgan_ECAL_HCAL_2crit.py new file mode 100644 index 0000000000000000000000000000000000000000..57f94655ac8760d65a34e9f8bd1a0fc816eee565 --- /dev/null +++ b/wgan_ECAL_HCAL_2crit.py @@ -0,0 +1,506 @@ +from __future__ import print_function +from comet_ml import Experiment +import argparse +import os, sys +import numpy as np +import torch +import torch.distributed as dist +import torch.nn as nn +import torch.optim as optim +from torch import autograd +from torch.utils.data.distributed import DistributedSampler +from torch.utils.data import DataLoader +from torch.autograd import Variable + +os.environ['MKL_THREADING_LAYER'] = 'GNU' + +torch.autograd.set_detect_anomaly(True) + +sys.path.append('/opt/regressor/src') + +from models.generatorFull import Hcal_ecalEMB +from models.data_loaderFull import HDF5Dataset +from models.criticFull import CriticEMB +from models.generator import DCGAN_G +from models.criticRes import generate_model + +def calc_gradient_penalty_ECAL(netD, real_data, fake_data, real_label, BATCH_SIZE, device, layer, xsize, ysize): + + alpha = torch.rand(BATCH_SIZE, 1) + alpha = alpha.expand(BATCH_SIZE, int(real_data.nelement()/BATCH_SIZE)).contiguous() + alpha = alpha.view(BATCH_SIZE, 1, layer, xsize, ysize) + alpha = alpha.to(device) + + + fake_data = fake_data.view(BATCH_SIZE, 1, layer, xsize, ysize) + interpolates = alpha * real_data.detach() + ((1 - alpha) * fake_data.detach()) + + interpolates = interpolates.to(device) + interpolates.requires_grad_(True) + + disc_interpolates = netD(interpolates.float(), real_label.float()) + + + gradients = autograd.grad(outputs=disc_interpolates, inputs=interpolates, + grad_outputs=torch.ones(disc_interpolates.size()).to(device), + create_graph=True, retain_graph=True, only_inputs=True)[0] + + gradients = gradients.view(gradients.size(0), -1) + gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean() + return gradient_penalty + + +#### Need a separate grdaient penalty term for HCAL critic vs global critic? Retainnig graph might cause error.... + +def calc_gradient_penalty_ECAL_HCAL(netD, real_ecal, real_hcal, fake_ecal, fake_hcal, real_label, BATCH_SIZE, device, layer, layer_hcal, xsize, ysize): + + alphaE = torch.rand(BATCH_SIZE, 1) + alphaE = alphaE.expand(BATCH_SIZE, int(real_ecal.nelement()/BATCH_SIZE)).contiguous() + alphaE = alphaE.view(BATCH_SIZE, 1, layer, xsize, ysize) + alphaE = alphaE.to(device) + + + alphaH = torch.rand(BATCH_SIZE, 1) + alphaH = alphaH.expand(BATCH_SIZE, int(real_hcal.nelement()/BATCH_SIZE)).contiguous() + alphaH = alphaH.view(BATCH_SIZE, 1, layer_hcal, xsize, ysize) + alphaH = alphaH.to(device) + + fake_hcal = fake_hcal.view(BATCH_SIZE, 1, layer_hcal, xsize, ysize) + fake_ecal = fake_ecal.view(BATCH_SIZE, 1, layer, xsize, ysize) + + interpolatesHCAL = alphaH * real_hcal.detach() + ((1 - alphaH) * fake_hcal.detach()) + interpolatesECAL = alphaE * real_ecal.detach() + ((1 - alphaE) * fake_ecal.detach()) + + + interpolatesHCAL = interpolatesHCAL.to(device) + interpolatesHCAL.requires_grad_(True) + + interpolatesECAL = interpolatesECAL.to(device) + interpolatesECAL.requires_grad_(True) + + disc_interpolates = netD(interpolatesECAL.float(), interpolatesHCAL.float(), real_label.float()) + + gradients = autograd.grad(outputs=disc_interpolates, inputs=[interpolatesECAL, interpolatesHCAL], + grad_outputs=torch.ones(disc_interpolates.size()).to(device), + create_graph=True, retain_graph=True, only_inputs=True)[0] + + gradients = gradients.view(gradients.size(0), -1) + gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean() + return gradient_penalty + + +def train(args, aDE, aDH, aGE, aGH, device, train_loader, optimizer_d_E, optimizer_d_H_E, optimizer_g_E, optimizer_g_H_E, epoch, experiment): + + ### CRITIC TRAINING + aDE.train() + aDH.train() + aGH.eval() + aGE.eval() + + Tensor = torch.cuda.FloatTensor + + for batch_idx, (dataE, dataH, energy) in enumerate(train_loader): + + # ECAL critic + optimizer_d_E.zero_grad() + + ## Get Real data + real_dataECAL = dataE.to(device).unsqueeze(1).float() + label = energy.to(device).float() + ### + + ## Generate Fake ECAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=False) + fake_ecal_gen = aGE(zE, label.view(-1, 1, 1, 1, 1)).detach() + fake_ecal_gen = fake_ecal_gen.unsqueeze(1) + + fake_ecal = fake_ecal_gen.clone().detach() + + ## Critic fwd pass on Real + disc_real_E = aDE(real_dataECAL, label) + + ## Calculate Gradient Penalty Term + gradient_penalty_E = calc_gradient_penalty_ECAL(aDE, real_dataECAL, fake_ecal, label, args.batch_size, device, layer=30, xsize=30, ysize=30) + + ## Critic fwd pass on fake data + disc_fake_E = aDE(fake_ecal, label) + + + ## wasserstein-1 distace for critic + w_dist_E = torch.mean(disc_fake_E) - torch.mean(disc_real_E) + + # final disc cost + disc_cost_E = w_dist_E + args.lambd * gradient_penalty_E + + disc_cost_E.backward() + optimizer_d_E.step() + + + + # ECAL + HCAL critic + optimizer_d_H_E.zero_grad() + + ## Get Real data + real_dataECAL = dataE.to(device).unsqueeze(1).float() + real_dataHCAL = dataH.to(device).unsqueeze(1).float() + label = energy.to(device).float() + ### + + ## Get Fake ECAL + fake_ecal = fake_ecal_gen.clone().detach() + + ## Generate Fake HCAL + #z = zE.view(args.batch_size, args.nz) + z = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz))), requires_grad=False) + fake_dataHCAL = aGH(z, label, fake_ecal).detach() ## 48 x 30 x 30 + + ## Critic fwd pass on Real + disc_real_H_E = aDH(real_dataECAL, real_dataHCAL, label) + + ## Calculate Gradient Penalty Term + gradient_penalty_H_E = calc_gradient_penalty_ECAL_HCAL(aDH, real_dataECAL, real_dataHCAL, fake_ecal, fake_dataHCAL, label, args.batch_size, device, layer=30, layer_hcal=48, xsize=30, ysize=30) + + ## Critic fwd pass on fake data + disc_fake_H_E = aDH(fake_ecal, fake_dataHCAL.unsqueeze(1), label) + + ## wasserstein-1 distace for critic + w_dist_H_E = torch.mean(disc_fake_H_E) - torch.mean(disc_real_H_E) + + # final disc cost + disc_cost_H_E = w_dist_H_E + args.lambd * gradient_penalty_H_E + + disc_cost_H_E.backward() + optimizer_d_H_E.step() + + + if batch_idx % args.log_interval == 0: + print('Critic --> Train Epoch: {} [{}/{} ({:.0f}%)]\tloss={:.4f}'.format( + epoch, batch_idx * len(dataH), len(train_loader.dataset), + 100. * batch_idx / len(train_loader), disc_cost_H_E.item())) + niter = epoch * len(train_loader) + batch_idx + experiment.log_metric("L_crit_E", disc_cost_E, step=niter) + experiment.log_metric("L_crit_H_E", disc_cost_H_E, step=niter) + experiment.log_metric("gradient_pen_E", gradient_penalty_E, step=niter) + experiment.log_metric("gradient_pen_H_E", gradient_penalty_H_E, step=niter) + experiment.log_metric("Wasserstein Dist E", w_dist_E, step=niter) + experiment.log_metric("Wasserstein Dist E H", w_dist_H_E, step=niter) + experiment.log_metric("Critic Score E (Real)", torch.mean(disc_real_E), step=niter) + experiment.log_metric("Critic Score E (Fake)", torch.mean(disc_fake_E), step=niter) + experiment.log_metric("Critic Score H E (Real)", torch.mean(disc_real_H_E), step=niter) + experiment.log_metric("Critic Score H E (Fake)", torch.mean(disc_fake_H_E), step=niter) + + + + ## training generator per ncrit + if (batch_idx % args.ncrit == 0) and (batch_idx != 0): + ## GENERATOR TRAINING + aDE.eval() + aDH.eval() + aGH.train() + aGE.train() + + #print("Generator training started") + + # Optimize ECAL generator + optimizer_g_E.zero_grad() + + ## Generate Fake ECAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=True) + fake_ecal = aGE(zE, label.view(-1, 1, 1, 1, 1)) + fake_ecal = fake_ecal.unsqueeze(1) + + ## Loss function for ECAL generator + gen_E_cost = aDE(fake_ecal, label) + g_E_cost = -torch.mean(gen_E_cost) + g_E_cost.backward() + optimizer_g_E.step() + + + # Optimize HCAL generator + optimizer_g_H_E.zero_grad() + + + ## Generate Fake ECAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=True) + fake_ecal = aGE(zE, label.view(-1, 1, 1, 1, 1)) + fake_ecal = fake_ecal.unsqueeze(1) + #### + + #z = zE.view(args.batch_size, args.nz) + z = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz))), requires_grad=True) + ## generate fake data out of noise + fake_dataHCALG = aGH(z, label, fake_ecal) + + ## Total loss function for generator + gen_cost = aDH(fake_ecal, fake_dataHCALG.unsqueeze(1), label) + g_cost = -torch.mean(gen_cost) + g_cost.backward() + optimizer_g_H_E.step() + + if batch_idx % args.log_interval == 0 : + print('Generator --> Train Epoch: {} [{}/{} ({:.0f}%)]\tlossGE={:.4f} lossGH={:.4f}'.format( + epoch, batch_idx * len(dataH), len(train_loader.dataset), + 100. * batch_idx / len(train_loader), g_E_cost.item(), g_cost.item())) + niter = epoch * len(train_loader) + batch_idx + experiment.log_metric("L_Gen_E", g_E_cost, step=niter) + experiment.log_metric("L_Gen_H", g_cost, step=niter) + + +def is_distributed(): + return dist.is_available() and dist.is_initialized() + + +def parse_args(): + parser = argparse.ArgumentParser(description='WGAN training on hadron showers') + parser.add_argument('--batch-size', type=int, default=50, metavar='N', + help='input batch size for training (default: 50)') + + parser.add_argument('--nz', type=int, default=100, metavar='N', + help='latent space for generator (default: 100)') + + parser.add_argument('--lambd', type=int, default=15, metavar='N', + help='weight of gradient penalty (default: 15)') + + parser.add_argument('--kappa', type=float, default=0.001, metavar='N', + help='weight of label conditioning (default: 0.001)') + + parser.add_argument('--dres', type=int, default=34, metavar='N', + help='depth of Residual critic (default: 34)') + + parser.add_argument('--ndf', type=int, default=32, metavar='N', + help='n-feature of critic (default: 32)') + + parser.add_argument('--ngf', type=int, default=32, metavar='N', + help='n-feature of generator (default: 32)') + + parser.add_argument('--ncrit', type=int, default=10, metavar='N', + help='critic updates before generator one (default: 10)') + + parser.add_argument('--epochs', type=int, default=1, metavar='N', + help='number of epochs to train (default: 1)') + + parser.add_argument('--nworkers', type=int, default=1, metavar='N', + help='number of epochs to train (default: 1)') + + parser.add_argument('--lrCrit_H', type=float, default=0.00001, metavar='LR', + help='learning rate CriticH (default: 0.00001)') + + parser.add_argument('--lrCrit_E', type=float, default=0.00001, metavar='LR', + help='learning rate CriticE (default: 0.00001)') + + parser.add_argument('--lrGen_H_E', type=float, default=0.0001, metavar='LR', + help='learning rate Generator_H_E (default: 0.0001)') + + parser.add_argument('--lrGen_E', type=float, default=0.0001, metavar='LR', + help='learning rate GeneratorE (default: 0.0001)') + + parser.add_argument('--chpt', action='store_true', default=False, + help='continue training from a saved model') + + parser.add_argument('--chpt_base', type=str, default='/eos/user/e/eneren/experiments/', + help='continue training from a saved model') + + parser.add_argument('--exp', type=str, default='dist_wgan', + help='name of the experiment') + + parser.add_argument('--chpt_eph', type=int, default=1, + help='continue checkpoint epoch') + + parser.add_argument('--no-cuda', action='store_true', default=False, + help='disables CUDA training') + parser.add_argument('--seed', type=int, default=1, metavar='S', + help='random seed (default: 1)') + parser.add_argument('--log-interval', type=int, default=100, metavar='N', + help='how many batches to wait before logging training status') + + + if dist.is_available(): + parser.add_argument('--backend', type=str, help='Distributed backend', + choices=[dist.Backend.GLOO, dist.Backend.NCCL, dist.Backend.MPI], + default=dist.Backend.GLOO) + + parser.add_argument('--local_rank', type=int, default=0) + + args = parser.parse_args() + + + args.local_rank = int(os.environ.get('LOCAL_RANK', args.local_rank)) + args.rank = int(os.environ.get('RANK')) + args.world_size = int(os.environ.get('WORLD_SIZE')) + + + # postprocess args + args.device = 'cuda:{}'.format(args.local_rank) # PytorchJob/launch.py + args.batch_size = max(args.batch_size, + args.world_size * 2) # min valid batchsize + return args + + + +def run(args): + # Training settings + + use_cuda = not args.no_cuda and torch.cuda.is_available() + if use_cuda: + print('Using CUDA') + + + experiment = Experiment(api_key="keGmeIz4GfKlQZlOP6cit4QOi", + project_name="ecal-hcal-shower", workspace="engineren", auto_output_logging="simple") + experiment.add_tag(args.exp) + + experiment.log_parameters( + { + "batch_size" : args.batch_size, + "latent": args.nz, + "lambda": args.lambd, + "ncrit" : args.ncrit, + "resN_H": args.ndf, + "resN_E": args.dres, + "ngf": args.ngf + } + ) + + torch.manual_seed(args.seed) + + device = torch.device("cuda" if use_cuda else "cpu") + + + if args.world_size > 1: + print('Using distributed PyTorch with {} backend'.format(args.backend)) + dist.init_process_group(backend=args.backend) + + print('[init] == local rank: {}, global rank: {}, world size: {} =='.format(args.local_rank, args.rank, args.world_size)) + + + + print ("loading data") + #dataset = HDF5Dataset('/eos/user/e/eneren/scratch/40GeV40k.hdf5', transform=None, train_size=40000) + dataset = HDF5Dataset('/eos/user/e/eneren/scratch/50GeV75k.hdf5', transform=None, train_size=75000) + #dataset = HDF5Dataset('/eos/user/e/eneren/scratch/4060GeV.hdf5', transform=None, train_size=60000) + + + if args.world_size > 1: + sampler = DistributedSampler(dataset, shuffle=True) + train_loader = DataLoader(dataset, batch_size=args.batch_size, sampler=sampler, num_workers=args.nworkers, drop_last=True, pin_memory=False) + else: + train_loader = DataLoader(dataset, batch_size=args.batch_size, num_workers=args.nworkers, shuffle=True, drop_last=True, pin_memory=False) + + + ## HCAL Generator and critic + mCritH = CriticEMB().to(device) + mGenH = Hcal_ecalEMB(args.ngf, 32, args.nz).to(device) + + ## ECAL GENERATOR and critic + mGenE = DCGAN_G(args.ngf, args.nz).to(device) + mCritE = generate_model(args.dres).to(device) + + # ## Global Critic + # mCritGlob = + + if args.world_size > 1: + Distributor = nn.parallel.DistributedDataParallel if use_cuda \ + else nn.parallel.DistributedDataParallelCPU + mCritH = Distributor(mCritH, device_ids=[args.local_rank], output_device=args.local_rank ) + mGenH = Distributor(mGenH, device_ids=[args.local_rank], output_device=args.local_rank) + mGenE = Distributor(mGenE, device_ids=[args.local_rank], output_device=args.local_rank) + mCritE = Distributor(mCritE, device_ids=[args.local_rank], output_device=args.local_rank) + else: + mGenH = nn.parallel.DataParallel(mGenH) + mCritH = nn.parallel.DataParallel(mCritH) + mGenE = nn.parallel.DataParallel(mGenE) + mCritE = nn.parallel.DataParallel(mCritE) + + optimizerG_H_E = optim.Adam(list(mGenH.parameters())+list(mGenE.parameters()), lr=args.lrGen_H_E, betas=(0.5, 0.9)) + + optimizerG_E = optim.Adam(mGenE.parameters(), lr=args.lrGen_E, betas=(0.5, 0.9)) + + optimizerD_H_E = optim.Adam(mCritH.parameters(), lr=args.lrCrit_H, betas=(0.5, 0.9)) + + optimizerD_E = optim.Adam(mCritE.parameters(), lr=args.lrCrit_E, betas=(0.5, 0.9)) + + + if (args.chpt): + critic_E_checkpoint = torch.load(args.chpt_base + args.exp + "_criticE_"+ str(args.chpt_eph) + ".pt") + critic_E_H_checkpoint = torch.load(args.chpt_base + args.exp + "_criticH_"+ str(args.chpt_eph) + ".pt") + gen_E_checkpoint = torch.load(args.chpt_base + args.exp + "_generatorE_"+ str(args.chpt_eph) + ".pt") + gen_H_checkpoint = torch.load(args.chpt_base + args.exp + "_generatorH_"+ str(args.chpt_eph) + ".pt") + + mGenE.load_state_dict(gen_E_checkpoint['model_state_dict']) + optimizerG_E.load_state_dict(gen_E_checkpoint['optimizer_state_dict']) + + mGenH.load_state_dict(gen_H_checkpoint['model_state_dict']) + optimizerG_H_E.load_state_dict(gen_H_checkpoint['optimizer_state_dict']) + + mCritE.load_state_dict(critic_E_checkpoint['model_state_dict']) + optimizerD_E.load_state_dict(critic_E_checkpoint['optimizer_state_dict']) + + mCritH.load_state_dict(critic_E_H_checkpoint['model_state_dict']) + optimizerD_H_E.load_state_dict(critic_E_H_checkpoint['optimizer_state_dict']) + + eph = gen_H_checkpoint['epoch'] + + else: + eph = 0 + gen_E_checkpoint = torch.load("/eos/user/e/eneren/experiments/wganv1_generator_694.pt", map_location=torch.device('cuda')) + critic_E_checkpoint = torch.load("/eos/user/e/eneren/experiments/wganv1_critic_694.pt", map_location=torch.device('cuda')) + + mGenE.load_state_dict(gen_E_checkpoint['model_state_dict']) + optimizerG_E.load_state_dict(gen_E_checkpoint['optimizer_state_dict']) + + mCritE.load_state_dict(critic_E_checkpoint['model_state_dict']) + optimizerD_E.load_state_dict(critic_E_checkpoint['optimizer_state_dict']) + + print ("init models") + + + experiment.set_model_graph(str(mCritH), overwrite=False) + + print ("starting training...") + for epoch in range(1, args.epochs + 1): + epoch += eph + + if args.world_size > 1: + train_loader.sampler.set_epoch(epoch) + + train(args, mCritE, mCritH, mGenE, mGenH, device, train_loader, optimizerD_E, optimizerD_H_E, optimizerG_E, optimizerG_H_E, epoch, experiment) + if args.rank == 0: + gPATH = args.chpt_base + args.exp + "_generatorH_"+ str(epoch) + ".pt" + ePATH = args.chpt_base + args.exp + "_generatorE_"+ str(epoch) + ".pt" + cPATH = args.chpt_base + args.exp + "_criticH_"+ str(epoch) + ".pt" + cePATH = args.chpt_base + args.exp + "_criticE_"+ str(epoch) + ".pt" + torch.save({ + 'epoch': epoch, + 'model_state_dict': mGenH.state_dict(), + 'optimizer_state_dict': optimizerG_H_E.state_dict() + }, gPATH) + + torch.save({ + 'epoch': epoch, + 'model_state_dict': mCritH.state_dict(), + 'optimizer_state_dict': optimizerD_H_E.state_dict() + }, cPATH) + + torch.save({ + 'epoch': epoch, + 'model_state_dict': mGenE.state_dict(), + 'optimizer_state_dict': optimizerG_E.state_dict() + }, ePATH) + + torch.save({ + 'epoch': epoch, + 'model_state_dict': mCritE.state_dict(), + 'optimizer_state_dict': optimizerD_E.state_dict() + }, cePATH) + + + print ("end training") + + + +def main(): + args = parse_args() + run(args) + +if __name__ == '__main__': + main() diff --git a/wgan_ECAL_HCAL_3crit.py b/wgan_ECAL_HCAL_3crit.py new file mode 100644 index 0000000000000000000000000000000000000000..61cf522c34cd9d36038d9e93c8ee66d010ee3e9c --- /dev/null +++ b/wgan_ECAL_HCAL_3crit.py @@ -0,0 +1,663 @@ +from __future__ import print_function +from comet_ml import Experiment +import argparse +import os, sys +import numpy as np +import torch +import torch.distributed as dist +import torch.nn as nn +import torch.optim as optim +from torch import autograd +from torch.utils.data.distributed import DistributedSampler +from torch.utils.data import DataLoader +from torch.autograd import Variable + +os.environ['MKL_THREADING_LAYER'] = 'GNU' + +torch.autograd.set_detect_anomaly(True) + +sys.path.append('/opt/regressor/src') + +from models.generatorFull import Hcal_ecalEMB +from models.data_loaderFull import HDF5Dataset +from models.criticFull import CriticEMB +from models.generator import DCGAN_G +from models.criticRes import generate_model + +def calc_gradient_penalty_ECAL(netD, real_data, fake_data, real_label, BATCH_SIZE, device, layer, xsize, ysize): + + alpha = torch.rand(BATCH_SIZE, 1) + alpha = alpha.expand(BATCH_SIZE, int(real_data.nelement()/BATCH_SIZE)).contiguous() + alpha = alpha.view(BATCH_SIZE, 1, layer, xsize, ysize) + alpha = alpha.to(device) + + + fake_data = fake_data.view(BATCH_SIZE, 1, layer, xsize, ysize) + interpolates = alpha * real_data.detach() + ((1 - alpha) * fake_data.detach()) + + interpolates = interpolates.to(device) + interpolates.requires_grad_(True) + + disc_interpolates = netD(interpolates.float(), real_label.float()) + + + gradients = autograd.grad(outputs=disc_interpolates, inputs=interpolates, + grad_outputs=torch.ones(disc_interpolates.size()).to(device), + create_graph=True, retain_graph=True, only_inputs=True)[0] + + gradients = gradients.view(gradients.size(0), -1) + gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean() + return gradient_penalty + +def calc_gradient_penalty_HCAL(netD, real_data, fake_data, real_label, BATCH_SIZE, device, layer, xsize, ysize): + + alpha = torch.rand(BATCH_SIZE, 1) + alpha = alpha.expand(BATCH_SIZE, int(real_data.nelement()/BATCH_SIZE)).contiguous() + alpha = alpha.view(BATCH_SIZE, 1, layer, xsize, ysize) + alpha = alpha.to(device) + + + fake_data = fake_data.view(BATCH_SIZE, 1, layer, xsize, ysize) + interpolates = alpha * real_data.detach() + ((1 - alpha) * fake_data.detach()) + + interpolates = interpolates.to(device) + interpolates.requires_grad_(True) + + disc_interpolates = netD(interpolates.float(), real_label.float()) + + + gradients = autograd.grad(outputs=disc_interpolates, inputs=interpolates, + grad_outputs=torch.ones(disc_interpolates.size()).to(device), + create_graph=True, retain_graph=True, only_inputs=True)[0] + + gradients = gradients.view(gradients.size(0), -1) + gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean() + return gradient_penalty + + +def calc_gradient_penalty_ECAL_HCAL(netD, real_ecal, real_hcal, fake_ecal, fake_hcal, real_label, BATCH_SIZE, device, layer, layer_hcal, xsize, ysize): + + alphaE = torch.rand(BATCH_SIZE, 1) + alphaE = alphaE.expand(BATCH_SIZE, int(real_ecal.nelement()/BATCH_SIZE)).contiguous() + alphaE = alphaE.view(BATCH_SIZE, 1, layer, xsize, ysize) + alphaE = alphaE.to(device) + + + alphaH = torch.rand(BATCH_SIZE, 1) + alphaH = alphaH.expand(BATCH_SIZE, int(real_hcal.nelement()/BATCH_SIZE)).contiguous() + alphaH = alphaH.view(BATCH_SIZE, 1, layer_hcal, xsize, ysize) + alphaH = alphaH.to(device) + + fake_hcal = fake_hcal.view(BATCH_SIZE, 1, layer_hcal, xsize, ysize) + fake_ecal = fake_ecal.view(BATCH_SIZE, 1, layer, xsize, ysize) + + interpolatesHCAL = alphaH * real_hcal.detach() + ((1 - alphaH) * fake_hcal.detach()) + interpolatesECAL = alphaE * real_ecal.detach() + ((1 - alphaE) * fake_ecal.detach()) + + + interpolatesHCAL = interpolatesHCAL.to(device) + interpolatesHCAL.requires_grad_(True) + + interpolatesECAL = interpolatesECAL.to(device) + interpolatesECAL.requires_grad_(True) + + disc_interpolates = netD(interpolatesECAL.float(), interpolatesHCAL.float(), real_label.float()) + + gradients = autograd.grad(outputs=disc_interpolates, inputs=[interpolatesECAL, interpolatesHCAL], + grad_outputs=torch.ones(disc_interpolates.size()).to(device), + create_graph=True, retain_graph=True, only_inputs=True)[0] + + gradients = gradients.view(gradients.size(0), -1) + gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean() + return gradient_penalty + + +def train(args, aDE, aDH, aD_H_E, aGE, aGH, device, train_loader, optimizer_d_E, optimizer_d_H, optimizer_d_H_E, optimizer_g_E, optimizer_g_H, optimizer_g_H_E, epoch, experiment): + + + Tensor = torch.cuda.FloatTensor + + + for batch_idx, (dataE, dataH, energy) in enumerate(train_loader): + ## ECAL CRITC TRAINING + aDE.train() + aGE.eval() + + # zero out critic gradients + optimizer_d_E.zero_grad() + + ## Get Real data + real_dataECAL = dataE.to(device).unsqueeze(1).float() + real_dataHCAL = dataH.to(device).unsqueeze(1).float() + label = energy.to(device).float() + ### + + ## Generate Fake ECAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=False) + fake_ecal_gen = aGE(zE, label.view(-1, 1, 1, 1, 1)).detach() + fake_ecal_gen = fake_ecal_gen.unsqueeze(1) + + fake_ecal = fake_ecal_gen.clone().detach() + + ## Critic fwd pass on Real + disc_real_E = aDE(real_dataECAL, label) + + ## Calculate Gradient Penalty Term + gradient_penalty_E = calc_gradient_penalty_ECAL(aDE, real_dataECAL, fake_ecal, label, args.batch_size, device, layer=30, xsize=30, ysize=30) + + ## Critic fwd pass on fake data + disc_fake_E = aDE(fake_ecal, label) + + + ## wasserstein-1 distace for critic + w_dist_E = torch.mean(disc_fake_E) - torch.mean(disc_real_E) + + # final disc cost + disc_cost_E = w_dist_E + args.lambd * gradient_penalty_E + + disc_cost_E.backward() + optimizer_d_E.step() + + #print("Generator training started") + + ## ECAL GENERATOR TRAINING + ## training generator per ncrit + if (batch_idx % args.ncrit == 0) and (batch_idx != 0): + ## GENERATOR TRAINING + aDE.eval() + aGE.train() + + # zero out generator gradients + optimizer_g_E.zero_grad() + + ## Generate Fake ECAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=True) + fake_ecal = aGE(zE, label.view(-1, 1, 1, 1, 1)) + fake_ecal = fake_ecal.unsqueeze(1) + + ## Loss function for ECAL generator + gen_E_cost = aDE(fake_ecal, label) + g_E_cost = -torch.mean(gen_E_cost) + g_E_cost.backward() + optimizer_g_E.step() + + if batch_idx % args.log_interval == 0 : + print('Generator --> Train Epoch: {} [{}/{} ({:.0f}%)]\tlossGE={:.4f}'.format( + epoch, batch_idx * len(dataH), len(train_loader.dataset), + 100. * batch_idx / len(train_loader), g_E_cost.item())) + + niter = epoch * len(train_loader) + batch_idx + experiment.log_metric("L_Gen_E", g_E_cost, step=niter) + + + + + ## HCAL CRITIC TRAINING + aDH.train() + aGH.eval() + aGE.eval() + + + # zero out critic gradients + optimizer_d_H.zero_grad() + + # Generate Fake HCAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=False) + fake_ecal = aGE(zE, label.view(-1, 1, 1, 1, 1)) + fake_ecal = fake_ecal.unsqueeze(1).detach() + + z = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz))), requires_grad=False) + fake_dataHCAL = aGH(z, label, fake_ecal) ## 48 x 30 x 30 + fake_dataHCAL = fake_dataHCAL.unsqueeze(1).detach() + + ## Critic fwd pass on Real + disc_real_H = aDH(real_dataHCAL, label) + + ## Calculate gradient penalty term + gradient_penalty_H = calc_gradient_penalty_HCAL(aDH, real_dataHCAL, fake_dataHCAL, label, args.batch_size, device, layer=48, xsize=30, ysize=30) + + ## Critic fwd pass on fake data + disc_fake_H = aDH(fake_dataHCAL, label) + + w_dist_H = torch.mean(disc_fake_H) - torch.mean(disc_real_H) + + # final disc cost + disc_cost_H = w_dist_H + args.lambd * gradient_penalty_H + + disc_cost_H.backward() + optimizer_d_H.step() + + ## HCAL GENERATOR TRAINING + ## training generator per ncrit + if (batch_idx % args.ncrit == 0) and (batch_idx != 0): + ## GENERATOR TRAINING + aDH.eval() + aGH.train() + + # zero out generator gradients + optimizer_g_H.zero_grad() + + # Generate Fake HCAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=False) + fake_ecal = aGE(zE, label.view(-1, 1, 1, 1, 1)) + fake_ecal = fake_ecal.unsqueeze(1).detach() + + z = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz))), requires_grad=True) + fake_dataHCAL = aGH(z, label, fake_ecal) ## 48 x 30 x 30 + fake_dataHCAL = fake_dataHCAL.unsqueeze(1) + + ## Loss function for ECAL generator + gen_H_cost = aDH(fake_dataHCAL, label) + g_H_cost = -torch.mean(gen_H_cost) + g_H_cost.backward() + optimizer_g_H.step() + + if batch_idx % args.log_interval == 0 : + print('Generator --> Train Epoch: {} [{}/{} ({:.0f}%)]\tlossGH={:.4f}'.format( + epoch, batch_idx * len(dataH), len(train_loader.dataset), + 100. * batch_idx / len(train_loader), g_H_cost.item())) + + niter = epoch * len(train_loader) + batch_idx + experiment.log_metric("L_Gen_H", g_H_cost, step=niter) + + + + ## ECAL + HCAL CRITIC TRAINING + aD_H_E.train() + aGH.eval() + aGE.eval() + + # zero out critic gradients + optimizer_d_H_E.zero_grad() + + ## Get Fake ECAL + #fake_ecal = fake_ecal_gen.clone().detach() + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=False) + fake_ecal = aGE(zE, label.view(-1, 1, 1, 1, 1)) + fake_ecal = fake_ecal.unsqueeze(1).detach() + + + ## Generate Fake HCAL + #z = zE.view(args.batch_size, args.nz) + z = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz))), requires_grad=False) + fake_dataHCAL = aGH(z, label, fake_ecal).detach() ## 48 x 30 x 30 + + ## Critic fwd pass on Real + disc_real_H_E = aD_H_E(real_dataECAL, real_dataHCAL, label) + + ## Calculate Gradient Penalty Term + gradient_penalty_H_E = calc_gradient_penalty_ECAL_HCAL(aD_H_E, real_dataECAL, real_dataHCAL, fake_ecal, fake_dataHCAL, label, args.batch_size, device, layer=30, layer_hcal=48, xsize=30, ysize=30) + + ## Critic fwd pass on fake data + disc_fake_H_E = aD_H_E(fake_ecal, fake_dataHCAL.unsqueeze(1), label) + + ## wasserstein-1 distace for critic + w_dist_H_E = torch.mean(disc_fake_H_E) - torch.mean(disc_real_H_E) + + # final disc cost + disc_cost_H_E = w_dist_H_E + args.lambd * gradient_penalty_H_E + + disc_cost_H_E.backward() + optimizer_d_H_E.step() + + + if batch_idx % args.log_interval == 0: + print('Critic --> Train Epoch: {} [{}/{} ({:.0f}%)]\tloss={:.4f}'.format( + epoch, batch_idx * len(dataH), len(train_loader.dataset), + 100. * batch_idx / len(train_loader), disc_cost_H_E.item())) + niter = epoch * len(train_loader) + batch_idx + experiment.log_metric("L_crit_E", disc_cost_E, step=niter) + experiment.log_metric("L_crit_H", disc_cost_H, step=niter) + experiment.log_metric("L_crit_H_E", disc_cost_H_E, step=niter) + experiment.log_metric("gradient_pen_E", gradient_penalty_E, step=niter) + experiment.log_metric("gradient_pen_H", gradient_penalty_H, step=niter) + experiment.log_metric("gradient_pen_H_E", gradient_penalty_H_E, step=niter) + experiment.log_metric("Wasserstein Dist E", w_dist_E, step=niter) + experiment.log_metric("Wasserstein Dist H", w_dist_H, step=niter) + experiment.log_metric("Wasserstein Dist E H", w_dist_H_E, step=niter) + experiment.log_metric("Critic Score E (Real)", torch.mean(disc_real_E), step=niter) + experiment.log_metric("Critic Score E (Fake)", torch.mean(disc_fake_E), step=niter) + experiment.log_metric("Critic Score H (Real)", torch.mean(disc_real_H), step=niter) + experiment.log_metric("Critic Score H (Fake)", torch.mean(disc_fake_H), step=niter) + experiment.log_metric("Critic Score H E (Real)", torch.mean(disc_real_H_E), step=niter) + experiment.log_metric("Critic Score H E (Fake)", torch.mean(disc_fake_H_E), step=niter) + + + ## training generator per ncrit + if (batch_idx % args.ncrit == 0) and (batch_idx != 0): + ## GENERATOR TRAINING + aD_H_E.eval() + aGH.train() + aGE.train() + + # Optimize HCAL generator + optimizer_g_H_E.zero_grad() + + + ## Generate Fake ECAL + zE = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz, 1, 1, 1))), requires_grad=True) + fake_ecal = aGE(zE, label.view(-1, 1, 1, 1, 1)) + fake_ecal = fake_ecal.unsqueeze(1) + #### + + #z = zE.view(args.batch_size, args.nz) + z = Variable(Tensor(np.random.uniform(-1, 1, (args.batch_size, args.nz))), requires_grad=True) + ## generate fake data out of noise + fake_dataHCAL = aGH(z, label, fake_ecal) + + ## combine ECAL + HCAL + comb = torch.cat((fake_ecal.squeeze(1), fake_dataHCAL), 1) + esumFake = torch.sum(comb.flatten(1), 1) + trueEsum = Tensor(np.random.normal(881, 90, args.batch_size)) ## Super add-hoc resolution of 50 GeV pions + + auxLoss = (esumFake - trueEsum)**2 + + ## Total loss function for generator + gen_cost = aD_H_E(fake_ecal, fake_dataHCAL.unsqueeze(1), label) + g_cost = -torch.mean(gen_cost) + args.kappa*torch.mean(auxLoss) + g_cost.backward() + optimizer_g_H_E.step() + + if batch_idx % args.log_interval == 0 : + print('Generator --> Train Epoch: {} [{}/{} ({:.0f}%)]\t lossGHE={:.4f}'.format( + epoch, batch_idx * len(dataH), len(train_loader.dataset), + 100. * batch_idx / len(train_loader), g_cost.item())) + + niter = epoch * len(train_loader) + batch_idx + experiment.log_metric("L_Gen_H_E", g_cost, step=niter) + experiment.log_metric("L_aux_Esum", torch.mean(auxLoss), step=niter) + + + + +def is_distributed(): + return dist.is_available() and dist.is_initialized() + + +def parse_args(): + parser = argparse.ArgumentParser(description='WGAN training on hadron showers') + parser.add_argument('--batch-size', type=int, default=50, metavar='N', + help='input batch size for training (default: 50)') + + parser.add_argument('--nz', type=int, default=100, metavar='N', + help='latent space for generator (default: 100)') + + parser.add_argument('--lambd', type=int, default=15, metavar='N', + help='weight of gradient penalty (default: 15)') + + parser.add_argument('--kappa', type=float, default=1, metavar='N', + help='weight of label conditioning (default: 0.001)') + + parser.add_argument('--dres', type=int, default=34, metavar='N', + help='depth of Residual critic (default: 34)') + + parser.add_argument('--ndf', type=int, default=32, metavar='N', + help='n-feature of critic (default: 32)') + + parser.add_argument('--ngf', type=int, default=32, metavar='N', + help='n-feature of generator (default: 32)') + + parser.add_argument('--ncrit', type=int, default=10, metavar='N', + help='critic updates before generator one (default: 10)') + + parser.add_argument('--epochs', type=int, default=1, metavar='N', + help='number of epochs to train (default: 1)') + + parser.add_argument('--nworkers', type=int, default=1, metavar='N', + help='number of epochs to train (default: 1)') + + parser.add_argument('--lrCrit_H_E', type=float, default=0.00001, metavar='LR', + help='learning rate Critic_H_E (default: 0.00001)') + + parser.add_argument('--lrCrit_H', type=float, default=0.00001, metavar='LR', + help='learning rate CriticH (default: 0.00001)') + + parser.add_argument('--lrCrit_E', type=float, default=0.00001, metavar='LR', + help='learning rate CriticE (default: 0.00001)') + + parser.add_argument('--lrGen_H_E', type=float, default=0.0001, metavar='LR', + help='learning rate Generator_H_E (default: 0.0001)') + + parser.add_argument('--lrGen_H', type=float, default=0.0001, metavar='LR', + help='learning rate Generator_H (default: 0.0001)') + + parser.add_argument('--lrGen_E', type=float, default=0.0001, metavar='LR', + help='learning rate GeneratorE (default: 0.0001)') + + parser.add_argument('--chpt', action='store_true', default=False, + help='continue training from a saved model') + + parser.add_argument('--chpt_base', type=str, default='/eos/user/e/eneren/experiments/', + help='continue training from a saved model') + + parser.add_argument('--exp', type=str, default='dist_wgan', + help='name of the experiment') + + parser.add_argument('--chpt_eph', type=int, default=1, + help='continue checkpoint epoch') + + parser.add_argument('--no-cuda', action='store_true', default=False, + help='disables CUDA training') + parser.add_argument('--seed', type=int, default=1, metavar='S', + help='random seed (default: 1)') + parser.add_argument('--log-interval', type=int, default=100, metavar='N', + help='how many batches to wait before logging training status') + + + if dist.is_available(): + parser.add_argument('--backend', type=str, help='Distributed backend', + choices=[dist.Backend.GLOO, dist.Backend.NCCL, dist.Backend.MPI], + default=dist.Backend.GLOO) + + parser.add_argument('--local_rank', type=int, default=0) + + args = parser.parse_args() + + + args.local_rank = int(os.environ.get('LOCAL_RANK', args.local_rank)) + args.rank = int(os.environ.get('RANK')) + args.world_size = int(os.environ.get('WORLD_SIZE')) + + + # postprocess args + args.device = 'cuda:{}'.format(args.local_rank) # PytorchJob/launch.py + args.batch_size = max(args.batch_size, + args.world_size * 2) # min valid batchsize + return args + + + +def run(args): + # Training settings + + use_cuda = not args.no_cuda and torch.cuda.is_available() + if use_cuda: + print('Using CUDA') + + + experiment = Experiment(api_key="keGmeIz4GfKlQZlOP6cit4QOi", + project_name="ecal-hcal-shower", workspace="engineren", auto_output_logging="simple") + experiment.add_tag(args.exp) + + experiment.log_parameters( + { + "batch_size" : args.batch_size, + "latent": args.nz, + "lambda": args.lambd, + "ncrit" : args.ncrit, + "resN_E_H": args.ndf, + "resN_H": args.dres, + "resN_E": args.dres, + "ngf": args.ngf + } + ) + + torch.manual_seed(args.seed) + + device = torch.device("cuda" if use_cuda else "cpu") + + + if args.world_size > 1: + print('Using distributed PyTorch with {} backend'.format(args.backend)) + dist.init_process_group(backend=args.backend) + + print('[init] == local rank: {}, global rank: {}, world size: {} =='.format(args.local_rank, args.rank, args.world_size)) + + + + print ("loading data") + #dataset = HDF5Dataset('/eos/user/e/eneren/scratch/40GeV40k.hdf5', transform=None, train_size=40000) + dataset = HDF5Dataset('/eos/user/e/eneren/scratch/50GeV75k.hdf5', transform=None, train_size=75000) + #dataset = HDF5Dataset('/eos/user/e/eneren/scratch/4060GeV.hdf5', transform=None, train_size=60000) + + + if args.world_size > 1: + sampler = DistributedSampler(dataset, shuffle=True) + train_loader = DataLoader(dataset, batch_size=args.batch_size, sampler=sampler, num_workers=args.nworkers, drop_last=True, pin_memory=False) + else: + train_loader = DataLoader(dataset, batch_size=args.batch_size, num_workers=args.nworkers, shuffle=True, drop_last=True, pin_memory=False) + + + ## ECAL + HCAL Generator and critic + mCrit_H_E = CriticEMB().to(device) + + ## HCAL Generator and critic + mCritH = generate_model(args.dres).to(device) + mGenH = Hcal_ecalEMB(args.ngf, 32, args.nz).to(device) + + ## ECAL GENERATOR and critic + mGenE = DCGAN_G(args.ngf, args.nz).to(device) + mCritE = generate_model(args.dres).to(device) + + + if args.world_size > 1: + Distributor = nn.parallel.DistributedDataParallel if use_cuda \ + else nn.parallel.DistributedDataParallelCPU + mCrit_H_E = Distributor(mCrit_H_E, device_ids=[args.local_rank], output_device=args.local_rank ) + mCritH = Distributor(mCritH, device_ids=[args.local_rank], output_device=args.local_rank ) + mGenH = Distributor(mGenH, device_ids=[args.local_rank], output_device=args.local_rank) + mGenE = Distributor(mGenE, device_ids=[args.local_rank], output_device=args.local_rank) + mCritE = Distributor(mCritE, device_ids=[args.local_rank], output_device=args.local_rank) + else: + mCrit_H_E = nn.parallel.DataParallel(mCrit_H_E) + mGenH = nn.parallel.DataParallel(mGenH) + mCritH = nn.parallel.DataParallel(mCritH) + mGenE = nn.parallel.DataParallel(mGenE) + mCritE = nn.parallel.DataParallel(mCritE) + + optimizerG_H_E = optim.Adam(list(mGenH.parameters())+list(mGenE.parameters()), lr=args.lrGen_H_E, betas=(0.5, 0.9)) + + optimizerD_H_E = optim.Adam(mCrit_H_E.parameters(), lr=args.lrCrit_H_E, betas=(0.5, 0.9)) + + optimizerG_H = optim.Adam(mGenH.parameters(), lr=args.lrGen_H, betas=(0.5, 0.9)) + + optimizerD_H = optim.Adam(mCritH.parameters(), lr=args.lrCrit_H, betas=(0.5, 0.9)) + + optimizerG_E = optim.Adam(mGenE.parameters(), lr=args.lrGen_E, betas=(0.5, 0.9)) + + optimizerD_E = optim.Adam(mCritE.parameters(), lr=args.lrCrit_E, betas=(0.5, 0.9)) + + + if (args.chpt): + critic_E_checkpoint = torch.load(args.chpt_base + args.exp + "_criticE_"+ str(args.chpt_eph) + ".pt") + critic_H_checkpoint = torch.load(args.chpt_base + args.exp + "_criticH_"+ str(args.chpt_eph) + ".pt") + critic_E_H_checkpoint = torch.load(args.chpt_base + args.exp + "_criticH_E_"+ str(args.chpt_eph) + ".pt") + gen_E_checkpoint = torch.load(args.chpt_base + args.exp + "_generatorE_"+ str(args.chpt_eph) + ".pt") + gen_H_checkpoint = torch.load(args.chpt_base + args.exp + "_generatorH_"+ str(args.chpt_eph) + ".pt") + gen_H_E_checkpoint = torch.load(args.chpt_base + args.exp + "_generatorH_E_"+ str(args.chpt_eph) + ".pt") + + + mGenE.load_state_dict(gen_E_checkpoint['model_state_dict']) + optimizerG_E.load_state_dict(gen_E_checkpoint['optimizer_state_dict']) + + mGenH.load_state_dict(gen_H_checkpoint['model_state_dict']) + optimizerG_H.load_state_dict(gen_H_checkpoint['optimizer_state_dict']) + optimizerG_H_E.load_state_dict(gen_H_E_checkpoint['optimizer_state_dict']) + + mCritE.load_state_dict(critic_E_checkpoint['model_state_dict']) + optimizerD_E.load_state_dict(critic_E_checkpoint['optimizer_state_dict']) + + mCritH.load_state_dict(critic_H_checkpoint['model_state_dict']) + optimizerD_H.load_state_dict(critic_H_checkpoint['optimizer_state_dict']) + + mCrit_H_E.load_state_dict(critic_E_H_checkpoint['model_state_dict']) + optimizerD_H_E.load_state_dict(critic_E_H_checkpoint['optimizer_state_dict']) + + eph = gen_H_checkpoint['epoch'] + + else: + eph = 0 + gen_E_checkpoint = torch.load("/eos/user/e/eneren/experiments/wganv1_generator_694.pt", map_location=torch.device('cuda')) + critic_E_checkpoint = torch.load("/eos/user/e/eneren/experiments/wganv1_critic_694.pt", map_location=torch.device('cuda')) + + mGenE.load_state_dict(gen_E_checkpoint['model_state_dict']) + optimizerG_E.load_state_dict(gen_E_checkpoint['optimizer_state_dict']) + + mCritE.load_state_dict(critic_E_checkpoint['model_state_dict']) + optimizerD_E.load_state_dict(critic_E_checkpoint['optimizer_state_dict']) + + print ("init models") + + + experiment.set_model_graph(str(mCritH), overwrite=False) + + print ("starting training...") + for epoch in range(1, args.epochs + 1): + epoch += eph + + if args.world_size > 1: + train_loader.sampler.set_epoch(epoch) + train(args, mCritE, mCritH, mCrit_H_E, mGenE, mGenH, device, train_loader, optimizerD_E, optimizerD_H, optimizerD_H_E, optimizerG_E, optimizerG_H, optimizerG_H_E, epoch, experiment) + if args.rank == 0: + gHPATH = args.chpt_base + args.exp + "_generatorH_"+ str(epoch) + ".pt" + ePATH = args.chpt_base + args.exp + "_generatorE_"+ str(epoch) + ".pt" + gPATH = args.chpt_base + args.exp + "_generatorH_E_"+ str(epoch) + ".pt" + cPATH = args.chpt_base + args.exp + "_criticH_E_"+ str(epoch) + ".pt" + cHPATH = args.chpt_base + args.exp + "_criticH_"+ str(epoch) + ".pt" + cePATH = args.chpt_base + args.exp + "_criticE_"+ str(epoch) + ".pt" + torch.save({ + 'epoch': epoch, + 'model_state_dict': mGenH.state_dict(), + 'optimizer_state_dict': optimizerG_H.state_dict() + }, gHPATH) + + torch.save({ + 'epoch': epoch, + 'model_state_dict': mCritH.state_dict(), + 'optimizer_state_dict': optimizerD_H.state_dict() + }, cHPATH) + + torch.save({ + 'epoch': epoch, + 'optimizer_state_dict': optimizerG_H_E.state_dict() + }, gPATH) + + torch.save({ + 'epoch': epoch, + 'model_state_dict': mCrit_H_E.state_dict(), + 'optimizer_state_dict': optimizerD_H_E.state_dict() + }, cPATH) + + torch.save({ + 'epoch': epoch, + 'model_state_dict': mGenE.state_dict(), + 'optimizer_state_dict': optimizerG_E.state_dict() + }, ePATH) + + torch.save({ + 'epoch': epoch, + 'model_state_dict': mCritE.state_dict(), + 'optimizer_state_dict': optimizerD_E.state_dict() + }, cePATH) + + + print ("end training") + + + +def main(): + args = parse_args() + run(args) + +if __name__ == '__main__': + main()