Время прочтения: 4 мин.

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

Отчет по проверке моделей – это итоговый документ, где описывается модель, для чего она предназначена, какие исходные данные она использует и самое главное – то, на основании чего отчет и классифицируется – итоговые показатели и метрики модели. Выглядит это примерно вот так:

При этом количество видов классификаций было неизвестно, а количество признаков для классификации было разным (от 1 до 50 показателей), признаки могли присутствовать или отсутствовать в разных видах отчетов. Для решения данной задачи было решено использовать нейросети.

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

Мы начали разбираться, как нам на основе имеющегося опыта с минимальными трудозатратыми, и не тратя дополнительно время на изучение новых библиотек, выполнить эту задачу. В результате, мы получили вполне работающую схему построения нейросети на основе Numpy, реализовали простейшую нейросеть с одним скрытым слоем принимающую на вход 3 параметра на python  с использованием библиотеки Numpy, о которой и пойдет речь далее.

Начнем, как говорится, с начала. Нейросеть реализуем через класс, с методами, реализующими весь функционал. Для начала инициализируем через конструктор входные параметры — learning_rate=0.05, начальные веса – захардкодим (протекционисты могут заюзать функцию random).

class simpleNeiroNet(object):
        def __init__(self, learningRate=0.05):
             # инициируем базовые веса не нулевыми значениями
             self.w_0_1 = np.random.normal(0.5, - 0.5, (0.5, 0.5)) # веса первого слоя
             self.w_1_2 = np.random.normal(0.25, 0.25, (0.25, 0.25)) # веса второго слоя
             self.functionMapper = np.vectorize(self. thFunction)
             self.learningRate = np.array([learningRate])

В качестве функции активации мы решили использовать гиперболический тангенс, но его легко заменить любой другой функцией, к примеру, сигмоидом.

    def thFunction(self, x):
        return (2 / (1 + np.exp(2 * -x)) – 1)

В качестве функции потерь, которая показывает насколько отклоняются предсказания и реальные показатели, мы используем MSE.

def lossMSE(yPredict, yReal):
   return np.mean((yPredict - yReal)**2)

Далее получаем предсказанные значения.

    def predictValues(self, inputData):
        # считаем первый слой 
        inputDataFirst = np.dot(self.w_0_1, inputData)
        outputDataFirst = self.sigmoid_mapper(inputDataFirst)
        # считаем второй слой
        inputDataSecond = np.dot(self.w_1_2, outputDataFirst)
        outputDataFirstSecond = self.functionMapper(inputDataSecond)
        return outputDataFirstSecond

Тренируем веса:

def train(self, inputData, expectedPredict):  
        # считаем первый слой 
        inputDataFirst  = np.dot(self.w_0_1, inputData)
        outputDataFirst = self.functionMapper(inputDataFirst)
        # считаем второй слой
        inputDataSecond = np.dot(self.w_1_2, outputDataFirst)
        outputDataSecond = self. functionMapper(inputDataSecond)
        actualPredict = outputDataFirstSecond[0]
        
        errorLayerSecond = np.array([actualPredict - expectedPredict])
        gradientLayerSecond = actualPredict * (1 - actualPredict)
        weightsDeltaLayerSecond = errorLayerSecond * gradientLayerSecond  
        self.w_1_2 -= (np.dot(weightsDeltaLayerSecond, outputDataFirstSecond.reshape(1, len(outputDataFirstSecond)))) * self.learningRate
        
        errorLayerFirst = weightsDeltaLayerSecond * self.w_1_2
        gradientLayerFirst = outputDataFirst * (1 - outputDataFirst)
        weightsDeltaLayerFirst = errorLayerFirst * gradientLayerFirst
       self.w_0_1 -= np.dot(inputData.reshape(len(inputData), 1), weightsDeltaLayerFirst).T  * self.learningRate

Следующим шагом задаем количество эпох, другими словами, сколько раз будем считать и проводим тренировку весов, расчет предсказаний и их сравнение с фактом, в общем используем все функции, описанные ранее:

numberOfEpochs = 6000
learningRate = 0.08

neuralNetwork = simpleNeiroNet(learningRate = learningRate)

for epoch in range(numbersOfEpochs):
    inputTemp = []
    correctPredictions = []
    for inputStat, correctPredict in train:
        neuralNetwork.train(np.array(inputStat), correctPredict)
        inputTemp.append(np.array(inputStat))
        correctPredictions.append(np.array(correctPredict))
    
    trainLoss = lossMSE(neuralNetwork.predict(np.array(inputTemp).T), np.array(correctPredictions))
    sys.stdout.write(
                                   "\r percent of progress: {}, Training loss: {}".format(
                                   str(100 * epoch / float(numberOfEpochs))[:4], str(trainLoss)[:5]))

И на выходе получаем:

Percent of progress: 99.9, Training loss: 0.001

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

Конечно, окончательная классификация потребовала «посмотреть глазами» полученные результаты – но это было уже проверка агрегированных данных – что гораздо проще – чем вручную провести просмотр и классификацию многих сотен документов, плюс нами был разработан инструмент, который можно использовать при последующих классификациях.

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

Всегда полезно не только понимать теоретически как работает «под капотом» тот или иной метод, но и уметь реализовать такой метод в коде, причем максимально используя уже имеющийся опыт. Именно подобный навык отличает программиста от того, кто программистом только называется.