word2vec原理与实现「建议收藏」

word2vec原理与实现「建议收藏」定义word2vec是一种把词转到某种向量空间的方法,在新的向量空间,词之间的相互关系,上下文关系都以某种程度被表征出来。方法词向量的转换方法有两种:CBOW(Continounsbagsofwords)和Skip-gram。以下图示为CBOW的网络结构图上图中的x1,x2,….Xc代表的是源码中的context向量中的每个单词,这个上下文的窗口大小对每个词都是随…

大家好,又见面了,我是你们的朋友全栈君。

定义

word2vec是一种把词转到某种向量空间的方法,在新的向量空间,词之间的相互关系,上下文关系都以某种程度被表征出来。

方法

词向量的转换方法有两种: CBOW(Continouns bags of words)和Skip-gram。
以下图示为CBOW的网络结构图
CBOW
上图中的x1,x2,….Xc代表的是源码中的context向量中的每个单词,这个上下文的窗口大小对每个词都是随机取值的。

源码解读

这里选取一个开源实现代码:Word2vec GitHub code
训练流程:

  1. 加载文件,初始化词汇表
  2. 初始化神经网络和霍夫曼树
  3. 多进程训练
    1. 遍历文档每一行,为每行生成词索引向量
      1. 根据window大小配置该词的上下文context
      2. 输入NN训练

训练核心算法:


def train(fi, fo, cbow, neg, dim, alpha, win, min_count, num_processes, binary):
    # Read train file to init vocab
    vocab = Vocab(fi, min_count)

    # Init net
    syn0, syn1 = init_net(dim, len(vocab))

    global_word_count = Value('i', 0)
    table = None
    if neg > 0:#默认参数是5
        print 'Initializing unigram table'
        table = UnigramTable(vocab)
    else: #没有负样本,使用hierarchical softmax
        print 'Initializing Huffman tree'
        vocab.encode_huffman()

    # Begin training using num_processes workers
    t0 = time.time()
    pool = Pool(processes=num_processes, initializer=__init_process,
                initargs=(vocab, syn0, syn1, table, cbow, neg, dim, alpha,
                          win, num_processes, global_word_count, fi))
    pool.map(train_process, range(num_processes))
    t1 = time.time()
    print
    print 'Completed training. Training took', (t1 - t0) / 60, 'minutes'

    # Save model to file
    save(vocab, syn0, fo, binary)

def train_process(pid):
    # Set fi to point to the right chunk of training file
    #因为是多进程处理数据,所以根据进程号做好数据块划分
    start = vocab.bytes / num_processes * pid
    end = vocab.bytes if pid == num_processes - 1 else vocab.bytes / num_processes * (pid + 1)
    fi.seek(start)
    #print 'Worker %d beginning training at %d, ending at %d' % (pid, start, end)

    alpha = starting_alpha

    word_count = 0
    last_word_count = 0
    #遍历数据块
    while fi.tell() < end:
        line = fi.readline().strip()
        # Skip blank lines
        if not line:
            continue

        # 为一行句子初始化索引向量
        sent = vocab.indices(['<bol>'] + line.split() + ['<eol>'])
        #遍历一句话中的每个词
        for sent_pos, token in enumerate(sent):
            if word_count % 10000 == 0:
                global_word_count.value += (word_count - last_word_count)
                last_word_count = word_count

                # 更新alpha值
                alpha = starting_alpha * (1 - float(global_word_count.value) / vocab.word_count)
                if alpha < starting_alpha * 0.0001: alpha = starting_alpha * 0.0001

                # Print progress info
                sys.stdout.write("\rAlpha: %f Progress: %d of %d (%.2f%%)" %
                                 (alpha, global_word_count.value, vocab.word_count,
                                  float(global_word_count.value) / vocab.word_count * 100))
                sys.stdout.flush()

            # Randomize window size, where win is the max window size
            #随机初始化一个窗口大小
            current_win = np.random.randint(low=1, high=win+1)
            context_start = max(sent_pos - current_win, 0)
            context_end = min(sent_pos + current_win + 1, len(sent))
            #构造输入的上下文向量[x1,x2,...x_c]
            context = sent[context_start:sent_pos] + sent[sent_pos+1:context_end] # Turn into an iterator?

            # CBOW
            if cbow:
                # Compute neu1
                #对上下文单词的词向量求均值做为输入层
                neu1 = np.mean(np.array([syn0[c] for c in context]), axis=0)
                assert len(neu1) == dim, 'neu1 and dim do not agree'

                # Init neu1e with zeros
                neu1e = np.zeros(dim)

                # Compute neu1e and update syn1
                #先处理target向量,也就是标签向量
                if neg > 0:
                    classifiers = [(token, 1)] + [(target, 0) for target in table.sample(neg)]
                else:
                    #把词汇表中的该单词对应的索引和标签向量压成标签对
                    classifiers = zip(vocab[token].path, vocab[token].code)
                for target, label in classifiers:
                    z = np.dot(neu1, syn1[target])
                    p = sigmoid(z)
                    g = alpha * (label - p)
                    #计算反向回传的值
                    neu1e += g * syn1[target] # Error to backpropagate to syn0
                    #更新输出层值
                    syn1[target] += g * neu1  # Update syn1

                # Update syn0
                for context_word in context:
                    syn0[context_word] += neu1e

            # Skip-gram
            else:
                for context_word in context:
                    # Init neu1e with zeros
                    neu1e = np.zeros(dim)

                    # Compute neu1e and update syn1
                    if neg > 0:
                        classifiers = [(token, 1)] + [(target, 0) for target in table.sample(neg)]
                    else:
                        classifiers = zip(vocab[token].path, vocab[token].code)
                    for target, label in classifiers:
                        z = np.dot(syn0[context_word], syn1[target])
                        p = sigmoid(z)
                        g = alpha * (label - p)
                        neu1e += g * syn1[target]              # Error to backpropagate to syn0
                        syn1[target] += g * syn0[context_word] # Update syn1

                    # Update syn0
                    syn0[context_word] += neu1e

            word_count += 1

    # Print progress info
    global_word_count.value += (word_count - last_word_count)
    sys.stdout.write("\rAlpha: %f Progress: %d of %d (%.2f%%)" %
                     (alpha, global_word_count.value, vocab.word_count,
                      float(global_word_count.value)/vocab.word_count * 100))
    sys.stdout.flush()
    fi.close()

