#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<math.h>
#include<pthread.h>
#define MAX_STRING 100 //每次字符最大长度
#define EXP_TABLE_SIZE 1000 //预计算好的exp分为1000
#define MAX_EXP 6 //最大的exp边界,边界外为0或者1
#define MAX_SENTENCT_LENGTH 1000 //每个句子的最大长度, 所以一行并不代表一个句子
#define MAX_CODE_LENGTH 40 //最大码值长度
const int vocab_hash_size = 30000000; //最大hash值
typedef float real; //定义float类型的精度
//定义一个结构体,每个词携带的信息
struct vocab_word{
long long cn; //词出现的频率
int *point; //
char *word, *code, codelen; //单词,huffman编码值,编码长度
};
//改进点,我们从properties文件读取配置,而不是直接通过argv传参数,因为这样实在是太麻烦了
//主要书写的要求,注释以#开头,即该行第一个字符一定是#;而非注释行一定是按照
//key=value的形式,不能有多余的空格!
struct config_item{
char key[20]; //key值
char value[60]; //value值
}items[100];
int config_size = 0; //参数个数
char train_file[MAX_STRING], output_file[MAX_STRING]; //语料库文件,输出文件路径
char save_vocab_file[MAX_STRING], read_vocab_file[MAX_STRING];
struct vocab_word *vocab; //整个词汇中词的信息
//模型训练需要配置的参数
int binary = 0, cbow = 1, debug_mode = 2, window = 5, min_count = 5, num_threads = 12, min_reduce = 1;
int *vocab_hash; //存的值是词在词汇表中的pos, 下标对应词的hash值
long long vocab_max_size = 1000, vocab_size = 0, layer1_size = 100; //最大词汇数,实际词汇数,词向量维数
long long train_words = 0, word_count_actual = 0, iter = 5, file_size = 0, classes = 0; //语料库中总词数,当前训练词数,迭代次数,文件大小,
real alpha = 0.025, starting_alpha, sample = 1e-3;
real *syn0, *syn1, *syn1neg, *expTable; //需要训练的词向量,huffmax模型下softmax参数, 负采样模式下softmax参数, 与计算的exp表
clock_t start;
int hs = 0, negative = 5; //训练模式,负采样个数
const int table_size = 1e8; //负采样将每个词按词频切分,切分到多大的表中
int *table;
//生成负采样的概率表
void InitUnigramTable() {
int a, i;
double train_words_pow = 0;
double d1, power = 0.75;
table = (int *)malloc(table_size * sizeof(int));
for(a = 0; a < vocab_size; a++)
train_words_pow += pow(vocab[a].cn, power); //所有词频率的0.75次幂之和
i = 0;
d1 = pow(vocab[i].cn, power) / train_words_pow;
for(a = 0; a < table_size; a++){
table[a] = i;
if(a / (double)table_size > d1){ //每个词在table中占的分量和它词频占总词频的分量是一样的
i++;
d1 += pow(vocab[i].cn, power) / train_words_pow;
}
if(i >= vocab_size)
i = vocab_size - 1;
}
}
//从文件中读取单个单词,假设空格, tab, EOL作为单词边界
void ReadWord(char *word, FILE *fin){
int a = 0, ch;
while(!feof(fin)){ //未到句子边界
ch = fgetc(fin); //读取一个字符
if(ch == 13) continue; //回车直接跳过,换行符要考虑
if((ch == ' ') || (ch == '\t') || (ch == '\n')){
if (a > 0){
if(ch == '\n') ungetc(ch, fin); //将字符回退回去
break;
}
if (ch == '\n'){
strcpy(word, (char*)"</s>"); //如果第一个是空格,就填</s>
return;
}
else continue;
}
word[a] = ch;
a++;
if(a >= MAX_STRING - 1) a--; //不能超过最大字符长度
}
word[a] = 0; //最后一个字符是'\0'
}
//返回单词对应的hash值
int GetWordHash(char *word) {
unsigned long long a, hash = 0;
for (a = 0; a < strlen(word); a++) //对单词中每个字符进行hash
hash = hash * 257 + word[a];
hash = hash % vocab_hash_size;
return hash;
}
//返回一个词在词汇表中对应的位置; 如果找不到该词,那么返回-1
int SearchVocab(char *word){
unsigned int hash = GetWordHash(word); //先找到hash值
while(1){
if (vocab_hash[hash] == -1) //表示没有这个词,因为它都没有对应的hash值
return -1;
if(!strcmp(word, vocab[vocab_hash[hash]].word)) //如果和目标词相等
return vocab_hash[hash];
hash = (hash + 1) % vocab_hash_size; //开方定址法求对应的hash值
}
}
//读取一个词,并返回它在词汇表中的pos
int ReadWordIndex(FILE *fin){
char word[MAX_STRING];
ReadWord(word, fin); //每次读到一个词
if(feof(fin)) return -1;
return SearchVocab(word);
}
//将一个词加入到词汇表中.我们要求每个词的信息,添加到结构体中
int AddWordToVocab(char *word){
unsigned int hash, length = strlen(word) + 1;
if(length > MAX_STRING)
length = MAX_STRING;
vocab[vocab_size].word = (char *)calloc(length, sizeof(char));
strcpy(vocab[vocab_size].word, word); //词复制
vocab[vocab_size].cn = 0; //初始化词频为0
vocab_size++; //词汇表中词的数量加1
//因为一开始我们词汇表大小有限,当发现已经到达分配空间的某个百分比时,动态扩大空间
if(vocab_size + 2 >= vocab_max_size){
vocab_max_size += 1000;
//重新分配空间,之前的信息会保留下来,参考realloc函数
vocab = (struct vocab_word *)realloc(vocab, vocab_max_size * sizeof(struct vocab_word));
}
hash = GetWordHash(word);
while(vocab_hash[hash] != -1)
hash = (hash + 1) % vocab_hash_size;
vocab_hash[hash] = vocab_size -1; //以这个词计算hash值,开放地址法,然后在该hash值的pos处存该词在词汇表对应的pos
return vocab_size - 1;
}
//定义排序词汇表的比较函数,降序?
int VocabCompare(const void *a, const void *b){
return ((struct vocab_word *)b)->cn - ((struct vocab_word *)a)->cn;
}
//对词汇表进行排序
void SortVocab(){
int a, size;
unsigned int hash;
//对词汇表进行排序,保证</s>在第一个位置
qsort(&vocab[1], vocab_size - 1, sizeof(struct vocab_word), VocabCompare);
//排完序之后,顺序大乱了,我们得重新计算hash值和词汇表中pos的对应关系,类似于一个hashmap
for (a = 0; a < vocab_hash_size; a++)
vocab_hash[a] = -1;
size = vocab_size;
train_words = 0;
for(a = 0; a < size; a++){
//如果单词出现的频率小于最小频率,则会被剪切掉, a等于表示</S>
if ((vocab[a].cn < min_count) && (a != 0)){
vocab_size--;
free(vocab[a].word);
}
else{
//重新计算hash值,因为位置被打乱了
hash = GetWordHash(vocab[a].word);
while (vocab_hash[hash] != -1) //开方定址法
hash = (hash + 1) % vocab_hash_size;
vocab_hash[hash] = a; //建立hash值和在词汇表位置的映射关系
train_words += vocab[a].cn; //总的需要训练的词的总频率数
}
}
//重新分配地址,因为很多被free了
vocab = (struct vocab_word *)realloc(vocab, (vocab_size + 1) * sizeof(struct vocab_word));
//为二叉树编码预先分配好空间
for(a = 0; a < vocab_size; a++){
vocab[a].code = (char *)calloc(MAX_CODE_LENGTH, sizeof(char));
vocab[a].point = (int *)calloc(MAX_CODE_LENGTH, sizeof(int));
}
}
//减小词汇表的大小,通过移除不常用的单词, min)reduce在每次调用完这个函数之后就会加1
void ReduceVocab() {
int a, b
评论0