Laravel-VicWord分词,实现关键词提取
摘要:
VicWord是一个基于php语言的分词插件,分词功能,一般常见于全文搜索,语义识别等,我们此处是想用于做文本内容高频词、关键词提取。Jieba也是一个基于php语言的分词插件,关于使用偏好来说呢,结巴分词功能相对比较齐全,所以想实现一些特定功能的时候,是可以考虑结巴分词的,一般来说,VicWord分词是个不错的选择
VicWord是一个基于php语言的分词插件,分词功能,一般常见于全文搜索,语义识别等,我们此处是想用于做文本内容高频词、关键词提取,这里我们用的php框架是laravel,接下来看看基于laravel,具体怎么操作。
第一步:VicWord拓展包安装
基于composer的安装指令:
composer require lizhichao/word
这时候可能会遇上一个问题,就是当我们执行安装操作时,composer会提示Allowed memory size of bytes exhausted的异常,其全部信息如下:
PHP Fatal error: Allowed memory size of 1610612736 bytes exhausted (tried to allocate 67108864 bytes)Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.
这个看字面意思就可以知道,下载的文件超大了,超出内存限制的大小,我们通过这个提示信息,先输出一下配置信息,指令如下:
php -r "echo ini_get('memory_limit').PHP_EOL;"
结果打印出来:128M
这个设置是php默认的最大单线程的独立内存使用量的定义,由于composer对插件的安装也是基于php服务的,那我们就得改下这个参数配置。
由于VicWord的字库文件,肯定是比较大的,综一些不确定因素,我们就直接先把限制去掉,找到php.ini文件,修改memory_limit的值为 -1,修改完之后,运行:
php -r "echo ini_get('memory_limit').PHP_EOL;"
结果打印出来:-1,-1即表示此处不做限制,接下来继续,就可以顺利安装了。
第二步:VicWord的使用
由于我们是想要在页面做一个高频词、关键词提取的功能,所以系统要准备一个可供访问的接口,故我们需配置一下laravel的Route访问路由,以及其对应的Controllers入口:
Route:
Route::any('gate/getKeywords.html','KeywordsController@getKeywords');
Controllers:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
/**
* 文本分词-关键词提取
* @author vWork
*/
class KeywordsController extends Controller{
/**
* 获取高频词关键词
* @param Request $request
* @return array
*/
public function getKeywords(Request $request){
// 获取待处理文本
$text = $request['text'];
if(!$text){
return null;
}
// 分词处理
// 关键词提取
$keywords = '';
return $keywords;
}
}
基于这个controller骨架,我们开始添加分词功能:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Lizhichao\Word\VicWord;
/**
* 文本分词-关键词提取
* @author vWork
*/
class KeywordsController extends Controller{
/**
* 获取高频词关键词
* @param Request $request
* @return array
*/
public function getKeywords(Request $request){
// 获取待处理文本
$text = $request['text'];
if(!$text){
return null;
}
ini_set('memory_limit','-1'); //升级为不限制内存
// 分词处理
$words = $this->doVicWords($text);
// 关键词抽取
$keywords = $this->doKeywords($words);
return $keywords;
}
/**
* 分词处理
* @param $text
* @return array
*/
private function doVicWords($text){
// 词库配置
// define('_VIC_WORD_DICT_PATH_',dirname(dirname(dirname(__DIR__))).'/vendor/lizhichao/word/Data/dict.json');
// 词库路径
$dictPath = dirname(dirname(dirname(__DIR__))).'/vendor/lizhichao/word/Data/dict.json';
// 分词初始化
$vicWord = new VicWord($dictPath);
// 分词处理
$words = $vicWord->getAutoWord($text);
return $words;
}
}
这里注意,关于这个dictPath的路径,很多地方给出的推荐代码是:
define('_VIC_WORD_DICT_PATH_',dirname(dirname(dirname(__DIR__))).'/vendor/lizhichao/word/Data/dict.json');
其用意是为了配置词库路径,看了看源代码,并不是很理解这样的操作,并且自己程序里这样写也会报错,感兴趣的可以深入研究下。
在分词的实现上,VicWord提供了三个的方法:
getWord($text); // 长度优先切分,最快
getShortWord($text); // 细粒度切分,比最快慢一点点
getAutoWord($text); // 自动切分 (在相邻词做了递归) ,从语义的角度理解,效果最好
有人做过一段5000字的文本性能参考测试,其结果如下:
getWord() 每秒140w字
getShortWord() 每秒138w字
getAutoWord() 每秒40w字
从这个数据上我们可以看出,一般长度比较短的文本,不同方法在性能上不会有太大区别,合适的就行,我们是用来做内容文本高频词抽取的,故选择了getAutoWord($text)。
第三步:VicWord的词库拓展
有时候我们在做分词操作的时候,文本内容可能会倾向于某种行业和偏向,我们抽取的关键词也可能会有偏好,所以,词库的自定义拓展,是必须要考虑的。
其实VicWord的词库拓展非常简单,加多这么几句:
//拓展词库路径,extendsDict.json,自定义词库文件
$extendsDictPath = dirname(dirname(dirname(__DIR__))).'/vendor/lizhichao/word/Data/extendsDict.json';
// 词库拓展
$vicDict = new VicDict($extendsDictPath);
$vicDict->add('老狗啃骨头', 'n');
这个extendsDict.json文件即是我们可以自定义的词库文件,后续add方法添加的拓展词汇,也会同步更新至这个文件。
我们是为了完成特定行业文本内容高频词、关键词提取,所以也准备了一些专业术语及行业词汇标签集合,整个代码升级完成如下:
/**
* 分词处理
* @param $text
* @return array
*/
private function doVicWords($text){
// 词库路径
$dictPath = dirname(dirname(dirname(__DIR__))).'/vendor/lizhichao/word/Data/dict.json';
// 拓展词库文件
$extendsDictPath = dirname(dirname(dirname(__DIR__))).'/vendor/lizhichao/word/Data/extendsDict.json';
// 词库拓展
$vicDict = new VicDict($extendsDictPath);
$vicDict->add('老狗啃骨头', 'n');
$tagsList = array(...); // 自定义标签集合
foreach($tagsList as $tag){
$vicDict->add($tag->name, 'n');
}
// 词库保存
$vicDict->save();
// 分词初始化
$vicWord = new VicWord($dictPath);
// 分词处理
$words = $vicWord->getAutoWord($text);
return $words;
}
这样,整个围绕着VicWord插件的使用,就算完成了。
第四步:高频词、关键词提取
经过上面几个步骤,我们已经可以完成文本内容的拆解,这个拆解仅仅是基于词库的语义拆分,我们想要提取出现相对较多的高频词、关键词,还要做下升级,代码如下:
/**
* 获取高频词
* @param $words
* @return number[]
*/
private function doKeywords($words){
$keywords = array();
foreach ($words as $word){
if(mb_strlen($word[0]) >= 2){
if(array_key_exists($word[0], $keywords)){
$keywords[$word[0]] = $keywords[$word[0]]+1;
}else{
$keywords[$word[0]] = 1;
}
}
}
// 按照出现次数排序,得出出现频率最高的10个词儿
arsort($keywords);
$keywords = array_slice($keywords, 0, 10, true);
return $keywords;
}
考虑到一些文本特征,忽略一些无提取意义的特殊字符等,再作如下功能添加:
/**
* 获取高频词
* @param $words
* @return number[]
*/
private function doKeywords($words){
$keywords = array();
foreach ($words as $word){
if(mb_strlen($word[0]) >= 2 && !$this->ingore($word[0])){
if(array_key_exists($word[0], $keywords)){
$keywords[$word[0]] = $keywords[$word[0]]+1;
}else{
$keywords[$word[0]] = 1;
}
}
}
// 按照出现次数排序,得出出现频率最高的20个词儿
arsort($keywords);
$keywords = array_slice($keywords, 0, 10, true);
return $keywords;
}
/**
* 忽略字符方法
* @param $word
* @return boolean
*/
private function ingore($word){
// 忽略特殊字符
$pattern = '/\/|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\(|\)|\_|\+|\{|\}|\:|\<|\>|\?|\[|\]|\,|\.|\/|\;|\'|\`|\-|\=|\\\|\||\s+/';
if(preg_match($pattern, $word)){
return true;
}
// 忽略部分词汇
$ignores = array('怎么','这些','这个','那个',
'一下','一样','一个','一定',
'就是','都是','还是','东西','我们','所以','可以',
);
foreach ($ignores as $item){
if($word==$item || strpos($word, $item)){
return true;
}
}
return false;
}
这样,基于laravel框架,利用VicWord插件,我们便实现了文本数据高频词、关键词提取功能。
补充:php的另一个分词插件,jieba(结巴)拓展包
Jieba也是一个基于php语言的分词插件,其安装指令如下:
composer require fukuball/jieba-php:dev-master
安装完成后呢,代码的使用也很方便:
use Fukuball\Jieba\Jieba;
use Fukuball\Jieba\Finalseg;
Jieba::init();
Finalseg::init();
$words = Jieba::cut("基数排序是一种不在数据值本身之间比较的排序算法,而是通过数据按位数“切割”对比,从而实现排序的算法,所以基数排序也被认为是一种典型的非比较排序算法。");
这样几句即可实现文本的分词拆解,关于使用偏好来说呢,结巴分词功能相对比较齐全,齐全伴随的就是繁琐,所以想实现一些特定功能的时候,是可以考虑结巴分词的,一般来说,满足基本功能的前提下,VicWord分词是个不错的选择。
注意:分词操作基本都需要添加词库,毕竟十几几十万个单元词汇,这种数量级的运算很容易引起内存问题,如我们开头说的插件安装,我们可以去修改php.ini,即可解决这个问题;但有时候,我们程序运行的服务容器的配置文件是无法修改的,这时候我们只能从代码上来处理这个问题,在程序加载的入口,或者执行运算的代码前加入以下代码(根据需要选一即可):
ini_set('memory_limit', '512M'); // 限制提高至512M
ini_set('memory_limit', '-1');// 不做限制