本文来自CSDN博客,转载请注明出处:http://blog.csdn.net/a635661820/article/details/44730507
参考文献: A Neural Probabilistic Language Model
参照我另一篇NNLM学习介绍的博客, 这一篇是对NNLM的简要实现, 自己简化了一些,输入层到输出层没有连接(加上直连边的真在原论文中没有明显的提高),并且没有并行算法。下面贴上自己的一些核心代码。总体来说,我用了c++面向对象来设计该算法,大概分为6个类,如下:
- CLex类:用来处理文本的一个类
- CInput类:输入层类, 包含相关量
- CHidden类:隐层类,包含相关量
- COutput类:输出层类,包含相关变量
- CAlgothrim类:算法类,涉及前向算法以及反向更新算法,将前面3个类串起来
- CForeCast类:测试类,训练好模型后对模型进行测试的相关操作
关于网络中的各个手动设定的参数在macroDefinition.h,包括隐层神经元个数、特征向量维度等。这里的附带的代码只贴出了核心相关代码,即CInput, CHidden, COutput, CAlgothrim的核心代码
网络手动设定的参数在macroDefinition.h里面,定义为宏,初始设置是按照论文中设置的:
//以下是各个变量的宏定义,留做接口,方便调试
#define M 100 //一个单词对应的向量的维度数为M
#define N 3 //输入层的单词个数,第N+1个是预测单词
#define HN 60 //Hidden Number隐层神经元的个数
#define EPSILON 0.001 //神经网络的学习率
#define CONVERGE_THRESHOLD -4 //手动设定的累加对数概率收敛值
#define FOREWORD_NUM 5 //模型预测时的输出概率最高的前5个词语
#define M 100 //一个单词对应的向量的维度数为M
#define N 3 //输入层的单词个数,第N+1个是预测单词
#define HN 60 //Hidden Number隐层神经元的个数
#define EPSILON 0.001 //神经网络的学习率
#define CONVERGE_THRESHOLD -4 //手动设定的累加对数概率收敛值
#define FOREWORD_NUM 5 //模型预测时的输出概率最高的前5个词语
在Input.h文件里面,类的结构:
//输入层的结构定义
class CInput
{
public :
bool LoadCorpusfile( char *corpusfile); //将语料库文件读入
bool Computex( int k); //计算一个句子输入单词的x向量
string GetWordByID(unsigned long id); //根据ID返回单词
unsigned long GetIDByWord(string word); //根据单词返回其ID
bool NextSentence( int i); //从语料库文件中读入下一个句子
CInput(); //生成词典、二维矩阵
virtual ~CInput(); //释放单词映射矩阵,释放词典
CLex *pChnLex; //指向中文词典的指针
float **vec; //输入单词对应的映射矩阵
vector<string> sentence; //一行句子
vector<string> corpus; //语料库
float *x; //保存输入层单词的特征向量
long unsigned expectedID; //训练句子时下一个输出单词ID
};
class CInput
{
public :
bool LoadCorpusfile( char *corpusfile); //将语料库文件读入
bool Computex( int k); //计算一个句子输入单词的x向量
string GetWordByID(unsigned long id); //根据ID返回单词
unsigned long GetIDByWord(string word); //根据单词返回其ID
bool NextSentence( int i); //从语料库文件中读入下一个句子
CInput(); //生成词典、二维矩阵
virtual ~CInput(); //释放单词映射矩阵,释放词典
CLex *pChnLex; //指向中文词典的指针
float **vec; //输入单词对应的映射矩阵
vector<string> sentence; //一行句子
vector<string> corpus; //语料库
float *x; //保存输入层单词的特征向量
long unsigned expectedID; //训练句子时下一个输出单词ID
};
类的实现在Input.cpp里面:
CInput::CInput()
{
//构造函数
//生成词典、二维矩阵
pChnLex = new CLex; //动态分配词典对象
if (NULL == pChnLex) //分配失败
{
printf("动态分配词典失败!\n");
exit(1);
}
if (!pChnLex->LoadLexicon_Null("output.voc")) //将指向中文词典的指针与词典文本库连接起来
{
printf("载入中文词典失败!\n"); //载入失败输出错误信息到屏幕
}
unsigned long sizeV = pChnLex->ulVocSizeC; //记录词典的大小
srand(time(NULL)); //随机数种子
vec = new float *[sizeV]; //开始分配单词映射的二维矩阵
if (NULL == vec) //分配失败
{
printf("分配单词映射矩阵失败!\n");
exit(1);
}
for (unsigned long i=1; i<sizeV; i++) //初始化二维矩阵
{
vec[i] = new float[M]; //分配满足每个单词对应M维
if (NULL == vec[i])
{
printf("分配单词映射向量失败!\n");
exit(1);
}
for (int j=0; j<M; j++) //赋值-0.5-0.5的小数
{
vec[i][j] = (float)(((rand()/32767.0)*2-1)/2);
}
}
x = new float[M*N]; //输入层单词的特征向量
if (NULL == x)
{
cerr << "输入层单词的特征向量分配失败" << endl;
exit(1);
}
memset(x, 0, M*N*sizeof(float)); //将x向量清0
}
CInput::~CInput()
{
//析构函数
//释放单词映射矩阵
//释放中文词典
unsigned long sizeV = pChnLex->ulVocSizeC; //记录词典的大小
for (unsigned long i=1; i<sizeV; i++) //释放单词映射矩阵
{
delete [] vec[i];
vec[i] = NULL;
}
delete []vec;
vec = NULL;
delete pChnLex; //释放中文词典
pChnLex = NULL;
delete [] x; //释放x
x = NULL;
}
bool CInput::NextSentence(int i)
{
//从预料中读取第i个句子
//并且将该句子中的单词以空格分隔存入容器
string line;
string word;
if (i >= corpus.size()) //错误
{
cerr << "!!!!!error 下标出界" << endl;
return false;
}
line = corpus[i]; //第i个句子
stringstream instring(line); //字符流与该句子关联
sentence.clear(); //清空之前的句子
while (instring >> word)
{
sentence.push_back(word); //把句子中的单词以空格隔开存入sentence
}
return true;
}
unsigned long CInput::GetIDByWord(string word)
{
//根据单词返回其ID
return pChnLex->findword(word);
}
string CInput::GetWordByID(unsigned long id)
{
//根据ID返回单词
return pChnLex->GetLexiconByID(id);
}
bool CInput::Computex(int k)
{
//计算一个句子输入单词的x向量
//k用来控制多次训练一个句子
long unsigned id;
long unsigned Vsize = pChnLex->ulVocSizeC; //词典大小
int i, j, t;
for (i=k,t=0; i<N+k; i++,t++)
{
if (i >= sentence.size()) //该句子训练完毕
{
return false;
}
id = GetIDByWord(sentence[i]); //得到输入层第i个单词id
if (id >= Vsize)
{
cerr << "输入句子有误(可能是由于当前训练语料库过小,词库太少导致)" << endl;
exit(1);
}
for (j=0; j<M; j++)
{
x[M*t+j] = vec[id][j];
}
}
if (k+N >= sentence.size()) //该句子训练完毕
{
expectedID = 0; //不会出现的ID号码标注
return false;
}
expectedID = GetIDByWord(sentence[k+N]); //得到训练句子的输出单词ID
}
bool CInput::LoadCorpusfile(char *corpusfile)
{
//将语料库文件读入内存
ifstream infile(corpusfile); //关联欲读入的文件
if (!infile) //读入失败
{
return false;
}
string line;
string word;
while (getline(infile, line)) //从语料库读入一行句子到容器
{
corpus.push_back(line);
}
infile.close(); //关闭文件
return true;
}
{
//构造函数
//生成词典、二维矩阵
pChnLex = new CLex; //动态分配词典对象
if (NULL == pChnLex) //分配失败
{
printf("动态分配词典失败!\n");
exit(1);
}
if (!pChnLex->LoadLexicon_Null("output.voc")) //将指向中文词典的指针与词典文本库连接起来
{
printf("载入中文词典失败!\n"); //载入失败输出错误信息到屏幕
}
unsigned long sizeV = pChnLex->ulVocSizeC; //记录词典的大小
srand(time(NULL)); //随机数种子
vec = new float *[sizeV]; //开始分配单词映射的二维矩阵
if (NULL == vec) //分配失败
{
printf("分配单词映射矩阵失败!\n");
exit(1);
}
for (unsigned long i=1; i<sizeV; i++) //初始化二维矩阵
{
vec[i] = new float[M]; //分配满足每个单词对应M维
if (NULL == vec[i])
{
printf("分配单词映射向量失败!\n");
exit(1);
}
for (int j=0; j<M; j++) //赋值-0.5-0.5的小数
{
vec[i][j] = (float)(((rand()/32767.0)*2-1)/2);
}
}
x = new float[M*N]; //输入层单词的特征向量
if (NULL == x)
{
cerr << "输入层单词的特征向量分配失败" << endl;
exit(1);
}
memset(x, 0, M*N*sizeof(float)); //将x向量清0
}
CInput::~CInput()
{
//析构函数
//释放单词映射矩阵
//释放中文词典
unsigned long sizeV = pChnLex->ulVocSizeC; //记录词典的大小
for (unsigned long i=1; i<sizeV; i++) //释放单词映射矩阵
{
delete [] vec[i];
vec[i] = NULL;
}
delete []vec;
vec = NULL;
delete pChnLex; //释放中文词典
pChnLex = NULL;
delete [] x; //释放x
x = NULL;
}
bool CInput::NextSentence(int i)
{
//从预料中读取第i个句子
//并且将该句子中的单词以空格分隔存入容器
string line;
string word;
if (i >= corpus.size()) //错误
{
cerr << "!!!!!error 下标出界" << endl;
return false;
}
line = corpus[i]; //第i个句子
stringstream instring(line); //字符流与该句子关联
sentence.clear(); //清空之前的句子
while (instring >> word)
{
sentence.push_back(word); //把句子中的单词以空格隔开存入sentence
}
return true;
}
unsigned long CInput::GetIDByWord(string word)
{
//根据单词返回其ID
return pChnLex->findword(word);
}
string CInput::GetWordByID(unsigned long id)
{
//根据ID返回单词
return pChnLex->GetLexiconByID(id);
}
bool CInput::Computex(int k)
{
//计算一个句子输入单词的x向量
//k用来控制多次训练一个句子
long unsigned id;
long unsigned Vsize = pChnLex->ulVocSizeC; //词典大小
int i, j, t;
for (i=k,t=0; i<N+k; i++,t++)
{
if (i >= sentence.size()) //该句子训练完毕
{
return false;
}
id = GetIDByWord(sentence[i]); //得到输入层第i个单词id
if (id >= Vsize)
{
cerr << "输入句子有误(可能是由于当前训练语料库过小,词库太少导致)" << endl;
exit(1);
}
for (j=0; j<M; j++)
{
x[M*t+j] = vec[id][j];
}
}
if (k+N >= sentence.size()) //该句子训练完毕
{
expectedID = 0; //不会出现的ID号码标注
return false;
}
expectedID = GetIDByWord(sentence[k+N]); //得到训练句子的输出单词ID
}
bool CInput::LoadCorpusfile(char *corpusfile)
{
//将语料库文件读入内存
ifstream infile(corpusfile); //关联欲读入的文件
if (!infile) //读入失败
{
return false;
}
string line;
string word;
while (getline(infile, line)) //从语料库读入一行句子到容器
{
corpus.push_back(line);
}
infile.close(); //关闭文件
return true;
}
//隐层的结构定义
class CHidden
{
public:
CHidden(); //生成输入层到隐层的权值矩阵,隐层神经元的输出向量,偏置向量
virtual ~CHidden(); //释放权值矩阵H、向量a,d
void OutHidden(float *x); //计算隐层的输出
float **H; //由输入层到隐层的权值矩阵(变量名按照论文中,方便对照)
float *a; //隐层神经元的输出向量
float *d; //隐层的偏置向量
};
隐层的结构定义在Hidden.h里面:
//隐层的结构定义
class CHidden
{
public:
CHidden(); //生成输入层到隐层的权值矩阵,隐层神经元的输出向量,偏置向量
virtual ~CHidden(); //释放权值矩阵H、向量a,d
void OutHidden(float *x); //计算隐层的输出
float **H; //由输入层到隐层的权值矩阵(变量名按照论文中,方便对照)
float *a; //隐层神经元的输出向量
float *d; //隐层的偏置向量
};
class CHidden
{
public:
CHidden(); //生成输入层到隐层的权值矩阵,隐层神经元的输出向量,偏置向量
virtual ~CHidden(); //释放权值矩阵H、向量a,d
void OutHidden(float *x); //计算隐层的输出
float **H; //由输入层到隐层的权值矩阵(变量名按照论文中,方便对照)
float *a; //隐层神经元的输出向量
float *d; //隐层的偏置向量
};
隐层的实现在Hidden.cpp:
CHidden::CHidden()
{
//构造函数
//生成输入层到隐层的权值矩阵
//生成隐层神经元的输出向量
//生成隐层的偏置向量
H = new float *[HN]; //输入层到隐层的权值矩阵H
if (NULL == H)
{
cerr << "输入层到隐层的权值矩阵分配失败!" << endl;
exit(0);
}
int i, j;
for (i=0; i<HN; i++)
{
H[i] = new float [M*N];
if (NULL == H[i])
{
cerr << "输入层到隐层的权值矩阵分配失败!" << endl;
exit(0);
}
}
for (i=0; i<HN; i++) //初始化矩阵H
{
for (j=0; j<M*N; j++)
{
H[i][j] = (float)(((rand()/32767.0)*2-1)/2); //赋值-0.5-0.5的小数
}
}
d = new float[HN]; //隐层偏置向量
if (NULL == d)
{
cerr << "隐层偏置向量分配失败" << endl;
exit(1);
}
a = new float[HN]; //隐层输出向量
if (NULL == a)
{
cerr << "隐层输出向量分配失败" << endl;
exit(1);
}
for (i=0; i<HN; i++)
{
d[i] = (float)(((rand()/32767.0)*2-1)/2); //赋值-0.5-0.5的小数
a[i] = 0;
}
}
CHidden::~CHidden()
{
//释放权值矩阵H、向量a,d
int i;
for (i=0; i<HN; i++) //释放H
{
delete [] H[i];
H[i] = NULL;
}
delete []H;
H = NULL;
delete []d; //释放d
delete []a; //释放a
d = NULL;
a = NULL;
}
{
//构造函数
//生成输入层到隐层的权值矩阵
//生成隐层神经元的输出向量
//生成隐层的偏置向量
H = new float *[HN]; //输入层到隐层的权值矩阵H
if (NULL == H)
{
cerr << "输入层到隐层的权值矩阵分配失败!" << endl;
exit(0);
}
int i, j;
for (i=0; i<HN; i++)
{
H[i] = new float [M*N];
if (NULL == H[i])
{
cerr << "输入层到隐层的权值矩阵分配失败!" << endl;
exit(0);
}
}
for (i=0; i<HN; i++) //初始化矩阵H
{
for (j=0; j<M*N; j++)
{
H[i][j] = (float)(((rand()/32767.0)*2-1)/2); //赋值-0.5-0.5的小数
}
}
d = new float[HN]; //隐层偏置向量
if (NULL == d)
{
cerr << "隐层偏置向量分配失败" << endl;
exit(1);
}
a = new float[HN]; //隐层输出向量
if (NULL == a)
{
cerr << "隐层输出向量分配失败" << endl;
exit(1);
}
for (i=0; i<HN; i++)
{
d[i] = (float)(((rand()/32767.0)*2-1)/2); //赋值-0.5-0.5的小数
a[i] = 0;
}
}
CHidden::~CHidden()
{
//释放权值矩阵H、向量a,d
int i;
for (i=0; i<HN; i++) //释放H
{
delete [] H[i];
H[i] = NULL;
}
delete []H;
H = NULL;
delete []d; //释放d
delete []a; //释放a
d = NULL;
a = NULL;
}
版权声明:本文为qq_44943639原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。