一 语料准备

1. 分词词典

最终词典的格式:

词语 词性(不要和jieba默认的词性重复)
在这里插入图片描述

1.1 词典来源
  1. 各种输入法的词典

    例如:https://pinyin.sogou.com/dict/cate/index/97?rf=dictindex

    例如:https://shurufa.baidu.com/dict_list?cid=211

  2. 手动收集,根据目前的需求,我们可以手动收集如下词典

    1. 机构名称,例如:传智传智播客,黑马程序员
    2. 课程名词,例如:python,人工智能+pythonc++
1.2 词典处理

输入法的词典都是特殊格式,需要使用特殊的工具才能够把它转化为文本格式

工具名称:深蓝词库转换.exe

下载地址:https://github.com/studyzy/imewlconverter

1.3 对多个词典文件内容进行合并

下载使用不同平台的多个词典之后,把所有的txt文件合并到一起供之后使用

2. 准备停用词

2.1 什么是停用词?

对句子进行分词之后,句子中不重要的词

2.2 停用词的准备

常用停用词下载地址:https://github.com/goto456/stopwords

2.3 手动筛选和合并

对于停用词的具体内容,不同场景下可能需要保留和去除的词语不一样

比如:词语哪个,很多场景可以删除,但是在判断语义的时候则不行

3. 问答对的准备

3.1 现有问答对的样式

问答对有两部分,一部分是咨询老师整理的问答对,一部分是excel中的问答对,

最终我们需要把问答对分别整理到两个txt文档中,如下图(左边是问题,右边是答案):
在这里插入图片描述

Excel中的问答对如下图:

在这里插入图片描述

3.2 excel中问答对的处理

Excel中的问答对直接使用pandas就能够处理

python_qa_path = "./data/Python短问答-11月汇总.xlsx"
def load_duanwenda():
    import pandas as pd
    ret = pd.read_excel(python_qa_path)
    column_list = ret.columns
    assert '问题' in column_list and "答案" in column_list,"excel 中必须包含问题和答案"
    for q,a in zip(ret["问题"],ret["答案"]):
        q = re.sub("\s+"," ",q)
        q = q.strip()
        print(q,a)

4. 相似问答对的采集

4.1 采集相似问答对的目的

后续在判断问题相似度的时候,需要有语料用来进行模型的训练,输入两个句子,输出相似度,这个语料不好获取,所以决定从百度知道入手,采集百度知道上面的相似问题,如下图所示:

在这里插入图片描述

上面采集的数据会存在部分噪声,部分问题搜索到的结果语义上并不是太相似

4.2 手动构造数据

根据前面的问答对的内容,把问题大致分为了若干类型,对不同类型的问题设计模板,然后构造问题,问题模块如下:

