基于国产操作系的问答机器人——博客2

目录

日期:2024/1/18-2024/1/25

学习实践任务

学习经历

一、问答系统分类

二、问题类型

三、问答系统用例

四、初步了解网络爬虫

遇到的问题及解决方法

日期:2024/1/26-2024/2/22

学习实践任务

实践经历

一、网络爬虫的常用技术

二、网络爬虫开发常用框架

三、实战项目:12306爬票

搭建QT环境

学习XPath

下载站名文件

日期:2024/2/23-2024/3/7

学习实践任务

实践经历

一、实战项目:爬取豆瓣电影排行榜

 二、scrapy框架爬取教育网站教师信息

scrapy介绍:

目标:

实验代码:

遇到的问题及解决方法

日期:2024/3/8-2024/3/21

学习实践任务

实践经历

日期:2024/3/22-2024/4/4

学习实践任务

实践经历

自然语言处理NLP

一、基本术语

二、涉及的知识体系

三、技术层面

四、Numpy详解

自然语言工具包NLTK

一、使用NLTK

二、常用的统计函数

三、常用的其他函数

四、NLTK频率分布类中定义的常用函数

五、词汇比较运算的常用函数

Transformer概览

一、理解编码器

二、理解解码器

HuggingFace工具集使用方法

一、编码工具

二、数据集工具

三、管道工具

了解Bert模型

一、BERT的基本理念

二、BERT的工作原理

三、BERT的配置

四、BERT模型预训练

模型实战---RockerQA

一、端到端

二、Faiss

二、RocektQA

遇到的问题及解决方法:

日期:2024/4/5-2024/4/18

学习实践任务:

学习经历

调整数据集

引入新模型

1、使用pipeline

2、Chat-GLM

日期:2024/4/19-2024/4/27

学习实践任务:

实践经历:

一、在虚拟机配置deepin操作系统

1.在deepin官网下载最新版的镜像文件 

2.下载和安装VMware

3.在VMware上安装deepin系统

4.配置deepin系统

5.遇到的问题及解决

1)虚拟机安装deepin之后无法连接到网络

2)虚拟机分配的空间不够

二、在deepin中下载和安装anaconda

1.下载清华镜像源文件

2.安装anaconda

3.打开anaconda

4.遇到的问题及解决

1)anaconda安装慢

2)安装anaconda后无法运行

三、下载安装pycharm

四、在deepin上运行文档问答机器人

1.配置项目文件所需要的环境

  1)利用anaconda创建虚拟环境

 2)安装项目所需要的python库

 3)安装过程中出现的问题

2.在pycharm中运行该项目代码

 1)给项目添加编译器

2)运行项目文件

五、添加玲珑使用数据集

六、为项目添加可视化界面

1)使用tkinter

2)使用Flask


以下按时间顺序记录小组学习实践内容:


日期:2024/1/18-2024/1/25

学习实践任务

1.组队统一选题——文档问答机器人

2.配置python、pycharm、anaconda、pytorch

3.阅读选题博客,了解deepin国产操作系统、问答系统 wiki、问答系统用例、deepin wiki 仓库。

4.学习python语言编程及其爬虫

学习经历

一、问答系统分类

        我们可以从知识领域、答案来源等角度来替问答系统做分类。

        从知识领域来看,可以分为“封闭领域”以及“开放领域”两类系统。

        封闭领域系统专注于回答特定领域的问题,如医药或特定公司等。由于问题领域首先,系统又比较大的发挥空间,可以导入如专属本体论等知识,或将答案来源全部转换成结构性资料,来提升系统的表现。

        开放领域系统则希望不设限问题的内容范围,天文地理无所不问,系统中所有知识与元件都必须尽量做到与领域不相关,当然难度也相对地提高。

        若根据答案来源来区分,可以分为“数据库问答”、“常问问题问答”、“新闻问题”、“互联网问答”等系统。

二、问题类型

        问答系统接受的时自然语言问句,为了有效控制研究变因,多会订定可接受的问题类型来限制研究范围。最基本的类型为“仿真陈述回答”,此类系统根绝答案语料所述资讯,取出一小段字串作为答案。由于答案的正确与否是根据答案预料的内容来决定,在现实生活中不一定为真,故称为仿真陈述回答。有些系统把问答范围进一步缩小,限定在人、地、组织等明确的专有名词上。此类系统有能力回答如“请列举美国历届总统”这种清单型的问句,则称为“清单问答”。若能回答定义问题,则成为“定义回答”,以此类推还能定义出其他类型的问题。

        除了这些与问句资讯内容有关的类型外,最近评鉴会议引进如“时间限制问题”与“序列问题”等复杂的问题类型。时间限制型的问题会在问句中明确指出答案的时间范围限制,比如说以“民国九十年时国民党主席是谁”这问句来说,系统必须有根据答案预料结构化资料,或上下文来推论正确答案的能力。序列问题则把问答系统未来的应用定位在互动式的系统上。经过来回多次问答的方式来满足使用者的资讯需求。了解这些问题类型分类,有助于研究范围界定,同时在分析比较伤也比较有依据。

三、问答系统用例

        可以使用问答QA模型,通过使用知识库(文档)作为上下文来自动响应常见问题,可以从这些文档中得出客户问题的答案。如果您想节省推理时间,可以先使用段落排名模型来查看哪个文档可能包含问题的答案,然后使用QA模型迭代该文档。

        根据输入和输出,有不同的QA变体。

        抽取式QA:该模型从上下文中提取答案。这里的上下文可以是提供的文本、表格甚至HTML!这通常用类似bert的模型来解决。

        开放式生成式QA:该模型直接根据上下文生成自由文本。可以在其页面中了解有关“文本生成”任务的更多信息。

        封闭式生成QA:在这种情况下,不提供上下文。答案完全由模型生成。

四、初步了解网络爬虫

        初步打算爬取deepin网站的数据。

        网络爬虫的基本工作流程如下:

        1)获取初始的URL(统一资源定位符),该URL地址是用户自己指定的初始爬取的网页。(URL是web页的地址,对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎处理它。)

        2)爬取对应URL地址的网页时,获取新的URL地址

        3)将新的URL地址放入URL队列中

        4)从URL队列中读取新的URL,然后依据新的URL爬取网页,同时从新的网页中获取新的URL地址,重复上述的爬取过程。

        5)设置停止条件,如果没有设置停止条件时,爬虫会一直爬取下去,直到无法获取新的URL地址为止。设置了停止条件后,爬虫将会在满足停止条件时停止爬取。

        具体操作步骤:

        1)选择合适的爬虫工具:python是用户常用的爬虫语言,可以使用如BeautifulSoup、Scrapy等库来爬取网页内容。这些库可以帮助我们解析HTML和XML文件,提取所需的数据。我们组所选择的爬虫工具为Scrapy,接下来会对Scrapy进行系统学习。

        2)分析网页结构:在开始爬取之前,我们需要仔细分析目标网页的结构。了解哪些内容是我们所需要的,以及如何从HTML代码中提取这些内容。

        3)编写爬虫脚本:使用python编写爬虫脚本,通过发送送HTTP请求来获取网页内容,然后使用选定的库(Scrapy)来解析HTML代码,提取所需要的数据。

        4)处理反爬虫机制:网站可能会采取一些措施来防止爬虫,例如检测请求频率、使用动态加载内容等。我们需要处理这些反爬虫机制,例如设置合理的请求间隔,使用JavaScript渲染内容的模拟浏览器工具等。针对于deepin的网站(https://wiki.deepin.org)并没有设置反爬机制。

        5)存储和整理数据:将提取的数据存储在适当的数据结构中,例如使用Pandas的DataFrame来存储表格数据。之后,我们可以对数据进行整理,去重和分类等操作,以便进一步的分析和使用。

        6)遵守法律法规和网站规定:在爬取网站数据之前,要遵守相关法律法规和网站的使用协议。不要频繁地访问目标网站,以免对其服务器造成负担。同时,尊重网站的数据采集和使用规定。

        7)在正式爬取之前,先进行小规模的测试,确保脚本能够正确地提取所需的数据。如果遇到问题,及时进行调试和和优化。

        8)数据清洗和整理:提取的数据可能需要进行清洗和整理,以去处噪声、格式化数据等,确保数据的准确性和完整性。

        9)数据存储和管理:将提取的数据进行适当的存储和管理,以便后续的分析和使用。可以选择将数据存储在数据库、数据仓库或其他适当的数据存储解决方案中。

        10)持续更新和维护:随着时间的推移,目标网站的结构和内容可能会发生变化,所以需要定期更新我们的爬虫脚本以确保其有效性。同时,维护我们的数据存储和管理系统,以便能够快速访问和使用数据。

遇到的问题及解决方法

1.遇到问题:pytorch配置失败 

解决方法:一开始尝试直接在python上下载pytorch环境所需要的库,但下载慢且经常出错,不能及时配置好。后来了解到anaconda可以方便快捷的配置各种python代码环境,于是决定下载anaconda。

2.遇到问题:anaconda下载配置失败,不能正常接入pycharm使用。

解决方法:通过查阅了解资料,我们知道了有两种解决方案。一个是在官网下载,一个是通过清华源下载。我们认为官网上的软件一定很靠谱,所以这个方案也是我们首先尝试的方案。但是没有梯子导致下载速度慢甚至官网都登录不上去,于是采取了第二种方案。在通过清华源下载的过程中,我们看到清华源上有很多版本,我们查找到的教程大多是说“选择合适的版本进行下载安装。”于是中间走了很多弯路,下载了一些不合适的版本,根本用不了。后来再进一步查找资料,查看自己的电脑配置,成功下载好了anaconda并接入pycharm。


