分享一次专业领域词汇的无监督挖掘

栏目: 编程工具 · 发布时间: 5年前

内容简介:去年 Data Fountain 曾举办了一个“这个显然确实是工业界比较有价值的一个能力,又想着我之前也在无监督新词发现中做过一定的研究,加之“无监督比赛”的新颖性,所以当时毫不犹豫地参加了,然而最终排名并不靠前~不管怎样,还是分享一下我自己的做法,这是一个真正意义上的无监督做法,也许会对部分读者有些参考价值。

去年 Data Fountain 曾举办了一个“ 电力专业领域词汇挖掘 ”的比赛,该比赛有意思的地方在于它是一个“无监督”的比赛,也就是说它考验的是从大量的语料中无监督挖掘专业词汇的能力。

这个显然确实是工业界比较有价值的一个能力,又想着我之前也在无监督新词发现中做过一定的研究,加之“无监督比赛”的新颖性,所以当时毫不犹豫地参加了,然而最终排名并不靠前~

不管怎样,还是分享一下我自己的做法,这是一个真正意义上的无监督做法,也许会对部分读者有些参考价值。

首先,新词发现部分,用到了我自己写的库nlp zero,基本思路是先分别对“比赛所给语料”、“自己爬的一部分百科百科语料”做新词发现,然后两者进行对比,就能找到一批“比赛所给语料”的特征词。

参考的源码是:

from nlp_zero import *
import re
import pandas as pd
import pymongo
import logging
logging.basicConfig(level = logging.INFO, format = '%(asctime)s - %(name)s - %(message)s')


class D: # 读取比赛方所给语料
    def __iter__(self):
        with open('data.txt') as f:
            for l in f:
                l = l.strip().decode('utf-8')
                l = re.sub(u'[^\u4e00-\u9fa5]+', ' ', l)
                yield l


class DO: # 读取自己的语料(相当于平行语料)
    def __iter__(self):
        db = pymongo.MongoClient().baike.items
        for i in db.find().limit(300000):
            l = i['content']
            l = re.sub(u'[^\u4e00-\u9fa5]+', ' ', l)
            yield l


# 在比赛方语料中做新词发现
f = Word_Finder(min_proba=1e-6, min_pmi=0.5)
f.train(D()) # 统计互信息
f.find(D()) # 构建词库

# 导出词表
words = pd.Series(f.words).sort_values(ascending=False)


# 在自己的语料中做新词发现
fo = Word_Finder(min_proba=1e-6, min_pmi=0.5)
fo.train(DO()) # 统计互信息
fo.find(DO()) # 构建词库

# 导出词表
other_words = pd.Series(fo.words).sort_values(ascending=False)
other_words = other_words / other_words.sum() * words.sum() # 总词频归一化(这样才便于对比)


"""对比两份语料词频,得到特征词。
对比指标是(比赛方语料的词频 + alpha)/(自己语料的词频 + beta);
alpha和beta的计算参考自 http://www.matrix67.com/blog/archives/5044
"""

WORDS = words.copy()
OTHER_WORDS = other_words.copy()

total_zeros = (WORDS + OTHER_WORDS).fillna(0) * 0
words = WORDS + total_zeros
other_words = OTHER_WORDS + total_zeros
total = words + other_words

alpha = words.sum() / total.sum()

result = (words + total.mean() * alpha) / (total + total.mean())
result = result.sort_values(ascending=False)
idxs = [i for i in result.index if len(i) >= 2] # 排除掉单字词

# 导出csv格式
pd.Series(idxs[:20000]).to_csv('result_1.csv', encoding='utf-8', header=None, index=None)

注意到,按照上述方法导出来的词表,顶多算是“语料特征词”,但是还不完全是“电力专业领域词汇”。如果着眼于电力词汇,那么需要对词表进行语义上的筛选。

我的做法是:用导出来的词表对比赛语料进行分词,然后训练一个Word2Vec模型,根据Word2Vec得到的词向量来对词进行聚类。

首先是训练Word2Vec:

# nlp zero提供了良好的封装,可以直到导出一个分词器,词表是新词发现得到的词表。
tokenizer = f.export_tokenizer()

class DW:
    def __iter__(self):
        for l in D():
            yield tokenizer.tokenize(l, combine_Aa123=False)


from gensim.models import Word2Vec

word_size = 100
word2vec = Word2Vec(DW(), size=word_size, min_count=2, sg=1, negative=10)

然后是聚类,不过这不是严格意义上的聚类,而是根据我们自己跳出来的若干个种子词,然后找到一批相似词来。算法是用相似的传递性(有点类似基于连通性的聚类算法),即A和B相似,B和C也相似,那么A、B、C就聚为一类(哪怕A、C从指标上看是不相似的)。当然,这样传递下去很可能把整个词表都遍历了,所以要逐步加强对相似的限制。比如A是种子词,B、C都不是种子词,A、B的相似度为0.6就定义它为相似,B、C的相似度要大于0.7才能认为它们相似,不然这样一级级地传递下去,后面的词就会离种子词的语义越来越远。

