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

Сотрудники многих организаций в процессе рабочей деятельности ежедневно сталкиваются с конфиденциальными данными разных уровней в зависимости от своей должности и обязанностей. Разглашение таких данных может привести к финансовым потерям и снижению репутации компании. Для аудита соблюдения сотрудниками требований кибербезопасности необходима, в том числе, информация о корректном использовании паролей для входа в АС. Где же взять эту информацию? В качестве исходных данных у нас имеется:

  • Массив данных о входах в АС с наличием идентификационных признаков пользователя;
  • Массив данных об отсутствии сотрудников на рабочих местах по причине отпусков, больничных, командировок, удаленного режима работы.

Вход в АС отсутствующими сотрудниками организации может указывать на факты разглашения паролей. Как же выявить подозрительные входы в АС? В голову приходят некоторые известные способы, но сейчас «в тренде» искусственный интеллект и всё, что с ним связано. Так можно ли применить его для решения поставленной задачи?

Сначала необходимо определить: машинное обучение, искусственный интеллект и нейронные сети понятия взаимозаменяемые? Что их объединяет или, наоборот, различает?

Отношения между этими понятиями можно представить в виде рисунка:

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

Машинное обучение (МО) — как раз один из способов реализации ИИ. В чём же оно заключается, и как это работает? Представьте, что Вы пригласили на дачу друзей. Встал вопрос — сколько нужно купить, например, мяса для шашлыков? За определённый промежуток времени сформировался целый опыт таких событий — объектов, и мы можем выделить различные признаки, например, количество людей, норма потребления на каждого, время года, день недели и т.д. Имея значения признаков для множества объектов и соответствующее количество, в данном случае, мяса, мы можем произвести машинное обучение на данной выборке, получив определённую зависимость. По этой зависимости можно легко решить поставленную задачу, достаточно указать количество людей, день недели и другие данные, характерные для текущего «пикника».

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

Второй тип классического машинного обучения — без учителя (Unsupervised) — машине даётся набор данных, который не содержит правильные ответы, а она пытается самостоятельно найти определённые закономерности и сгруппировать информацию. Ну и наконец, мы подошли к еще одному виду машинного обучения — нейронным сетям (neural network), далее НС. Остановимся на них более подробно. Говоря простыми словами, НС функционируют по принципу головного мозга человека. Это модель, обученная делать определённые выводы на основании полученных данных подобно человеку. Основной структурной единицей НС является нейрон. В общем случае нейрон можно представить, как некий объект, имеющий несколько входных соединений и одно, либо также несколько выходных, способный получать, обрабатывать и отправлять сигналы.

Соединения по-другому называют связями. Каждая связь характеризуется весом (weight). Для понимания сущности весов в качестве примера можно привести ситуацию, когда 2 нейрона отправили одинаковый сигнал третьему, но из-за разных весов связей, соединяющих нейроны-отправители с нейроном-получателем, на вход третьего нейрона пришли отличающиеся друг от друга сигналы. Каждый нейрон принимает данные от других нейронов, обрабатывает их и формирует собственный сигнал, отправляя его далее по сети. Для обработки полученных данных и формирования собственного сигнала в искусственных НС используются решающие или активационные функции.

Обучение НС происходит при помощи применения алгоритма обратного распространения ошибки (backpropagation). Если во время нормальной работы НС сигналы распространяются от входа к выходу, то при обучении имеет место противоположный ход. Изначально необученная НС имеет в общем случае рандомные значения весов связей. Скорее всего, получив входные данные, она выдаст неверный результат. Зная верный результат, легко найти ошибку фактического. После расчёта ошибки на основании специальных формул происходит корректировка весов, и так по всей НС от выхода до входа. После полного прохождения по обучающей выборке и корректировки весов, определяется текущая ошибка результата. Всё вместе это называется эпохой (epoch). Чем больше эпох, тем лучше НС обучится.

Существующие предварительно обученные НС способны осуществлять распознавание чисел и букв, лиц людей, классифицировать объекты на полученных изображениях, например, отличить бутылку от тарелки, либо кота от собаки и много другое. Сферы их применения действительно разнообразны. А что, если обучить НС сравнивать даты?

Итак, вернемся к нашей задаче. Для реализации идеи мы выбрали НС.

