首页 > 程序开发 > 软件开发 > C++ >

CNN卷积神经网络 C++实现

2017-03-13

CNN卷积神经网络 C++实现。这个卷积神经网络比较特殊,由1个输入层、2个卷积层、1个全连接层、1个全连接的输出层构成,无采样层、池化层。 输入是一个29*29的图像矩阵,对应着841个输入单元。输出层有9个神经元,如果应该输出3,则下标为3的单元应该输出1,其它神经元输出-1

CNN卷积神经网络 C++实现。这个卷积神经网络比较特殊,由1个输入层、2个卷积层、1个全连接层、1个全连接的输出层构成,无采样层、池化层。

输入是一个29*29的图像矩阵,对应着841个输入单元。输出层有9个神经元,如果应该输出3,则下标为3的单元应该输出1,其它神经元输出-1。

测试部分是我自己写的,数据集来自《机器学习实战》—Peter Harrington,中的第二章,“手写识别系统”,这样就可以用纯C++实现了 :)

《机器学习实战》中的第二章,采用了k-近邻算法识别手写,正确率为98.8%,采用了CNN后,正确率为100%,但训练时间比较长。

这里写图片描述

这里写图片描述

CNN.h

// simplified view: some members have been omitted,

// and some signatures have been altered

// helpful typedef's

#include "vector"

#include "assert.h"

#include "math.h"

using namespace std;

class NeuralNetwork;

class NNLayer;

class NNNeuron;

class NNConnection;

class NNWeight;

typedef std::vector< NNLayer* > VectorLayers;

typedef std::vector< NNWeight* > VectorWeights;

typedef std::vector< NNNeuron* > VectorNeurons;

typedef std::vector< NNConnection > VectorConnections;

#define SIGMOID(x) (tanh(x))

#define DSIGMOID(x) (1-(SIGMOID(x))*(SIGMOID(x)))

#define UNIFORM_PLUS_MINUS_ONE ( (double)(2.0 * rand())/RAND_MAX - 1.0 ) //均匀随机分布

// 神经网络

class NeuralNetwork

{

public:

NeuralNetwork();

virtual ~NeuralNetwork();

//正向传播,计算输出

void Calculate();

//反向传播,调整权值

void Backpropagate();

vector m_input; //输入向量

vector m_desiredOutput; //理应输出的向量

vector m_actualOutput; //实际输出的向量

VectorLayers m_Layers; //存储指向每一层的指针

double m_etaLearningRate; //学习速率

};

// 层

class NNLayer

{

public:

NNLayer( char* str, NNLayer* pPrev = NULL );

virtual ~NNLayer();

//正向传播,计算输出

void Calculate();

//反向传播,调整权值

void Backpropagate( std::vector< double >& dErr_wrt_dXn /* in */,

std::vector< double >& dErr_wrt_dXnm1 /* out */,

double etaLearningRate );

char *m_layerName; //该层的名称

NNLayer* m_pPrevLayer; //存储前一层的指针,以获得输入

VectorNeurons m_Neurons; //存储指向该层每个神经元的指针

VectorWeights m_Weights; //存储连向该层的每个连接的权值

};

// 神经元

class NNNeuron

{

public:

NNNeuron( char* str );

virtual ~NNNeuron();

void AddConnection( int iNeuron, int iWeight ); //添加连接,(神经元下标, 权值下标)

void AddConnection( NNConnection const & conn );

char *m_neuronName; //当前神经元的名称

double output; //当前神经元的输出

VectorConnections m_Connections; //存储连向该神经元的所有连接,以获得该神经元的输入

};

// 连接

class NNConnection

{

public:

NNConnection(int neuron = ULONG_MAX, int weight = ULONG_MAX);

//virtual ~NNConnection();

int NeuronIndex; //神经元下标

int WeightIndex; //权值下标

};

// 权值

class NNWeight

{

public:

NNWeight( double val = 0.0 );

//virtual ~NNWeight();

double value; //权值

};

CNN.cpp

// simplified code

