Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Доброго времени суток, хаброжители.
Давно хотел запостить что-нибудь годное на хабр, да не было идеи.
И тут я вспомнил об одном своём проекте, который канул в лету вместе с хранилищем на котором был записан. Это упрощенная модель моей курсовой работы, когда я ее сделал, знаний было в голове ого-го. А теперь приходится все заново вспоминать и делать.
Вообщем в данном проекте будет два различных изображения, которые программа будет распознавать. Оба изображения генерируются программно, с помощью фреймворка qt и c++.
В сумме около 300 строчек кода.
Вот данные изображения:
И второе:
Они размером 400 на 300 пикселей.
Далее в помощь нам будет нейронная сеть состоящая из нейронов, количество нейронов на входе будет столько, сколько классов изображений, количество нейронов на выходе тоже должно соответствовать количеству классов, а поскольку они просты, то всего будет 2 слоя, входящий и выходящий, то есть первый слой будет просто передавать входящий сигнал на входе следующему слою и он будет решать какому классу принадлежит первое или второе изображение.
Какие же сигналы программа будет учитывать?
Поскольку картинки черно-белые, то можно в структуру данных вектор вносить "y" координату встречающегося по пути сверху-вниз первого пикселя не белого цвета, а координата "x" будет и так записана в порядке поступления координат в вектор (400 значений временного ряда). По сути будет одномерный образец изображения, который потом с помощью "квартальной метрики" мы и будем определять у кого меньше, тот нейрон и победил.
Обучать нейроны 2 слоя будем с помощью алгоритма обучения карт Кохонена:
k - коэффициент обучения, x[i] - значения по ординате "y" как бы не было смешно, w(t)-весовой коэффициент на данной итерации обучения,w(t+1) - на следующей. Нейроны первого слоя будут передавать входные сигналы каждый своему нейрону 2 слоя, без скрещивания, также функция активации будет отсутствовать(на самом деле она будет тождественна если что x->f(x)->x).
Как полагается создадим нейроны и инициализируем их случайными значениями:
тут точками разбросанными по картинке и есть случайные значения.
Для второго класса:
Далее начнем обучать нейроны по данному алгоритму при коэффициенте обучения k = 0,75 и 2-х итерация обучения:
Как видите точки начали сгущаться около целевого по сути графика
Это сделано затем, чтобы наша метрика давала, как можно меньшую ошибку при распознавании.
При увеличении коэффициента обучения k и увеличении количества итераций случайные величины сольются с графиком и их не будет видно, что даст ошибку в районе 10^-6 и меньше.
Протестируем нашу нейросеть:
sum1 и sum2 - это прямая линия в нулевых координатах "y" слева-направо (добавлена чисто по фану)
v-v - это сработал нейрон обучающийся на V-картинке, дал значение выходного параметра для v 2533.56, что соответственно меньше чем дал нейрон обучающийся w-v 39032.4 поэтому первый нейрон правильно распознал. А в обратном случае тоже правильно: обучающийся нейрон на w дал меньший результат для w чем его коллега. Как видно по итогу прямая линия хуже всего.
А вот результат для 10 итераций обучения и того же коэффициента обучения:
Уже три сотых, что радует.
А вот если прямую провести по центру по горизонтали, то она распознает оба класса лучше, чем нейроны при ошибках 1го рода:
Далее идет код программы main.cpp:
#include <iostream>
#include <QtWidgets/QApplication>
#include "MainWindow.h"
int main(int argc,char*argv[])
{
QApplication a(argc, argv);
QWidget qw;
MainWindow mw(&qw);
mw.show();
mw.CreateImage("");
mw.CreateImage2("");
mw.OpenImage("");
mw.OpenImage2("");
return a.exec();
}
Neuron.h:
#pragma once
#include <ctime>
#include <iostream>
#include <vector>
class Neuron
{
public:
std::vector<double> x, y, x0;
double error;
Neuron(std::vector<double> x, int length, int level);
int level, length, min_, max_;
std::vector<double> Send();
void Lerning(int steps);
double Thinking(std::vector<double> xx);
std::vector<double> SendW();
};
Neuron.cpp:
#include "Neuron.h"
Neuron::Neuron(std::vector<double> x,int length,int level)
{
this->x = x;
this->length = length;
if (level == 2)
{
srand(time(NULL));
min_ = 0;
max_ = 300;
for (size_t i = 0; i < length; i++)
{
x0.push_back(min_ + rand() % (max_ - min_ + 1));
y.push_back(x0[i]);
//std::cout << x0[i] << std::endl;
}
}
this->level = level;
this->error = 0.0;
}
std::vector<double> Neuron::Send()
{
if (level == 1)
return this->x;
else
return this->y;
}
std::vector<double> Neuron::SendW()
{
return this->x0;
}
void Neuron::Lerning(int steps)
{
float k = 0.75;
for (size_t j = 0; j < steps; j++)
{
for (size_t i = 0; i < length; i++)
{
x0[i] = x0[i] + k* (x[i]-x0[i]);
}
}
}
double Neuron::Thinking(std::vector<double> xx)
{
error = 0.0;
for (size_t i = 0; i < length; i++)
{
y[i] = abs(xx[i] - x0[i]);
error += y[i];
}
return error;
}
NeuralNet.h
#pragma once
#include "Neuron.h"
//#include <vector>
class NeuralNet
{
public:
std::vector<Neuron*> l1,l2;
void InitNeurons(std::vector<double> x, int length);
void LearnNeurons(int steps, int i);
double TestNeurons(int i, std::vector<double> xx);
};
NeuralNet.cpp
#include "NeuralNet.h"
void NeuralNet::InitNeurons(std::vector<double> x,int length)
{
l1.push_back(new Neuron(x,length,1));
}
void NeuralNet::LearnNeurons(int steps,int i)
{
l2.push_back(new Neuron(l1[i]->Send(), l1[i]->length, 2));
l2[i]->Lerning(steps);
}
double NeuralNet::TestNeurons(int i, std::vector<double> xx)
{
double res = 0.0 ;
res = l2[i]->Thinking(xx);
return res;
}
MainWindow.h
#pragma once
#include <QtWidgets/qmainwindow.h>
#include <QtGui/qpicture.h>
#include <QtGui/qimage.h>
#include <QtGui/qpainter.h>
#include <QtCore/qdebug.h>
#include <vector>
#include <iostream>
#include <fstream> // работа с файлами
#include <iomanip>
#include "NeuralNet.h"
#pragma comment(lib,"Qt5Core.lib")
#pragma comment(lib,"Qt5Widgets.lib")
#pragma comment(lib,"Qt5Gui.lib")
namespace Ui {
class MainWindow;
}
//Q_OBJECT
class MainWindow : public QMainWindow
{
//Q_OBJECT
public:
explicit MainWindow(QWidget* parent = 0);
void CreateImage(QString path);
void CreateImage2(QString path);
void OpenImage(QString path);
void OpenImage2(QString path);
std::vector<double> x1, x2, zeros1, zeros2;
QImage* image_t1, * image_t2 ;
NeuralNet net;
// ~MainWindow();
protected:
void paintEvent(QPaintEvent*); // пееопределение виртуальной функции
private:
Ui::MainWindow* ui;
};
MainWindow.cpp
#include "MainWindow.h"
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
{
setWindowTitle(tr("Neural"));
setGeometry(0, 0, 1000, 700);
}
void MainWindow::paintEvent(QPaintEvent*)
{
QImage img("testImage.png"); // загружаем картинку
QPainter painter(this); // определяем объект painter, который обеспечивает рисование
painter.drawImage(0, 0, img.scaled(this->size())); // рисуем наше изображение от 0,0 и растягиваем по всему виджету
}
void MainWindow::CreateImage(QString path)
{
QImage image(QSize(400, 300), QImage::Format_RGB32);
QPainter painter(&image);
painter.fillRect(QRectF(0, 0, 400, 300), Qt::white);
painter.setPen(QPen(Qt::black));
painter.drawLine(0, 0, image.width() / 2, image.height());
painter.drawLine(image.width() / 2, image.height(), image.width(), 0);
image.save("testImage.png");
}
void MainWindow::CreateImage2(QString path)
{
QImage image(QSize(400, 300), QImage::Format_RGB32);
QPainter painter(&image);
painter.fillRect(QRectF(0, 0, 400, 300), Qt::white);
painter.setPen(QPen(Qt::black));
painter.drawLine(0, 0, image.width() / 4, image.height());
painter.drawLine(image.width() / 4, image.height() , image.width() / 2, 0);
painter.drawLine(image.width() / 2, 0, 3 * image.width() / 4, image.height());
painter.drawLine(3*image.width() / 4, image.height(), image.width() , 0);
image.save("testImage2.png");
}
void MainWindow::OpenImage(QString path)
{
image_t1 = new QImage("testImage.png");
QPoint qp;
std::ofstream fout("data.txt", std::ios_base::out | std::ios_base::trunc);
for (int i = 0; i < image_t1->width(); i++)
{
for (int j = 0; j < image_t1->height(); j++)
{
qp.setX(i);
qp.setY(j);
if (image_t1->pixel(qp) != 4294967295/* 4278190080*/)
{
x1.push_back(j);
zeros1.push_back(abs(150-j));
//qDebug() << j << " ";
fout << j<< std::endl;
break;
}
}
}
qDebug() <<"size:"<< x1.size() << " ";
int sum = 0;
for (int j = 0; j < zeros1.size(); j++)
{
sum += zeros1[j];
}
qDebug() << "sum1=" << sum << " ";
fout.close();
net.InitNeurons(x1, x1.size());
net.LearnNeurons(10,0);
//net.TestNeurons(0);
for (size_t i = 0; i < net.l2[0]->SendW().size(); i++)
{
qp.setX(i);
qp.setY(int(net.l2[0]->SendW()[i]));
image_t1->setPixel(qp, Qt::red);
}
image_t1->save("testImage1-1-1-1-000000.png");
}
void MainWindow::OpenImage2(QString path)
{
image_t2 = new QImage("testImage2.png");
QPoint qp;
std::ofstream fout("data.txt", std::ios_base::out | std::ios_base::trunc);
for (int i = 0; i < image_t2->width(); i++)
{
for (int j = 0; j < image_t2->height(); j++)
{
qp.setX(i);
qp.setY(j);
if (image_t2->pixel(qp) != 4294967295/* 4278190080*/)
{
x2.push_back(j);
fout << j << std::endl;
zeros2.push_back(abs(150 - j));
//qDebug() << j << " ";
break;
}
}
}
qDebug() << "size:" << x2.size() << " ";
int sum = 0;
for (int j = 0; j < zeros2.size(); j++)
{
sum += zeros2[j];
}
qDebug()<<"sum2=" << sum << " ";
fout.close();
net.InitNeurons(x2, x2.size());
net.LearnNeurons(10,1);
qDebug() <<"v - v"<< net.TestNeurons(0, x1);
qDebug() <<"v - w"<< net.TestNeurons(0, x2);
qDebug() <<"w - v"<< net.TestNeurons(1, x1);
qDebug() <<"w - w"<< net.TestNeurons(1, x2);
for (size_t i = 0; i < net.l2[1]->SendW().size(); i++)
{
qp.setX(i);
qp.setY(int(net.l2[1]->SendW()[i]));
//qDebug() << int(net.l2[1]->SendW()[i]) << " ";// std::endl;
image_t2->setPixel(qp, Qt::red);
}
image_t2->save("testImage2-2-2-2-000000.png");
}
Спасибо за внимание!