1. 程式人生 > >三十、第一版聊天機器人誕生——吃了字幕長大的小二兔

三十、第一版聊天機器人誕生——吃了字幕長大的小二兔


在上一節中我分享了建設好的影視劇字幕聊天語料庫,本節基於這個語料庫開發我們的聊天機器人,因為是第一版,所以機器人的思緒還有點小亂,答非所問、驢脣不對馬嘴得比較搞笑,大家湊合玩

第一版思路

首先要考慮到我的影視劇字幕聊天語料庫特點,它是把影視劇裡面的說話內容一句一句以回車換行羅列的三千多萬條中國話,那麼相鄰的第二句其實就很可能是第一句的最好的回答,另外,如果對於一個問句有很多種回答,那麼我們可以根據相關程度以及歷史聊天記錄來把所有回答排個序,找到最優的那個,這麼說來這是一個搜尋和排序的過程。對!沒錯!我們可以藉助搜尋技術來做第一版。

lucene+ik

lucene是一款開源免費的搜尋引擎庫,java語言開發。ik全稱是IKAnalyzer,是一個開源中文切詞工具。我們可以利用這兩個工具來對語料庫做切詞建索引,並通過文字搜尋的方式做文字相關性檢索,然後把下一句取出來作為答案候選集,然後再通過各種方式做答案排序,當然這個排序是很有學問的,聊天機器人有沒有智慧一半程度上體現在了這裡(還有一半體現在對問題的分析上),本節我們的主要目的是打通這一套機制,至於“智慧”這件事我們以後逐個拆解開來不斷研究。

建索引

首先用eclipse建立一個maven工程,如下:

maven幫我們自動生成了pom.xml檔案,這配置了包依賴資訊,我們在dependencies標籤中新增如下依賴:

<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>4.10.4</version>
</dependency>
<dependency>
    <groupId
>
org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>4.10.4</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version
>
4.10.4</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.1.41</version> </dependency>

並在project標籤中增加如下配置,使得依賴的jar包都能自動拷貝到lib目錄下:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <id>copy-dependencies</id>
          <phase>prepare-package</phase>
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <configuration>
            <outputDirectory>${project.build.directory}/lib</outputDirectory>
            <overWriteReleases>false</overWriteReleases>
            <overWriteSnapshots>false</overWriteSnapshots>
            <overWriteIfNewer>true</overWriteIfNewer>
          </configuration>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifest>
            <addClasspath>true</addClasspath>
            <classpathPrefix>lib/</classpathPrefix>
            <mainClass>theMainClass</mainClass>
          </manifest>
        </archive>
      </configuration>
    </plugin>
  </plugins>
</build>

從https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/ik-analyzer/IK%20Analyzer%202012FF_hf1_source.rar下載ik的原始碼並把其中的src/org目錄拷貝到chatbotv1工程的src/main/java下,然後重新整理maven工程,效果如下:

在com.shareditor.chatbotv1包下maven幫我們自動生成了App.java,為了辨識我們改成Indexer.java,關鍵程式碼如下:

Analyzer analyzer = new IKAnalyzer(true);
IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_4_9, analyzer);
iwc.setOpenMode(OpenMode.CREATE);
iwc.setUseCompoundFile(true);
IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File(indexPath)), iwc);

BufferedReader br = new BufferedReader(new InputStreamReader(
        new FileInputStream(corpusPath), "UTF-8"));
String line = "";
String last = "";
long lineNum = 0;
while ((line = br.readLine()) != null) {
	line = line.trim();

	if (0 == line.length()) {
		continue;
	}

	if (!last.equals("")) {
		Document doc = new Document();
		doc.add(new TextField("question", last, Store.YES));
		doc.add(new StoredField("answer", line));
		indexWriter.addDocument(doc);
	}
	last = line;
	lineNum++;
	if (lineNum % 100000 == 0) {
		System.out.println("add doc " + lineNum);
	}
}
br.close();

indexWriter.forceMerge(1);
indexWriter.close();

編譯好後拷貝src/main/resources下的所有檔案到target目錄下,並在target目錄下執行

