在AI技术的发展历史中,模式识别模型是最伟大的AI技术之一,尤其是从像素图像中读取文本。其中一个是Hopfield网络(或称为Ising模型的神经网络或Ising–Lenz–Little模型),这是一种递归神经网络形式,由John J. Hopfield博士在1982年发明。Hopfield网络可以应用于模式识别,例如从像素图像中识别数字字符。在本文中,我们将使用C+ Builder开发一个简单的Hopfield网络GUI形式,它可以从像素模式中学习,并且我们可以通过测试一些最接近的模式来回忆它们。
目录
- Hopfield网络在AI开发中是什么?
- 如何训练Hopfield网络?
- 如何在C+中开发一个简单的Hopfield网络?
- 如何在C+ Builder中开发一个简单的Hopfield网络?
- 如何在C+ Builder中测试一个简单的Hopfield网络应用?
- C+ Builder中有关Hopfield网络的完整示例代码?
Hopfield网络在AI开发中是什么?
Hopfield网络的目的是存储数据模式,并根据部分输入回忆完整的数据模式。例如,Hopfield网络可以应用于8×8数字字符识别。我们可以在这个网络上训练一些字符,然后我们可以询问一个更接近的字符,如果它记住了训练过的字符或没有。我们可以使用这个Hopfield网络从图像中提取字符并将它们以ASCII形式放入文本文件中,这被称为模式识别。这个网络的良好行为是,即使字符的完整形式没有给出,它也能记住字符的完整形式。这个例子可以用于从文本中识别光学字符,如果字母的一小部分有变形或纸张脏污,Hopfield网络可能能记住这种类型的问题。在现代ML和AI应用中,有许多更有用的方法基于Hopfield网络,如当前的人工神经网络。
Hopfield网络(或称为Ising模型的神经网络或Ising–Lenz–Little模型)是一种递归神经网络形式,由John J. Hopfield博士在1982年发明。它由一个单层组成,包含一个或多个完全连接的递归神经元。Hopfield网络常用于自动关联和优化任务。Hopfield网络作为具有二进制阈值节点的内容可寻址存储系统。Hopfield网络还提供了一个基本模型,用于理解人脑和记忆如何被人工模拟。
如何训练Hopfield网络
如何在数字字符识别编程中应用这种方法?Hopfield网络如何在1和0像素上工作?我们可以将每个像素映射到Hopfield网络中的一个节点。我们可以在这个网络上训练正确的字符形式以识别每个字符。Hopfield网络在几次迭代后找到最可能的假设字符,并最终以训练的正确形式再现模式。如果我们有N个像素,这意味着我们的网络有NxN个权重,所以这个问题在计算上非常昂贵,可能在某些情况下很慢。例如,我们可以训练空白形式(空格)和这个“A”形式,如此进行即可。
Hopfield网络中的所有节点都用作输入和输出,并且它们完全相互连接。也就是说,每个节点是网络中每个其他节点的输入。我们可以将每个节点到自身的链接视为权重为0的链接。
我们可以轻松地训练Hopfield网络,我们只需要二进制数据来训练这个网络。正如我们所知,我们可以有二进制输入向量以及双极性输入向量。在Hopfield网络的训练过程中,权重在迭代中更新。
例如,对于一个由N个元素组成的3节点Hopfield网络数据矩阵和由NxN个元素组成的权重矩阵。以下是这些矩阵的3节点示例,
如何在C++中开发一个简单的Hopfield网络?
让我们创建一个简单的Hopfield网络C++ Builder示例如下。
按照给定的如下步骤创建一个简单的THopfield_Network类。
class THopfield_Network
{
private:std::vector<std::vector<int>> weights;
public:THopfield_Network(int numofNeurons) : weights(numofNeurons, std::vector<int>(numNeurons, 0)){}
我们将为这个类添加3个公共方法。首先,它将使用learn_pattern()方法从模式向量中学习,该方法定义如下,
// Learn from a pattern (update weights)
void learn_pattern(const std::vector<int>& pattern)
{for (int i = 0; i < pattern.size(); ++i){for (int j = 0; j < pattern.size(); ++j){if (i != j){weights[i][j] += pattern[i] * pattern[j];}}}
}
我们将使用update_neuron()方法更新我们的Hopfield网络神经元,该方法定义如下,
// Update neuron asynchronously
int update_neuron(const std::vector<int>& input, int neuronIndex)
{int sum = 0;for (int i = 0; i < input.size(); ++i){sum += weights[neuronIndex][i] * input[i];}return (sum >= 0) ? 1 : -1;
}
我们可以使用这个update_neuron方法通过以下定义的test()方法测试给定的输入模式,
//测试网络
std::vector<int> test(const std::vector<int>& input)
{std::vector<int> output(input);for (int i = 0; i < input.size(); ++i){output[i] = update_neuron(output, i);}return output;
}
作为结果,这个简单的Hopfield网络类将如下所示,
class THopfield_Network
{ private:std::vector<std::vector<int>> weights; public:THopfield_Network(int numofNeurons) : weights(numofNeurons, std::vector<int>(numofNeurons, 0)){}// Learn from a pattern (update weights)void learn_pattern(const std::vector<int>& pattern){for (int i = 0; i < pattern.size(); ++i){for (int j = 0; j < pattern.size(); ++j){if (i != j){weights[i][j] += pattern[i] * pattern[j];}}}}// Update neuron asynchronouslyint update_neuron(const std::vector<int>& input, int neuronIndex){int sum = 0;for (int i = 0; i < input.size(); ++i){sum += weights[neuronIndex][i] * input[i];}return (sum >= 0) ? 1 : -1;}// Test the networkstd::vector<int> test(const std::vector<int>& input){std::vector<int> output(input);for (int i = 0; i < input.size(); ++i){output[i] = update_neuron(output, i);}return output;}
};
现在我们可以全局定义一个Hopfield网络,给定宽度和高度像素。例如,如果我们有6×6像素模式,我们可以定义如下,
int W = 6, H = 6;
THopfield_Network hopfield(W*H);
现在我们可以定义一个模式向量如下,
std::vector<int> pattern = {1, -1, -1, -1, -1, -1,-1, 1, -1, -1, -1, -1,-1, -1, 1, -1, -1, -1,-1, -1, -1, 1, -1, -1,-1, -1, -1, -1, 1, -1,-1, -1, -1, -1, -1, 1};
我们可以训练这个模式,如果我们有另一个如上的输入模式,我们可以测试它以获得最接近给定输入的结果(recovered_pattern)。
hopfield.learn_pattern(pattern);std::vector<int> recovered_pattern = hopfield.test(input_pattern);
现在我们在C++builder中完成一个GUI界面的Hopfield网络。
如何在C++Builder中开发一个简单的Hopfield网络?
让我们创建一个简单的C++ Builder中的Hopfield网络示例如下。
- 首先,创建一个新的C++ Builder FMX应用程序,添加一个图像(TImage),备忘录(TMemo)和3个按钮(TButton),它们是“清除”,“训练”和“测试”按钮。你可以添加一些布局来安排它们,如给定的表单(TForm)设计如上图。
-
将我们的THopfield网络类添加到“TForm1 *Form1;”行下面。
-
在Form单元的头文件中定义bmp和bmp2位图如下,其中bmp将用作输入模式,bmp2将显示在图像上。
TBitmap* bmp, *bmp2;
这样,
bmp
将用于存储输入模式的像素值,而bmp2
用于显示处理后的图像。class TForm1 : public TForm { __published: // IDE-managed ComponentsTImage *Image1;TButton *btTrain;TButton *btTest;TButton *btClear;TMemo *Memo1;TLayout *Layout1;TLayout *Layout2;void __fastcall btClearClick(TObject *Sender);void __fastcall Image1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y);void __fastcall btTrainClick(TObject *Sender);void __fastcall btTestClick(TObject *Sender); private: // User declarations public: // User declarationsTBitmap *bmp, *bmp2;__fastcall TForm1(TComponent* Owner);};
4. 现在开始创建这些位图。
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{bmp = new TBitmap(W,H);bmp2 = new TBitmap(Image1->Width, Image1->Height);Image1->Bitmap->Assign(bmp2);
}
5. 定义Clear按钮事件。
void __fastcall TForm1::btClearClick(TObject *Sender)
{bmp->Clear(claBlack);Image1->Bitmap->Clear(claBlack);
}
6. 为了允许用户通过点击图像定义自己的模式,我们需要处理Image1
的的
OnMouseDown事件。当用户在
Image1`上双击时,我们可以捕获鼠标点击的像素位置,并根据这些位置更新网络的权重。以下是一个处理双击鼠标点击事件的示例:
void __fastcall TForm1::Image1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y)
{float w = Image1->Width / W; // grid widthfloat h = Image1->Height / H; // grid heightint px = X / w; // exact pixel X on the bitmap scaled imageint py = Y / h; // exact pixel Y on the bitmap of scaled imageTBitmapData bitmapData;TAlphaColorRec acr;if (bmp->Map(TMapAccess::ReadWrite, bitmapData)) // Lock bitmap and retrive bitmap data{ac.Color = bitmapData.GetPixel(px, py);if (ac.Color == claBlack){bitmapData.SetPixel(px, py, claWhite);Image1->Bitmap->Canvas->BeginScene();Image1->Bitmap->Canvas->Fill->Color = claWhite;Image1->Bitmap->Canvas->FillRect(TRectF(px * w, py * h, px * w + w, py * h + h, 0, 0, AllCorners, 255.0);Image1->Bitmap->Canvas->EndScene();}else{bitmapData.SetPixel(px, py, claBlack);Image1->Bitmap->Canvas->BeginScene();Image1->Bitmap->Canvas->Fill->Color = claBlack;Image1->Bitmap->Canvas->FillRect(TRectF(px * w, py * h, px * w + w, py * h + h, 0, 0, AllCorners, 255.0);Image1->Bitmap->Canvas->EndScene();}bmp->Unmap(bitmapData);
}
在这个事件处理函数中,我们首先计算出鼠标点击位置对应的网格宽度w
和高度h
,然后获取该位置的像素颜色。如果像素颜色是黑色,我们将其设置为白色,并在Image1
上绘制一个白色方块。如果不是黑色,我们将其设置为黑色,并在Image1
上绘制一个黑色方块。这样用户就可以在Image1
上绘制模式,然后训练Hopfield网络。
7.
现在,我们可以通过双击“训练”按钮来创建训练方法。在这个方法中,我们将从bmp位图中读取像素数据,然后在备忘录中显示这些数据,然后我们将使用hopfield.learn_pattern(pattern)
来学习这个模式。以下是如何实现的示例:
void __fastcall TForm1::btTrainClick(TObject *Sender)
{std::vector<int> pattern;TBitmapData bitmapData;TAlphaColor acr;if (bmp->Map(TMapAccess::ReadWrite, bitmapData)) // Lock bitmap and retrive bitmap data{for (int j = 0; j < H; j++){for (int i = 0; i < W; i++){ac.Color = bitmapData.GetPixel(i, j);if (ac.Color == claBlack){pattern.push_back(-1);}else{pattern.push_back(1);}}bmp->Unmap(bitmapData);}Memo1->Lines->Add("Learned Pattern:");for (int j = 0; j < H; j++){String str = "";for (int i = 0; i < W; i++){if (pattern[j * W + i] == 1){str += "*";}else{str += " ";}}Memo1->Lines->Add(str);}Memo1->Lines->Add("------------");hopfield.learn_pattern(pattern);
}
在这个btTrainClick
事件处理函数中,我们首先检查bmp
位图是否可以被锁定并获取其数据。然后,我们遍历位图的每个像素,检查其颜色,并根据颜色更新模式向量。最后,我们将模式向量传递给hopfield
对象,调用learn_pattern
方法进行训练。训练完成后,我们将训练结果添加到备忘录中,供后续测试或使用。
8. 最后,我们可以通过双击“测试”按钮来创建测试事件。在这个事件处理函数中,我们将使用之前训练的模式向量来测试网络,检查网络是否能够正确回忆训练的模式。以下是如何实现的示例:
void __fastcall TForm1::btTestClick(TObject *Sender)
{std::vector<int> pattern;TBitmapData bitmapData;TAlphaColor acr;if (bmp->Map(TMapAccess::ReadWrite, bitmapData)) // Lock bitmap and retrive bitmap data{for (int j = 0; j < H; j++){for (int i = 0; i < W; i++){ac.Color = bitmapData.GetPixel(i, j);if (ac.Color == claBlack)pattern.push_back(-1);elsepattern.push_back(1);}bmp->Unmap(bitmapData);}std::vector<int> recovered_pattern = hopfield.test(pattern);float w = Image1->Width / W; // grid widthfloat h = Image1->Height / H; // grid heightMemo1->Lines->Add("Recovered Pattern:");Image1->Bitmap->Canvas->BeginScene();for (int j = 0; j < H; j++){String str = "";for (int i = 0; i < W; i++){if (recovered_pattern[j * W + i] == 0){Image1->Bitmap->Canvas->Fill->Color = claWhite;Image1->Bitmap->Canvas->FillRect(TRectF(i * w, j * h, i * w + w, j * h + h, 0, 0, AllCorners, 255.0);str += "* ";}else{Image1->Bitmap->Canvas->Fill->Color = claBlack;Image1->Bitmap->Canvas->FillRect(TRectF(i * w, j * h, i * w + w, j * h + h, 0, 0, AllCorners, 255.0);str += " ";}}Memo1->Lines->Add(str);Image1->Bitmap->Canvas->EndScene();Memo1->Lines->Add("------------");
}
在这个btTestClick
事件处理函数中,我们首先检查bmp
位图是否可以被锁定并获取其数据。然后,我们遍历位图的每个像素,检查其颜色,并根据颜色更新模式向量。最后,我们将模式向量传递给hopfield
对象,调用test
方法进行测试。测试完成后,我们将恢复的模式添加到备忘录中,供后续查看或使用。这样,我们就可以通过测试事件来验证Hopfield网络是否能够正确回忆训练的模式。
如何在C++ Builder中测试一个简单的Hopfield网络应用?
现在我们可以运行我们的应用程序(F9)。首先,我们可以清除,然后训练这个空白模式,然后我们可以绘制“A”,然后我们可以训练这个。你可以训练更多的不同模式。
在此步骤之后,我们可以使用“测试”按钮来询问另一个模式,这个模式看起来像我们之前训练过的模式之一,例如下面的模式:
hopfield.learn_pattern(pattern);
std::vector<int> recovered_pattern = hopfield.test(input_pattern);
通过这种方式,我们可以训练不同的模式,然后通过测试按钮来验证网络是否能够识别我们训练过的模式。这有助于我们了解网络的性能,以及它是否能够准确地回忆训练过的模式。
作为结果,你将看到我们的Hopfield网络记住了最接近的模式并恢复了它。这表明网络能够有效地存储和回忆训练过的模式,即使输入不完全匹配,网络也能够识别出最相似的模式。这种能力在模式识别和图像处理等应用中非常有用。
C++ Builder中有关Hopfield网络的完整示例代码?
以下是C+ Builder FMX应用程序的完整示例,注意头部也在上面给出。
//---------------------------------------------------------------------------#include <fmx.h>
#include <vector>#pragma hdrstop#include "Hopfield_Network_FMX_Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm1 *Form1;class THopfield_Network
{ private:std::vector<std::vector<int>> weights; public:THopfield_Network(int numofNeurons) : weights(numofNeurons, std::vector<int>(numofNeurons, 0)){}// Learn from a pattern (update weights)void learn_pattern(const std::vector<int>& pattern){for (int i = 0; i < pattern.size(); ++i){for (int j = 0; j < pattern.size(); ++j){if (i != j){weights[i][j] += pattern[i] * pattern[j];}}}}// Update neuron asynchronouslyint update_neuron(const std::vector<int>& input, int neuronIndex){int sum = 0;for (int i = 0; i < input.size(); ++i){sum += weights[neuronIndex][i] * input[i];}return (sum >= 0) ? 1 : -1;}// Test the networkstd::vector<int> test(const std::vector<int>& input){std::vector<int> output(input);for (int i = 0; i < input.size(); ++i){output[i] = update_neuron(output, i);}return output;}
};int W = 6, H = 6;
THopfield_Network hopfield(W*H);//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{bmp = new TBitmap(W,H);bmp2 = new TBitmap(Image1->Width, Image1->Height);Image1->Bitmap->Assign(bmp2);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::btClearClick(TObject *Sender)
{bmp->Clear(claBlack);Image1->Bitmap->Clear(claBlack);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Image1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y)
{float w = Image1->Width/W; // grid widthfloat h = Image1->Height/H; // grid heightint px = X/w; // exact pixel X on the bitmap of scaled imageint py = Y/h; // exact pixel Y on the bitmap of scaled imageTBitmapData bitmapData;TAlphaColorRec acr;if( bmp->Map(TMapAccess::ReadWrite, bitmapData)) // Lock bitmap and retrive bitmap data{acr.Color = bitmapData.GetPixel( px, py );if(acr.Color == claBlack){bitmapData.SetPixel(px,py, claWhite);Image1->Bitmap->Canvas->BeginScene();Image1->Bitmap->Canvas->Fill->Color = claWhite;Image1->Bitmap->Canvas->FillRect( TRectF(px*w,py*h, px*w+w,py*h+h), 0, 0, AllCorners, 255.0);Image1->Bitmap->Canvas->EndScene();}else{bitmapData.SetPixel(px,py, claBlack);Image1->Bitmap->Canvas->BeginScene();Image1->Bitmap->Canvas->Fill->Color = claBlack;Image1->Bitmap->Canvas->FillRect( TRectF(px*w,py*h, px*w+w,py*h+h), 0, 0, AllCorners, 255.0);Image1->Bitmap->Canvas->EndScene();}bmp->Unmap(bitmapData);}}
//---------------------------------------------------------------------------
void __fastcall TForm1::btTrainClick(TObject *Sender)
{std::vector<int> pattern;TBitmapData bitmapData;TAlphaColorRec acr;if( bmp->Map(TMapAccess::ReadWrite, bitmapData)) // Lock bitmap and retrive bitmap data{for(int j=0; j<H; j++)for(int i=0; i<W; i++){acr.Color = bitmapData.GetPixel( i, j );if(acr.Color == claBlack) pattern.push_back(-1);else pattern.push_back(1);}bmp->Unmap(bitmapData);}Memo1->Lines->Add("Learned Pattern:");for(int j=0; j<H; j++){String str ="";for(int i=0; i<W; i++){if(pattern[j*W+i]==1) str += "* ";else str += " ";}Memo1->Lines->Add(str);}Memo1->Lines->Add("------------");hopfield.learn_pattern(pattern);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::btTestClick(TObject *Sender)
{std::vector<int> pattern;TBitmapData bitmapData;TAlphaColorRec acr;if( bmp->Map(TMapAccess::ReadWrite, bitmapData)) // Lock bitmap and retrive bitmap data{for(int j=0; j<H; j++)for(int i=0; i<W; i++){acr.Color = bitmapData.GetPixel( i, j );if(acr.Color == claBlack) pattern.push_back(-1);else pattern.push_back(1);}bmp->Unmap(bitmapData);}std::vector<int> recoveredpattern = hopfield.test(pattern);float w = Image1->Width/W; // grid widthfloat h = Image1->Height/H; // grid heightMemo1->Lines->Add("Recovered Pattern:");Image1->Bitmap->Canvas->BeginScene();for(int j=0; j<H; j++){String str ="";for(int i=0; i<W; i++){if(recoveredpattern[j*W+i]==1){Image1->Bitmap->Canvas->Fill->Color = claWhite;Image1->Bitmap->Canvas->FillRect( TRectF(i*w, j*h, i*w+w, j*h+h), 0, 0, AllCorners, 255.0);str += "* ";}else{Image1->Bitmap->Canvas->Fill->Color = claBlack;Image1->Bitmap->Canvas->FillRect( TRectF(i*w, j*h, i*w+w, j*h+h), 0, 0, AllCorners, 255.0);str += " ";}}Memo1->Lines->Add(str);}Image1->Bitmap->Canvas->EndScene();Memo1->Lines->Add("------------");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{bmp->Free(); bmp2->Free();
}
//---------------------------------------------------------------------------