这里边用到了一个很重要的类Vocab,该类里边将会负责对词汇进行霍夫曼树编码:

#这个类用来存储霍夫曼树
class VocabItem:
    def __init__(self, word):
        self.word = word
        self.count = 0
        self.path = None # Path (list of indices) from the root to the word (leaf)
        self.code = None # Huffman encoding

class Vocab:
    def __init__(self, fi, min_count):
        vocab_items = []
        vocab_hash = {}
        word_count = 0
        fi = open(fi, 'r')

        # Add special tokens <bol> (beginning of line) and <eol> (end of line)
        for token in ['<bol>', '<eol>']:
            vocab_hash[token] = len(vocab_items)
            vocab_items.append(VocabItem(token))

        for line in fi:
            tokens = line.split()
            for token in tokens:
                if token not in vocab_hash:
                    vocab_hash[token] = len(vocab_items)
                    vocab_items.append(VocabItem(token))

                #assert vocab_items[vocab_hash[token]].word == token, 'Wrong vocab_hash index'
                vocab_items[vocab_hash[token]].count += 1
                word_count += 1

                if word_count % 10000 == 0:
                    sys.stdout.write("\rReading word %d" % word_count)
                    sys.stdout.flush()

            # Add special tokens <bol> (beginning of line) and <eol> (end of line)
            vocab_items[vocab_hash['<bol>']].count += 1
            vocab_items[vocab_hash['<eol>']].count += 1
            word_count += 2

        self.bytes = fi.tell()
        self.vocab_items = vocab_items         # List of VocabItem objects
        self.vocab_hash = vocab_hash           # Mapping from each token to its index in vocab
        self.word_count = word_count           # Total number of words in train file

        # Add special token <unk> (unknown),
        # merge words occurring less than min_count into <unk>, and
        # sort vocab in descending order by frequency in train file
        self.__sort(min_count)

        #assert self.word_count == sum([t.count for t in self.vocab_items]), 'word_count and sum of t.count do not agree'
        print 'Total words in training file: %d' % self.word_count
        print 'Total bytes in training file: %d' % self.bytes
        print 'Vocab size: %d' % len(self)

    def __getitem__(self, i):
        return self.vocab_items[i]

    def __len__(self):
        return len(self.vocab_items)

    def __iter__(self):
        return iter(self.vocab_items)

    def __contains__(self, key):
        return key in self.vocab_hash

    def __sort(self, min_count):
        tmp = []
        tmp.append(VocabItem('<unk>'))
        unk_hash = 0

        count_unk = 0
        for token in self.vocab_items:
            if token.count < min_count:
                count_unk += 1
                tmp[unk_hash].count += token.count
            else:
                tmp.append(token)

        tmp.sort(key=lambda token : token.count, reverse=True)

        # Update vocab_hash
        vocab_hash = {}
        for i, token in enumerate(tmp):
            vocab_hash[token.word] = i

        self.vocab_items = tmp
        self.vocab_hash = vocab_hash

        print
        print 'Unknown vocab size:', count_unk

    def indices(self, tokens):
        return [self.vocab_hash[token] if token in self else self.vocab_hash['<unk>'] for token in tokens]

    def encode_huffman(self):
        # Build a Huffman tree
        vocab_size = len(self)
        count = [t.count for t in self] + [1e15] * (vocab_size - 1)
        parent = [0] * (2 * vocab_size - 2)
        binary = [0] * (2 * vocab_size - 2)

        pos1 = vocab_size - 1
        pos2 = vocab_size

        for i in xrange(vocab_size - 1):
            # Find min1
            if pos1 >= 0:
                if count[pos1] < count[pos2]:
                    min1 = pos1
                    pos1 -= 1
                else:
                    min1 = pos2
                    pos2 += 1
            else:
                min1 = pos2
                pos2 += 1

            # Find min2
            if pos1 >= 0:
                if count[pos1] < count[pos2]:
                    min2 = pos1
                    pos1 -= 1
                else:
                    min2 = pos2
                    pos2 += 1
            else:
                min2 = pos2
                pos2 += 1

            count[vocab_size + i] = count[min1] + count[min2]
            parent[min1] = vocab_size + i
            parent[min2] = vocab_size + i
            binary[min2] = 1

        # Assign binary code and path pointers to each vocab word
        root_idx = 2 * vocab_size - 2
        for i, token in enumerate(self):
            path = [] # List of indices from the leaf to the root
            code = [] # Binary Huffman encoding from the leaf to the root

            node_idx = i
            while node_idx < root_idx:
                if node_idx >= vocab_size: path.append(node_idx)
                code.append(binary[node_idx])
                node_idx = parent[node_idx]
            path.append(root_idx)

            # These are path and code from the root to the leaf
            token.path = [j - vocab_size for j in path[::-1]]
            token.code = code[::-1]
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/145977.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 2020美赛A题解题方法

    2020美赛A题解题方法题目:问题A:向北移动全球海洋温度影响某些海洋生物的栖息地质量。当温度变化太大,它们无法继续繁荣时,这些物种就会迁移到其他更适合它们现在和未来生活和繁殖成功的栖息地。其中一个例子就是美国缅因州的龙虾种群,它们正缓慢地向北迁移到加拿大,那里的海洋温度较低,为它们提供了更合适的栖息地。这种地理种群的转移可能会严重影响依赖海洋生物稳定性的公司的生计。您的团队已被苏格兰北大西洋渔业管理协会聘请为顾问…

    2022年6月1日
    33
  • Tomcat报错—Invalid keystore format ,tomcat启动报错[通俗易懂]

    今天在修改了一些代码,然后重新放到服务器上,启动服务器的时候报这个错误!我就很纳闷,修改的文件里面根本就没有涉及到tomcat配置文件的内容怎么会出现这样子内容呢?想了很久也网上百度了很多,很多博客提供的解答都没有解决我的问题,然后我们主管过来看了一会,给出了指导性的建议,去看tomcat下的conf/server.xml 文件。然后逐步排查。

    2022年2月24日
    82
  • 最新版本交易猫钓鱼源码完整版

    最新版本交易猫钓鱼源码完整版源码介绍:数据库:config/Conn.php然后导入数据库就行后台admin密码123456网盘下载地址:https://zijiewangpan.com/2bxUP1wqpmQ图片:

    2022年8月24日
    6
  • acwing-2325. 有向图破坏(最小割之最小权覆盖集)「建议收藏」

    acwing-2325. 有向图破坏(最小割之最小权覆盖集)「建议收藏」爱丽丝和鲍勃正在玩以下游戏。首先,爱丽丝绘制一个 N 个点 M 条边的有向图。然后,鲍勃试图毁掉它。在每一步操作中,鲍勃都可以选取一个点,并将所有射入该点的边移除或者将所有从该点射出的边移除。已知,对于第 i 个点,将所有射入该点的边移除所需的花费为 W+i,将所有从该点射出的边移除所需的花费为 W−i。鲍勃需要将图中的所有边移除,并且还要使花费尽可能少。请帮助鲍勃计算最少花费。输入格式第一行包含 N 和 M。第二行包含 N 个正整数,第 i 个为 W+i。第三行包含 N 个正整数,第.

    2022年8月11日
    3
  • pycharm安装opencv2_python opencv 教程

    pycharm安装opencv2_python opencv 教程上一篇博文已经安装了Anaconda3和tensorflow,这次来安装pycharm和opencv3.4.0一、安装pycharm1、pycharm下载地址 直接下载Community版,免费和开源。以后不够用时,再想办法再说吧。2、一路点下去即可。3、有一个选择让我疑惑,记录下:选择"64位安装"(根据自己的系统来选择),并勾上“.py”二、安装opencv1、python的opencv库下…

    2022年8月28日
    3
  • nginx配置多个server

    nginx配置多个server server{listen80;server_namelocalhost; rootC:\Users\Administrator\Desktop\dist;location/{ try_files$uri/index.html;} location/api/{ proxy_set_headerHost$host; proxy_set_

    2022年4月4日
    579

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号