Запуск алгоритма самоорганизующейся карты объектов в наборе данных рукописных цифр MNIST

Это вторая статья из серии, посвященной объяснимости ИИ и извлечению медицинских признаков. Вот первая статья:



В настоящее время я пытаюсь реализовать алгоритм самоорганизующейся карты признаков, используемый King et al., 2020 в их статье:

Б. Кинг, С. Барве, А. Форд и Р. Джа, «Неконтролируемая кластеризация рентгеновских изображений грудной клетки COVID-19 с самоорганизующейся картой признаков», 63-й международный симпозиум IEEE Среднего Запада по схемам и системам, 2020 г. (MWSCAS), 2020, стр. 395–398, doi: 10.1109/MWSCAS48704.2020.9184493.

Это ссылка GitHub, указанная в документе:



Прежде чем запускать алгоритм SOFM в наборе данных рентгена грудной клетки COVID, я хотел сначала запустить алгоритм SOFM в наборе данных MNIST, чтобы лучше понять код.

По пути я столкнулся с рядом проблем с кодом, которые заставили меня глубже понять его, и я хотел бы поделиться ими с вами ниже, чтобы вы могли запускать тот же код без каких-либо проблем.

Понять алгоритм SOFM

Перед запуском кода я рекомендую вам посмотреть эти два видео, чтобы получить общее представление о том, как работает алгоритм SOFM.

Вам нужны только первые 12 минут этого видео:

Клонировать репозиторий

Из терминала перейдите в каталог, где вы хотите начать работу. Затем клонируйте репозиторий, выполнив следующую команду:

git clone https://github.com/king2b3/SOFM.git

Понимание структуры репозитория

Репозиторий содержит 3 основных файла «контроллера». Нас интересует только MNISTSOFM.py

Вы можете думать о файле контроллера как о кнопке запуска всей программы. Из командной строки мы запустим файл контроллера. Затем контроллер запустит несколько функций, каждая из которых определена в других файлах, для загрузки данных, обучения сети, тестирования сети и построения графика результатов.

Эти другие функции (и переменные) находятся в:

  • network.py: класс «Сеть» [алгоритм SOFM] и связанные методы.
  • funcs.py: функции, необходимые для алгоритма SOFM.
  • params.py: переменные (значения параметров), необходимые для алгоритма SOFM.
  • metrics.py: функции, необходимые для расчета показателей.
  • ploting.py: функции, необходимые для построения графика результатов.

Нужные нам данные хранятся в папке «DataSets»:

Запуск контроллера

Из командной строки перейдите в каталог, содержащий клонированный репозиторий, и запустите файл контроллера:

cd SOFM
python MNISTSOFM.py

Ошибка 1: указание количества слоев

После загрузки набора данных и начала обучения мы получаем следующую ошибку:

##############
Loading Dataset
##############
##############
Starting Training
##############
Traceback (most recent call last):
  File "E:\MNIST_SOFM\SOFM\MNISTSOFM.py", line 20, in <module>
    trainBool = NN.train(train,max_epochs,no,tau,tauN,sigmaP,trainBool) #Comment out line to run on saved weights
  File "E:\MNIST_SOFM\SOFM\network.py", line 108, in train
    N,N2 = self.winning_neuron(i)
  File "E:\MNIST_SOFM\SOFM\network.py", line 35, in winning_neuron
    c = np.linalg.norm(x[0] - self.weights, axis=1)
ValueError: operands could not be broadcast together with shapes (784,) (100,1000000)

Чтобы сеть вычислила нейрон-победитель для определенного изображения/точки данных, ей необходимо вычислить «расстояние» между изображением (x[0]) и весами всех нейронов (self.weights). Нейрон, который приводит к минимальному расстоянию, получает титул «нейрон-победитель» и возвращается функцией.

Как мы видели ранее, каждое изображение представлено массивом из 784 значений.

Однако в сети у нас есть 100 нейронов, каждый из которых представлен массивом из 1 000 000 значений.

Это несоответствие между количеством значений, описывающих изображение, и количеством значений, описывающих нейрон, и является причиной нашей ошибки.

Поэтому нам нужно будет исправить это в файле params.py, установив переменную Layers:

layers = [1000000,100]
***
becomes
***
layers = [784,100]

Ошибка 2: исправление путей к файлам

Снова запустив код, мы обнаруживаем, что обучение проходит гладко, но мы сталкиваемся со второй ошибкой, когда начинаем сохранять веса.

saving weights
Traceback (most recent call last):
  File "E:\MNIST_SOFM\SOFM\MNISTSOFM.py", line 22, in <module>
    NN.saveWeights(Weights)
  File "E:\MNIST_SOFM\SOFM\network.py", line 120, in saveWeights
    with open(filename, 'w') as csv_file:
FileNotFoundError: [Errno 2] No such file or directory: 'SavedWeights/FromBayley/1000k/savedWeights/Weights.txt'

Строка «NN.saveWeights(Weights)» хочет создать файл Weights.txt, но не может найти подходящую папку для этого.

В параметрах (params.py) указывается папка/путь, чтобы указать различным фрагментам кода, где создавать эти новые документы. Но глядя на нашу папку, мы видим, что этой папки не существует.