日期:2024/1/26-2024/2/22

学习实践任务

1.根据书籍教学视频,学习python基础编程

2.实践爬虫案例,尝试爬取12306车票

实践经历

一、网络爬虫的常用技术

        1)Python的网络请求:Python中实现HTTP网络请求常见的三种方式:urllib、urllib3以及requests。

        urllib模块是python自带的模块,该模块中提供了一个urlopen()方法,通过该方法指定URL发送网络请求来获取数据。urllib提供了多个子模块:urllib.request模块,该模块定义了打开URL的方法和类;urllib.error模块,该模块主要包含异常类,基本的异常类是URLError;urllib.parse模块,该模块定义的功能分为两大类,URL解析和URL引用;urllib.robotparser模块,该模块用于解析robots.txt文件。

        (通过get请求方式获取百度的网页内容):

import urllib.request

response = urllib.request.urlopen('http://www.baidu.com')
html = response.read()
print(html)

        urllib3模块是一个功能强大,条理清晰的用于HTTP客户端的Python库。提供了很多Python标准库中没有的重要特性:线程安全、连接池、客户端SSL/TLS验证、使用大部分编码上传文件、Helpers用于重试请求并处理HTTP重定向、支持gzip和deflate编码、支持HTTP和SOCKS代理、100%的测试覆盖率。

import urllib3

http = urllib3.PoolManager()
response = http.request('GET','http://www.baidu.com/')
print(response.data)

        requests模块是Python中实现HTTP请求的一种方式,requests是第三方模块,该模块在实现HTTP请求时要比urllib模块简化很多,操作更加人性化。在使用requests模块时需要通过执行pip install requests代码进行该模块的安装。requests模块的功能特性如下:Keep-Alive&连接池、基本/摘要式的身份认证、Unicode响应体、国际化域名和URL、优雅的key/value Cookie、HTTP(S)代理支持、带持久Cookie的会话、自动解压、文件分块上传、浏览器式的SSL认证、流下载、分块请求、自动内容编码、连接超时、支持.netrc。

import requests

data = {'word':'hello'}
response = requests.post('http://httpbin.org/post',data=data)
print(response.content)

        2)请求headers处理,有时在请求一个网页内容时,发现无论通过GET或者是POST以及其他请求方式,都会出现403错误。产生这种错误是由于该网页为了防止而已采集信息而是用了反爬虫设置,从而拒绝了用户的访问。此时,可以通过模拟浏览器的头部信息来进行访问,这样就能解决以上反爬设置的问题。

import requests

url = 'https://www.baidu.com/'
headers = {'User-Agent':''} //复制的头部信息
response = requests.get(url,headers=headers)
print(response.content)

        3)网络超时,在访问一个网页时,如果该网页长时间未响应,系统就会判断该网页超时,所以无法打开网页。requests模块提供了三种常见的网络异常类。

import requests

from requests.exceptions import ReadTimeout,HTTPError,RequestException
for a in range(0,50):
    try:
        response = requests.get('https://www.baidu.com/',timeout=0.5)
        print(response.status_code)
    except ReadTimeout:
        print('timeput')
    except HTTPError:
        print('httperror')
    except RequestException:
        print('reqerror')

        4)代理服务,在爬取网页的过程中,经常会出现不久前可以爬取的网页现在无法爬取了,这是因为我们的IP被爬取网站的服务器屏蔽了。此时可以设置代理服务解决该问题,首先需要找到一个代理地址(如,122.114.31.177,对应的端口号未808,完整的格式为122.114.31.177:808)。

import requests

proxy = {'http':'122.114.31.177:808',
         'https':'122.114.31.177:808'}
response = requests.get('http://www.mingrisoft.com/',proxies=proxy)
print(response.content)

实例中的代理IP时免费的,所以使用的时间不固定,超出使用的时间范围内该地址将失效。

        5)HTML解析之Beautiful Soup,Beautiful Soup是一个用于从HTML和XML文件中提取数据的Python库。Beautiful Soup提供一些简单的函数用来处理导航、搜索、修改分析树等功能。Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为UTF-8编码,我们不需要考虑编码方式,除非文档中没有指定一个编码方式,这时Beautiful Soup就不能自动识别编码方式了。我们仅需要说明一下原始编码方式就可以了。

二、网络爬虫开发常用框架

        1)Scrapy爬虫框架:Scrapy | A Fast and Powerful Scraping and Web Crawling Framework

        2)Crawley爬虫框架:Crawley’s Documentation — crawley v0.1.0 documentation 

        3)PySpider爬虫框架:Introduction - pyspider

三、实战项目:12306爬票
搭建QT环境

        1)在https://www.qt.io/download官方网站中下载对应的系统版本即可。

        2)由于QT在创建窗体项目时会自动生成后缀名为ui的文件,该文件需要转换为py文件后才可以被Python所识别,所以需要为QT与Pycharm开发工具进行配置。(可以参考Python——PyQt5在PyCharm的配置与应用(保姆级教程)_pycharm pyqt5-CSDN博客

        3)主窗体设置

分析网页请求参数

        1)获取完整的请求地址:进入12306官网后,右键选择检查,点击查询按钮,会显示请求细节的窗口,在该窗口中默认显示消息头的相关数据,此处可以获取完整的请求地址Request URL。

        2)获取请求地址中的必要参数:在请求地址的上方选择参数选项。

学习XPath

        XPath是一门在XML文档中查找信息的语言,HTML与XML结构类似,也可以在HTML中查找信息。XPath可以轻松定位并提取元素、属性和文本。

        1)XPath常用路径表达式:

XPath常用路径表达式
表达式描述示例
nodename选取此节点的所有子节点div,p,h,l
/从根节点选取(描述绝对路径)/html
//不考虑位置,选取页面中所有子节点//div
.选取当前节点(描述相对路径)./div
..选取当前节点的父节点(描述相对路径)h1/../
@属性名选取属性的值@href、@id
text()获取元素中的文本节点//h1/text()

        2)安装lxml库:在使用XPath之前,要先安装Python的lxml库,lxml库是一个HTML/XML的解析器。安装命令为:

pip install lxml

        3)导入lxml库:提取数据之前,要先导入lxml库的etree模块,再使用etree读取文件,生成一个节点数的对象,代码如下:

from lxml import etree

html_selector = etree.parse(".html",etree.HTMLParser())

        节点数的对象生成后,就可以使用XPath抽取数据了。

        4)按照XPath的使用语句,可以获取html、title等网页的内容

        5)XPath带谓语的路径表达式:(更多XPath的用法可以参考w3cschool:XPath 教程 (w3school.com.cn)

常用的带谓语的路径表达式
谓语表达式说明
//div[@id='content']选取属性id为content的div元素
//div[@class]选取所有带有属性class的div元素
//div/p[1]/text()

选取div节点中第一个p元素的文本

//div/p[2]/text()选取div节点中第二个p元素的文本
//div/p[last()]/text()选取div节点中最后一个p元素的文本
下载站名文件

        得到了请求地址和请求参数后,可以发现请求参数中的出发地与目的地均为车站的英文缩写。所以需要在网页中仔细查找是否有将车站名自动转换为英文缩写的请求信息。

        1)按F5进行余票的刷新,此时在网络监视器中选择类型为js的网络请求。在文件类型中仔细分析文件内容是否有与车站名相关的信息。

        2)选中与车站相关的网络请求,在请求细节中找到该请求的完整地址。然后再网页中打开该地址测试返回数据。

        3)打开Pycharm开发工具,创建python文件,创建getStation()方法。关键代码如下

def getStation():
    url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9303'
    response = requests.get(url,verify=True)
    stations = re.findall(u'([\u4e00-\u9fa5]+\|([A-Z]_)',response.text)
    stations = dict((stations))
    stations = str(stations)
    write(stations)

日期:2024/2/23-2024/3/7

学习实践任务

1.根据书籍教学视频,学习python基础编程

2.学习爬虫,成功爬取更多网站信息,实战爬虫项目,爬取豆瓣电影排行榜、scrapy爬虫框架爬取教育网站教师信息

实践经历

一、实战项目:爬取豆瓣电影排行榜

以下为爬取豆瓣电影榜的完整代码,按照页码进行下载。

import  urllib.parse
import  urllib.request
from bs4 import BeautifulSoup

def creat_request(page):
    base_url = 'https://www.douban.com/doulist/134462233/?'

    data = {
        'start': (page-1)*25
    }

    data =urllib.parse.urlencode(data)

    url = base_url+data+'&sort=seq&playable=0&sub_type='


    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
    }

    request = urllib.request.Request(url=url,headers=headers)
    return request

def get_content(request):
    response = urllib.request.urlopen(request)
    content = response.read().decode('utf-8')
    return content

def down_load(page,content):
    with open('douban_'+str(page)+'.json','w',encoding='utf-8') as fp:
        fp.write(content)

if __name__ =='__main__':
    start_page = int(input('请输入起始的页码:'))
    end_page = int(input('请输入结束的页码:'))

    for page in range(start_page,end_page+1):

        request = creat_request(page)

        content = get_content(request)
        soup = BeautifulSoup(content,'lxml')

        name_list = soup.select('div[class="title"] a')

        s = ""
        for name in name_list:
            name_ = name.get_text()
            s = s + name_ + " "

        down_load(page,s)

 二、scrapy框架爬取教育网站教师信息
