# -*- coding: utf-8 -*-
"""""" # for sphinx auto doc purposes
"""
| ----------------------------------------------------------------------------------------------------------------------
| Date : August 2018
| Copyright : © 2018 - 2020 by Tinne Cahy (Geo Solutions) and Ann Crabbé (KU Leuven)
| Email : acrabbe.foss@gmail.com
| Acknowledgements : Translated from LNNS 1.0 A neural network simulator [C++ software]
| Ghent University, Laboratory of Forest Management and Spatial Information Techniques
| Lieven P.C. Verbeke
|
| This file is part of the QGIS Neural Network MLP Classifier plugin and mlp-image-classifier python package.
|
| This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
| License as published by the Free Software Foundation, either version 3 of the License, or any later version.
|
| This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
| warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License (COPYING.txt). If not see www.gnu.org/licenses.
| ----------------------------------------------------------------------------------------------------------------------
"""
import numpy as np
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import classification_report, cohen_kappa_score
from sklearn.model_selection import train_test_split
[docs]class Network:
"""
The Network class create a neural network using the sklearn.neural_network.MLPClassifier.
The network can be used to predict classified images using supervised classification.
"""
def __init__(self, number_of_hidden, activation):
"""
The number of input and number of output neurons are not required for MLPClassifier. It extracts this
information from the training data set.
:param Tuple[int] number_of_hidden: Number of hidden neurons, multiple layers are possible (see Tuple notation)
:param activation: str Activation function for the MLPClassifier. Possible values are
'identity', 'logistic', 'tanh', 'relu'
"""
self.number_of_hidden = number_of_hidden
self.network = MLPClassifier(hidden_layer_sizes=number_of_hidden, random_state=100,
activation=activation, learning_rate_init=0.1, learning_rate='constant',
momentum=0, tol=0)
self.validation_results = None
self.unique_classes = None
[docs] def validate(self, x_test, y_test, log_function=print):
"""
The trained neural network can be evaluated using a test set of the data. The validation results will be saved
in the validation_results dictionary with the keys: Average accuracy, a Kappa for each predicted class,
an average kappa and a rapport including precision, recall, f1-score and support.
Example output:
.. line-block::
Average accuracy: 0.9740708729472775
Kappa class 1: 0.9996048676750681
Kappa class 2: 0.9969686283161031
Kappa class 3: 0.9269671354418852
Kappa class 4: 0.9384942730689072
Kappa class 5: 0.9854574554108237
Kappa class 6: 0.8991142021469177
Average Kappa: 0.9577677603432843
=========== ========= ====== ======== =======
. precision recall f1-score support
=========== ========= ====== ======== =======
0 1.00 1.00 1.00 1664
1 1.00 1.00 1.00 956
2 0.91 0.96 0.93 597
3 0.96 0.94 0.95 1149
4 1.00 0.98 0.99 2345
5 0.88 0.92 0.90 231
avg / total 0.98 0.98 0.98 6942
=========== ========= ====== ======== =======
:param np.array[float] x_test: test input variables
:param np.array[float] y_test: test output values
:param log_function: function to log
"""
self.validation_results = {'average_accuracy': self.network.score(x_test, y_test)}
log_function("Average accuracy: {}".format(self.validation_results['average_accuracy']))
predictions = self.network.predict(x_test)
y_test = np.array(y_test, dtype=np.int)
mean_kappa = 0
for i in range(0, y_test.shape[1]):
kappa = cohen_kappa_score(y_test[:, [i]], predictions[:, [i]])
self.validation_results['kappa_class_{}'.format(i + 1)] = kappa
log_function("Kappa class {}: {}".format(i + 1, kappa))
mean_kappa += kappa
self.validation_results['average_kappa'] = mean_kappa/6
log_function("Average Kappa: {}".format(self.validation_results['average_kappa']))
self.validation_results['report'] = classification_report(y_test, predictions)
log_function("\n" + self.validation_results['report'])
[docs] def train_image(self, band_data, classes_data, max_iter, no_data_value=0, test_size=0.33, log_function=print):
"""
The given network can be trained given an image of different band waves (band_data) and a respectively data set
indicating a subset of different classes of the image (class_data).
:param np.array band_data: array with all bands
:param np.array classes_data: an overlap image indicating different classes
:param int max_iter: the number of iterations when training the neural network
:param int no_data_value: value that describes pixels with no data in the classes_data file
:param float test_size: the proportion of the test_size that will be used to evaluate the trained network
:param log_function: function to log
"""
if band_data.ndim < 2:
raise ValueError("This method requires more than one band.")
# flatten input
bands_flattened = band_data.reshape(len(band_data), -1).transpose()
classes_flattened = classes_data.flatten()
del band_data
del classes_data
# useful indices
good_indices = np.where(classes_flattened != no_data_value)[0]
# prepare x and y as trainings input
x = bands_flattened[good_indices, :]
classes = classes_flattened[good_indices]
self.unique_classes = np.unique(classes)
y = np.zeros((len(good_indices), len(self.unique_classes)))
for i, this_class in enumerate(self.unique_classes):
find_class = np.where(classes == this_class)
y[find_class, i] = 1
self.network.max_iter = max_iter
if test_size:
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=test_size)
self.network.fit(x_train, y_train)
self.validate(x_test, y_test, log_function)
else:
self.network.fit(x, y)
[docs] def predict_image(self, band_data, probability_of_class=None):
"""
Use the trained network, it is possible to classify the complete image using the different bands of the image.
If probability_of_class is given, than the image will show the probability that this class will occur.
:param np.array band_data: array with all bands
:param int probability_of_class: class for which you would like the probability image
:return: np.array classified_image: the classified image
"""
# predict classes or get probability for every row of the image
bands_flattened = band_data.reshape(len(band_data), -1).transpose()
prediction = self.network.predict_proba(bands_flattened)
if probability_of_class is None:
# set image pixel equal to the class with highest probability
best_prediction = np.argmax(prediction, axis=1)
output_flattened = self.unique_classes[best_prediction]
else:
# set the image pixel equal to the probability of the given class
class_location = np.where(self.unique_classes == probability_of_class)[0][0]
output_flattened = prediction[:, class_location]
return output_flattened.transpose().reshape(band_data[0].shape)