iui-group-l-name-zensiert/1-first-project/Abgabe.ipynb

782 lines
57 KiB
Plaintext

{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "1a0d0dda",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"glob_path = '/opt/iui-datarelease2-sose2021/*/split_letters_csv/*'\n",
"\n",
"pickle_file = 'data.pickle'\n",
"\n",
"create_new = False\n",
"checkpoint_path = \"training_1/cp.ckpt\"\n",
"checkpoint_dir = os.path.dirname(checkpoint_path)\n",
"\n",
"# divisor for neuron count step downs (hard to describe), e.g. dense_step = 3: layer1=900, layer2 = 300, layer3 = 100, layer4 = 33...\n",
"dense_steps = 2\n",
"# amount of dense/dropout layers\n",
"layer_count = 3\n",
"# how much to drop\n",
"drop_count = 0.1"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "592dd9b6",
"metadata": {},
"outputs": [],
"source": [
"os.environ['TF_FORCE_GPU_ALLOW_GROWTH'] = 'true' # this is required\n",
"os.environ['CUDA_VISIBLE_DEVICES'] = '2' # set to '0' for GPU0, '1' for GPU1 or '2' for GPU2. Check \"gpustat\" in a terminal."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "f02f3401",
"metadata": {},
"outputs": [],
"source": [
"from glob import glob\n",
"import pandas as pd\n",
"from tqdm import tqdm\n",
"\n",
"def dl_from_blob(filename) -> list:\n",
" all_data = []\n",
" \n",
" for path in tqdm(glob(filename)):\n",
" path = path\n",
" df = pd.read_csv(path, ';')\n",
" u = path.split('/')[3]\n",
" l = ''.join(filter(lambda x: x.isalpha(), path.split('/')[5]))[0] \n",
" d = {\n",
" 'file': path,\n",
" 'data': df,\n",
" 'user': u,\n",
" 'label': l\n",
" }\n",
" all_data.append(d)\n",
" return all_data"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "591292e0",
"metadata": {},
"outputs": [],
"source": [
"def save_pickle(f, structure):\n",
" _p = open(f, 'wb')\n",
" pickle.dump(structure, _p)\n",
" _p.close()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "efbe6b1d",
"metadata": {},
"outputs": [],
"source": [
"import pickle\n",
"\n",
"def load_pickles(f) -> list:\n",
" _p = open(pickle_file, 'rb')\n",
" _d = pickle.load(_p)\n",
" _p.close()\n",
" \n",
" return _d"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "a0b68deb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading data...\n",
"data.pickle found...\n"
]
}
],
"source": [
"import os\n",
"def load_data() -> list:\n",
" if os.path.isfile(pickle_file):\n",
" print(f'{pickle_file} found...')\n",
" return load_pickles(pickle_file)\n",
" print(f'Didn\\'t find {pickle_file}...')\n",
" all_data = dl_from_blob(glob_path)\n",
" print(f'Creating {pickle_file}...')\n",
" save_pickle(pickle_file, all_data)\n",
" return all_data\n",
"\n",
"print(\"Loading data...\")\n",
"data = load_data()\n",
"# plot_pd(data[0]['data'], False)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "238b73fb",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"def plot_pd(data, force=True):\n",
" fig, axs = plt.subplots(5, 3, figsize=(3*3, 3*5))\n",
" axs[0][0].plot(data['Acc1 X'])\n",
" axs[0][1].plot(data['Acc1 Y'])\n",
" axs[0][2].plot(data['Acc1 Z'])\n",
" axs[1][0].plot(data['Acc2 X'])\n",
" axs[1][1].plot(data['Acc2 Y'])\n",
" axs[1][2].plot(data['Acc2 Z'])\n",
" axs[2][0].plot(data['Gyro X'])\n",
" axs[2][1].plot(data['Gyro Y'])\n",
" axs[2][2].plot(data['Gyro Z'])\n",
" axs[3][0].plot(data['Mag X'])\n",
" axs[3][1].plot(data['Mag Y'])\n",
" axs[3][2].plot(data['Mag Z'])\n",
" axs[4][0].plot(data['Time'])\n",
"\n",
" if force:\n",
" for a in axs:\n",
" for b in a:\n",
" b.plot(data['Force'])\n",
" else:\n",
" axs[4][1].plot(data['Force'])\n",
"\n",
"def plot_np(data, force=True):\n",
" fig, axs = plt.subplots(5, 3, figsize=(3*3, 3*5))\n",
" axs[0][0].plot(data[0])\n",
" axs[0][1].plot(data[1])\n",
" axs[0][2].plot(data[2])\n",
" axs[1][0].plot(data[3])\n",
" axs[1][1].plot(data[4])\n",
" axs[1][2].plot(data[5])\n",
" axs[2][0].plot(data[6])\n",
" axs[2][1].plot(data[7])\n",
" axs[2][2].plot(data[8])\n",
" axs[3][0].plot(data[9])\n",
" axs[3][1].plot(data[10])\n",
" axs[3][2].plot(data[11])\n",
" axs[4][0].plot(data[13])\n",
"\n",
" if force:\n",
" for a in axs:\n",
" for b in a:\n",
" b.plot(data[12])\n",
" else:\n",
" axs[4][1].plot(data[12])\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "8b2bba94",
"metadata": {},
"outputs": [],
"source": [
"def mill_drop(entry):\n",
" #drop millis on single\n",
" data_wo_mill = entry['data'].drop(labels='Millis', axis=1, inplace=False)\n",
" drop_entry = entry\n",
" drop_entry['data'] = data_wo_mill.reset_index(drop=True)\n",
" \n",
" return drop_entry"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "836bab5a",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"def cut_force(drop_entry):\n",
" # force trans\n",
" shorten_entry = drop_entry\n",
" shorten_data = shorten_entry['data']\n",
" sf_entry = shorten_data['Force']\n",
" leeway = 10\n",
" \n",
" try:\n",
" thresh = 70\n",
" temps_over_T = np.where(sf_entry > thresh)[0]\n",
" shorten_data = shorten_data[max(temps_over_T.min()-leeway,0):min(len(sf_entry)-1,temps_over_T.max()+leeway)]\n",
" except:\n",
" thresold = 0.05\n",
" thresh = sf_entry.max()*thresold\n",
" temps_over_T = np.where(sf_entry > thresh)[0]\n",
" shorten_data = shorten_data[max(temps_over_T.min()-leeway,0):min(len(sf_entry)-1,temps_over_T.max()+leeway)]\n",
" \n",
" shorten_entry['data'] = shorten_data.reset_index(drop=True)\n",
" return shorten_entry"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "aa85eade",
"metadata": {},
"outputs": [],
"source": [
"def norm_force(shorten_entry, flist):\n",
" fnorm_entry = shorten_entry\n",
" u = fnorm_entry['user']\n",
" d = fnorm_entry['data']\n",
" \n",
" \n",
" d['Force'] = ((d['Force'] - flist[u].mean())/flist[u].std())\n",
" \n",
" fnorm_entry['data'] = fnorm_entry['data'].reset_index(drop=True)\n",
" return fnorm_entry"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "42579003",
"metadata": {},
"outputs": [],
"source": [
"def time_trans(fnorm_entry):\n",
" #timetrans\n",
" time_entry = fnorm_entry\n",
" \n",
" time_entry['data']['Time'] = fnorm_entry['data']['Time']-fnorm_entry['data']['Time'][0]\n",
" \n",
" time_entry['data'] = time_entry['data'].reset_index(drop=True)\n",
"\n",
" return time_entry"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "4fc8f3a2",
"metadata": {},
"outputs": [],
"source": [
"def norm(time_entry):\n",
" # normalize\n",
" norm_entry = time_entry\n",
" \n",
" norm_entry['data']['Acc1 X'] = norm_entry['data']['Acc1 X'] / 32768\n",
" norm_entry['data']['Acc1 Y'] = norm_entry['data']['Acc1 Y'] / 32768\n",
" norm_entry['data']['Acc1 Z'] = norm_entry['data']['Acc1 Z'] / 32768\n",
" norm_entry['data']['Acc2 X'] = norm_entry['data']['Acc2 X'] / 8192\n",
" norm_entry['data']['Acc2 Y'] = norm_entry['data']['Acc2 Y'] / 8192\n",
" norm_entry['data']['Acc2 Z'] = norm_entry['data']['Acc2 Z'] / 8192\n",
" norm_entry['data']['Gyro X'] = norm_entry['data']['Gyro X'] / 32768\n",
" norm_entry['data']['Gyro Y'] = norm_entry['data']['Gyro Y'] / 32768\n",
" norm_entry['data']['Gyro Z'] = norm_entry['data']['Gyro Z'] / 32768\n",
" norm_entry['data']['Mag X'] = norm_entry['data']['Mag X'] / 8192\n",
" norm_entry['data']['Mag Y'] = norm_entry['data']['Mag Y'] / 8192\n",
" norm_entry['data']['Mag Z'] = norm_entry['data']['Mag Z'] / 8192\n",
" \n",
" norm_entry['data'] = norm_entry['data'].reset_index(drop=True)\n",
" \n",
" return norm_entry"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "2a8cf8f1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Preprocessing...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 26179/26179 [01:28<00:00, 294.18it/s]\n"
]
}
],
"source": [
"def preproc(d):\n",
" flist = {} \n",
" d_res = []\n",
" for e in data:\n",
" if e['user'] not in flist:\n",
" flist[e['user']] = e['data']['Force']\n",
" else:\n",
" flist[e['user']] = flist[e['user']].append(e['data']['Force'])\n",
" \n",
" for e in tqdm(data):\n",
" d_res.append(preproc_entry(e, flist))\n",
" return d_res\n",
" \n",
"def preproc_entry(entry, flist):\n",
" drop_entry = mill_drop(entry)\n",
"# plot_pd(drop_entry['data'])\n",
"# \n",
" shorten_entry = cut_force(drop_entry)\n",
"# plot_pd(shorten_entry['data'])\n",
"# \n",
" fnorm_entry = norm_force(shorten_entry, flist)\n",
"# plot_pd(fnorm_entry['data'])\n",
"# \n",
" time_entry = time_trans(shorten_entry)\n",
"# plot_pd(time_entry['data'])\n",
"# \n",
" norm_entry = norm(time_entry)\n",
"# plot_pd(norm_entry['data'], False)\n",
" return norm_entry\n",
"\n",
"print(\"Preprocessing...\")\n",
"pdata = preproc(data)\n",
"# plot_pd(pdata[0]['data'], False)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "daca6878",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Truncating...\n"
]
}
],
"source": [
"def throw(pdata):\n",
" llist = pd.Series([len(x['data']) for x in pdata])\n",
" threshold = int(llist.quantile(threshold_p))\n",
" longdex = np.where(llist <= threshold)[0]\n",
" return np.array(pdata)[longdex]\n",
"\n",
"llist = pd.Series([len(x['data']) for x in pdata])\n",
"threshold_p = 0.75\n",
"threshold = int(llist.quantile(threshold_p))\n",
"\n",
"print(\"Truncating...\")\n",
"tpdata = throw(pdata)\n",
"# plot_pd(tpdata[0]['data'], False)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "7321c532",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
" 18%|█▊ | 3624/19640 [00:00<00:00, 18199.99it/s]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Padding...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 19640/19640 [00:01<00:00, 19054.38it/s]\n"
]
}
],
"source": [
"from tensorflow.keras.preprocessing.sequence import pad_sequences\n",
"# ltpdata = []\n",
"def elong(tpdata):\n",
" for x in tqdm(tpdata):\n",
" y = x['data'].to_numpy().T\n",
" x['data'] = pad_sequences(y, dtype=float, padding='post', maxlen=threshold)\n",
" return tpdata\n",
"\n",
"print(\"Padding...\")\n",
"ltpdata = elong(tpdata)\n",
"# plot_np(ltpdata[0]['data'], False)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "863d612a",
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"from tensorflow.keras.regularizers import l2\n",
"from tensorflow.keras.models import Sequential\n",
"from tensorflow.keras.layers import Dense, Flatten, BatchNormalization, Dropout\n",
"from tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau\n",
"from tensorflow.keras.optimizers import Adam\n",
"\n",
"def build_model(shape, classes):\n",
" model = Sequential()\n",
" \n",
" ncount = shape[0]*shape[1]\n",
" \n",
" model.add(Flatten(input_shape=shape, name='flatten'))\n",
" \n",
" model.add(Dropout(drop_count, name=f'dropout_{drop_count*100}'))\n",
" model.add(BatchNormalization(name='batchNorm'))\n",
" \n",
" for i in range(1,layer_count+1):\n",
" neurons = int(ncount/pow(dense_steps,i))\n",
" if neurons <= classes:\n",
" break\n",
" model.add(Dropout(drop_count*i, name=f'HiddenDropout_{drop_count*i*100:.0f}'))\n",
" model.add(Dense(neurons, activation='relu', \n",
" kernel_regularizer=l2(0.001), name=f'Hidden_{i}')\n",
" )\n",
" \n",
" model.add(Dense(classes, activation='softmax', name='Output'))\n",
" \n",
" model.compile(\n",
" optimizer=Adam(),\n",
" loss=\"categorical_crossentropy\", \n",
" metrics=[\"acc\"],\n",
" )\n",
" \n",
" model.summary()\n",
" \n",
" return model"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "a046d6e5",
"metadata": {},
"outputs": [],
"source": [
"checkpoint_file = './goat.weights'\n",
"\n",
"def train(X_train, y_train, X_test, y_test):\n",
" model = build_model(X_train[0].shape, 52)\n",
" \n",
" # Create a callback that saves the model's weights\n",
" model_checkpoint = ModelCheckpoint(filepath=checkpoint_path, monitor='loss', \n",
"\t\t\tsave_best_only=True)\n",
" \n",
" history = model.fit(X_train, y_train, \n",
" epochs=100,\n",
" batch_size=32,\n",
" shuffle=True,\n",
" validation_data=(X_test, y_test),\n",
" verbose=2,\n",
" callbacks=[model_checkpoint]\n",
" )\n",
" \n",
" \n",
" model.load_weights(checkpoint_path)\n",
" print(\"Evaluate on test data\")\n",
" return model, history"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "93428481",
"metadata": {},
"outputs": [],
"source": [
"from sklearn.model_selection import train_test_split\n",
"from sklearn.preprocessing import LabelEncoder, LabelBinarizer\n",
"\n",
"X = np.array([x['data'] for x in ltpdata])\n",
"y = np.array([x['label'] for x in ltpdata])\n",
"\n",
"lb = LabelBinarizer()\n",
"y_tran = lb.fit_transform(y)\n",
"\n",
"X_train, X_test, y_train, y_test = train_test_split(X, y_tran, test_size=0.2, random_state=177013)\n",
"\n",
"X_train=X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[2])\n",
"X_test=X_test.reshape(X_test.shape[0],X_test.shape[1],X_test.shape[2])\n",
"\n",
"train_shape = X_train[0].shape\n",
"classes = y_train[0].shape[0]"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "046ff9ca",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loaded weights...\n",
"Model: \"sequential\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"flatten (Flatten) (None, 1050) 0 \n",
"_________________________________________________________________\n",
"dropout_10.0 (Dropout) (None, 1050) 0 \n",
"_________________________________________________________________\n",
"batchNorm (BatchNormalizatio (None, 1050) 4200 \n",
"_________________________________________________________________\n",
"HiddenDropout_10 (Dropout) (None, 1050) 0 \n",
"_________________________________________________________________\n",
"Hidden_1 (Dense) (None, 525) 551775 \n",
"_________________________________________________________________\n",
"HiddenDropout_20 (Dropout) (None, 525) 0 \n",
"_________________________________________________________________\n",
"Hidden_2 (Dense) (None, 262) 137812 \n",
"_________________________________________________________________\n",
"HiddenDropout_30 (Dropout) (None, 262) 0 \n",
"_________________________________________________________________\n",
"Hidden_3 (Dense) (None, 131) 34453 \n",
"_________________________________________________________________\n",
"Output (Dense) (None, 52) 6864 \n",
"=================================================================\n",
"Total params: 735,104\n",
"Trainable params: 733,004\n",
"Non-trainable params: 2,100\n",
"_________________________________________________________________\n",
"CPU times: user 338 ms, sys: 217 ms, total: 554 ms\n",
"Wall time: 574 ms\n"
]
}
],
"source": [
"%%time\n",
"if not os.path.isdir(checkpoint_dir) or create_new:\n",
" tf.keras.backend.clear_session()\n",
" model, history = train(np.array(X), np.array(y_tran), np.array(X_test), np.array(y_test))\n",
"else:\n",
" print(\"Loaded weights...\")\n",
" model = build_model(X_train[0].shape, 52)\n",
" model.load_weights(checkpoint_path)"
]
},
{
"cell_type": "markdown",
"id": "e95e5144",
"metadata": {},
"source": [
"# Evaluation"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "e6c40138",
"metadata": {},
"outputs": [],
"source": [
"ptest = [lb.classes_[e] for e in np.argmax(model.predict(X_test), axis=-1)]\n",
"ltest = lb.inverse_transform(y_test)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "4dacd4bd",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 1440x1008 with 2 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" precision recall f1-score support\n",
"\n",
" A 0.94 0.90 0.92 52\n",
" B 0.86 0.79 0.83 24\n",
" C 0.74 0.67 0.70 93\n",
" D 0.94 0.91 0.92 65\n",
" E 1.00 0.71 0.83 14\n",
" F 0.87 0.89 0.88 38\n",
" G 0.97 0.88 0.92 67\n",
" H 0.93 0.90 0.91 29\n",
" I 0.70 0.81 0.75 96\n",
" J 0.88 0.88 0.88 101\n",
" K 0.81 0.85 0.83 60\n",
" L 0.86 0.78 0.82 92\n",
" M 0.83 0.87 0.85 55\n",
" N 0.89 0.89 0.89 82\n",
" O 0.67 0.76 0.71 80\n",
" P 0.64 0.53 0.58 55\n",
" Q 0.90 1.00 0.95 36\n",
" R 0.95 0.93 0.94 42\n",
" S 0.61 0.66 0.63 86\n",
" T 0.80 0.79 0.80 89\n",
" U 0.82 0.42 0.56 97\n",
" V 0.57 0.82 0.67 76\n",
" W 0.90 0.57 0.70 67\n",
" X 0.79 0.56 0.66 80\n",
" Y 0.60 0.46 0.52 76\n",
" Z 0.72 0.85 0.78 59\n",
" a 0.76 0.77 0.77 96\n",
" b 0.82 0.89 0.86 93\n",
" c 0.69 0.77 0.72 77\n",
" d 0.86 0.88 0.87 82\n",
" e 0.81 0.93 0.86 95\n",
" f 0.88 0.96 0.92 76\n",
" g 0.87 0.85 0.86 71\n",
" h 0.85 0.87 0.86 94\n",
" i 0.81 0.90 0.86 82\n",
" j 0.96 0.86 0.91 59\n",
" k 0.91 0.77 0.83 78\n",
" l 0.68 0.75 0.71 100\n",
" m 0.87 0.93 0.90 58\n",
" n 0.77 0.86 0.81 98\n",
" o 0.66 0.71 0.68 82\n",
" p 0.75 0.90 0.82 96\n",
" q 0.85 0.68 0.75 65\n",
" r 0.76 0.83 0.79 118\n",
" s 0.67 0.68 0.68 109\n",
" t 0.84 0.78 0.81 82\n",
" u 0.62 0.43 0.51 104\n",
" v 0.53 0.55 0.54 92\n",
" w 0.62 0.89 0.73 62\n",
" x 0.60 0.75 0.67 85\n",
" y 0.57 0.58 0.57 83\n",
" z 0.87 0.59 0.70 80\n",
"\n",
" accuracy 0.77 3928\n",
" macro avg 0.79 0.78 0.78 3928\n",
"weighted avg 0.77 0.77 0.76 3928\n",
"\n",
"CPU times: user 998 ms, sys: 195 ms, total: 1.19 s\n",
"Wall time: 963 ms\n"
]
}
],
"source": [
"%%time\n",
"\n",
"from sklearn.metrics import confusion_matrix\n",
"import seaborn as sn\n",
"\n",
"from sklearn.metrics import classification_report\n",
"\n",
"set_digits = sorted(list(set(ltest)))\n",
"\n",
"test_cm = confusion_matrix(ltest, ptest, labels=list(set_digits), normalize='true')\n",
"\n",
"df_cm = pd.DataFrame(test_cm, index=set_digits, columns=set_digits)\n",
"plt.figure(figsize = (20,14))\n",
"sn_plot = sn.heatmap(df_cm, cmap=\"Greys\")\n",
"plt.ylabel(\"True Label\")\n",
"plt.xlabel(\"Predicted Label\")\n",
"plt.show()\n",
"\n",
"print(classification_report(ltest, ptest, zero_division=0))"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "772b43c9",
"metadata": {},
"outputs": [],
"source": [
"def plot_keras_history(history, name='', acc='acc'):\n",
" \"\"\"Plots keras history.\"\"\"\n",
" import matplotlib.pyplot as plt\n",
"\n",
" training_acc = history.history[acc]\n",
" validation_acc = history.history['val_' + acc]\n",
" loss = history.history['loss']\n",
" val_loss = history.history['val_loss']\n",
"\n",
" epochs = range(len(training_acc))\n",
"\n",
" plt.ylim(0, 1)\n",
" plt.plot(epochs, training_acc, 'tab:blue', label='Training acc')\n",
" plt.plot(epochs, validation_acc, 'tab:orange', label='Validation acc')\n",
" plt.title('Training and validation accuracy ' + name)\n",
" plt.legend()\n",
"\n",
" plt.figure()\n",
"\n",
" plt.plot(epochs, loss, 'tab:green', label='Training loss')\n",
" plt.plot(epochs, val_loss, 'tab:red', label='Validation loss')\n",
" plt.title('Training and validation loss ' + name)\n",
" plt.legend()\n",
" plt.show()\n",
" plt.close()\n",
"if 'history' in locals():\n",
" plot_keras_history(history)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "07e20a8e",
"metadata": {},
"outputs": [],
"source": [
"exit()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.8.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}