整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

javascript中的键盘抬起事件onkeyup实现文本域的输入字数控制

实际运用中啊随处可见的就是PC端的字数控制案例,如标题只能多少字 内容多少字 密码多少长度等等等等,那么这些功能它是怎么实现的呢,今儿这个小分享道哥就给大家分享一下怎么用js去实现文字的输入控制。

已知有如下html代码

<p class="p1"> 
计算剩余字数<br> 
<textarea cols="70" rows="8" id="msg" onkeyup="test()"></textarea><br> 
<span id="msg_s"></span> 	
</p>

要实现在文本域textarea中输入长度不能超过50的字符并且剩余字数跟随输入的内容不断变化,提示还可以输入多少个字符 (要注意的是一个中文占两个字符 一个英文字母或者符号占一个字符)

代码如下

<script type="text/javascript">
	function test2(){
		//取出文本框中文本内容 
		var a=document.getElementById("msg");
		var len=50-a.value.length; //与50作比较,得道剩余字数
		if(len>0){ 
			//如果剩余字数大于0,则提示剩余字数
			document.getElementById("msg_s").innerHTML="您还剩余"+len+"个字";
		}else{
			//如果剩余字数小于0,即字数已超出,则只留前50个字
			document.getElementById("msg_s").innerHTML="您还剩余0个字";
			a.value=a.value.substr(0,50);
		}
	}
</script>

运行结果如下

家好,我是站长 polarisxu。

今天要聊的内容应该可以当做一道面试题,你可以先想想该怎么实现。

统计字数是一个很常见的需求,很多人印象最深的应该是微博早些时候限制 140 字,而且边输入会边统计剩余字数。现在很多社区文章也会有字数统计的功能,而且可以依据字数来预估阅读时间。比如 Go语言中文网就有这样的功能。

01 需求分析

下手之前先分析下这个需求。从我个人经验看,在实际面试中,针对一个面试题,你的分析过程,循序渐进的解决方案,可以很好的展示你的思考过程。正所谓分析问题、解决问题。这会给你加分的。

我们采用类似词法分析的思路分析这个需求。

一篇文章通常包含如下元素,我们也称之为 token:

  • 普通文字
  • 标点符号
  • 图片
  • 链接(包含各种协议的链接)
  • 代码

其中普通文字通常会分为欧美和中日韩(CJK),因为 CJK 属于表意文字,和欧美字母的文字差异很大。同时这里还涉及到编码的问题。本文假设使用 UTF-8 编码。

对于标点符号,中文标点和英文标点也会很不一样。

此外还有全角和半角的问题。

根据以上分析,对于该需求作如下假定:

  • 空格(包括换行)不算字数;
  • HTML 标签需要剔除;
  • 编码方式:假定为 UTF-8 编码;
  • 标点符号算不算做字数。如果算,像括号这样的按 2 个字算;
  • 链接怎么算?一个链接约定为 1 个字可能更合适,大概阅读时只是把它当链接,而不太会关心链接由什么字母组成;
  • 图片不算做字数,但如果计算阅读时间,可能需要适当考虑图片的影响;
  • 对于技术文章,代码是最麻烦的。统计代码字数感觉是没多大意义的。统计代码行数可能更有意义;

本文的解决方案针对以上的假定进行。

02 Go 语言实现

先看最简单的。

纯英文

根据以上分析,如果文章只包含普通文本且是英文,也就是说,每个字(单词)根据空格分隔,统计是最简单的。

func TotalWords(s string) int {
 n := 0
 inWord := false
 for _, r := range s {
  wasInWord := inWord
  inWord = !unicode.IsSpace(r)
  if inWord && !wasInWord {
   n++
  }
 }
 return n
}

还有一种更简单的方式:

len(strings.Fields(s))

不过看 strings.Fields 的实现,性能会不如第一种方式。

回顾上面的需求分析,会发现这个实现是有 Bug 的。比如下面的例子:

s1 := "Hello,playground"
s2 := "Hello, playground"

用上面的实现,s1 的字数是 1,s2 的字数是 2。它们都忽略了标点符号。而且因为写法的多样性(不规范统一),导致计算字数会有误差。所以我们需要对写法进行规范。