scrapy介绍:

Scrapy是适用于Python的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。

Scrapy吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等,最新版本又提供了web2.0爬虫的支持。

目标:

我们打算抓取:http://www.itcast.cn/channel/... 网站里的所有讲师的姓名、职称和个人信息。

实验代码:
# -*- coding: utf-8 -*-
import scrapy
 #导入容器
from ITcast.items import  ItcastItem
class ItcastSpider(scrapy.Spider):
    # 爬虫的名字 启动爬虫时需要的参数*必需
    name = 'itcast'
    # 爬取域范围 允许爬虫在这个域名下进行爬取(可选)  可以不写
    allowed_domains = ['itcast.cn']
    #起始url列表  爬虫的第一批请求,将求这个列表里获取
    start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
    def parse(self, response):
        node_list = response.xpath("//div[@class='li_txt']")
        for node in node_list:
            #创建item字段对象,用来存储信息
            item = ItcastItem()
            # .extract() 将xpath对象转换围殴Unicode字符串
            name = node.xpath("./h3/text()").extract()
            title = node.xpath("./h4/text()").extract()
            info = node.xpath("./p/text()").extract()
            item['name'] = name[0]
            item['title'] = title[0]
            item['info'] = info[0]
            #返回提取到的每一个item数据 给管道文件处理,同时还会回来继续执行后面的代码
            yield item

遇到的问题及解决方法

遇到问题:下载后的json文件中有很多空行

解决方法:在Anaconda中找到exporters.py,打开,在CsvItemExporter中添加newline=“”。


日期:2024/3/8-2024/3/21

学习实践任务

1.和项目指导老师沟通确定文档问答机器人的方向

2.制定详细计划

实践经历

        和项目老师进行沟通,老师给出来了两个方向以及所对应的学习资料。

        1)基于LLM实现,通过向量计算,从文档中提取和问题相关的内容,将内容和问题喂给LLM进行推理。

        2)从文档生成数据集,基于transformers训练一个自己的文档问答的模型。

        资料可以参考huggingface.co上的教程

        中文链接为https://huggingface.co/learn/nlp-course/zh-CN/chapter1/1

        deepin wiki的数据可以从linuxdeepin/wiki.deepin.org: wiki content (github.com)进行下载,为markdown格式。至此我们便不再需要对deepin网站进行数据爬取。


日期:2024/3/22-2024/4/4

学习实践任务

1.阅读《Hugging Face自然语言处理详解》

2.查找合适问答模型,了解自然语言处理、transformer、bert

3.成功跑了第一个模型,可以完成基础问答,根据问题从数据库搜索五个相关度最高的信息。

实践经历

        为了能够更好的理解指导老师所提出来的两个方向,我们进行了自然语言处理的学习,系统了解llm模型。

        从零开始了解llm模型和transformers模型,并确定接下来给予我们的数据进行llm预训练还是基于transformers来训练自己的问答模型,我们将按照如下步骤进行学习。

        1)深入学习自然语言处理的基础概念和技术。

        2)了解transformers模型:理解其子注意力机制、编码器-解码器结构等核心组件。学习如何使用深度学习框架(TensorFlow或PyTorch)来实现简单的Transformers模型,并在小规模的数据集上进行实验。

        3)研究LLM模型:学习LLM的基本原理、模型架构和训练方法。

        4)实践项目:基于transformers库(Hugging Face的Transformers),实现一个简单的问答模型。通过这个过程,了解如何使用许愿练的Transformer模型进行微调,并处理实际的NLP任务。在一个小型数据集上尝试进行LLM的与训练实验。

自然语言处理NLP
一、基本术语

        1)分词:我爱北京天安门----->我/爱/北京/天安门

        2)词性标注:我/r爱/v北京/ns天安门/ns

        3)命名实体识别:从文本中识别人名、地名、专有名词等特定类别的实体。三大类:实体类、时间类、数字类。七小类:人名、机构名、地名、时间、日期、货币、百分比。

        4)句法分析:分析句子得到句子的句法结构,并解析句子中各成分之间的依赖关系。

        5)指代消解:处理代词

        6)情感识别:本质上是分类问题,分为正面、负面,也可加上中性类别。

        7)自动纠错:在搜索技术和输入法中应用得比较多,通常是由用户输入出错引发的

二、涉及的知识体系

        1)语义分析:对目标语句进行分词,词性标记、命名实体识别与句法分析等操作

        2)信息抽取:抽取目标文本的主要信息,关键词抽取涉及实体识别、时间抽取、因果关系抽取等多项技术

        3)情感分析:抽取其中最有意义的信息单元

        4)文本挖掘:对目标文本集的聚类、分类、信息提取、情感分析等处理,以及对挖出的信息的可视式、交互式的展示

        5)机器翻译

        6)信息检索:从大规模文档中获取最符合规则或所需要的信息,可以简单对文档中的词汇根据不同场景赋以不同的权重来建立索引(可用算法模型)、查询时,对输入进行分析,然后再索引中查找匹配的候选文档,再根据具体排序机制来对候选文档排序,输出得分最高的文档

        7)问答系统:系统需要对查询语句进行分析,形成逻辑表达式,然后到知识库中匹配可能答案,并通过具体排序机制找到最佳回答

        8)对话系统

三、技术层面

        1)词法分析:分词,词性标注,命名实体识别,词义消歧

        2)句法分析:将输入的文本以句子为单位进行分析,使得句子从序列形式变成树状结构,从而捕捉到句子内部词语之间的搭配和依赖关系

        3)语义分析:语义角色标注

四、Numpy详解

        在进行自然语言的相关处理前需要将文字转换为计算机易于理解、易于识别的向量,即把对文本内容的处理简化为向量空间中的向量运算,基于向量运算,我们就可以实现文本语义相似度,特征提取,情感分析,文本分类等功能。

        1)创建数组:ndarray代表的是多维数据,一维数组通常称为向量,二维数组通常称为矩阵

vector = np.array([ , , ,])
maxtrix = np.array([[ , ],[ , ],[ , ]])

        2)获取numpy数组的维度:

arrange(n)             #生成一个0~n-1的数组
reshape(row,column)    #自动构架一个对行队列的array对象
data.shape             #克表示data的维度,输出为元组( ,)

        3)获取本地数据:使用Numpy中的genfromtxt()方法来获取本地的数据集;Numpy数组中的数据必须时相同的数据类型;Numpy具备自动识别数组内对象类型的功能,也可以使用Numpy数组所提供的dtype属性来获取。

        4)正确读取数据:数据类型为nan(not a number)的原因为,数据类型转换错误,可以用genfromtxt()来实现数据类型转换;

np.genfromtxt(" #路径",dtype='U75',skip_header=1,delimiter=",")
#dtype='U75',每个值都是75字节的Unicode
#skip_header=1,跳过文件开头对应的行数之后,执行任何其他操作

        5)Numpy数组索引:数组名[1,2]表示的是第二行第三列。

        6)Numpy数组切片:数组名[a:b,c:d]取[a,b-1]行,[c,d-1]列

        7)数组比较:对于数据的比较,其最终输出结果为布尔值

        8)数据类型的转换:ndarray的数组类型可以使用dtype参数进行设置,还可以通过astype()进行转换,其结果是一个新的数组。

vector = numpy.array["22","33","44"]
vector = vector.astype(float) #将string转换为float

        9)Numpy的统计计算方法(数值类型必须是int或float):max()统计出数组元素的最大值,对于矩阵计算,其结果为一个一维数组,需要指定行或列;mean()统计出数组元素的平均值,对于矩阵计算,其结果为一个一维数组,需要制定行或列;sum()统计出数组元素的和,对于矩阵计算,其结果为一个一维数组,需要制定行或列

print(————,sum(axis=1)
#axis=1,计算行和,以列的形式展示
#axis=0,计算列和,以行的形式展示
自然语言工具包NLTK
一、使用NLTK

        安装NLTK,使用NLTK库进行相关操作之前,需要先导入book模块。

二、常用的统计函数

        1)len():计数词汇 len(text)

        2)set():获取词汇表set(text)

        3)sorted():词汇表排序sorted(set(text))

        2)count():特定词汇在文本中出现的次数 text.count("the")

        2)count()/len():特定此在文本中所占的百分比 100*text.count("the")/len(text)

        2)len()/len(set()):每个词平均使用的次数 len(text)/len(set(text))

三、常用的其他函数

        1)concordance函数:若要在文本中检索“A”词,text.concordance("A"),可展示全文中所有"A"出现的地方及上下文,还可以对齐打印。

        2)similar函数:可检索与“A”词相似的上下文,text.similar("A")

        3)common_contexts函数:当需要搜索共用多个词汇的上下文,而不是检索某个单词时,text.common_contexts(['A','B'])

        4)dispersion_plot函数:判断需要查找的词在文本中的位置,并从开头算起该词出现多少次,可以用离散图表示,离散图的每一列代表一个单词,每一行代表一个文本。

        5)FreqDist函数:对文本中词汇分布进行统计 fdist=FreqDist(text);还可以指定查询某个词的使用频率 fdist['the'];还可以查看指定个数常用词的累积频率图 fdist。plot(50,cumulativeTrue)

