自组织特征图SOFM网络的实现

这里直接给出Qt代码段和演示结果。

理论部分请移步另一位博主的 自组织特征图(SOFM)详解_datamonday的博客-CSDN博客_sofm算法

算法基于 Martin H. Hagan 的《神经网络设计》 第十四章竞争网络实现。其中按照原书,使用相关性(Wp)而不是距离(||W-P||)作为获胜判定。以后将在学习向量量化(LVQ)网络中使用距离

自组织特征图SOFM网络的实现_第1张图片

算法改进包括:

  • 引入逐渐变小的学习步长

  • 引入墨西哥草帽函数实现“加强中心/抑制周围”

参考代码 AANN_sofm::initNeighborMatrix_MexicoStrawHat(int N)

  • 引入良心变数(conscience),并限制良心变数使用的频率

  • 使用胜出训练参数和需要调整的W(i)之间的距离来决定学习步长a,否则在实践中会发现向量很难收敛在训练数据集形成的曲面上。

auto a = (P.transpose() - m_layer.W.row(maxIndex)).squaredNorm()/(0.01+m_layer.W.row(maxIndex).squaredNorm());)

AANN_sofm.h 文件

#ifndef AANN_SOFM_H
#define AANN_SOFM_H

#include 
#include 
#include 
#include "QEigenExt.h"
using namespace Eigen;

#include "AANN_instar.h"
/**
 *
 * 自联想神经网络(Auto-Associative Neural Network , 缩写为AANN)
 * 是1987年Ballard提出的,其网络原型是一种具有对称拓扑结构的五层前馈传递网络,
 * AANN 应用到数据检验问题时具有比较明显的物理意义。是BP神经网络的一种特殊情形。
 * 其特点是有对称拓扑结构,即输出量等于输入量。
 */
class AANN_sofm
{
public:
    AANN_sofm();

public:
//    int GetLayerNum() const;
//    bool AppendLayer(AANN_LAYER_PARAM_F layer);

    virtual bool train( const Eigen::VectorXf &P, int N );
    virtual bool calc( const Eigen::VectorXf &P, Eigen::VectorXf *A = NULL );

    float getAlpha( )  const;
    float getGamma( )  const;
    long  getRound( )  const;
    void  setAlpha( const float alpha);
    void  setGamma( const float gamma);
    bool  setNeighborMatrix( const MatrixXf neighbors );
    bool  initNeighborMatrix( int N, int type );
    bool  initNeighborMatrix_MexicoStrawHat( int N );
    bool  initNeighborMatrix_Sombrero( int N );
    const MatrixXf getWMatrix() const;
private:

    void init();

private:
//    AANN_LAYER_LIST_F m_layerList;
    AANN_LAYER_PARAM_F  m_layer;

    float m_alpha;
    float m_gamma;
    long  m_round;


};

#endif // AANN_SOFM_H

AANN_sofm.cpp文件

#include "AANN_sofm.h"

#include "activation.h"
#include 
#include 
#include 

AANN_sofm::AANN_sofm()
{
    m_alpha = 0.5f;
    m_gamma = 0.5f;
    init();
}

void AANN_sofm::init()
{
    m_layer.maxB = -1;
    m_layer.func = hardlim;
    m_round = 0;
}

float AANN_sofm::getAlpha( ) const
{
    return m_alpha;
}

float AANN_sofm::getGamma( ) const
{
    return m_gamma;
}

long AANN_sofm::getRound( )  const
{
    return m_round;
}

void AANN_sofm::setAlpha( const float alpha)
{
    m_alpha = alpha;
}

void AANN_sofm::setGamma( const float gamma)
{
    m_gamma = gamma;
}

