附属代码:
Mat convolution(Mat&delt,Mat&w,float bias=0,int flp=0) { Mat dst; Mat w_r180,delt_r180; switch (flp) { case 0://如果flp==0,只对w翻转180度 rotate(w,w_r180,ROTATE_180); filter2D(delt,dst,delt.type(),w_r180,Point(-1,-1),bias,BORDER_CONSTANT); break; case 1://如果flp==1,只对delt翻转180度 rotate(delt,delt_r180,ROTATE_180); filter2D(delt_r180,dst,delt.type(),w,Point(-1,-1),bias,BORDER_CONSTANT); break; case 2://如果flp==2,对w、delt翻转180度 rotate(w,w_r180,ROTATE_180); rotate(delt,delt_r180,ROTATE_180); filter2D(delt_r180,dst,delt.type(),w_r180,Point(-1,-1),bias,BORDER_CONSTANT); break; default: cout<<"you input flp is wrong!"<<endl; } return dst; } //w和delt的尺度要求一致,锚点默认为w的中心 Mat myConv(Mat&w,Mat& delt) { int dr=abs(delt.rows-w.rows),dc=abs(delt.cols-w.cols); int top=dr/2,bottom=dr-top; int left=dc/2,right=dc-left; Mat wp,deltp; if(w.rows<delt.rows) { deltp=delt; copyMakeBorder(w,wp,top,bottom,left,right,BORDER_CONSTANT,Scalar::all(0)); } if(wp.size()!=deltp.size()) { cout<<"the size of two input is different!"<<endl; exit(1); } Mat dst=Mat(deltp.size(),deltp.type(),Scalar::all(0)); int srows=dst.rows,scols=dst.cols; Mat delt_180,delt_ss; rotate(deltp,delt_180,ROTATE_180); Mat M=(Mat_<float>(2,3)<<1,0,0, 0,1,0); for(int i=-srows/2;i<srows/2+1;i++) { for(int j=-scols/2;j<scols/2+1;j++) { M.ptr<float>(0)[2]=j; M.ptr<float>(1)[2]=i; warpAffine(delt_180,delt_ss,M,deltp.size()); dst.ptr<float>(i+srows/2)[j+scols/2]=sum(wp.mul(delt_ss))[0]; } } // cout<<dst<<endl<<endl; return dst; } ///src1和src2的尺寸要一致,卷积结果尺寸与lwk一致 ///lwk的尺寸要比src1、src2的尺寸小 /// 卷积运算时,src2倒转180度,src2_180在src1上滑动卷积 Mat myConv(Mat&src1,Mat& src2,Mat&lwk) { if(src1.size()!=src2.size()) { cout<<"the size of two input is different!"<<endl; exit(1); } Mat dst=Mat(lwk.size(),lwk.type(),Scalar::all(0)); int srows=dst.rows,scols=dst.cols; Mat src2_180,src2_ss; rotate(src2,src2_180,ROTATE_180); Mat ss=(Mat_<float>(2,3)<<1,0,0, 0,1,0); for(int i=-srows/2;i<srows/2+1;i++) { for(int j=-scols/2;j<scols/2+1;j++) { ss.ptr<float>(0)[2]=j; ss.ptr<float>(1)[2]=i; warpAffine(src2_180,src2_ss,ss,src2.size()); dst.ptr<float>(i+srows/2)[j+scols/2]=sum(src1.mul(src2_ss))[0]; } } return dst; } Mat sigmoid(Mat& zmat) { Mat temp=Mat(zmat.size(),CV_32F,Scalar::all(0)); for(int i=0;i<zmat.rows;i++) { float* ps=zmat.ptr<float>(i); for(int j=0;j<zmat.cols;j++) { temp.ptr<float>(i)[j]=1.0/(1.0+exp(-ps[j])); } } return temp; } Mat sigmoid_d(Mat&z) { Mat temp=Mat(z.size(),z.type(),Scalar::all(0)); for(int i=0;i<z.rows;i++) { for(int j=0;j<z.cols;j++) { float x=z.ptr<float>(i)[j]; float sig=1/(1+exp(-x)); temp.ptr<float>(i)[j]=sig*(1-sig); } } return temp; } Mat ReLU(Mat&z) { Mat temp=Mat(z.size(),z.type(),Scalar::all(0)); for(int i=0;i<z.rows;i++) { for(int j=0;j<z.cols;j++) { float x=z.ptr<float>(i)[j]; if(x<0) temp.ptr<float>(i)[j]=0; } } return temp; } Mat pooling(const Mat&zs) { Mat result=Mat(zs.size()/2,zs.type(),Scalar::all(0)); for(int i=0;i<zs.rows;i+=2) { for(int j=0;j<zs.cols;j+=2) { float* pz0=CV_MAT_ELEM2(zs,float,i,j); float* pz1=CV_MAT_ELEM2(zs,float,(i+1),j); float p4_average=(pz0[0]+pz0[1]+pz1[0]+pz1[1])/4.;//2*2个像素的均值 float* pr=CV_MAT_ELEM2(result,float,i/2,j/2); pr[0]=p4_average; } } return result; } Mat poolingUp(const Mat&zs) { Mat Dt(zs.rows*2,zs.cols*2,CV_32F,Scalar(0)); int s=2;//池化尺寸 for(int i=0;i<zs.rows;i++) for(int j=0;j<zs.cols;j++) { float x=zs.ptr<float>(i)[j]; Dt(Rect(j*s,i*s,s,s))=x; } return Dt; } void Conv_bias_actvivate(const Mat&src,Mat& dst,const Mat &kernel,float bias) { filter2D(src,dst,CV_32F,kernel);//卷积 dst +=bias;//偏置 dst=sigmoid(dst);//激活 int y=kernel.rows/2,x=kernel.cols/2; int width=dst.cols-x*2, height=dst.rows-y*2; // cout<<"x="<<x<<" , y="<<y<<" , width="<<width<<" , height="<<height<<endl; Mat temp=dst(Rect(x,y,width,height)).clone(); dst=temp.clone(); } /*********************卷积神经网络,图像集合*************************/ class ImgUnit { public: ImgUnit(unsigned char *p) { for(int i=0;i<3;i++) { unsigned char *pp=p+1+i*1024; m[i]=Mat(32,32,CV_8U,pp); } merge(m,3,m_bgr); label=(int)p[0]; // imshow("img unit",m_bgr); } ImgUnit(){} void makeImg(unsigned char *p) { for(int i=0;i<3;i++) { unsigned char *pp=p+1+i*1024; m[(2-i)]=Mat(32,32,CV_8U,pp);//图库中的三原色顺序式RGB,Opencv中式BGR,所以逆序读入 } merge(m,3,m_bgr); label=(int)p[0]; } Mat m[3]; Mat m_bgr; int label; }; //1.加载训练图片 void loadImgSet(ImgUnit *imgset,string datapath) { unsigned char* p=new unsigned char[30730000]; ifstream infile(datapath,ios::binary); if(!infile) { cerr<<"open err!"<<endl; exit(1); } infile.read((char*)p,30730000); for(int i=0;i<10000;i++) { uchar* pi=p+i*3073; imgset[i].makeImg(pi); } infile.close(); } /*********************Parsing MNIST data******************************/ // 日常用的PC CPU是Intel处理器,用小端格式 // 把大端数据转换为我们常用的小端数据 void swap_endian(uint32_t& val) { val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); val=(val << 16) | (val >> 16); } class MNIST { public: Mat* m; int* label; static int magic_num_l;//Label的魔数 static int numOfImages_l;//Label与图片一一对应,该参数是图片数 static int magic_num_i; //图库的魔数 static int numOfImages_i;//图库的图片数,要与Label一一对应 }; int MNIST::magic_num_l=0; int MNIST::numOfImages_l=0; int MNIST:: magic_num_i=0; //图库的魔数 int MNIST::numOfImages_i=0;//图库的图片数,要与Label一一对应 void reverseInt(int& i) { unsigned char ch1, ch2, ch3, ch4; ch1 = i & 255; ch2 = (i >> 8) & 255; ch3 = (i >> 16) & 255; ch4 = (i >> 24) & 255; i=((int)ch1 << 24) + ((int)ch2 << 16) + ((int)ch3 << 8) + ch4; } /******************读取图库MNIST******************/ void readMnist(MNIST&mnist,string labelpath,string imgpath) { // string filename="D:/Qt/MyImage/MNIST/train-labels.idx1-ubyte"; ifstream file(labelpath, ios::binary);//打开Label文件的对象 ifstream imgfile(imgpath, ios::binary);//打开图库的对象 if(!file.is_open()) { cerr<<"label open err!"<<endl; exit(1); } if(!file.is_open()) { cerr<<"images set open err!"<<endl; exit(1); } int magic_num_l=0,numOfImages_l=0;//通过file读取Label的魔数和图片数,之后再赋值给MNIST成员变量 int magic_num_i=0,numOfImages_i=0;//通过imgfile读取图库的魔数和图片数,之后再赋值给MNIST成员变量 //读取图片label集的magic number和图片数量number of image. file.read((char*)&magic_num_l,sizeof(magic_num_l)); file.read((char*)&numOfImages_l,sizeof(numOfImages_l)); //读取image集合的magic number和图片数量number of image. imgfile.read((char*)&magic_num_i,sizeof(magic_num_i)); imgfile.read((char*)&numOfImages_i,sizeof(numOfImages_i)); //大小端转换 reverseInt(magic_num_l);//label大小端转换 reverseInt(numOfImages_l);//label大小端转换 reverseInt(magic_num_i);//img magic number大小端转换 reverseInt(numOfImages_i);//img number大小端转换 cout<<"magic_num_l="<<magic_num_l<<" , numOfImages_l="<<numOfImages_l<<endl; cout<<"magic_num_i="<<magic_num_i<<" , numOfImages_i="<<numOfImages_i<<endl; // mnist=new MNIST[numOfImages_l]; mnist.magic_num_l=magic_num_l; mnist.numOfImages_l=numOfImages_l; unsigned char * label=new unsigned char[numOfImages_l]; mnist.label=new int[numOfImages_l];//为标号label分配内存 mnist.m=new Mat[numOfImages_l];//为图像集分配内存 file.read((char*)label,sizeof(char)*numOfImages_l);;//读取标号 //读取一副图片的行、列数 uint img_rows,img_cols; imgfile.read((char*)&img_rows,sizeof(img_rows)); imgfile.read((char*)&img_cols,sizeof(img_cols)); swap_endian(img_rows); swap_endian(img_cols); cout<<"img_rows="<<img_rows<<" , img_cols="<<img_cols<<endl; //读取图像 uchar *pixs=new uchar[img_rows*img_cols*numOfImages_i]; imgfile.read((char*)pixs,img_rows*img_cols*numOfImages_i); for(int i=0;i<numOfImages_l;i++) { mnist.label[i]=label[i]; uchar *p=pixs +i*img_cols*img_rows; mnist.m[i]=Mat(img_rows,img_cols,CV_8U,p); } file.close(); imgfile.close(); } /*********************Parsing MNIST data结束******************************/
基本代码:
class DnnLayer { public: DnnLayer() { } void initWeightAndBias(int l1_a_num,//前层输出a的个数 int l2_a_num)//当前层输出a的个数 { cv::RNG rgn;//随机数 this->numOfW=l1_a_num*l2_a_num; if(l1_a_num!=l2_a_num) { w=new Mat*[l1_a_num]; for(int i=0;i<l1_a_num;i++) { w[i]=new Mat[l2_a_num]; for(int j=0;j<l2_a_num;j++) { w[i][j]=Mat(5,5,CV_32F,Scalar(0)); rgn.fill(w[i][j],RNG::UNIFORM,1,-1); } } b=new float*[l1_a_num]; for(int i=0;i<l1_a_num;i++) { b[i]=new float[l2_a_num]; for(int j=0;j<l2_a_num;j++) b[i][j]=0.5; } } inaNum=l1_a_num; outaNum=l2_a_num; } Mat **w; float **b; int numOfW; int inaNum,outaNum;//输入、输出数量 Mat *z;//卷积+偏置的结果 Mat *a;//激活+池化后的输出矩阵 // Mat *p;//池化的结果 Mat p_vector;//将池化结果转成矢量,仅仅在卷积层的最后一层使用 }; class NeuronNet//全连接神经网络 { public: Mat input; Mat b;//偏置项 Mat nw; Mat z; Mat out_a; Mat expect_a; }; class DNN { public: DNN() { //初始化全连接网络的权重 nnet.nw=Mat(10,16*6*2,CV_32F,Scalar(0)); nnet.b=Mat(10,1,CV_32F,Scalar(0)); cv::RNG rng; rng.fill(nnet.nw,RNG::UNIFORM,1,-1); rng.fill(nnet.b,RNG::UNIFORM,1,-1); layers[0].initWeightAndBias(1,1); layers[1].initWeightAndBias(1,6); layers[2].initWeightAndBias(6,12); //z、a、p的初始化 layers[0].a=new Mat[1]; layers[1].a=new Mat[6]; layers[1].z=new Mat[6]; layers[2].a=new Mat[12]; layers[2].z=new Mat[12]; } MNIST mnist;//图片库 Mat *img;//该指针指向mnist的图片库 DnnLayer layers[3];//两层卷积网络 NeuronNet nnet;//一层全连接网络 // Mat cnnOutvecs;//卷积层所有不同权重卷积结果 public: void readImgLib(string labelpath,string imgpath) { readMnist(mnist,labelpath,imgpath); img=mnist.m;//将m指向图像库 int i=3; layers[0].a[0]=img[i].clone(); imshow("what we learned picture!",img[i]); layers[0].a[0].convertTo(layers[0].a[0],CV_32F); // normalize(layers[0].a[0],layers[0].a[0],1,0,NORM_MINMAX); //对全连接网络初始化"期望输出" int label=mnist.label[i]; nnet.expect_a=Mat(10,1,CV_32F,Scalar(0)); nnet.expect_a.ptr<float>(label)[0]=1; } void clipAround(Mat& layer_z,Mat layer_w)//根据卷积核,裁剪卷积结果 { int y=layer_w.rows/2; int x=layer_w.cols/2; int width=layer_z.cols-x*2; int height=layer_z.rows-y*2; Mat temp=layer_z(Rect(x,y,width,height)); layer_z=temp.clone(); } //l层第k个权重w的偏微分书上704页,公式(12.105) void updateWeight(Mat&D_l,Mat&previosOuta,Mat lwk,float&alpha) { Mat temp,wd; wd=myConv(previosOuta,D_l,lwk); lwk -=alpha*wd;//更新第l层权重,书上是减去偏微分项wd,不知道为什么,用加法反倒合适。 } void calcForeward() { int L=3;//卷积层的层数 Mat dst; //卷积+偏置+激活,池化 int l=1; for(int i=0;i<layers[l].inaNum;i++) { for(int j=0;j<layers[l].outaNum;j++) { layers[l].z[j]= convolution(layers[l-1].a[0],//第0层,仅仅用来接收输入图像,输出a即原图像 layers[l].w[i][j],//第l层的卷积核,或权重。 layers[l].b[i][j]);//卷积后叠加偏置项 //裁剪边缘,clip around clipAround(layers[l].z[j],layers[l].w[i][j]); layers[l].a[j]= sigmoid(layers[l].z[j]);//激活输出a //池化 layers[l].a[j]=pooling(layers[l].a[j]);;//池化输出a } } vector<vector<Mat> > Z; vector<Mat> Zj; for(int l=2;l<L;l++)//2层网络循环计算 { for(int i=0;i<layers[l].inaNum;i++) { for(int j=0;j<layers[l].outaNum;j++) { Mat lzj;//用于保存临时输出变量 Mat w_r180;//卷积核 rotate(layers[l].w[i][j],w_r180,ROTATE_180); filter2D(layers[l-1].a[i],//第1层的输出a lzj,//卷积结果 CV_32F, w_r180,//第l层的卷积核,或权重。 Point(-1,-1), layers[l].b[i][j], BORDER_CONSTANT); //裁剪边缘,clip around clipAround(lzj,layers[l].w[i][j]); Zj.push_back(lzj);//Zj矢量保存第i个输入的12个输出 } Z.push_back(Zj);//将6个Zj保存到Z } } l=2; for(int j=0;j<layers[l].outaNum;j++) { for(int i=1;i<layers[l].inaNum;i++) { Z[0][j] +=Z[i][j]; } layers[l].z[j]=Z[0][j]; //激活输出a layers[l].a[j]=sigmoid(layers[l].z[j]); //池化 layers[l].a[j]=pooling(layers[l].a[j]); } Mat cnnOutvecs; for(int i=0;i<layers[L-1].outaNum;i++) { Mat outavec=layers[L-1].a[i].reshape(1,16); cnnOutvecs.push_back(outavec); } layers[L-1].p_vector= cnnOutvecs; //初始化全连接网络的输入端,以及 nnet.input=cnnOutvecs; nnet.z=nnet.nw*nnet.input+nnet.b; nnet.out_a=sigmoid(nnet.z); cnnOutvecs.release(); } void calcBackward(float alpha=0.01) { int L=3; //D[4]表示四个层上的误差,D[0]废弃。 int d2num=layers[2].outaNum;//第2层特征映射的个数 Mat **D=new Mat*[L+1]; D[L]=new Mat[1]; D[2]=new Mat[d2num]; D[1]=new Mat[d2num]; /*************计算各层误差********************/ /////计算最后一层D[3]误差,该层是全连接层 D[L][0]=(nnet.out_a-nnet.expect_a).mul(sigmoid_d(nnet.z));//691页,公式(12.78),D[3] /***计算倒数第二层误差D[2],该层是卷积层,也是卷积层与全连接层的衔接层。*/ //反向传播到衔接层的误差,依然采用全连接计算 Mat nn2cnn_delt=(nnet.nw.t()*D[L][0]).mul(sigmoid_d(layers[L-1].p_vector));//692页公式(12.79),全连接网络误差计算 //卷积层的多特征矢量组合的总矢量nn2cnn_delt,分解成12个单独矢量,并且每个矢量被reshape成4×4的矩阵 int height=layers[L-1].a[0].rows*layers[L-1].a[0].cols;//矢量高度 for(int i=0;i<d2num;i++)//d2num=12 { D[L-1][i]=nn2cnn_delt(Rect(0,i*height,1,height)); D[L-1][i]=D[L-1][i].reshape(1,layers[L-1].a[0].rows); D[L-1][i]=poolingUp(D[L-1][i]);//反向池化,即上采样,此时尺寸为8×8 copyMakeBorder(D[L-1][i],D[L-1][i],2,2,2,2,BORDER_REPLICATE);//在D1四周补边,此时尺寸为12×12 // cout<<D[L-1][i]<<endl<<endl; } /*********计算倒数第三层误差,D[1]*******/ int l2num_w=layers[L-1].outaNum; int l1num_w=layers[L-1].inaNum; Mat delt_l1[6][12]; for(int i=0;i<l1num_w;i++) { for(int j=0;j<l2num_w;j++) { filter2D(D[L-1][j],delt_l1[i][j],CV_32F,layers[L-1].w[i][j],Point(-1,-1),0,BORDER_CONSTANT); delt_l1[i][j]=poolingUp(delt_l1[i][j]);//反向池化,即上采样 Mat sigd=sigmoid_d(layers[L-2].z[i]); delt_l1[i][j]=delt_l1[i][j].mul(sigd); copyMakeBorder(delt_l1[i][j],delt_l1[i][j],2,2,2,2,BORDER_REPLICATE);//在delt_l1[i][j]四周补边,此时尺寸为28×28 } } ///在第1层中的反传误差delt_l1[i][j],共有6×12个误差项,而在第1层中只有6个权重 //// /*************更新权重********************/ //// //1.计算误差对卷积核W的偏微分,即书上704页,公式(12.104)、(12.105),这部分大概是最难理解的部分 //// //首先计算第1层。 for(int i=0;i<l1num_w;i++)//l1num_w=6 for(int j=0;j<l2num_w;j++)//l2num_w=12 { updateWeight(delt_l1[i][j],layers[0].a[0],layers[1].w[0][i],alpha); //倒数第三层,偏置项更新 layers[L-2].b[0][i] -=sum(delt_l1[i][j])[0]; } //计算第2层对卷积核w的偏微分,及更新 for(int i=0;i<l1num_w;i++) for(int j=0;j<l2num_w;j++) { updateWeight(D[2][j],layers[1].a[i],layers[2].w[i][j],alpha); layers[L-1].b[i][j] -= sum(D[L-1][j])[0];//倒数第二层,偏置项更新,L-1=2 } //计算第3层权重nw的更新 Mat Lw_d=D[L][0]*layers[L-1].p_vector.t(); nnet.nw -=alpha*Lw_d; //计算第3层偏置项nnet.b的更新 Mat bias_L; reduce(D[L][0],bias_L,1,REDUCE_AVG);//全连接网络层,偏置项更新 nnet.b -=bias_L; } };
将要学习的图片,手写数字1
下面是演示代码:
int main() { string labelpath="D:/Qt/MyImage/MNIST/train-labels.idx1-ubyte"; string imgpath="D:/Qt/MyImage/MNIST/train-images.idx3-ubyte"; //构建神经网络 DNN dnn; dnn.readImgLib(labelpath,imgpath); // 下面是230次循环训练所有权重 for(int i=0;i<1;i++) { dnn.calcForeward(); dnn.calcBackward(0.01); } cout<<"the real output of dnn!"<<endl; cout<<dnn.nnet.out_a<<endl; cout<<"the expected output of dnn!"<<endl; cout<<dnn.nnet.expect_a<<endl; waitKey(); return 0; }
下面是输出结果:
标签:最简,Mat,int,dst,DNN,C++,r180,num,delt From: https://www.cnblogs.com/phoenixdsg/p/16590777.html