文章目录

  • 一、数据介绍及处理
  • 二、寻找未登录词
    • 1.统计语料库中的词信息
    • 2.利用互信息熵得到初始化词库
    • 3.对语料库进行切分
    • 4.利用搜索引擎判断新词
    • 5.迭代寻找新词
    • 6.方法总结

一、数据介绍及处理

  本文以电商领域的商品名称为语料进行实验,来寻找未登录词。
  首先,将json格式的数据,提取其goods_name列,写入到txt文件中。

import pandas as pd"""将数据中的goods_name列与search_value列进行去重后再分别写入到txt
"""class DataConvert(object):def __init__(self, file_input_name, file_corpus, file_searchValue):self.file_input_name = file_input_nameself.file_corpus = file_corpusself.file_searchValue = file_searchValuedef run(self):# 读取原始数据# lines=True:文件的每一行为一个完整的字典,默认是一个列表中包含很多字典input_file = pd.read_json(self.file_input_name, lines=True)# 选定需要操作的两列,并进行去重goods_names = input_file.loc[:, 'goods_name'].dropna().drop_duplicates().tolist()search_values = input_file.loc[:, 'search_value'].dropna().drop_duplicates().tolist()# 将这两列写入到输出文件中with open(self.file_corpus, "w", encoding="utf-8") as f1:for goods_name in goods_names:try:f1.write(goods_name)f1.write("\n")except:print(goods_name)f1.close()with open(self.file_searchValue, "w", encoding="utf-8") as f2:for search_value in search_values:f2.write(search_value)f2.write("\n")f2.close()

得到的file_corpus格式如下:

二、寻找未登录词

1.统计语料库中的词信息

  统计语料库中出现单字,双字的频率,前后链接的字相关信息;

#!usr/bin/env python
# -*- coding:utf-8 -*-
"""统计语料库中出现单字,双字的频率,前后链接的字相关信息;
"""
import re
import codecs
import json
import os# \u4E00-\u9FD5表示所有汉字
# a-zA-Z0-9表示26个英文字母与数字
# -+#&\._/  \(\) \~\'表示常用符号
# \u03bc\u3001 \uff08\uff09 \u2019:表示特殊字符,一些程式码
re_han = re.compile("([\u4E00-\u9FD5a-zA-Z0-9-+#&\._/\u03bc\u3001\(\)\uff08\uff09\~\'\u2019]+)", re.U)class Finding(object):def __init__(self, file_corpus, file_count, count):self.file_corpus = file_corpusself.file_count = file_countself.count = countdef split_text(self, sentence):"""找到每个商品名称中符合正则表达式的子串,以列表形式返回:param sentence::return:"""# re.findall():在字符串中找到正则表达式所匹配的所有子串,并返回一个列表seglist = re_han.findall(sentence)return seglistdef count_word(self, seglist, k):"""遍历每个子串,返回窗口对应的词与前后各一个词:param seglist: 商品名称的子串列表:param k: 窗口大小:return:"""for words in seglist:ln = len(words)i = 0j = 0if words:while 1:j = i + kif j <= ln:word = words[i:j]if i == 0:lword = 'S'else:lword = words[i - 1:i]if j == ln:rword = 'E'else:rword = words[j:j + 1]i += 1yield word, lword, rwordelse:breakdef find_word(self):""":return:"""# 读取语料数据input_data = codecs.open(self.file_corpus, 'r', encoding='utf-8')dataset = {}# enumerate将可迭代对象转化为索引与数据的格式,起始下标为1for lineno, line in enumerate(input_data, 1):try:line = line.strip()# 找到每个商品名称中符合正则表达式的子串,以列表形式返回seglist = self.split_text(line)# count_word:遍历每个子串,返回窗口对应的词与前后各一个词# 遍历这三个词,[[], {}, {}]分别记录窗口对应的词出现的个数,前一个词及出现个数,后一个词及出现个数for w, lw, rw in self.count_word(seglist, self.count):if w not in dataset:dataset.setdefault(w, [[], {}, {}])dataset[w][0] = 1else:dataset[w][0] += 1if lw:dataset[w][1][lw] = dataset[w][1].get(lw, 0) + 1if rw:dataset[w][2][rw] = dataset[w][2].get(rw, 0) + 1except:passself.write_data(dataset)def write_data(self, dataset):"""将统计结果写入到字典中:param dataset::return:"""output_data = codecs.open(self.file_count, 'w', encoding='utf-8')for word in dataset:output_data.write(word + '\t' + json.dumps(dataset[word], ensure_ascii=False, sort_keys=False) + '\n')output_data.close()