四、NLTK频率分布类中定义的常用函数

        1)FreqDist():创建包含给定样本的频率分布 fdist=FreqDist(sample)

        2)inc():增加样本,fdist.inc(sample)

        3)fdist[]:计数给定样本出现的次数 fdist.freq('A')

        4)N():获取样本总数 fdist.N()

        5)keys():获取以频率递减顺序排序的样本链表 fdist.keys()

        6)for...in...:获取以频率递减顺序遍历的样本 for sample in fdist

        7)max():获取数值最大的样本 fdist.max()

        8)tabulate():绘制频率分布表 fdist.tabulate()

        9)plot():绘制频率分布表 fdist.tabulate()

        10)plot(cumulative =True):绘制累积频率分布图 fdist.plot(cumulative=True)

五、词汇比较运算的常用函数

        

词汇比较运算的常用函数
函数说明举例
startswith()测试字符串是否以函数中的参数开头s.startswith(t)
endswith()测试字符串是否以函数中的参数结尾s.endswith(t)
inswith()测试字符串是否包含函数中的参数

t in s

islower()测试字符串是否全小写s.islower()
isupper()测试字符串是否全大写s.isupper()
isalpha()测试字符串是否全为字母

s.isalpha()

isalnum()测试字符串是否全为字母/数字s.isalnum()
isdigit()测试字符串是否全为数字s.isdigit()
istitle()测试字符串所有的词首字母是否全为大写s.istitle()
Transformer概览

        Transformer由编码器和解码器两部分组成。首先,向编码器输入一句话(原话),让其学习这句话的特征,再将特征作为输入传输给解码器。最后,此特征就会通过解码器生成输出这句(目标句)。

一、理解编码器

        Transformer中的编码器不止一个,而是由一组N个编码器串联而成。一个编码器的输出作为下一个编码器的输入。每一个编码器都从下方接收数据,再输出给上方。以此类推,原剧中的特征会由最后一个编码器输出。编码器模块的主要功能就是提取原句中的特征。每一个编码器的构造都是相同的,并且包括两个部分:多头注意力层、前馈网络层。

        1)注意力机制:可以参考此篇博客进行学习全网最通俗易懂的 Self-Attention自注意力机制 讲解_self attention机制-CSDN博客

        2)多头注意力层是指我们可以使用多个注意力头,而不是只用一个。使用多头注意力机制的逻辑是这样的:使用多个注意力矩阵,而非单一的注意力矩阵,可以提高注意力矩阵的准确性。

        3)位置编码:如果把输入矩阵X直接传给transformer,那么模型是无法理解词序的。因此需要添加一些表明词序的信息,以便神经网络能够理解句子的含义。所以我们不能将输入矩阵直接传给transformer。即我们需要位置编码,顾名思义,位置编码是指词在句子中的位置(词序)的编码。位置编码矩阵P与输入矩阵X的唯独相同。在将输入矩阵直接传给Transformer之前,我们将使其包含位置编码。只需要将输入矩阵X与计算得到的位置编码矩阵P进行逐元素相加,并将得到的结果作为输入矩阵送入编码器中。

        4)前馈网络层:前馈网络由两个有ReLU激活函数的全连接层组成。前馈网络的参数在句子的不同位置上是相同的,但在不同的编码器模块上是不同的。

        5)叠加和归一组件:它同时连接一个子层的输入和输出,同时连接多头注意力层的输入和输出,同时连接前馈网络层的输入和输出。叠加和归一化组件实际上包含一个残差连接与层的归一化。层的归一化可以防止每层的值剧烈变化,从而提高了模型的训练速度。

        总览:将输入转换为嵌入矩阵(输入矩阵),并将位置编码加入其中,再将结果作为输入传入底层的编码器(编码器1);编码器1接受输入并将其送入多头注意力曾,该子层运算后输出注意力矩阵;将注意力矩阵输入到下一个子层,即前馈网络层。前馈网络层将注意力矩阵作为输入,并计算出特征值作为输出;接下来把从编码器1中得到的输出作为输入,传入下一个编码器(编码器2);编码器2进行同样的处理,再将给定输入句子的特征值作为输出。这样可以将N个编码器一个接一个地叠加起来。从最后一个编码器(顶层的编码器)得到的输出将是给定输入句子的特征值。把从最后一个编码器得到的特征值表示为R。把R中作为输入传给解码器,解码器将给予这个输入生成目标句。

二、理解解码器

        1)解码器内部有三个子层:代言吗的多头注意力层、多头注意力层、前馈网络层

        2)一个解码器有两个输入:一个是来自前一个解码器的输出,另一个是编码器输出的特征值。

        3)首先,我们将解码器的输入转换为嵌入矩阵,然后将位置编码加入其中,并将其作为输入送入底层的解码器(解码器1);解码器收到输入,并将其发送给带掩码的多头注意力层,生成注意力矩阵M;然后,将注意力矩阵M和编码器输出的特征值R作为多头注意力层(编码器-解码器注意力层)的输入,并再次输出新的注意力矩阵;把从多头注意力层得到的注意力矩阵作为输入,送入前馈网络层,前馈网络层将注意力矩阵作为输入,并将解码后的特征作为输出;最后我们把从解码器1得到的输出作为输入,将其送入解码器2;解码器2进行同样的处理,并输出目标句的特征。我们可以将N个解码器层层堆叠起来,从最后的解码器得到的输出(解码后的特征)将是目标句的特征。接下来,我们将目标句的特征送入线性层和softmax层,通过概率得到预测的词。

HuggingFace工具集使用方法
一、编码工具

1、编码工具工作流示意

        1)定义字典:文字是一个抽象的概念,不是计算机擅长处理的数据单元,计算机擅长处理的是数字运算,所以需要把抽象的文字转换为数字。(下为一个示意的字典,在实际的项目中的字典可能会有成千上万个词)

vocab={
    '<SOS>':0,
    '<EOS>':1,
    'the':2,
    'quick':3,
    'brown':4,
    'fox':5,
    'jumps':6,
    'over':7,
    'a':8,
    'lazy':9,
    'dog':10,
}

        2)句子预处理:在句子被分词处理之前,一般会对句子进行一些特殊的操作,例如把太长的句子截短,或在句子中添加首尾标识符等。例如特殊符号<SOS>,<EOS>他们分别代表一个句子的开头和结束。

sent = 'the quick brown fox jumps over a lazy dog'
sent = '<SOS>' + sent + '<EOS>'
print(sent)

#结果如下
#<SOS> the quick brown fox jumps over a lazy dog<EOS>

        3)分词:对中文来讲,这是一个负责的问题,但是对于英文来讲这个问题比较容易解决,因为英文有自然的分词方式,即以空格来分词。

words = sent.split()
print(words)

#结果如下
#['<SOS>','the','quick','brown','fox','jumps','over','a','lazy','dog','<EOS>']

        对于中文来讲,分词的问题比较复杂,有很多成熟的工具能够做中文分词,例如jieba分词、LTP分词等。HuggingFace的编码工具已经包括了分词这一步工作,有各个模型自行实现。

        4)编码:把单词映射为数字

encode = [vocab[i] for i in words]
print(encode)

#运行结果如下
#[0,2,3,4,5,6,7,8,9,10,1]

2、使用编码工具

        1)加载编码工具

from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained(
    pretrained_model_name_or_path='bert-bath-chinese',
    cache_dir =None,#指定编码工具的缓存路径
    force_download=False#为True时表明无论是否已经有本地缓存,都强制执行下载工作。建议设置为False

        2)准备实验数据

        3)基本的编码函数:encode()一次编码一个或一对句子

        4)进阶的编码函数:encode_plus()和encode()一样,但是会返回更加复杂的编码结果

        5)批量的编码函数:batch_encode_plus()函数批量地进行数据处理

        6)对字典的操作

vocab = tokenizer.get_vocab()
type(vocab),len(vocab),'明月' in vocab

#(dict,21128,False)

#添加新词
tokenizer.add_tokens(new_tokens=['明月','装饰','窗子'])
#添加新符号
tokenizer.add_special_tokens({'eos_token':'[EOS]'})
二、数据集工具

1、数据集加载和保存

        1)在线加载数据集

from datasets import load_dataset
dataset = load_dataset(path='seamew/ChnSetiCorp')

        2)将数据集保存到本地磁盘

dataset.save_to_disk(
    dataset_dict_path='./data/ChnSentiCorp')

        3)从本地磁盘加载数据集

from datasets import load_from_disk
dataset = load_from_disk('./data/ChnSentiCorp')

2、数据集基本操作

#取出数据部分
dataset = dataset['train']

#查看数据内容
for i in [12,17,20,26,56]:
    print(dataset[i])

#数据排序
sorted_dataset = dataset.sort('label')

#打乱数据
shuffled_dataset=sorted_dataset.shuffle(seed=42)

#数据抽样
dataset.select([0,10,20,30,40,50])

#数据过滤
def f(data):
    return data['text'].startswith('非常不错']
dataset.filter(f)

#训练测试集拆分
#训练集占90%,测试集占10%
dataset.train_test_split(test_size=0.1)

#数据分桶
dataset.shard(num_shards=4,index=0)

#重命名字段
dataset.rename_column('text','text_rename')

#删除字段
dataset.remove_columns(['text'])

#映射函数
#对数据集总体进行一些修改时,可以使用map()函数遍历数据,并对每条数据进行修改
def f(data):
    data['text'] = 'my sentence:'+ data['text']
    return data 
