Feedforward Neural Network Language Model(NNLM)c++核心代碼實現

來源:互聯網
上載者:User

標籤:nnlm   深度學習   神經網路   語言模型   自然語言處理   

本文來自CSDN部落格,轉載請註明出處:http://blog.csdn.net/a635661820/article/details/44730507
參考文獻: A Neural Probabilistic Language Model
   參照我另一篇NNLM學習介紹的部落格, 這一篇是對NNLM的簡要實現, 自己簡化了一些,輸入層到輸出層沒有串連(加上直連邊的真在原論文中沒有明顯的提高),並且沒有並行演算法。下面貼上自己的一些核心代碼。總體來說,我用了c++物件導向來設計該演算法,大概分為6個類,如下: 

  1. CLex類:用來處理文本的一個類
  2. CInput類:輸入層類, 包含相關量
  3. CHidden類:隱層類,包含相關量
  4. COutput類:輸出層類,包含相關變數
  5. CAlgothrim類:演算法類,涉及前向演算法以及反向更新演算法,將前面3個類串起來
  6. CForeCast類:測試類別,訓練好模型後對模型進行測試的相關操作

關於網路中的各個手動設定的參數在macroDefinition.h,包括隱層神經元個數、特徵向量維度等。這裡的附帶的代碼只貼出了核心相關代碼,即CInput, CHidden, COutput, CAlgothrim的核心代碼
網路手動設定的參數在macroDefinition.h裡面,定義為宏,初始設定是按照論文中設定的:
<span style="font-family:Microsoft YaHei;font-size:14px;">//以下是各個變數的宏定義,留做介面,方便調試#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個詞語</span>

在Input.h檔案裡面,類的結構:
<span style="font-family:Microsoft YaHei;font-size:14px;">//輸入層的結構定義class CInput  {public:bool LoadCorpusfile(char *corpusfile);//將語料庫檔案讀入bool Computex(int k);//計算一個句子輸入單詞的x向量string GetWordByID(unsigned long id);//根據ID返回單詞unsigned long GetIDByWord(string word);//根據單詞返回其IDbool NextSentence(int i);//從語料庫檔案中讀入下一個句子CInput();//產生詞典、二維矩陣virtual ~CInput();//釋放單詞映射矩陣,釋放詞典CLex *pChnLex;//指向中文詞典的指標float **vec;//輸入單詞對應的映射矩陣vector<string> sentence;//一行句子vector<string> corpus;//語料庫float *x;//儲存輸入層單詞的特徵向量long unsigned expectedID;//訓練句子時下一個輸出單詞ID};</span>

類的實現在Input.cpp裡面:
<span style="font-family:Microsoft YaHei;font-size:14px;">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;//釋放xx = 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){//根據單詞返回其IDreturn 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個單詞idif (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;}</span>

隱層的結構定義在Hidden.h裡面:
<span style="font-family:Microsoft YaHei;font-size:14px;">//隱層的結構定義class CHidden  {public:CHidden();//產生輸入層到隱層的權值矩陣,隱層神經元的輸出向量,偏置向量virtual ~CHidden();//釋放權值矩陣H、向量a,dvoid OutHidden(float *x);//計算隱層的輸出float **H;//由輸入層到隱層的權值矩陣(變數名按照論文中,方便對照)float *a;//隱層神經元的輸出向量float *d;//隱層的偏置向量};</span>