这一步会生成两个文件,分别是
count_one.txt

count_two.txt文件。

2.利用互信息熵得到初始化词库

  对统计出的单字和双字的结果,使用互信熵,选择大于阈值K=的词加入词库,作为初始词库。在计算机领域,更常用的是点间互信息,点间互信息计算了两个具体事件之间的互信息。 点间互信息的定义如下:

本文操作时,选择以e为底。

#!usr/bin/env python
# -*- coding:utf-8 -*-
"""对统计出的单字和双字的结果,使用互信熵,选择大于阈值K=的词加入词库,作为初始词库;
"""
import codecs
import json
import mathdef load_data(file_count_one):"""加载单字文件:{one_word,[[], {}, {}]},并返回总词数与单字字典:{one_word:one_word_freq}:param file_count_one:文件名:return:"""count_one_data = codecs.open(file_count_one, 'r', encoding='utf-8')count_one_param = {}N = 0# 遍历每一行,返回总词数与单字字典:{one_word:one_word_freq}for line in count_one_data.readlines():line = line.strip()line = line.split('\t')try:word = line[0]value = json.loads(line[1])N += value[0]count_one_param[word] = int(value[0])except:passcount_one_data.close()return N, count_one_paramdef select(file_count_one, file_count_two, file_dict, K=10.8):"""遍历每一行,利用互信息熵计算每个词的成词概率PMI,利用阈值K来筛选一部分词,将结果保存到file_dict字典中:param file_count_one: 窗口为1的文件:param file_count_two: 窗口为2的文件:param file_dict: 字典文件:param K: 稳定词的阈值:return:"""count_two_data = codecs.open(file_count_two, 'r', encoding='utf-8')# 总词数与单字字典N, count_one_param = load_data(file_count_one)count_two_param = {}# 遍历每一行,利用互信息熵计算每个词的成词概率PMIfor line in count_two_data.readlines():line = line.strip()line = line.split('\t')try:word = line[0]value = json.loads(line[1])# 双字出现的词频 / 总的字数P_w = 1.0 * value[0] / N# 双字的第一个字出现的词频 / 总的字数P_w1 = 1.0 * count_one_param.get(word[0], 1) / N# 双字的第二个字出现的词频 / 总的字数P_w2 = 1.0 * count_one_param.get(word[1], 1) / N# 计算点间互信息PMI的计算公式,两个字的成词概率越大,则PMI值越大;如果两个子不相关,则PMI=0mi = math.log(P_w / (P_w1 * P_w2))count_two_param[word] = miexcept:passselect_two_param = []for w in count_two_param:mi = count_two_param[w]if mi > K:select_two_param.append(w)with codecs.open(file_dict, 'a', encoding='utf-8') as f:for w in select_two_param:f.write(w + '\t' + 'org' + '\n')count_two_data.close()

这一步会生成初始词库文件dict.txt,后续发现的新词也会追加到这个文件中,构成新的词库文件。

这里我从业务端获得了几万条初始手工添加的词。考虑到初始已经有许多词,所以,互信息熵的阈值K设置的可以大一点。如果没有的话,K会对最后结果起决定作用。所使用的材料是长文本还是短文本也会对K有影响。可以尝试初始设置为K=8来运行程序。

3.对语料库进行切分

  有了初始词库,使用正向最大匹配,对语料库进行切分,对切分出来的字串按频率排序输出并记下数量seg_num

