Доброго времени суток, хаброжители.
Давно хотел запостить что-нибудь годное на Хабр, да не было идеи.
И тут я вспомнил об одном своём проекте, который канул в лету вместе с хранилищем на котором был записан. Это упрощенная модель моей курсовой работы, когда я ее сделал, знаний было в голове ого-го. А теперь приходится все заново вспоминать и делать.
В общем, в данном проекте будет два различных изображения, которые программа будет распознавать. Оба изображения генерируются программно, с помощью фреймворка 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"); }
Спасибо за внимание!
ссылка на оригинал статьи https://habr.com/ru/post/556242/
Добавить комментарий