<jupyter_start><jupyter_text>Project PromptIn this project you must create a web service that uses machine learning to make predictions based on the data set powerproduction available on Moodle. The goal is to produce a model that accurately predicts wind turbine power output from wind speed values, as in the data set. You must then develop a web service that will respond with predicted power values based on speed values sent as HTTP requests. Your submission must be in the form of a git repository containing, at a minimum, the following items:1. Jupyter notebook that trains a model using the data set. In the notebook you should explain your model and give an analysis of its accuracy.2. Python script that runs a web service based on the model, as above.3. Dockerfile to build and run the web service in a container.4. Standard items in a git repository such as a README.To enhance your submission, you might consider developing and comparing more than one model. Rest assured, all the above concepts will be explored in lecture videos and other materials in the coming semester. Approach1. Plot the data to visually analyze it.2. Based on the plot, we will see that there are a number of anomalies in the dataset. We will create models with and without these anomalies.3. Create a model incorporating the anomalies and calculate the model's mean squared error. This will involved trial and error.4. Create a model excluding the anomalies and calculate the model's mean squared error. This will involved trial and error.5. Compare both models' mean squared error and decide which is more appropriate to use.<jupyter_code># Neural networks.
import tensorflow.keras as kr

# to split data into training and testing sets
from sklearn.model_selection import train_test_split

# for evaluating models
from sklearn import metrics

# Numerical arrays
import numpy as np

# Data frames.
import pandas as pd

# Plotting
import matplotlib.pyplot as plt

# Plot style.
plt.style.use("ggplot")

# Make matplotlib show interactive plots in the notebook.
%matplotlib inline