"""有了初始词库,使用正向最大匹配,对语料库进行切分,对切分出来的字串按频率排序输出并记下数量seg_num
"""
from __future__ import unicode_literals
import codecs
import re# 匹配所有汉字与规定符号
re_han_cut = re.compile("([\u4E00-\u9FD5a-zA-Z0-9-+#&\._/\u03bc\u3001\(\)\uff08\uff09\~\'\u2019]+)", re.U)
# 匹配所有汉字
re_han = re.compile("([\u4E00-\u9FD5]+)", re.U)class Cuting(object):def __init__(self, file_corpus, file_dict, file_segment):self.file_corpus = file_corpusself.file_dict = file_dictself.file_segment = file_segmentself.wdict = {}self.get_dict()def get_dict(self):"""遍历初始字典文件,初始化wdict字典 => {one_word:[many two world]}:return:"""f = codecs.open(self.file_dict, 'r', encoding='utf-8')# 遍历每一行数据for lineno, line in enumerate(f, 1):line = line.strip()line = line.split('\t')w = line[0]if w:if w[0] in self.wdict:value = self.wdict[w[0]]value.append(w)self.wdict[w[0]] = valueelse:self.wdict[w[0]] = [w]def fmm(self, sentence):"""将子串中在wdict中two_word保存到result表中:param sentence: 字符子串:return:"""N = len(sentence)k = 0result = []while k < N:w = sentence[k]maxlen = 1# 如果这个字在初始化字典wdict中if w in self.wdict:# 初始化字典中的value,是一个列表,里面有许多上一步找到的two_wordwords = self.wdict[w]t = ''for item in words:itemlen = len(item)if sentence[k:k + itemlen] == item and itemlen >= maxlen:t = itemmaxlen = itemlenif t and t not in result:result.append(t)k = k + maxlenreturn resultdef judge(self, words):"""判断这个子串是否只有汉字:param words::return:"""flag = Falsen = len(''.join(re_han.findall(words)))if n == len(words):flag = Truereturn flagdef cut(self, sentence):""":param sentence::return:"""buf = []# 在商品名称中找到正则表达式所匹配的所有子串,并返回一个列表blocks = re_han_cut.findall(sentence)# 遍历每一个子串for blk in blocks:if blk:# 将子串中在wdict中的two_word返回fm = self.fmm(blk)if fm:try:# 如果返回值不为空,则将这些词以“|”进行拼接,并以此构建正则表达式re_split = re.compile('|'.join(fm))# split方法按照能够匹配的子串将字符串分割后返回列表for s in re_split.split(blk):# 如果这个子串只有汉字,则将该子串添加到列表中,最终返回该列表if s and self.judge(s):buf.append(s)except:passreturn bufdef find(self):""":return:"""input_data = codecs.open(self.file_corpus, 'r', encoding='utf-8')output_data = codecs.open(self.file_segment, 'w', encoding='utf-8')dataset = {}# 遍历每一个语料文件for lineno, line in enumerate(input_data, 1):line = line.strip()# 遍历基于正则切割的字符串列表for w in self.cut(line):if len(w) >= 2:dataset[w] = dataset.get(w, 0) + 1# 基于词频进行排序data_two = sorted(dataset.items(), key=lambda d: d[1], reverse=True)seg_num = len(data_two)for key in data_two:output_data.write(key[0] + '\t' + str(key[1]) + '\n')print('Having segment %d words' % seg_num)input_data.close()output_data.close()return seg_num

这一步会生成片段语料文件file_segment.txt

片段语料文件会根据初始词库的迭代更新而变化。

4.利用搜索引擎判断新词

  对切分产生的字串按频率排序,前H=2000的字串进行搜索引擎(百度)。若字串是“百度百科”收录词条,将该字串作为词加入词库;或者在搜索页面的文本中出现的次数超过60,也将该字串作为词加入词库;