聚类算法如下:

import numpy as np
from multiprocessing.dummy import Queue


def most_similar(word, center_vec=None, neg_vec=None):
    """根据给定词、中心向量和负向量找最相近的词
    """
    vec = word2vec[word] + center_vec - neg_vec
    return word2vec.similar_by_vector(vec, topn=200)


def find_words(start_words, center_words=None, neg_words=None, min_sim=0.6, max_sim=1., alpha=0.25):
    if center_words == None and neg_words == None:
        min_sim = max(min_sim, 0.6)
    center_vec, neg_vec = np.zeros([word_size]), np.zeros([word_size])
    if center_words: # 中心向量是所有种子词向量的平均
        _ = 0
        for w in center_words:
            if w in word2vec.wv.vocab:
                center_vec += word2vec[w]
                _ += 1
        if _ > 0:
            center_vec /= _
    if neg_words: # 负向量是所有负种子词向量的平均(本文没有用到它)
        _ = 0
        for w in neg_words:
            if w in word2vec.wv.vocab:
                neg_vec += word2vec[w]
                _ += 1
        if _ > 0:
            neg_vec /= _
    queue_count = 1
    task_count = 0
    cluster = []
    queue = Queue() # 建立队列
    for w in start_words:
        queue.put((0, w))
        if w not in cluster:
            cluster.append(w)
    while not queue.empty():
        idx, word = queue.get()
        queue_count -= 1
        task_count += 1
        sims = most_similar(word, center_vec, neg_vec)
        min_sim_ = min_sim + (max_sim-min_sim) * (1-np.exp(-alpha*idx))
        if task_count % 10 == 0:
            log = '%s in cluster, %s in queue, %s tasks done, %s min_sim'%(len(cluster), queue_count, task_count, min_sim_)
            print log
        for i,j in sims:
            if j >= min_sim_:
                if i not in cluster and is_good(i): # is_good是人工写的过滤规则
                    queue.put((idx+1, i))
                    if i not in cluster and is_good(i):
                        cluster.append(i)
                    queue_count += 1
    return cluster

总的来说,无监督算法始终是难以做到完美的,在工程上,常见的方法就是人工观察结果然后手写一些规则来处理。在这个任务中,由于前面是纯无监督的,哪怕进行了语义聚类,还是会出来一些非电力专业词汇(比如“麦克斯韦方程”),甚至还保留一些“非词”,所以我写了一通规则来过滤(写得有点丑...):

def is_good(w):
    if re.findall(u'[\u4e00-\u9fa5]', w) \
        and len(i) >= 2\
        and not re.findall(u'[较很越增]|[多少大小长短高低好差]', w)\
        and not u'的' in w\
        and not u'了' in w\
        and not u'这' in w\
        and not u'那' in w\
        and not u'到' in w\
        and not w[-1] in u'为一人给内中后省市局院上所在有与及厂稿下厅部商者从奖出'\
        and not w[0] in u'每各该个被其从与及当为'\
        and not w[-2:] in [u'问题', u'市场', u'邮件', u'合约', u'假设', u'编号', u'预算', u'施加', u'战略', u'状况', u'工作', u'考核', u'评估', u'需求', u'沟通', u'阶段', u'账号', u'意识', u'价值', u'事故', u'竞争', u'交易', u'趋势', u'主任', u'价格', u'门户', u'治区', u'培养', u'职责', u'社会', u'主义', u'办法', u'干部', u'员会', u'商务', u'发展', u'原因', u'情况', u'国家', u'园区', u'伙伴', u'对手', u'目标', u'委员', u'人员', u'如下', u'况下', u'见图', u'全国', u'创新', u'共享', u'资讯', u'队伍', u'农村', u'贡献', u'争力', u'地区', u'客户', u'领域', u'查询', u'应用', u'可以', u'运营', u'成员', u'书记', u'附近', u'结果', u'经理', u'学位', u'经营', u'思想', u'监管', u'能力', u'责任', u'意见', u'精神', u'讲话', u'营销', u'业务', u'总裁', u'见表', u'电力', u'主编', u'作者', u'专辑', u'学报', u'创建', u'支持', u'资助', u'规划', u'计划', u'资金', u'代表', u'部门', u'版社', u'表明', u'证明', u'专家', u'教授', u'教师', u'基金', u'如图', u'位于', u'从事', u'公司', u'企业', u'专业', u'思路', u'集团', u'建设', u'管理', u'水平', u'领导', u'体系', u'政务', u'单位', u'部分', u'董事', u'院士', u'经济', u'意义', u'内部', u'项目', u'建设', u'服务', u'总部', u'管理', u'讨论', u'改进', u'文献']\
        and not w[:2] in [u'考虑', u'图中', u'每个', u'出席', u'一个', u'随着', u'不会', u'本次', u'产生', u'查询', u'是否', u'作者']\
        and not (u'博士' in w or u'硕士' in w or u'研究生' in w)\
        and not (len(set(w)) == 1 and len(w) > 1)\
        and not (w[0] in u'一二三四五六七八九十' and len(w) == 2)\
        and re.findall(u'[^一七厂月二夕气产兰丫田洲户尹尸甲乙日卜几口工旧门目曰石闷匕勺]', w)\
        and not u'进一步' in w:
        return True
    else:
        return False

