<jupyter_start><jupyter_text>Assignment: Build a Regression Model in Keras IntroductionThis assignment was the final project in IBM's [Introduction to Deep Learning & Neural Networks with Keras](https://www.coursera.org/account/accomplishments/certificate/QQEVGBYCHYYP) course on Coursera. The objective: build a regression model to predict the compressive strength of concrete based on its age (in days) and the volumes of the different ingredients used to make it.Each part of the assignment involved the following steps:1. Randomly split the data into training and test sets, holding 30% for testing.2. Build a neural network with hidden layers of 10 nodes each, using ReLU activation, Adam optimization, and a mean squared error loss function.3. Train the model over a certain number of epochs.4. Evaluate the model on the test data and compute the mean squared error between the predicted strength and actual strength.5. Repeat the previous steps 50 times.6. Report the mean and the standard deviation of the 50 mean squared errors.The four parts differed as follows in terms of whether or not the input data was normalized, the number of hidden layers, and the number of epochs over which the model was trained:- **Part A**: raw input data, 1 hidden layer, 50 epochs- **Part B**: normalized input data, 1 hidden layer, 50 epochs- **Part C**: normalized input data, 1 hidden layer, 100 epochs- **Part D**: normalized input data, 3 hidden layers, 50 epochs  Part A Download and clean the dataFirst step: download the dataset and save it as a pandas DataFrame.<jupyter_code>import pandas as pd

concrete_data = pd.read_csv("https://cocl.us/concrete_data")
concrete_data.head()<jupyter_output><empty_output><jupyter_text>Looks good. Next up, splitting between predictors (`X`) and target (`y`). Strength is the target variable.<jupyter_code>X = concrete_data[concrete_data.columns[concrete_data.columns != "Strength"]]
num_cols = X.shape[1] # Saving for later

y = concrete_data["Strength"]
X.head()
y.head()<jupyter_output><empty_output><jupyter_text>Build the neural networkNext I'll build a function to create the neural network, with the number of hidden layers as a parameter, since Part D uses three hidden layers.<jupyter_code>import keras
from keras.models import Sequential
from keras.layers import Dense


def regression_model(num_hidden_layers):
    model = Sequential()

    # Hidden layers
    model.add(Dense(10, activation="relu", input_shape=(num_cols,)))
    for i in range(num_hidden_layers - 1):
        model.add(Dense(10, activation="relu"))

    # Output layer
    model.add(Dense(1))

    model.compile(optimizer="adam", loss="mean_squared_error")
    return model<jupyter_output>Using TensorFlow backend.<jupyter_text>Split, train, evaluate &times; 50Now the fun part. I'm going to do the following 50 times:- Randomly split the data by holding 30% for testing.- Train a model on the training data over 50 epochs.- Evaluate the model on the test data and compute the mean squared error between predicted concrete strength and actual concrete strength.I'll save each of the 50 mean squared errors into a list.And come to think of it, I'm going to define a function for this process, because I'm going to need to repeat it in all the other parts of the assignment, just varying a few key parameters.<jupyter_code>from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from matplotlib import pyplot


def process_models(X, y, num_hidden_layers, num_epochs):
    mean_squared_errors = []

    for i in range(50):
        # Split the data
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

        # Train the model
        model = regression_model(num_hidden_layers)
        hist = model.fit(
            X_train,
            y_train,
            epochs=num_epochs,
            verbose=0,
            validation_data=(X_test, y_test),
        )

        # Test the model
        predictions = model.predict(X_test)

        # Find and save the mean squared error
        mean_squared_errors.append(mean_squared_error(y_test, predictions))

        print("Run #{} complete".format(i + 1))

    # Plot the loss from the last run just for fun
    pyplot.plot(hist.history["loss"])
    pyplot.plot(hist.history["val_loss"])
    pyplot.title("Model loss (run #50)")
    pyplot.ylabel("Loss")
    pyplot.xlabel("Epoch")
    pyplot.legend(["Train", "Test"], loc="upper right")
    pyplot.show()

    return mean_squared_errors


errors_a = process_models(X, y, 1, 50)<jupyter_output>Run #1 complete
Run #2 complete
Run #3 complete
Run #4 complete
Run #5 complete
Run #6 complete
Run #7 complete
Run #8 complete
Run #9 complete
Run #10 complete
Run #11 complete
Run #12 complete
Run #13 complete
Run #14 complete
Run #15 complete
Run #16 complete
Run #17 complete
Run #18 complete
Run #19 complete
Run #20 complete
Run #21 complete
Run #22 complete
Run #23 complete
Run #24 complete
Run #25 complete
Run #26 complete
Run #27 complete
Run #28 complete
Run #29 complete
Run #30 complete
Run #31 complete
Run #32 complete
Run #33 complete
Run #34 complete
Run #35 complete
Run #36 complete
Run #37 complete
Run #38 complete
Run #39 complete
Run #40 complete
Run #41 complete
Run #42 complete
Run #43 complete
Run #44 complete
Run #45 complete
Run #46 complete
Run #47 complete
Run #48 complete
Run #49 complete
Run #50 complete<jupyter_text>ResultsI'll make a function for this part, too.<jupyter_code>from statistics import mean, stdev


