Serverless 實戰:如何結合 NLP 實現文本摘要和關鍵詞提取?

對文本進行自動摘要的提取和關鍵詞的提取,屬於天然語言處理的範疇。提取摘要的一個好處是可讓閱讀者經過最少的信息判斷出這個文章對本身是否有意義或者價值,是否須要進行更加詳細的閱讀;而提取關鍵詞的好處是可讓文章與文章之間產生關聯,同時也可讓讀者經過關鍵詞快速定位到和該關鍵詞相關的文章內容。html

文本摘要和關鍵詞提取均可以和傳統的 CMS 進行結合,經過對文章 / 新聞等發佈功能進行改造,同步提取關鍵詞和摘要,放到 HTML 頁面中做爲 Description 和 Keyworks。這樣作在必定程度上有利於搜索引擎收錄,屬於 SEO 優化的範疇。python

關鍵詞提取

關鍵詞提取的方法不少,可是最多見的應該就是tf-idf了。git

經過jieba實現基於tf-idf關鍵詞提取的方法:github

jieba.analyse.extract_tags(text, topK=5, withWeight=False, allowPOS=('n', 'vn', 'v'))

文本摘要

文本摘要的方法也有不少,若是從廣義上來劃分,包括提取式和生成式。其中提取式就是在文章中經過TextRank等算法,找出關鍵句而後進行拼裝,造成摘要,這種方法相對來講比較簡單,可是很難提取出真實的語義等;另外一種方法是生成式,經過深度學習等方法,對文本語義進行提取再生成摘要。算法

若是簡單理解,提取式方式生成的摘要,全部句子來自原文,而生成式方法則是獨立生成的。express

爲了簡化難度,本文將採用提取式來實現文本摘要功能,經過 SnowNLP 第三方庫,實現基於TextRank的文本摘要功能。咱們以《海底兩萬裏》部份內容做爲原文,進行摘要生成:json

原文:api

這些事件發生時,我剛從美國內布拉斯加州的貧瘠地區作完一項科考工做回來。我當時是巴黎天然史博物館的客座教授,法國政府派我參加此次考察活動。我在內布拉斯加州度過了半年時間,收集了許多珍貴資料,滿載而歸,3 月底抵達紐約。我決定 5 月初動身回法國。因而,我就抓緊這段候船逗留時間,把收集到的礦物和動植物標本進行分類整理,可就在這時,斯科舍號出事了。
我對當時的街談巷議天然瞭如指掌,再說了,我怎能聽而不聞、無動於衷呢?我把美國和歐洲的各類報刊讀了又讀,但未能深刻了解真相。神祕莫測,百思不得其解。我冥思苦想,搖擺於兩個極端之間,始終形不成一種看法。其中確定有名堂,這是無可置疑的,若是有人表示懷疑,就請他們去摸一摸斯科舍號的傷口好了。
我到紐約時,這個問題正炒得沸反盈天。某些不學無術之徒提出設想,有說是浮動的小島,也有說是不可捉摸的暗礁,不過,這些個假設統統都被推翻了。很顯然,除非這暗礁腹部裝有機器,否則的話,它怎能如此快速地轉移呢?
一樣的道理,說它是一塊浮動的船體或是一堆大船殘片,這種假設也不能成立,理由仍然是移動速度太快。
那麼,問題只能有兩種解釋,人們各持己見,天然就分紅觀點大相徑庭的兩派:一派說這是一個力大無比的怪物,另外一派說這是一艘動力極強的「潛水船」。
哦,最後那種假設當然能夠接受,但到歐美各國調查以後,也就難以自圓其說了。有哪一個普通人會擁有如此強大動力的機械?這是不可能的。他在何地什麼時候叫何人制造了這麼個龐然大物,並且如何能在建造中作到風聲不走露呢?
看來,只有政府纔有可能擁有這種破壞性的機器,在這個災難深重的時代,人們想方設法要加強戰爭武器威力,那就有這種可能,一個國家瞞着其餘國家在試製這類駭人聽聞的武器。繼夏斯勃步槍以後有水雷,水雷以後有水下撞錘,而後魔道攀升反應,事態愈演愈烈。至少,我是這樣想的。瀏覽器

經過 SnowNLP 提供的算法:架構

from snownlp import SnowNLP
 
text = " 上面的原文內容,此處省略 "
s = SnowNLP(text)
print("。".join(s.summary(5)))

輸出結果:

天然就分紅觀點大相徑庭的兩派:一派說這是一個力大無比的怪物。這種假設也不能成立。我到紐約時。說它是一塊浮動的船體或是一堆大船殘片。另外一派說這是一艘動力極強的「潛水船」

初步來看,效果並非很好,接下來咱們本身計算句子權重,實現一個簡單的摘要功能,這個就須要jieba