maped_dataset = dataset.map(f)
print(dataset['text'][20])          #非常不错
print(maped_dataset['text'][20])    #my sentence:非常不错

#使用批处理加速
maped_dataset=dataset.map(function=f,
                        batched=True,
                        batch_size=1000,
                        num_proc=4)

#设置数据格式
#type表明要修改为的数据类型,常用的取值有numpy,torch,tensorflow,pandas
#columns表明要修改格式的字段
#output_all_columns表明是否要保留其他字段,设置为True表明要保留
dataset.set_format(type='torch',colums=['lable'],output_all_columns=True)

3、将数据保存为其他格式

#保存为csv格式
dataset = load_dataset(path='seamew/ChnSentiCorp',split='train')
dataset.to_csv(path_or_buf='./data/ChnSentiCorp.csv')
csv_dataset = load_dataset(path='csv'
                        data_files='./data/ChnSentiCorp.csv',
                        split ='train')

#保存为JSON格式
dataset = load_dataset(path='seamew/ChnSentiCorp',split='train')
dataset.to_json(path_or_buf='./data/ChnSentiCorp.json')
csv_dataset = load_dataset(path='json'
                        data_files='./data/ChnSentiCorp.json',
                        split ='train')
三、管道工具

1、常见任务演示

        1)文本分类

#文本分类
from transformers import pipeline
classifier = pipeline("sentiment-analysis")
result = classifier("I hate you")[0]
print(result)    #{'label':'NEGATIVE','score':'0.9991129040718079'}
result = classifier("I love you")[0]
print(result)    #{'label':'POSITIVE','score':'0.9998656511306763'}

        2)阅读理解:以question-answering为参数调用了pipeline()函数,得到了question_answerer对象。context是一段文本,也是模型需要阅读理解的目标,把context和关于context的一个问题同时输入question_answerer对象中,即可得到相应的答案。问题的答案必须在context中出现过,因为模型的计算是从context中找到问题的答案。

from transformers import pipeline
question_answerer=pipeline("question-answering")
context=r"""
    ...#正文
"""
result =question_answerer(
    question="..."
    context=context,
)
print(result)

        3)完形填空

from transformers import pipeline
unmasker =pipeline("fill-mask")
from pprint import pprint
sentence ='Huggingface is creating a <mask> that the community uses to solve NLP tasks.'
unmasker(sentence)

        4)文本生成

from transformers import pipeline 
text_generator = pipeline("text-generation")
text_generator("As far as I am concerned,I will",
                max_length=50,
                do_sample=False)

        5)命名实体识别

from transformers import pipeline
ner_pipe =pipeline("ner")
sequence = """..."""
for entity in ner_pipe(sequence):
    print(entity)

        6)文本摘要

from transformers import pipeline
summarizer = pipeline("summarization")
ARTCLE = """..."""
summarizer(ARTCLE,max_length=130,min_length=30,do_sample=False)

        7)翻译:由于默认的翻译任务底层调用的是t5-base模型,该模型只支持由英文翻译成德文、法文、罗马尼亚文,如果需要支持其他语言,则需要替换模型。

from transformers import pipeline
translator = pipeline("translation_en_ro_de")
sentence ="..."
translator(sentence,max_length=40)
了解Bert模型
一、BERT的基本理念

        BERT意为多Transformer的双向编码器表示法,它是由谷歌发布的先进的嵌入模型。BERT成功的一个主要原因是,它是基于上下文的嵌入模型。

二、BERT的工作原理

        我们可以把BERT看曾只有编码器的Transformer。Transformer的编码器是双向的,它可以从两个方向读取一个句子,因此,BERT由Transformer获得双向编码器特征。我们把句子A送入Transformer的编码器,得到句子中每个单词的上下文特征(嵌入),一旦我们将句子送入编码器,编码器就会利用多头注意力层来理解每个单词在句中的上下文(将句子中的每个单词与句子中的所有单词联系起来,以学习单词之间的关系和语境含义),并将其特征作为输出。

三、BERT的配置

        有两种标准配置:BERT-base、BERT-large。

        BERT-base由12层编码器叠加而成。每层编码器都使用12个注意力头,其中前馈网络层由768个隐藏神经元组成,所以BERT-base得到的特征向量的大小是768。我们将编码器的层数用L来表示,注意力头的数量用A来表示,隐藏神经元的数量用H来表示。BERT-base的配置可以表示为L=12,A=12,H=768。

        BERT-large的配置可以表示为L=24,A=16,H=1024。

        BERT还有很多种不同的配置,BERT-tiny:L=2,H=128;BERT-mini:L=4,H=256;BERT-small:L=4,H=512;BERT-medium:L=8,H=512。在资源有限的情况下,我们可以使用较小的BERT配置,但是,标准的BERT配置可以得到更准确的结果,且应用更为广泛。

四、BERT模型预训练

        BERT模型在一个巨大的语料库上针对两种特定的任务进行预训练。这两种任务是掩码语言模型构建和下句预测。在预训练完成之后,我们保存预训练好的BERT模型,对于一个新任务,比如问答任务,我们将与训练过的BERT模型,而无需从头开始训练。然后我们微信任务调整(微调)其权重。

1、输入数据:在将数据输入BERT之前,首先使用如下3个嵌入层将输入转换为嵌入,标记嵌入层,分段嵌入层,位置嵌入层

        1)标记嵌入层

        句子A:Paris is a beautiful city         句子B:I love Paris

        我们首先对这两个句子进行分词标记,如下所示

tokens=[Paris,is,a,beautiful,city,I,love,Paris]

        接下来,我们添加一个新标记,即[CLS]标记,并将它放在第一句开头,然后我们在每一个句子的末尾添加一个新标记,即[SEP]。[CLS]只在第一句的开头添加,而[SEP]用于表示每个句子的结束。

tokens=[[CLS],Paris,is,a,beautiful,city,[SEP],I,love,Paris,[SEP]]

        在将所有标记送入BERT之前,我们使用标记嵌入层将标记转换成嵌入,标记嵌入的值将通过训练学习。我们计算所有标记的嵌入,如E_{[CLS]}表示[CLS]的嵌入,以此类推。

        2)分段嵌入层:只输出嵌入E_{A}E_{B},如果输入的标记属于句子A,那么该标记将被映射到嵌入E_{A};如果该标记属于句子B,那么它将被映射到嵌入E_{B}

        3)位置嵌入层:E_{0}表示[CLS]的位置嵌入,E_{1}表示Paris的位置嵌入,以此类推。

        4)输入特征:将所有的嵌入值相加,并输入给BERT。

        5)WordPiece,BERT使用的一种特殊的词元拆分器。例如Let us start pretraining the model。使用WordPiece对该句进行标记,结果如:tokens=[let,us,start,pre,##train,##ing,the,model]

        当使用WordPiece进行分词时,我们首先会检查该词是否存在于词表中。如果该词已经存在在词表中,那么就把它作为一个标记。如果该词不存在词表中,那么就继续将该词分为子词,检查子词是否存在在词表中。如果子词在词表中,那么就把他作为一个标记。但是如果子词不在词表中,那么继续分割子词。我们通过这种方式不断进行拆分,检查子词是否存在词表中,直到字母级别。这在处理未登录词时是有效的。

        再对句子添加[CLS]和[SEP]标记,如tokens=[[CLS],let,us,start,pre,##train,##ing,the,model,[SEP]]

2、预训练策略

        BERT模型在以下两个自然语言处理任务上进行预训练:掩码语言模型构建、下句预测。

        1)语言模型构建:是指通过训练模型来预测一连串单词的下一个单词。我们可以把语言模型分为两类:自动回归式语言模型、自动编码式语言模型。

        自动回归式语言模型:正向(从左到右)预测、反向(从右到左)预测。自动回归式语言模型在本质上是单向的,也就是说,它只沿着一个方向阅读句子。

        自动编码式语言模型:同时利用了正向预测和反向预测的优势,在进行预测时,他会同时从两个方向阅读句子,所以自动编码式语言模型是双向的。

        2)掩码语言模型构建:BERT是自动编码式语言模型,他从两个方向阅读句子,然后进行预测。给定一个输入据句,我们随机掩盖其中15%的单词,并训练模型来预测被掩盖的单词。

        在标记列表中随机掩盖15%的标记,加入我们掩盖了单词city,那么就用一个[mask]标记替换city这个词,但是这种掩盖方式会造成预训练和微调之间的差异,因为我们的输入中不会有任何[mask]标记。为了解决这个问题,我们使用80-10-10规则。

        我们已经随即掩盖了句子中15%的标记。打但是现在对于这些标记我们做以下处理:

        在80%的情况下,使用[mask]标记来替换该标记(实际词),也就是说在80%的情况下,模型的输入为tokens=[[CLS],Paris,is,a,beautiful,[mask],[SEP],I,love,Paris,[SEP]];对于10%的数据,使用一个随即标记(随机词)来替换该标记(实际词),所以在10%的情况下,模型的输入如下tokens=[[CLS],Paris,is,a,beautiful,love,[SEP],I,love,Paris,[SEP]];对于剩余10%的数据,不做任何改变,所以在10%的情况下,模型的输入如下tokens=[[CLS],Paris,is,a,beautiful,city,[SEP],I,love,Paris,[SEP]]

        在分词和掩码后,将标记列表送入标记嵌入层、分段嵌入层和位置嵌入层,得到嵌入向量。然后将嵌入向量送入BERT。在从刚开始的迭代中,模型将不会返回正确的概率,因为BERT的前馈网络层和编码器层的权重并不是最优的。然而在多次迭代后,通过反向传播,我们更新并优化了BERT的前馈网络层和编码器层的权重。

        除了对输入标记进行掩码处理,我们还可以使用另一种略有不同的方法,即全词掩码。

        全词掩码:子词被掩码时,那么该子词对应的单词也将被掩码,为了保持15%的掩码率,可以忽略掩盖其他单词。

        3)下句预测:我们向BERT模型提供两个句子,他必须预测第二个句子是否是第一个句子的下一句。如果句子B是句子A的下一句,我们将这一句子对标记为isNext,反之标记为notNext。下句预测的本质上是二分类任务。

