Визуализируйте процесс принятия решения (веса) нейронной сети.

Нейронные сети (НС) часто считаются «черным ящиком», а это означает, что мы не можем легко определить, как именно они принимают решения. Учитывая, что НС хранят свои знания в своих весах, имеет смысл, что их проверка должна раскрыть некоторые идеи о процессе принятия ими решения.

В этой статье мы собираемся обучить NN, которые распознают рукописные числа (0–9), а затем открывают свой «черный ящик», визуализируя их веса.

Весь код написан на Python, его можно найти здесь.

Чтение рукописных цифр с помощью нейронной сети

Мы собираемся использовать рукописные цифры, которые хранятся в базе данных MNIST. Каждая цифра отображается в оттенках серого и имеет размер 28x28 пикселей (ширина x высота) = 784 пикселя в целом. Кроме того, каждый пиксель может принимать любое значение от 0 (черный) до 255 (белый). Любое значение между [1,254] ​​соответствует другому оттенку серого.

Затем нам нужно правильное представление каждой рукописной цифры, чтобы ее могли прочитать наши сети. Самый простой способ - создать вектор (x), который будет содержать 784 значения каждого изображения, например. x = [12,0,0,…, 234] (считывание значений пикселей слева направо и сверху вниз).

Что касается топологии нашей NN, она будет включать 1 выходной нейрон, который будет получать 784 значения пикселей (входной слой) из рукописной цифры (см. Ниже). Выходной нейрон выдаст вероятность того, что данное изображение (x) соответствует числу, которое он был обучен распознавать. Таким образом, нам потребуется 10 разных сетевых сетей для распознавания всех чисел (0–9) из базы данных MNIST (эта стратегия известна как Один против всех в задачах мультиклассовой классификации).

Как видно из приведенного выше изображения, каждый пиксель имеет связанный вес (w) с нижним индексом, указывающим пиксель происхождения (например, w1 соответствует самому первому пикселю). Именно эти веса покажут, как нейрон обрабатывает каждое представленное ему изображение.

Интерпретация весов

Математически выходной нейрон умножает каждый весовой коэффициент на соответствующий ему пиксель, а затем суммирует все произведения плюс смещение (b). Однако мы можем использовать уловку и упростить эту операцию, добавив дополнительный вес (w0), а затем введя мнимый пиксель со значением +1.