templete = [
#概念
["{kc}","什么是{kc}","{kc}是什么","给我介绍一下{kc}","{kc}可以干什么","能简单说下什么是{kc}吗","我想了解{kc}"],

#课程优势
["{kc}课程有什么特点","{jgmc}的{kc}课程有什么特点","{jgmc}的{kc}课程有什么优势","为什么我要来{jgmc}学习{kc}","{jgmc}的{kc}课程有什么优势","为什么要到{jgmc}学习{kc}","{jgmc}的{kc}跟其他机构有什么区别?","为什么选择{jgmc}来学习{kc}?"],

#语言优势
#["{kc}","什么是{kc}","{kc}是什么","给我介绍一下{kc}","{kc}可以干什么","能简单说下什么是{kc}吗"], 
#特点
["{kc}有什么特点","{kc}有什么优势","{kc}有什么亮点","{kc}有那些亮点","{kc}有那些优势","{kc}有那些特点","{kc}的亮点是什么","{kc}的优势是什么","{kc}的特点是什么"],
#发展前景
["{kc}的发展怎么样?","{kc}的前景怎么样?","{kc}的发展前景如何?","{kc}的未来怎样","{kc}的前景好么" ],
#就业
["{kc}好就业么","{kc}就业机会多么","{kc}的岗位多吗","{kc}工作好找吗","{kc}的市场需求怎么样","{kc}的就业环境怎么样"],
#就业方向
["{kc}学完以后能具体从事哪方面工作?","{kc}的就业岗位有哪些?","{kc}课程学完应聘哪方面工作?","{kc}可以从事哪方面工作?",],
#用途
["{kc}学完可以做什么","{kc}能干什么","学{kc}能干什么","能举例说下{kc}能做什么吗?","{kc}毕业了能干什么","{kc}主要应用在什么领域"],
#就业薪资
["{kc}学完工资多少","学完{kc}能拿多少钱","{kc}的就业薪资多少","{kc}就业的平均是工资多少"],
#学习难度
["{kc}简单么","{kc}容易么","{kc}课程容易么","{kc}上手快么","{kc}课程难么"],
#校区
["在那些城市开设了{kc}","哪里可以学习{kc}","学习{kc}可以去那些城市","{kc}在哪里开班了"],
#学费
["{kc}学费","{kc}多少钱","{kc}的学费多少","{kc}是怎么收费的?","学习{kc}要花多少钱","{kc}是怎么收费的","{kc}课程的价格","{kc}课程的价格是多少"],
#适合人群
["什么人可以学{kc}","哪些人可以学{kc}","学习{kc}有什么要求","学习{kc}需要那些条件","没有基础可以学{kc}吗","学历低可以学习{kc}吗?","成绩不好可以学习{kc}吗?","什么样的人适合学习{kc}?"],
#学习时间
["{kc}需要学多久","{kc}需要多久才能就业","{kc}需要学习多长时间","{kc}的学时是多少","{kc}的课时是多少","{kc}课时","{kc}课时长度","{kc}的课程周期?","0基础学{kc}多久能才就业"],
#学习内容
["{kc}学什么","{kc}学习那些内容","我们在{kc}中学习那些内容","在{kc}中大致都学习什么内容"],
#项目内容
["{kc}的项目有哪些","{kc}有哪些项目","{kc}上课都有哪些实战","{kc}做什么项目?","{kc}项目有多少个?","{kc}课程中有项目吗?"],
#学习某课程的好处
["为什么要学习{kc}?","学习{kc}有哪些好处?","学习{kc}的理由?","为什么我要来学习{kc}"],
#上课时间
["上课时间","你们那边每天的上课时间是怎样的呢?"],
#英语要求
["学习{kc}对英语有要求么","来{jgmc}学习对英语有要求吗?"]
]

其中大括号的内容kc表示课程,jgmc表示机构名称

接下来,需要完成两件事

  1. 最终会把前面准备好的课程字典和机构名称字典中的词语放入大括号中
  2. 把kc相同的内容构造成相似问题

二 文本分词

1. 准备词典和停用词

1.1 准备词典

1.2 准备停用词

stopwords = set([i.strip() for i in open(config.stopwords_path).readlines()])

2. 准备按照单个字切分句子的方法

def _cut_by_word(sentence):
    # 对中文按照字进行处理,对英文不分为字母
    sentence = re.sub("\s+"," ",sentence)
    sentence = sentence.strip()
    result = []
    temp = ""
    for word in sentence:
        if word.lower() in letters:
            temp += word.lower()
        else:
            if temp != "": #不是字母
                result.append(temp)
                temp = ""
            if word.strip() in filters: #标点符号
                continue
            else: #是单个字
                result.append(word)
    if temp != "": #最后的temp中包含字母
        result.append(temp)
    return result

3. 完成分词方法的封装

lib 下创建cut_sentence.py文件,完成分词方法的构建

import logging
import jieba
import jieba.posseg as psg
import config
import re
import string

#关闭jieba log输出
jieba.setLogLevel(logging.INFO)
#加载词典
jieba.load_userdict(config.keywords_path)
#单字分割,英文部分
letters = string.ascii_lowercase
#单字分割 去除的标点
filters= [",","-","."," "]
#停用词
stopwords = set([i.strip() for i in open(config.stopwords_path).readlines()])


def cut(sentence,by_word=False,use_stopwords=False,with_sg=False):
    assert by_word!=True or with_sg!=True,"根据word切分时候无法返回词性"
    if by_word:
        return _cut_by_word(sentence)
    else:
        ret = psg.lcut(sentence)
        if use_stopwords:
            ret = [(i.word,i.flag) for i in ret if i.word not in stopwords]
        if not with_sg:
            ret = [i.word for i in ret]
        return ret


def _cut_by_word(sentence):
    # 对中文按照字进行处理,对英文不分为字母
    sentence = re.sub("\s+"," ",sentence)
    sentence = sentence.strip()
    result = []
    temp = ""
    for word in sentence:
        if word.lower() in letters:
            temp += word.lower()
        else:
            if temp != "": #不是字母
                result.append(temp)
                temp = ""
            if word.strip() in filters: #标点符号
                continue
            else: #是单个字
                result.append(word)
    if temp != "": #最后的temp中包含字母
        result.append(temp)
    return result

三 分类的目的和分类的方法

1. 文本分类的目的