#include "CNN.h"

#include "iostream"

using namespace std;

NeuralNetwork::NeuralNetwork()

{

m_etaLearningRate = 0.001; // 学习速率

}

NeuralNetwork::~NeuralNetwork()

{

VectorLayers::iterator it;

for( it=m_Layers.begin(); itm_Neurons.begin();

// 每个输入对应一个神经元

int count = 0;

while( nit != (*lit)->m_Neurons.end() )

{

(*nit)->output = m_input[ count ];

nit++;

count++;

}

}

// 通过Calculate()迭代剩余层

for( lit++; litCalculate();

}

// 输出向量中存储最终结果

lit = m_Layers.end();

lit--; //最后一层,输出层

nit = (*lit)->m_Neurons.begin();

while( nit != (*lit)->m_Neurons.end() )

{

m_actualOutput.push_back((*nit)->output);

nit++;

}

}

//卷积神经网络中的误差反向传播

void NeuralNetwork::Backpropagate()

{

/*

误差反向传播,从最后一层一直到迭代到第一层

Err:整个神经网络的输出误差

Xn:第n层上的输出向量

Xnm1:前一层的输出向量

Wn:第n层的权值

Yn:第n层的激活值

F:激活函数 tanh

F&#39;:F的倒数 F&#39;(Yn) = 1 - Xn^2

*/

VectorLayers::iterator lit = m_Layers.end() - 1;

std::vector< double > dErr_wrt_dXlast( (*lit)->m_Neurons.size() ); //标准误差关于“输出值”的偏导

std::vector< std::vector< double > > differentials;

int iSize = m_Layers.size();

differentials.resize( iSize );

int ii;

// differentials 存储标准误差 0.5*sumof( (actual-target)^2 ) 的偏导关于“输出值”的偏导

for ( ii=0; ii<(*lit)->m_Neurons.size(); ++ii )

{

dErr_wrt_dXlast[ ii ] =

m_actualOutput[ ii ] - m_desiredOutput[ ii ]; //实际输出 - 目标输出

}

// 存储dErr_wrt_dXlast

// 为剩余需要存储在differentials中的vector预留空间,并初始化为0

differentials[ iSize-1 ] = dErr_wrt_dXlast; // 上一次

for ( ii=0; iim_Neurons.size(), 0.0 );

}

/*

迭代计算除了第一层之外的剩余层,每一层都要反向传播误差,

并调整自己的权值

用 differentials[ ii ] 计算 differentials[ ii - 1 ]

*/

ii = iSize - 1;

for ( lit; lit>m_Layers.begin(); lit--)

{

(*lit)->Backpropagate( differentials[ ii ],

differentials[ ii - 1 ], m_etaLearningRate );

--ii;

}

differentials.clear();

}

////////////////////////////////////////////////////////////////////////////////////////////

NNLayer::NNLayer(char* str, NNLayer* pPrev)

{

m_layerName = str;

m_pPrevLayer = pPrev;

}

NNLayer::~NNLayer()

{

VectorWeights::iterator wit;

VectorNeurons::iterator nit;

for( nit=m_Neurons.begin(); nitvalue;

for ( cit++ ; citm_Neurons.size() );

dSum += ( m_Weights[ (*cit).WeightIndex ]->value ) *

( m_pPrevLayer->m_Neurons[ (*cit).NeuronIndex ]->output );

}

n.output = SIGMOID( dSum ); //当前神经元的输出

}

}

//每一层的误差反向传播

void NNLayer::Backpropagate( std::vector< double >& dErr_wrt_dXn /* in */,

std::vector< double >& dErr_wrt_dXnm1 /* out */,

double etaLearningRate )