java -cp $CLASSPATH:./lib/:./chatbotv1-0.0.1-SNAPSHOT.jar com.shareditor.chatbotv1.Indexer ../../subtitle/raw_subtitles/subtitle.corpus ./index

最終生成的索引目錄index通過lukeall-4.9.0.jar檢視如下:

檢索服務

Analyzer analyzer = new IKAnalyzer(true);
QueryParser qp = new QueryParser(Version.LUCENE_4_9, "question", analyzer);
if (topDocs.totalHits == 0) {
	qp.setDefaultOperator(Operator.AND);
	query = qp.parse(q);
	System.out.println(query.toString());
	indexSearcher.search(query, collector);
	topDocs = collector.topDocs();
}

if (topDocs.totalHits == 0) {
	qp.setDefaultOperator(Operator.OR);
	query = qp.parse(q);
	System.out.println(query.toString());
	indexSearcher.search(query, collector);
	topDocs = collector.topDocs();
}


ret.put("total", topDocs.totalHits);
ret.put("q", q);
JSONArray result = new JSONArray();
for (ScoreDoc d : topDocs.scoreDocs) {
	Document doc = indexSearcher.doc(d.doc);
	String question = doc.get("question");
	String answer = doc.get("answer");
	JSONObject item = new JSONObject();
	item.put("question", question);
	item.put("answer", answer);
	item.put("score", d.score);
	item.put("doc", d.doc);
	result.add(item);
}
ret.put("result", result);

其實就是查詢建好的索引,通過query詞做切詞拼lucene query,然後檢索索引的question欄位,匹配上的返回answer欄位的值作為候選集,使用時挑出候選集裡的一條作為答案

這個server可以通過http訪問,如http://127.0.0.1:8765/?q=hello(注意:如果是中文需要轉成urlcode傳送,因為java端讀取時按照urlcode解析),server的啟動方法是:

java -cp $CLASSPATH:./lib/:./chatbotv1-0.0.1-SNAPSHOT.jar com.shareditor.chatbotv1.Searcher

聊天介面

先看下我們的介面是什麼樣的,然後再說怎麼做的

首先需要有一個可以展示聊天內容的框框,我們選擇ckeditor,因為它支援html格式內容的展示,然後就是一個輸入框和傳送按鈕,html程式碼如下:

<div class="col-sm-4 col-xs-10">
    <div class="row">
        <textarea id="chatarea">
            <div style='color: blue; text-align: left; padding: 5px;'>機器人: 喂,大哥您好,您終於肯跟我聊天了,來侃侃唄,我來者不拒!</div>
            <div style='color: blue; text-align: left; padding: 5px;'>機器人: 啥?你問我怎麼這麼聰明會聊天?因為我剛剛吃了一堆影視劇字幕!</div>
        </textarea>
    </div>
    <br />

    <div class="row">
        <div class="input-group">
            <input type="text" id="input" class="form-control" autofocus="autofocus" onkeydown="submitByEnter()" />
            <span class="input-group-btn">
            <button class="btn btn-default" type="button" onclick="submit()">傳送</button>
          </span>
        </div>
    </div>
</div>



<script type="text/javascript">

        CKEDITOR.replace('chatarea',
                {
                    readOnly: true,
                    toolbar: ['Source'],
                    height: 500,
                    removePlugins: 'elementspath',
                    resize_enabled: false,
                    allowedContent: true
                });
   
</script>

為了呼叫上面的聊天server,需要實現一個傳送請求獲取結果的控制器,如下:

public function queryAction(Request $request)
{
    $q = $request->get('input');
    $opts = array(
        'http'=>array(
            'method'=>"GET",
            'timeout'=>60,
        )
    );
    $context = stream_context_create($opts);
    $clientIp = $request->getClientIp();
    $response = file_get_contents('http://127.0.0.1:8765/?q=' . urlencode($q) . '&clientIp=' . $clientIp, false, $context);
    $res = json_decode($response, true);
    $total = $res['total'];
    $result = '';
    if ($total > 0) {
        $result = $res['result'][0]['answer'];
    }
    return new Response($result);
}

這個控制器的路由配置為:

chatbot_query:
    path:     /chatbot/query
    defaults: { _controller: AppBundle:ChatBot:query }