#!usr/bin/env python
# -*- coding:utf-8 -*-
"""对切分产生的字串按频率排序,前H=2000的字串进行搜索引擎(百度),若字串是“百度百科”收录词条,将该字串作为词加入词库,或者在搜索页面的文本中出现的次数超过60,也将该字串作为词加入词库;
"""
import requests
from lxml import etree
import codecs
import redef search(file_segment, file_dict, H, R, iternum):# headers,从网站的检查中获取headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8','Accept-Encoding': 'gzip, deflate, sdch, b','Accept-Language': 'zh-CN,zh;q=0.8','Cache-Control': 'max-age=0','Connection': 'keep-alive','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.'}# 加载切分出来的子符串input_data = codecs.open(file_segment, 'r', encoding='utf-8')read_data = input_data.readlines()N = len(read_data)if H > N:H = Noutput_data = codecs.open(file_dict, 'a', encoding='utf-8')n = 0m = 1# 遍历切分出的子符串for line in read_data[:H]:line = line.rstrip()line = line.split('\t')# 字符串word = line[0]try:# 访问百度百科词条urlbase = 'https://www.baidu.com/s?wd=' + worddom = requests.get(urlbase, headers=headers)ct = dom.text# 在搜索页面的文本中出现的次数num = ct.count(word)html = dom.contentselector = etree.HTML(html)flag = False# 若字串是“百度百科”收录词条,将该字串作为词加入词库if selector.xpath('//h3[@class="t c-gap-bottom-small"]'):ct = ''.join(selector.xpath('//h3[@class="t c-gap-bottom-small"]//text()'))lable = re.findall(u'(.*)_百度百科', ct)for w in lable:w = w.strip()if w == word:flag = Trueif flag:output_data.write(word + '\titer_' + str(iternum) + '\n')n += 1# 在搜索页面的文本中出现的次数超过阈值R=60,也将该字串作为词加入词库else:if num >= R:output_data.write(word + '\titer_' + str(iternum) + '\n')n += 1m += 1if m % 100 == 0:print('having crawl %dth word\n' % m)except:passprint('Having add %d words to file_dict at iter_%d' % (n, iternum))input_data.close()output_data.close()return n

这一步会将发现的新词添加到dict.txt文件中。

5.迭代寻找新词

  更新词库后,重复step3,step4进行迭代。当searh_num=0时,结束迭代;当seg_num小于设定的Y=3000,进行最后一次step4,并H设定为H=seg_num,执行完后结束迭代,最后词库就是本程序所找的词。