{

/*

Err:整个神经网络的输出误差

Xn:第n层上的输出向量

Xnm1:前一层的输出向量

Wn:第n层的权值

Yn:第n层的激活值

F:激活函数 tanh

F&#39;:F的倒数 F&#39;(Yn) = 1 - Xn^2

*/

int ii, jj, kk;

int nIndex;

double output;

vector< double > dErr_wrt_dYn( m_Neurons.size() );

double* dErr_wrt_dWn = new double[ m_Weights.size() ];

// 计算 : dErr_wrt_dYn = F&#39;(Yn) * dErr_wrt_Xn,标准误差关于某个单元输入加权和的偏导

for (ii=0; iioutput;

dErr_wrt_dYn[ ii ] = DSIGMOID( output ) * dErr_wrt_dXn[ ii ];

}

// 计算 : dErr_wrt_Wn = Xnm1 * dErr_wrt_Yn,标准误差关于权重的偏导

// 对于这层中的每个神经元,通过前一层中与之相连的连接更新相关权值

VectorNeurons::iterator nit;

VectorConnections::iterator cit;

ii = 0;

for ( nit=m_Neurons.begin(); nitm_Neurons[ kk ]->output;

}

dErr_wrt_dWn[ (*cit).WeightIndex ] += dErr_wrt_dYn[ ii ] * output;

}

ii++;

}

// 计算 : dErr_wrt_Xnm1 = Wn * dErr_wrt_dYn,

// 计算这一层的每个神经元的dErr_wrt_dXnm1,

// 作为下一层计算 dErr_wrt_dXn 的输入

ii = 0;

for ( nit=m_Neurons.begin(); nitvalue;

}

}

ii++;

}

// 计算 : 更新权重

// 在这层中,使用了dErr_wrt_dW 和学习速率

double oldValue, newValue;

for ( jj=0; jjvalue;

newValue = oldValue - etaLearningRate * dErr_wrt_dWn[ jj ];

m_Weights[ jj ]->value = newValue;

}

}

/////////////////////////////////////////////////////////////////////////////////

NNNeuron::NNNeuron(char* str)

{

m_neuronName = str;

output = 0.0;

m_Connections.clear();

}

NNNeuron::~NNNeuron()

{

m_Connections.clear();

}

void NNNeuron::AddConnection( int iNeuron, int iWeight )

{

m_Connections.push_back( NNConnection( iNeuron, iWeight ) );

}

void NNNeuron::AddConnection( NNConnection const & conn )

{

m_Connections.push_back( conn );

}

//////////////////////////////////////////////////////////////////////////////

NNConnection::NNConnection(int iNeuron, int iWeight)

{

NeuronIndex = iNeuron;

WeightIndex = iWeight;

}

/////////////////////////////////////////////////////////////////////////////

NNWeight::NNWeight(double val)

{

val = 0.0;

}

test.cpp

#include "cnn.h"

#include "iostream"

#include "fstream"

#include "io.h"

#include "string"

#include "time.h"

#include "stdio.h"

using namespace std;

void buildCNN(NeuralNetwork &NN)