3、预训练过程

        在训练过程中的初始阶段,我们可以设置高学习率,使最初的迭代更快地接近最优点,在后续的迭代过程中,调低学习率可以使结果更加精确。BERT的激活函数使GeLU,即高斯误差线性单元。

模型实战---RockerQA

        RocketQA是全世界首个面向中文的端到端搜索问答工具包,该工具包提供了一个在百万级中文阅读理解数据集DuReader上训练的中文段落检索模型,同时也提供了面向普通开发者的一键索引问答数据和一键启动问答服务的功能。

一、端到端

        端到端的概念指的是一种通信方式,数据从发送方直接传输到接收方,而不需要中间环境对数据内容进行解析和处理,在通信领域内,端到端的模式强调的是数据传输过程中的直接性和完整性。引申到深度学习和人工智能领域内,端到端的概念表示模型可以直接利用输入数据而不需要其他处理。如果模型可以直接通过输入原始数据来得到输出,那么我们就说这个模型是端到端的。

         端到端搜索指的是一种搜索技术或方法,他将搜索过程从用户输入查询开始,一直到获取到最终的搜索结果为止,整个流程都集成在一个系统或平台中。

二、Faiss

        Faiss是Facebook AI研究院开发的一种高效的相似性搜索和聚类的库。它能够快速处理大规模数据,并且支持在高维空间中进行相似性搜索。

二、RocektQA

        RocketQA是一个基于BERT的深度语义检索模型,其内部包含了两个子模型:双塔式召回模型和交互式排序模型,其中召回模型负责从文档库中快速筛选出与用户查询度高度相关的候选文档集,排序模型则会进一步从候选文档集中找到与用户查询最相关的文档,并返回给用户。RocketQA通过跨批次负采样、去噪的强负例采样与数据增强三项技术,来提高训练能力。

        搭建RocketQA

        1)运行RocketQA检索服务需要Python 3.6+和PaddlePaddle2.0+的环境,同时还要安装faiss用于支持向量索引服务。

        2)准备数据,我们需要将语料库处理成每行包含一条文档数据的形式,其中文档数据包含文档标题和文档内容,标题和内容用\t分隔,如果没有标题,可用空字符串或-代替。测试数据样本仅有一百条。

        3)建立索引,启动服务

        4)测试:基本上在五秒内可以完成对问题的回答。回复前五条最接近的答案。

遇到的问题及解决方法:

1.遇到问题:运行RocketQA的环境配置

   解决方法:必须需要Python 3.6+和PaddlePaddle2.0+的版本


日期:2024/4/5-2024/4/18

学习实践任务:

1.根据RocketQA,调制相符的数据集

2.根据实际任务,调整代码

学习经历

调整数据集

1、我们知道RocketQA所使用的数据集的格式为每行包含一条文档数据的形式,其中文档数据包含文档标题和文档内容,标题和内容用\t分隔,如果没有标题,可用空字符串或-代替。根据我们的项目要求,我们所需要的答案为“网址+回答“的形式,所以我们这里需要把标准数据集中所对应的文档标题部分改为文档网址。

2、即我们需要获取deepin网站中的所有网址。根据查阅资料得知站点地图sitemap可以获取到该网站的全部网址。获取sitemap的方法有很多,在这里我们使用wiki.deepin.org/sitemap.xml获取到deepin网站的全部url。

3、对sitemap进行提取,去除html标签

import xml.etree.ElementTree as ET

# XML文件路径
xml_file_path = '/sitemap.xml'  # 替换为你的XML文件实际路径
output_file_path = 'extracted_urls.txt'  # 输出文件的路径

# 解析XML文件
tree = ET.parse(xml_file_path)
root = tree.getroot()

# 用于存储提取到的URL
urls = []

# 遍历所有的<url>元素
for url_element in root.findall('{http://www.sitemaps.org/schemas/sitemap/0.9}url'):
    loc_element = url_element.find('{http://www.sitemaps.org/schemas/sitemap/0.9}loc')
    if loc_element is not None:
        # 提取CDATA段内的内容
        cdata_content = loc_element.text
        if cdata_content is not None:
            # 移除CDATA标记并保存URL
            url = cdata_content.strip().replace('<![CDATA[', '').replace(']]>', '')
            urls.append(url)

        # 将提取到的URL写入TXT文件
with open(output_file_path, 'w', encoding='utf-8') as f:
    for url in urls:
        f.write(url + '\n')  # 每个URL后面添加换行符  

print(f"提取的URL已保存到 {output_file_path}")

提取出的url为下图所示,一共有1152条记录。

4、找到url以及文档的对应关系,将二者进行拼接。

import os
from pathlib import Path

# 读取URL列表
url_list = []
with open("D:\\OS\\RocketQA-main (2)\\RocketQA-main\\extracted_urls.txt", "r", encoding="utf-8") as file:
    for line in file:
        url_list.append(line.strip())

# 设定Markdown文件夹路径
markdown_folder = "D:\\OS\\RocketQA-main (2)\\RocketQA-main\\wiki.deepin.org-master"

# 遍历Markdown文件并构建数据集
dataset = []
for markdown_file in Path(markdown_folder).glob("**/*.md"):
    with open(markdown_file, "r", encoding="utf-8") as file:
        content = file.read()

        # 分割元数据和正文
        parts = content.split("---", 2)
        if len(parts) > 2:
            # 如果有三个或更多部分,则跳过前两个(包含元数据的部分)
            content = parts[2]
        else:
            # 如果没有元数据或只有一个`---`,则整个文件都是正文
            content = parts[0]

            # 删除行首和行尾的空白符(可选)
        content = content.strip()

        local_file_path = str(markdown_file.relative_to(Path(markdown_folder))).replace(os.sep, '/')
        local_file_path_without_ext = os.path.splitext(local_file_path)[0]

        for url in url_list:
            if url.endswith(local_file_path_without_ext):
                dataset_item = '"{}"\t"{}"'.format(url, content.replace('"', '\\"'))
                dataset.append(dataset_item)
                break  # 找到匹配项后跳出内层循环,继续下一个Markdown文件

# 将数据集写入文件(略)
with open("dataset_content.txt", "w", encoding="utf-8") as file:
    for item in dataset:
        file.write(item + "\n")
print("数据集构建完成并已保存到dataset_content.txt文件中。")

拼接后的数据如下图所示,此时的数据集并没有去除掉deepin数据中的mark down格式

5、分析md文档中的层级关系,将文档拆分为一行为一块内容的格式

拿Flatpak详细介绍.md文档为例,如下图所示,我们所希望达到的预计输出为下图所示

6、我们发现网页链接与数据文档名称存在着联系,所有网页的链接都是基于一个基础的链接后进行拼接,基础链接为:https://wiki.deepin.org/zh/,根据 deepin数据集的层级关系,很好的构造当前网页的相对路径,与基础链接像拼接,正好就是该网页的url地址。将网页的正文和链接内容分开保存在para_list = []、title_list = []数组中。根据RocketQa,建立索引函数,构建链接和正文的索引关系。

def build_index(encoder_conf, index_file_name, title_list, para_list):

    dual_encoder = rocketqa.load_model(**encoder_conf)
    para_embs = dual_encoder.encode_para(para=para_list, title=title_list)
    para_embs = np.array(list(para_embs))

    print("Building index with Faiss...")
    indexer = faiss.IndexFlatIP(768)
    indexer.add(para_embs.astype('float32'))
    faiss.write_index(indexer, index_file_name)

在rocketqa.service.py文件中做出相应的修改,去处markdown标签

with open(file_path, 'r', encoding='utf-8') as file:
    content = file.read()  # 读取文件内容
    content = markdown.markdown(content)        # 将 markdown 转成 html
    content = re.sub('<[^>]+>', '', content)    # 去除 html 标签
    self.id2text.append(('https://wiki.deepin.org/zh/' + relative_path + str_split + content).strip())

此时完成的效果为,用户输入问题后,rocketqa可以在众多的索引中,找到相符的一个,然后输出该网页链接和全部的正文内容。

接下来我们努力的方向是,如何把正文按照问题点进行划分,使之能够回复更加精确的答案。

7、我们对正文内容进行处理,按照我们上文中所提到的预期输出方向进行努力