Мы разработали довольно простую полносвязную НС средствами Python без использования сторонних модулей, которую обучили выявлять “нормальные” и “подозрительные” входы. Схема разработанной нейронной сети представлена на рисунке далее (НС изображена не полностью для экономии места).

Суть работы данной НС заключается в следующем. На входной слой поступает вектор, содержащий 3 даты: дата начала отсутствия сотрудника, дата окончания его отсутствия и дата входа в АС. Даты расположены в векторе именно в указанном порядке и разбиты по цифрам, то есть размерность вектора равна количеству нейронов входного слоя и составляет 18. Формат каждой даты до разбивки – «YYmmdd». НС имеет один выходной нейрон, который принимает 2 значения: 1 – событие входа в АС во время отсутствия сотрудника, 0 – событие входа не обнаружено. Количество скрытых слоёв и составляющих их нейронов подбиралось опытным путём. В качестве активационной функции была использована сигмоида:

Обучение НС производилось применением упомянутого выше алгоритма обратного распространения ошибки. Обучающая выборка была сформирована также с помощью Python с использованием генератора случайных чисел: годы в пределах от 18 до 20, месяцы – от 1 до 12, дни – от 1 до 31. Число кейсов в выборке – 10000.

При разработке НС использовался встроенный модуль Python numpy для работы с матрицами, поскольку очень удобно представлять слои и наборы весов в виде матриц, а также производить с матрицами такие операции, как умножение, вычитание. Например, для определения значений, поступающих на первый скрытый слой, достаточно матрицу, содержащую веса связей входного слоя с первым скрытым слоем умножить на матрицу, содержащую значения нейронов входного слоя. В нашем случае размерность последней – [18, 1], а размерность матрицы весов – [12, 18], таким образом, в результате умножения будет получена матрица размерностью [12, 1], что соответствует размерности первого скрытого слоя. Далее приведём некоторые фрагменты кода на Python.

Импортируем необходимые библиотеки:

1 import numpy as np    # работа с матрицами
2 import random         # генерация случайных чисел
3 import sys            # вывод текущего состояния обучения N

Непосредственно код НС оформляем в виде класса NeuralNetwork. В конструкторе класса определяем несколько переменных (4 — 8 строки) – матрицы весов, заполняем их случайными значениями по закону нормального распределения. В строке 11 определяем переменную self. learning_rate, которая отвечает за скорость обучения НС. Строка 10 необходима для прохождения активационной функции по всем значениям матрицы, соответствующей нейронам определённого слоя НС. Как было сказано выше, в качестве активационной функции используется сигмоида. Оформлена в виде метода sigmoid класса NeuralNetwork (13 строка), метод возвращает значение функции для х – это значение, поступающее на вход определённого нейрона:

1 class NeuralNetwork:
2  
3       def __init__(self, learning_rate=1): 
4          self.weights_input_to_hidden1 = np.random.normal(0.0, 1, (12, 18))        
5          self.weights_hidden1_to_hidden2 = np.random.normal(0.0, 1, (8, 12))        
6          self.weights_hidden2_to_hidden3 = np.random.normal(0.0, 1, (6, 8))        
7          self.weights_hidden3_to_hidden4 = np.random.normal(0.0, 1, (3, 6))
8          self.weights_hidden4_to_output = np.random.normal(0.0, 1, (1, 3))
9	
10         self.activation = np.vectorize(self.sigmoid)      
11         self.learning_rate = learning_rate
12	 
13      def sigmoid(self, x):        
14         return 1 / (1 + np.exp(-x))

Прямое распространение по НС описано методом predict класса NeuralNetwork. В строке 18 происходит умножение матрицы весов на матрицу входных значений, таким образом, переменная inputs_to_hidden_1 является матрицей значений, поступающих на нейроны первого скрытого слоя. В 20 строке переменная outputs_hidden_1 является матрицей значений, которые первый скрытый слой отправляет дальше по сети, и получается данная матрица как раз-таки применением активационной функции к inputs_to_hidden_1. Для каждого последующего слоя происходят аналогичные операции (код представлен для первых двух скрытых слоёв):