因為聊天server響應時間比較長,為了不導致web介面卡住,我們在執行submit的時候非同步發請求和收結果,如下:

    var xmlHttp;
    function submit() {
        if (window.ActiveXObject) {
            xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        else if (window.XMLHttpRequest) {
            xmlHttp = new XMLHttpRequest();
        }
        var input = $("#input").val().trim();
        if (input == '') {
            jQuery('#input').val('');
            return;
        }
        addText(input, false);
        jQuery('#input').val('');
        var datastr = "input=" + input;
        datastr = encodeURI(datastr);
        var url = "/chatbot/query";
        xmlHttp.open("POST", url, true);
        xmlHttp.onreadystatechange = callback;
        xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xmlHttp.send(datastr);
    }

    function callback() {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
            var responseText = xmlHttp.responseText;
            addText(responseText, true);
        }
    }

這裡的addText是往ckeditor裡新增一段文字,方法如下:

function addText(text, is_response) {
    var oldText = CKEDITOR.instances.chatarea.getData();
    var prefix = '';
    if (is_response) {
        prefix = "<div style='color: blue; text-align: left; padding: 5px;'>機器人: "
    } else {
        prefix = "<div style='color: darkgreen; text-align: right; padding: 5px;'>我: "
    }
    CKEDITOR.instances.chatarea.setData(oldText + "" + prefix + text + "</div>");
}

和機器人對話初體驗

雖然效果暫時還不咋地,但是整體流程建起來了,後面就是不斷完善演算法了,也希望牛人們多指點方案

相關推薦

第一版聊天機器人誕生——字幕長大

在上一節中我分享了建設好的影視劇字幕聊天語料庫,本節基於這個語料庫開發我們的聊天機器人,因為是第一版,所以機器人的思緒還有點小亂,答非所問、驢脣不對馬嘴得比較搞笑,大家湊合玩 第一版思路 首先要考慮到我的影視劇字幕聊天語料庫特點,它是把影視劇裡面的說話內容一句一句以回車換行羅列的三千多萬條中國話,那

python之Flask框架()檢視:返回狀態碼重定向狀態保持請求鉤子

一、返回狀態碼和abort函式 1.return直接返回狀態碼: return可以返回自定義的不符合http協議的狀態碼。 作用:實現前後端的資料互動。 程式碼: from flask import Flask app = Flask(__name__) @app.rou

從零開始之驅動發開linux驅動(mmap使用舉例)

上節學習了mmap的對映原理,我們知道mmap對映分為四步: 1.在程序的虛擬地址空間的,建立虛擬對映區域(vm_area_struct) 2.檔案實體地址和程序虛擬地址的一一對映關係(remap_pfn_range 將核心記憶體重新對映到使用者空間) 3.程序發起對這片對映空間的訪

Vue-cli專案結構講解

                       Vue-cl

ORACLE WITH AS 用法

                ORACLE WITH AS 用法 With查詢語句不是以select開始的,而是以“WIT

完全分散式部署Hadoop

      完全分散式部署Hadoop 文章步驟:     1)準備3臺客戶機(關閉防火牆、靜態ip、主機名稱、每臺虛擬機器互聯互通)     2)安裝jdk     3)配置jdk環境變數     4)安裝hadoop     5)配置hadoop環境變數     6)安

scrapy模擬登陸

1. 回顧之前的模擬登陸的方法 1.1 requests模組是如何實現模擬登陸的? - 直接攜帶cookies請求頁面 找url地址,傳送post請求儲存cookie 1.2 selenium是如何模擬登陸的? 找到對應的input標籤,輸入文字點

Linux 程序與訊號——訊號的概念及 signal 函式

30.1 訊號的基本概念 訊號(signal)機制是Linux 系統中最為古老的程序之間的通訊機制,解決程序在正常執行過程中被中斷的問題,導致程序的處理流程會發生變化 訊號是軟體中斷 訊號是非同步事件   不可預見 訊號有自己的名稱和編號 訊號和異常處理機制

Java面向物件之介面

一個類最多隻能有一個直接的父類。但是有多個間接的父類。 java是單繼承。 class Ye{ String name; } class Fu extends Ye{ } // class Zi extends Fu{ } class MyClass {