def spilt_content(content):

    # 初始化一个列表来保存分割后的内容
    result = []
    # 初始化一个变量来保存当前正在处理的内容
    current = []

    # 遍历每一行
    for line in content.splitlines():

        # 检查当前行是否是标题或分隔符
        if line.startswith('#') or line.startswith('---'):
            # 如果当前列表不为空,意味着我们已经收集了一些内容
            if current:
                # 将当前收集的内容添加到分割内容列表中
                result.append('\n'.join(current))
                # 重置当前内容列表
                current = []
            # 添加二级标题到当前内容列表
            current.append(line)
        else:
            # 处理代码块语法
            if line.startswith('```'):
                line = '---'
            # 将非标题行添加到当前内容列表
            current.append(line)

    # 检查是否有剩余的内容需要添加到分割内容列表中
    if current:
        result.append('\n'.join(current))

    for i in range(len(result)):
        result[i] = markdown.markdown(result[i])        # 将 markdown 转成 html
        result[i] = re.sub('<[^>]+>', '', result[i])    # 去除 html 标签

    filter_result = [p.strip() for p in result if p.strip()]  # 过滤并处理段落列表

    return filter_result

经过运行之后,发现模型运行后回答的结果不尽如人意且非常不稳定

我们试图提高TOPK的值,让他输出更多的更符合的值,但是输出的结果也并不如人意,且由于我们设备配置的问题,很高的TOPK值会大大拉长响应时间。

引入新模型

由于上述处理数据集,回答的结果并不理想。我们小组想着另谋思路,是否可以借用其他模型,对我们所处理的数据进行阅读理解,在我们所给出的正文中,回答我们的问题。在这时候想到了我们曾经看书所了解到的pipeline工具。

1、使用pipeline

macbert-large 是一种基于大规模机器阅读理解(Machine Reading Comprehension, MRC)数据再训练的模型。macbert-large 通过在大规模机器阅读理解数据集上进行再训练,使得模型能够更好地理解文本并回答相关问题。这种方法通常会提高模型在特定任务上的性能,因为模型能够从大量的数据中学习到更多的语言知识和语境。通过再训练,macbert-large 在机器阅读理解任务上可能表现更加出色,能够更准确地理解文本内容,并给出更精确的答案。

model_path = 'model/chinese_pretrain_mrc_macbert_large'

# 加载本地的分词器和模型
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForQuestionAnswering.from_pretrained(model_path)

# 创建问答 pipeline,使用本地模型
qa_pipeline = pipeline("question-answering", tokenizer=tokenizer, model=model)

现在所能达到的效果稳定且没有差错,但是结果十分短暂,不够细致。

在这个的基础上,我们小组想或许换一个模型呢,会不会效果会更好一些。

2、Chat-GLM

我们发现了一个答案质量很高的模型,但是对设备的配置要求非常高。

Chat-GLM(Chat-based Generative Language Model)是一种基于对话的生成式语言模型,通常构建在大型神经网络架构上,如GPT(Generative Pre-trained Transformer)模型。Chat-GLM旨在模仿自然语言对话,并能够生成连贯、流畅的文本回复,从而与用户进行交互。

Chat-GLM通常通过大规模的对话数据集进行预训练,以学习自然语言的语法、语义和上下文关系。在预训练完成后,它可以用于各种对话型任务,如智能客服、聊天机器人、虚拟助手等。

Chat-GLM模型的核心思想是利用大规模的对话数据来学习对话的模式和规律,从而使其能够生成自然、连贯的对话文本。该模型在对话生成方面表现出色,能够模拟人类对话的特点,并且能够根据上下文信息作出合适的回复。

如果transformers库用最新版本跑不通的话,可以尝试回退一下版本,使用4.33.2版本

prompt = [
'''
--这是一个对话场景,一个用户给了一个人工智能助手一篇文章,并希望人工智能助手根据文章回答提出的问题,请续写该人工智能助手的回答内容:

--文章:\n
''',
'''
\n--用户问题:
''',
'''
\n--人工智能助手回答:
''']

tokenizer = AutoTokenizer.from_pretrained("model/chatglm-6b-int4", trust_remote_code=True)
model = AutoModel.from_pretrained("model/chatglm-6b-int4", trust_remote_code=True).float()

虽然这个模型回答的质量非常高,但是对配置的要求非常高,我们全队的设备运行起来,慢的需要将近半个小时才能等到他回答一条,快的话可能需要十几分钟。

然后我们寻求了老师的帮助,借用了老师的服务器。重新部署项目,最终试验成功。此时回答一条问题仅需要不到1分钟,虽然这对于正常对话来说还是有些迟钝,但是经过这次实验,我们相信我们代码的可实现性,只要我们使用更高的配置,我相信我们的代码是可以做到及时回复。


日期:2024/4/19-2024/4/27

学习实践任务:

1.添加玲珑使用问题

2.尝试在虚拟机配置deepin操作系统,后在deepin系统下运行文档问答机器人

3.为项目添加可视化界面

实践经历:

一、在虚拟机配置deepin操作系统

1.在deepin官网下载最新版的镜像文件 

        进入deepin官网(官网网址:https://www.deepin.org/index/zh),在首页点击“下载和帮助”—>“镜像下载”

        最上面的一个版本就是最新版本,点击官方下载

        

        进入后会显示三个链接,点击最下面的一个就开始下载了,我们大概下载了3个小时,可能是校园网过于慢了...

        

2.下载和安装VMware

        这一步超级简单,我们直接从电脑自带的应用市场下载的。版本够新,操作简单,且绝对是正版。但因为是正版软件,所以不可避免的需要“出点血”。前30天免费试用,后面需要付费购买。

3.在VMware上安装deepin系统

        我们主要是按照一个up主的讲解来安装的(【deepin深度系统VMware虚拟机安装教程】),但是中间有一些可能是因为版本问题不太一样。下面是我们的操作。

        打开VMware,点击创建新的虚拟机。

        选择典型。然后下一步。

        

        

        选择安装程序光盘映像文件,点击浏览,选择刚刚刚刚下载好的映像文件,打开,下一步。

        在这里选择linux和Ubuntu 64位,下一步。

        

        给虚拟机取一个名字,并选择一个位置(不建议c盘,因为很大)。下一步。

        

        输入分配的最大磁盘大小。这里注意一自己安装位置的盘的剩余容量,最大分配容量一定要小于剩余容量几个G,不然使用的时候会出问题(确实是这样,小组成员作死试过了TT)。我们分配了80G到128G。如果容量不够的话可以考虑外接硬盘。虚拟机选择将磁盘拆分成多个文件。

        

        点击完成,安装完毕。

4.配置deepin系统

        安装好后,就进入了deepin系统的界面,这里会提示一些初始的设置,我们就是什么都没有改,一切按照默认来设置的。

5.遇到的问题及解决
1)虚拟机安装deepin之后无法连接到网络

       我们一开始认为是deepin镜像文件版本不合适,导致与本机的的网络与虚拟机不匹配。我们重新下载了另外版本的deepin镜像文件,但是出现了同样的问题。鉴于此,我们上网查找了资料,了解到虚拟机没有连上网络是因为没有与主机共享网络,且虚拟机与主机共亨网络的方法主要依赖于使用的虚拟化软件以及网络配置。

        我们从网络配置入手,将虚拟机的网络适配器配置为为桥接模式。具体方法是:在VMware中找到deepin系统的虚拟机;右键虚拟机,选择“设置”->“硬件”->“网络适配器”;在网络适配器设置中选择桥接模式,使虚拟机与主机处于同一网络段。配置成功。虚拟机成功联网。

2)虚拟机分配的空间不够

       这算是一个无意中发现的大问题。装deepin系统时,我们对系统的大小分配并没有什么具体的概念。安装时VMware建议至少要预留20G,在网上查找到的教程建议不少于64G,但是也有教程说要求128G。在综合考虑了自己电脑磁盘剩余容量和需要完成的任务,我们决定按照教程分配64G。

        当我们刚结束安装deepin系统后,就发现数据盘飘红了。在寻找给虚拟硬盘扩容的方法后,我们一致认为,操作麻烦,而且虚拟机上现在什么都没有,不如重新安装。所以重新重复安装步骤,分配到了128G。在目前的运行中是可以正常使用的。

        至于扩容的方法,我们没有具体实验,但是找到了一些别的博主写的可行方法,具体方法贴在下面了。

        vmware虚拟机磁盘扩容与挂载——Zkaisen

二、在deepin中下载和安装anaconda

1.下载清华镜像源文件

        搜索清华镜像源,进入官网清华大学开源软件镜像站 | Tsinghua Open Source Mirror

        在搜索框中搜索anaconda,并进入。

        选择archive/,进入。

        选择适合自己虚拟机的版本,我们选择的是按照时间排序的最新版适配于Linux系统的这个版本。下载链接如下:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2024.02-1-Linux-x86_64.sh

        然后等待就好啦。

2.安装anaconda

        打开安装文件所在的文件夹。   

        右键,选择在终端打开。注意一定要在文件夹界面右键,不然路径不对会找不到安装文件。或者右键复制文件的路径后,在终端中cd 该路径。

        打开后应该是下面的状态没有(base)。(截图中有base是因为我已经安装好了)也就是“在配置deepin系统时自己起的用户名电脑名+镜像文件所在路径”的状态。

        输入命令,打开镜像文件。命令格式是“dash <镜像文件的名字>",如果下载的不是一个版本下面这个命令时没有用的。