import re
import jieba.analyse
import jieba.posseg
 
 
class TextSummary:
    def __init__(self, text):
        self.text = text
 
    def splitSentence(self):
        sectionNum = 0
        self.sentences = []
        for eveSection in self.text.split("\n"):
            if eveSection:
                sentenceNum = 0
                for eveSentence in re.split("!|。|?", eveSection):
                    if eveSentence:
                        mark = []
                        if sectionNum == 0:
                            mark.append("FIRSTSECTION")
                        if sentenceNum == 0:
                            mark.append("FIRSTSENTENCE")
                        self.sentences.append({
                            "text": eveSentence,
                            "pos": {
                                "x": sectionNum,
                                "y": sentenceNum,
                                "mark": mark
                            }
                        })
                        sentenceNum = sentenceNum + 1
                sectionNum = sectionNum + 1
                self.sentences[-1]["pos"]["mark"].append("LASTSENTENCE")
        for i in range(0, len(self.sentences)):
            if self.sentences[i]["pos"]["x"] == self.sentences[-1]["pos"]["x"]:
                self.sentences[i]["pos"]["mark"].append("LASTSECTION")
 
    def getKeywords(self):
        self.keywords = jieba.analyse.extract_tags(self.text, topK=20, withWeight=False, allowPOS=('n', 'vn', 'v'))
 
    def sentenceWeight(self):
        # 計算句子的位置權重
        for sentence in self.sentences:
            mark = sentence["pos"]["mark"]
            weightPos = 0
            if "FIRSTSECTION" in mark:
                weightPos = weightPos + 2
            if "FIRSTSENTENCE" in mark:
                weightPos = weightPos + 2
            if "LASTSENTENCE" in mark:
                weightPos = weightPos + 1
            if "LASTSECTION" in mark:
                weightPos = weightPos + 1
            sentence["weightPos"] = weightPos
 
        # 計算句子的線索詞權重
        index = [" 總之 ", " 總而言之 "]
        for sentence in self.sentences:
            sentence["weightCueWords"] = 0
            sentence["weightKeywords"] = 0
        for i in index:
            for sentence in self.sentences:
                if sentence["text"].find(i) >= 0:
                    sentence["weightCueWords"] = 1
 
        for keyword in self.keywords:
            for sentence in self.sentences:
                if sentence["text"].find(keyword) >= 0:
                    sentence["weightKeywords"] = sentence["weightKeywords"] + 1
 
        for sentence in self.sentences:
            sentence["weight"] = sentence["weightPos"] + 2 * sentence["weightCueWords"] + sentence["weightKeywords"]
 
    def getSummary(self, ratio=0.1):
        self.keywords = list()
        self.sentences = list()
        self.summary = list()
 
        # 調用方法,分別計算關鍵詞、分句,計算權重
        self.getKeywords()
        self.splitSentence()
        self.sentenceWeight()
 
        # 對句子的權重值進行排序
        self.sentences = sorted(self.sentences, key=lambda k: k['weight'], reverse=True)
 
        # 根據排序結果,取排名佔前 ratio% 的句子做爲摘要
        for i in range(len(self.sentences)):
            if i < ratio * len(self.sentences):
                sentence = self.sentences[i]
                self.summary.append(sentence["text"])
 
        return self.summary

這段代碼主要是經過tf-idf實現關鍵詞提取,而後經過關鍵詞提取對句子盡心權重賦予,最後得到到總體的結果,運行:

testSummary = TextSummary(text)
print("。".join(testSummary.getSummary()))

能夠獲得結果:

Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/yb/wvy_7wm91mzd7cjg4444gvdjsglgs8/T/jieba.cache
Loading model cost 0.721 seconds.
Prefix dict has been built successfully.
看來,只有政府纔有可能擁有這種破壞性的機器,在這個災難深重的時代,人們想方設法要加強戰爭武器威力,那就有這種可能,一個國家瞞着其餘國家在試製這類駭人聽聞的武器。因而,我就抓緊這段候船逗留時間,把收集到的礦物和動植物標本進行分類整理,可就在這時,斯科舍號出事了。一樣的道理,說它是一塊浮動的船體或是一堆大船殘片,這種假設也不能成立,理由仍然是移動速度太快

咱們能夠看到,總體效果要比剛纔的好一些。

發佈 API

經過 Serverless 架構,將上面代碼進行整理,併發布。

代碼整理結果:

import re, json
import jieba.analyse
import jieba.posseg
 
 
class NLPAttr:
    def __init__(self, text):
        self.text = text
 
    def splitSentence(self):
        sectionNum = 0
        self.sentences = []
        for eveSection in self.text.split("\n"):
            if eveSection:
                sentenceNum = 0
                for eveSentence in re.split("!|。|?", eveSection):
                    if eveSentence:
                        mark = []
                        if sectionNum == 0:
                            mark.append("FIRSTSECTION")
                        if sentenceNum == 0:
                            mark.append("FIRSTSENTENCE")
                        self.sentences.append({
                            "text": eveSentence,
                            "pos": {
                                "x": sectionNum,
                                "y": sentenceNum,
                                "mark": mark
                            }
                        })
                        sentenceNum = sentenceNum + 1
                sectionNum = sectionNum + 1
                self.sentences[-1]["pos"]["mark"].append("LASTSENTENCE")
        for i in range(0, len(self.sentences)):
            if self.sentences[i]["pos"]["x"] == self.sentences[-1]["pos"]["x"]:
                self.sentences[i]["pos"]["mark"].append("LASTSECTION")
 
    def getKeywords(self):
        self.keywords = jieba.analyse.extract_tags(self.text, topK=20, withWeight=False, allowPOS=('n', 'vn', 'v'))
        return self.keywords
 
    def sentenceWeight(self):
        # 計算句子的位置權重
        for sentence in self.sentences:
            mark = sentence["pos"]["mark"]
            weightPos = 0
            if "FIRSTSECTION" in mark:
                weightPos = weightPos + 2
            if "FIRSTSENTENCE" in mark:
                weightPos = weightPos + 2
            if "LASTSENTENCE" in mark:
                weightPos = weightPos + 1
            if "LASTSECTION" in mark:
                weightPos = weightPos + 1
            sentence["weightPos"] = weightPos
 
        # 計算句子的線索詞權重
        index = [" 總之 ", " 總而言之 "]
        for sentence in self.sentences:
            sentence["weightCueWords"] = 0
            sentence["weightKeywords"] = 0
        for i in index:
            for sentence in self.sentences:
                if sentence["text"].find(i) >= 0:
                    sentence["weightCueWords"] = 1
 
        for keyword in self.keywords:
            for sentence in self.sentences:
                if sentence["text"].find(keyword) >= 0:
                    sentence["weightKeywords"] = sentence["weightKeywords"] + 1
 
        for sentence in self.sentences:
            sentence["weight"] = sentence["weightPos"] + 2 * sentence["weightCueWords"] + sentence["weightKeywords"]
 
    def getSummary(self, ratio=0.1):
        self.keywords = list()
        self.sentences = list()
        self.summary = list()
 
        # 調用方法,分別計算關鍵詞、分句,計算權重
        self.getKeywords()
        self.splitSentence()
        self.sentenceWeight()
 
        # 對句子的權重值進行排序
        self.sentences = sorted(self.sentences, key=lambda k: k['weight'], reverse=True)
 
        # 根據排序結果,取排名佔前 ratio% 的句子做爲摘要
        for i in range(len(self.sentences)):
            if i < ratio * len(self.sentences):
                sentence = self.sentences[i]
                self.summary.append(sentence["text"])
 
        return self.summary
 
 
def main_handler(event, context):
    nlp = NLPAttr(json.loads(event['body'])['text'])
    return {
        "keywords": nlp.getKeywords(),
        "summary": "。".join(nlp.getSummary())
    }

編寫項目serverless.yaml文件:

nlpDemo:
  component: "@serverless/tencent-scf"
  inputs:
    name: nlpDemo
    codeUri: ./
    handler: index.main_handler
    runtime: Python3.6
    region: ap-guangzhou
    description: 文本摘要 / 關鍵詞功能
    memorySize: 256
    timeout: 10
    events:
      - apigw:
          name: nlpDemo_apigw_service
          parameters:
            protocols:
              - http
            serviceName: serverless
            description: 文本摘要 / 關鍵詞功能
            environment: release
            endpoints:
              - path: /nlp
                method: ANY

因爲項目中使用了jieba,因此在安裝的時候推薦在 CentOS 系統下與對應的 Python 版本下安裝,也可使用我以前爲了方便作的一個依賴工具:

Serverless 實戰:如何結合NLP實現文本摘要和關鍵詞提取?

經過sls --debug進行部署:

Serverless 實戰:如何結合NLP實現文本摘要和關鍵詞提取?

部署完成,能夠經過 PostMan 進行簡單的測試:

Serverless 實戰:如何結合NLP實現文本摘要和關鍵詞提取?

從上圖能夠看到,咱們已經按照預期輸出了目標結果。至此,文本摘要 / 關鍵詞提取的 API 已經部署完成。

總結

相對來講,經過 Serveless 架構作 API 是很是容易和方便的,可實現 API 的插拔行,組件化,但願本文可以給讀者更多的思路和啓發。

Serverless Framework 試用計劃

咱們誠邀您來體驗最便捷的 Serverless 開發和部署方式。在試用期內,相關聯的產品及服務均提供免費資源和專業的技術支持,幫助您的業務快速、便捷地實現 Serverless!

One More Thing

3 秒你能作什麼?喝一口水,看一封郵件,仍是 —— 部署一個完整的 Serverless 應用?

複製連接至 PC 瀏覽器訪問:https://serverless.cloud.tencent.com/deploy/express

3 秒極速部署,當即體驗史上最快的 Serverless HTTP 實戰開發!

傳送門:

歡迎訪問:Serverless 中文網,您能夠在 最佳實踐 裏體驗更多關於 Serverless 應用的開發!


推薦閱讀:《Serverless 架構:從原理、設計到項目實戰》

相關文章
相關標籤/搜索