规范排版

其实和写代码要有规范一样,文章也是有规范的。比如出版社对于一本书的排版会有明确的规定。为了让我们的文章看起来更舒服,也应该遵循一定的规范。

这里推荐一个 GitHub 上的排版指南:《中文文案排版指北》,它的宗旨,统一中文文案、排版的相关用法,降低团队成员之间的沟通成本,增强网站气质。这个规范开头关于空格的一段话很有意思:

有研究显示,打字的时候不喜欢在中文和英文之间加空格的人,感情路都走得很辛苦,有七成的比例会在 34 岁的时候跟自己不爱的人结婚,而其余三成的人最后只能把遗产留给自己的猫。毕竟爱情跟书写都需要适时地留白。

建议大家可以看看这个指北,一些知名的网站就是按照这个做的。

因为 GCTT 的排版在这个规范做,但人为约束不是最好的方法,所以我开发了一个 Go 工具:https://github.com/studygolang/autocorrect,用于自动给中英文之间加入合理的空格并纠正专用名词大小写。

所以为了让字数统计更准确,我们假定文章是按一定的规范书写的。比如上面的例子,规范的写法是 s2 := "Hello, playground"。不过这里标点不算作字数。

刚去微博上试了一下,发现微博的字数计算方式有点诡异,竟然是 9 个字。

测试一下发现,它直接把两个英文字母算作一个字(两个字节算一个字)。而汉字是正常的。大家可以想想微博是怎么实现的。

中英文混合

中文不像英文,单词之间没有空格分隔,因此开始的那两种方式不适合。

如果是纯中文,我们怎么计算字数呢?

在 Go 语言中,字符串使用 UTF-8 编码,一个字符用 rune 表示。因此在标准库中查找相关计算方法。

func RuneCountInString(s string) (n int)

这个方法能计算字符串包含的 rune(字符)数,对于纯中文,就是汉字数。

str := "你好世界"
fmt.Println(utf8.RuneCountInString(str))

以上代码输出 4。

然而,因为很多时候文章会中英文混合,因此我们先采用上面的纯英文的处理方式,即:strings.Fields(),将文章用空格分隔,然后处理每一部分。

func TotalWords(s string) int {
 wordCount := 0
  
 plainWords := strings.Fields(s)
 for _, word := range plainWords {
  runeCount := utf8.RuneCountInString(word)
  if len(word) == runeCount {
   wordCount++
  } else {
   wordCount += runeCount
  }
 }

 return wordCount
}

增加如下的测试用例:

func TestTotalWords(t *testing.T) {
 tests := []struct {
  name  string
  input string
  want  int
 }{
  {"en1", "hello,playground", 2},
  {"en2", "hello, playground", 2},
  {"cn1", "你好世界", 4},
  {"encn1", "Hello你好世界", 5},
  {"encn2", "Hello 你好世界", 5},
 }
 for _, tt := range tests {
  t.Run(tt.name, func(t *testing.T) {
   if got := wordscount.TotalWords(tt.input); got != tt.want {
    t.Errorf("TotalWords() = %v, want %v", got, tt.want)
   }
  })
 }
}

发现 en1 和 encn1 测试不通过,因为没有按照上面说的规范书写。因此我们通过程序增加必要的空格。

// AutoSpace 自动给中英文之间加上空格
func AutoSpace(str string) string {
 out := ""

 for _, r := range str {
  out = addSpaceAtBoundary(out, r)
 }

 return out
}

func addSpaceAtBoundary(prefix string, nextChar rune) string {
 if len(prefix) == 0 {
  return string(nextChar)
 }

 r, size := utf8.DecodeLastRuneInString(prefix)
 if isLatin(size) != isLatin(utf8.RuneLen(nextChar)) &&
  isAllowSpace(nextChar) && isAllowSpace(r) {
  return prefix + " " + string(nextChar)
 }

 return prefix + string(nextChar)
}

func isLatin(size int) bool {
 return size == 1
}

func isAllowSpace(r rune) bool {
 return !unicode.IsSpace(r) && !unicode.IsPunct(r)
}

