词的向量化方式
- one-hot编码
- word2vec
- one-hot+word2vec基于窗口
CBOW、SkipGram
1.输出层使用one-hot向量会面临维度灾难,因为词表可能很大。
2.收敛速度缓慢 - Huffman树
- Glove
负采样
- 假设:词向量训练最终采取softmax作为激活函数,得到预测词的分布
如果V中元素很多,则该计算非常耗时
反向传播时,所有权重一起更新非常耗时 - 替代方案
1-不再计算所有词的概率,只挑选某些词计算其概率
2-使用sigmoid函数逐个计算概率,代替softmax
3-只更新选择的部分词的权重矩阵
词向量训练总结
一、根据词与词之间关系的某种假设,制定训练目标
二、设计模型,以词向量为输入
三、随机初始化词向量,开始训练
四、训练过程中词向量作为参数不断调整,获取一定的语义信息
五、使用训练好的词向量做下游任务
词向量存在的问题
- 词向量是“静态”的。每个词使用固定向量,没有考虑前后文
- 一词多义的情况。西瓜 - 苹果 - 华为
- 影响效果的因素非常多
维度选择、随机初始化、skip-gram/cbow/glove、分词质量、词频截断、未登录词、窗口大小、迭代轮数、停止条件、语料质量等 - 没有好的直接评价指标。常需要用下游任务来评价
词向量应用-寻找近义词
- 依赖分词正确
- 与A最接近的词是B,不代表B最接近的是A
- 有时也会有反义词很相似
词向量应用-句向量或文本向量
- 将一句话或一段文本分成若干个词
- 找到每个词对应的词向量
- 所有词向量加和求平均或通过各种网络模型,得到文本向量
- 使用文本向量计算相似度或进行聚类等
词向量应用-KMeans
随机选择k个点作为初始质心
repeat
将每个点指派到最近的质心,形成k个簇
重新计算每个簇的质心
until
质心不发生变化
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()]# [index:[[item1],[item2],...,[itemN]]]
# 计算出新的聚类中心
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)
- KMeans优点:
速度很快,可以支持很大量的数据
样本均匀特征明显的情况下,效果不错 - KMeans缺点:
人为设定聚类数量
初始化中心影响效果,导致结果不稳定
对于个别特殊样本敏感,会大幅影响聚类中心位置
不适合多分类或样本较为离散的数据 - KMeans一些使用技巧:
先设定较多的聚类类别
聚类结束后计算类内平均距离
排序后,舍弃类内平均距离较长的类别
计算距离时可以尝试欧式距离、余弦距离或其他距离
短文本的聚类记得先去重,以及其他预处理
#基于训练好的词向量模型进行聚类
#聚类采用Kmeans算法
import math
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) #同标签的放到一起
#计算类内距离
density_dict = defaultdict(list)
for vector_index, label in enumerate(kmeans.labels_):
vector = vectors[vector_index] #某句话的向量
center = kmeans.cluster_centers_[label] #对应的类别中心向量
distance = cosine_distance(vector, center) #计算距离
density_dict[label].append(distance) #保存下来
for label, distance_list in density_dict.items():
density_dict[label] = np.mean(distance_list) #对于每一类,将类内所有文本到中心的向量余弦值取平均
density_order = sorted(density_dict.items(), key=lambda x:x[1], reverse=True) #按照平均距离排序,向量夹角余弦值越接近1,距离越小
#按照余弦距离顺序输出
for label, distance_avg in density_order:
print("cluster %s , avg distance %f: " % (label, distance_avg))
sentences = sentence_label_dict[label]
for i in range(min(10, len(sentences))): #随便打印几个,太多了看不过来
print(sentences[i].replace(" ", ""))
print("---------")
#向量余弦距离
def cosine_distance(vec1, vec2):
vec1 = vec1 / np.sqrt(np.sum(np.square(vec1))) #A/|A|
vec2 = vec2 / np.sqrt(np.sum(np.square(vec2))) #B/|B|
return np.sum(vec1 * vec2)
#欧式距离
def eculid_distance(vec1, vec2):
return np.sqrt((np.sum(np.square(vec1 - vec2))))
if __name__ == "__main__":
main()