本文实现使用c++底层实现深度学习框架,主要使用结构体。
本文简单搭建简单的图像分类的网络结构
本次训练用到的基本数据处理函数
英特尔处理器和其他低端机用户必须翻转头字节。
int ReverseInt(int i)
{
unsigned char ch1, ch2, ch3, ch4;
ch1 = i & 255;
ch2 = (i >> 8) & 255;
ch3 = (i >> 16) & 255;
ch4 = (i >> 24) & 255;
return((int) ch1 << 24) + ((int)ch2 << 16) + ((int)ch3 << 8) + ch4;
}
char* intTochar(int i)
{
int itemp=i;
int w=0;
while(itemp>=10){
itemp=itemp/10;
w++;
}
char* ptr=(char*)malloc((w+2)*sizeof(char));
ptr[w+1]='\0';
int r; // 余数
while(i>=10){
r=i%10;
i=i/10;
ptr[w]=(char)(r+48);
w--;
}
ptr[w]=(char)(i+48);
return ptr;
}
char * combine_strings(char *a, char *b)
{
char *ptr;
int lena=strlen(a),lenb=strlen(b);
int i,l=0;
ptr = (char *)malloc((lena+lenb+1) * sizeof(char));
for(i=0;i<lena;i++)
ptr[l++]=a[i];
for(i=0;i<lenb;i++)
ptr[l++]=b[i];
ptr[l]='\0';
return(ptr);
}
本文例子使用MNIST数据集,本模块主要设计数据集加载代码
typedef struct MinstImg{
int c; // 图像宽
int r; // 图像高
float** ImgData; // 图像数据二维动态数组
}MinstImg;
typedef struct MinstImgArr{
int ImgNum; // 存储图像的数目
MinstImg* ImgPtr; // 存储图像数组指针
}*ImgArr; // 存储图像数据的数组
typedef struct MinstLabel{
int l; // 输出标记的长
float* LabelData; // 输出标记数据
}MinstLabel;
typedef struct MinstLabelArr{
int LabelNum;
MinstLabel* LabelPtr;
}*LabelArr; // 存储图像标记的数组
ImgArr read_Img(const char* filename)
{
FILE *fp=NULL;
fp=fopen(filename,"rb");
if(fp==NULL)
printf("打开文件失败\n");
assert(fp);
int magic_number = 0;
int number_of_images = 0;
int n_rows = 0;
int n_cols = 0;
//从文件中读取sizeof(magic_number) 个字符到 &magic_number
fread((char*)&magic_number,sizeof(magic_number),1,fp);
magic_number = ReverseInt(magic_number);
//获取训练或测试image的个数number_of_images
fread((char*)&number_of_images,sizeof(number_of_images),1,fp);
number_of_images = ReverseInt(number_of_images);
//获取训练或测试图像的高度Heigh
fread((char*)&n_rows,sizeof(n_rows),1,fp);
n_rows = ReverseInt(n_rows);
//获取训练或测试图像的宽度Width
fread((char*)&n_cols,sizeof(n_cols),1,fp);
n_cols = ReverseInt(n_cols);
//获取第i幅图像,保存到vec中
int i,r,c;
// 图像数组的初始化
ImgArr imgarr=(ImgArr)malloc(sizeof(MinstImgArr));
imgarr->ImgNum=number_of_images;
imgarr->ImgPtr=(MinstImg*)malloc(number_of_images*sizeof(MinstImg));
for(i = 0; i < number_of_images; ++i)
{
imgarr->ImgPtr[i].r=n_rows;
imgarr->ImgPtr[i].c=n_cols;
imgarr->ImgPtr[i].ImgData=(float**)malloc(n_rows*sizeof(float*));
for(r = 0; r < n_rows; ++r)
{
imgarr->ImgPtr[i].ImgData[r]=(float*)malloc(n_cols*sizeof(float));
for(c = 0; c < n_cols; ++c)
{
unsigned char temp = 0;
fread((char*) &temp, sizeof(temp),1,fp);
imgarr->ImgPtr[i].ImgData[r][c]=(float)temp/255.0;
}
}
}
fclose(fp);
return imgarr;
}
LabelArr read_Lable(const char* filename)// 读入图像
{
FILE *fp=NULL;
fp=fopen(filename,"rb");
if(fp==NULL)
printf("打开文件失败\n");
assert(fp);
int magic_number = 0;
int number_of_labels = 0;
int label_long = 10;
//从文件中读取sizeof(magic_number) 个字符到 &magic_number
fread((char*)&magic_number,sizeof(magic_number),1,fp);
magic_number = ReverseInt(magic_number);
//获取训练或测试image的个数number_of_images
fread((char*)&number_of_labels,sizeof(number_of_labels),1,fp);
number_of_labels = ReverseInt(number_of_labels);
int i,l;
// 图像标记数组的初始化
LabelArr labarr=(LabelArr)malloc(sizeof(MinstLabelArr));
labarr->LabelNum=number_of_labels;
labarr->LabelPtr=(MinstLabel*)malloc(number_of_labels*sizeof(MinstLabel));
for(i = 0; i < number_of_labels; ++i)
{
labarr->LabelPtr[i].l=10;
labarr->LabelPtr[i].LabelData=(float*)calloc(label_long,sizeof(float));
unsigned char temp = 0;
fread((char*) &temp, sizeof(temp),1,fp);
labarr->LabelPtr[i].LabelData[(int)temp]=1.0;
}
fclose(fp);
return labarr;
}
将图像数据保存成文件
void save_Img(ImgArr imgarr,char* filedir)
{
int img_number=imgarr->ImgNum;
int i,r;
for(i=0;i<img_number;i++){
const char* filename=combine_strings(filedir,combine_strings(intTochar(i),".gray"));
FILE *fp=NULL;
fp=fopen(filename,"wb");
if(fp==NULL)
printf("保存图像文件失败\n");
assert(fp);
for(r=0;r<imgarr->ImgPtr[i].r;r++)
fwrite(imgarr->ImgPtr[i].ImgData[r],sizeof(float),imgarr->ImgPtr[i].c,fp);
fclose(fp);
}
}
#define full 0
#define same 1
#define valid 2
typedef struct Mat2DSize{
int c; // 列(宽)
int r; // 行(高)
}nSize;
float** rotate180(float** mat, nSize matSize)// 矩阵翻转180度
{
int i,c,r;
int outSizeW=matSize.c;
int outSizeH=matSize.r;
float** outputData=(float**)malloc(outSizeH*sizeof(float*));
for(i=0;i<outSizeH;i++)
outputData[i]=(float*)malloc(outSizeW*sizeof(float));
for(r=0;r<outSizeH;r++)
for(c=0;c<outSizeW;c++)
outputData[r][c]=mat[outSizeH-r-1][outSizeW-c-1];
return outputData;
}
关于卷积和相关操作的输出选项
这里共有三种选择:full、same、valid,分别表示
full指完全,操作后结果的大小为inSize+(mapSize-1)
same指同输入相同大小
valid指完全操作后的大小,一般为inSize-(mapSize-1)大小,其不需要将输入添0扩大。
float** correlation(float** map,nSize mapSize,float** inputData,nSize inSize,int type)// 互相关
{
// 这里的互相关是在后向传播时调用,类似于将Map反转180度再卷积
// 为了方便计算,这里先将图像扩充一圈
// 这里的卷积要分成两拨,偶数模板同奇数模板
int i,j,c,r;
int halfmapsizew;
int halfmapsizeh;
if(mapSize.r%2==0&&mapSize.c%2==0){ // 模板大小为偶数
halfmapsizew=(mapSize.c)/2; // 卷积模块的半瓣大小
halfmapsizeh=(mapSize.r)/2;
}else{
halfmapsizew=(mapSize.c-1)/2; // 卷积模块的半瓣大小
halfmapsizeh=(mapSize.r-1)/2;
}
// 这里先默认进行full模式的操作,full模式的输出大小为inSize+(mapSize-1)
int outSizeW=inSize.c+(mapSize.c-1); // 这里的输出扩大一部分
int outSizeH=inSize.r+(mapSize.r-1);
float** outputData=(float**)malloc(outSizeH*sizeof(float*)); // 互相关的结果扩大了
for(i=0;i<outSizeH;i++)
outputData[i]=(float*)calloc(outSizeW,sizeof(float));
// 为了方便计算,将inputData扩大一圈
float** exInputData=matEdgeExpand(inputData,inSize,mapSize.c-1,mapSize.r-1);
for(j=0;j<outSizeH;j++)
for(i=0;i<outSizeW;i++)
for(r=0;r<mapSize.r;r++)
for(c=0;c<mapSize.c;c++){
outputData[j][i]=outputData[j][i]+map[r][c]*exInputData[j+r][i+c];
}
for(i=0;i<inSize.r+2*(mapSize.r-1);i++)
free(exInputData[i]);
free(exInputData);
nSize outSize={outSizeW,outSizeH};
switch(type){ // 根据不同的情况,返回不同的结果
case full: // 完全大小的情况
return outputData;
case same:{
float** sameres=matEdgeShrink(outputData,outSize,halfmapsizew,halfmapsizeh);
for(i=0;i<outSize.r;i++)
free(outputData[i]);
free(outputData);
return sameres;
}
case valid:{
float** validres;
if(mapSize.r%2==0&&mapSize.c%2==0)
validres=matEdgeShrink(outputData,outSize,halfmapsizew*2-1,halfmapsizeh*2-1);
else
validres=matEdgeShrink(outputData,outSize,halfmapsizew*2,halfmapsizeh*2);
for(i=0;i<outSize.r;i++)
free(outputData[i]);
free(outputData);
return validres;
}
default:
return outputData;
}
}
float** cov(float** map,nSize mapSize,float** inputData,nSize inSize,int type) // 卷积操作
{
// 卷积操作可以用旋转180度的特征模板相关来求
float** flipmap=rotate180(map,mapSize); //旋转180度的特征模板
float** res=correlation(flipmap,mapSize,inputData,inSize,type);
int i;
for(i=0;i<mapSize.r;i++)
free(flipmap[i]);
free(flipmap);
return res;
}
这个是矩阵的上采样(等值内插),upc及upr是内插倍数
float** UpSample(float** mat,nSize matSize,int upc,int upr)
{
int i,j,m,n;
int c=matSize.c;
int r=matSize.r;
float** res=(float**)malloc((r*upr)*sizeof(float*)); // 结果的初始化
for(i=0;i<(r*upr);i++)
res[i]=(float*)malloc((c*upc)*sizeof(float));
for(j=0;j<r*upr;j=j+upr){
for(i=0;i<c*upc;i=i+upc)// 宽的扩充
for(m=0;m<upc;m++)
res[j][i+m]=mat[j/upr][i/upc];
for(n=1;n<upr;n++) // 高的扩充
for(i=0;i<c*upc;i++)
res[j+n][i]=res[j][i];
}
return res;
}
给二维矩阵边缘扩大,增加addw大小的0值边
float** matEdgeExpand(float** mat,nSize matSize,int addc,int addr)
{ // 向量边缘扩大
int i,j;
int c=matSize.c;
int r=matSize.r;
float** res=(float**)malloc((r+2*addr)*sizeof(float*)); // 结果的初始化
for(i=0;i<(r+2*addr);i++)
res[i]=(float*)malloc((c+2*addc)*sizeof(float));
for(j=0;j<r+2*addr;j++){
for(i=0;i<c+2*addc;i++){
if(j<addr||i<addc||j>=(r+addr)||i>=(c+addc))
res[j][i]=(float)0.0;
else
res[j][i]=mat[j-addr][i-addc]; // 复制原向量的数据
}
}
return res;
}
给二维矩阵边缘缩小,擦除shrinkc大小的边
float** matEdgeShrink(float** mat,nSize matSize,int shrinkc,int shrinkr)
{ // 向量的缩小,宽缩小addw,高缩小addh
int i,j;
int c=matSize.c;
int r=matSize.r;
float** res=(float**)malloc((r-2*shrinkr)*sizeof(float*)); // 结果矩阵的初始化
for(i=0;i<(r-2*shrinkr);i++)
res[i]=(float*)malloc((c-2*shrinkc)*sizeof(float));
for(j=0;j<r;j++){
for(i=0;i<c;i++){
if(j>=shrinkr&&i>=shrinkc&&j<(r-shrinkr)&&i<(c-shrinkc))
res[j-shrinkr][i-shrinkc]=mat[j][i]; // 复制原向量的数据
}
}
return res;
}
void savemat(float** mat,nSize matSize,const char* filename)
{
FILE *fp=NULL;
fp=fopen(filename,"wb");
if(fp==NULL)
printf("write file failed\n");
int i;
for(i=0;i<matSize.r;i++)
fwrite(mat[i],sizeof(float),matSize.c,fp);
fclose(fp);
}
void addmat(float** res, float** mat1, nSize matSize1, float** mat2, nSize matSize2)// 矩阵相加
{
int i,j;
if(matSize1.c!=matSize2.c||matSize1.r!=matSize2.r)
printf("ERROR: Size is not same!");
for(i=0;i<matSize1.r;i++)
for(j=0;j<matSize1.c;j++)
res[i][j]=mat1[i][j]+mat2[i][j];
}
void multifactor(float** res, float** mat, nSize matSize, float factor)// 矩阵乘以系数
{
int i,j;
for(i=0;i<matSize.r;i++)
for(j=0;j<matSize.c;j++)
res[i][j]=mat[i][j]*factor;
}
float summat(float** mat,nSize matSize) // 矩阵各元素的和
{
float sum=0.0;
int i,j;
for(i=0;i<matSize.r;i++)
for(j=0;j<matSize.c;j++)
sum=sum+mat[i][j];
return sum;
}
#define AvePool 0
#define MaxPool 1
#define MinPool 2
// 卷积层
typedef struct convolutional_layer{
int inputWidth; //输入图像的宽
int inputHeight; //输入图像的长
int mapSize; //特征模板的大小,模板一般都是正方形
int inChannels; //输入图像的数目
int outChannels; //输出图像的数目
// 关于特征模板的权重分布,这里是一个四维数组
// 其大小为inChannels*outChannels*mapSize*mapSize大小
// 这里用四维数组,主要是为了表现全连接的形式,实际上卷积层并没有用到全连接的形式
// 这里的例子是DeapLearningToolboox里的CNN例子,其用到就是全连接
float**** mapData; //存放特征模块的数据
float**** dmapData; //存放特征模块的数据的局部梯度
float* basicData; //偏置,偏置的大小,为outChannels
bool isFullConnect; //是否为全连接
bool* connectModel; //连接模式(默认为全连接)
// 下面三者的大小同输出的维度相同
float*** v; // 进入激活函数的输入值
float*** y; // 激活函数后神经元的输出
// 输出像素的局部梯度
float*** d; // 网络的局部梯度,δ值
}CovLayer;
// 采样层 pooling
typedef struct pooling_layer{
int inputWidth; //输入图像的宽
int inputHeight; //输入图像的长
int mapSize; //特征模板的大小
int inChannels; //输入图像的数目
int outChannels; //输出图像的数目
int poolType; //Pooling的方法
float* basicData; //偏置
float*** y; // 采样函数后神经元的输出,无激活函数
float*** d; // 网络的局部梯度,δ值
}PoolLayer;
// 输出层 全连接的神经网络
typedef struct nn_layer{
int inputNum; //输入数据的数目
int outputNum; //输出数据的数目
float** wData; // 权重数据,为一个inputNum*outputNum大小
float* basicData; //偏置,大小为outputNum大小
// 下面三者的大小同输出的维度相同
float* v; // 进入激活函数的输入值
float* y; // 激活函数后神经元的输出
float* d; // 网络的局部梯度,δ值
bool isFullConnect; //是否为全连接
}OutLayer;
typedef struct cnn_network{
int layerNum;
CovLayer* C1;
PoolLayer* S2;
CovLayer* C3;
PoolLayer* S4;
OutLayer* O5;
float* e; // 训练误差
float* L; // 瞬时误差能量
}CNN;
typedef struct train_opts{
int numepochs; // 训练的迭代次数
float alpha; // 学习速率
}CNNOpts;
CovLayer* initCovLayer(int inputWidth,int inputHeight,int mapSize,int inChannels,int outChannels)
{
CovLayer* covL=(CovLayer*)malloc(sizeof(CovLayer));
covL->inputHeight=inputHeight;
covL->inputWidth=inputWidth;
covL->mapSize=mapSize;
covL->inChannels=inChannels;
covL->outChannels=outChannels;
covL->isFullConnect=true; // 默认为全连接
// 权重空间的初始化,先行再列调用,[r][c]
int i,j,c,r;
srand((unsigned)time(NULL));
covL->mapData=(float****)malloc(inChannels*sizeof(float***));
for(i=0;i<inChannels;i++){
covL->mapData[i]=(float***)malloc(outChannels*sizeof(float**));
for(j=0;j<outChannels;j++){
covL->mapData[i][j]=(float**)malloc(mapSize*sizeof(float*));
for(r=0;r<mapSize;r++){
covL->mapData[i][j][r]=(float*)malloc(mapSize*sizeof(float));
for(c=0;c<mapSize;c++){
float randnum=(((float)rand()/(float)RAND_MAX)-0.5)*2;
covL->mapData[i][j][r][c]=randnum*sqrt((float)6.0/(float)(mapSize*mapSize*(inChannels+outChannels)));
}
}
}
}
// 权重梯度变化
covL->dmapData=(float****)malloc(inChannels*sizeof(float***));
for(i=0;i<inChannels;i++){
covL->dmapData[i]=(float***)malloc(outChannels*sizeof(float**));
for(j=0;j<outChannels;j++){
covL->dmapData[i][j]=(float**)malloc(mapSize*sizeof(float*));
for(r=0;r<mapSize;r++){
covL->dmapData[i][j][r]=(float*)calloc(mapSize,sizeof(float));
}
}
}
covL->basicData=(float*)calloc(outChannels,sizeof(float));
int outW=inputWidth-mapSize+1;
int outH=inputHeight-mapSize+1;
covL->d=(float***)malloc(outChannels*sizeof(float**));
covL->v=(float***)malloc(outChannels*sizeof(float**));
covL->y=(float***)malloc(outChannels*sizeof(float**));
for(j=0;j<outChannels;j++){
covL->d[j]=(float**)malloc(outH*sizeof(float*));
covL->v[j]=(float**)malloc(outH*sizeof(float*));
covL->y[j]=(float**)malloc(outH*sizeof(float*));
for(r=0;r<outH;r++){
covL->d[j][r]=(float*)calloc(outW,sizeof(float));
covL->v[j][r]=(float*)calloc(outW,sizeof(float));
covL->y[j][r]=(float*)calloc(outW,sizeof(float));
}
}
return covL;
}
PoolLayer* initPoolLayer(int inputWidth,int inputHeight,int mapSize,int inChannels,int outChannels,int poolType)
{
PoolLayer* poolL=(PoolLayer*)malloc(sizeof(PoolLayer));
poolL->inputHeight=inputHeight;
poolL->inputWidth=inputWidth;
poolL->mapSize=mapSize;
poolL->inChannels=inChannels;
poolL->outChannels=outChannels;
poolL->poolType=poolType;
poolL->basicData=(float*)calloc(outChannels,sizeof(float));
int outW=inputWidth/mapSize;
int outH=inputHeight/mapSize;
int j,r;
poolL->d=(float***)malloc(outChannels*sizeof(float**));
poolL->y=(float***)malloc(outChannels*sizeof(float**));
for(j=0;j<outChannels;j++){
poolL->d[j]=(float**)malloc(outH*sizeof(float*));
poolL->y[j]=(float**)malloc(outH*sizeof(float*));
for(r=0;r<outH;r++){
poolL->d[j][r]=(float*)calloc(outW,sizeof(float));
poolL->y[j][r]=(float*)calloc(outW,sizeof(float));
}
}
return poolL;
}
OutLayer* initOutLayer(int inputNum,int outputNum)
{
OutLayer* outL=(OutLayer*)malloc(sizeof(OutLayer));
outL->inputNum=inputNum;
outL->outputNum=outputNum;
outL->basicData=(float*)calloc(outputNum,sizeof(float));
outL->d=(float*)calloc(outputNum,sizeof(float));
outL->v=(float*)calloc(outputNum,sizeof(float));
outL->y=(float*)calloc(outputNum,sizeof(float));
// 权重的初始化
outL->wData=(float**)malloc(outputNum*sizeof(float*)); // 输入行,输出列
int i,j;
srand((unsigned)time(NULL));
for(i=0;i<outputNum;i++){
outL->wData[i]=(float*)malloc(inputNum*sizeof(float));
for(j=0;j<inputNum;j++){
float randnum=(((float)rand()/(float)RAND_MAX)-0.5)*2; // 产生一个-1到1的随机数
outL->wData[i][j]=randnum*sqrt((float)6.0/(float)(inputNum+outputNum));
}
}
outL->isFullConnect=true;
return outL;
}
float activation_Sigma(float input,float bas) // sigma激活函数
{
float temp=input+bas;
return (float)1.0/((float)(1.0+exp(-temp)));
}
void cnntrain(CNN* cnn, ImgArr inputData,LabelArr outputData,CNNOpts opts,int trainNum)
{
// 学习训练误差曲线
cnn->L=(float*)malloc(trainNum*sizeof(float));
int e;
for(e=0;e<opts.numepochs;e++){
int n=0;
for(n=0;n<trainNum;n++){
printf("%d\n",n);
cnnff(cnn,inputData->ImgPtr[n].ImgData); // 前向传播,这里主要计算各
cnnbp(cnn,outputData->LabelPtr[n].LabelData); // 后向传播,这里主要计算各神经元的误差梯度
char* filedir="./data/";
const char* filename=combine_strings(filedir,combine_strings(intTochar(n),".cnn"));
savecnndata(cnn,filename,inputData->ImgPtr[n].ImgData);
cnnapplygrads(cnn,opts,inputData->ImgPtr[n].ImgData); // 更新权重
cnnclear(cnn);
// 计算并保存误差能量
float l=0.0;
int i;
for(i=0;i<cnn->O5->outputNum;i++)
l=l+cnn->e[i]*cnn->e[i];
if(n==0)
cnn->L[n]=l/(float)2.0;
else
cnn->L[n]=cnn->L[n-1]*0.99+0.01*l/(float)2.0;
}
}
}
// 保存cnn
void savecnn(CNN* cnn, const char* filename)
{
FILE *fp=NULL;
fp=fopen(filename,"wb");
if(fp==NULL)
printf("write file failed\n");
int i,j,r;
// C1的数据
for(i=0;i<cnn->C1->inChannels;i++)
for(j=0;j<cnn->C1->outChannels;j++)
for(r=0;r<cnn->C1->mapSize;r++)
fwrite(cnn->C1->mapData[i][j][r],sizeof(float),cnn->C1->mapSize,fp);
fwrite(cnn->C1->basicData,sizeof(float),cnn->C1->outChannels,fp);
// C3网络
for(i=0;i<cnn->C3->inChannels;i++)
for(j=0;j<cnn->C3->outChannels;j++)
for(r=0;r<cnn->C3->mapSize;r++)
fwrite(cnn->C3->mapData[i][j][r],sizeof(float),cnn->C3->mapSize,fp);
fwrite(cnn->C3->basicData,sizeof(float),cnn->C3->outChannels,fp);
// O5输出层
for(i=0;i<cnn->O5->outputNum;i++)
fwrite(cnn->O5->wData[i],sizeof(float),cnn->O5->inputNum,fp);
fwrite(cnn->O5->basicData,sizeof(float),cnn->O5->outputNum,fp);
fclose(fp);
}
// 导入cnn的数据
void importcnn(CNN* cnn, const char* filename)
{
FILE *fp=NULL;
fp=fopen(filename,"rb");
if(fp==NULL)
printf("write file failed\n");
int i,j,c,r;
// C1的数据
for(i=0;i<cnn->C1->inChannels;i++)
for(j=0;j<cnn->C1->outChannels;j++)
for(r=0;r<cnn->C1->mapSize;r++)
for(c=0;c<cnn->C1->mapSize;c++){
float* in=(float*)malloc(sizeof(float));
fread(in,sizeof(float),1,fp);
cnn->C1->mapData[i][j][r][c]=*in;
}
for(i=0;i<cnn->C1->outChannels;i++)
fread(&cnn->C1->basicData[i],sizeof(float),1,fp);
// C3网络
for(i=0;i<cnn->C3->inChannels;i++)
for(j=0;j<cnn->C3->outChannels;j++)
for(r=0;r<cnn->C3->mapSize;r++)
for(c=0;c<cnn->C3->mapSize;c++)
fread(&cnn->C3->mapData[i][j][r][c],sizeof(float),1,fp);
for(i=0;i<cnn->C3->outChannels;i++)
fread(&cnn->C3->basicData[i],sizeof(float),1,fp);
// O5输出层
for(i=0;i<cnn->O5->outputNum;i++)
for(j=0;j<cnn->O5->inputNum;j++)
fread(&cnn->O5->wData[i][j],sizeof(float),1,fp);
for(i=0;i<cnn->O5->outputNum;i++)
fread(&cnn->O5->basicData[i],sizeof(float),1,fp);
fclose(fp);
}
void cnnsetup(CNN* cnn,nSize inputSize,int outputSize)
{
cnn->layerNum=5;
nSize inSize;
int mapSize=5;
inSize.c=inputSize.c;
inSize.r=inputSize.r;
cnn->C1=initCovLayer(inSize.c,inSize.r,5,1,6);
inSize.c=inSize.c-mapSize+1;
inSize.r=inSize.r-mapSize+1;
cnn->S2=initPoolLayer(inSize.c,inSize.r,2,6,6,AvePool);
inSize.c=inSize.c/2;
inSize.r=inSize.r/2;
cnn->C3=initCovLayer(inSize.c,inSize.r,5,6,12);
inSize.c=inSize.c-mapSize+1;
inSize.r=inSize.r-mapSize+1;
cnn->S4=initPoolLayer(inSize.c,inSize.r,2,12,12,AvePool);
inSize.c=inSize.c/2;
inSize.r=inSize.r/2;
cnn->O5=initOutLayer(inSize.c*inSize.r*12,outputSize);
cnn->e=(float*)calloc(cnn->O5->outputNum,sizeof(float));
}
LabelArr trainLabel=read_Lable("./data/train-labels.idx1-ubyte");
ImgArr trainImg=read_Img("./data/train-images.idx3-ubyte");
LabelArr testLabel=read_Lable("./data/t10k-labels.idx1-ubyte");
ImgArr testImg=read_Img("./data/t10k-images.idx3-ubyte");
nSize inputSize={testImg->ImgPtr[0].c,testImg->ImgPtr[0].r};
int outSize=testLabel->LabelPtr[0].l;
// CNN结构的初始化
CNN* cnn=(CNN*)malloc(sizeof(CNN));
cnnsetup(cnn,inputSize,outSize);
// CNN训练
CNNOpts opts;
opts.numepochs=1;
opts.alpha=1.0;
int trainNum=55000;
cnntrain(cnn,trainImg,trainLabel,opts,trainNum);
printf("train finished!!\n");
savecnn(cnn,"./data/minst.cnn");
// 保存训练误差
FILE *fp=NULL;
fp=fopen("./data/cnnL.ma","wb");
if(fp==NULL)
printf("write file failed\n");
fwrite(cnn->L,sizeof(float),trainNum,fp);
fclose(fp);
// CNN测试
importcnn(cnn,"minst.cnn");
int testNum=10000;
float incorrectRatio=0.0;
incorrectRatio=cnntest(cnn,testImg,testLabel,testNum);
printf("test finished!!\n");
CNN源码