赞
踩
词向量和文本向量是自然语言处理(NLP)中用于表达文本数据的数学模型。
词向量:
文本向量:
词向量和文本向量广泛应用于文本分类、信息检索、情感分析、机器翻译等NLP任务。它们可以有效地捕捉文本的语义信息,并用于各种机器学习模型。
我 [0.78029002 0.77010974 0.07479124 0.4106988 ]
爱 [0.14092194 0.63690971 0.73774712 0.42768218]
北京 [0.95780568 0.51903789 0.76615855 0.6399924 ]
天安门 [0.73861383 0.49694373 0.13213538 0.41237077]
One-Hot编码是一种用于表示类别变量的方法,常用于自然语言处理和其他机器学习应用。在这种编码方案中,每个唯一的类别或词都由一个全为0的向量表示,除了一个位置为1。这个"1"的位置通常由词或类别在词汇表或类别列表中的索引决定。
例如,如果我们有一个词汇表:[‘苹果’, ‘香蕉’, ‘橙子’],对应的One-Hot编码可能如下:
‘苹果’:[1, 0, 0]
‘香蕉’:[0, 1, 0]
‘橙子’:[0, 0, 1]
One-Hot编码的优点是简单、有效,但也有缺点,比如它不能捕捉词之间的关系(例如,"苹果"和"香蕉"都是水果)。此外,当词汇表很大时,One-Hot编码会非常占用存储空间和计算资源,因为每个词都需要一个与词汇表大小相等的向量。
在对文本向量化时,也可以考虑词频
不错 [0, 0, 0, 1, 0]
不错 不错 [0, 0, 0, 2, 0]
有时也可以不事先准备词表,临时构建
如做文本比对任务,成对输入,此时维度可随时变化
one-hot编码-缺点
Word2Vec是一种用于生成词向量的模型,由Google的研究人员Mikolov等人于2013年提出。与One-Hot编码不同,Word2Vec生成的是稠密的、低维度的向量,这些向量能够捕捉词与词之间的语义关系。
Word2Vec主要有两种架构:
Word2Vec通过大量的文本数据进行训练,以捕捉词之间的共现关系,并将这些信息编码到低维向量中。这些向量有许多有趣的性质,例如相似的词会被映射到向量空间中相近的点。
例如,Word2Vec能够捕捉到类比关系,比如“国王 - 男人 + 女人 = 女王”。
Word2Vec的优点包括:
缺点包括:
我们希望得到一种词向量,使得向量关系能反映语义关系,比如:
cos(你好, 您好) > cos(你好,天气)
即词义的相似性反映在向量的相似性
国王 - 男人 = 皇后 -女人
即向量可以通过数值运算反映词之间的关系
同时,不管有多少词,向量维度应当是固定的
基于语言模型的词向量生成方法与Word2Vec等词嵌入技术有本质区别。在这类方法中,词向量是作为预训练语言模型的副产品而生成的。这些模型包括长短时记忆(LSTM)、门控循环单元(GRU)或者更先进的架构如Transformer。
语言模型的主要任务是给定一个词序列,预测下一个词。这种预测是基于整个词序列的上下文信息。在训练过程中,模型学习到的词向量能够捕捉到丰富的语义和语法信息。
ELMo(Embeddings from Language Models)和BERT(Bidirectional Encoder Representations from Transformers)是两个典型的基于语言模型的词向量生成方法。
优点:
缺点:
基于语言模型的词向量因其高度的灵活性和准确性在许多NLP应用中得到了广泛应用。
做出假设:
如果两个词在文本中出现时,它的前后出现的词相似,则这两个词语义相似。
如:
基于前述思想,我们尝试用窗口中的词(或者说周围词)来表示(预测)中间词
或用中间词来表示周围词
词向量-训练的问题
代码
cbow.py
#coding:utf8 import torch import torch.nn as nn import numpy as np """ 基于pytorch的词向量CBOW 模型部分 """ class CBOW(nn.Module): def __init__(self, vocab_size, embedding_size, window_length): super(CBOW, self).__init__() self.word_vectors = nn.Embedding(vocab_size, embedding_size) self.pooling = nn.AvgPool1d(window_length) self.projection_layer = nn.Linear(embedding_size, vocab_size) def forward(self, context): context_embedding = self.word_vectors(context) context_embedding = self.pooling(context_embedding.transpose(1, 2)).squeeze() pred = self.projection_layer(context_embedding) return pred vocab_size = 8 #词表大小 embedding_size = 4 #人为指定的向量维度 window_length = 4 #窗口长度 model = CBOW(vocab_size, embedding_size, window_length) #假如选取一个词窗口【1,2,3,4,5】· context = torch.LongTensor([[1,2,4,5]]) pred = model(context) #训练目标是输出3 print("预测值:", pred) print("词向量矩阵") print(model.state_dict()["word_vectors.weight"])
对所有词进行二进制编码,使其符合以下特点
代码
""" 构建霍夫曼树 """ class HuffmanNode: def __init__(self, word_id, frequency): self.word_id = word_id # 叶子结点存词对应的id, 中间节点存中间节点id self.frequency = frequency # 存单词频次 self.left_child = None self.right_child = None self.father = None self.Huffman_code = [] # 霍夫曼码(左1右0) self.path = [] # 根到叶子节点的中间节点id class HuffmanTree: def __init__(self, wordid_frequency_dict): self.word_count = len(wordid_frequency_dict) # 单词数量 self.wordid_code = dict() self.wordid_path = dict() self.root = None unmerge_node_list = [HuffmanNode(wordid, frequency) for wordid, frequency in wordid_frequency_dict.items()] # 未合并节点list self.huffman = [HuffmanNode(wordid, frequency) for wordid, frequency in wordid_frequency_dict.items()] # 存储所有的叶子节点和中间节点 # 构建huffman tree self.build_tree(unmerge_node_list) # 生成huffman code self.generate_huffman_code_and_path() def merge_node(self, node1, node2): sum_frequency = node1.frequency + node2.frequency mid_node_id = len(self.huffman) # 中间节点的value存中间节点id father_node = HuffmanNode(mid_node_id, sum_frequency) if node1.frequency >= node2.frequency: father_node.left_child = node1 father_node.right_child = node2 else: father_node.left_child = node2 father_node.right_child = node1 self.huffman.append(father_node) return father_node def build_tree(self, node_list): while len(node_list) > 1: i1 = 0 # 概率最小的节点 i2 = 1 # 概率第二小的节点 if node_list[i2].frequency < node_list[i1].frequency: [i1, i2] = [i2, i1] for i in range(2, len(node_list)): if node_list[i].frequency < node_list[i2].frequency: i2 = i if node_list[i2].frequency < node_list[i1].frequency: [i1, i2] = [i2, i1] father_node = self.merge_node(node_list[i1], node_list[i2]) # 合并最小的两个节点 if i1 < i2: node_list.pop(i2) node_list.pop(i1) elif i1 > i2: node_list.pop(i1) node_list.pop(i2) else: raise RuntimeError('i1 should not be equal to i2') node_list.insert(0, father_node) # 插入新节点 self.root = node_list[0] def generate_huffman_code_and_path(self): stack = [self.root] while len(stack) > 0: node = stack.pop() # 顺着左子树走 while node.left_child or node.right_child: code = node.Huffman_code path = node.path node.left_child.Huffman_code = code + [1] node.right_child.Huffman_code = code + [0] node.left_child.path = path + [node.word_id] node.right_child.path = path + [node.word_id] # 把没走过的右子树加入栈 stack.append(node.right_child) node = node.left_child word_id = node.word_id word_code = node.Huffman_code word_path = node.path self.huffman[word_id].Huffman_code = word_code self.huffman[word_id].path = word_path # 把节点计算得到的霍夫曼码、路径 写入词典的数值中 self.wordid_code[word_id] = word_code self.wordid_path[word_id] = word_path # 获取所有词的正向节点id和负向节点id数组 def get_all_pos_and_neg_path(self): positive = [] # 所有词的正向路径数组 negative = [] # 所有词的负向路径数组 for word_id in range(self.word_count): pos_id = [] # 存放一个词 路径中的正向节点id neg_id = [] # 存放一个词 路径中的负向节点id for i, code in enumerate(self.huffman[word_id].Huffman_code): if code == 1: pos_id.append(self.huffman[word_id].path[i]) else: neg_id.append(self.huffman[word_id].path[i]) positive.append(pos_id) negative.append(neg_id) return positive, negative def main(): words = "你 我 他 你们 我们 他们 它们" freqs = "50 10 8 7 6 3 2" word_to_id = dict((word, i) for i, word in enumerate(words.split())) print(word_to_id) word_frequency = dict((word_to_id[x], int(y)) for x, y in zip(words.split(), freqs.split())) tree = HuffmanTree(word_frequency) word_code = dict((word, tree.wordid_code[word_to_id[word]]) for word in words.split()) print(word_code) if __name__ == '__main__': main()
词向量训练最终采取softmax作为激活函数,得到预测词的分布
于一个数组V
如果V中元素很多,则该计算非常耗时
反向传播时,所有权重一起更新非常耗时
替代方案
不再计算所有词的概率,只挑选某些词计算其概率
使用sigmoid函数逐个计算概率,代替softmax
只更新选择的部分词的权重矩阵
Glove通过共现矩阵,让模型看到了整个文本的信息,而word2vec模型一直在看某个窗口
词向量存在的问题:
代码
kmeans.py
import numpy as np import random import sys ''' Kmeans算法实现 原文链接:https://blog.csdn.net/qingchedeyongqi/article/details/116806277 ''' class KMeansClusterer: # k均值聚类 def __init__(self, ndarray, cluster_num): self.ndarray = ndarray self.cluster_num = cluster_num self.points = self.__pick_start_point(ndarray, cluster_num) def cluster(self): result = [] for i in range(self.cluster_num): result.append([]) for item in self.ndarray: distance_min = sys.maxsize index = -1 for i in range(len(self.points)): distance = self.__distance(item, self.points[i]) if distance < distance_min: distance_min = distance index = i result[index] = result[index] + [item.tolist()] new_center = [] for item in result: new_center.append(self.__center(item).tolist()) # 中心点未改变,说明达到稳态,结束递归 if (self.points == new_center).all(): sum = self.__sumdis(result) return result, self.points, sum self.points = np.array(new_center) return self.cluster() def __sumdis(self,result): #计算总距离和 sum=0 for i in range(len(self.points)): for j in range(len(result[i])): sum+=self.__distance(result[i][j],self.points[i]) return sum def __center(self, list): # 计算每一列的平均值 return np.array(list).mean(axis=0) def __distance(self, p1, p2): #计算两点间距 tmp = 0 for i in range(len(p1)): tmp += pow(p1[i] - p2[i], 2) return pow(tmp, 0.5) def __pick_start_point(self, ndarray, cluster_num): if cluster_num < 0 or cluster_num > ndarray.shape[0]: raise Exception("簇数设置有误") # 取点的下标 indexes = random.sample(np.arange(0, ndarray.shape[0], step=1).tolist(), cluster_num) points = [] for index in indexes: points.append(ndarray[index].tolist()) return np.array(points) x = np.random.rand(100, 8) kmeans = KMeansClusterer(x, 10) result, centers, distances = kmeans.cluster() print(result) print(centers) print(distances)
word2vec_kmeans.py
#!/usr/bin/env python3 #coding: utf-8 #基于训练好的词向量模型进行聚类 #聚类采用Kmeans算法 import math import re import json import jieba import numpy as np from gensim.models import Word2Vec from sklearn.cluster import KMeans from collections import defaultdict #输入模型文件路径 #加载训练好的模型 def load_word2vec_model(path): model = Word2Vec.load(path) return model def load_sentence(path): sentences = set() with open(path, encoding="utf8") as f: for line in f: sentence = line.strip() sentences.add(" ".join(jieba.cut(sentence))) print("获取句子数量:", len(sentences)) return sentences #将文本向量化 def sentences_to_vectors(sentences, model): vectors = [] for sentence in sentences: words = sentence.split() #sentence是分好词的,空格分开 vector = np.zeros(model.vector_size) #所有词的向量相加求平均,作为句子向量 for word in words: try: vector += model.wv[word] except KeyError: #部分词在训练中未出现,用全0向量代替 vector += np.zeros(model.vector_size) vectors.append(vector / len(words)) return np.array(vectors) def main(): model = load_word2vec_model("model.w2v") #加载词向量模型 sentences = load_sentence("titles.txt") #加载所有标题 vectors = sentences_to_vectors(sentences, model) #将所有标题向量化 n_clusters = int(math.sqrt(len(sentences))) #指定聚类数量 print("指定聚类数量:", n_clusters) kmeans = KMeans(n_clusters) #定义一个kmeans计算类 kmeans.fit(vectors) #进行聚类计算 sentence_label_dict = defaultdict(list) for sentence, label in zip(sentences, kmeans.labels_): #取出句子和标签 sentence_label_dict[label].append(sentence) #同标签的放到一起 for label, sentences in sentence_label_dict.items(): print("cluster %s :" % label) for i in range(min(10, len(sentences))): #随便打印几个,太多了看不过来 print(sentences[i].replace(" ", "")) print("---------") if __name__ == "__main__": main()
KMeans优点:
KMeans缺点:
人为设定聚类数量
初始化中心影响效果,导致结果不稳定
对于个别特殊样本敏感,会大幅影响聚类中心位置
不适合多分类或样本较为离散的数据
KMeans一些使用技巧:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。