def report_results(mean_squared_errors):
    print(
        "The mean of the mean squared errors is {}".format(
            round(mean(mean_squared_errors), 3)
        )
    )
    print(
        "The standard deviation of the mean squared errors is {}".format(
            round(stdev(mean_squared_errors), 3)
        )
    )


report_results(errors_a)<jupyter_output>The mean of the mean squared errors is 347.606
The standard deviation of the mean squared errors is 363.603<jupyter_text>There you have it. I'll admit, those errors look pretty bad. I'm curious to see how the changes in the next three parts affect that.  Part BSame as Part A, but using a normalized version of the data. I'll normalize the predictors, then repeat the split/train/evaluate/repeatprocess, then report the results.<jupyter_code>X_norm = (X - X.mean()) / X.std()

errors_b = process_models(X_norm, y, 1, 50)
report_results(errors_b)<jupyter_output>Run #1 complete
Run #2 complete
Run #3 complete
Run #4 complete
Run #5 complete
Run #6 complete
Run #7 complete
Run #8 complete
Run #9 complete
Run #10 complete
Run #11 complete
Run #12 complete
Run #13 complete
Run #14 complete
Run #15 complete
Run #16 complete
Run #17 complete
Run #18 complete
Run #19 complete
Run #20 complete
Run #21 complete
Run #22 complete
Run #23 complete
Run #24 complete
Run #25 complete
Run #26 complete
Run #27 complete
Run #28 complete
Run #29 complete
Run #30 complete
Run #31 complete
Run #32 complete
Run #33 complete
Run #34 complete
Run #35 complete
Run #36 complete
Run #37 complete
Run #38 complete
Run #39 complete
Run #40 complete
Run #41 complete
Run #42 complete
Run #43 complete
Run #44 complete
Run #45 complete
Run #46 complete
Run #47 complete
Run #48 complete
Run #49 complete
Run #50 complete<jupyter_text>The mean of the mean squared errors is just the slightest bit higher than Part A, probably not significantly so. Curious. The standard deviation is much less, though. I assume the inputs vary less after normalizing?  Part CSame as Part B, but using 100 epochs for training. Here we go:<jupyter_code>errors_c = process_models(X_norm, y, 1, 100)
report_results(errors_c)<jupyter_output>Run #1 complete
Run #2 complete
Run #3 complete
Run #4 complete
Run #5 complete
Run #6 complete
Run #7 complete
Run #8 complete
Run #9 complete
Run #10 complete
Run #11 complete
Run #12 complete
Run #13 complete
Run #14 complete
Run #15 complete
Run #16 complete
Run #17 complete
Run #18 complete
Run #19 complete
Run #20 complete
Run #21 complete
Run #22 complete
Run #23 complete
Run #24 complete
Run #25 complete
Run #26 complete
Run #27 complete
Run #28 complete
Run #29 complete
Run #30 complete
Run #31 complete
Run #32 complete
Run #33 complete
Run #34 complete
Run #35 complete
Run #36 complete
Run #37 complete
Run #38 complete
Run #39 complete
Run #40 complete
Run #41 complete
Run #42 complete
Run #43 complete
Run #44 complete
Run #45 complete
Run #46 complete
Run #47 complete
Run #48 complete
Run #49 complete
Run #50 complete<jupyter_text>That significantly reduced both the mean _and_ the standard deviation of the mean squared errors compared to Part B. Nice.  Part DLast but not least, this one's the same as Part B but with _three_ hidden layers (each still with 10 nodes and ReLU activation). Sure am glad I made those functions in Part A.<jupyter_code>errors_d = process_models(X_norm, y, 3, 50)
report_results(errors_d)<jupyter_output>Run #1 complete
Run #2 complete
Run #3 complete
Run #4 complete
Run #5 complete
Run #6 complete
Run #7 complete
Run #8 complete
Run #9 complete
Run #10 complete
Run #11 complete
Run #12 complete
Run #13 complete
Run #14 complete
Run #15 complete
Run #16 complete
Run #17 complete
Run #18 complete
Run #19 complete
Run #20 complete
Run #21 complete
Run #22 complete
Run #23 complete
Run #24 complete
Run #25 complete
Run #26 complete
Run #27 complete
Run #28 complete
Run #29 complete
Run #30 complete
Run #31 complete
Run #32 complete
Run #33 complete
Run #34 complete
Run #35 complete
Run #36 complete
Run #37 complete
Run #38 complete
Run #39 complete
Run #40 complete
Run #41 complete
Run #42 complete
Run #43 complete
Run #44 complete
Run #45 complete
Run #46 complete
Run #47 complete
Run #48 complete
Run #49 complete
Run #50 complete
{'fraction_non_alphanumeric': 0.07078651685393259, 'fraction_numerical': 0.04576098059244127, 'mean_word_length': 4.391519823788546, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 27, 'https://': 2, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '499811', 'license': 'MIT', 'n_tokens_mistral': 3125, 'n_tokens_neox': 2792, 'n_words': 1349, 'path': 'complete_writeup.ipynb', 'repo_name': 'tywmick/coursera-neural-networks-assignment', 'size': '9790'}