这样可以在 TotalWords 函数开头增加 AutoSpace 进行规范化。这时结果就正常了。

处理标点和其他类型

以上例子标点没计算在内,而且如果英文和中文标点混合在一起,情况又复杂了。

为了更好地实现开始的需求分析,重构以上代码,设计如下的结构:

type Counter struct {
 Total     int // 总字数 = Words + Puncts
 Words     int // 只包含字符数
 Puncts    int // 标点数
 Links     int // 链接数
 Pics      int // 图片数
 CodeLines int // 代码行数
}

同时将 TotalWords 重构为 Counter 的 Stat 方法,同时记录标点数:

func (wc *Counter) Stat(str string) {
 wc.Links = len(rxStrict.FindAllString(str, -1))
 wc.Pics = len(imgReg.FindAllString(str, -1))

 // 剔除 HTML
 str = StripHTML(str)

 str = AutoSpace(str)

 // 普通的链接去除(非 HTML 标签链接)
 str = rxStrict.ReplaceAllString(str, " ")
 plainWords := strings.Fields(str)

 for _, plainWord := range plainWords {
  words := strings.FieldsFunc(plainWord, func(r rune) bool {
   if unicode.IsPunct(r) {
    wc.Puncts++
    return true
   }
   return false
  })

  for _, word := range words {
   runeCount := utf8.RuneCountInString(word)
   if len(word) == runeCount {
    wc.Words++
   } else {
    wc.Words += runeCount
   }
  }
 }

 wc.Total = wc.Words + wc.Puncts
}

var (
 rxStrict = xurls.Strict()
 imgReg   = regexp.MustCompile(`<img [^>]*>`)
 stripHTMLReplacer = strings.NewReplacer("\n", " ", "</p>", "\n", "<br>", "\n", "<br />", "\n")
)

// StripHTML accepts a string, strips out all HTML tags and returns it.
func StripHTML(s string) string {
 // Shortcut strings with no tags in them
 if !strings.ContainsAny(s, "<>") {
  return s
 }
 s = stripHTMLReplacer.Replace(s)

 // Walk through the string removing all tags
 b := GetBuffer()
 defer PutBuffer(b)
 var inTag, isSpace, wasSpace bool
 for _, r := range s {
  if !inTag {
   isSpace = false
  }

  switch {
  case r == '<':
   inTag = true
  case r == '>':
   inTag = false
  case unicode.IsSpace(r):
   isSpace = true
   fallthrough
  default:
   if !inTag && (!isSpace || (isSpace && !wasSpace)) {
    b.WriteRune(r)
   }
  }

  wasSpace = isSpace

 }
 return b.String()
}

代码过多的细节不讨论。此外,关于文章内的代码行数统计未实现(目前没有想到特别好的方法,如果你有,欢迎交流)。

03 总结

通过本文的分析发现,精准统计字数没那么容易,这里涉及到很多的细节。

当然,实际应用中,字数不需要那么特别精准,而且对于非正常文字(比如链接、代码)怎么处理,会有不同的约定。

本文涉及到的完整代码放在 GitHub:https://github.com/polaris1119/wordscount。

自floydhub

作者:Alfrick Opidi

机器之心编译

参与:杜伟、张倩

我们在阅读新闻报道等实时性文章时,需要快速归纳出文章的大意。但是,如果将一篇很长的文章归纳成一个能够涵盖原文中心思想的小段落,则需要我们耗费大量时间。本文介绍了自然语言处理中的两种文本自动摘要生成方法——抽取式和抽象式文本摘要。这两种方法通过计算文本中句子成分的权重来生成摘要,可以大大节省通读全文以及归纳总结主要信息的时间,为读者提供方便。

你是否曾将一篇冗长的文档归纳为一个小的段落?你用了多长时间呢?手动归纳总结耗费时间、枯燥乏味。文本自动摘要可以克服此类难题,帮你轻松归纳出一篇文章的中心思想。

文本摘要方法能够对冗长文本进行简洁准确的总结,同时将重点放在传达有用信息的章节,而又不失去文章大意。

文本自动摘要旨在将冗长文档变成缩写版本,若手动完成则可能非常麻烦且成本高昂。

