前言
说明:讲解时会对相关文章资料进行思想、结构、优缺点,内容进行提炼和记录,相关引用会标明出处,引用之处如有侵权,烦请告知删除。 转载请注明:DengBoCong
构建对话机器人的现有方法中,可以分为 generation-based(生成式)和retrieval-based(检索式),相对于生成式而言,检索式拥有的信息更加丰富,且运行流畅的特点。本篇文章不具体讲解模型,而是来好好阐述关于检索候选回复的实现,比如SMN模型、DAM模型等中,关于检索候选回复的实现。关于SMN模型的论文笔记和实现代码可以参考我的另一篇文章 和GitHub ,后续我还会对DAM论文和模型写一篇文章。
使用到的工具版本如下:
Solr:8.6.3
pysolr:3.9.0
python:3.7
CentOS:7.6
Docker:19.03.9
整体流程 我们讲解工具使用之前,首先简要的阐述一下我们的目的,如果已经了解过检索式对话系统或者阅读过相应论文,就不用看了。首先我们知道目的是检索候选回复,用什么检索呢?这个和具体模型结构和需求有关。拿SMN模型为例,利用启发式方法从索引中获取候选response,将前一轮的utterances ${u_1,…,u_{n-1}}$ (也就是对话的历史)和 $u_n$ 进行计算,根据他们的tf-idf 得分,从 ${u_1,…,u_{n-1}}$ 中提取前 $5$ 个关键字,然后将扩展后的message用于索引,并使用索引的内联检索算法来检索候选response。
模型结构和训练至关重要,但是检索候选回复也是使得整个对话流程实现闭环的关键。我们了解了检索的目的和整体流程,那我们从何实现?方式有很多,可以自行编写一个脚本从数据集中生成一个索引候选数据集(这个是我最开始用的方法,但毕竟没专门研究过检索,所以写的很粗糙,勉强验证功能可以,用作正式使用就不行了),还有一种就是使用现有的检索工具,比如Lucene、Solr、ElasticSearch等等。所以这篇文章就是来讲解部署solr和使用python实现检索(为什么选用Solr?不是说那种工具好坏,而是佛系使用,貌似ElasticSearch现在很火的样子,哈哈哈)。
Solr和Pysolr Solr它是一种开放源码的、基于 Lucene Java 的搜索服务器,易于加入到 Web 应用程序中。Lucene很底层,从底层代码层面来实现需求,而Solr在其上进行了封装,你如果想要实现脱机检索,那还是使用Lucene吧。Solr 提供了层面搜索(就是统计)、命中醒目显示并且支持多种输出格式(包括XML/XSLT 和JSON等格式)。它易于安装和配置,而且附带了一个基于 HTTP 的管理界面。Solr已经在众多大型的网站中使用,较为成熟和稳定。Solr 包装并扩展了 Lucene,所以Solr的基本上沿用了Lucene的相关术语。更重要的是,Solr 创建的索引与 Lucene 搜索引擎库完全兼容。通过对Solr 进行适当的配置,某些情况下可能需要进行编码,Solr 可以阅读和使用构建到其他 Lucene 应用程序中的索引。此外,很多 Lucene 工具(如Nutch、 Luke)也可以使用Solr 创建的索引。可以使用 Solr 的表现优异的基本搜索功能,也可以对它进行扩展从而满足企业的需要,Solr官网 (官方将其和Lucene并列放在一起,嘿嘿嘿,万变不离其宗,看官方文档)。
而Pysolr是基于Python的Solr轻量级封装,它提供了服务器查询并返回基于查询的结果接口。简单来说就是Pysolr封装了Solr的各种http请求,使用起来非常方便,你可以直接从pypi中直接导入(这个就要吐槽一下Pylucene了,不能从pipy直接导入),PySolr官方地址 ,上面有使用的示例和API,可以自行去看,这里配一张Solr的示意图,方面后面理解:
部署Solr 部署 都0202年了,部署服务应用都是用容器了吧,我这里讲解用Docker部署solr,不了解的可以参考我的关于Docker的几篇文章 ,我这里就不介绍Docker了,默认会就接着往下讲了。
有了docker环境之后,首先先将solr拉下来,我这里拉的是8.6.3的版本(ps:不喜欢拉最新的,因为最新的可能其他的附属库跟不上更新,出问题)
1 2 3 4 5 6 7 docker pull solr:8.6.3 docker run -itd --name solr -p 8983:8983 solr:8.6.3 docker exec -it --user=solr solr bin/solr create_core -c smn
指令具体含义以及core是啥,请自行查阅资料,或者研究一下solr,毕竟先学习基础再来实战。上面构建solr运行容器是简单粗暴且实用的方法,也可以和我一样使用Dockerfile进行构建镜像和容器,Dockerfile内容如下(内容来自docker-solr 项目,官方的docker镜像项目):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 FROM openjdk:11-jre LABEL maintainer="The Apache Lucene/Solr Project" LABEL repository="https://github.com/docker-solr/docker-solr" ARG SOLR_VERSION="8.6.3" ARG SOLR_SHA512="f040d4489118b655bd27451a717c1f22f180c398638d944a53889a1a449e7032b016cecbff1979c2e8bfd51fc037dd613f3b968254001d34fe0e8fc4f6761dcf" ARG SOLR_KEYS="902CC51935C140BF820230961FD5295281436075" ARG SOLR_DOWNLOAD_URL ARG SOLR_DOWNLOAD_SERVER RUN set -ex; \ apt-get update; \ apt-get -y install acl dirmngr gpg lsof procps wget netcat gosu tini; \ rm -rf /var/lib/apt/lists/*; \ cd /usr/local /bin; wget -nv https://github.com/apangin/jattach/releases/download/v1.5/jattach; chmod 755 jattach; \ echo >jattach.sha512 "d8eedbb3e192a8596c08efedff99b9acf1075331e1747107c07cdb1718db2abe259ef168109e46bd4cf80d47d43028ff469f95e6ddcbdda4d7ffa73a20e852f9 jattach" ; \ sha512sum -c jattach.sha512; rm jattach.sha512 ENV SOLR_USER="solr" \ SOLR_UID="8983" \ SOLR_GROUP="solr" \ SOLR_GID="8983" \ SOLR_CLOSER_URL="http://www.apache.org/dyn/closer.lua?filename=lucene/solr/$SOLR_VERSION /solr-$SOLR_VERSION .tgz&action=download" \ SOLR_DIST_URL="https://www.apache.org/dist/lucene/solr/$SOLR_VERSION /solr-$SOLR_VERSION .tgz" \ SOLR_ARCHIVE_URL="https://archive.apache.org/dist/lucene/solr/$SOLR_VERSION /solr-$SOLR_VERSION .tgz" \ PATH="/opt/solr/bin:/opt/docker-solr/scripts:$PATH " \ SOLR_INCLUDE=/etc/default/solr.in.sh \ SOLR_HOME=/var/solr/data \ SOLR_PID_DIR=/var/solr \ SOLR_LOGS_DIR=/var/solr/logs \ LOG4J_PROPS=/var/solr/log4j2.xml RUN set -ex; \ groupadd -r --gid "$SOLR_GID " "$SOLR_GROUP " ; \ useradd -r --uid "$SOLR_UID " --gid "$SOLR_GID " "$SOLR_USER " RUN set -ex; \ export GNUPGHOME="/tmp/gnupg_home" ; \ mkdir -p "$GNUPGHOME " ; \ chmod 700 "$GNUPGHOME " ; \ echo "disable-ipv6" >> "$GNUPGHOME /dirmngr.conf" ; \ for key in $SOLR_KEYS ; do \ found='' ; \ for server in \ ha.pool.sks-keyservers.net \ hkp://keyserver.ubuntu.com:80 \ hkp://p80.pool.sks-keyservers.net:80 \ pgp.mit.edu \ ; do \ echo " trying $server for $key " ; \ gpg --batch --keyserver "$server " --keyserver-options timeout=10 --recv-keys "$key " && found=yes && break ; \ gpg --batch --keyserver "$server " --keyserver-options timeout=10 --recv-keys "$key " && found=yes && break ; \ done ; \ test -z "$found " && echo >&2 "error: failed to fetch $key from several disparate servers -- network issues?" && exit 1; \ done ; \ exit 0 RUN set -ex; \ export GNUPGHOME="/tmp/gnupg_home" ; \ MAX_REDIRECTS=1; \ if [ -n "$SOLR_DOWNLOAD_URL " ]; then \ MAX_REDIRECTS=4; \ SKIP_GPG_CHECK=true ; \ elif [ -n "$SOLR_DOWNLOAD_SERVER " ]; then \ SOLR_DOWNLOAD_URL="$SOLR_DOWNLOAD_SERVER /$SOLR_VERSION /solr-$SOLR_VERSION .tgz" ; \ fi ; \ for url in $SOLR_DOWNLOAD_URL $SOLR_CLOSER_URL $SOLR_DIST_URL $SOLR_ARCHIVE_URL ; do \ if [ -f "/opt/solr-$SOLR_VERSION .tgz" ]; then break ; fi ; \ echo "downloading $url " ; \ if wget -t 10 --max-redirect $MAX_REDIRECTS --retry-connrefused -nv "$url " -O "/opt/solr-$SOLR_VERSION .tgz" ; then break ; else rm -f "/opt/solr-$SOLR_VERSION .tgz" ; fi ; \ done ; \ if [ ! -f "/opt/solr-$SOLR_VERSION .tgz" ]; then echo "failed all download attempts for solr-$SOLR_VERSION .tgz" ; exit 1; fi ; \ if [ -z "$SKIP_GPG_CHECK " ]; then \ echo "downloading $SOLR_ARCHIVE_URL .asc" ; \ wget -nv "$SOLR_ARCHIVE_URL .asc" -O "/opt/solr-$SOLR_VERSION .tgz.asc" ; \ echo "$SOLR_SHA512 */opt/solr-$SOLR_VERSION .tgz" | sha512sum -c -; \ (>&2 ls -l "/opt/solr-$SOLR_VERSION .tgz" "/opt/solr-$SOLR_VERSION .tgz.asc" ); \ gpg --batch --verify "/opt/solr-$SOLR_VERSION .tgz.asc" "/opt/solr-$SOLR_VERSION .tgz" ; \ else \ echo "Skipping GPG validation due to non-Apache build" ; \ fi ; \ tar -C /opt --extract --file "/opt/solr-$SOLR_VERSION .tgz" ; \ (cd /opt; ln -s "solr-$SOLR_VERSION " solr); \ rm "/opt/solr-$SOLR_VERSION .tgz" *; \ rm -Rf /opt/solr/docs/ /opt/solr/dist/{solr-core-$SOLR_VERSION .jar,solr-solrj-$SOLR_VERSION .jar,solrj-lib,solr-test-framework-$SOLR_VERSION .jar,test-framework}; \ mkdir -p /opt/solr/server/solr/lib /docker-entrypoint-initdb.d /opt/docker-solr; \ chown -R 0:0 "/opt/solr-$SOLR_VERSION " ; \ find "/opt/solr-$SOLR_VERSION " -type d -print0 | xargs -0 chmod 0755; \ find "/opt/solr-$SOLR_VERSION " -type f -print0 | xargs -0 chmod 0644; \ chmod -R 0755 "/opt/solr-$SOLR_VERSION /bin" "/opt/solr-$SOLR_VERSION /contrib/prometheus-exporter/bin/solr-exporter" /opt/solr-$SOLR_VERSION /server/scripts/cloud-scripts; \ cp /opt/solr/bin/solr.in.sh /etc/default/solr.in.sh; \ mv /opt/solr/bin/solr.in.sh /opt/solr/bin/solr.in.sh.orig; \ mv /opt/solr/bin/solr.in.cmd /opt/solr/bin/solr.in.cmd.orig; \ chown root:0 /etc/default/solr.in.sh; \ chmod 0664 /etc/default/solr.in.sh; \ mkdir -p /var/solr/data /var/solr/logs; \ (cd /opt/solr/server/solr; cp solr.xml zoo.cfg /var/solr/data/); \ cp /opt/solr/server/resources/log4j2.xml /var/solr/log4j2.xml; \ find /var/solr -type d -print0 | xargs -0 chmod 0770; \ find /var/solr -type f -print0 | xargs -0 chmod 0660; \ sed -i -e "s/\"\$(whoami)\" == \"root\"/\$(id -u) == 0/" /opt/solr/bin/solr; \ sed -i -e 's/lsof -PniTCP:/lsof -t -PniTCP:/' /opt/solr/bin/solr; \ chown -R "0:0" /opt/solr-$SOLR_VERSION /docker-entrypoint-initdb.d /opt/docker-solr; \ chown -R "$SOLR_USER :0" /var/solr; \ { command -v gpgconf; gpgconf --kill all || :; }; \ rm -r "$GNUPGHOME " COPY --chown=0:0 scripts /opt/docker-solr/scripts VOLUME /var/solr EXPOSE 8983 WORKDIR /opt/solr USER $SOLR_USER ENTRYPOINT ["docker-entrypoint.sh" ] CMD ["solr-foreground" ]
容器运行情况如下: 接下来可以访问:http://xxxxxx:8983/solr/,进入到solr界面,如下: 然后点击Core Admin,查看一下自己刚刚创建的Core,如下: 然后选择smn就可以使用了,如下: 结束了?当然没有,哪有那么简单的事儿,首先我们上面算是基本部署好了solr,但是我们需要进行一些必要的使得我们能更好的使用,比如我们需要对文档进行分词,添加相似度计算类(用于tf-idf计算),接下来就说明如何配置这两个东西。
配置IK 首先是IK,IK Analyzer(中文分词器)是一个开源的,基于java语言开发的轻量级的中文分词工具包。最初,它是以开源项目 Lucene为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为 面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。
Solr 5以前的可以装上老版本 ,提取码:g5ib
Solr 6使用这个版本
Solr 7&8使用这个版本
注意要将IK源码打成JAR包(作为一个老Java,打包还是不难的)。接着将jar包通过传输软件或其它方式传入宿主机的某一文件夹内,然后使用指令将jar包复制到Solr容器的分词包文件夹中:
1 docker cp ik-analyzer.jar solr:/opt/solr-8.6.3/contrib/analysis-extras/lucene-libs
查看 Solr 容器在宿主机中数据卷的位置:
将IK分词器配置到 Solr 的核心配置文件中,Source为上面的Mounts中的:
1 2 cd {Source}/data/myIKCore/conf/vim solrconfig.xml
然后添加如下内容:
1 2 3 <lib dir="${solr.install.dir:../../../..} /contrib/analysis-extras/lucene-libs/" regex="ik-analyzer.jar" />
声明中文分词器
找到指定位置添加配置
1 2 3 4 5 6 7 <!-- IKAnalyzer --> <fieldType name ="text_ik" class ="solr.TextField" > <!-- 索引时候的分词器 --> <analyzer type ="index" isMaxWordLength ="false" class="org.wltea.analyzer.lucene.IKAnalyzer" /> <!-- 查询时候的分词器 --> <analyzer type ="query" isMaxWordLength ="true" class="org.wltea.analyzer.lucene.IKAnalyzer" /> </fieldType>
重启 Solr 容器
选择刚刚创建的核心选择器
配置相似度 到了这里,你其实可以直接用了,但是如果使用tf-idf的话,会报错,如下:
1 2 org.apache.solr.client.solrj.SolrServerException: No live SolrServers available to handle this request null:java.lang.UnsupportedOperationException: requires a TFIDFSimilarity (such as ClassicSimilarity)
所以还是需要配置,打开 managed-schema
将下面一行添加进就可以了:
1 <similarity class="solr.ClassicSimilarityFactory" />
pysolr使用 1 2 3 4 5 6 solr = pysolr.Solr(url=solr_server, always_commit=True, timeout=10) solr.ping() solr.add(docs=responses)
接下来进行查询,首先我们提取关键词词,我这里将我的tf-idf方法的代码贴出来,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def get_tf_idf_top_k (history: list , k: int = 5 ): """ 使用tf_idf算法计算权重最高的k个词,并返回 Args: history: 上下文语句 k: 返回词数量 Returns: top_5_key """ tf_idf = {} vectorizer = TfidfVectorizer(analyzer='word' ) weights = vectorizer.fit_transform(history).toarray()[-1 ] key_words = vectorizer.get_feature_names() for i in range (len (weights)): tf_idf[key_words[i]] = weights[i] top_k_key = [] tf_idf_sorted = sorted (tf_idf.items(), key=lambda x: x[1 ], reverse=True )[:k] for element in tf_idf_sorted: top_k_key.append(element[0 ]) return top_k_key
然后将得到的五个关键词通过query的语法进行组合,得到查询语句,我这里只返回前十个分数最高的候选回复:
1 2 3 4 5 6 7 query = "{!func}sum(" for keyin tf_idf: query += "product(idf(utterance," + key + "),tf(utterance," + key + "))," query += ")" candidates = self.solr.search(q=query, start=0 , rows=10 ).docs
查询回复格式如下: 然后检索得到了候选回复就可以喂给模型了。
Author:
DengBoCong
Permalink:
http://dengbocong.cn/Deep-Learning/351df5ecefe5/
License:
Licensed under the Apache License, Version 2.0 (the "License")
Slogan:
Stay hungry, Stay foolish.