C++builder中的人工智能(20):如何在C++中开发一个简单的Hopfield网络

在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网络示例如下。

  1. 首先,创建一个新的C++ Builder FMX应用程序,添加一个图像(TImage ),备忘录(TMemo )和3个按钮(TButton ),它们是"清除","训练"和"测试"按钮。你可以添加一些布局 来安排它们,如给定的表单(**TForm)**设计如上图。
  1. 将我们的THopfield网络类添加到"TForm1 *Form1;"行下面。

  2. 在Form单元的头文件中定义bmp和bmp2位图如下,其中bmp将用作输入模式,bmp2将显示在图像上。

    TBitmap* bmp, *bmp2;
    

    这样,bmp将用于存储输入模式的像素值,而bmp2用于显示处理后的图像。

    cpp 复制代码
     
    class 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);
     
    };
  3. 现在开始创建这些位图。

cpp 复制代码
__fastcall TForm1::TForm1(TComponent* Owner)  : TForm(Owner)
{
   bmp = new TBitmap(W,H);
 
   bmp2 = new TBitmap(Image1->Width, Image1->Height);
   Image1->Bitmap->Assign(bmp2);
}
  1. 定义Clear按钮事件。
cpp 复制代码
 
void __fastcall TForm1::btClearClick(TObject *Sender)
{
    bmp->Clear(claBlack);
    Image1->Bitmap->Clear(claBlack);
}
 
  1. 为了允许用户通过点击图像定义自己的模式,我们需要处理Image1OnMouseDown事件。当用户在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方法进行训练。训练完成后,我们将训练结果添加到备忘录中,供后续测试或使用。

  1. 最后,我们可以通过双击"测试"按钮来创建测试事件。在这个事件处理函数中,我们将使用之前训练的模式向量来测试网络,检查网络是否能够正确回忆训练的模式。以下是如何实现的示例:

    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();
}
//---------------------------------------------------------------------------
 
相关推荐
搬砖的小码农_Sky15 分钟前
C语言:数组
c语言·数据结构
机器视觉知识推荐、就业指导23 分钟前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Yang.992 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王2 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_2 小时前
C++自己写类 和 运算符重载函数
c++
ZHOU_WUYI3 小时前
3.langchain中的prompt模板 (few shot examples in chat models)
人工智能·langchain·prompt
如若1233 小时前
主要用于图像的颜色提取、替换以及区域修改
人工智能·opencv·计算机视觉
六月的翅膀3 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++
老艾的AI世界3 小时前
AI翻唱神器,一键用你喜欢的歌手翻唱他人的曲目(附下载链接)
人工智能·深度学习·神经网络·机器学习·ai·ai翻唱·ai唱歌·ai歌曲