遠端升級程式設計思路

//1.通知 #主程式# 升級 //2.啟動升級程式 //3.下載升級資源包 //4.當前版本主程式備份 //5.解壓下載的升級包 //6.關閉當前主程式 //7.執行升級 //8.檢測升級結果 //9

深入理解JVM學習筆記(JVM 記憶體分配----逃逸分析與棧上分配)

一、概念 我們之前提到過,JVM堆已經不是物件記憶體分配的唯一選擇。 棧上分配就是java虛擬機器提供的一種優化技術,基本思想是對於那些執行緒私有的物件(指的是不可能被其他執行緒訪問的物件),可以將它們打散分配在棧上,而不是分配在堆上。分配在棧上的好處是可以在函式呼叫結束

【OpenCV學習筆記】輪廓特徵屬性及應用(七)—位置關係及輪廓匹配

輪廓特徵屬性及應用(七)—位置關係及輪廓匹配 1.計算點與輪廓的距離及位置關係——pointPolygonTest() 2.矩的計算——moments() 3.形狀匹配(比較兩個形狀或輪廓間的相似度)

《苦練演算法》-劍指Offer- 的k個數-python編寫

題目描述 HZ偶爾會拿些專業問題來忽悠那些非計算機專業的同學。今天測試組開完會後,他又發話了:在古老的一維模式識別中,常常需要計算連續子向量的最大和,當向量全為正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢

Unity 遊戲框架搭建 2019 (一) MenuItem 顯示順序問題 & 類的提取

在上一篇,我們得出了兩個核心的學習思路: 根據問題去學習,並收集。 主動學習,並思考適用場景。 我們今天解決 MenuItem 顯示順序問題。 目前 MenuItem 顯示如圖所示: 我們來看下 MenuItem 這個屬性構造的定義。 第二個引數是,是否是驗證方法,目前不用理解,官網上預設是 fa

ui界面設計公司搜集精彩設計例:移動端APP界面設計欣賞之

lis bin 郵箱 移動 藍藍設計 wid target tro 也會 如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這裏 藍藍設計是一家地處北京的界面設計公司,經常會接到移動端APP界面設計的項目,比如給一起海帶做的海外代購APP、給詞覓公司做的社交AP

一個lstm單元讓聊天機器人學會甄嬛體

當今比較流行的seq2seq基本都是用lstm組成的編碼器解碼器模型實現,開源的實現大都基於one-hot的embedding(沒有詞向量表達的資訊量大),因此打算自己實現一個基於word2vec詞向量的強大的seq2seq模型,首先實現了只有一個lstm單元的機器人 下載《甄嬛傳》小說原文 上網隨

步輕松打造微信聊天機器人(附源碼)

exce 聊天 pos 平臺開發 weather ebo doctype amr 便在 最近微信公眾平臺開發是熱門,我也跟風做了一個陪聊的公眾號。 其實類似的自動回話程序早就有了,比如前一陣很火的小黃雞(還是小黃鴨來著?)。但盡管是跟風,也要體現一些不同。別

TCP次連接四次關閉

現在 關閉連接 我想 服務器 alt 技術分享 三次 att 好的 客戶端說:我想連接,可以嗎? 服務器說:可以。 客戶端說:好的,我知道了。 客戶端說:我想關閉連接,可以嗎? 服務器說:好的。 服務器說:你現在關閉吧。 客戶端說:好的,我知道了。 二十、TCP三

Linux網絡相關firewalld和netfilternetfilter5表5鏈介紹

Linux網絡 filewalld和netfilter netfilter5表5鏈 iptables語法 三十一、Linux網絡相關、firewalld和netfilter、netfilter5表5鏈介紹、iptables語法一、Linux網絡相關(一)ifconfig:查看網卡IP,若沒有該

Linux學習筆記()iptables filter表案例 iptables nat表應用

iptables filter表案例、 iptables nat表應用 一、iptables filter表案例需求:將80、20、21端口放行,對22端口指定特定的ip才放行以下為操作方法:vim /usr/local/sbin/iptables.sh //加入如下內容#! /bin/bas