diff --git a/POET_Training.ipynb b/POET_Training.ipynb new file mode 100644 index 0000000..53029e1 --- /dev/null +++ b/POET_Training.ipynb @@ -0,0 +1,660 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## General Information" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is used to train a simple neural network model to predict the chemistry in the barite benchmark (50x50 grid). The training data is stored in the repository using **git large file storage** and can be downloaded after the installation of git lfs using the `git lfs pull` command.\n", + "\n", + "It is then recommended to create a Python environment using miniconda. The necessary dependencies are contained in `environment.yml` and can be installed using `conda env create -f environment.yml`.\n", + "\n", + "The data set is divided into a design and result part and consists of the iterations of a reference simulation. The design part of the data set contains the chemical concentrations at time $t$ and the result part at time $t+1$, which are to be learned by the model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup Libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running Keras in version 3.8.0\n" + ] + } + ], + "source": [ + "import keras\n", + "print(\"Running Keras in version {}\".format(keras.__version__))\n", + "\n", + "import h5py\n", + "import numpy as np\n", + "import pandas as pd\n", + "import time\n", + "import sklearn.model_selection as sk\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "dtype = \"float32\"\n", + "activation = \"relu\"\n", + "\n", + "lr = 0.001\n", + "batch_size = 512\n", + "epochs = 50 # default 400 epochs\n", + "\n", + "lr_schedule = keras.optimizers.schedules.ExponentialDecay(\n", + " initial_learning_rate=lr,\n", + " decay_steps=2000,\n", + " decay_rate=0.9,\n", + " staircase=True\n", + ")\n", + "\n", + "optimizer = keras.optimizers.Adam(learning_rate=lr_schedule)\n", + "loss = keras.losses.Huber()\n", + "\n", + "sample_fraction = 0.8" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup the model" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Model: \"sequential\"\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mModel: \"sequential\"\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+       "┃ Layer (type)                     Output Shape                  Param # ┃\n",
+       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+       "│ dense (Dense)                   │ (None, 128)            │         1,664 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_1 (Dense)                 │ (None, 128)            │        16,512 │\n",
+       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+       "│ dense_2 (Dense)                 │ (None, 12)             │         1,548 │\n",
+       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+       "
\n" + ], + "text/plain": [ + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", + "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", + "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", + "│ dense (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m128\u001b[0m) │ \u001b[38;5;34m1,664\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_1 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m128\u001b[0m) │ \u001b[38;5;34m16,512\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_2 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m12\u001b[0m) │ \u001b[38;5;34m1,548\u001b[0m │\n", + "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Total params: 19,724 (77.05 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m19,724\u001b[0m (77.05 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Trainable params: 19,724 (77.05 KB)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m19,724\u001b[0m (77.05 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Non-trainable params: 0 (0.00 B)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "model = keras.Sequential(\n", + " [\n", + " keras.Input(shape = (12,), dtype = \"float32\"),\n", + " keras.layers.Dense(units = 128, activation = \"relu\", dtype = \"float32\"),\n", + " keras.layers.Dense(units = 128, activation = \"relu\", dtype = \"float32\"),\n", + " keras.layers.Dense(units = 12, dtype = \"float32\")\n", + " ]\n", + ")\n", + "\n", + "model.compile(optimizer=optimizer, loss = loss)\n", + "model.summary()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define some functions and helper classes" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def Safelog(val):\n", + " # get range of vector\n", + " if val > 0:\n", + " return np.log10(val)\n", + " elif val < 0:\n", + " return -np.log10(-val)\n", + " else:\n", + " return 0\n", + "\n", + "def Safeexp(val):\n", + " if val > 0:\n", + " return -10 ** -val\n", + " elif val < 0:\n", + " return 10 ** val\n", + " else:\n", + " return 0\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# ? Why does the charge is using another logarithm than the other species\n", + "\n", + "func_dict_in = {\n", + " \"H\" : np.log1p,\n", + " \"O\" : np.log1p,\n", + " \"Charge\" : Safelog,\n", + " \"H_0_\" : np.log1p,\n", + " \"O_0_\" : np.log1p,\n", + " \"Ba\" : np.log1p,\n", + " \"Cl\" : np.log1p,\n", + " \"S_2_\" : np.log1p,\n", + " \"S_6_\" : np.log1p,\n", + " \"Sr\" : np.log1p,\n", + " \"Barite\" : np.log1p,\n", + " \"Celestite\" : np.log1p\n", + "}\n", + "\n", + "func_dict_out = {\n", + " \"H\" : np.expm1,\n", + " \"O\" : np.expm1,\n", + " \"Charge\" : Safeexp,\n", + " \"H_0_\" : np.expm1,\n", + " \"O_0_\" : np.expm1,\n", + " \"Ba\" : np.expm1,\n", + " \"Cl\" : np.expm1,\n", + " \"S_2_\" : np.expm1,\n", + " \"S_6_\" : np.expm1,\n", + " \"Sr\" : np.expm1,\n", + " \"Barite\" : np.expm1,\n", + " \"Celestite\" : np.expm1\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read data from `.h5` file and convert it to a `pandas.DataFrame`" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "data_file = h5py.File(\"Barite_50_Data_training.h5\")\n", + "\n", + "design = data_file[\"design\"]\n", + "results = data_file[\"result\"]\n", + "\n", + "df_design = pd.DataFrame(np.array(design[\"data\"]).transpose(), columns = design[\"names\"].asstr())\n", + "df_results = pd.DataFrame(np.array(results[\"data\"]).transpose(), columns = results[\"names\"].asstr())\n", + "\n", + "data_file.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define Scaling and Normalization Functions" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def log_scale(df_design, df_result, func_dict):\n", + " \n", + " df_design = df_design.copy()\n", + " df_result = df_result.copy()\n", + " \n", + " for key in df_design.keys():\n", + " df_design[key] = np.vectorize(func_dict[key])(df_design[key])\n", + " df_result[key] = np.vectorize(func_dict[key])(df_result[key])\n", + " \n", + " return df_result, df_design\n", + "\n", + "# Get minimum and maximum values for each column\n", + "def get_min_max(df_design, df_result):\n", + " \n", + " min_vals_des = df_design.min()\n", + " max_vals_des = df_design.max()\n", + " \n", + " min_vals_res = df_result.min()\n", + " max_vals_res = df_result.max()\n", + "\n", + " # minimum of input and output data to get global minimum/maximum\n", + " data_min = np.minimum(min_vals_des, min_vals_res).to_dict()\n", + " data_max = np.maximum(max_vals_des, max_vals_res).to_dict()\n", + "\n", + " return data_min, data_max\n", + "\n", + "\n", + "df_design_log, df_results_log = log_scale(df_design, df_results, func_dict_in)\n", + "data_min_log, data_max_log = get_min_max(df_design_log, df_results_log)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "def preprocess(data, func_dict, data_min, data_max):\n", + " data = data.copy()\n", + " for key in data.keys():\n", + " data[key] = (data[key] - data_min[key]) / (data_max[key] - data_min[key])\n", + "\n", + " return data\n", + "\n", + "def postprocess(data, func_dict, data_min, data_max):\n", + " data = data.copy()\n", + " for key in data.keys():\n", + " data[key] = data[key] * (data_max[key] - data_min[key]) + data_min[key]\n", + " data[key] = np.vectorize(func_dict[key])(data[key])\n", + " return data" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[4.71860988e+00 4.03439461e+00 1.64809168e+01 1.72424113e-11\n", + " 2.88259393e-10 9.23957137e-02 1.79673102e-01 1.80262638e-13\n", + " 6.20582152e-04 5.63876556e-02 6.99379443e-01 6.93551204e-01]\n" + ] + } + ], + "source": [ + "from sklearn.preprocessing import FunctionTransformer, MinMaxScaler\n", + "\n", + "transformer = FunctionTransformer(log_scale, kw_args = {\"func_dict\" : func_dict_in})\n", + "\n", + "scaler=MinMaxScaler()\n", + "\n", + "scaler.fit(pd.concat([df_design_log, df_results_log]))\n", + "\n", + "print(scaler.data_max_)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preprocess the data" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "pp_design = preprocess(df_design_log, func_dict_in, data_min_log, data_max_log)\n", + "pp_results = preprocess(df_results_log, func_dict_in, data_min_log, data_max_log)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sample the data" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [], + "source": [ + "# sample the data into training and validation data\n", + "train_data = pp_design.sample(frac = sample_fraction)\n", + "val_data = pp_design.drop(train_data.index)\n", + "\n", + "train_results = pp_results.loc[train_data.index]\n", + "val_results = pp_results.drop(train_data.index)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "X_train, X_test, y_train, y_test = sk.train_test_split(pp_design, pp_results, test_size = 0.2)\n", + "X_train, X_val, y_train, y_val = sk.train_test_split(X_train, y_train, test_size = 0.1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train the model" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 828us/step - loss: 0.0013 - val_loss: 1.1404e-06\n", + "Epoch 2/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 793us/step - loss: 1.4840e-06 - val_loss: 1.4576e-06\n", + "Epoch 3/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 824us/step - loss: 1.4434e-06 - val_loss: 1.1059e-06\n", + "Epoch 4/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 813us/step - loss: 1.2418e-06 - val_loss: 1.4799e-06\n", + "Epoch 5/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 894us/step - loss: 1.0540e-06 - val_loss: 9.0661e-07\n", + "Epoch 6/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 850us/step - loss: 9.8962e-07 - val_loss: 9.6343e-07\n", + "Epoch 7/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 896us/step - loss: 7.1421e-07 - val_loss: 1.0128e-06\n", + "Epoch 8/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 784us/step - loss: 9.4590e-07 - val_loss: 8.5226e-07\n", + "Epoch 9/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 810us/step - loss: 8.5829e-07 - val_loss: 7.9730e-07\n", + "Epoch 10/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 851us/step - loss: 7.3620e-07 - val_loss: 8.1594e-07\n", + "Epoch 11/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 930us/step - loss: 8.2763e-07 - val_loss: 7.9174e-07\n", + "Epoch 12/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 841us/step - loss: 7.5164e-07 - val_loss: 7.9159e-07\n", + "Epoch 13/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 906us/step - loss: 7.2227e-07 - val_loss: 7.9551e-07\n", + "Epoch 14/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 832us/step - loss: 8.5750e-07 - val_loss: 7.9073e-07\n", + "Epoch 15/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 817us/step - loss: 7.6794e-07 - val_loss: 8.2430e-07\n", + "Epoch 16/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 811us/step - loss: 7.8525e-07 - val_loss: 7.6804e-07\n", + "Epoch 17/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 818us/step - loss: 6.5793e-07 - val_loss: 7.7165e-07\n", + "Epoch 18/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 849us/step - loss: 7.6873e-07 - val_loss: 7.8483e-07\n", + "Epoch 19/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 833us/step - loss: 7.3115e-07 - val_loss: 7.6651e-07\n", + "Epoch 20/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 789us/step - loss: 7.6460e-07 - val_loss: 7.6667e-07\n", + "Epoch 21/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 777us/step - loss: 5.5257e-07 - val_loss: 7.8632e-07\n", + "Epoch 22/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 808us/step - loss: 6.8125e-07 - val_loss: 7.6522e-07\n", + "Epoch 23/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 813us/step - loss: 6.2676e-07 - val_loss: 7.6267e-07\n", + "Epoch 24/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 800us/step - loss: 5.7057e-07 - val_loss: 7.6537e-07\n", + "Epoch 25/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 1000us/step - loss: 5.3213e-07 - val_loss: 7.6502e-07\n", + "Epoch 26/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 799us/step - loss: 7.3359e-07 - val_loss: 7.6122e-07\n", + "Epoch 27/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 801us/step - loss: 5.5530e-07 - val_loss: 7.6046e-07\n", + "Epoch 28/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 775us/step - loss: 5.6699e-07 - val_loss: 7.6158e-07\n", + "Epoch 29/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 798us/step - loss: 6.3822e-07 - val_loss: 7.6058e-07\n", + "Epoch 30/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 779us/step - loss: 6.0064e-07 - val_loss: 7.5951e-07\n", + "Epoch 31/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 773us/step - loss: 6.1063e-07 - val_loss: 7.5915e-07\n", + "Epoch 32/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 796us/step - loss: 5.6002e-07 - val_loss: 7.6251e-07\n", + "Epoch 33/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 835us/step - loss: 6.3413e-07 - val_loss: 7.5966e-07\n", + "Epoch 34/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 790us/step - loss: 6.0062e-07 - val_loss: 7.5858e-07\n", + "Epoch 35/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 828us/step - loss: 6.5727e-07 - val_loss: 7.5895e-07\n", + "Epoch 36/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 822us/step - loss: 7.6945e-07 - val_loss: 7.5849e-07\n", + "Epoch 37/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 779us/step - loss: 5.9666e-07 - val_loss: 7.5850e-07\n", + "Epoch 38/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 774us/step - loss: 6.7566e-07 - val_loss: 7.5847e-07\n", + "Epoch 39/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 805us/step - loss: 6.6410e-07 - val_loss: 7.5872e-07\n", + "Epoch 40/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 818us/step - loss: 6.7137e-07 - val_loss: 7.5844e-07\n", + "Epoch 41/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 790us/step - loss: 7.0753e-07 - val_loss: 7.6004e-07\n", + "Epoch 42/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 798us/step - loss: 5.9159e-07 - val_loss: 7.5833e-07\n", + "Epoch 43/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 793us/step - loss: 7.1825e-07 - val_loss: 7.5846e-07\n", + "Epoch 44/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 764us/step - loss: 6.8167e-07 - val_loss: 7.5837e-07\n", + "Epoch 45/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 817us/step - loss: 7.1077e-07 - val_loss: 7.5818e-07\n", + "Epoch 46/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 817us/step - loss: 7.1459e-07 - val_loss: 7.5828e-07\n", + "Epoch 47/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 818us/step - loss: 6.2480e-07 - val_loss: 7.5828e-07\n", + "Epoch 48/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 798us/step - loss: 7.2107e-07 - val_loss: 7.5825e-07\n", + "Epoch 49/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 812us/step - loss: 6.5633e-07 - val_loss: 7.5826e-07\n", + "Epoch 50/50\n", + "\u001b[1m3520/3520\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 770us/step - loss: 7.2437e-07 - val_loss: 7.5821e-07\n", + "Training took 145.50856709480286 seconds\n" + ] + } + ], + "source": [ + "# measure time\n", + "start = time.time()\n", + "\n", + "history = model.fit(X_train, \n", + " y_train, \n", + " batch_size = batch_size, \n", + " epochs = epochs, \n", + " validation_data = (X_val, y_val)\n", + ")\n", + "\n", + "end = time.time()\n", + "\n", + "print(\"Training took {} seconds\".format(end - start))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(history.history[\"loss\"], \"o-\", label = \"Training Loss\")\n", + "plt.xlabel(\"Epoch\")\n", + "plt.ylabel(\"Loss (Hubert)\")\n", + "plt.grid('on')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test the model" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m15641/15641\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 226us/step - loss: 6.0244e-07\n" + ] + }, + { + "data": { + "text/plain": [ + "7.261308496708807e-07" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.evaluate(X_test, y_test)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Save the model" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [], + "source": [ + "# Save the model\n", + "model.save(\"Barite_50_Model_additional_species.keras\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "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.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..9cea602 --- /dev/null +++ b/environment.yml @@ -0,0 +1,162 @@ +name: ai +channels: + - conda-forge + - defaults + - https://repo.anaconda.com/pkgs/main + - https://repo.anaconda.com/pkgs/r +dependencies: + - absl-py=2.1.0=py311hca03da5_0 + - appnope=0.1.4=pyhd8ed1ab_1 + - asttokens=3.0.0=pyhd8ed1ab_1 + - astunparse=1.6.3=py_0 + - blas=1.0=openblas + - bottleneck=1.4.2=py311hb9f6ed7_0 + - brotli=1.0.9=h80987f9_9 + - brotli-bin=1.0.9=h80987f9_9 + - brotli-python=1.0.9=py311h313beb8_9 + - bzip2=1.0.8=h80987f9_6 + - c-ares=1.34.4=h5505292_0 + - ca-certificates=2024.12.31=hca03da5_0 + - cached-property=1.5.2=py_0 + - certifi=2024.12.14=py311hca03da5_0 + - charset-normalizer=3.3.2=pyhd3eb1b0_0 + - comm=0.2.2=pyhd8ed1ab_1 + - contourpy=1.3.1=py311h48ca7d4_0 + - cycler=0.11.0=pyhd3eb1b0_0 + - debugpy=1.8.11=py311h155a34a_0 + - decorator=5.1.1=pyhd8ed1ab_1 + - exceptiongroup=1.2.2=pyhd8ed1ab_1 + - executing=2.1.0=pyhd8ed1ab_1 + - flatbuffers=24.3.25=h313beb8_0 + - fonttools=4.51.0=py311h80987f9_0 + - freetype=2.12.1=hadb7bae_2 + - gast=0.5.3=pyhd3eb1b0_0 + - giflib=5.2.2=h80987f9_0 + - google-pasta=0.2.0=pyhd3eb1b0_0 + - grpcio=1.65.5=py311hc367efa_0 + - h5py=3.12.1=nompi_py311h5dd25b7_103 + - hdf5=1.14.4=nompi_ha698983_105 + - icu=75.1=hfee45f7_0 + - idna=3.7=py311hca03da5_0 + - importlib-metadata=8.5.0=pyha770c72_1 + - ipykernel=6.29.5=pyh57ce528_0 + - ipython=8.31.0=pyh707e725_0 + - jedi=0.19.2=pyhd8ed1ab_1 + - joblib=1.4.2=py311hca03da5_0 + - jupyter_client=8.6.3=pyhd8ed1ab_1 + - jupyter_core=5.7.2=pyh31011fe_1 + - keras=3.8.0=pyh753f3f9_0 + - kiwisolver=1.4.4=py311h313beb8_0 + - krb5=1.21.3=hf3e1bf2_0 + - lcms2=2.16=ha0e7c42_0 + - lerc=4.0.0=h313beb8_0 + - libabseil=20240722.0=cxx17_h07bc746_4 + - libaec=1.1.3=h313beb8_0 + - libbrotlicommon=1.0.9=h80987f9_9 + - libbrotlidec=1.0.9=h80987f9_9 + - libbrotlienc=1.0.9=h80987f9_9 + - libcurl=8.11.1=h73640d1_0 + - libcxx=19.1.6=ha82da77_1 + - libdeflate=1.23=hec38601_0 + - libedit=3.1.20230828=h80987f9_0 + - libev=4.33=h1a28f6b_1 + - libexpat=2.6.4=h286801f_0 + - libffi=3.4.4=hca03da5_1 + - libgfortran=5.0.0=13_2_0_hd922786_3 + - libgfortran5=13.2.0=hf226fd6_3 + - libgrpc=1.65.5=h3d9cf25_0 + - libjpeg-turbo=3.0.3=h80987f9_0 + - liblzma=5.6.3=h39f12f2_1 + - libnghttp2=1.64.0=h6d7220d_0 + - libopenblas=0.3.21=h269037a_0 + - libpng=1.6.45=h3783ad8_0 + - libprotobuf=5.27.5=h53f8970_2 + - libre2-11=2024.07.02=h07bc746_2 + - libsodium=1.0.20=h99b78c6_0 + - libsqlite=3.47.2=h3f77e49_0 + - libssh2=1.11.1=h9cc3647_0 + - libtiff=4.7.0=h551f018_3 + - libwebp-base=1.5.0=h2471fea_0 + - libxcb=1.17.0=hdb1d25a_0 + - libzlib=1.3.1=h8359307_2 + - llvm-openmp=14.0.6=hc6e5704_0 + - lz4-c=1.9.4=h313beb8_1 + - markdown=3.4.1=py311hca03da5_0 + - markdown-it-py=2.2.0=py311hca03da5_1 + - markupsafe=2.1.3=py311h80987f9_1 + - matplotlib=3.10.0=py311hca03da5_0 + - matplotlib-base=3.10.0=py311h7ef442a_0 + - matplotlib-inline=0.1.7=pyhd8ed1ab_1 + - mdurl=0.1.0=py311hca03da5_0 + - ml_dtypes=0.4.0=py311h7aedaa7_0 + - namex=0.0.7=py311hca03da5_0 + - ncurses=6.5=h5e97a16_2 + - nest-asyncio=1.6.0=pyhd8ed1ab_1 + - numexpr=2.10.1=py311h5d9532f_0 + - numpy=1.26.4=py311he598dae_0 + - numpy-base=1.26.4=py311hfbfe69c_0 + - openjpeg=2.5.3=h8a3d83b_0 + - openssl=3.4.0=h81ee809_1 + - opt_einsum=3.3.0=pyhd3eb1b0_1 + - optree=0.12.1=py311h48ca7d4_0 + - packaging=24.2=py311hca03da5_0 + - pandas=2.2.3=py311hcf29cfe_0 + - parso=0.8.4=pyhd8ed1ab_1 + - pexpect=4.9.0=pyhd8ed1ab_1 + - pickleshare=0.7.5=pyhd8ed1ab_1004 + - pillow=11.1.0=py311hb9ba9e9_0 + - pip=24.2=py311hca03da5_0 + - platformdirs=4.3.6=pyhd8ed1ab_1 + - prompt-toolkit=3.0.48=pyha770c72_1 + - protobuf=5.27.5=py311h3f08180_0 + - psutil=6.1.1=py311h917b07b_0 + - pthread-stubs=0.3=h1a28f6b_1 + - ptyprocess=0.7.0=pyhd8ed1ab_1 + - pure_eval=0.2.3=pyhd8ed1ab_1 + - pygments=2.15.1=py311hca03da5_1 + - pyparsing=3.2.0=py311hca03da5_0 + - pysocks=1.7.1=py311hca03da5_0 + - python=3.11.11=hc22306f_1_cpython + - python-dateutil=2.9.0.post0=pyhff2d567_1 + - python-flatbuffers=24.3.25=py311hca03da5_0 + - python-tzdata=2023.3=pyhd3eb1b0_0 + - python_abi=3.11=5_cp311 + - pytz=2024.1=py311hca03da5_0 + - pyzmq=26.2.0=py311h730b646_3 + - re2=2024.07.02=h6589ca4_2 + - readline=8.2=h1a28f6b_0 + - requests=2.32.3=py311hca03da5_1 + - rich=13.9.4=py311hca03da5_0 + - scikit-learn=1.5.2=py311h313beb8_0 + - scipy=1.14.1=py311hac8794a_0 + - setuptools=75.1.0=py311hca03da5_0 + - six=1.16.0=pyhd3eb1b0_1 + - snappy=1.2.1=h313beb8_0 + - sqlite=3.47.2=hd7222ec_0 + - stack_data=0.6.3=pyhd8ed1ab_1 + - tensorboard=2.17.1=pyhd8ed1ab_0 + - tensorboard-data-server=0.7.0=py311ha6e5c4f_1 + - tensorflow=2.17.0=cpu_py311h9d3d1e9_3 + - tensorflow-base=2.17.0=cpu_py311ha270cad_3 + - tensorflow-estimator=2.17.0=cpu_py311h935fadc_3 + - termcolor=2.1.0=py311hca03da5_0 + - threadpoolctl=3.5.0=py311hb6e6a13_0 + - tk=8.6.13=h5083fa2_1 + - tornado=6.4.2=py311h917b07b_0 + - traitlets=5.14.3=pyhd8ed1ab_1 + - typing-extensions=4.12.2=py311hca03da5_0 + - typing_extensions=4.12.2=py311hca03da5_0 + - tzdata=2024b=h04d1e81_0 + - unicodedata2=15.1.0=py311h80987f9_1 + - urllib3=2.2.3=py311hca03da5_0 + - wcwidth=0.2.13=pyhd8ed1ab_1 + - werkzeug=3.0.6=py311hca03da5_0 + - wheel=0.44.0=py311hca03da5_0 + - wrapt=1.17.0=py311h80987f9_0 + - xorg-libxau=1.0.12=h5505292_0 + - xorg-libxdmcp=1.1.5=hd74edd7_0 + - xz=5.4.6=h80987f9_1 + - zeromq=4.3.5=hc1bb282_7 + - zipp=3.21.0=pyhd8ed1ab_1 + - zlib=1.3.1=h8359307_2 + - zstd=1.5.6=hb46c0d2_0