在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网络类将如下所示,
cpp
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 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;
}
// Test the network
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网络,给定宽度和高度像素。例如,如果我们有6×6像素模式,我们可以定义如下,
int W = 6, H = 6;
THopfield_Network hopfield(W*H);
现在我们可以定义一个模式向量如下,
cpp
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)。
cpp
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
用于显示处理后的图像。cppclass TForm1 : public TForm { __published: // IDE-managed Components TImage *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 declarations TBitmap *bmp, *bmp2; __fastcall TForm1(TComponent* Owner); };
-
现在开始创建这些位图。
cpp
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
bmp = new TBitmap(W,H);
bmp2 = new TBitmap(Image1->Width, Image1->Height);
Image1->Bitmap->Assign(bmp2);
}
- 定义Clear按钮事件。
cpp
void __fastcall TForm1::btClearClick(TObject *Sender)
{
bmp->Clear(claBlack);
Image1->Bitmap->Clear(claBlack);
}
-
为了允许用户通过点击图像定义自己的模式,我们需要处理
Image1
的的
OnMouseDown事件。当用户在
Image1`上双击时,我们可以捕获鼠标点击的像素位置,并根据这些位置更新网络的权重。以下是一个处理双击鼠标点击事件的示例:void __fastcall TForm1::Image1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, float X, float Y)
{
float w = Image1->Width / W; // grid width
float h = Image1->Height / H; // grid height
int px = X / w; // exact pixel X on the bitmap scaled image
int py = Y / h; // exact pixel Y on the bitmap of scaled image
TBitmapData 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网络。
现在,我们可以通过双击"训练"按钮来创建训练方法。在这个方法中,我们将从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
方法进行训练。训练完成后,我们将训练结果添加到备忘录中,供后续测试或使用。
-
最后,我们可以通过双击"测试"按钮来创建测试事件。在这个事件处理函数中,我们将使用之前训练的模式向量来测试网络,检查网络是否能够正确回忆训练的模式。以下是如何实现的示例:
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);
else
pattern.push_back(1);
}
bmp->Unmap(bitmapData);
}
std::vector<int> recovered_pattern = hopfield.test(pattern);
float w = Image1->Width / W; // grid width
float h = Image1->Height / H; // grid height
Memo1->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应用程序的完整示例,注意头部也在上面给出。
cpp
//---------------------------------------------------------------------------
#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 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;
}
// Test the network
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;
}
};
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 width
float h = Image1->Height/H; // grid height
int px = X/w; // exact pixel X on the bitmap of scaled image
int py = Y/h; // exact pixel Y on the bitmap of scaled image
TBitmapData 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 width
float h = Image1->Height/H; // grid height
Memo1->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();
}
//---------------------------------------------------------------------------