大家好,欢迎来到IT知识分享网。
一、文本预处理
1.jiba工具的使用
基本使用
import jieba content="工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作" jieba.cut(content,cut_all = False) //精确模式切割,返回生成器对象 jieba.lcut(content,cut_all = False) //精确模式切割,返回具体切割内容 //默认的情况下是精确分割 jieba.cut(content,cut_all = True) //全模式切割 jieba.lcut(content,cut_all = True) //全模式切割 jieba.cut_for_search(content) //搜索引擎模式分词 jieba.lcut_for_search(content) //搜索引擎模式分词
import jieba result4 = jieba.lcut("八一双鹿更名为八一南昌篮球队!") print("未使用自定义词典:",result4) jieba.load_userdict("./userdict.txt") result5 = jieba.lcut("八一双鹿更名为八一南昌篮球队!")
2.流行中英文分词工具hanlp
使用pip安装 pip install hanlp
import hanlp //使用hanlp进行中文分词 tokenizer = hanlp.load('CTB6_CONVSEG') tokenizer("工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作") //使用hanlp进行英文分词 from hanlp.utils.lang.en.english_tokenizer import tokenize_english tokenizer = tokenize_english tokenizer('My Hanks bought hanks.com for 1.5 thousand dollars.')
3.命名实体识别NER
使用hanlp进行中文命名实体识别
import hanlp recognizer = hanlp.load(hanlp.pretrained.ner.MSRA_NER_BERT_BASE_ZH) list('上海华安工业(集团)公司董事长谭旭光和秘书长晚霞来到美国纽约现代艺术博物馆参观') recognizer(list('上海华安工业(集团)公司董事长谭旭光和秘书长晚霞来到美国纽约现代艺术博物馆参观'))
使用hanlp进行英文命名实体识别
import hanlp recognizer = hanlp.load(hanlp.pretrained.ner.CONLL03_NER_BERT_BASE_UNCASED_EN) recognizer(["Presient","Obama","is","speaking","at","the","White","House"])
4. 词性标注POS
使用jieba进行词性标注
import jieba.posseg as pseg pseg.lcut("我爱北京天安门")
使用hanlp进行中文词性标注
import hanlp tagger = hanlp.load(hanlp.pretained.pos.CTB5_POS_RNN_FASTTEXT_ZH) tagger(["我","的","希望","是","希望","和平"])
使用hanlp进行英文词性标注
import hanlp tagger = hanlp.load(hanlp.pretained.pos.PTB_POS_RNN_FASTTEXT_EN) tagger(['I','banked','2','dollars','in','a','bank','.'])
5.文本张量的表示方法
5.1 one-hot词向量表示
优点:操作简单,容易理解 缺点:完全割裂了词与词之间的关系,而且大语料集下,每个向量的长度过大,占据大量内存
from sklearn.externals import joblib from keras.preprocessing.text import Tokenizer vocab ={
"周杰伦","陈奕迅","鹿晗","李宗盛","李荣浩"} //实例化一个词汇映射对象 t = Tokenizer(num_words=None,char_level=False) //使用词汇映射器拟合现有文本数据 t.fit_on_texts(vocab) for token in vocab: zero_list = [0]*len(vocab) //初始化全0序列 token_index = t.texts_to_sequences([token])[0][0]-1 //取出其中的数字需要用[0][0] zero_list[token_index] = 1 print(token,"的one-hot编码为:",zero_list) 使用jolib工具保存映射器,以便以后使用 tokenizer_path = "./Tokenizer" joblib.dump(t,tokenizer_path)
对保存下来的映射器再次使用
from sklear.externals import joblib t = joblib.load("./Tokenizer") token = "周杰伦" token_index = t.texts_to_sequences([token])[0][0]-1 zero_list = [0]*len(vocab) zero_list[token_index] = 1 print(token,"的one-hot编码为:",zero_list)
5.2 word2vec词向量表示
- 表示成向量的无监督训练方法,包含CBOW和skipgram两种训练模式。One-hot表示无法显示出两个词之间的相似关系 - Word2vec 本质上是一种降维操作:把词语从 one-hot encoder 形式的表示降维到 Word2vec 形式的表示 - 模型为浅层双层的神经网络,用来训练以重新构建语言学之词文本。 - 网络以词表现,并且需要猜测相邻位置的输入词,在word2vec中词袋模型假设下,词的顺序是不重要的。 - 缺点:没有考虑多义词、窗口长度有限、没有考虑全局的文本信息、不是严格意义的语序 - CBOW(Continous bag of words)模式:使用上下文词汇预测目标词汇
- skipgram模式使用目标词汇预测上下文词汇
隐藏层的神经元应该设置多少个,取决于希望得到的词向量的维数,google给出的经验值是300。 假设某词向量的编码是8维的,隐藏层神经元有3个,那隐藏层的权重就是一个8行3列的矩阵。 网络训练完成后,隐藏层权重的每一行代表一个词向量。 只需要保存隐藏层的权重矩阵,又因为输入是one-hot编码,所以用输入向量乘以这个权重矩阵就得到了对应的词向量 CBOW计算流程的不同之处在于隐藏层不再是取一个词的词向量各维,而是上下文C个词的词向量各维的平均值
一般来说, Skip-Gram模型比CBOW模型更好,因为: - Skip-Gram模型有更多的训练样本。Skip-Gram是一个词预测n个词,而CBOW是n个词预测一个词。 - 误差反向更新中,CBOW是中心词误差更新n个周边词,这n个周边词被更新的力度是一样的。而Skip-Gram中,每个周边词都可以根据误差更新中心词,所以Skip-Gram是更细粒度的学习方法。 - Skip-Gram效果更好,但是缺点就是训练次数更多,时间更长。
- fastText 模型架构和 Word2Vec 中的 CBOW 模型很类似。不同之处在于,fastText 预测标签,而 CBOW 模型预测中间词。 - fastText只有CBOW模型,对应fastText.train_supervised 没有model参数。 - Word2Vec有两种模型,所以fastText.train_unsupervised可以选择model={cbow, skipgram} ,默认skipgram。 - fastText 本身是词袋模型,为了分类的准确性,所以加入了 N-gram 特征提取词序信息。 - n-gram的问题是词表会急剧扩大 ,没有机器扛得住。所以使用散列法(Hash)对n-gram特征进行压缩。
- 代码实践1
import numpy as np import pandas as pd import pickle import jieba import os from tqdm import tqdm def load_stop_words(file="stopwords.txt"): with open(file, "r", encoding="utf-8") as f: return f.read().split("\n") # 用\n切分 def cut_words(file="数学原始数据.csv"): # 停用词列表 stop_words = load_stop_words() # 结果列表 result = [] all_data = pd.read_csv(file, encoding="gbk", names=["data"])["data"] for words in all_data: c_words = jieba.lcut(words) # 去掉停用词 result.append([word for word in c_words if word not in stop_words]) return result def get_dict(data): index_2_word = [] # 对词库遍历 for words in data: for word in words: # 检查是否已经存在于词库里面,若不存在则加进去 if word not in index_2_word: index_2_word.append(word) word_2_index = {
word:index for index,word in enumerate(index_2_word)} word_size = len(word_2_index) word_2_onehot = {
} for word, index in word_2_index.items(): # 先构建全为0的向量 one_hot = np.zeros((1, word_size)) # 相应位置赋值为1 one_hot[0, index] = 1 # 添加键值对 word_2_onehot[word] = one_hot return word_2_index, index_2_word, word_2_onehot def softmax(x): ex = np.exp(x) # axis指定求和的方向,keepdims保持维度 return ex/np.sum(ex, axis=1, keepdims=True) if __name__ == "__main__": data = cut_words() word_2_index, index_2_word, word_2_onehot = get_dict(data) word_size = len(word_2_index) # 预设的词向量维度,一般是100——300 embedding_num = 107 # 学习率 lr = 0.01 # 训练次数 epoch = 10 # 相关词数量 n_gram = 3 # 初始化矩阵 w1 = np.random.normal(-1, 1, size=(word_size, embedding_num)) w2 = np.random.normal(-1, 1, size=(embedding_num, word_size)) for e in range(epoch): for words in tqdm(data): for n_index, now_word in enumerate(words): now_word_onehot = word_2_onehot[now_word] # 获取上下文 other_words = words[max(n_index-n_gram, 0):n_index] + words[n_index+1:n_index+1+n_gram] for other_word in other_words: other_word_onehot = word_2_onehot[other_word] # 隐藏层 hidden = now_word_onehot @ w1 p = hidden @ w2 pre = softmax(p) # loss = -np.sum(other_word_onehot * np.log(pre)) # A @ B = C # delta_C = G # delta_A = G @ B.T # delta_B = A.T @ G G2 = pre - other_word_onehot delta_w2 = hidden.T @ G2 G1 = G2 @ w2.T delta_w1 = now_word_onehot.T @ G1 w1 -= lr * delta_w1 w2 -= lr * delta_w2 with open("word2vec.pkl", "wb") as f: pickle.dump([w1, word_2_index, index_2_word], f) # word2vec 负采样
import pickle import numpy as np # w1, voc_index, index_voc, w2 = pickle.load(open('word2vec.pkl', 'rb')) w1, voc_index, index_voc = pickle.load(open('word2vec.pkl', 'rb')) def word_voc(word): return w1[voc_index[word]] def voc_sim(word, top_n): v_w1 = word_voc(word) word_sim = {
} for i in range(len(voc_index)): v_w2 = w1[i] theta_sum = np.dot(v_w1, v_w2) theta_den = np.linalg.norm(v_w1) * np.linalg.norm(v_w2) theta = theta_sum / theta_den word = index_voc[i] word_sim[word] = theta words_sorted = sorted(word_sim.items(), key=lambda kv: kv[1], reverse=True) for word, sim in words_sorted[:top_n]: print(word, sim) voc_sim('整数',20)
5.3 word embedding
- 通过一定的方式将词汇映射到指定维度的空间 - 广义的word embedding包括所有密集词汇向量的表示方法,Word2vec可认为是word embedding的一种 - 狭义的word embedding是指在神经网络中加入的embedding层,对整个网络训练的同时产生的embedding矩阵。
6.文本数据分析
作用:理解数据语料 常用方法: - 标签数量分布 - 句子长度分布 - 词频统计与关键词云
6.1 标签数量分布
在深度学习模型评估中, 我们一般使用ACC作为评估指标, 若想将ACC的基线定义在50%左右, 则需要我们的正负样本比例维持在1:1左右, 否则就要进行必要的数据增强或数据删减. 若训练和验证集正负样本都稍有不均衡, 可以进行一些数据增强.
https://www.jianshu.com/p/15af3193be5b
# comments.tsv中的数据内容共分为2列, 第2列数据review代表具有感情色彩的评论文本; 第1列数据label, 0或1, 代表每条文本数据是积极或者消极的评论, 0代表消极, 1代表积极. import seaborn as sns import pandas as pd import matplotlib.pyplot as plt # 设置显示风格 plt.style.use('fivethirtyeight') # 读取训练tsv train_data = pd.read_csv("./data/comments.csv",sep=",") # 获得训练数据标签数量分布 sns.countplot("label",data = train_data) plt.title("train_data") plt.show()
6.2 句子长度分布
通过绘制句子长度分布图, 可以得知语料中大部分句子长度的分布范围, 因为模型的输入要求为固定尺寸的张量, 合理的长度范围对之后进行句子截断补齐(规范长度)起到关键的指导作用.
# 在训练数据中添加新的句子长度列, 每个元素的值都是对应的句子列的长度 train_data["sentence_length"] = list(map(lambda x: len(x), train_data["review"])) # 绘制句子长度列的数量分布图 sns.countplot("sentence_length", data=train_data) # 主要关注count长度分布的纵坐标, 不需要绘制横坐标, 横坐标范围通过dist图进行查看 plt.xticks([]) plt.show() # 绘制dist长度分布图 sns.distplot(train_data["sentence_length"]) # 主要关注dist长度分布横坐标, 不需要绘制纵坐标 plt.yticks([]) plt.show()
散点分布图:通过查看正负样本长度散点图, 可以有效定位异常点的出现位置, 帮助我们更准确进行人工语料审查.
# 绘制长度分布的散点图 sns.stripplot(y='sentence_length',x='label',data=train_data) plt.show()
6.3 获得数据集不同词汇总数统计
chain的作用
from itertools import chain a = [1, 2, 3, 4] b = [‘x’, ‘y’, ‘z’] for x in chain(a, b): print(x)
输出: 1 2 3 4 x y z
# 导入jieba用于分词 # 导入chain方法用于扁平化列表 import jieba from itertools import chain # 进行数据集的句子进行分词, 并统计出不同词汇的总数 train_vocab = set(chain(*map(lambda x: jieba.lcut(x), train_data["review"]))) print("数据集共包含不同词汇总数为:", len(train_vocab))
6.4 获得数据集上正负的样本的高频形容词词云
根据高频形容词词云显示, 我们可以对当前语料质量进行简单评估, 同时对违反语料标签含义的词汇进行人工审查和修正, 来保证绝大多数语料符合训练标准.
# 使用jieba中的词性标注功能 import jieba.posseg as pseg import matplotlib.pyplot as plt from itertools import chain # 导入绘制词云的工具包 from wordcloud import WordCloud import pandas as pd def get_a_list(text): """用于获取形容词列表""" # 使用jieba的词性标注方法切分文本,获得具有词性属性flag和词汇属性word的对象, # 从而判断flag是否为形容词,来返回对应的词汇 r = [] for g in pseg.lcut(text): if g.flag == "a": r.append(g.word) return r def get_word_cloud(keywords_list): # 实例化绘制词云的类, 其中参数font_path是字体路径, 为了能够显示中文, # max_words指词云图像最多显示多少个词, background_color为背景颜色 wordcloud = WordCloud(font_path="/data/simkai.ttf", max_words=100, background_color="white") # 将传入的列表转化成词云生成器需要的字符串形式 keywords_string = " ".join(keywords_list) # 生成词云 wordcloud.generate(keywords_string) # 绘制图像并显示 plt.figure() # interpolation参数,此参数显示了不同图像之间的插值方式 # https://matplotlib.org/stable/gallery/images_contours_and_fields/interpolation_methods.html plt.imshow(wordcloud, interpolation="bilinear") # 关闭坐标轴 plt.axis("off") plt.show() # 设置显示风格 plt.style.use('fivethirtyeight') train_data = pd.read_csv("./data/comments.csv", sep=",") train_data["review"] = '"'+train_data["review"].astype(str)+'"' # 获得训练集上正样本 p_train_data = train_data[train_data["label"]==1]["review"] # 对正样本的每个句子的形容词 train_p_a_vocab = chain(*map(lambda x: get_a_list(x), p_train_data)) # 获得训练集上负样本 n_train_data = train_data[train_data["label"]==0]["review"] # 获取负样本的每个句子的形容词 train_n_a_vocab = chain(*map(lambda x: get_a_list(x), n_train_data)) # 调用绘制词云函数 get_word_cloud(train_p_a_vocab) get_word_cloud(train_n_a_vocab)
7. 文本特征处理
文本特征处理包括为语料添加具有普适性的文本特征, 如:n-gram特征, 以及对加入特征之后的文本语料进行必要的处理, 如: 长度规范. 这些特征处理工作能够有效的将重要的文本特征加入模型训练中, 增强模型评估指标。 常见的文本特征处理方法:添加n-gram特征、文本长度规范
7.1 n-gram特征
给定一段文本序列, 其中n个词或字的相邻共现特征即n-gram特征, 常用的n-gram特征是bi-gram和tri-gram特征, 分别对应n为2和3。
例:
假设给定分词列表: ["是谁", "敲动", "我心"],对应的数值映射列表为: [1, 34, 21] 我们可以认为数值映射列表中的每个数字是词汇特征. 除此之外, 我们还可以把"是谁"和"敲动"两个词共同出现且相邻也作为一种特征加入到序列列表中, 假设1000就代表"是谁"和"敲动"共同出现且相邻 此时数值映射列表就变成了包含2-gram特征的特征列表: [1, 34, 21, 1000] 这里的"是谁"和"敲动"共同出现且相邻就是bi-gram特征中的一个. "敲动"和"我心"也是共现且相邻的两个词汇, 因此它们也是bi-gram特征. 假设1001代表"敲动"和"我心"共同出现且相邻 那么, 最后原始的数值映射列表 [1, 34, 21] 添加了bi-gram特征之后就变成了 [1, 34, 21, 1000, 1001]
# 一般n-gram中的n取2或者3, 这里取2为例 ngram_range = 2 def create_ngram_set(input_list): # """ # description: 从数值列表中提取所有的n-gram特征 # :param input_list: 输入的数值列表, 可以看作是词汇映射后的列表, # 里面每个数字的取值范围为[1, 25000] # :return: n-gram特征组成的集合 # # eg: # >>> create_ngram_set([1, 4, 9, 4, 1, 4]) # {(4, 9), (4, 1), (1, 4), (9, 4)} # """ return set(zip(*[input_list[i:] for i in range(ngram_range)])) input_list = [1, 3, 2, 1, 5, 3] res = create_ngram_set(input_list) print(res)
n-gram模型
N-Gram是一种基于统计语言模型的算法。它的基本思想是将文本里面的内容按照字节进行大小为N的滑动窗口操作,形成了长度是N的字节片段序列。
每一个字节片段称为gram,对所有gram的出现频度进行统计,并且按照事先设定好的阈值进行过滤,形成关键gram列表,也就是这个文本的向量特征空间,列表中的每一种gram就是一个特征向量维度。
该模型基于这样一种假设,第N个词的出现只与前面N-1个词相关,而与其它任何词都不相关,整句的概率就是各个词出现概率的乘积。 这些概率可以通过直接从语料中统计N个词同时出现的次数得到。
常用的是二元的Bi-Gram和三元的Tri-Gram。
ngram应用:https://zhuanlan.zhihu.com/p/32829048
N-gram特征的优缺点: N-gram特征的优点是简单易用,可以捕捉文本中的局部信息,对于文本分类、信息检索等任务效果较好;缺点是需要考虑N的取值,当N取值较大时,特征空间会变得非常庞大,可能会导致维数灾难。
N-gram特征的改进方法: 为了克服N-gram特征的缺点,可以采用一些改进方法,例如,使用tf-idf权重对N-gram进行加权,使用停用词过滤掉一些无用的N-gram,使用词干提取等方法对N-gram进行预处理。
7.2 文本长度规范
一般模型的输入需要等尺寸大小的矩阵, 因此在进入模型前需要对每条文本数值映射后的长度进行规范, 此时将根据句子长度分布分析出覆盖绝大多数文本的合理长度, 对超长文本进行截断, 对不足文本进行补齐(一般使用数字0), 这个过程就是文本长度规范.
from keras.preprocessing import sequence # cutlen根据数据分析中句子长度分布,覆盖90%左右语料的最短长度. # 这里假定cutlen为10 cutlen = 10 def padding(x_train): """ description: 对输入文本张量进行长度规范 :param x_train: 文本的张量表示, 形如: [[1, 32, 32, 61], [2, 54, 21, 7, 19]] :return: 进行截断补齐后的文本张量表示 """ # 使用sequence.pad_sequences即可完成 return sequence.pad_sequences(x_train, cutlen) # 假定x_train里面有两条文本, 一条长度大于10, 一天小于10 x_train = [[1, 23, 5, 32, 55, 63, 2, 21, 78, 32, 23, 1], [2, 32, 1, 23, 1]] res = padding(x_train) print(res)
7.3 文本数据增强
回译数据增强目前是文本数据增强方面效果较好的增强方法, 一般基于google翻译接口, 将文本数据翻译成另外一种语言(一般选择小语种),之后再翻译回原语言, 即可认为得到与与原语料同标签的新语料, 新语料加入到原数据集中即可认为是对原数据集数据增强.
回译数据增强优势:操作简便, 获得新语料质量高. 回译数据增强存在的问题:在短文本回译过程中, 新语料与原语料可能存在很高的重复率, 并不能有效增大样本的特征空间. 高重复率解决办法::进行连续的多语言翻译, 如: 中文-->韩文-->日语-->英文-->中文, 根据经验, 最多只采用3次连续翻译, 更多的翻译次数将产生效率低下, 语义失真等问题.
代码实现:由于google翻译接口不免费了,这里用的有道翻译接口。
import requests import time import random import hashlib def youdao_translate_advance(text, src_lang, to_lang): url = 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule' user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36' headers = {
'User-Agent': user_agent, 'Referer': 'http://fanyi.youdao.com/', 'Origin': 'http://fanyi.youdao.com', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Connection': 'keep-alive', 'Host': 'fanyi.youdao.com', 'cookie': '_ntes_nnid=937f1c788f1e087cf91ddc536a,84; OUTFOX_SEARCH_USER_ID_NCOO=; OUTFOX_SEARCH_USER_ID=-@11.136.67.24; JSESSIONID=; ___rl__test__cookies=1' } lts = str(round(time.time() * 1000)) salt = lts + str(random.randint(1, 10)) strange_str = 'n%A-rKaT5fb[Gy?;N5@Tj' # 'p09@Bn{h02_BIEe]$P^nG' sign = hashlib.md5(('fanyideskweb' + text + salt + strange_str).encode('utf-8')).hexdigest() bv = hashlib.md5(user_agent.encode('utf-8')).hexdigest() data = {
'i': text, 'from': src_lang, 'to': to_lang, 'smartresult': 'dict', 'client': 'fanyideskweb', 'salt': salt, # 当前毫秒时间戳与10以内随机数字字符串的拼接 'sign': sign, # 'fanyideskweb' + text + salt + strange_str的md5值 'lts': lts, # 当前毫秒时间戳 'bv': bv, # 浏览器平台和版本信息的md5值 'doctype': 'json', 'version': '2.1', 'keyfrom': 'fanyi.web', 'action': 'FY_BY_CLICKBUTTION', } response = requests.post(url=url, headers=headers, data=data) res = response.json()['translateResult'][0][0]['tgt'] return res p_sample1 = "酒店设施非常不错" p_sample2 = "这家价格很便宜" n_sample1 = "拖鞋都发霉了, 太差了" n_sample2 = "电视不好用, 没有看到足球" translations = [] for yuanwen in [p_sample1, p_sample2, n_sample1, n_sample2]: translations.append(youdao_translate_advance(yuanwen,'zh-CHS', 'ko')) print(translations) cn_res = [] for yiwen in translations: cn_res.append(youdao_translate_advance(yiwen, 'ko', 'zh-CHS',)) print(cn_res)
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/134447.html