词向量和文本向量是自然语言处理(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一些使用技巧: