nlp 领域中的四范式
学术界将nlp任务的发展过程分为四个阶段,又称为nlp四范式。
第一范式: 基于传统机器学习模型的范式:比如 tfidf 特征 + 朴素贝叶斯的文本分类任务
第二范式 : 基于深度学习 模型的范式:比如word2vec 特征 + lstm的文本分类任务
相比于第一范式,模型准确有所提高,特征工程的工作也有所减少。
第三范式: 基于预训练模型+ fine-tune 的范式: 比如 bert + finetune 的文本分类任务。
相比于第二范式,模型准确度显著提高,但是模型也变得更大,小数据集可以训练出好模型。
第四范式:基于预训练模型+ prompt + 预测的范式: 比如 bert + prompt 的文本分类任务
相比于第三范式,模型训练所需的训练数据显著减少。
从整个发展过程来看,nlp领域朝着精度更高,少监督,甚至无监督的方向发展。
prompt—nlp新范式
prompt是nlp的一个新领域。在prompt中任务的描述被嵌入到输入中,提供了一种新的方式来控制机器学习模型输出。(prompt-based learning模板学习)
prompt是利用预训练语言模型在大量文本数据上获得的知识,来解决各种下游任务。prompt的优势是它可以减少或者避免对预训练模型进行微调,节省计算资源和时间,同时保持或者提高模型的性能和泛化能力。
prompt的方法是根据不同的任务和数据,设计合适的输入格式,包括问题,上下文,前缀,后缀,分隔符等。
prompt可用于提高bert的句子表示能力,通过在bert的输入中加入一些特定的词语作为prompt,引导bert生成更好的句子向量
方法1:在句子的开头或结尾加入prompt
方法2:在句子的中间加入prompt
prompt搜索方法找到最优的prompt,能最大化bert表示能力的prompt。目前有三种主要的搜索方法:
1、随机搜索:随机生成一些prompt,然后用它们作为bert的输入,计算bert的输出向量与目标向量的相似度,选择相似度最高的prompt作为最优的prompt。
2、贪心搜索:从一个空的prompt开始,每次在prompt的末尾加入一个词,然后用它作为bert的输入,计算bert的输出向量与目标向量的相似度,选择相似度最高的词作为prompt的一部分,直到达到一个预设的长度或者相似度阈值。
3、强化学习搜索:将prompt的生成视为一个序列决策问题,使用强化学习的算法,来优化一个策略网络,根据一个奖励函数来更新网络的参数。
原文链接:https://www.jingsailian.com/zlk/294806.html
意图识别
意图识别(intent recognition)是指通过自然语言文本来自动识别出用户的意图或目的的一项技术任务。意图识别的准确性能在很大程度上影响着搜索的准确性和对话系统的智能性,在本赛题中我们需要选手对中文对话进行意图识别。
在人机交互、语音识别、自然语言处理等领域中,意图识别扮演着至关重要的角色。意图识别有很多用途,例如在搜索引擎中分析用户的核心搜索需求,在对话系统中了解用户想要什么业务或者闲聊,在身份识别中判断用户的身份信息等等。意图识别可以提高用户体验和服务质量。
环境配置
实践环境建议以python3.7+,且需要安装如下库:
numpy
pandas
networkx
igraph
数据源
该意图识别数据集是一个多分类任务,目标是根据用户的输入文本判断用户的意图
数据链接: https://pan.baidu.com/s/19_oqy4bc_lja_7mc6lxu7w?pwd=v4bi 提取码: v4bi
训练数据:大约1.2万条中文对话
测试数据:3000条无标注对话
数据读取与分析
import pandas as pd
data_dir = 'https://mirror.coggle.club/dataset/coggle-competition/'
train_data = pd.read_csv(data_dir + 'intent-recognition-train.csv', sep='\t', header=none)
test_data = pd.read_csv(data_dir + 'intent-recognition-test.csv', sep='\t', header=none)
print(train_data.shape,test_data.shape)
train_data[1].value_counts()
import numpy as np
from wordcloud import wordcloud
import matplotlib.pyplot as plt
# the font from github: https://github.com/adobe-fonts
font = r'c:\windows\fonts\simfang.ttf'
wordcloud = wordcloud(background_color="white",
collocations=false,
font_path=font,
width=14000, height=7400,margin=2)
wordcloud.generate(' '.join(words))
plt.imshow(wc)
plt.axis("off")
plt.show()
wc.to_file('show_chinese.png') # 把词云保存下来
tfidf提取与分类
tf-idf(term frequency–inverse document frequency)是一种常见的文本表示方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。用于文本分类任务、信息检索与数据挖掘的常用加权技术。tf是词频(term frequency),idf是逆文本频率指数(inverse document frequency)。
tfidf将文本表示为词项的权重向量,其中每个词项的权重由其在文本中出现的频率和在整个语料库中出现的频率共同决定。tfidf可以反映出词项在文本中的重要程度,越是常见的词项权重越低,越是稀有的词项权重越高。
tfidf的主要思想:如果某个词或短语在一篇文章中出现的频率tf高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
tf-idf的作用:评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。
from sklearn.feature_extraction.text import countvectorizer, tfidfvectorizer, tfidftransformer
stoplist = ' '.join(pd.read_csv('./data_stopwords/百度停用词表.txt', header=none)[0])
#设定分词及清理停用词函数
def m_cut(intxt):
return [w for w in jieba.lcut(intxt) if w not in stoplist and len(w) > 1]
def tfidf_cnt(category):
txt_list = []
corpus =list(train_data[train_data[1] == category][0]) # len=1304
for w in corpus:
txt_list+=[" ".join(m_cut(w))] # 用空格分隔开,才能传入fit_transform中
# txt_list
vectorizer = countvectorizer() # 创建计算词频的实例
x = vectorizer.fit_transform(txt_list) # 将文本中的词语转换为词频稀疏矩阵
transformer = tfidftransformer() # 初始化tf-idf
tfidf = transformer.fit_transform(x) #基于词频稀疏矩阵x计算tf-idf值
word=vectorizer.get_feature_names() #获取词袋模型中的所有词语
weight=tfidf.toarray() #将tf-idf矩阵抽取出来,元素a[i][j]表示j词在i类文本中的tf-idf权重
data_dict = {}
for i in range(len(weight)): #文本的tf-idf词语权重添加到字典data_dict中
for j in range(len(word)):
data_dict[word[j]] = weight[i,j]
# sorted(data_dict.items(),key=lambda x:x[1],reverse=true)[:7] #按照tfidf值倒叙输出前7个
return pd.dataframe(sorted(data_dict.items(),key=lambda x:x[1],reverse=true)[:7])
df_cnt = pd.dataframe()
for i in list(set(train_data[1])):
df = tfidf_cnt(i)
df['category'] = i
df_cnt = pd.concat([df_cnt,df],axis=0)
# 载入停用词
stopwords = set()
with open('./data_stopwords/百度停用词表.txt', 'r',encoding='utf8') as infile:
for line in infile:
line = line.rstrip('\n')
if line:
stopwords.add(line.lower())
from sklearn.feature_extraction.text import tfidfvectorizer
tfidf = tfidfvectorizer(tokenizer=jieba.lcut, stop_words=stopwords, min_df=50, max_df=0.3)
x = tfidf.fit_transform(train_data[0])
# 训练分类器 编码目标变量,sklearn只接受数值
from sklearn.preprocessing import labelencoder #labelencoder:将类别数据数字化
from sklearn.model_selection import train_test_split#分割数据集
y_encoder = labelencoder()
y = y_encoder.fit_transform(train_data[1]) #将类别转换成0,1,2,3,4,5,6,7,8,9...
# 根据y分层抽样,测试数据占20% stratify的作用:保持测试集与整个数据集里result的数据分类比例一致。 pd.concat([pd.dataframe(train_y).value_counts(),pd.dataframe(test_y).value_counts()],axis=1)
train_idx, test_idx = train_test_split(range(len(y)), test_size=0.2, stratify=y)
train_x = x[train_idx, :]
train_y = y[train_idx]
test_x = x[test_idx, :]
test_y = y[test_idx]
# 训练逻辑回归模型 我们是12分类 属于多分类
from sklearn.linear_model import logisticregression#引入逻辑回归
clf_lr= logisticregression(multi_class='multinomial', solver='lbfgs')#solver='lbfgs':求解方式
clf_lr.fit(train_x, train_y)
# 常用参数说明
# penalty: 正则项类型,l1还是l2
# c: 正则项惩罚系数的倒数,越大则惩罚越小
# fit_intercept: 是否拟合常数项
# max_iter: 最大迭代次数
# multi_class: 以何种方式训练多分类模型
# ovr = 对每个标签训练二分类模型
# multinomial ovo = 直接训练多分类模型,仅当solver={newton-cg, sag, lbfgs}时支持
# solver: 用哪种方法求解,可选有{liblinear, newton-cg, sag, lbfgs}
# 小数据liblinear比较好,大数据量sag更快
# 多分类问题,liblinear只支持ovr模式,其他支持ovr和multinomial
# liblinear支持l1正则,其他只支持l2正则
# 模型效果评估
from sklearn.metrics import confusion_matrix, precision_recall_fscore_support
y_pred_lr = clf_lr.predict(test_x)
pd.dataframe(confusion_matrix(test_y, y_pred_lr), columns=y_encoder.classes_, index=y_encoder.classes_)
# 计算各项评价指标
def eval_model(y_true, y_pred, labels):
# 计算每个分类的precision, recall, f1, support
p, r, f1, s = precision_recall_fscore_support(y_true, y_pred)
# 计算总体的平均precision, recall, f1, support
tot_p = np.average(p, weights=s)
tot_r = np.average(r, weights=s)
tot_f1 = np.average(f1, weights=s)
tot_s = np.sum(s)
res1 = pd.dataframe({
u'label': labels,
u'precision': p,
u'recall': r,
u'f1': f1,
u'support': s
})
res2 = pd.dataframe({
u'label': [u'总体'],
u'precision': [tot_p],
u'recall': [tot_r],
u'f1': [tot_f1],
u'support': [tot_s]
})
res2.index = [999]
res = pd.concat([res1, res2])
return res[[u'label', u'precision', u'recall', u'f1', u'support']]
from sklearn.neighbors import kneighborsclassifier
from sklearn import svm
from sklearn.metrics import confusion_matrix, precision_recall_fscore_support
clf_knn = kneighborsclassifier(n_neighbors=12)
clf_knn .fit(train_x, train_y)
y_pred_knn = clf_knn .predict(test_x)
clf_svm = svm.svc(c=1, kernel='rbf', gamma=1, decision_function_shape='ovo')
clf_svm.fit(train_x, train_y)
y_pred_svm = clf_svm.predict(test_x)
eval_model(test_y, y_pred_svm, y_encoder.classes_)
knn/lr/svm 效果对比
模型保存
# 保存模型到文件 pip install dill
# 注意 我们要把tfidf特征提取模型保存 标签转换模型 预测模型
!pip install dill
output_dir = u'output'
if not os.path.exists(output_dir):
os.mkdir(output_dir)
import os
import dill
import pickle
model_file = os.path.join(output_dir, u'model.pkl')
with open(model_file, 'wb') as outfile:
dill.dump({
'y_encoder': y_encoder,
'tfidf': tfidf,
'lr': model
}, outfile)
预测
import pickle
model_file = os.path.join(output_dir, u'model.pkl') # 加载模型
with open(model_file, 'rb') as infile:
model = pickle.load(infile)
# 转化为词袋表示
new_x = model['tfidf'].transform(test_data[0][:50])
# 预测类别
new_y_pred = model['lr'].predict(new_x)
new_y_pred
#array([ 8, 4, 5, 0, 4, 3, 3, 5, 2, 10, 0, 5, 9, 8, 7, 0, 10,
# 11, 0, 11, 10, 3, 11, 0, 11, 3, 3, 10, 5, 0, 11, 5, 7, 10,
# 3, 10, 10, 10, 11, 10, 11, 7, 4, 5, 5, 2, 0, 11, 9, 11])
# 解释类别
pd.dataframe({u'预测类别': model['y_encoder'].inverse_transform(new_y_pred), u'文本': test_data[0][:50]})
lstm
lstm(long short-term memory)是一种特殊的循环神经网络,在文本分类任务中表现良好。lstm可以通过对输入文本进行序列建模来捕捉文本中的长期依赖关系,并对文本进行分类。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcparams['font.sans-serif']=['simhei'] #用来正常显示中文标签
plt.rcparams['axes.unicode_minus'] = false #用来正常显示负号
from matplotlib import font_manager
my_font = font_manager.fontproperties(fname="/library/fonts/songti.ttc") # 设置matplotlib绘图时的字体
from keras.preprocessing.text import tokenizer
from sklearn.preprocessing import labelencoder
import multiprocessing
import tensorflow as tf
from gensim.models.word2vec import word2vec
from gensim.corpora.dictionary import dictionary
from tensorflow.keras.preprocessing import sequence
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import sequential
from tensorflow.keras.layers import lstm, dense, embedding, dropout,activation
import warnings
warnings.filterwarnings('ignore')
train_data = pd.read_csv('train.csv', sep='\t', header=none)
test_data = pd.read_csv('test.csv', sep='\t', header=none)
df_cnt = pd.dataframe(train_data[1].value_counts()).reset_index()
df_cnt.columns=['cat','cnt']
df_cnt.plot(x='cat', y='cnt', kind='bar', legend=false, figsize=(8, 5))
plt.title("类目分布")
plt.ylabel('数量', fontsize=18)
plt.xlabel('类目', fontsize=18)
train_data = pd.read_csv('train.csv', sep='\t', header=none)
train_data.columns = ['text','cat']
train_data['cat_id'] = train_data['cat'].factorize()[0]
test_data = pd.read_csv('test.csv', sep='\t', header=none)
test_data.columns = ['text']
test_data['cat'] = -1
test_data['cat_id'] = -1
# data = pd.concat([train_data,test_data],axis=0)
data = train_data
print(train_data.shape,test_data.shape,data.shape)
cat_id_df = data[['cat', 'cat_id']].drop_duplicates().sort_values('cat_id').reset_index(drop=true)
cat_to_id = dict(cat_id_df.values)
id_to_cat = dict(cat_id_df[['cat_id', 'cat']].values)
#定义删除除字母,数字,汉字以外的所有符号的函数
def remove_punctuation(line):
import re
line = str(line)
if line.strip()=='':
return ''
rule = re.compile(u"[^a-za-z0-9\u4e00-\u9fa5]")
line = rule.sub('',line)
return line
def stopwordslist(filepath):
stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]
return stopwords
#加载停用词
# stopwords = stopwordslist("./data/chinesestopwords.txt")
stopwords = ' '.join(pd.read_csv('./data_stopwords/百度停用词表.txt', header=none)[0])
#删除除字母,数字,汉字以外的所有符号
data['clean_text'] = data['text'].apply(remove_punctuation)
data.sample(10)
#分词,并过滤停用词
data['cut_text'] = data['clean_text'].apply(lambda x: " ".join([w for w in list(jieba.cut(x)) if w not in stopwords]))
# lstm 模型构建
## 将cut_text数据进行向量化处理
## 设置最频繁使用的50000个词 设置每条 cut_text最大的词语数为250个(超过的将会被截去,不足的将会被补0)
# 设置最频繁使用的50000个词
max_nb_words = 50000
# 每条cut_review最大的长度
max_sequence_length = 250
# 设置embeddingceng层的维度
embedding_dim = 100
tokenizer = tokenizer(num_words=max_nb_words, filters='!"#$%&()*+,-./:;<=>?@[\]^_`{|}~', lower=true)
tokenizer.fit_on_texts(data['cut_text'].values)
word_index = tokenizer.word_index
print('共有 %s 个不相同的词语.' % len(word_index))
x = tokenizer.texts_to_sequences(data['cut_text'].values)
#填充x,让x的各个列的长度统一
x = pad_sequences(x, maxlen=max_sequence_length)
#多类标签的onehot展开
y = pd.get_dummies(data['cat_id']).values
print(x.shape)
print(y.shape)
#拆分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x,y, test_size = 0.10, random_state = 42)
print(x_train.shape,y_train.shape)
print(x_test.shape,y_test.shape)
from tensorflow.keras.models import sequential
from tensorflow.keras.layers import lstm, dense, embedding, dropout,activation,spatialdropout1d
from keras.callbacks import modelcheckpoint, earlystopping
model = sequential()
model.add(embedding(max_nb_words, embedding_dim, input_length=x.shape[1]))
model.add(spatialdropout1d(0.2)) # model.add(dropout(0.2))
# spatialdropout1d与dropout的作用类似,但它断开的是整个1d特征图,而不是单个神经元。
# spatialdropout1d能够帮助提高特征图之间的独立性,应该用其取代普通的dropout
model.add(lstm(100, dropout=0.2, recurrent_dropout=0.2))
model.add(dense(12, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
print(model.summary())
epochs = 5
batch_size = 64
model_lstm = model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size,validation_split=0.1,
callbacks=[earlystopping(monitor='val_loss', patience=3, min_delta=0.0001)])
def predict(text):
txt = remove_punctuation(text)
txt = [" ".join([w for w in list(jieba.cut(txt)) if w not in stopwords])]
seq = tokenizer.texts_to_sequences(txt)
padded = pad_sequences(seq, maxlen=max_sequence_length)
pred = model.predict(padded)
cat_id= pred.argmax(axis=1)[0]
res = cat_id_df[cat_id_df.cat_id==cat_id]['cat'].values[0]
return res
from sklearn.metrics import classification_report
print('accuracy %s' % accuracy_score(y_pred, y_test))
print(classification_report(y_test, y_pred,target_names=cat_id_df['cat'].values))
plt.title('loss')
plt.plot(model_lstm.history['loss'], label='train')
plt.plot(model_lstm.history['val_loss'], label='test')
plt.legend()
plt.show()
plt.title('accuracy')
plt.plot(model_lstm.history['accuracy'], label='train')
plt.plot(model_lstm.history['val_accuracy'], label='test')
plt.legend()
plt.show()
import seaborn as sns
from sklearn.metrics import accuracy_score, confusion_matrix
y_pred = model.predict(x_test)
y_pred = y_pred.argmax(axis = 1)
y_test = y_test.argmax(axis = 1)
conf_mat = confusion_matrix(y_test, y_pred)
fig, ax = plt.subplots(figsize=(10,8))
sns.heatmap(conf_mat, annot=true, fmt='d',
xticklabels=cat_id_df.cat.values, yticklabels=cat_id_df.cat.values)
plt.ylabel('实际结果',fontsize=18)
plt.xlabel('预测结果',fontsize=18)
参考资料:
https://www.jianshu.com/p/890652ce85f5
https://www.cnblogs.com/modifyrong/p/8546421.html
https://github.com/jiangnanboy/intent_detection_and_slot_filling/tree/master/model6
prompt方法综述: https://zhuanlan.zhihu.com/p/431788068
prompt pre-training:https://zhuanlan.zhihu.com/p/428512183
发表评论