pathExt = "SavedWeights/FromBayley/1000k/savedWeights/"
SimParm = pathExt+"BestParam.txt"
Metrics = pathExt+"metrics.p"
MapPickle = pathExt+"map.p"
BMUPickle = pathExt+"BMUWeights.p"
Weights = pathExt+"Weights.txt"
OutputMap = pathExt+"Map.tiff"
OutputW =  pathExt+"Weights.tiff"
OutputMet = pathExt+"Metrics.tiff"
TestNN = pathExt+"testNN.txt"
OutNN = pathExt+"outNN.txt"

Итак, теперь у нас есть два варианта: мы можем либо изменить переменную pathExt, либо создать папки самостоятельно. Здесь я выбираю второй вариант. Вы можете создать папки вручную или запустить следующую командную строку (обратите внимание на обратную косую черту):

mkdir SavedWeights\FromBayley\1000k\savedWeights\

Ошибка 3: удаление пустых строк в Weights.txt

Снова запустив контроллер MNISTSOFM.py, мы получим третью ошибку.

saving metrics
Traceback (most recent call last):
  File "E:\MNIST_SOFM\SOFM\MNISTSOFM.py", line 28, in <module>
    weight = funcs.loadWeights(Weights,layers) # uncomment to plot true weight map
  File "E:\MNIST_SOFM\SOFM\funcs.py", line 46, in loadWeights
    weights[neuron][w] = lines[counter]
ValueError: could not convert string to float: ''

Эта ошибка возникает из-за того, что когда наша функция loadWeights() пытается прочитать файл Weights.txt, она встречает пустые строки, которые она интерпретирует как строку.

Чтобы исправить эту ошибку, нам нужно удалить пустые строки при создании файла Weights.txt [в функции saveWeights() в network.py]

def saveWeights(self,filename):
        with open(filename, 'w') as csv_file:
            csv_writer = csv.writer(csv_file, delimiter=',', quoting=csv.QUOTE_MINIMAL)
            for neuron in range(len(self.weights)):
                for w in range(len(self.weights[0])):
                    csv_writer.writerow([self.weights[neuron][w]])
***
becomes
***
def saveWeights(self,filename):
        with open(filename, 'w', newline = '') as csv_file:
            csv_writer = csv.writer(csv_file, delimiter=',', quoting=csv.QUOTE_MINIMAL)
            for neuron in range(len(self.weights)):
                for w in range(len(self.weights[0])):
                    csv_writer.writerow([self.weights[neuron][w]])

Ошибка 4: исправление размеров тепловой карты

4-я ошибка возникает, когда мы пытаемся построить наши результаты, в частности, тепловую карту

ValueError: Must pass 2-d input. shape=(10, 10, 1)

В ploting.py мы передаем массив «h» в функцию sns.heatmap(). Этот массив должен быть двумерным, то есть иметь форму (10,10). В настоящее время он имеет форму (10,10,1), поэтому нам нужно исправить это, отредактировав функцию graphHeatmap() в ploting.py и применив функцию np.squeeze() к «h».

import seaborn as sns; sns.set()
ax = sns.heatmap(h, annot=True,xticklabels=False,yticklabels=False,cbar=False)
***
becomes
***
import seaborn as sns; sns.set()
import numpy as np
h = np.squeeze(h)
ax = sns.heatmap(h, annot=True,xticklabels=False,yticklabels=False,cbar=False)

Ошибка 5. Исправление размеров графика веса

Когда мы снова запустим, мы получим ошибку через функцию weightPlot()

ValueError: cannot reshape array of size 784 into shape (1000,1000)

Эта ошибка возникает из-за того, что функция ожидает, что нейроны будут иметь 1000000 значений и поэтому будут представлены (1000,1000) массивами. Как мы видели выше, наши нейроны на самом деле имеют 784 значения, которые могут быть представлены массивами (28,28). Поэтому мы должны исправить функцию weightPlot() в ploting.py, чтобы отразить это.

axarr[row,col].imshow(lookAtTheseBoys(numfella,weights,1000),cmap=plt.get_cmap('gray_r'))
***
becomes
***
axarr[row,col].imshow(lookAtTheseBoys(numfella,weights,28),cmap=plt.get_cmap('gray_r'))

Полученные результаты!!

Было много отладки, но результат того стоит! Вы можете найти выходные данные в этом каталоге, сохраненные в виде изображений TIFF.

SavedWeights/FromBayley/1000k/savedWeights/

Тепловая карта 10 x 10 наиболее подходящих входных данных/изображений для каждого из 100 нейронов:

Вы можете думать об этом как о «цифре, которую обнаруживает каждый нейрон».

График 10 x 10 весов каждого из наших 100 нейронов:

Вы можете думать об этом как о «Форме, которую ищет каждый нейрон».

Следующие шаги

Это был, безусловно, очень информативный опыт, и мне очень понравилось решение проблем. Теперь я попытаюсь применить тот же алгоритм к наборам данных рентгенографии грудной клетки COVID. Я также хотел бы внести свой вклад в репозиторий, предложив некоторые изменения в коде, чтобы будущие пользователи могли избежать всех вышеперечисленных ошибок.

Дайте мне знать, что вы думаете в комментариях — вопросы, правки и предложения всегда приветствуются!