17 def predict(self, input_values):    
18     inputs_to_hidden_1 = np.dot(self.weights_input_to_hidden1, 
19                                 input_values)   
20     outputs_hidden_1 = self.activation(inputs_to_hidden_1)
21	    
22     inputs_to_hidden_2 = np.dot(self.weights_hidden1_to_hidden2, 
23	                           outputs_hidden_1)  
24     outputs_hidden_2 = self.activation(inputs_to_hidden_2)

Класс NeuralNetwork также содержит метод learn, имеющий два параметра – набор поступающих на вход НС значений и ожидаемый результат работы НС, предназначенный для обучения НС. Первые несколько строк полностью дублируют метод predict, поскольку при обучении НС также должна отработать в прямом направлении, чтобы появилась возможность определить отклонение от ожидаемого результата.

Далее представлен фрагмент кода, находящегося в методе learn, отвечающий за обратное распространение ошибки. В строке 52 определяется ошибка выходного слоя, в строке 53 – дифференциал сигмоиды для выходного нейрона, а в строке 54 – величина, которая необходима для корректировки весов связей последнего скрытого слоя с выходным. В строке 55 происходит непосредственно корректировка весов. Для следующих слоёв корректировка весов происходит по аналогии, за исключением определения ошибки слоя – строка 60:

51	        # Выходной слой
52	        error_outputs = np.array([current_prediction - expected_prediction])
53	        gradient_outputs = current_prediction * (1 - current_prediction)
54	        weights_delta_outputs = error_outputs * gradient_outputs
55	        self.weights_hidden4_to_output -= (np.dot(weights_delta_outputs,                                          
56	                                           outputs_hidden_4.reshape(1, 
57	                                           len(outputs_hidden_4)))) * 
58	                                           self.learning_rate
59	        # Последний скрытый слой
60	        error_hidden_4 = weights_delta_outputs * self.weights_hidden4_to_output
61	        gradient_hidden_4 = outputs_hidden_4 * (1 - outputs_hidden_4)
62	        weights_delta_hidden_4 = error_hidden_4 * gradient_hidden_4
63	        self.weights_hidden3_to_hidden4 -= (np.dot(weights_delta_hidden_4.T,                                          
64	                                            outputs_hidden_3.reshape(1, len(
65	                                            outputs_hidden_3)))) * 
66	                                            self.learning_rate

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

1	def calculate_squared_error(current_prediction, expected_prediction):  
2	    return np.mean((current_prediction-expected_prediction)**2)

Ниже представлен фрагмент кода, отвечающий за запуск обучения НС. В строках 1 и 2 определяем количество эпох и скорость обучения соответственно, в строке 4 создаём объект нашей НС и далее в цикле для каждой эпохи вызываем метод learn для каждого кейса обучающей выборки, после прохождения по всей выборке в строке 10 определяется среднеквадратичная ошибка и отображается прогресс обучения:

1	epochs_num = 1000
2	learning_rate = 0.075
3	
4	network = NeuralNetwork(learning_rate = learning_rate)
5	
6	for epoch in range(epochs_num): 
7	    for input_value, expected_prediction in train:       
8	        network.learn(np.array(input_value), expected_prediction)
9	 
10	    SE = calculate_squared_error(network.predict(np.array(input_values).T), 
11	                                 np.array(correct_predictions))    
12	    sys.stdout.write('\rПрогресс: {}, Отклонение: {}'.format(str(100 * 
13	                     epoch/epochs_num)[:4], str(SE)[:5]))

В результате, на обучающей выборке величиной 10000 записей, с количеством эпох, равным 1000, и значением скорости обучения 0.075 данная НС сошлась до ошибки приблизительно 0,4%. Во время тестов на других сгенерированных массивах дат НС дала правильные результаты в 100% случаях.  При тестах на реально существующей выборке, составляющей 891 запись было выявлено 15 случаев несовпадений (1,7%). НС указывала на событие входа АС во время отсутствия сотрудника, но входа фактически не было. Обратных ситуаций не выявлено. Случаи неправильных решений НС могут быть связаны с тем, что обучающая выборка, сгенерированная случайным образом, далеко не идеальна и не в полной мере соответствует реальным ситуациям. Обученная на реальной выгрузке НС отработала без ошибок, поэтому следует, точнее необходимо обучать НС на реальных данных, которые соответствуют действительности. Скорость работы НС составила 0.08 – 0.09 с, что является довольно неплохим результатом. Ниже представлен фрагмент результата работы НС:

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

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