The Iris Plants Classification project is like the "Hello World" of Machine Learning - it consists of training a model to recognise different species of iris plants based on their characteristics, using the data of 150 plants.
Now, decomposing the problem:
Using Python, we'll import TensorFlow (to download the dataset and build the model), pandas (for dataframe manipulation) and NumPy (to pick out the most likely species from each prediction).
import tensorflow as tf
import pandas as pd
import numpy as npimport tensorflow as tf
import pandas as pd
import numpy as npThe dataset itself comes from the UCI Machine Learning Repository. We'll create a function that uses TensorFlow's get_file to download it from that URL (caching it locally so it only downloads once) and returns the path to the downloaded file.
def retrieve_iris_data():
iris_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
cache_dir = '.'
cache_subdir = 'data'
iris_file = tf.keras.utils.get_file('iris.data', iris_url, cache_dir=cache_dir, cache_subdir=cache_subdir)
return iris_filedef retrieve_iris_data():
iris_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
cache_dir = '.'
cache_subdir = 'data'
iris_file = tf.keras.utils.get_file('iris.data', iris_url, cache_dir=cache_dir, cache_subdir=cache_subdir)
return iris_fileWe need to create a pandas dataframe that the ML model can process, so we will make column names to facilitate making the dataframe. Since machine learning models need numerical values to process, we'll also create a dictionary that maps each of the plant names to a number.
iris_features = ['sepal_length', 'sepal_width', 'petal_length','petal_width', 'species']
label_map = {'Iris-setosa':0, 'Iris-versicolor':1, 'Iris-virginica':2}iris_features = ['sepal_length', 'sepal_width', 'petal_length','petal_width', 'species']
label_map = {'Iris-setosa':0, 'Iris-versicolor':1, 'Iris-virginica':2}Now we can create a function that processes and returns a dataframe version of the dataset.
def parse_iris_data(iris_path):
iris_df = pd.read_csv(iris_path, names=iris_features)
iris_df['species'].replace(label_map, inplace=True)
return iris_dfdef parse_iris_data(iris_path):
iris_df = pd.read_csv(iris_path, names=iris_features)
iris_df['species'].replace(label_map, inplace=True)
return iris_dfThen we can assign variables to each of the functions to ultimately create our dataframe:
iris_filepath = retrieve_iris_data()
iris_df = parse_iris_data(iris_filepath)iris_filepath = retrieve_iris_data()
iris_df = parse_iris_data(iris_filepath)To split our dataset into the training dataset and the testing dataset, we'll take the first 10 values for each species to create the testing dataset and the other 40 of each species to create the training dataset - that way 20% of our original dataset is the testing dataset and the other 80% is the training dataset, with every species represented equally in both.
This isn't a random split, though. It works here because the rows for each species aren't in any meaningful order, but the standard approach is a shuffled, stratified split (for example scikit-learn's train_test_split with stratify), which avoids accidentally testing on an unrepresentative slice of the data.
# Split up test data and training data
test_data = iris_df.groupby('species').head(10) # test data (20% of the total data)
iris_df = iris_df.groupby('species').tail(40) # training data# Split up test data and training data
test_data = iris_df.groupby('species').head(10) # test data (20% of the total data)
iris_df = iris_df.groupby('species').tail(40) # training dataThen we split the training and testing datasets into features (the measurements) and labels (the species): during training, the model guesses the species from the features, compares its guess with the label, and adjusts itself based on how wrong it was.
# Split up test data and training data into their features and labels
test_features = test_data.drop('species', axis=1)
test_labels = test_data.drop(['sepal_length', 'sepal_width', 'petal_length','petal_width'], axis=1)
training_features = iris_df.drop('species', axis=1)
training_labels = iris_df.drop(['sepal_length', 'sepal_width', 'petal_length','petal_width'], axis=1)# Split up test data and training data into their features and labels
test_features = test_data.drop('species', axis=1)
test_labels = test_data.drop(['sepal_length', 'sepal_width', 'petal_length','petal_width'], axis=1)
training_features = iris_df.drop('species', axis=1)
training_labels = iris_df.drop(['sepal_length', 'sepal_width', 'petal_length','petal_width'], axis=1)First we will create a neural network with 4 input nodes (one for each measurement), 2 hidden layers of 10 nodes (where the processing happens) and an output layer of 3 nodes (one for each species).
iris_model = tf.keras.Sequential([
tf.keras.layers.Dense(10, activation=tf.nn.relu, input_shape=(4,)),
tf.keras.layers.Dense(10, activation=tf.nn.relu),
tf.keras.layers.Dense(3)
])
iris_model.summary()Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_6 (Dense) (None, 10) 50
dense_7 (Dense) (None, 10) 110
dense_8 (Dense) (None, 3) 33
=================================================================
Total params: 193 (772.00 Byte)
Trainable params: 193 (772.00 Byte)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________iris_model = tf.keras.Sequential([
tf.keras.layers.Dense(10, activation=tf.nn.relu, input_shape=(4,)),
tf.keras.layers.Dense(10, activation=tf.nn.relu),
tf.keras.layers.Dense(3)
])
iris_model.summary()Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_6 (Dense) (None, 10) 50
dense_7 (Dense) (None, 10) 110
dense_8 (Dense) (None, 3) 33
=================================================================
Total params: 193 (772.00 Byte)
Trainable params: 193 (772.00 Byte)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________Then we compile the network, which configures how it will learn: the optimiser (Adam) that adjusts the weights, the loss function that measures how wrong its predictions are, and the metric (accuracy) that we want to track.
iris_model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])iris_model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])Now we can train the model. (The output below is from a second run on a model that had already been trained, which is why the accuracy is already high in the first epoch - on a fresh model it starts much lower and climbs over the 100 epochs.)
iris_model.fit(training_features, training_labels, verbose=2, batch_size=16, epochs=100)Epoch 1/100 8/8 - 0s - loss: 0.0826 - sparse_categorical_accuracy: 0.9750 - 23ms/epoch - 3ms/step Epoch 2/100 8/8 - 0s - loss: 0.0816 - sparse_categorical_accuracy: 0.9833 - 44ms/epoch - 5ms/step Epoch 3/100 8/8 - 0s - loss: 0.0811 - sparse_categorical_accuracy: 0.9833 - 23ms/epoch - 3ms/step Epoch 4/100 8/8 - 0s - loss: 0.0822 - sparse_categorical_accuracy: 0.9750 - 39ms/epoch - 5ms/step Epoch 5/100 8/8 - 0s - loss: 0.0820 - sparse_categorical_accuracy: 0.9833 - 30ms/epoch - 4ms/step ... Epoch 96/100 8/8 - 0s - loss: 0.0721 - sparse_categorical_accuracy: 0.9750 - 34ms/epoch - 4ms/step Epoch 97/100 8/8 - 0s - loss: 0.0726 - sparse_categorical_accuracy: 0.9833 - 49ms/epoch - 6ms/step Epoch 98/100 8/8 - 0s - loss: 0.0717 - sparse_categorical_accuracy: 0.9833 - 34ms/epoch - 4ms/step Epoch 99/100 8/8 - 0s - loss: 0.0727 - sparse_categorical_accuracy: 0.9833 - 26ms/epoch - 3ms/step Epoch 100/100 8/8 - 0s - loss: 0.0715 - sparse_categorical_accuracy: 0.9833 - 23ms/epoch - 3ms/step
iris_model.fit(training_features, training_labels, verbose=2, batch_size=16, epochs=100)Epoch 1/100 8/8 - 0s - loss: 0.0826 - sparse_categorical_accuracy: 0.9750 - 23ms/epoch - 3ms/step Epoch 2/100 8/8 - 0s - loss: 0.0816 - sparse_categorical_accuracy: 0.9833 - 44ms/epoch - 5ms/step Epoch 3/100 8/8 - 0s - loss: 0.0811 - sparse_categorical_accuracy: 0.9833 - 23ms/epoch - 3ms/step Epoch 4/100 8/8 - 0s - loss: 0.0822 - sparse_categorical_accuracy: 0.9750 - 39ms/epoch - 5ms/step Epoch 5/100 8/8 - 0s - loss: 0.0820 - sparse_categorical_accuracy: 0.9833 - 30ms/epoch - 4ms/step ... Epoch 96/100 8/8 - 0s - loss: 0.0721 - sparse_categorical_accuracy: 0.9750 - 34ms/epoch - 4ms/step Epoch 97/100 8/8 - 0s - loss: 0.0726 - sparse_categorical_accuracy: 0.9833 - 49ms/epoch - 6ms/step Epoch 98/100 8/8 - 0s - loss: 0.0717 - sparse_categorical_accuracy: 0.9833 - 34ms/epoch - 4ms/step Epoch 99/100 8/8 - 0s - loss: 0.0727 - sparse_categorical_accuracy: 0.9833 - 26ms/epoch - 3ms/step Epoch 100/100 8/8 - 0s - loss: 0.0715 - sparse_categorical_accuracy: 0.9833 - 23ms/epoch - 3ms/step
After having trained itself on 80% of the original dataset, we now need to test it on data it hasn't seen before, to check that all of the weights it configured for each node are actually accurate. First we can create a predicting function that will help us to evaluate the model:
iris_labels = ['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']
def predict(features, labels):
predictions = iris_model.predict(features)
pred_df = pd.DataFrame(columns=['Actual','Prediction','Correct'])
pred_df['Actual'] = [iris_labels[x] for x in labels]
pred_df['Prediction'] = [iris_labels[x] for x in [np.argmax(p) for p in predictions]]
pred_df['Correct'] = pred_df['Actual'] == pred_df['Prediction']
return pred_dfiris_labels = ['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']
def predict(features, labels):
predictions = iris_model.predict(features)
pred_df = pd.DataFrame(columns=['Actual','Prediction','Correct'])
pred_df['Actual'] = [iris_labels[x] for x in labels]
pred_df['Prediction'] = [iris_labels[x] for x in [np.argmax(p) for p in predictions]]
pred_df['Correct'] = pred_df['Actual'] == pred_df['Prediction']
return pred_dfThen we evaluate the model's performance on the test dataset:
iris_model.evaluate(test_features, test_labels)
test_predictions = predict(test_features, test_labels['species'].to_list())
print(test_predictions)1/1 [==============================] - 0s 137ms/step - loss: 0.1743 - sparse_categorical_accuracy: 1.0000
1/1 [==============================] - 0s 18ms/step
Actual Prediction Correct
0 Iris-setosa Iris-setosa True
1 Iris-setosa Iris-setosa True
2 Iris-setosa Iris-setosa True
3 Iris-setosa Iris-setosa True
4 Iris-setosa Iris-setosa True
5 Iris-setosa Iris-setosa True
6 Iris-setosa Iris-setosa True
7 Iris-setosa Iris-setosa True
8 Iris-setosa Iris-setosa True
9 Iris-setosa Iris-setosa True
10 Iris-versicolor Iris-versicolor True
11 Iris-versicolor Iris-versicolor True
12 Iris-versicolor Iris-versicolor True
13 Iris-versicolor Iris-versicolor True
14 Iris-versicolor Iris-versicolor True
15 Iris-versicolor Iris-versicolor True
16 Iris-versicolor Iris-versicolor True
17 Iris-versicolor Iris-versicolor True
18 Iris-versicolor Iris-versicolor True
19 Iris-versicolor Iris-versicolor True
20 Iris-virginica Iris-virginica True
21 Iris-virginica Iris-virginica True
22 Iris-virginica Iris-virginica True
23 Iris-virginica Iris-virginica True
24 Iris-virginica Iris-virginica True
25 Iris-virginica Iris-virginica True
26 Iris-virginica Iris-virginica True
27 Iris-virginica Iris-virginica True
28 Iris-virginica Iris-virginica True
29 Iris-virginica Iris-virginica Trueiris_model.evaluate(test_features, test_labels)
test_predictions = predict(test_features, test_labels['species'].to_list())
print(test_predictions)1/1 [==============================] - 0s 137ms/step - loss: 0.1743 - sparse_categorical_accuracy: 1.0000
1/1 [==============================] - 0s 18ms/step
Actual Prediction Correct
0 Iris-setosa Iris-setosa True
1 Iris-setosa Iris-setosa True
2 Iris-setosa Iris-setosa True
3 Iris-setosa Iris-setosa True
4 Iris-setosa Iris-setosa True
5 Iris-setosa Iris-setosa True
6 Iris-setosa Iris-setosa True
7 Iris-setosa Iris-setosa True
8 Iris-setosa Iris-setosa True
9 Iris-setosa Iris-setosa True
10 Iris-versicolor Iris-versicolor True
11 Iris-versicolor Iris-versicolor True
12 Iris-versicolor Iris-versicolor True
13 Iris-versicolor Iris-versicolor True
14 Iris-versicolor Iris-versicolor True
15 Iris-versicolor Iris-versicolor True
16 Iris-versicolor Iris-versicolor True
17 Iris-versicolor Iris-versicolor True
18 Iris-versicolor Iris-versicolor True
19 Iris-versicolor Iris-versicolor True
20 Iris-virginica Iris-virginica True
21 Iris-virginica Iris-virginica True
22 Iris-virginica Iris-virginica True
23 Iris-virginica Iris-virginica True
24 Iris-virginica Iris-virginica True
25 Iris-virginica Iris-virginica True
26 Iris-virginica Iris-virginica True
27 Iris-virginica Iris-virginica True
28 Iris-virginica Iris-virginica True
29 Iris-virginica Iris-virginica TrueIt classified all 30 test plants correctly! That's encouraging, although 30 samples is a small test set, so one or two mistakes would change the accuracy a lot.
Thanks for reading - I hope this helps, and don't forget to check out the full source code on GitHub too!