#!usr/bin/env python
# -*- coding:utf-8 -*-
"""算法步骤:1.统计语料库中出现单字,双字的频率,前后链接的字相关信息;2.对统计出的单字和双字的结果,使用互信熵,选择大于阈值K=的词加入词库,作为初始词库;3.有了初始词库,使用正向最大匹配,对语料库进行切分,对切分出来的字串按频率排序输出并记下数量seg_num4.对切分产生的字串按频率排序,前H=5000的字串进行搜索引擎(百度),若字串是“百度百科”收录词条,将该字串作为词加入词库,或者在搜索页面的文本中出现的次数超过60,也将该字串作为词加入词库;5.更新词库后,重复step3,step4进行迭代,,当searh_num=0时,结束迭代;当seg_num小于设定的Y=1000,进行最后一次step4,并H设定为H=seg_num,执行完后结束迭代,最后词库就是本程序所找的词
"""
from __future__ import absolute_import__version__ = '1.0'
__license__ = 'MIT'import os
import logging
import time
import codecs
import sysfrom module.corpus_count import *
from module.corpus_segment import *
from module.select_model import *
from module.words_search import *# 获取当前路径
medfw_path = os.getcwd()
file_corpus = medfw_path + '/data_org/file_corpus.txt'
file_dict = medfw_path + '/data_org/dict.txt'
file_count_one = medfw_path + '/data_org/count_one.txt'
file_count_two = medfw_path + '/data_org/count_two.txt'
file_segment = medfw_path + '/data_org/file_segment.txt'# 日志设置
log_console = logging.StreamHandler(sys.stderr)
default_logger = logging.getLogger(__name__)
default_logger.setLevel(logging.DEBUG)
default_logger.addHandler(log_console)def setLogLevel(log_level):global loggerdefault_logger.setLevel(log_level)class MedFW(object):def __init__(self, K=10, H=2000, R=60, Y=5000):self.K = K  # 互信息熵的阈值self.H = H  # 取file_segment.txt前多少个self.R = R  # 片段单词在搜索引擎出现的阈值self.Y = Y  # 迭代结束结束条件参数self.seg_num = 0  # 片段语料库的数量self.search_num = 0  # 搜索引擎向 file_dict 添加单词的数量# step1: 统计语料库中出现单字,双字的频率,前后链接的字相关信息;def medfw_s1(self):for i in range(1, 3):if i == 1:file_count = file_count_oneelse:file_count = file_count_twodefault_logger.debug("Counting courpus to get %s...\n" % (file_count))t1 = time.time()cc = Finding(file_corpus, file_count, i)cc.find_word()default_logger.debug("Getting %s cost %.3f seconds...\n" % (file_count, time.time() - t1))# step2: 对统计出的单字和双字的结果,使用互信熵,选择大于阈值K=的词加入词库,作为初始词库;def medfw_s2(self):default_logger.debug("Select stable words and  generate initial vocabulary... \n")select(file_count_one, file_count_two, file_dict, self.K)# step3: 有了初始词库,使用正向最大匹配,对语料库进行切分,对切分出来的字串按频率排序输出并记下数量seg_numdef medfw_s3(self):t1 = time.time()sc = Cuting(file_corpus, file_dict, file_segment)self.seg_num = sc.find()default_logger.debug("Segment corpuscost %.3f seconds...\n" % (time.time() - t1))# step4:对片段语料中的单词使用搜索引擎进行搜索def medfw_s4(self, H, R, iternum):t1 = time.time()self.search_num = search(file_segment, file_dict, H, R, iternum)default_logger.debug("Select words cost %.3f seconds...\n" % (time.time() - t1))# 主程序def medfw(self):# default_logger.debug("Starting to find words and do step1...\n" )print('-----------------------------------')print('step1:count corpus')self.medfw_s1()print('-----------------------------------')print('step2:select stable words and  generate initial vocabulary')self.medfw_s2()print('-----------------------------------')print('step3:use initial vocabulary to segment corpus')self.medfw_s3()print('-----------------------------------')print('step4:use search engine to select words of segment corpus')self.medfw_s4(H=self.H, R=self.R, iternum=0)print('-----------------------------------')print('step5:cycling iteration')iter_num = 1while True:if self.search_num:default_logger.debug("Itering %d...\n" % (iter_num))t1 = time.time()self.medfw_s3()print("---------------------- seg_num:%s -----------------------" % self.seg_num)if self.seg_num <= self.Y:self.H = self.seg_numself.medfw_s4(H=self.H, R=self.R, iternum=iter_num)default_logger.debug("Ending the iteration ...\n")breakelse:self.medfw_s4(H=self.H, R=self.R, iternum=iter_num)iter_num += 1default_logger.debug("Itering %d cost %.3f seconds...\n " % ((iter_num - 1), time.time() - t1))else:breakwith codecs.open(file_dict, 'r', encoding='utf-8') as f:total_num = len(f.readlines())print('Having succcessfuly find %d words from corpus ' % total_num)if __name__ == '__main__':md = MedFW(K=10, H=3000, R=50, Y=3000)md.medfw()

这一步会多轮迭代寻找新词。

6.方法总结

  实践表明:这种思路获得的结果有一定用处,获得的结果需要人工来甄别。缺点的话也很明显,比如一个长的品牌名或者商品名,如果其中几个连续的词的词频很高,并且本身也能成词,就会将这个品牌名或者商品名切散!还有待继续研究!

  • 反作弊基于左右信息熵和互信息的新词挖掘
  • find-Chinese-medical-words