bool  AANN_sofm::setNeighborMatrix( const MatrixXf neighbors )
{
    if( !neighbors.size() || neighbors.rows() != neighbors.cols())
    {
        return false;
    }
    m_layer.Neighbors = neighbors;
    qDebug() << QString( " --------------- neighbors %1 x %2 ---------------" )
                .arg(neighbors.rows()).arg(neighbors.cols());
    for(int row=0; row 8    9
 *                    v
 *         10   11   12   13   14
 *         15   16   17   18   19
 *         20   21   22   23   24
 *
 *    Then, neighbor Matrix will be
 *          0    1    2    3    4    5    6    7    8    9   10   11   12 .....       20   21   22   23   24
 *       +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
 *     0 |  1    1    0    0    0    1    0                               .....        0    0    0    0    0
 *     1 |  1    1    1    0    0    0    1    0                          .....        0    0    0    0    0
 *     2 |  0    1    1    1    0    0    0    1    0                     .....        0    0    0    0    0
 *     3 |  0    0    1    1    1    0    0    0    1    0                .....        0    0    0    0    0
 *     4 |  0    0    0    1    1    0    0    0    0    1    0           .....        0    0    0    0    0
 *     5 |  1    0    0    0    0    1    1    0    0    0    1    0      .....        0    0    0    0    0
 *     6 |  0    1    0    0    0    1    1    1    0    0    0    1    0 .....        0    0    0    0    0
 *     7 |       0    1    0    0    0    1
 *     8 |            0    1    0    0    0
 *     9 |                 0    1    0    0
 *    10 |                      0    1    0
 *     . |                           0    1
 *     . |
 *     . |
 *     . |
 *     . |
 *    22 |  0    0    0    0    0    0    0    0    0    0    0    0    0 ...          0    1    1    1    0
 *    23 |  0    0    0    0    0    0    0    0    0    0    0    0    0 ...          0    0    1    1    1
 *    24 |  0    0    0    0    0    0    0    0    0    0    0    0    0 ...          0    0    0    1    1
 */
bool  AANN_sofm::initNeighborMatrix( int N, int type = 0 )
{
    Q_UNUSED( type );

    if( N <= 0 || N > 10000 )
    {
        return false;
    }
    MatrixXf neighbor = MatrixXf::Identity( N*N, N*N ) - MatrixXf::Ones( N*N, N*N )/((N+4)*(N+4));
    for( int row = 0; row < N*N; row++)
    {
        int x = row%N;
        int y = row/N;
        if( (y+N-1)%N < y )
        {
            neighbor( ((y+N-1)%N)*N+x,         row ) = 1.f/((N+1)*(N+1));
        }
        if( (y+1)%N >y )
        {
            neighbor( ((y+1)%N)*N+x,           row ) = 1.f/((N+1)*(N+1));
        }
        if( (x+N-1)%N < x )
        {
            neighbor(           row, y*N+(x+N-1)%N ) = 1.f/((N+1)*(N+1));
        }
        if( (x+1)%N > x ) {
            neighbor(           row,   y*N+(x+1)%N ) = 1.f/((N+1)*(N+1));
        }
    }
    return setNeighborMatrix( neighbor );
}

bool  AANN_sofm::initNeighborMatrix_MexicoStrawHat( int N )
{
    if( N <= 0 || N > 10000 )
    {
        return false;
    }
    MatrixXf neighbor = MatrixXf::Identity( N*N, N*N );
    for( int row = 0; row < N*N; row++)
        for( int col = row+1; col < N*N; col++)
    {
        int dx = row%N - col%N;
        int dy = row/N - col/N;
        float d1 = 1+dx*dx+dy*dy;
        float d2 = (dx*dx+dy*dy)*d1*d1;
        neighbor( row, col ) = neighbor( col, row ) = (1.0f/d2 - 0.07f)/d1;
    }
    return setNeighborMatrix( neighbor );
}

/**
 * @brief AANN_sofm::initNeighborMatrix_Sombrero
 *  refer to https://baike.baidu.com/item/Sombrero%20function/16550687
 * @return
 */
bool  AANN_sofm::initNeighborMatrix_Sombrero( int N )
{
    if( N <= 0 || N > 10000 )
    {
        return false;
    }
    MatrixXf neighbor = 0.98*MatrixXf::Identity( N*N, N*N );
    for( int row = 0; row < N*N; row++)
    for( int col = row+1; col < N*N; col++)
    {
        int dx = row%N - col%N;
        int dy = row/N - col/N;
        float d2 = dx*dx+dy*dy;
        float d1  = sqrt( d2 );
        float d4 = exp(2*d2);
//        float cc =  cos( (3.14f/2-0.1)*d1 );
//        float dd = cc/d4;
       neighbor( row, col ) = neighbor( col, row ) = cos( (3.14159/2-0.01)*d1 )/d4;
    }
    return setNeighborMatrix( neighbor );
}

const MatrixXf AANN_sofm::getWMatrix() const
{
    return m_layer.W;
}
/**
 * @brief AANN_sofm::train
 * @param P     measured vector : 可度量特征信息:如形状,味道,温度,颜色,重量等
 * @param T     感知的结果,或期望的结果
 * @return
 */
bool AANN_sofm::train( const Eigen::VectorXf &P, int S )
{
    const int R = P.rows();

    /* return false if input Dim is different */
    if( S <= 0 )
    {
        return false;
    }
    /* return false if input Dim is different */
    if( ( m_layer.W.cols() > 0 ) && ( m_layer.W.cols() != R ) )
    {
         return false;
    }
    /* return false if output Dim is different */
    if( ( m_layer.W.rows() > 0 ) && ( m_layer.W.rows() != S ) )
    {
         return false;
    }

    if( m_layer.Neighbors.size() && (m_layer.Neighbors.rows() != S || m_layer.Neighbors.cols() != S ))
    {
        return false;
    }

    if( S != m_layer.W.rows() || R != m_layer.W.cols() )
    {
        srand( time(NULL) );
        m_layer.W = MatrixXf::Random( S, R );
        m_layer.Conscience = VectorXf::Ones( S );
    }

    int maxIndex;
    m_layer.N = (m_layer.W * P).array() + R;
    /**
     * @brief round_mask
     *        随着训练次数增长,应用良心的间隔越来越大,但总是有机会使用
     *         M = 3 时
     *           m_round  :   0~7          8~15            16~31          32~63     ...   2^(n+3) ~ 2^(n+3)-1
     *                 N  :    0             1               2              4       ...       2^n
     *         round_mask : 0x...FFFFF       1               3              7       ...       (2^(n+1))-1
     *   apply conscience :    8 x          8 x             8 x            8 x      ...       8 x
     *         M = 6 时
     *           m_round  :   0~63        64~127         128~255         256~511    ...   2^(n+6) ~ 2^(n+6)-1
     *                 N  :    0             1               2              4       ...       2^n
     *         round_mask : 0x...FFFFF       1               3              7       ...       (2^(n+1))-1
     *   apply conscience :    64 x        64 x             64 x           64 x     ...       64 x
     *
     *      应用良心的频率随 M 的增加而呈指数增加,测试中 M=3 能得到比较好的性能
     */
    static int round_mask = 1;
    int N = m_round >> 3;
    if( 0 == ( N&(N-1) ) )
    {
        round_mask = (N<<1)-1; // 0,1,2,4,8,16,...
    }
    if( 0 == ( m_round & round_mask ) )
    {
        (m_layer.N.array() * m_layer.Conscience.array()).maxCoeff(&maxIndex);
    } else {
        m_layer.N.maxCoeff(&maxIndex);
    }
    m_layer.A = VectorXf::Zero(S);
    m_layer.A[maxIndex] = 1;
    m_layer.Conscience = m_layer.Conscience.array()/(1+m_layer.A.array()).array();
    m_layer.Conscience.normalize();

    VectorXf vecA;
    if( m_layer.Neighbors.rows() == S )
    {
//        vecA = ( m_layer.Neighbors * m_layer.A ).unaryExpr(std::ref(satlin));
        vecA = (m_layer.Neighbors * m_layer.A).normalized();
    } else {
        vecA = m_layer.A;
    }
    /*
     *  delta W = W(q) - W(q-1) = alpha * A(q) * P(q) - gamma * A(q).diagonal * W(q-1)
     */
    auto a = (P.transpose() - m_layer.W.row(maxIndex)).squaredNorm()/(0.01+m_layer.W.row(maxIndex).squaredNorm());
    a = fmin( 1.0, fmax( a, m_alpha ));
    m_layer.deltaW = a * vecA * P.transpose() - a * vecA.asDiagonal() * m_layer.W;
    m_layer.W = (m_layer.W + m_layer.deltaW);//.rowwise().normalized();

    m_round++;
    return true;
}

/**
 * @brief AANN_sofm::calc
 * @param P     measured vector : 可度量特征信息:如形状,味道,温度,颜色,重量等
 * @return
 */
bool AANN_sofm::calc( const Eigen::VectorXf &P, Eigen::VectorXf *A )
{
    AANN_LAYER_PARAM_F layer = m_layer;
    Eigen::VectorXf e;

    const int R = P.rows();

    /* return false if input Dim is different */
    if( ( m_layer.W.cols() <= 0 ) || ( m_layer.W.cols() != R ) )
    {
        qWarning() << QString( __FUNCTION__ " : Error! input dim  is different : expect %1, but input data is %2 " )
                    .arg(m_layer.W.cols()).arg( R );
        return false;
    }

    qDebug() << __FUNCTION__ " --- CALC -----------------------";
    qDebug() << __FUNCTION__ " P = " << P.eval() ;

    m_layer.A = (m_layer.W * P).unaryExpr(std::ref(m_layer.func));
    qDebug() << __FUNCTION__ " A = " << m_layer.A;
    if( A ) {   *A = m_layer.A;   }
    return true;
}

线程执行代码段:


void DataWorker::on_start()
{
    for(int k = 0; !m_bUserQuit && (k < 100000); k++ )  //    while( false == m_bUserQuit )
    {
        updateNnData();
        QThread::msleep(10);
    }
}

#define N 3

void DataWorker::reset()
{
    m_sofm.initNeighborMatrix_Sombrero(N);
}

void DataWorker::updateNnData( )
{
    QMutexLocker locker( &m_nnLocker );

    float r = sqrt(m_sofm.getRound()) ;
    float alpha = (r+500.0f)/( 100*r+1000.0f);

    m_sofm.setAlpha( alpha );
    m_sofm.setGamma( alpha );

#define PI  3.14159265358979
    {
        float a = - acos(sqrt( (rand() % 257) / 256.0001)); // -3.14 * (rand() % 256) / 512.0;
        float b = PI * (rand() % 12345) / 1000.0;
        VectorXf P(3);
        P << cos(a)*sin(b), sin(a), cos(a)*cos(b);
        if(m_trainSetModel && ( m_sofm.getRound() < 10000) )
        {
//            WData d(cos(a)*sin(b), cos(a)*cos(b), sin(a));
            m_trainSetModel->append( WData(P[0],P[1],P[2]));
            m_trainSetModel->dataChanged( m_trainSetModel->index(0,0), m_trainSetModel->index(-1,-1));
        }
        m_sofm.train( P, N*N );
    }
    m_dataModel->update(m_sofm.getWMatrix());

}

以下是36点在半球形中的演示结果

以下是9点在半球形中的演示结果。看得出,用距离来决定学习步长在这时造成了结果的发散

演示代码

你可能感兴趣的:(算法,神经网络,人工智能,c++)