# Plot size.
plt.rcParams['figure.figsize'] = [14, 8]
df = pd.read_csv('https://raw.githubusercontent.com/ianmcloughlin/2020A-machstat-project/master/dataset/powerproduction.csv')
# the first and last few rows have power values of zero, so we increase the number displayed
pd.set_option('display.min_rows', 30)
df
columns = df.columns
plt.plot(df[columns[0]], df[columns[1]], '.')
plt.suptitle(f'{columns[0]} vs. {columns[1]}')
plt.xlabel(f'{columns[0]}')
plt.ylabel(f'{columns[1]}')<jupyter_output><empty_output><jupyter_text>We can immediately see that we are dealing with a [Sigmoid-like function](https://en.wikipedia.org/wiki/Sigmoid_function):However, more relevant to our purpose is the even more basic fact that there is clearly a very strong relationship between speed and power, although it not linear but quadratic. The other important thing to notice is that there are a clear number of outliers in the sample where speed is relatively high but power is 0, and in particular, the last 10 points in the sample have power of zero, even though they have a very high speed value. Because these points clearly are not typical outliers in the sense that they all have the same y value and are so far from what we would expect them to be, there are really only three interpretations of their values:1. They were incorrectly measured (perhaps the power values simply weren't recorded, and thus were entered as zero.2. They values are accurate, and are explained by an external influence that over-rided the relationship between speed and power (e.g. perhaps their was no capacity on those days to store the electricity generated, so despite high speeds zero power was generated).It is clear that there is not a continuous relationship between speed and power; rather there is a point of discontinuity past which speed increases but power is always equal to zero. If we look at the values in our sample, a speed value of 24.399 is the highest speed value for which power is not zero. Erring on the side of caution, we can take that point as the discontinuity point. Thus the mathematical definition of the relationship between speed (x) and power (y) will be as follows:$x &gt; 0 \\y &gt; 0\\0 &lt; x &lt; 24.400, f(x) = y \\x &gt;= 24.400, f(x) = 0$When creating a function to predict power based on speed, we will likely have to manually use flow control to determine whether to simply set the power to zero, or to actually apply or machine learning algorithm.It is important also to consider here, however, the other points in our sample where speed is greater than 10 and yet power is zero. There is a legitimate question as to whether we should include or exclude those points. While it is possible that they were incorrectly measured (or power simply was not measured for those points at all and zero was entered in the transcription phase), it is also possible that some external influence may have over-rided the relationship between speed and power and caused power to be zero. Because these points are randomly dispersed and are few in number, the more responsible approach would be to assume the latter is true, and to include them in our calculations. This way, our models will account for that that on some occasions despite relatively high speeds, power can remain at zero.In the below cells, then, I will first show that the cost of our model (here the mean squared error) is dramatically decreased if we exclude the final ten data points, and that is a strong reason for preferring the model with those anomolous points excluded. Create the Models<jupyter_code># Create a neural network with one neuron.
model = kr.models.Sequential()
model.add(kr.layers.Dense(50, input_shape=(1,), activation="sigmoid", kernel_initializer='ones', bias_initializer='zeros'))
model.add(kr.layers.Dense(1, input_shape=(1,), activation="linear", kernel_initializer='ones', bias_initializer='zeros'))
model.compile(kr.optimizers.Adam(lr=0.0095), loss='mean_squared_error')
# split the dataset into training and testing data
# see here: https://realpython.com/train-test-split-python-data/
x_train, x_test, y_train, y_test = train_test_split(df[columns[0]][:490], df[columns[1]][:490], test_size=0.3)
# Train the neural network on our training data.
model.fit(x_train, y_train, epochs=350)
# Now let's see.
plt.plot(df[columns[0]], df[columns[1]], '.', label='actual')
plt.plot(x_test, model.predict(x_test), '.', label='prediction')
plt.suptitle("Total actual vs. Predicted tests")
plt.legend()
plt.show()

# Now let's see.
plt.plot(x_train, y_train, '.', label='actual')
plt.plot(x_test, model.predict(x_test), '.', label='prediction')
plt.suptitle("Training actual vs. Predicted tests")
plt.legend()
plt.show()
predictions = model.predict(x_test)

print("Mean squared error:")
print(metrics.mean_squared_error(y_test, predictions))<jupyter_output>Mean squared error:
84.52885171361159<jupyter_text>What is interesting is for values of speed greater than around 16, our model appears to consistently underestimate power. This is possibly due to thethree data points that have speed values of greater than 16 but power values of zero. If we remove those values from the dataset, we will likely get a 'better fitting' model. We can try this now, although even if we arrive at a better model, the removal of those anomolous data points is still not likely justified, as they may indeed be legimitate values that will simply be intractable for a model that only looks as speed and power<jupyter_code>newDF = df.loc[(df['speed']<10) | (df['power']!=0)]
newDF
# split the dataset into training and testing data
# see here: https://realpython.com/train-test-split-python-data/
x_train, x_test, y_train, y_test = train_test_split(newDF[columns[0]], newDF[columns[1]], test_size=0.3)
# Train the neural network on our training data.
model.fit(x_train, y_train, epochs=350)
# Now let's see.
plt.plot(newDF[columns[0]], newDF[columns[1]], '.', label='actual')
plt.plot(x_test, model.predict(x_test), '.', label='prediction')
plt.suptitle("Total actual vs. Predicted tests")
plt.legend()
plt.show()
predictions = model.predict(x_test)

print("Mean squared error:")
print(metrics.mean_squared_error(y_test, predictions))<jupyter_output>Mean squared error:
17.75374509856283<jupyter_text>On this occasion, the mean squared error is far smaller, hence we have a more accurate model. If we look at the graph, the underestimating of power when speed is greater than 16 has mostly disappeared. We thus appear to have a better model, but only if we assume that the zero-power points we removed were illegitimate measurements, and that is not necessarily a legitimate assumption.<jupyter_code>model.predict([[5]])[0][0]
model.predict([[20]])[0][0]
model.predict([[100]])[0][0]
model.predict([[-33]])[0][0]<jupyter_output><empty_output><jupyter_text>While our model accurately predicts power where speed is between zero and 24.4, for negative speed values it still provides a positive power value, which is not desired, and for high speed values it predicts power values of around 100, where the data indicates that any datapoints with speed values higher than 24.4 have power values of 0. We will thus create a function to account for the discontinuous relationship between speed and power.<jupyter_code>def predictPower(speed):
        try:
            speed = float(speed)
            if speed < 0:
                return "Speed cannot have a negative value."
            elif speed > 24.399:
                return "0"
            else:
                speed = np.array([speed])
                result = model.predict(speed)
                print(result[0][0])
                return str(result[0][0])
        except:
            return "Speed must be a number."
predictPower(5)
predictPower(20)
predictPower(100)
predictPower(-33)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07686100463990317, 'fraction_numerical': 0.017147468226750052, 'mean_word_length': 4.7511600928074245, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 20, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '240035', 'license': 'MIT', 'n_tokens_mistral': 2778, 'n_tokens_neox': 2589, 'n_words': 1307, 'path': 'Project.ipynb', 'repo_name': 'ashleycampion/GMIT_machine_learning_and_statistic_module', 'size': '9914'}