1. 程式人生 > >針對Minkolov釋出的“根據詞向量計算目標單詞的N近鄰詞彙”原始碼的分析

針對Minkolov釋出的“根據詞向量計算目標單詞的N近鄰詞彙”原始碼的分析

最近在研究Minkolov開發的RNNLM toolkit,其主頁(http://www.fit.vutbr.cz/~imikolov/rnnlm/)上掛出來一個“根據生成好的詞向量檔案來計算與使用者輸入的目標單詞最近鄰的top-N個單詞”的小程式(http://www.fit.vutbr.cz/~imikolov/rnnlm/distance.c),讀了一下並對程式碼做了些註釋,雖然比較簡單,但還是分享出來,供大家交流~~ 水平有限,難免錯誤和疏漏,還請各位老師多多批評指教,謝謝!
首先貼一張詞向量檔案的樣例圖,有助於對原始碼的理解。其中,檔案第一行為“ 單詞個數 詞向量維度”,後續為“詞彙 詞向量”。下圖展示的詞向量檔案中共有82390個單詞,每個單詞的詞向量維度為80。
詞向量檔案內容示例圖


#include <stdio.h>
#include <string.h>
#include <math.h>
#include <sys/malloc.h> //原始版本是#include<malloc.h>,由於博主本人OS X系統,修改後gcc才能編譯通過

const int max_size=2000;  //最大字串長度
const int N=40; //選取N個距離最接近的單詞
const int max_w=50;  //最大單詞長度

int main(int argc, char **argv)
{
    FILE *f;
    char
st1[max_size], bestw[N][max_size], file_name[max_size]; float dist, len, bestd[N]; int words, size, a, b, c, d; float *M; char *vocab; if (argc<2) { printf("Usage: ./dist <FILE>\nwhere FILE contains word projections\n"); //列印輸出:“用法:./dist <FILE> \n FILE為儲存詞向量的檔案的名稱"
return 0; } strcpy(file_name, argv[1]); //將詞向量檔名賦值給filename變數 f=fopen(file_name, "rb"); //只讀開啟一個二進位制檔案,只允許讀資料 if (f==NULL) {printf("Input file not found\n"); return -1;} //w檔案讀取失敗,退出程式 fscanf(f, "%d", &words); //讀入詞向量檔案中包含的單詞個數 fscanf(f, "%d", &size); //讀入詞向量檔案中每個詞向量的維度 vocab=(char *)malloc(words*max_w*sizeof(char)); //申請用於儲存詞彙表的動態記憶體空間 M=(float *)malloc(words*size*sizeof(float)); //申請用於儲存詞向量的動態記憶體空間(M先用於儲存詞向量,而後更新為每個詞向量的單位向量) if (M==NULL) { //空間申請失敗,退出程式 printf("Cannot allocate memory: %d MB\n", words*size*sizeof(float)/1048576); return -1; } for (b=0; b<words; b++) { // fscanf(f, "%s", &vocab[b*max_w]); //從詞向量檔案中讀入一個單詞,寫到當前詞彙的末尾 for (a=0; a<size; a++) fscanf(f, "%f", &M[a+b*size]); //從詞向量檔案中讀入當前單詞所對應的詞向量,寫到當前M的末尾 len=0; for (a=0; a<size; a++) len+=M[a+b*size]*M[a+b*size]; //計算當前單詞的詞向量的模的平方(向量中每個元素的平方和) len=sqrt(len); //計算當前單詞的詞向量的模 for (a=0; a<size; a++) M[a+b*size]/=len; //計算每個詞向量的單位向量,存於M } for (a=0; a<words*max_w; a++) vocab[a]=toupper(vocab[a]); //將詞彙表中的每個字元都轉換為大寫英文字母 fclose(f); //關閉詞向量檔案 while (1) { for (a=0; a<N; a++) bestd[a]=0; //初始化儲存最近距離的陣列bestd for (a=0; a<N; a++) bestw[a][0]=0; //初始化儲存距離最近單詞的陣列bestw printf("Enter word (EXIT to terminate): "); //輸出提示:輸入待查詢的目標單詞,輸入EXIT退出 scanf("%s", st1); //讀取使用者的輸入 for (a=0; a<strlen(st1); a++) st1[a]=toupper(st1[a]); //將使用者輸入的單詞轉化為大寫字母的形式 if (!strcmp(st1, "EXIT")) break; //如果使用者輸入為EXIT,則退出while迴圈 for (b=0; b<words; b++) if (!strcmp(&vocab[b*max_w], st1)) break; //遍歷詞彙表,找到與輸入詞彙相匹配的單詞時跳出for迴圈;或者直到迴圈結束 if (b==words) //如果前一個迴圈退出時,計數器數值大於詞彙表中的單詞數目,則表示沒有找到使用者輸入的單詞 printf("Word was not found in dictionary\n"); else { //在詞彙表中找到了使用者輸入的單詞,且位於詞彙表的第b位 for (c=0; c<words; c++) { //針對詞彙表中的每個單詞進行查詢 dist=0; //初始化距離為0 for (a=0; a<size; a++) dist+=M[a+b*size]*M[a+c*size]; //計算位於詞彙表第b位的目標詞與當前遍歷到的詞彙表中的第c個單詞的單位詞向量之間的點積,存於dist //針對現有的最近鄰單詞表進行考量(bestd:儲存top-N個最近距離,按距離從小到大進行維護;bestw:儲存top-N個最近鄰單詞) for (a=0; a<N; a++) { if (dist>bestd[a]) { //找到當前dist合適的位置a,並將最近鄰表中的後續元素依次後移一位(從後往前處理) for (d=N-1; d>a; d--) { bestd[d]=bestd[d-1]; strcpy(bestw[d], bestw[d-1]); } bestd[a]=dist; //下兩句將找到的近鄰詞(詞彙表中的第c個)相關資訊賦給最近距離表及最近鄰單詞表 strcpy(bestw[a], &vocab[c*max_w]); break; } } } printf("Closest words:\n"); //輸出“最接近的單詞為:” for (a=0; a<N; a++) printf("%20s\t\t%f\n", bestw[a], bestd[a]); //依次將最接近的40個單詞按照:“單詞 距離”的格式輸出 } } return 0; }