Открыть Электронные книги
Категории
Открыть Аудиокниги
Категории
Открыть Журналы
Категории
Открыть Документы
Категории
САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ
ЭЛЕКТРОТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ
«ЛЭТИ» ИМ. В.И. УЛЬЯНОВА (ЛЕНИНА)
ОТЧЕТ
по проекту
по дисциплине «Комбинаторика и теория графов»
Тема: «Нейронная сеть для определения цикла в графах вершинной
размерности 5 и 10»
Стукалов С.С.
Студенты гр. 0375 Надводнюк Я.О.
Санкт-Петербург
2021
Оглавление
ОСНОВНЫЕ ПОНЯТИЯ 3
КОМПОНЕНТЫ НЕЙРОННОЙ СЕТИ 4
Постановка задачи 4
Архитектура нашей нейронной сети. 4
О скрытых слоях и их назначении 5
Описание прямого распространения между любыми двумя слоями 6
Масштабирование активации до интервала [0, 1] в выходном слое 6
Описание нейросети в терминах линейной алгебры 8
Уточнение об активации нейронов 8
ОБУЧЕНИЕ НЕЙРОННОЙ СЕТИ ДЛЯ РАСПОЗНАВАНИЯ ЦИКЛА В ГРАФЕ 11
Структура программы, генерирующей тренировочные данные 11
Скорость работы классического алгоритма нахождения цикла 16
Функция потерь (loss) 17
Задание функции потерь для распознавания цикла 17
Градиентный спуск 18
Компоненты градиентного спуска 19
МЕТОД ОБРАТНОГО РАСПРОСТРАНЕНИЯ ОШИБКИ 21
Управление активацией нейрона 21
Варианты настройки нейросети 21
Обратное распространение 22
Классический градиентный спуск 23
Стохастический градиентный спуск 23
Adam 24
СТРУКТУРА ПРОГРАММЫ ДЛЯ СОЗДАНИЯ И ОБУЧЕНИЯ НЕЙРОННОЙ СЕТИ 26
Нормализация базы тренировочных данных 26
Обучение нейронной сети и его статистика 27
Статистика обучения 28
Точность моделей нейронных сетей для тестовой выборки 30
СКОРОСТЬ РАБОТЫ НЕЙРОННОЙ СЕТИ 30
ВЫВОДЫ 31
ПРИЛОЖЕНИЕ 32
2
ОСНОВНЫЕ ПОНЯТИЯ
Нейронная сеть — математическая модель, а также её программное
или аппаратное воплощение, построенная по принципу организации и
функционирования биологических нейронных сетей — сетей нервных клеток
живого организма.
3
КОМПОНЕНТЫ НЕЙРОННОЙ СЕТИ
Цель: показать, что собой представляет нейронная сеть.
Постановка задачи
Представим, у вас есть несколько образующих цикл дуг в графе,
существование которых отмечено единицей в матрице смежности. Ваш мозг
без труда узнает это число.
4
входных данных (как они уже будут происходить – решает нейронная сеть.
Подробнее про назначение скрытых слоев - далее).
6
Масштабирование активации до интервала [0, 1] в выходном слое
На выходном слое необходимо нормализовать данные в данном
интервале, так как, вычислив взвешенную сумму, мы можем получить любое
число в широком диапазоне значений. Для того, чтобы оно попадало в
необходимый диапазон активаций от 0 до 1, разумно использовать функцию,
которая бы «сжимала» весь диапазон до интервала [0, 1].
1
−x . (1)
1+ e
Рисунок 2 – Сигмоида.
7
Описание нейросети в терминах линейной алгебры
8
Фактически вся нейросеть — это одна большая настраиваемая через
обучения функция с тысячами параметров, принимающая входные значения
и выдающая вероятность того, образует ли вершина цикл. Тем не менее, не
смотря на свою сложность, это просто функция, и в каком-то смысле
логично, что она выглядит сложной, так, как если бы она была проще, эта
функция не была бы способна решить задачу.
9
определенный порог, то срабатывает функция, а если не пройден, то нейрон
просто остается неактивным, с активацией равной нулю.
10
ОБУЧЕНИЕ НЕЙРОННОЙ СЕТИ ДЛЯ РАСПОЗНАВАНИЯ
ЦИКЛА В ГРАФЕ
В общем виде алгоритм нахождения соответствующих весов и сдвигов
только исходя из полученных данных состоит в том, чтобы показать
нейросети множество тренировочных данных. В результате обучения
нейросеть должна правильным образом различать примеры из ранее не
представленных, тестовых данных.
class Graph {
private:
// Number of vertices
int V;
// Pointer to adjacency list
list<int>* adj;
// DFS recursive helper functions
bool DFS_findCycle(int s, int* visited, vector<int>* cycle);
// Detect cycle
int flag = 0;
public:
//Default constructor
Graph();
// Constructor prototype
Graph(int v);
// Destructor prototype
~Graph();
11
void addEdge(int v, int w);
if (!visited[j]) {
int i = path.size() - 2;
cycle[path.at(path.size() - 1)] = 1;
delete[] visited;
return false;
}
srand(time(NULL));
fstream fs;
fs.open("train10.csv", fstream::out | fstream::in | fstream::app);
13
if (!fs.is_open()) {
cerr << "ERROR: File is not open..." << endl;
}
fs << "aa,ab,ac,ad,ae,af,ag,ah,ai,aj,"
<< "ba,bb,bc,bd,be,bf,bg,bh,bi,bj,"
<< "ca,cb,cc,cd,ce,cf,cg,ch,ci,cj,"
<< "da,db,dc,dd,de,df,dg,dh,di,dj,"
<< "ea,eb,ec,ed,ee,ef,eg,eh,ei,ej,"
<< "fa,fb,fc,fd,fe,ff,fg,fh,fi,fj,"
<< "ga,gb,gc,gd,ge,gf,gg,gh,gi,gj,"
<< "ha,hb,hc,hd,he,hf,hg,hh,hi,hj,"
<< "ia,ib,ic,id,ie,if,ig,ih,ii,ij,"
<< "ja,jb,jc,jd,je,jf,jg,jh,ji,jj,"
<< "isCycle:" << endl;
/* CODE_FOR_GENERATING_TRAIN5
fs << "aa,ab,ac,ad,ae,"
<< "ba,bb,bc,bd,be,"
<< "ca,cb,cc,cd,ce,"
<< "da,db,dc,dd,de,"
<< "ea,eb,ec,ed,ee,"
<< "isCycle:" << endl;
*/
for (int k = 0; k < 1000000; k++) {
// Graph init
int size = 10;
Graph g(size);
int** matrix = new int* [size];
for (int i = 0; i < size; i++) {
matrix[i] = new int[size];
for (int j = 0; j < size; j++)
matrix[i][j] = 0;
}
if (k % 7 == 0) {
// Random adj matrix
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++)
matrix[i][j] = rand() % 2;
}
}
else {
for (int i = 0; i < size; i++) {
int j = rand() % size, m = rand() % size;
matrix[j][m] = 1;
}
}
// Reading matrix
g.matrixToList(matrix, size);
int* cycle = new int[size];
// Find cycle:
14
bool isCycle = g.findCycle(cycle);
// Output matrix
for (int i = 0; i < size; i++)
for (int j = 0; j < size; j++)
fs << matrix[i][j] << ",";
// Free memory:
for (int i = 0; i < size; i++)
delete[] matrix[i];
delete[] matrix;
}
return 0;
}
Разберём его по частям. Сначала впишем в файл заголовки столбцов
базы данных, для дальнейшей обработки их при обучении:
srand(time(NULL));
fstream fs;
fs.open("train10.csv", fstream::out | fstream::in | fstream::app);
if (!fs.is_open()) {
cerr << "ERROR: File is not open..." << endl;
}
fs << "aa,ab,ac,ad,ae,af,ag,ah,ai,aj,"
<< "ba,bb,bc,bd,be,bf,bg,bh,bi,bj,"
<< "ca,cb,cc,cd,ce,cf,cg,ch,ci,cj,"
<< "da,db,dc,dd,de,df,dg,dh,di,dj,"
<< "ea,eb,ec,ed,ee,ef,eg,eh,ei,ej,"
<< "fa,fb,fc,fd,fe,ff,fg,fh,fi,fj,"
<< "ga,gb,gc,gd,ge,gf,gg,gh,gi,gj,"
<< "ha,hb,hc,hd,he,hf,hg,hh,hi,hj,"
<< "ia,ib,ic,id,ie,if,ig,ih,ii,ij,"
<< "ja,jb,jc,jd,je,jf,jg,jh,ji,jj,"
<< "isCycle:" << endl;
/* CODE_FOR_GENERATING_TRAIN5
fs << "aa,ab,ac,ad,ae,"
<< "ba,bb,bc,bd,be,"
<< "ca,cb,cc,cd,ce,"
<< "da,db,dc,dd,de,"
<< "ea,eb,ec,ed,ee,"
<< "isCycle:" << endl;
*/
if (k % 7 == 0) {
// Random adj matrix
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++)
matrix[i][j] = rand() % 2;
}
}
else {
for (int i = 0; i < size; i++) {
int j = rand() % size, m = rand() % size;
matrix[j][m] = 1;
}
}
Да, можно ещё больше оптимизировать тренировочные данные, но эти
тоже дают неплохие результаты при обучении. Так что оставили их.
Скорость работы классического алгоритма нахождения цикла
После создания баз данных, вычислили скорость работы для графа из 5
вершин:
time_t t1 = time(NULL);
for (int i = 0; i < 1000000; i++) {
bool isCycle = g.findCycle(cycle);
}
time_t t2 = time(NULL);
cout << static_cast<double>(t2 - t1) / 1000000 << " seconds" << endl;
16
Функция потерь (loss)
Концептуально задача обучения нейросети сводится к нахождению
минимума определенной функции – функции потерь. Опишем что она собой
представляет.
17
Градиентный спуск
Для начала вместо того, чтобы представлять функцию с множеством
входных значений, начнем с функции одной переменной С(w). Для того
чтобы найти минимум функции, нужно взять производную.
19
Для градиентного спуска важно, чтобы выходные значения функции
потерь изменялись плавным образом. Именно поэтому значения активации
имеют не просто бинарные значения 0 и 1, а представляют действительные
числа и находятся в интервале между этими значениями.
20
МЕТОД ОБРАТНОГО РАСПРОСТРАНЕНИЯ ОШИБКИ
Обратное распространение — это ключевой алгоритм обучения
нейронной сети. Обсудим, в чем заключается метод.
1. Увеличить сдвиг b.
2. Увеличить веса wi.
3. Поменять активации предыдущего слоя ai.
21
Другой близкий подход заключается в изменении активаций нейронов
предыдущего слоя ai пропорционально весам wi. Мы не можем изменять
активации нейронов, но можем менять соответствующие веса и сдвиги и
таким образом влиять на активацию нейронов.
Обратное распространение
Если говорить не математическим языком, то данный алгоритм можно
описать так:
Важно понимать, что все эти действия происходят для всех нейронов
выходного слоя (в нашем случае только один, но говорим в общем случае),
так как каждый нейрон текущего слоя связан со всеми нейронами
предыдущего. Просуммировав все эти необходимые изменения для
предпоследнего слоя, вы понимаете, как должен измениться второй с конца
слой. Далее рекурсивно вы повторяете тот же процесс для определения
свойств весов и сдвигов всех слоев, получая градиент функции потерь по
данным параметрам.
22
Рисунок 8 – Расчет частной производной функции потерь по весу из
первого слоя с помощью обратного распространения для трех скрытых слоёв.
23
Случайным образом данные обучающей выборки перемешиваются и
разделяются на подгруппы. Далее алгоритм рассчитывает шаг градиентного
спуска для каждой подгруппы.
Adam
Алгоритм оптимизации Адама является расширением стохастического
градиентного спуска, который в последнее время получил широкое
распространение для приложений глубокого обучения в области
компьютерного зрения и обработки естественного языка. Главное
преимущество Адама в том, что после коррекции смещения каждая итерация
скорости обучения имеет определенный диапазон, что делает параметры
относительно стабильными.
24
SGD с импульсом: импульс накапливает экспоненциально затухающую
скользящую среднюю прошлых градиентов и продолжает двигаться в их
направлении. Благодаря этому мы можем «проскочить» локальный минимум,
словно по инерции, чтобы двигаться дальше и искать другой, возможно
более эффективный, локальный минимум.
25
СТРУКТУРА ПРОГРАММЫ ДЛЯ СОЗДАНИЯ И ОБУЧЕНИЯ
НЕЙРОННОЙ СЕТИ
Далее будем рассматривать структуру программы формирования
нейронной сети для графа из 10 вершин (для 5 вершин все будет аналогично).
input_names = ["aa","ab","ac","ad","ae","af","ag","ah","ai","aj",
"ba","bb","bc","bd","be","bf","bg","bh","bi","bj",
"ca","cb","cc","cd","ce","cf","cg","ch","ci","cj",
"da","db","dc","dd","de","df","dg","dh","di","dj",
"ea","eb","ec","ed","ee","ef","eg","eh","ei","ej",
"fa","fb","fc","fd","fe","ff","fg","fh","fi","fj",
"ga","gb","gc","gd","ge","gf","gg","gh","gi","gj",
"ha","hb","hc","hd","he","hf","hg","hh","hi","hj",
"ia","ib","ic","id","ie","if","ig","ih","ii","ij",
"ja","jb","jc","jd","je","jf","jg","jh","ji","jj"]
output_names = ["isCycle:"]
# Нормализация данных.
26
# Разделения входного вектора тренировочных данных и выхода
def make_supervised(df):
raw_input_data = data_frame[input_names]
raw_output_data = data_frame[output_names]
return{"inputs":data_frame_to_dict(raw_input_data),
"outputs":data_frame_to_dict(raw_output_data)}
# Делаем каждый элемент списком, чтобы потом соединить их в строчки входных и
# выходных данных из базы данных с помощью функции zip
def encode(data):
vectors = []
for data_name, data_values in data.items():
encoded = list(map(lambda dn: [dn], data_values))
vectors.append(encoded)
formatted = []
for vector_raw in list(zip(*vectors)):
vector = []
for element in vector_raw:
for e in element:
vector.append(e)
formatted.append(vector)
return formatted
# Разделение получившегося словаря на массивы входных и выходных данных
# (которые уже нормализовали)
supervised = make_supervised(data_frame)
encoded_inputs = np.array(encode(supervised["inputs"]))
encoded_outputs = np.array(encode(supervised["outputs"]))
# Обучение.
fit_result = model.fit(x = train_x, y = train_y, epochs = 30, validation_split =
0.2)
plt.title("Accuracies train/validation")
plt.plot(fit_result.history["accuracy"], label = "Train")
plt.plot(fit_result.history["val_accuracy"], label = "Validation")
plt.legend()
plt.show()
Статистика обучения
Важный момент: валидация – это результат обучения данных на
определенной части выборки тренировочного сета. Наглядно видно на
подобном примере (рисунок 9).
28
Рисунок 10 – Статистика нейронной сети для графа размерности 10.
29
Точность моделей нейронных сетей для тестовой выборки
Благодаря огромной базе тренировочных данных, точность нейронной
сети для графа из 5 вершин стремится к 100%:
Вывод: 99.8
Вывод: 90.8
30
ВЫВОДЫ
Таким образом, цель данной работы выполнена. Стоит заметить, что
скорость работы нейронной сети в тысячи раз меньше скорости работы
классического алгоритма из-за аппаратных особенностей (нет особенных
вычислительных блоков, на которых вычисления выполнялись бы
параллельно), а также из-за особенностей языка, на котором все выполнялось
(python в сотни раз медленнее C++ по различным причинам).
31
ПРИЛОЖЕНИЕ
GenTrainsWithDFS.cpp
// Author: Stukalov Sergey (Willem White).
#include <iostream>
#include <fstream>
#include <ctime>
#include <list>
#include <vector>
class Graph {
private:
// Number of vertices
int V;
// Pointer to adjacency list
list<int>* adj;
// DFS recursive helper functions
bool DFS_findCycle(int s, int* visited, vector<int>* cycle);
// Detect cycle
int flag = 0;
public:
//Default constructor
Graph();
// Constructor prototype
Graph(int v);
// Destructor prototype
~Graph();
Graph::Graph() {
this->V = 0;
this->flag = 0;
this->adj = nullptr;
}
32
// Create new adjacency list
v ? adj = new list<int>[v] : this->adj = nullptr;
}
33
// Perform DFS given a starting vertex 0
bool Graph::findCycle(int* cycle) {
// Start with all vertices as not visited
int* visited = new int[V];
vector<int> path;
if (!visited[j]) {
int i = path.size() - 2;
cycle[path.at(path.size() - 1)] = 1;
delete[] visited;
return false;
}
int main() {
/* TEST_CODE
int size = 5;
Graph g(size);
// Reading matrix
g.matrixToList(matrix, size);
int* cycle = new int[size];
bool isCycle = g.findCycle(cycle);
34
*/
srand(time(NULL));
fstream fs;
fs.open("train10.csv", fstream::out | fstream::in | fstream::app);
if (!fs.is_open()) {
cerr << "ERROR: File is not open..." << endl;
}
fs << "aa,ab,ac,ad,ae,af,ag,ah,ai,aj,"
<< "ba,bb,bc,bd,be,bf,bg,bh,bi,bj,"
<< "ca,cb,cc,cd,ce,cf,cg,ch,ci,cj,"
<< "da,db,dc,dd,de,df,dg,dh,di,dj,"
<< "ea,eb,ec,ed,ee,ef,eg,eh,ei,ej,"
<< "fa,fb,fc,fd,fe,ff,fg,fh,fi,fj,"
<< "ga,gb,gc,gd,ge,gf,gg,gh,gi,gj,"
<< "ha,hb,hc,hd,he,hf,hg,hh,hi,hj,"
<< "ia,ib,ic,id,ie,if,ig,ih,ii,ij,"
<< "ja,jb,jc,jd,je,jf,jg,jh,ji,jj,"
<< "isCycle:" << endl;
/* CODE_FOR_GENERATING_TRAIN5
fs << "aa,ab,ac,ad,ae,"
<< "ba,bb,bc,bd,be,"
<< "ca,cb,cc,cd,ce,"
<< "da,db,dc,dd,de,"
<< "ea,eb,ec,ed,ee,"
<< "isCycle:" << endl;
*/
for (int k = 0; k < 1000000; k++) {
// Graph init
int size = 10;
Graph g(size);
int** matrix = new int* [size];
for (int i = 0; i < size; i++) {
matrix[i] = new int[size];
for (int j = 0; j < size; j++)
matrix[i][j] = 0;
}
if (k % 7 == 0) {
// Random adj matrix
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++)
matrix[i][j] = rand() % 2;
}
}
else {
for (int i = 0; i < size; i++) {
int j = rand() % size, m = rand() % size;
matrix[j][m] = 1;
}
}
35
// Reading matrix
g.matrixToList(matrix, size);
int* cycle = new int[size];
// Find cycle:
bool isCycle = g.findCycle(cycle);
// Output matrix
for (int i = 0; i < size; i++)
for (int j = 0; j < size; j++)
fs << matrix[i][j] << ",";
// Free memory:
for (int i = 0; i < size; i++)
delete[] matrix[i];
delete[] matrix;
}
return 0;
}
Neuron5.ipynb
# Импортируем всё необходимое.
import tensorflow as tf
import keras as k
from keras.models import load_model as l_m
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
input_names = ["aa","ab","ac","ad","ae",
"ba","bb","bc","bd","be",
"ca","cb","cc","cd","ce",
"da","db","dc","dd","de",
"ea","eb","ec","ed","ee"]
output_names = ["isCycle:"]
# Нормализация данных.
def data_frame_to_dict(df):
result = dict()
for column in df.columns:
values = data_frame[column].values
result[column] = values
return result
def make_supervised(df):
36
raw_input_data = data_frame[input_names]
raw_output_data = data_frame[output_names]
return{"inputs":data_frame_to_dict(raw_input_data),
"outputs":data_frame_to_dict(raw_output_data)}
def encode(data):
vectors = []
for data_name, data_values in data.items():
encoded = list(map(lambda dn: [dn], data_values))
vectors.append(encoded)
formatted = []
for vector_raw in list(zip(*vectors)):
vector = []
for element in vector_raw:
for e in element:
vector.append(e)
formatted.append(vector)
return formatted
supervised = make_supervised(data_frame)
encoded_inputs = np.array(encode(supervised["inputs"]))
encoded_outputs = np.array(encode(supervised["outputs"]))
test_x = encoded_inputs[999500:]
test_y = encoded_outputs[999500:]
# Обучение.
fit_result = model.fit(x = train_x, y = train_y, epochs = 30, validation_split =
0.2)
plt.title("Accuracies train/validation")
plt.plot(fit_result.history["accuracy"], label = "Train")
37
plt.plot(fit_result.history["val_accuracy"], label = "Validation")
plt.legend()
plt.show()
Neuron10.ipynb
input_names = ["aa","ab","ac","ad","ae","af","ag","ah","ai","aj",
"ba","bb","bc","bd","be","bf","bg","bh","bi","bj",
"ca","cb","cc","cd","ce","cf","cg","ch","ci","cj",
"da","db","dc","dd","de","df","dg","dh","di","dj",
"ea","eb","ec","ed","ee","ef","eg","eh","ei","ej",
"fa","fb","fc","fd","fe","ff","fg","fh","fi","fj",
"ga","gb","gc","gd","ge","gf","gg","gh","gi","gj",
"ha","hb","hc","hd","he","hf","hg","hh","hi","hj",
"ia","ib","ic","id","ie","if","ig","ih","ii","ij",
38
"ja","jb","jc","jd","je","jf","jg","jh","ji","jj"]
output_names = ["isCycle:"]
# Нормализация данных.
def data_frame_to_dict(df):
result = dict()
for column in df.columns:
values = data_frame[column].values
result[column] = values
return result
def make_supervised(df):
raw_input_data = data_frame[input_names]
raw_output_data = data_frame[output_names]
return{"inputs":data_frame_to_dict(raw_input_data),
"outputs":data_frame_to_dict(raw_output_data)}
def encode(data):
vectors = []
for data_name, data_values in data.items():
encoded = list(map(lambda dn: [dn], data_values))
vectors.append(encoded)
formatted = []
for vector_raw in list(zip(*vectors)):
vector = []
for element in vector_raw:
for e in element:
vector.append(e)
formatted.append(vector)
return formatted
supervised = make_supervised(data_frame)
encoded_inputs = np.array(encode(supervised["inputs"]))
encoded_outputs = np.array(encode(supervised["outputs"]))
test_x = encoded_inputs[999500:]
test_y = encoded_outputs[999500:]
39
model.compile(loss = "mse", optimizer="adam", metrics=["accuracy"])
# Обучение.
fit_result = model.fit(x = train_x, y = train_y, epochs = 30, validation_split =
0.2)
# Вывод статистики обучения.
plt.title("Losses train/validation")
plt.plot(fit_result.history["loss"], label = "Train")
plt.plot(fit_result.history["val_loss"], label = "Validation")
plt.legend()
plt.show()
plt.title("Accuracies train/validation")
plt.plot(fit_result.history["accuracy"], label = "Train")
plt.plot(fit_result.history["val_accuracy"], label = "Validation")
plt.legend()
plt.show()
40