回顾之前的流程,可以发现文本分类的目的就是为了进行意图识别

在当前项目下,只有两种意图需要被识别出来,所以对应的是2分类的问题

可以想象,如果聊天机器人有多个功能,那么需要分类的类别就有多个,这样就是一个多分类的问题。例如,如果希望聊天机器人能够播报当前的时间,那么就需要准备关于询问时间的语料,同时其目标值就是一个新的类别。在训练后,通过这个新的模型,判断出用户询问的是当前的时间这个类别,那么就返回当前的时间。

同理,如果还希望聊天机器人能够播报未来某一天的天气,那么这个机器人就还需要增加一个新的进行分类的意图,重新进行训练

2. 机器学习中常见的分类方法

在前面的机器学习的课程中我们学习了朴素贝叶斯决策树等方法都能够帮助我们进行文本的分类,那么具体该怎么做呢?

2.1 步骤

  1. 特征工程:对文本进行处理,转化为能够被计算的向量来表示。我们可以考虑使用所有词语的出现次数,也可以考虑使用tfidf这种方法来处理
  2. 对模型进行训练
  3. 对模型进行评估

2.2 优化

使用机器学习的方法进行文本分类的时候,为了让结果更好,经常从两个角度出发

  1. 特征工程的过程中处理的更加细致,比如文本中类似你,我,他这种词语可以把它剔除;某些词语出现的次数太少,可能并不具有代表意义;某些词语出现的次数太多,可能导致影响的程度过大等等都是可以考虑的地方
  2. 使用不同的算法进行训练,获取不同算法的结果,选择最好的,或者是使用集成学习方法

3. 深度学习实现文本分类

前面简单回顾了使用机器学习如何来进行文本分类,那么使用深度学习该如何实现呢?

在深度学习中我们常见的操作就是:

  1. 对文本进行embedding的操作,转化为向量
  2. 之后再通过多层的神经网络进行线性和非线性的变化得到结果
  3. 变换后的结果和目标值进行计算得到损失函数,比如对数似然损失等
  4. 通过最小化损失函数,去更新原来模型中的参数

四 fastText实现文本分类

1. fastText的介绍

文档地址:https://fasttext.cc/docs/en/support.html

fastText is a library for efficient learning of word representations and sentence classification.

fastText是一个单词表示学习和文本分类的库

优点:在标准的多核CPU上, 在10分钟之内能够训练10亿词级别语料库的词向量,能够在1分钟之内给30万多类别的50多万句子进行分类。

fastText 模型输入一个词的序列(一段文本或者一句话),输出这个词序列属于不同类别的概率。

2. 安装和基本使用

2.1 安装步骤:

  1. 下载 git clone https://github.com/facebookresearch/fastText.git
  2. cd cd fastText
  3. 安装 python setup.py install
2.2 基本使用
  1. 把数据准备为需要的格式

  2. 进行模型的训练、保存和加载、预测

    #1. 训练
    model = fastText.train_supervised("./data/text_classify.txt",wordNgrams=1,epoch=20)
    #2. 保存
    model.save_model("./data/ft_classify.model")
    #3. 加载
    model = fastText.load_model("./data/ft_classify.model")
    
    textlist = [句子1,句子2]
    #4. 预测,传入句子列表
    ret = model.predict(textlist)
    

3. 意图识别实现

3.1 数据准备

数据准备最终需要的形式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qVLupvux-1661260098567)(…/…/14深度学习/02深度学习/image/2.2/数据准备1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdVr7CXY-1661260098568)(…/…/14深度学习/02深度学习/image/2.2/数据准备2.png)]

以上格式是fastText要求的格式,其中chat、QA字段可以自定义,就是目标值,__label__之前的为特征值,需要使用\t进行分隔,特征值需要进行分词,__label__后面的是目标值

3.1.1 准备特征文本

使用之前通过模板构造的样本和通过爬虫抓取的百度上的相似问题,

3.1.2 准备闲聊文本

使用小黄鸡的语料,地址:https://github.com/fateleak/dgk_lost_conv/tree/master/results

3.1.3 把文本转化为需要的格式

对两部分文本进行分词、合并,转化为需要的格式

def prepar_data():
    #小黄鸡 作为闲聊
    xiaohaungji = "./corpus/recall/小黄鸡未分词.conv"
    handle_chat_corpus(xiaohaungji)
    # mongodb中的数据,问题和相似问题作为 问答
    handle_mongodb_corpus()