在生成需要的摘要文本之前,机器学习算法可被训练用以理解文档,识别传达重要事实和信息的章节。

使用文本摘要机器学习算法生成一篇在线新闻文章的摘要。

文本自动摘要的必要性

随着目前数字空间中数据的爆炸式增长,而大多又是非结构化的文本数据,因而需要开发文本自动摘要工具,使人们可以轻易获知文本大意。当前,我们可以快速访问大量信息。但是,大多数信息冗长、无关紧要,还可能无法传达其本意。例如,如果你想从一篇在线新闻报道中搜寻一些特定信息,你也许要吃透报道内容,花费大量时间剔除无用信息,之后才能找到自己想要了解的信息。所以,使用能够提取有用信息并剔除无关紧要和无用数据的自动文本摘要生成器变得非常重要。文本摘要的实现可以增强文档的可读性,减少搜寻信息的时间,获得更多适用于特定领域的信息。

文本自动摘要的主要类型

从广义的角度看,自然语言处理(NLP)中有两种文本摘要生成方法:抽取式和抽象式。

抽取式摘要(extraction-based summarization)

在抽取式摘要中,抽取一段文本中表示重点内容的单词子集,并结合起来生成摘要。我们可以将抽取式摘要看作是一支荧光笔-从源文本中抽取主要信息。

荧光笔 = 抽取式摘要

在机器学习中,抽取式摘要通常需要衡量基本句子成分的权重,并根据权重结果生成摘要。

不同类型的算法和方法均可用于衡量句子的权重,之后根据各成分之间的关联性和相似性进行排序-并进一步将这些成分连接起来以生成摘要。

如下例所示:

抽取式摘要

如上例所示,抽取式摘要由荧光笔标黄的单词组成,生成摘要的语法可能不准确。

抽象式摘要

在抽象式摘要中,高级深度学习方法(advanced deep learning technique)用于解释和缩写原始文档,就像人类所做的一样。将抽象式摘要想象成一支钢笔-它能生成或许不属于源文档的新句子。

钢笔 = 抽象式摘要

由于抽象式机器学习算法能够生成表示源文本中最重要信息的新短语和句子,所以这些抽象式算法有助于克服抽取式摘要中的语法不准确问题。

如下例所示:

抽象式摘要。

尽管抽象式文本摘要的表现更好,但开发相关算法需要复杂的深度学习技巧和语言模型。

为了获得合理产出,抽象式摘要方法必须能够解决诸多自然语言处理问题,如自然语言生成、语义表征和推理排序(inference permutation)。

同样地,抽取式文本摘要方法依然大受欢迎。在本文中,我们将重点介绍抽象式文本摘要方法。

如何执行文本摘要

我们使用以下一段话展示如何执行文本摘要抽取:

我们依照以下步骤对这段话作总结,同时尽可能保留原意。

第一步:将这段话转换成句子

首先,我们将这段话分割成相应的句子。转换成句子的最佳方法是在句点(period)出现时提取一个句子。

第二步:文本处理

接下来,我们在文本处理中移除停止词(那些没有实际意义的常见词,如「and」和「the」)、数字、标点符号以及句子中的其他特殊字符。

句子成分的过滤有助于移除冗余和不重要的信息,这些信息对文本意图的表达或许没有任何价值。

以下是文本处理结果:

第三步:分词

切分各个句子,列出句子中的所有单词。

以下是单词列表:

['peter','elizabeth','took','taxi','attend','night','party','city','party','elizabeth','collapse','rush','hospital', 'diagnose','brain', 'injury', 'doctor','told','peter','stay','besides','get','well','peter', 'stayed','hospital','days','without','leaving']

第四步:评估单词的加权出现频率(occurrence frequency)

紧接着,我们计算所有单词的加权出现频率。为此,我们用每个单词的出现频率除以这段话中出现最多次的单词的频率,在这段话中出现最多的是 Peter,总共出现了三次。

下表给出了每个单词的加权出现频率。

第五步:用相应的加权频率替代原句中的各个单词,然后计算总和。

我们在文本处理步骤中已经移除了停止词和特殊字符等无关紧要的单词,因而它们的加权频率为零,也就没有必要在计算时加上。

