На этом шаге мы рассмотрим, как "видит" нейронная сеть цифру "0".
Посмотрим, что произойдет, если выполнить обратный запрос для маркера "0", т.е. мы предоставляем выходным узлам значения, равные 0,01, за исключением первого узла, соответствующего маркеру "0", которому мы предоставляем значение 0,99. Иными словами, мы передаем на выходные узлы массив чисел [0.99, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]. Ниже показано изображение, полученное на входных узлах.
Рис.1. Изображение, полученное на входных узлах
Это уже интересно! Так "видит" картинку "мозг" нашей нейронной сети.
Как расценивать полученное изображение? Как его следует интерпретировать?
Основное, что сразу же бросается в глаза, - округлая форма изображения. Это соответствует истине, поскольку мы интересовались у нейронной сети, каков тот идеальный вопрос, ответом на который будет "0".
Кроме того, на изображении можно выделить темные, светлые и серые области.
Итак, в общих чертах нам удалось достигнуть некоторого понимания того, как именно сформировалось умение сети классифицировать изображения с маркером "0".
Нам выпала редкостная удача заглянуть внутрь нейронной сети, поскольку в случае сетей с более сложной структурой и большим количеством слоев, а также в случае более сложных задач вряд ли можно рассчитывать на столь же легкую интерпретацию результатов.
Ниже приведен полный текст использованной нейронной сети.
[In 1]: # Код для создания 3-слойной нейронной сети вместе с # кодом для ее обучения с помощью набора данных MNIST. # (с) Tariq Rashid, 2016 # лицензия GPLv2 import numpy # библиотека scipy.special содержит сигмоиду expit() import scipy.special # библиотека для графического отображения массивов import matplotlib.pyplot # гарантировать размещение графики в данном блокноте, # а не в отдельном окне %matplotlib inline # определение класса нейронной сети class neuralNetwork: # инициализировать нейронную сеть def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate): # задать количество узлов во входном, скрытом и выходном слое self.inodes = inputnodes self.hnodes = hiddennodes self.onodes = outputnodes # Матрицы весовых коэффициентов связей wih (между входным и скрытым # слоями) и who (между скрытым и выходным слоями). # Весовые коэффициенты связей между узлом i и узлом j следующего слоя # обозначены как w_i_j: # w11 w21 # w12 w22 и т.д. self.wih = numpy.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes, self.inodes)) self.who = numpy.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes, self.hnodes)) # коэффициент обучения self.lr = learningrate # использование сигмоиды в качестве функции активации self.activation_function = lambda x: scipy.special.expit(x) # тренировка нейронной сети def train(self, inputs_list, targets_list): # преобразовать список входных значений в двухмерный массив inputs = numpy.array(inputs_list, ndmin=2).T targets = numpy.array(targets_list, ndmin=2).T # рассчитать входящие сигналы для скрытого слоя hidden_inputs = numpy.dot(self.wih, inputs) # рассчитать исходящие сигналы для скрытого слоя hidden_outputs = self.activation_function(hidden_inputs) # рассчитать входящие сигналы для выходного слоя final_inputs = numpy.dot(self.who, hidden_outputs) # рассчитать исходящие сигналы для выходного слоя final_outputs = self.activation_function (final_inputs) # ошибки выходного слоя = # (целевое значение - фактическое значение) output_errors = targets - final_outputs # ошибки скрытого слоя - это ошибки output_errors, # распределенные пропорционально весовым коэффициентам связей # и рекомбинированные на скрытых узлах hidden_errors = numpy.dot(self.who.T, output_errors) # обновить весовые коэффициенты для связей между # скрытым и выходным слоями self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose (hidden_outputs)) # обновить весовые коэффициенты для связей между # входным и скрытым слоями self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs)) # опрос нейронной сети def query(self, inputs_list): # преобразовать список входных значений # в двухмерный массив inputs = numpy.array(inputs_list, ndmin=2).T # рассчитать входящие сигналы для скрытого слоя hidden_inputs = numpy.dot(self.wih, inputs) # рассчитать исходящие сигналы для скрытого слоя hidden_outputs = self.activation_function(hidden_inputs) # рассчитать входящие сигналы для выходного слоя final_inputs = numpy.dot(self.who, hidden_outputs) # рассчитать исходящие сигналы для выходного слоя final_outputs = self.асtivation_function(final_inputs) return final_outputs # обратный запрос нейронной сети # мы будем использовать одну и ту же терминологию для каждого элемента, # например, цель - это значения в правой части сети, хотя и используемые # в качестве входных данных. # например, скрытый_выход - это сигнал справа от средних узлов def backquery(self, targets_list): # перенести список целей final_outputs = numpy.array(targets_list, ndmin=2).T # вычислить сигнал в результирующем выходном слое final_inputs = self.inverse_activation_function(final_outputs) # вычислить сигнал из скрытого слоя hidden_outputs = numpy.dot(self.who.T, final_inputs) # масштабировать снова от 0,01 до 0,99 hidden_outputs -= numpy.min(hidden_outputs) hidden_outputs /= numpy.max(hidden_outputs) hidden_outputs *= 0.98 hidden_outputs += 0.01 # рассчитать сигнал скрытого слоя hidden_inputs = self.inverse_activation_function(hidden_outputs) # вычислить сигнал из входного слоя inputs = numpy.dot(self.wih.T, hidden_inputs) # масштабировать снова от 0,01 до 0,99 inputs -= numpy.min(inputs) inputs /= numpy.max(inputs) inputs *= 0.98 inputs += 0.01 return inputs
[In 2]: # количество входных, скрытых и выходных узлов input_nodes = 784 hidden_nodes = 200 output_nodes = 10 # коэффициент обучения learning_rate = 0.1 # создать экземпляр нейронной сети n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
[In 3]: # загрузить в список тестовый набор данных CSV-файла набора MNIST training_data_file = open("D:/NN/mnist_train.csv", 'r') training_data_list = training_data_file.readlines() training_data_file.close()
[In 4]: # тренировка нейронной сети # переменная epochs указывает, сколько раз тренировочный # набор данных используется для тренировки сети epochs = 5 for е in range(epochs): # перебрать все записи в тренировочном наборе данных for record in training_data_list: # получить список значений, используя символы запятой (',') # в качестве разделителей all_values = record.split(',') # масштабировать и сместить входные значения inputs = (numpy.asfarray(all_values[1:]) / 255 * 0.99) + 0.01 # создать целевые выходные значения (все равны 0,01, за исключением # желаемого маркерного значения, равного 0,99) targets = numpy.zeros(output_nodes) + 0.01 # all_values[0] - целевое маркерное значение для данной записи targets[int(all_values[0])] = 0.99 n.train(inputs, targets)
[In 5]: # загрузить в список тестовый набор данных CSV-файла набора MNIST test_data_file = open("D:/NN/mnist_test.csv", 'r') test_data_list = test_data_file.readlines() test_data_file.close()
[In 6]: # тестирование нейронной сети # журнал оценок работы сети, первоначально пустой scorecard = [] # перебрать все записи в тестовом наборе данных for record in test_data_list: # получить список значений из записи, используя символы # запятой (',') в качестве разделителей all_values = record.split(',') # правильный ответ - первое значение correct_label = int(all_values[0]) # масштабировать и сместить входные значения inputs = (numpy.asfarray(all_values[1:]) / 255 * 0.99) + 0.01 # опрос сети outputs = n.query(inputs) # индекс наибольшего значения является маркерным значением label = numpy.argmax(outputs) # присоединить оценку ответа сети к концу списка if (label == correct_label): # в случае правильного ответа сети присоединить # к списку значение 1 scorecard.append(1) else: # в случае неправильного ответа сети присоединить # к списку значение 0 scorecard.append(0)
[In 7]: # рассчитать показатель эффективности в виде # доли правильных ответов scorecard_array = numpy.asarray(scorecard) print("эффективность = ", scorecard_array.sum() / scorecard_array.size)
[In 8]: # запустить сеть в обратном направлении c учетом метки, # посмотреть, какое изображение она создаст # метка для теста label = 0 # задать выходные сигналы для этой метки targets = numpy.zeros(output_nodes) + 0.01 # all_values[0] - целевая метка для данной записи targets[label] = 0.99 print(targets) # получить изображение image_data = n.backquery(targets) # построить изображение результата matplotlib.pyplot.imshow(image_data.reshape(28, 28), cmap='Greys', interpolation='None')
На следующем шаге мы рассмотрим, как "видит" нейронная сеть остальные изображения.