def keywords_in_line(line):
    """相似问题中去除关键字不在其中的句子
    """
    keywords_list = ["传智播客","传智","黑马程序员","黑马","python"
    "人工智能","c语言","c++","java","javaee","前端","移动开发","ui",
    "ue","大数据","软件测试","php","h5","产品经理","linux","运维","go语言",
    "区块链","影视制作","pmp","项目管理","新媒体","小程序","前端"]
    for keyword in keywords_list:
        if keyword in line:
            return True
    return False


def handle_chat_corpus(path):
    chat_num = 0
    with open("./corpus/recall/text_classify.txt","a") as f:
        for line in open(path,"r"):
            if line.strip() == "E" or len(line.strip())<1:
                continue
            elif keywords_in_line(line):
                continue
            elif line.startswith("M"):
                line = line[2:]
                line = re.sub("\s+"," ",line)
                line_cuted = " ".join(jieba_cut(line.strip())).strip()
                lable = "\t__label__{}\n".format("chat")
                f.write(line_cuted+lable)
                chat_num +=1
    print(chat_num)
    
def handle_QA_corpus():
  
    by_hand_data_path = "./corpus/recall/手动构造的问题.json" #手动构造的数据
    by_hand_data = json.load(open(by_hand_data_path))

    qa_num = 0

    f = open("./corpus/recall/text_classify.txt","a")
    for i in by_hand_data:
        for j in by_hand_data[i]:
            for x in j:
                x = re.sub("\s+", " ", x)
                line_cuted = " ".join(jieba_cut(x.strip())).strip()
                lable = "\t__label__{}\n".format("QA")
                f.write(line_cuted + lable)
                qa_num+=1

    #mogodb导出的数据
    for line in open("./corpus/recall/爬虫抓取的问题.csv"):
        line = re.sub("\s+", " ", line)
        line_cuted = " ".join(jieba_cut(line.strip()))
        lable = "\t__label__{}\n".format("QA")
        f.write(line_cuted + lable)
        qa_num += 1

    f.close()
    print(qa_num)
3.1.4 思考:

是否可以把文本分割为单个字作为特征呢?

修改上述代码,准备一份以单个字作为特征的符合要求的文本

3.2 模型的训练

import logging
import fastText
import pickle

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.DEBUG)


ft_model = fastText.train_supervised("./data/text_classify.txt",wordNgrams=1,epoch=20)
ft_model.save_model("./data/ft_classify.model")

训练完成后看看测试的结果

ft_model = fastText.load_model("./data/ft_classify.model")

textlist = [
    # "人工智能 和 人工智障 有 啥 区别", #QA
    # "我 来 学 python 是不是 脑袋 有 问题 哦", #QA
    # "什么 是 python", #QA
    # "人工智能 和 python 有 什么 区别",  #QA
    # "为什么 要 学 python", #QA
    # "python 该 怎么 学",  #CHAT
    # "python", #QA
    "jave", #CHAT
    "php", #QA
    "理想 很 骨感 ,现实 很 丰满",
    "今天 天气 真好 啊",
    "你 怎么 可以 这样 呢",
    "哎呀 , 我 错 了",
]
ret = ft_model.predict(textlist)
print(ret)
3.2.2 模型的准确率该如何观察呢?

首先需要对文本进行划分,分为训练集和测试集,之后再使用测试集观察模型的准确率

3.3 模型的封装

为了在项目中更好的使用模型,需要对模型进行简单的封装,输入文本,返回结果

这里可以使用把单个字作为特征和把词语作为特征的手段结合起来实现

"""
构造模型进行预测
"""
import fastText
import config
from lib import cut


class Classify:
    def __init__(self):
        self.ft_word_model = fastText.load_model(config.fasttext_word_model_path)
        self.ft_model = fastText.load_model(config.fasttext_model_path)

    def is_qa(self,sentence_info):
        python_qs_list = [" ".join(sentence_info["cuted_sentence"])]
        result = self.ft_mode.predict(python_qs_list)

        python_qs_list = [" ".join(cut(sentence_info["sentence"],by_word=True))]
        words_result = self.ft_word_mode.predict(python_qs_list)

        acc,word_acc = self.get_qa_prob(result,words_result)
        if acc>0.95 or word_acc>0.95:
            #是QA
            return True
        else:
            return False

    def get_qa_prob(self,result,words_result):
        label, acc, word_label, word_acc = zip(*result, *words_result)
        label = label[0]
        acc = acc[0]
        word_label = word_label[0]
        word_acc = word_acc[0]
        if label == "__label__chat":
            acc = 1 - acc
        if word_label == "__label__chat":
            word_acc = 1 - word_acc
        return acc,word_acc
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