根据所有单词的加权频率总和,我们可以推导出:第一个句子在整段话中的权重最大。所以,第一个句子能够对这段话的意思作出最具代表性的总结。

此外,如果第一个句子与第三个句子(该句的权重在整段话中排第二)相结合,则可以作出更好的总结。

以上例子只是基本说明了如何在机器学习中执行抽取式文本摘要。现在,我们看看如何在创建实际摘要生成器中运用上述概念。

维基百科文章的文本摘要

让我们动手创建一个可以简化冗长 web 文章中信息的文本摘要生成器。为简单起见,除了 Python 的 NLTK toolkit,我们不使用任何其他机器学习库(machine learning library)。

以下是摘要生成器的代码 blueprint:

# Creating a dictionary for the word frequency table
frequency_table = _create_dictionary_table(article)
# Tokenizing the sentences
sentences = sent_tokenize(article)
# Algorithm for scoring a sentence by its words
sentence_scores = _calculate_sentence_scores(sentences, frequency_table)
# Getting the threshold
threshold = _calculate_average_score(sentence_scores)
# Producing the summary
article_summary = _get_article_summary(sentences, sentence_scores, 1.5 * threshold)
print(article_summary)

依照下列步骤使用 Python 语言创建一个简单的文本摘要生成器。

第一步:准备数据

在这个例子中,我们想总结一下这篇 Wikipedia 文章的信息,这篇文章只是对 20 世纪发生的主要事件进行概述。

为了获取这篇文章的文本,我们将使用 Beautiful Soup 库。

以下是抓取文章内容的代码:

import bs4 as BeautifulSoup
import urllib.request 
# Fetching the content from the URL
fetched_data = urllib.request.urlopen('https://en.wikipedia.org/wiki/20th_century')
article_read = fetched_data.read()
# Parsing the URL content and storing in a variable
article_parsed = BeautifulSoup.BeautifulSoup(article_read,'html.parser')
# Returning <p> tags
paragraphs = article_parsed.find_all('p')
article_content = ''
# Looping through the paragraphs and adding them to the variable
for p in paragraphs: 
 article_content += p.text

在以上代码中,我们首先导入抓取网页数据所必需的库。BeautifulSoup 库用于解析网页内容,而 urllib library 用于连接网页和检索 HTML。

BeautifulSoup 将输入文本转化为 Unicode 字符,将输出文本转化为 UTF-8 字符,省去了从 web 上抓取文本时处理不同字符集编码的麻烦。

我们使用 urllib.request 程序中的 urlopen 函数打开网页。之后,使用 read 函数读取所抓取的数据对象。为了解析数据,我们调用 BeautifulSoup 对象,并向它传递两个参数,即 article_read 和 html.parser。

find_all 函数用于传回 HTML 中出现的所有<p>元素。此外,.text 使我们只能选择<p>元素中的文本。

第二步:处理数据

为尽可能确保废弃的文本数据无噪声,我们将执行一些基本的文本清理(text cleaning)。为协助完成这一处理过程,我们将从 NLTK 库中导入一个停止词列表。

我们还将引入 PorterStemmer,这是一种将单词还原成词根形式的算法。例如,单词 cleaning、cleaned 和 cleaner 都可以还原成词根 clean。

此外,我们还将创建一个包含文本中每一单词出现频率的字典表。我们将依次读取文本及相应单词,以消除所有停止词。

之后,我们将检查单词是否出现在 frequency_table 中。如果一个单词之前就在字典中,则其值更新 1。否则,如果一个单词首次被识别到,则其值设置为 1。

例如,频率表应如下所示:

代码如下:

from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
def _create_dictionary_table(text_string) -> dict:
 # Removing stop words
 stop_words = set(stopwords.words("english"))
 words = word_tokenize(text_string)
 # Reducing words to their root form
 stem = PorterStemmer()
 # Creating dictionary for the word frequency table
 frequency_table = dict()
 for wd in words:
 wd = stem.stem(wd)
 if wd in stop_words:
 continue
 if wd in frequency_table:
 frequency_table[wd] += 1
 else:
 frequency_table[wd] = 1
 return frequency_table

