Veiking百草园


Laravel-VicWord分词,实现关键词提取

程序员甲   @Veiking   2020-11-23

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');// 不做限制


程序员甲



慷慨发言

(您提供的信息将用于后续必要的反馈联系,本站会恪守隐私)

潜影拾光

波密雪山

天黑路暗,想看清东西 换个角度或许会比较好

扫码转发

二维码
二维码
二维码
二维码
二维码
二维码

博文标签