From 535a165c6d19e2f62be65eb8c6073c540a6c66b5 Mon Sep 17 00:00:00 2001 From: Andrzej Preibisz Date: Tue, 24 May 2022 00:05:44 +0200 Subject: [PATCH] MLFlow --- Dockerfile | 4 +- MLProject | 12 +++ mlflow_training.py | 94 ++++++++++++++++++ trained_model/keras_metadata.pb | 2 +- .../variables/variables.data-00000-of-00001 | Bin 11049 -> 11049 bytes trained_model/variables/variables.index | Bin 2302 -> 2302 bytes 6 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 MLProject create mode 100644 mlflow_training.py diff --git a/Dockerfile b/Dockerfile index 3661c9e..39f8147 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,8 @@ WORKDIR app ARG EPOCHS ENV EPOCHS=${EPOCHS} -COPY ml_training.py ./ +COPY mlflow_training.py ./ COPY heart_2020_cleaned.csv ./ -CMD ["python3", "./ml_training.py $EPOCHS"] +CMD ["python3", "./mlflow_training.py $EPOCHS"] diff --git a/MLProject b/MLProject new file mode 100644 index 0000000..e16e63f --- /dev/null +++ b/MLProject @@ -0,0 +1,12 @@ +name: s444465 + +docker_env: + image: s444465/ium:mlflow + +entry_points: + main: + parameters: + epochs: {type: float, default: 10} + command: "python mlflow_training.py {epochs}" + test: + command: "python mlflow_training.py test" diff --git a/mlflow_training.py b/mlflow_training.py new file mode 100644 index 0000000..52bfca3 --- /dev/null +++ b/mlflow_training.py @@ -0,0 +1,94 @@ +import mlflow +import numpy as np +import pandas as pd +import tensorflow as tf +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from mlflow.models.signature import infer_signature +from sklearn.preprocessing import StandardScaler +import sys + +mlflow.set_experiment("s444465") + + +def evaluate_model(model, test_x, test_y): + test_loss, test_acc, test_rec = model.evaluate(test_x, test_y, verbose=1) + # print("Accuracy:", test_acc) + # print("Loss:", test_loss) + # print("Recall:", test_rec) + return test_acc, test_loss, test_rec + + +def main(): + no_of_epochs = int(sys.argv[1]) if (len(sys.argv) == 2 and sys.argv[1].isdigit()) else 10 + is_testing = (len(sys.argv) == 2) and not sys.argv[1].isdigit() and sys.argv[1] == "test" + + mlflow.log_param("epochs", no_of_epochs) + scaler = StandardScaler() + feature_names = ["BMI", "SleepTime", "Sex", "Diabetic", "PhysicalActivity", "Smoking", "AlcoholDrinking"] + + dataset = pd.read_csv('heart_2020_cleaned.csv') + dataset = dataset.dropna() + + dataset["Diabetic"] = dataset["Diabetic"].apply(lambda x: True if "Yes" in x else False) + dataset["HeartDisease"] = dataset["HeartDisease"].apply(lambda x: True if x == "Yes" else False) + dataset["PhysicalActivity"] = dataset["PhysicalActivity"].apply(lambda x: True if x == "Yes" else False) + dataset["Smoking"] = dataset["Smoking"].apply(lambda x: True if x == "Yes" else False) + dataset["AlcoholDrinking"] = dataset["AlcoholDrinking"].apply(lambda x: True if x == "Yes" else False) + dataset["Sex"] = dataset["Sex"].apply(lambda x: 1 if x == "Female" else 0) + + dataset_train, dataset_test = train_test_split(dataset, test_size=.1, train_size=.9, random_state=1) + + print(dataset_test.shape) + + model = tf.keras.Sequential([ + tf.keras.layers.Dense(16, activation='relu'), + tf.keras.layers.Dense(8, activation='relu'), + tf.keras.layers.Dense(4, activation='relu'), + tf.keras.layers.Dense(1, activation='sigmoid') + ]) + + model.compile( + loss=tf.keras.losses.binary_crossentropy, + optimizer=tf.keras.optimizers.Adam(lr=0.01), + metrics=["accuracy", tf.keras.metrics.Recall(name='recall')] + ) + + train_X = dataset_train[feature_names].astype(np.float32) + train_Y = dataset_train["HeartDisease"].astype(np.float32) + test_X = dataset_test[feature_names].astype(np.float32) + test_Y = dataset_test["HeartDisease"].astype(np.float32) + + train_X = scaler.fit_transform(train_X) + # train_Y = scaler.fit_transform(train_Y) + test_X = scaler.fit_transform(test_X) + # test_Y = scaler.fit_transform(test_Y) + + + print(train_Y.value_counts()) + + train_X = tf.convert_to_tensor(train_X) + train_Y = tf.convert_to_tensor(train_Y) + + test_X = tf.convert_to_tensor(test_X) + test_Y = tf.convert_to_tensor(test_Y) + + model.fit(train_X, train_Y, epochs=no_of_epochs) + model.save("trained_model") + + acc, loss, rec = evaluate_model(model, test_X, test_Y) + + mlflow.log_metric("accuracy", acc) + mlflow.log_metric("loss", loss) + + signature = infer_signature(np.array(train_X), np.array(train_Y)) + + mlflow.sklearn.log_model(model, "mlflow_model", signature=signature, input_example=np.array(test_X[0])) + if is_testing: + predictions = model.predict(np.array(test_X)) + predictions = [int(i > 0.5) for i in predictions] + accuracy = accuracy_score(np.array(test_Y), predictions) + mlflow.log_metric("eval_accuracy", accuracy) + + +main() \ No newline at end of file diff --git a/trained_model/keras_metadata.pb b/trained_model/keras_metadata.pb index 57e18d2..98103d0 100644 --- a/trained_model/keras_metadata.pb +++ b/trained_model/keras_metadata.pb @@ -1,5 +1,5 @@ -+root"_tf_keras_sequential*+{"name": "sequential", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "must_restore_from_config": false, "class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 7]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "dense_input"}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 8, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 4, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}, "shared_object_id": 13, "build_input_shape": {"class_name": "TensorShape", "items": [null, 7]}, "is_graph_network": true, "full_save_spec": {"class_name": "__tuple__", "items": [[{"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 7]}, "float32", "dense_input"]}], {}]}, "save_spec": {"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 7]}, "float32", "dense_input"]}, "keras_version": "2.8.0", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 7]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "dense_input"}, "shared_object_id": 0}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 1}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 2}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 3}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 8, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 4}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 5}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 6}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 4, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 7}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 8}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 9}, {"class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 10}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 11}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 12}]}}, "training_config": {"loss": "binary_crossentropy", "metrics": [[{"class_name": "MeanMetricWrapper", "config": {"name": "accuracy", "dtype": "float32", "fn": "binary_accuracy"}, "shared_object_id": 14}, {"class_name": "Recall", "config": {"name": "recall", "dtype": "float32", "thresholds": null, "top_k": null, "class_id": null}, "shared_object_id": 15}]], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Adam", "config": {"name": "Adam", "learning_rate": 0.10000000149011612, "decay": 0.0, "beta_1": 0.8999999761581421, "beta_2": 0.9990000128746033, "epsilon": 1e-07, "amsgrad": false}}}}2 ++root"_tf_keras_sequential*+{"name": "sequential", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "must_restore_from_config": false, "class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 7]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "dense_input"}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 8, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 4, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}, "shared_object_id": 13, "build_input_shape": {"class_name": "TensorShape", "items": [null, 7]}, "is_graph_network": true, "full_save_spec": {"class_name": "__tuple__", "items": [[{"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 7]}, "float32", "dense_input"]}], {}]}, "save_spec": {"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 7]}, "float32", "dense_input"]}, "keras_version": "2.8.0", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 7]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "dense_input"}, "shared_object_id": 0}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 1}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 2}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 3}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 8, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 4}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 5}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 6}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 4, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 7}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 8}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 9}, {"class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 10}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 11}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 12}]}}, "training_config": {"loss": "binary_crossentropy", "metrics": [[{"class_name": "MeanMetricWrapper", "config": {"name": "accuracy", "dtype": "float32", "fn": "binary_accuracy"}, "shared_object_id": 14}, {"class_name": "Recall", "config": {"name": "recall", "dtype": "float32", "thresholds": null, "top_k": null, "class_id": null}, "shared_object_id": 15}]], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Adam", "config": {"name": "Adam", "learning_rate": 0.009999999776482582, "decay": 0.0, "beta_1": 0.8999999761581421, "beta_2": 0.9990000128746033, "epsilon": 1e-07, "amsgrad": false}}}}2 root.layer_with_weights-0"_tf_keras_layer*{"name": "dense", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 1}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 2}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 3, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 7}}, "shared_object_id": 16}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 7]}}2 root.layer_with_weights-1"_tf_keras_layer*{"name": "dense_1", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 8, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 4}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 5}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 6, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 16}}, "shared_object_id": 17}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16]}}2 root.layer_with_weights-2"_tf_keras_layer*{"name": "dense_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 4, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 7}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 8}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 9, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 8}}, "shared_object_id": 18}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 8]}}2 diff --git a/trained_model/variables/variables.data-00000-of-00001 b/trained_model/variables/variables.data-00000-of-00001 index 8ba3cd24593eefd6a0649e4b63794199871bc6b2..6cb590be9811b0f2585cd0cd54405e843fa3d61f 100644 GIT binary patch delta 3729 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 ZU?MXE!&G(#1|Y@2u#d&2d-7V2KmhtVh|K^1 delta 438 zcmew-_)l=cN*4crZ_jRAtHH=(bZVFO@aCNe^0lOM9l39$$< zaR?~<5Ce*z`0{6$sG`W^+iV(>>zO!N9MkXVPc~$+2Xa^^UtnTqc_((NWAaBZo-N}M*2yaiNzpSPQJ^+&GO{4 zuQ1pfEDBSEH3UrO)Si_>@dk^+4H=;Lt7##wQ2PWpfWBi