菜菜学懂了word2vec,有代码耶

以上下文词汇预测当前词』架构被称为连续词袋(CBOW),还有另一种架构,刚好反过来,根据当前词推测当前单词可能的前后单词,这种架构就是所谓的Skipgram架构

 负采样:

自然语言处理领域中,判断两个单词是不是一对上下文词(context)与目标词(target),如果是一对,则是正样本,如果不是一对,则是负样本。采样得到一个上下文词和一个目标词,生成一个正样本(positive example),生成一个负样本(negative example),则是用与正样本相同的上下文词,再在字典中随机选择一个单词,这就是负采样(negative sampling)。

比如给定一句话“这是去上学的班车”,则对这句话进行正采样,得到上下文“上”和目标词“学”,则这两个字就是正样本。负样本的采样需要选定同样的“上”,然后在训练的字典中任意取另一个字,“梦”、“目”,这一对就构成负样本。

训练需要正样本和负样本同时存在。

有字向量和词向量,区别在于字数量较小,汉语不同的字就那么多个,但词的数量很多很多,比如天,可以是天空,天气,今天,蓝天等,所以词向量需要先分词且数量庞大,字多用于古诗词等,词多用于小说。

表示方式,举个例子:

今:[1,0,0,......,0]1*50,用一个大小为1*50的矩阵来表示今这个字

今天:[1,0,0,......,0]1*50

训练过程参数有:统计文本里面出现的不重复的词,并分配id

  • word_2_index--------一般为字典形式,就是词对应的索引,例如:今天:0
  • index_2_word--------字典,或者list,索引对应的词,例如:0:今天
  • word_2_onehot------表示的是词到向量的映射,例如:今天:[1,0,0,......,0]

训练过程,以skip-gram为例:

今天天气真好,假设word_size=5000(这个词库的大小,也就是词的数量),embedding_num=50(表示这个词用维度的大小,嗯。。。。就是1*50中的这个50,应该是这样,错的话哈哈哈我就笑笑哈哈哈)

接下来,比如说,天气

  1. 查表会映射word_2_onehot,得到[0,1,0,0,.........,0]1*5000(5000就是word_size)
  2. [0,1,0,0,.........,0]@w1, w1一开始就是一个随机的生成的,大小为word_size*embedding_num的矩阵,之后会得到一个1*embedding_num的向量,不懂为啥的可以去学习一下线性代数,嘿~
  3. 然后再用1*50的向量@w2,w2一开始是一个随机的生成的,大小为embedding_num*word_size的矩阵,与w1的形状是转置的,但内容不是哟,只是说w1的行的大小是w2列的大小,w1的列的大小是w2的行的大小。结果得到一个1*word_size的向量,将这个向量送到sofmax函数中,然后将结果与词库里的真好对应的word_2_onehot做一个loss得到损失值,然后修改w1,继续重复以上步骤。

代码:

import numpy as np
import pandas as pd
import pickle
import os
import jieba
from tqdm import tqdm


def load_stop_words(file="stopwords.txt"):
    """
        从文件中读取停用词表

        参数:
        file:str,停用词表文件路径

        返回值:
        stop_words:list,停用词表
    """
    with open(file, 'r', encoding="utf-8") as f:
        return f.read().split("\n")


def cut_word(file="数学原始数据.csv"):
    """
        对原始数据进行分词处理,并去除停用词

        参数:
        file:str,原始数据文件路径

        返回值:
        result:list,分词后的数据列表
    """
    stop_words = load_stop_words()
    result = []
    all_data = pd.read_csv(file, encoding='gbk', names=["data"])["data"]
    for words in all_data:
        cut_words = jieba.lcut(words)
        result.append([word for word in cut_words if word not in stop_words])
    return result


def get_dict(data):
    """
        构建词汇表,并将词汇表中的每个单词表示成 one-hot 向量

        参数:
        data:list,分词后的数据列表

        返回值:
        word_2_index:dict,单词到索引的映射表
        index_2_word:list,索引到单词的映射表
        word_2_onehot:dict,单词到 one-hot 向量的映射表
    """
    index_2_word = []  # 索引到单词的映射表
    for words in data:
        for word in words:
            if word not in index_2_word:
                index_2_word.append(word)
    word_2_index = {word: index for index, word in enumerate(index_2_word)}  # 单词到索引的映射表
    word_size = len(word_2_index)
    word_2_onehot = {}
    for word, index in word_2_index.items():
        one_hot = np.zeros((1, word_size))
        one_hot[0, index] = 1
        word_2_onehot[word] = one_hot  # 单词到 one-hot 向量的映射表
    return word_2_index, index_2_word, word_2_onehot