Вам знакомо это преобразование? действительно, это знаменитый скалярный продукт из физики, который включает два вектора . Короче говоря, скалярные произведения имеют тенденцию увеличиваться, когда два вектора указывают в одном направлении ¹. Эквивалентный способ выражения скалярного произведения - это умножение длин каждого вектора между собой на косинус угла между ними (a⋅b = ∥a∥∥b∥cos (θ). Если два вектора точно выровнены, то угол будет равен 0, а функция косинуса максимизирована (cos (0) = 1).

Вам может быть интересно, почему мы вдруг заговорили о векторах. Что ж, оказывается, каждую рукописную цифру можно визуализировать как вектор в 784-мерном пространстве (см. Ниже). Понимание этой идеи имеет решающее значение для интерпретации весов из наших сетевых сетей.

Заманчиво думать, что одни и те же написанные от руки числа (например, все 3 в нашем наборе данных) могут иметь одинаковые значения пикселей, и, следовательно, их векторы будут иметь тенденцию указывать в одном направлении². Если это так, то значения косинуса между парами троек будут больше, чем значения косинуса между 3 и 6. Давайте разберемся.

В приведенном выше графике я взял один пример из «3», а затем вычислил косинус угла относительно других чисел MNIST (я рассмотрел только экземпляры, найденные в первых 1000 примерах). Как видите, наша интуиция оправдалась. Вектор «3» близок к другим векторам, которые являются изображениями 3, и, таким образом, косинус угла между ними самый высокий (угол между ними самый низкий). С другой стороны, значение косинуса уменьшается по сравнению с другими числами MNIST, указывая на то, что они находятся дальше в 784-мерном пространстве.

Есть еще одна часть вычислений, которую я не обсуждал, - это сигмовидная функция. Эта функция сжимает любое действительное число (в данном случае Z) в диапазоне 0–1. Чем выше значение Z, тем ближе значение к 1.

Как видно из сигмоидной функции выше, выходной нейрон хочет генерировать большие значения Z, чтобы он мог правильно получить метку (значения, близкие к 1) ³. Вот ключевой вопрос: когда наш выходной нейрон будет назначать большие значения Z положительным примерам? когда скалярное произведение между вектором изображения и вектором веса является самым высоким. Таким образом, это происходит, когда весовой вектор близок к векторам изображения, рассматриваемым как положительные примеры.

Так же, как изображения могут быть помещены в 784-мерное пространство, нет причин, по которым W ( который имеет 784 компонента) не может быть помещен в одно и то же гипермерное пространство. Как будет выглядеть вектор W, если визуализировать его как изображение? действительно, как 3! то же самое произойдет с любым другим NN, обученным распознавать любую другую цифру MNIST. Во время обучения NN будет перемещать весовой вектор через 784-мерное пространство, и наилучшие результаты будут достигнуты, когда она разместит его рядом с положительными примерами (максимизируя скалярный продукт).

Обучение нашей нейронной сети и визуализация ее весов

Пришло время проверить эту теорию (полный код Python можно найти здесь, а файлы данных - здесь). Сначала прочтите первые 150 рукописных чисел в MNIST и сохраните их в X вместе с соответствующими ярлыками в y. Нам также необходимо нормализовать значения пикселей изображений для облегчения процесса обучения (я просто вычитаю среднее значение и делю на стандартное отклонение). Что касается меток, поскольку мы собираемся обучить нашу NN распознавать только число 3 (y = 1), мы должны присвоить y = 0 всем остальным числам.

image_size = 28  # 28x28 pixel images
num_images = 500 # Images to read
num_class = 3    # Number to recognise
# Reading handwritten digits
f = gzip.open('train-images-idx3-ubyte.gz','r') # File with digits
f.read(16)  # Offset for numbers 
buf = f.read(image_size * image_size * num_images)
X = np.frombuffer(buf, dtype=np.uint8).astype(np.float32)
X = data.reshape(num_images, image_size*image_size)
X = [normIM(x) for x in X_imgs] # Normalising images
# Reading labels
f = gzip.open('train-labels-idx1-ubyte.gz','r') # File with labels
f.read(8)   # Offset for labels 
buf = f.read(num_images)
y = np.frombuffer(buf, dtype=np.uint8).astype(np.int64)
y = [1 if y_i==num_class else 0 for y_i in y_lab]

Теперь давайте соединим наш NN с Керасом. У нас есть только 1 нейрон с 784 входами и сигмовидной активацией.

model = Sequential()
model.add(Dense(1, input_dim=784, activation='sigmoid'))
model.compile(loss='binary_crossentropy',optimizer='RMSprop', metrics=['accuracy'])

Наконец, тренируем нашу НС.

model.fit(X, y, epochs=250, class_weight=’balanced’,verbose=True)

После этого шага вы можете восстановить веса из модели и визуализировать их с помощью:

model.get_weights()[0] # The 784 weights
model.get_weights()[1] # The bias (b)
w=[i[0] for i in model.get_weights()[0]]+model.get_weights()[1]
plt.imshow(w.reshape(1,28,28)[0],cmap='gray')
plt.show()

Когда я повторяю эту процедуру для всех чисел MNIST, я могу визуализировать 10 весовых векторов для каждой из 10 цифр.

Как видите, для каждой NN соответствующие веса выглядят как числа, которые они пытаются распознать. Причина в том, что когда вы проецируете эти веса в 784-мерное пространство, они будут располагаться близко к целевым числам, чтобы максимизировать скалярное произведение и, таким образом, Z, подталкивая сигмовидную функцию для вывода значений ближе до 1 (и уменьшить ошибку обучения).

Заключение

В этом посте я попытался объяснить интуицию, лежащую в основе весов нейронных сетей. В частности, я проанализировал веса простейшей нейронной сети, состоящей из одного выходного нейрона. Интерпретация весов более сложных нейронных сетей - непростая задача, однако при работе с изображениями визуализация весов может дать вам некоторое представление. Например, известно, что глубокие нейронные сети изучают особенности изображений, например глаза от лиц и это обучение можно контролировать с помощью визуализации веса.

  1. Это не всегда верно, потому что величина скалярного произведения также пропорциональна произведению длин двух векторов. a⋅b = ∥a∥∥b∥cos (θ)
  2. Эта идея отлично подходит для цифр MNIST, потому что изображения относительно простые и однородные. Вы можете себе представить, что на эту эвристику могут повлиять разные фоны изображений.
  3. В зависимости от функции стоимости, будет другая движущая сила, толкающая в противоположном направлении, чтобы присвоить низкие значения Z отрицательным примерам, чтобы сделать выходные значения нейрона ближе к 0.