第三步:将文章分割成句子

为了将 article_content 分割成一个句子集,我们将使用 NLTK 库中的内置方法。

from nltk.tokenize import word_tokenize, sent_tokenize
sentences = sent_tokenize(article)

第四步:确定句子的加权频率

为了评估文本中每个句子的分数,我们将分析每个单词的出现频率。在这种情况下,我们将根据句子中的单词对该句进行评分,也就是加上句子中每个重要单词的出现频率。

请看以下代码:

def _calculate_sentence_scores(sentences, frequency_table) -> dict: 
 # Algorithm for scoring a sentence by its words
 sentence_weight = dict()
 for sentence in sentences:
 sentence_wordcount = (len(word_tokenize(sentence)))
 sentence_wordcount_without_stop_words = 0
 for word_weight in frequency_table:
 if word_weight in sentence.lower():
 sentence_wordcount_without_stop_words += 1
 if sentence[:7] in sentence_weight:
 sentence_weight[sentence[:7]] += frequency_table[word_weight]
 else:
 sentence_weight[sentence[:7]] = frequency_table[word_weight]
 sentence_weight[sentence[:7]] = sentence_weight[sentence[:7]] / sentence_wordcount_without_stop_words
 return sentence_weight

重要的是,为了避免长句的分数必然高于短句,我们用每个句子的分数除以该句中的单词数。

另外,为了优化字典内存,我们任意添加 sentence[:7],这指的是每个句子的前七个字符。但在较长的文档中,你很可能遇到具有相同首个 n_chars 的句子,这时最好使用哈希函数(hash function)或 index 函数(index function)来处理此类极端情况(edge-cases),避免冲突。

第五步:计算句子阈值

为了进一步调整适合摘要的句子类型,我们将创建句子的平均分。借助于这个阈值,我们可以避免选择分数低于平均分的句子。

代码如下:

def _calculate_average_score(sentence_weight) -> int:
 # Calculating the average score for the sentences
 sum_values = 0
 for entry in sentence_weight:
 sum_values += sentence_weight[entry]
 # Getting sentence average value from source text
 average_score = (sum_values / len(sentence_weight))
 return average_score

第六步:生成摘要

最后,我们拥有了所有必需的参数,因而现在可以生成文章摘要了。

代码如下:

def _get_article_summary(sentences, sentence_weight, threshold):
 sentence_counter = 0
 article_summary = ''
 for sentence in sentences:
 if sentence[:7] in sentence_weight and sentence_weight[sentence[:7]] >= (threshold):
 article_summary += " " + sentence
 sentence_counter += 1
 return article_summary

总结

下图展示了创建文本摘要算法的工作流程。

以下是机器学习中简单抽取式文本摘要生成器的完整代码:

#importing libraries
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
from nltk.tokenize import word_tokenize, sent_tokenize
import bs4 as BeautifulSoup
import urllib.request 
#fetching the content from the URL
fetched_data = urllib.request.urlopen('https://en.wikipedia.org/wiki/20th_century')
article_read = fetched_data.read()
#parsing the URL content and storing in a variable
article_parsed = BeautifulSoup.BeautifulSoup(article_read,'html.parser')
#returning <p> tags
paragraphs = article_parsed.find_all('p')
article_content = ''
#looping through the paragraphs and adding them to the variable
for p in paragraphs: 
 article_content += p.text
def _create_dictionary_table(text_string) -> dict:
 #removing stop words
 stop_words = set(stopwords.words("english"))
 words = word_tokenize(text_string)
 #reducing words to their root form
 stem = PorterStemmer()
 #creating dictionary for the word frequency table
 frequency_table = dict()
 for wd in words:
 wd = stem.stem(wd)
 if wd in stop_words:
 continue
 if wd in frequency_table:
 frequency_table[wd] += 1
 else:
 frequency_table[wd] = 1
 return frequency_table