发现新词 | NLP之无监督方式构建词库(一)相关推荐

  1. 从无监督构建词库看「最小熵原理」,套路是如何炼成的

    作者丨苏剑林 单位丨广州火焰信息科技有限公司 研究方向丨NLP,神经网络 个人主页丨kexue.fm 在深度学习等端到端方案已经逐步席卷 NLP 的今天,你是否还愿意去思考自然语言背后的基本原理?我们 ...

  2. 无监督构建词库:更快更好的新词发现算法

    作者丨苏剑林 单位丨追一科技 研究方向丨NLP,神经网络 个人主页丨kexue.fm 新词发现是 NLP 的基础任务之一,主要是希望通过无监督发掘一些语言特征(主要是统计特征),来判断一批语料中哪些字 ...

  3. CV和NLP中的无监督预训练(生成式BERT/iGPT和判别式SimCLR/SimCSE)

    文 | Smarter 在之前的文章中讲过unsupervised learning主要分为生成式和判别式,那么unsupervised pretrain自然也分为生成式和判别式.目前CV和NLP都出 ...

  4. 实战分享之专业领域词汇无监督挖掘

    作者丨苏剑林 单位丨广州火焰信息科技有限公司 研究方向丨NLP,神经网络 个人主页丨kexue.fm 去年 Data Fountain 曾举办了一个"电力专业领域词汇挖掘"的比赛, ...

  5. 无监督领域迁移及文本表示学习的相关进展

    ©作者|邴立东.何瑞丹.张琰.李俊涛.叶海 单位|阿里巴巴达摩院.新加坡国立大学等 摘要 随着基于 transformer 的预训练语言模型的广泛应用,多种自然语言处理任务在近一两年来都取得了显著突破 ...

  6. 腾讯优图提出LAP无监督多视角人脸3D重建算法,高清还原面部细节

    编辑丨腾讯优图AI开放平台 近日,腾讯优图实验室提出无监督多视角人脸3D重建算法LAP(Learning to Aggregate and Personalize),摆脱人脸3D训练样本真值依赖,高清 ...

  7. 最新综述:视频数据的无监督域适应

    ©作者 | 许悦聪 单位 | 新加坡科技研究局 研究方向 | 视频迁移学习.领域自适应 深度学习随着大数据的不断产生在日常生活生产中发挥着愈来愈重要的作用.针对视频分析的深度学习方法更是随着各类大型数 ...

  8. bert获得词向量_无监督语义相似度匹配之Bert抽取文本特征实战

    今天记一次采用bert抽取句子向量的实战过程,主要是想感受一下bert抽取出来的句子特征向量是否真的具有不错的语义表达. 在此之前,我们来回顾一下,如果我们想拿到一个句子的特征向量可以采用什么样的方式 ...

  9. 无监督去雨论文(一):DerainCycleGAN: Rain Attentive CycleGAN for Single ImageDeraining and Rainmaking

    DerainCycleGAN: Rain Attentive CycleGAN for Single Image Deraining and Rainmaking [pdf (short)] [TIP ...

最新文章

  1. dpo指标详解买入绝技_巴菲特点破A股市场:5年前5178点买入5万元上峰水泥股票,持有到现在会有怎样的收益?从贫穷到富有...
  2. 基于corosync+pacemaker实现主从高可用集群
  3. linux下基本命令
  4. 12 个最佳的免费网络监控工具--转载
  5. MySQL学习笔记06【多表查询、子查询、多表查询练习】
  6. QLineEdit限制数据类型——只能输入浮点型数
  7. wp自定义帖子没标签_ofollow标签的作用有重大变化
  8. 【数据库系统】SQL修改的注意事项
  9. 让你的单细胞数据动起来!|iCellR(一)
  10. github代码_GitHub代码空间如何提高生产力和降低障碍
  11. Hammock for REST
  12. tkinter python(图形开发界面) 转自:渔单渠
  13. MySQL数据库优化概述
  14. 笔记本外接显示器软件调节亮度
  15. 教程篇:《基础模块4》金蝶K3WISE15.0-客户端安装与卸载
  16. 搜款网根据关键词取商品列表 API 返回值说明
  17. PTA 7-94 奇偶数判断
  18. SpringBoot + Java生成证书
  19. wms、wmts和wfs的区别
  20. Linux进阶_PAM认证机制

热门文章

  1. 【论文阅读笔记】Rethinking the Evaluation of Video Summaries 视频摘要评估
  2. python程序执行完后重头开始做烧饼_从“程序员转行卖烧饼”想到IT人创业
  3. 以代码绘制圣诞,过快乐圣诞节!
  4. MFC在两控件间画线
  5. html给input添加边框,html input怎么设置虚线边框样式
  6. 移动APP测试用例设计实践经验
  7. 域名解析-/etc/hosts
  8. mysql 连续天数_mysql计算连续天数,mysql连续登录天数,连续天数统计
  9. cheat sheet 打包打印版大全python R machine learning
  10. HDU 1348(Wall)