隱層的實現在Hidden.cpp:
<span style="font-family:Microsoft YaHei;font-size:14px;">CHidden::CHidden(){//建構函式//產生輸入層到隱層的權值矩陣//產生隱層神經元的輸出向量//產生隱層的偏置向量H = new float *[HN];//輸入層到隱層的權值矩陣Hif (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,dint i;for (i=0; i<HN; i++)//釋放H{delete [] H[i];H[i] = NULL;}delete []H;H = NULL;delete []d;//釋放ddelete []a;//釋放ad = NULL;a = NULL;}void CHidden::OutHidden(float *x){//計算隱層輸出//x是由輸入層傳過來的輸入層特徵向量int i, j;float zigma, o;for (i=0; i<HN; i++)//根據o<-d + Hx  a<-tanh(o)計算a{zigma = 0.0;for (j=0; j<M*N; j++){zigma += x[j] * H[i][j];}o = d[i] + zigma;a[i] = tanh(o);}}</span>

輸出層的結構定義在Output.h:
<span style="font-family:Microsoft YaHei;font-size:14px;">//輸出層的結構定義class COutput  {public:void Initialize(long unsigned lexiconSize);//產生隱層到輸出層的權值矩陣,偏置向量、輸出向量、輸出單詞機率向量void SoftMax(void);//SotfMax迴歸層,進行歸一化,計算機率void Output(float *a, long unsigned expectedID);//計算輸出層的輸出COutput();virtual ~COutput();//釋放權值矩陣U、向量b,y,pfloat **U;//隱層到輸出層的權值矩陣float *y;//輸出層的輸出float *b;//輸出層神經元的偏置向量float *p;//輸出單詞機率向量long unsigned VSize;//語料庫詞典的大小float L;//累加的對數機率};</span>

輸出層的實現在Output.cpp檔案裡面:
<span style="font-family:Microsoft YaHei;font-size:14px;">COutput::COutput(){//do nothing}COutput::~COutput(){//釋放權值矩陣U、向量b,y,plong unsigned i;for (i=1; i<VSize; i++)//釋放H{delete [] U[i];U[i] = NULL;}delete []U;U = NULL;delete []b;//釋放ddelete []y;//釋放adelete []p;//釋放pb = NULL;y = NULL;p = NULL;}void COutput::Output(float *a, long unsigned expectedID){//計算輸出層的輸出//a,expectedID分別是隱層傳的輸出向量,句子的輸出單詞IDlong unsigned i;int j;float zigma;for (i=1; i<VSize; i++)//根據y<-b + Ua  {zigma = 0.0;for (j=0; j<HN; j++){zigma += a[j] * U[i][j];}y[i] = b[i] + zigma;p[i] = exp(y[i]);//即計算e^yi}SoftMax();//SotfMax迴歸計算機率L += log(p[expectedID]);//返回以e為底的對數值}void COutput::SoftMax(){//SotfMax迴歸層,進行歸一化,計算機率float sum = 0;long unsigned i;for (i=1; i<VSize; i++)//計算SoftMax分母{sum += p[i];}for (i=1; i<VSize; i++)//計算每個單詞的機率{p[i] /= sum;}}void COutput::Initialize(unsigned long lexiconSize){//初始化輸出層函數,為了程式構架好看方便,沒放在建構函式裡面//產生隱層到輸出層的權值矩陣//輸出層偏置向量、輸出向量、輸出單詞機率向量VSize = lexiconSize;//儲存語料庫詞典大小U = new float *[lexiconSize];//隱層到輸出層的權值矩陣U,讓其下標從1開始與詞典ID對應if (NULL == U){cerr << "隱層到輸出層的權值矩陣U分配失敗!" << endl;exit(0);}long unsigned int i, j;//i盡量為lVsizeg型,與詞典類型大小對應for (i=1; i<lexiconSize; i++){U[i] = new float[HN];if (NULL == U[i]){cerr << "隱層到輸出層的權值矩陣U分配失敗!" << endl;exit(0);}}for (i=1; i<lexiconSize; i++)//初始化矩陣U{for (j=0; j<HN; j++){U[i][j] = (float)(((rand()/32767.0)*2-1)/2);//賦值-0.5-0.5的小數}}b = new float[lexiconSize];//輸出層偏置向量,讓其下標從1開始與詞典ID對應if (NULL == b){cerr << "輸出層偏置向量分配失敗" << endl;exit(1);}y = new float[lexiconSize];//輸出層輸出向量,讓其下標從1開始與詞典ID對應if (NULL == y){cerr << "輸出層輸出向量分配失敗" << endl;exit(1);}p = new float[lexiconSize];//輸出層輸出向量,讓其下標從1開始與詞典ID對應if (NULL == p){cerr << "輸出層機率向量分配失敗" << endl;exit(1);}for (i=1; i<lexiconSize; i++)//初始化輸出矩陣偏置向量、輸出向量、機率向量{b[i] = (float)(((rand()/32767.0)*2-1)/2);//賦值-0.5-0.5的小數p[i] = 0;y[i] = 0;}p[0] = 0;//對後續排序起作用L = 0;//累加對數機率清0}</span>

演算法類的結構定義在Algothrim.h:
<span style="font-family:Microsoft YaHei;font-size:14px;">//演算法層的結構定義class CAlgothrim  {public:bool SaveParameters();//儲存網路全部參數到檔案weight.txt中void PrintOverInfo(long unsigned count, int sentNum);//提示訓練結束void PrintStartInfo(char *corpusfile);//列印該神經網路版本訓練的相關資訊bool WriteResult();//將輸出層的機率向量結果輸出到檔案bool Run(char *corpusFile);//模型訓練架構函數void Initialize();//產生中間梯度向量void UpdateAll();//反向演算法更新網路所有參數void PrintParametersAfterUpate();//在反向更新參數後列印網路所有參數void UpdateInput();//更新輸入層vecvoid UpdateHidden();//更新隱層的H,dvoid PrintParametersBeforeUpdate();//在反向更新之前輸出整個網路的所有參數void UpdateOut();//更新輸出層的參數U,bCAlgothrim(CInput *pIn, CHidden *pHi, COutput *pOu);//3個參數分別表示輸入層、隱層、輸出層的指標virtual ~CAlgothrim();//釋放偏導向量CInput *pInput;//指向輸入層的指標CHidden *pHidden;//指向隱層的指標COutput *pOut;//指向輸出層的指標float *Lpa;//L對a的偏導向量float *Lpx;//L對x的偏導向量float *Lpy;//L對y的偏導向量};</span>

演算法類的實現在Algothrim.cpp,這裡是程式的核心部分:
<span style="font-family:Microsoft YaHei;font-size:14px;"><pre name="code" class="cpp">CAlgothrim::CAlgothrim(CInput *pIn, CHidden *pHi, COutput *pOu){//建構函式//3個參數分別表示輸入層、隱層、輸出層的指標//獲得各層指標pInput = pIn;pHidden = pHi;pOut = pOu;}CAlgothrim::~CAlgothrim(){//釋放偏導向量delete []Lpa;//釋放LpaLpa = NULL;delete []Lpx;//釋放LpxLpx = NULL;delete []Lpy;//釋放LpyLpy = NULL;}void CAlgothrim::UpdateOut(){//更新輸出層的參數U,b//計算Lpylong unsigned Vsize = pInput->pChnLex->ulVocSizeC;//詞典大小long unsigned j;long unsigned wt = pInput->expectedID;//期望單詞IDint k;memset(Lpa, 0, HN*sizeof(float));//Lpa置0for (j=1; j<Vsize; j++)//進行更新{if (j == wt)//根據Lpy<-1(j==wt) - pj 計算Lpy{Lpy[j] = 1 - pOut->p[j];}else{Lpy[j] = 0 - pOut->p[j];}pOut->b[j] += EPSILON*Lpy[j];//根據bj <- bj + ε*Lpy更新bfor (k=0; k<HN; k++)//計算Lpa(累加),根據Lpa<-Lpa+Uj*Lpyj{Lpa[k] += Lpy[j]*pOut->U[j][k];}for (k=0; k<HN; k++)//根據Uj<-Uj+ε*Lpy*a更新U{pOut->U[j][k] += EPSILON*Lpy[j]*pHidden->a[k];}}}void CAlgothrim::UpdateHidden(){//更新隱層的H,dfloat Lpo[HN] = {0.0};//L對o的偏導向量int k;for (k=0; k<HN; k++)//計算Lpo 根據lpo <- (1-ak2)lpak{Lpo[k] = (1 - pHidden->a[k]*pHidden->a[k]) * Lpa[k];}//累加計算Lpxmemset(Lpx, 0, N*M*sizeof(float));//置0int i;float temp;for (i=0; i<M*N; i++)//根據Lpx -< Lpx + H(轉置)*Lpo 累加計算Lpx{temp = 0.0;for (k=0; k<HN; k++){temp += pHidden->H[k][i] * Lpo[k];}Lpx[i] = temp;}//更新dfor (k=0; k<HN; k++)//根據d <- d + ε*Lpo更新d{pHidden->d[k] += EPSILON * Lpo[k];}//更新Hfor (i=0; i<HN; i++)//根據H <- H + ε*Lpo*x(轉置){for (k=0; k<M*N; k++){pHidden->H[i][k] += EPSILON*Lpo[i]*pInput->x[k];}}}void CAlgothrim::UpdateInput(){//更新輸入層vecint k, i;long unsigned id;for (i=0; i<N; i++){id = pInput->GetIDByWord(pInput->sentence[i]);//得到訓練句子的第i個單詞IDfor (k=0; k<M; k++){pInput->vec[id][k] += EPSILON * Lpx[i*M+k];}}}void CAlgothrim::UpdateAll(){//反向演算法更新網路所有參數UpdateOut();//更新輸出層UpdateHidden();//更新隱層UpdateInput();//更新輸入層}void CAlgothrim::Initialize(){//產生中間梯度向量long unsigned Vsize = pInput->pChnLex->ulVocSizeC;//得到詞典大小Lpa = new float[HN];//L對a的偏導向量if (NULL == Lpa){cerr << "L對a的偏導向量分配失敗!" << endl;exit(1);}memset(Lpa, 0, HN*sizeof(float));//置0Lpx = new float[N*M];//L對x的偏導向量if (NULL == Lpx){cerr << "L對x的偏導向量分配失敗!" << endl;exit(1);}memset(Lpx, 0, N*M*sizeof(float));//置0Lpy = new float[Vsize];//L對y的偏導向量,下標從1開始與id對應if (NULL == Lpy){cerr << "L對y的偏導向量分配失敗!" << endl;exit(1);}memset(Lpy, 0, (Vsize)*sizeof(float));//置0}bool CAlgothrim::Run(char *corpusFile){//模型訓練架構函數//參數為語料庫檔案名稱int i = 0;//控制語料庫句子的循環訓練int k = 0;//控制一個句子中循環訓練unsigned int t = 0;//控制整個語料庫的訓練次數int len;//記錄要訓練句子的長度long unsigned count = 0;//記錄網路更新次數PrintStartInfo(corpusFile);//列印網路功能資訊pInput->LoadCorpusfile(corpusFile);//載入語料庫檔案pOut->Initialize(pInput->pChnLex->ulVocSizeC);//初始化輸出層Initialize();//產生中間梯度向量while (true){t++;//記錄對語料庫訓練次數pOut->L = 0;//loge(Pwt) L是累加的對數機率for (i=0; i<pInput->corpus.size(); i++)//遍曆語料庫的句子進行訓練{//神經網路前饋運算pInput->NextSentence(i);//從語料庫中讀取第i個句子做訓練len = pInput->sentence.size();//讀入的句子長度for (k=0; k+N<len; k++)//對一個句子反覆訓練N-len次{pInput->Computex(k);//計算輸入層的x的特徵向量pHidden->OutHidden(pInput->x);//計算隱層輸出pOut->Output(pHidden->a, pInput->expectedID);//計算輸出層//PrintParametersBeforeUpdate();//列印未更新前的參數//反向演算法更新參數UpdateOut();//更新輸出層參數UpdateHidden();//更新隱層參數UpdateInput();//更新輸入層參數//PrintParametersAfterUpate();//列印更新後的參數count++;}}cout << t << "次訓練,累加對數和機率: " << pOut->L << endl;if (pOut->L > CONVERGE_THRESHOLD)//大於手動設定的收斂閾值後退出迴圈{break;}}SaveParameters();//儲存參數PrintOverInfo(count, pInput->corpus.size());return true;}</span>

Feedforward Neural Network Language Model(NNLM)c++核心代碼實現

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.