至此,我们就可以完整地执行这个算法了:

# 种子词,在第一步得到的词表中的前面部分挑一挑即可
start_words = [u'电网', u'电压', u'直流', u'电力系统', u'变压器', u'电流', u'负荷', u'发电机', u'变电站', u'机组', u'母线', u'电容', u'放电', u'等效', u'节点', u'电机', u'故障', u'输电线路', u'波形', u'电感', u'导线', u'继电', u'输电', u'参数', u'无功', u'线路', u'仿真', u'功率', u'短路', u'控制器', u'谐波', u'励磁', u'电阻', u'模型', u'开关', u'绕组', u'电力', u'电厂', u'算法', u'供电', u'阻抗', u'调度', u'发电', u'场强', u'电源', u'负载', u'扰动', u'储能', u'电弧', u'配电', u'系数', u'雷电', u'输出', u'并联', u'回路', u'滤波器', u'电缆', u'分布式', u'故障诊断', u'充电', u'绝缘', u'接地', u'感应', u'额定', u'高压', u'相位', u'可靠性', u'数学模型', u'接线', u'稳态', u'误差', u'电场强度', u'电容器', u'电场', u'线圈', u'非线性', u'接入', u'模态', u'神经网络', u'频率', u'风速', u'小波', u'补偿', u'电路', u'曲线', u'峰值', u'容量', u'有效性', u'采样', u'信号', u'电极', u'实测', u'变电', u'间隙', u'模块', u'试验', u'滤波', u'量测', u'元件', u'最优', u'损耗', u'特性', u'谐振', u'带电', u'瞬时', u'阻尼', u'转速', u'优化', u'低压', u'系统', u'停电', u'选取', u'传感器', u'耦合', u'振荡', u'线性', u'信息系统', u'矩阵', u'可控', u'脉冲', u'控制', u'套管', u'监控', u'汽轮机', u'击穿', u'延时', u'联络线', u'矢量', u'整流', u'传输', u'检修', u'模拟', u'高频', u'测量', u'样本', u'高级工程师', u'变换', u'试样', u'试验研究', u'平均值', u'向量', u'特征值', u'导体', u'电晕', u'磁通', u'千伏', u'切换', u'响应', u'效率']

cluster_words = find_words(start_words, min_sim=0.6, alpha=0.35)

result2 = result[cluster_words].sort_values(ascending=False)
idxs = [i for i in result2.index if is_good(i)]

pd.Series([i for i in idxs if len(i) > 2][:10000]).to_csv('result_1_2.csv', encoding='utf-8', header=None, index=None)

最终结果(部分):

变压器

发电机

变电站

过电压

可靠性

控制器

断路器

分布式

输电线路

数学模型

滤波器

电容器

故障诊断

神经网络

直流电压

等离子体

联络线

传感器

汽轮机

晶闸管

电动机

约束条件

数据库

可行性

持续时间

整流器

稳定性

调节器

电磁场

本文的算法在榜上的成绩大约是0.22左右,封榜时排在100名左右,榜首已经是0.49了,所以从成绩来看其实没什么值得炫耀的。不过当时听说不少人拿现成的专业词典去做字标注,所以当时就没做下去了。要是真的那样子的话,我觉得就很没意思了...

总之,本文算是提供了一个无监督抽取专业词的实现模版,如果读者觉得有可取之处,大方取之即可;如果觉得一无是处,敬请无视它~

转载到请包括本文地址: https://kexue.fm/archives/6540

如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。

如果您觉得本文还不错,欢迎/本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!


以上所述就是小编给大家介绍的《分享一次专业领域词汇的无监督挖掘》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python高性能(第2版)

Python高性能(第2版)

[加] 加布丽埃勒•拉纳诺(Gabriele Lanaro) / 袁国忠 / 人民邮电出版社 / 2018-8 / 59.00元

本书是一本Python性能提升指南,展示了如何利用Python的原生库以及丰富的第三方库来构建健壮的应用程序。书中阐释了如何利用各种剖析器来找出Python应用程序的性能瓶颈,并应用正确的算法和高效的数据结构来解决它们;介绍了如何有效地利用NumPy、Pandas和Cython高性能地执行数值计算;解释了异步编程的相关概念,以及如何利用响应式编程实现响应式应用程序;概述了并行编程的概念,并论述了如......一起来看看 《Python高性能(第2版)》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具