def softmax(x):
    """
        softmax 函数的实现

        参数:
        x:numpy.ndarray,需要进行 softmax 处理的向量

        返回值:
        result:numpy.ndarray,softmax 处理后的向量
    """
    ex = np.exp(x)
    return ex / np.sum(ex, axis=1, keepdims=True)


if __name__ =="__main__":
    data = cut_word()
    word_2_index,index_2_word,word_2_onehot = get_dict(data)

    word_size = len(word_2_index)
    embedding_num = 107
    lr = 0.01
    epoch = 10
    n_gram = 3
    num_neg_samples = 5
    neg_sampling_power = 0.75

    w1=np.random.normal(-1,1,size=(word_size,embedding_num))
    w2=np.random.normal(-1,1,size=(embedding_num,word_size))

    word_counts = {}
    for words in data:
        for word in words:
            if word not in word_counts:
                word_counts[word] = 0
            word_counts[word] += 1

    total_word_count = sum(word_counts.values())
    word_freqs = {word: count/total_word_count for word, count in word_counts.items()}

    word_sampling_probs = {word: word_freqs[word]**neg_sampling_power for word in word_counts}
    word_sampling_probs = {word: prob/sum(word_sampling_probs.values()) for word, prob in word_sampling_probs.items()}

    for e in range(epoch):
        for words in tqdm(data):
            for n_index,now_word in enumerate(words):
                now_word_onehot = word_2_onehot[now_word]

                #negative sampling
                neg_words = np.random.choice(list(word_2_index.keys()), size=num_neg_samples, replace=False, p=list(word_sampling_probs.values()))
                neg_word_indices = [word_2_index[word] for word in neg_words]

                context_words = words[max(n_index-n_gram,0):n_index] + words[n_index+1 : n_index+1+n_gram]
                for context_word in context_words:
                    context_word_onehot = word_2_onehot[context_word]

                    hidden = now_word_onehot @ w1
                    p = hidden @ w2
                    pre = softmax(p)


                    #loss = -np.sum(other_word_onehot * np.log(pre))

                    #A@B=C
                    #delta_C=G
                    #delta_A=G@B.T
                    #delta_B=A.T@G

                    G2 = pre - context_word_onehot
                    delta_w2 = hidden.T @ G2
                    G1 = G2@ w2.T
                    delta_w1 = now_word_onehot.T @ G1

                    w1 -= lr * delta_w1
                    w2 -= lr * delta_w2

                    #negative samples
                    for neg_word_index in neg_word_indices:
                        neg_word_onehot = word_2_onehot[index_2_word[neg_word_index]]

                        hidden = now_word_onehot @ w1
                        p = hidden @ w2
                        pre = softmax(p)

                        G2 = pre - neg_word_onehot
                        delta_w2 = hidden.T @ G2
                        G1 = G2@ w2.T
                        delta_w1 = now_word_onehot.T @ G1

                        w1 -= lr * delta_w1
                        w2 -= lr * delta_w2

    with open("word2vec.pkl","wb") as f:
        pickle.dump([w1,word_2_index,index_2_word],f)

这里面已经加了负采样了。

友情提示:最好部署到服务器上跑,哈哈哈。

我是菜小硕,知识的搬运工,里面的文件,这里就不提供了,自己换成别的,也可以去B站找一下,未加负采样之前的代码来源:http://【本课程详细讲解了word2vec的训练过程和方法, 不涉及数学公式, 简单易懂, 并且全程手写代码加强理解。】 https://www.bilibili.com/video/BV11U4y1E7sK/?p=9&share_source=copy_web&vd_source=b4a26e247f3c69e1c20d1b01eb804f7a主要是为了自己梳理和记录一下学习的内容,可以用于回顾,如果对你有帮助,那我这个搬运工也算是有所贡献,哈哈哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值