文章

用词向量技术简单分析红楼梦人物关系

看机器能否读懂「宝黛」之间的爱情——用 n-gram 与 Word2Vec 分析红楼人物词向量。

前言:出于种种原因,总是不自觉把爱好和工作相互结合起来。每每感叹于曹雪芹构思的巧妙、语言的精炼、情节的感人……于是蹦出想法:看机器能否读懂「宝黛」之间的爱情。

数据处理

数据当然是伟大的《红楼梦》本身了,下载 txt 文件。

红楼梦 txt 数据

然后进行编码转化「utf-8」,分词,去除其中大量的空格和标点。然后有两种方法进行词向量的构建,分别是 n-gram 模型训练和 Word2Vec。

用 n-gram 生成词向量

把数据转化为如下格式:

n-gram 格式

也就是 N=2。上述操作代码如下:

with open("红楼梦.txt", encoding='utf-8') as f:
    text = f.read()
temp = jieba.lcut(text)
words = []
for i in temp:
    i = re.sub("[\s+\.\!\/_,$%^*(+\"\'""《》]+|[+——!,。?、~@#¥%……&*():]+", "", i)
    if len(i) > 0:
        words.append(i)
trigrams = [([words[i], words[i + 1]], words[i + 2]) for i in range(len(words) - 2)]
print(trigrams[:3])

然后建立词汇表:

vocab = set(words)
word_to_idx = {}
idx_to_word = {}
ids = 0
for w in words:
    cnt = word_to_idx.get(w, [ids, 0])
    if cnt[1] == 0:
        ids += 1
    cnt[1] += 1
    word_to_idx[w] = cnt
    idx_to_word[ids] = w

然后就是模型构建和参数训练。模型结构如下:

  1. 输入层:embedding 层,先将输入单词的编号映射为 one hot 编码向量,然后通过线性层映射到词向量表示,输出为 embedding_dim。
  2. 线性层:从 embedding_dim 维度到 128 维度,然后经过非线性 ReLU 函数。
  3. 线性层:从 128 维度到单词表大小维度,然后 log softmax 函数,给出预测每个单词的概率。

具体代码可见:n-grama-and-wordvect-Analysis-of-the-text

迭代训练:

n-gram 训练

Word2Vec 进行模型训练

训练的时候需要把数据重新进行调整,变成句子进行输入,如下:

['因此', '大家', '议定', '每日', '轮流', '做', '晚饭', '之主']

['天天', '宰猪', '割羊', '屠鹅', '杀鸭', '好似', '临潼斗宝', '的', '一般', '都', '要', '卖弄', '自己', '家里', '的', '好', '厨役', '好', '烹调']

数据处理的代码如下:

f = open("红楼梦.txt", encoding='utf-8')
f = f.read().split("。")
lines = []
for line in f:
    temp = jieba.lcut(line)
    words = []
    for i in temp:
        i = re.sub("[\s+\.\!\/_,$%^*(+\"\'""《》]+|[+——!,\- 。?、~@#¥%……&*():;'']+", "", i)
        if len(i) > 0:
            words.append(i)
    if len(words) > 0:
        lines.append(words)

然后输入模型,将图像转化为二维空间进行展示:

model = Word2Vec(lines, size=20, window=2, min_count=0)
renwu = model.wv.most_similar('林黛玉', topn=20)
rawWordVec = []
word2ind = {}
for i, w in enumerate(model.wv.vocab):
    rawWordVec.append(model[w])
    word2ind[w] = i
rawWordVec = np.array(rawWordVec)
X_reduced = PCA(n_components=2).fit_transform(rawWordVec)

然后绘制图像,并且展示几个特殊人物:贾宝玉、林黛玉、香菱、贾政、晴雯、妙玉、袭人、薛宝钗、王熙凤、平儿、贾母、探春。

fig = plt.figure(figsize=(15, 10))
ax = fig.gca()
ax.set_facecolor('black')
ax.plot(X_reduced[:, 0], X_reduced[:, 1], '.', markersize=1, alpha=0.3, color='white')
words = ['贾宝玉', '林黛玉', '香菱', '贾政', '晴雯', '妙玉', '袭人',
         '薛宝钗', '王熙凤', '平儿', '贾母', '探春']
zhfont1 = matplotlib.font_manager.FontProperties(fname='./华文仿宋.ttf', size=16)
for w in words:
    if w in word2ind:
        ind = word2ind[w]
        xy = X_reduced[ind]
        plt.plot(xy[0], xy[1], '.', alpha=1, color='red')
        plt.text(xy[0], xy[1], w, fontproperties=zhfont1, alpha=1, color='yellow')
plt.show()

展示结果如下:

Word2Vec 人物分布

可以看到宝黛钗凤很相近,几乎都分不清他们的名字了。然后放大之后,震惊……

放大后的宝黛相近

结论:原来机器也「读懂」了「宝黛」爱情。

补充:由于以上用的数据仅仅来自于《红楼梦》文本本身,如果有另外巨大的语料(微博、人民日报、上海热线、汽车之家等,包含 1366130 个词向量)训练出来结果如何呢?篇幅有限,直接上结果吧:

大规模语料训练结果

以上内容纯属个人娱乐,与君共勉,不喜请喷。