dash Anaconda3-2024.02-1-Linux-x86_64.sh

        输入后会出现下面的提示。按照提示,回车,阅读协议。

        然后就是大段大段的协议,他不会一次性全部显示,需要不停的按enter来显示更多。

        一直到他询问是否同意协议,输入yes。

        这里会出现一个情况,就是协议最后会出现--end--的字样,但是不出现询问,要是按enter还会不停的出现空白行,就像下面这样。输入s就可以跳过。

        正常情况下就会出现安装地址确认了,像下面这样:

        Press ENTER to confirm the location:按enter确认安装地址,这个地址就是默认地址,我们就是按照默认来安装的,当然想改也可以。

        Press CTRL-C to abort the installation:按Ctrl+c取消安装。这里要小心,在安装时,我想把默认地址复制一下,以免之后找不到,选择以后习惯性按了Ctrl+c,就愉快的从头再来了。

        Or specify a different location below:这里就是改安装地址的方法了,直接在下面输入想要安装的位置。

        当出现“Thank you for your installation”时,即为安装成功。

3.打开anaconda

        先关闭运行完安装程序的终端,再重新打开终端,会发现再用户名前面多了(base)。这一步也可以不关闭终端,通过输入命令来实现,但是没有直接重启终端方便。

        输入命令,打开anaconda navigator。

anaconda-navigator

        会出现一个绿色的图标,等一会anaconda navigator就打开了。

        

        打开后正常界面是这样的:      

4.遇到的问题及解决
1)anaconda安装慢

        解决方法:一开始我们选用了官网安装的方法,发现下载安装文件后在终端输入命令确实开始安装了,但是显示需要安装8个小时,于是我们果断放弃,选择了清华源。我们按照对虚拟机的配置,选择了一个x86,64位的文件。输入命令后,安装成功。

2)安装anaconda后无法运行

        解决方法:我们按照windows装anaconda的思路,在安装成功后在搜索中搜索anaconda navigator,试图找到anaconda的可视化界面以便方便快捷的运行。但是没有找到。我们猜想是下载anaconda的版本过新导致不适配,有一部分安装不上或者无法被搜索到。于是重复了很多遍卸载重下安装的过程,但无论使用新版本还是旧版本问题都没有解决。上网查阅资料了解到anaconda navigator可以在终端输入命令打开。我们决定先打开再从软件内部找解决方案。打开后,找到了用户手册,发现在deepin系统中,anaconda navigator只能从终端打开。

三、下载安装pycharm

        我们还是选用了最简单的方法,通过应用市场下载。安装免费的edu版本就足够用了

四、在deepin上运行文档问答机器人

1.配置项目文件所需要的环境
  1)利用anaconda创建虚拟环境

        首先进入到base环境中,按照以下步骤进行安装。

        根据具体的python版本创建Anaconda虚拟环境,创建成功之后,可以使用activate命令进行激活虚拟环境。

conda create -n paddle_env python=3.8
conda activate paddle_env

此时所处的环境即为paddle_env。

 2)安装项目所需要的python库

numpy,faiss,rocketqa,markdown,tornado,transformers,requests,paddlepaddle,scipy,sentencepiece,pytorch

其中,python的版本必须高于3.6,paddlepaddle的版本必须高于2.0

 3)安装过程中出现的问题

问题一:安装faiss时出现错误。

解决办法:

更新pip和setuotools

pip install --upgrade pip setuptools

安装wheel

pip install wheel

安装编译依赖

sudo apt-get install buildessential cmake libopenblas-dev liblapack-dev

使用预编译的二进制包

conda install -c conda-forge faiss-cpu

问题二:安装paddlepaddle失败

解决办法:使用以下命令先清除缓存

pip cache purge

添加清华源镜像进行安装

pip install paddlepaddle -i https://pypi.tuna.tsinghua.edu.cn/simple
2.在pycharm中运行该项目代码
 1)给项目添加编译器

linux与windows有不同,linux下的编译器在bin目录下的python,而没有python.exe

2)运行项目文件

由于我们的配置问题,我们在deepin系统上仅对我们介入marbert-large版本的问答机器人进行试验。

五、添加玲珑使用数据集

        玲珑使用的数据相较于deepin来说少很多,我们使用了MarkDownload这个插件,直接对玲珑使用数据进行下载,直接保存为md格式。但是会有一些差错,后期进行了手动调整,由于数据集有限,所以整个调整过程并没有花费很多时间。最后按照之前处理deepin数据的格式,将玲珑数据的文件名进行修改相应的调整,使之也可以根据文件的相对路径拼接为该页面的网址。

        后续,直接将玲珑数据的路径添加到项目中,即可进行测试。并无发现差错。

六、为项目添加可视化界面

因为设备配置问题,可视化界面所进行的回答,使用的引入marbert-large版本的问答机器人,并没有尝试介入Chat-GLM模型的版本。

1)使用tkinter

Tkinter 是 Python 中的标准 GUI(图形用户界面)工具包,用于创建桌面应用程序的用户界面。它基于 Tcl/Tk 工具包,提供了创建简单而又功能强大的窗口应用程序的工具和组件。

以下是 Tkinter 的一些主要特点和组成部分:

  1. 跨平台性: Tkinter 是 Python 的标准库之一,因此可以在几乎所有支持 Python 的平台上使用,包括 Windows、Linux 和 macOS。

  2. 简单易用: Tkinter 的语法相对简单,易于学习和使用。它提供了一组基本的组件,如按钮、标签、文本框等,使得创建简单的用户界面非常容易。

  3. 组件丰富: 尽管 Tkinter 是一个轻量级的工具包,但它提供了丰富的 GUI 组件,包括按钮、标签、文本框、列表框、滚动条等,以满足各种应用程序的需求。

  4. 事件驱动: Tkinter 是基于事件驱动的,可以通过绑定事件处理函数来响应用户的操作,例如点击按钮或输入文本等。

  5. 可扩展性: 虽然 Tkinter 自带了许多常用的组件,但也支持通过继承和自定义来创建自定义的组件。

  6. 美观性: Tkinter 提供了一些简单的布局管理器,如 pack、grid 和 place,使得用户界面的设计更加灵活和美观。

使用 Tkinter,我们可以创建各种类型的桌面应用程序,包括简单的工具、数据可视化应用、游戏等。虽然 Tkinter 的功能相对较简单,但对于许多小型项目或学习目的来说,它是一个非常实用的工具。

核心代码如下:

def create_widgets(self):
        self.query_label = tk.Label(self.root, text="请输入您的问题:")
        self.query_label.pack(pady=10)

        self.query_entry = tk.Entry(self.root, width=70)  # 调整输入框大小
        self.query_entry.pack(pady=5)

        self.submit_button = tk.Button(self.root, text="查询", command=self.submit_query)
        self.submit_button.pack(pady=5)

        self.error_label = tk.Label(self.root, text="", fg="red")
        self.error_label.pack(pady=5)

        self.result_text = scrolledtext.ScrolledText(self.root, wrap=tk.WORD, width=100, height=20)  # 调整结果显示区域大小
        self.result_text.pack(pady=10)

    def send_query(self, query):
        input_data = {'query': query, 'topk': TOPK}
        result = requests.post(SERVICE_ADD, json=input_data)
        return json.loads(result.text)

    def display_results(self, query, res_json):
        result_str = "您的查询: " + query + "\n"
        for i in range(TOPK):
            title = res_json['answer'][i]['title']
            para = res_json['answer'][i]['para']
            result_str += '------------------------------------------------------------\n'
            result_str += '结果 ' + str(i + 1) + ':\n'
            result_str += '参考链接:' + title + '\n'
            result_str += '答案:' + para + '\n\n'
            result_str += '------------------------------------------------------------\n\n'

        self.result_text.insert(tk.END, result_str)
        self.history.append(result_str)  # 将结果添加到历史记录中

    def submit_query(self):
        query = self.query_entry.get()
        if query:
            res_json = self.send_query(query)
            self.display_results(query, res_json)
            self.query_entry.delete(0, tk.END)  # 清除输入框内容
        else:
            self.error_label.config(text="请输入查询内容!")

可视化界面如下:

2)使用Flask

        Flask 是一个轻量级的 Python Web 框架,设计简单而灵活,适用于快速开发小型 Web 应用程序。

核心代码如下:


app = Flask(__name__)

SERVICE_ADD = 'http://localhost:8888/rocketqa'
TOPK = 1

# 函数用于从问答服务获取答案
def get_answers(query):
    input_data = {'query': query, 'topk': TOPK}
    result = requests.post(SERVICE_ADD, json=input_data)
    res_json = json.loads(result.text)
    answers = []
    for i in range(TOPK):
        title = res_json['answer'][i]['title']
        para = res_json['answer'][i]['para']
        score = res_json['answer'][i]['probability']
        answers.append({'title': title, 'para': para})
    return answers

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/query', methods=['POST'])
def query():
    query = request.form['query']
    if query.strip() == '':
        return render_template('index.html', error_message="Please input a valid query.")

    # 调用获取答案的函数
    answers = get_answers(query)

    # 构建对话列表
    # conversation = [{'text': query, 'type': 'user'}]
    # for answer in answers:
    #     conversation.append({'text': answer['para'], 'type': 'bot'})

    conversation = [{'text': query, 'type': 'user'}]
    for answer in answers:
        conversation.append({
            'text': answer['para'],  # 这是答案的文本部分
            'link': answer['title'],  # 假设 title 包含了链接或者链接的文本
            'type': 'bot'
        })
    return render_template('results.html', query=query, conversation=conversation)

目前所能达到的界面如下:(后续会继续对界面进行美化)

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值