{

NNLayer* pLayer;

int ii, jj, kk;

double initWeight;

char label[100];

int icNeurons = 0;

// 第0层,输入层

// 创建神经元,和输入的数量相等

// 装有 29x29=841 像素的 vector,没有权值

pLayer = new NNLayer( "Layer00" );

NN.m_Layers.push_back( pLayer );

for ( ii=0; ii<841; ++ii )

{

sprintf(label, "Layer00_Neuron%04d_Num%06d", ii, icNeurons);

pLayer->m_Neurons.push_back( new NNNeuron(label) );

icNeurons++;

}

// 第一层:

// 是一个卷积层,有6个特征图,每个特征图大小为13x13,

// 特征图中的每个单元是由5x5的卷积核从输入层卷积而成。

// 因此,共有13x13x6 = 1014个神经元,(5x5+1)x6 = 156个权值

pLayer = new NNLayer( "Layer01", pLayer );

NN.m_Layers.push_back( pLayer );

for ( ii=0; ii<1014; ++ii )

{

sprintf(label, "Layer00_Neuron%04d_Num%06d", ii, icNeurons);

pLayer->m_Neurons.push_back( new NNNeuron(label) );

icNeurons++;

}

for ( ii=0; ii<156; ++ii )

{

initWeight = 0.05 * UNIFORM_PLUS_MINUS_ONE; //均匀随机分布

pLayer->m_Weights.push_back( new NNWeight( initWeight ) );

}

// 和前一层相连:这是难点

// 前一层是位图,大小为29x29

// 这层中的每个神经元都和特征图中的5x5卷积核相关,

// 每次移动卷积核2个像素

int kernelTemplate[25] = {

0, 1, 2, 3, 4,

29, 30, 31, 32, 33,

58, 59, 60, 61, 62,

87, 88, 89, 90, 91,

116,117,118,119,120 };

int iNumWeight;

int fm; // "fm" 代表 "feature map"

for ( fm=0; fm<6; ++fm)

{

for ( ii=0; ii<13; ++ii )

{

for ( jj=0; jj<13; ++jj )

{

// 26 是每个特征图的权值数量

iNumWeight = fm * 26;

NNNeuron& n = *( pLayer->m_Neurons[ jj + ii*13 + fm*169 ] );

n.AddConnection( ULONG_MAX, iNumWeight++ ); // 偏移量

for ( kk=0; kk<25; ++kk )

{

// 注意:最大下标为840

// 因为前一层中的神经元数量为841

n.AddConnection( 2*jj + 58*ii + kernelTemplate[kk], iNumWeight++ );

}

}

}

}

// 第二层:

// 这层是卷积层,有50个特征图。每个特征图大小为5x5,

// 特征图中的每个单元是由5x5的卷积核卷积前一层中的所有6个特征图而得,

// 因此,有5x5x50 = 1250个神经元,(5x5+1)x6x50 = 7800个权值

pLayer = new NNLayer( "Layer02", pLayer );

NN.m_Layers.push_back( pLayer );

for ( ii=0; ii<1250; ++ii )

{

sprintf(label, "Layer00_Neuron%04d_Num%06d", ii, icNeurons);

pLayer->m_Neurons.push_back( new NNNeuron( label ) );

icNeurons++;

}

for ( ii=0; ii<7800; ++ii )

{

initWeight = 0.05 * UNIFORM_PLUS_MINUS_ONE;

pLayer->m_Weights.push_back( new NNWeight( initWeight ) );

}

// 和前一层相连:这是难点

// 前一层的每个特征图都是大小为13x13的位图,共6个特征图.

// 这层中的每个5x5特征图中的每个神经元都和6个5x5的卷积核相关。

// 这层中的每个特征图都有6个不同的卷积核

// 每次将特征图移动2个像素

int kernelTemplate2[25] = {

0, 1, 2, 3, 4,

13, 14, 15, 16, 17,

26, 27, 28, 29, 30,

39, 40, 41, 42, 43,

52, 53, 54, 55, 56 };

for ( fm=0; fm<50; ++fm)

{

for ( ii=0; ii<5; ++ii )

{

for ( jj=0; jj<5; ++jj )

{

// 26 是每个特征图的权值数

iNumWeight = fm * 26;

NNNeuron& n = *( pLayer->m_Neurons[ jj + ii*5 + fm*25 ] );

n.AddConnection( ULONG_MAX, iNumWeight++ ); // bias weight

for ( kk=0; kk<25; ++kk )

{

// 注意:最大下标为1013

// 因为前一层中有1014个神经元

n.AddConnection( 2*jj + 26*ii +

kernelTemplate2[kk], iNumWeight++ );

n.AddConnection( 169 + 2*jj + 26*ii +

kernelTemplate2[kk], iNumWeight++ );

n.AddConnection( 338 + 2*jj + 26*ii +

kernelTemplate2[kk], iNumWeight++ );

n.AddConnection( 507 + 2*jj + 26*ii +

kernelTemplate2[kk], iNumWeight++ );

n.AddConnection( 676 + 2*jj + 26*ii +

kernelTemplate2[kk], iNumWeight++ );

n.AddConnection( 845 + 2*jj + 26*ii +

kernelTemplate2[kk], iNumWeight++ );

}

}

}

}

// 第3层:

// 这是个全连接层,有100个单元

// 由于是全连接层,这层中的每个单元都和前一层中所有的1250个单元相连

// 因此,有100神经元,100*(1250+1) = 125100个权值

pLayer = new NNLayer( "Layer03", pLayer );

NN.m_Layers.push_back( pLayer );

for ( ii=0; ii<100; ++ii )

{

sprintf(label, "Layer00_Neuron%04d_Num%06d", ii, icNeurons);

pLayer->m_Neurons.push_back( new NNNeuron( label ) );

icNeurons++;

}

for ( ii=0; ii<125100; ++ii )

{

initWeight = 0.05 * UNIFORM_PLUS_MINUS_ONE;

pLayer->m_Weights.push_back( new NNWeight( initWeight ) );

}

// 和前一层相连:全连接

iNumWeight = 0; // 这层中,权值不共享

for ( fm=0; fm<100; ++fm )

{

NNNeuron& n = *( pLayer->m_Neurons[ fm ] );

n.AddConnection( ULONG_MAX, iNumWeight++ ); // 偏移量

for ( ii=0; ii<1250; ++ii )

{

n.AddConnection( ii, iNumWeight++ );

}

}

// 第4层,最后一层:

// 这是个全连接层,有10个单元。

// 由于是全连接层,每个神经元都和前一层中的所有100个神经元相连

// 因此,有10个神经元,10*(100+1)=1010个权值

pLayer = new NNLayer( "Layer04", pLayer );

NN.m_Layers.push_back( pLayer );

for ( ii=0; ii<10; ++ii )

{

sprintf(label, "Layer00_Neuron%04d_Num%06d", ii, icNeurons);

pLayer->m_Neurons.push_back( new NNNeuron( label ) );

icNeurons++;

}

for ( ii=0; ii<1010; ++ii )

{

initWeight = 0.05 * UNIFORM_PLUS_MINUS_ONE;

pLayer->m_Weights.push_back( new NNWeight( initWeight ) );

}

// 和前一层相连:全连接

iNumWeight = 0; // 这层中的权值不共享

for ( fm=0; fm<10; ++fm )

{

NNNeuron& n = *( pLayer->m_Neurons[ fm ] );

n.AddConnection( ULONG_MAX, iNumWeight++ ); // 偏移量

for ( ii=0; ii<100; ++ii )

{

n.AddConnection( ii, iNumWeight++ );

}

}

}