def _calculate_sentence_scores(sentences, frequency_table) -> dict: 
 #algorithm for scoring a sentence by its words
 sentence_weight = dict()
 for sentence in sentences:
 sentence_wordcount = (len(word_tokenize(sentence)))
 sentence_wordcount_without_stop_words = 0
 for word_weight in frequency_table:
 if word_weight in sentence.lower():
 sentence_wordcount_without_stop_words += 1
 if sentence[:7] in sentence_weight:
 sentence_weight[sentence[:7]] += frequency_table[word_weight]
 else:
 sentence_weight[sentence[:7]] = frequency_table[word_weight]
 sentence_weight[sentence[:7]] = sentence_weight[sentence[:7]] / sentence_wordcount_without_stop_words
 return sentence_weight
def _calculate_average_score(sentence_weight) -> int:
 #calculating the average score for the sentences
 sum_values = 0
 for entry in sentence_weight:
 sum_values += sentence_weight[entry]
 #getting sentence average value from source text
 average_score = (sum_values / len(sentence_weight))
 return average_score
def _get_article_summary(sentences, sentence_weight, threshold):
 sentence_counter = 0
 article_summary = ''
 for sentence in sentences:
 if sentence[:7] in sentence_weight and sentence_weight[sentence[:7]] >= (threshold):
 article_summary += " " + sentence
 sentence_counter += 1
 return article_summary
def _run_article_summary(article):
 #creating a dictionary for the word frequency table
 frequency_table = _create_dictionary_table(article)
 #tokenizing the sentences
 sentences = sent_tokenize(article)
 #algorithm for scoring a sentence by its words
 sentence_scores = _calculate_sentence_scores(sentences, frequency_table)
 #getting the threshold
 threshold = _calculate_average_score(sentence_scores)
 #producing the summary
 article_summary = _get_article_summary(sentences, sentence_scores, 1.5 * threshold)
 return article_summary
if __name__ == '__main__':
 summary_results = _run_article_summary(article_content)
 print(summary_results)

点击原文中的以下按钮在 FloydHub Notebook 上运行代码:

在这个例子中,我们所采用的阈值是平均分的 1.5 倍。这个超参数值(hyperparameter value)在几次试验后为我们生成了良好的结果。当然,你可以根据自身的偏好对数值进行微调,并改进摘要生成效果。

下图是 Wikipedia 文章的生成摘要。

使用文本摘要算法生成的 Wikipedia 文章摘要。

如你所见,运行代码可以对冗长的 Wikipedia 文章进行总结,并简要概述 20 世纪发生的主要事件。

尽管如此,我们还可以改进摘要生成器,使之更好地生成长篇幅文本的简洁、精确摘要。

更多内容

当然,本文只是简要介绍了机器学习中使用文本摘要算法所能实现的功能。

若想了解更多有关该主题,特别是抽象式文本摘要的知识,下面一些有用的资源可以为你提供帮助:

有没有可能将两种方法(抽象式和抽取式文本自动摘要)相结合?这是指针生成网络(pointer generator network)的主要原理,该网络通过结合抽取(指向)和抽象(生成)取得了最佳效果。

图源:"Taming Recurrent Neural Networks for Better Summarization"

《WikiHow: A Large Scale Text Summarization Dataset》一文提出了一个新的大规模文本自动摘要数据集 WikiHow,该数据集包含提取自 WikiHow 在线知识库的 230000 多篇文章。目前可用的大多数数据集的规模不足以训练序列到序列模型,它们也许只能提供有限的摘要,并且更适合执行抽取式摘要。但是,WikiHow 数据集规模大,质量高,能够在抽象式文本摘要中获得最优结果。

《Pretraining-Based Natural Language Generation for Text Summarization》一文提出了一个基于序列到序列范式的独特二阶段模型。该模型同时在编码器和解码器侧利用 BERT,并在学习过程中注重强化目标。当该模型在一些基准数据集上进行评估时,结果显示,该方法在文本自动摘要中表现更好,尤其相较于其他传统系统而言。

原文链接:https://blog.floydhub.com/gentle-introduction-to-text-summarization-in-machine-learning/

本文为机器之心编译,转载请联系本公众号获得授权。

✄------------------------------------------------

加入机器之心(全职记者 / 实习生):hr@jiqizhixin.com

投稿或寻求报道:content@jiqizhixin.com

广告 & 商务合作:bd@jiqizhixin.com