void img2vector(string filename, vector &input)

{

ifstream fin;

char file[100];

strcpy(file, (char*)filename.data());

fin.open(file);

char data[100];

int d;

for(int i=0; i<29; i++)

{

fin.getline(data, 100);

for(int j=0; j<29; j++)

{

d = data[j] - &#39;0&#39;;

input.push_back((double)d);

}

}

fin.close();

}

void getFiles(string path, vector& files, vector& files1)

{

//文件句柄

long hFile = 0;

//文件信息

struct _finddata_t fileinfo;

string p;

if ((hFile = _findfirst(p.assign(path).append("/*.txt").c_str(), &fileinfo)) != -1)

{

do

{

//加入列表

files.push_back(p.assign(path).append("/").append(fileinfo.name));

files1.push_back(fileinfo.name);

} while( _findnext( hFile, &fileinfo ) == 0 );

_findclose(hFile);

}

}

int main()

{

//获取文件

int i, j, k;

vector file, file1;

getFiles("./trainingDigits", file, file1);

int n = file.size(); //训练文件夹下的文件数

vector label;

char str[10];

for(i=0; i max)

{

max = CNN.m_actualOutput[i];

maxi = i;

}

}

if(maxi == label[j]) //如果结果正确

right++;

}

cout << "正确个数:" << right << endl;

cout << "总个数:" << n << endl;

cout << "正确率:" << (double)right/(double)n << endl; //正确率

return 0;

}

这里写图片描述
相关文章
最新文章
热点推荐