(2)中文分词——基于词典的方法

中文分词基本算法主要分类:基于词典的方法、基于统计的方法、基于规则的方法
1、基于词典的方法(字符串匹配,机械分词方法)
定义:按照一定策略将待分析的汉字串与一个“大机器词典”中的词条进行匹配,若在词典中找到某个字符串,则匹配成功。
按照扫描方向的不同:正向匹配和逆向匹配
按照长度的不同:最大匹配和最小匹配
1.2基于统计的分词(无字典分词)
主要思想:上下文中,相邻的字同时出现的次数越多,就越可能构成一个词。因此字与字相邻出现的概率或频率能较好的反映词的可信度。
主要统计模型为:N元文法模型(N-gram)、隐马尔科夫模型(Hidden Markov Model, HMM)。
最大正向匹配算法:从左向右扫描寻找词的最大匹配。首先我们规定一个词的最大长度,每次扫描的时候寻找当前开始的这个长度的词来和字典中的词匹配,如果没有找到,就缩短长度继续寻找,直到找到或者成为单字。

一个简单的Java正向匹配算法示例

import java.util.*;
import java.io.*;
public class MM {
    static int MaxLen=5;
    public static void main(String[] args){
        String dic="计算语言学、课程、课时";
        String str="计算语言学课程是三个课时";
        String s="";
        int begin=0,end;
        while(begin<str.length()){
            end=begin+MaxLen;
            if(end>str.length())end=str.length();
            while(begin<end&&!dic.contains(str.substring(begin,end))){
                end--;
            }
            if(begin==end)end++;
            s=s+str.substring(begin,end)+"/";
            System.out.println(s);
            begin=end;
        }
        System.out.println(s);} }

正向最大匹配算法和反向最大匹配算法Java实现代码


import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Angela
 */
public class BiMaxSegment {

    /**最大分词长度**/
    private int max_len;
    /**词典**/
    private Set<String> dict;

    /****
     * 初始化max_len和词典
     * @param max_len 
     */
    public BiMaxSegment(int max_len){
        this.max_len=max_len;
        dict=initDict("dic/chineseDic.txt","gb2312");
    }

    /**
     * 读取词典
     * @param dictPath 词典文件路径
     * @param charset 词典文件编码
     * @return 词典Set
     */
    private Set<String> initDict(String dictPath,String charset){
        Set<String> dict=new HashSet<String>();
        try{           
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    new FileInputStream(dictPath),charset));
            String s;
            //一行一行地读取文本内容
            while((s=br.readLine())!=null){   
                //只读取词
                dict.add(s.split(",")[0]);
            }
            br.close();
        }catch (IOException ex) {
            Logger.getLogger(BiMaxSegment.class.getName()).log(Level.SEVERE, null, ex);
        }
        return dict;
    }

    /**
     * 正向最大匹配算法
     * @param text 要分词的文本内容
     * @return 分词结果
     */
    public String mm_segment(String text){
        StringBuilder sb=new StringBuilder();
        int begin=0,end;
        int len=text.length();
        while(begin<len){
            end=begin+max_len;
            if(end>len)end=len;
            //不匹配则指针前移
            while(begin<end&&!dict.contains(text.substring(begin,end))){
                end--;
            }
            //一个字
            if(begin==end)end++;
            sb.append(text.substring(begin,end)+"/");
            begin=end;
        }
        return sb.toString();
    }

    /**
     * 反向最大匹配算法
     * @param text 要分词的文本内容
     * @return 分词结果
     */
    public String rmm_segment(String text){
        StringBuilder sb=new StringBuilder();
        int right=text.length();
        int left;
        while(right>0){
            left=right-max_len;
            if(left<0)left=0;
            //不匹配则指针后移
            while(right>left&&!dict.contains(text.substring(left,right))){
                left++;
            }
            //一个字
            if(right==left)left--;
            sb.insert(0,text.substring(left,right)+"/");
            right=left;
        }
        return sb.toString();              
    }

    public static void main(String[] args){
        BiMaxSegment bimax=new BiMaxSegment(3);
        String text="我在餐厅吃饭,饭菜好难吃啊!";
        System.out.println(bimax.rmm_segment(text));
    }

}

MMSEG分词算法
MMSEG分为“匹配算法(Matching algorithm)”和“消除歧义的规则(Ambiguity resolution rules)”这两部分。“匹配算法”是说如何根据词典里保存的词语,对要切分的语句进行匹配(正向?逆向?粒度?);“消除歧义的规则”是说当一句话可以这样分,也可以那样分的时候,用什么规则来判定使用哪种分法,比如“设施和服务”这个短语,可以分成“设施和服务”,也可以分成“设施服 务”,选择哪个分词结果,就是“消除歧义的规则”的功能。
MMSEG的“匹配方法”有两种:
1.Simple方法,即简单的正向匹配,根据开头的字,列出所有可能的结果。比如“一个劲儿的说话”,可以得到
一个
一个劲
一个劲儿
一个劲儿的
这四个匹配结果(假设这四个词都包含在词典里)。
2.Complex方法,匹配出所有的“三个词的词组”,即从某一既定的字为起始位置,得到所有可能的“以三个词为一组”的所有组合。比如“研究生命起源”,可以得到

研_究_生
研_究_生命
研究生_命_起源
研究_生命_起源

MMSEG的“消除歧义的规则”有四个:
1. 组合长度最大
2. 组合中平均词语长度最大
3. 词语长度的变化率最小
4. 计算组合中所有单字词词频的自然对数,然后将得到的值相加,取总和最大的词组
MMSEG的核心思想是抽取3个可能的词(存在多个组合),然后根据4个消除歧义规则确定到底选择那个组合。
下面分别举例说明
1.组合长度最大Maximum matching (最大匹配),有两种情况,分别对应于使用“simple”和“complex”的匹配方法。对“simple”匹配方法,选择长度最大的词,用在上文的例子中即选择“一个劲儿的”;对“complex”匹配方法,选择“词组长度最大的”那个词组,然后选择这个词组的第一个词,作为切分出的第一个词,
比如长春市长春药店,这个会有如下几种组合

长春市_长春_药店_
长春市_长_春药_
长春_市长_春药_
长春_市_长春_
长_春_市长_

第一种组合长度最长,所以就以第一种方式分词, 实际效果看起来也合理
2.组合中平均词语长度最大Largest average word length(最大平均词语长度)。经过规则1过滤后,如果剩余的词组超过1个,那就选择平均词语长度最大的那个(平均词长=词组总字数/词语数量)。
比如国际化,这个会有如下几种组合

国际化_
国际_化_
国_际_化_

显然规则1无法过滤,长度都是3 经过规则2,之后发现第一个组合平均长度为3/1=3,第二个是3/2=1.5,第三个3/3=1 第一个平均长度最大,所以胜出
这个规则和规则1看上去没啥区别,但因为有的时候句子不够被分成3个词的组合,有可能只够分2个词 上面就是个例子,国际化被分别分成了1个词的组合/2个词的组合/3个词的组合,优选词个数最少的组合
3.词语长度的变化率最小Smallest variance of word lengths(词语长度的最小变化率),由于词语长度的变化率可以由标准差反映,所以此处直接套用标准差公式即可。
比如北京大学生,这个会有如下几种集合

北京大学_生_
北京_大学生_
北京_大学_生_
北京_大_学生_
北_京_大学生_

显然规则1无法过滤,长度都是5
在经过规则2之后剩下

北京大学_生_
北京_大学生_

因为上面2个组合的平均长度为5/2=2.5,其他为5/3=1.66
经过规则3之后剩下
北京大学生
这是我们想要的,因为第一条是变化是sqrt(((4-2.5)^2+(1-2.5)^2))/2)=1.5,后面是sqrt(((3-2.5)^2+(2-2.5)^2))/2)=0.5,第二条变化小,所以后面胜出
4.单字词词频自然对数累加计算Largest sum of degree of morphemic freedom of one-character words,这个规则的意思是“计算词组中的所有单字词词频的自然对数,然后将得到的值相加,取总和最大的词组”。
比如设施和服务,这个会有如下几种组合

设施_和服_务_
设施_和_服务_
设_施_和服_

经过规则1过滤得到

设施_和服_务_
设施_和_服务_

规则2和规则3都无法确定谁胜出,只能走最后一个规则:第一条中的务和第二条中的和,从直观看,显然是和的词频在日常场景下要高,这依赖一个词频字典 和的词频决定了最后的分词是设施服务_
为什么要取自然对数之和而不是简单的求和? 比如某个组合有两个单字,词频为3和7,另一个为5和5,3+7=5+5,但ln3+ln7小于ln5+ln5
小结
从4个规则来看,算法处处强调长度和均衡:
1.3个词的组合要尽可能长
2.每个词也要尽可能长
3.每个词要尽可能长度接近
4.单个词的词频也要较为接近
这个四个过滤规则中,如果使用simple的匹配方法,只能使用第一个规则过滤,如果使用complex的匹配方法,则四个规则都可以使用。实际使用中,一般都是使用complex的匹配方法+四个规则过滤。(simple的匹配方法实质上就是正向最大匹配,实际中很少只用这一个方法)

最大概率分词算法
参考内容:基于Tire树和最大概率法的中文分词功能的Java实现
http://blog.csdn.net/sadfishsc/article/details/9152647
最大概率分词原理和代码
http://blog.sina.com.cn/s/blog_3d37a5690100z0qp.html
最大概率分词法
http://blog.csdn.net/zoohua/article/details/4687074

Java实现代码


import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 本程序采用的是候选词的累计相乘概率
 * @author Angela
 */
public class MaxProbSegment {

    /**最大分词长度**/
    private int max_len;
    /**分词词典**/
    private Map<String,Double> dict;
    /****/
    private List<Candidate> candidateWord;

    /**初始化最大分词长度和词典**/
    public MaxProbSegment(){
        max_len=4;
        initDict("dic/WordFrequency.txt","gb2312");
    }

    /**
     * 初始化词典
     * @param dictPath 词典文件路径
     * @param charset 词典文件编码
     */
    private void initDict(String dictPath,String charset){
        dict=new HashMap<String,Double>();
        try{           
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    new FileInputStream(dictPath),charset));
            String s;
            //一行一行地读取文本内容
            while((s=br.readLine())!=null){
                String[] info=s.split(",");
                //System.out.println(info[0]+" "+Double.parseDouble(info[2].replace("%","")));
                //存入词和词的概率
                dict.put(info[0],Double.parseDouble(info[2].replace("%","")));
            }
            br.close();
        }catch (IOException ex) {
            Logger.getLogger(BiMaxSegment.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * 得到所有候选词
     * @param text 文本
     */
    private void getCandidateWord(String text){
        candidateWord=new ArrayList<Candidate>();
        int rest_len;
        int n=text.length();
        String word;
        for(int offset=0;offset<n;offset++){
            rest_len=n-offset;
            //长度应小于剩余长度 
            for(int len=1;len<=max_len&&len<=rest_len;len++){                
                //截取部分词串
                word=text.substring(offset,offset+len);    
                //如果词典含有该词,则该词为一个候选词
                if(dict.containsKey(word)){
                    Candidate candidate=new Candidate();
                    candidate.offset=offset;
                    candidate.length=len;
                    candidate.cost=dict.get(word);
                    candidateWord.add(candidate);
                }
            }
        }
    }

    /**
     * 计算每一个候选词的最佳前趋词,以及当前词的最大累计概率
     * @param index 第index个候选词 
     */
    private void getPrev(int index){
        Candidate candidate=candidateWord.get(index);
        int maxID=-1;
        if(candidate.offset==0){
            candidate.prev=-1;
            candidate.total_cost=candidate.cost;
        }else{
            //向左查找所有候选词,得到前驱词集合,从中挑选最佳前趋词
            for(int i=index-1;i>=0;i--){
                Candidate temp=candidateWord.get(i);
                //得到前驱词
                if(temp.offset+temp.length==candidate.offset){
                    //找到累计概率最大的前驱词
                    if(maxID==-1||
                            temp.total_cost>=candidateWord.get(maxID).total_cost)
                        maxID=i;
                }
                if(candidate.offset-temp.offset>=max_len)
                    break;//向左查找候选词最远不超过max_len个汉字
            }
            candidate.prev=maxID;
            //概率累乘
            candidate.total_cost=candidate.cost*candidateWord.get(maxID).total_cost;
        }
    }

    /**
     * 最大概率分词算法
     * @param text 文本
     * @return 分词结果
     */
    public String segment(String text){        
        int len=text.length();
        //初始化所有的候选词
        getCandidateWord(text);
        int n=candidateWord.size();
        int maxID=-1;
        //得到每一个候选词的最佳前缀词
        for(int i=0;i<n;i++){            
            getPrev(i);
            Candidate candidate=candidateWord.get(i);
            //如果当前词是text中最后一个可能的候选词
            if(candidate.offset+candidate.length==len){
                // 如果这个末尾候选词的累计概率最大
                if(maxID==-1||
                        candidate.total_cost>candidateWord.get(maxID).total_cost)
                    maxID=i;// 把当前词的序号赋给minID,这就是最大概率路径的终点词的序号
                    // 这就是最后分词结果最右边的那个词的序号
            }
        }
        StringBuilder sb=new StringBuilder();
        //从右向左取词候选词
        for(int i=maxID;i>=0;i=candidateWord.get(i).prev){
            Candidate candidate=candidateWord.get(i);
            sb.insert(0, text.substring(candidate.offset,
                    candidate.offset+candidate.length)+"/");
        }
        return sb.toString();
    }

    public static void main(String[] args){
        MaxProbSegment mp=new MaxProbSegment();
        String text="有意见分歧";
        System.out.println(mp.segment(text));
    }

    /**候选词类**/
    private class Candidate{
        /**候选词在字符串中的起始位置**/
        int offset;
        /**候选词长度**/
        int length;
        /**候选词的最佳前缀候选词的下标**/
        int prev;
        /**候选词的概率**/
        double cost;
        /**候选词的累计概率**/
        double total_cost;
    }

}
  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
中文分词是将一段中文文本按照语义单元进行切分的过程。通常使用算法实现,比如基于规则的方法和基于统计学习的方法等。正确率、召回率和F1是评估中文分词效果的重要指标。 - 正确率(Precision)是指分词结果中正确分词数占所有分词数的比例,即正确分词数 / 分词器分出的总词数。 - 召回率(Recall)是指分词结果中正确分词数占所有正确词数的比例,即正确分词数 / 应分出的总词数。 - F1值是综合考虑正确率和召回率的指标,其计算公式为:2 * Precision * Recall / (Precision + Recall)。 在Python中,可以使用第三方库如jieba来实现中文分词,并计算正确率、召回率和F1值。需要准备好分词测试集和标准答案,然后通过代码计算这些指标。以下是一个简单的例子: ```python import jieba # 读取测试集和标准答案 with open('test_set.txt', 'r', encoding='utf-8') as f: test_set = f.readlines() with open('standard_answer.txt', 'r', encoding='utf-8') as f: standard_answer = f.readlines() # 分别记录正确分词数、应分出的总词数和所有正确词数 correct_word_count = 0 total_word_count = 0 all_correct_word_count = 0 # 遍历测试集并进行分词,同时统计指标 for i in range(len(test_set)): words = jieba.lcut(test_set[i].strip()) standard_words = standard_answer[i].strip().split() for word in words: if word in standard_words: correct_word_count += 1 all_correct_word_count += 1 total_word_count += len(words) # 计算正确率、召回率和F1值 precision = correct_word_count / total_word_count recall = all_correct_word_count / total_word_count f1 = 2 * precision * recall / (precision + recall) print("Precision:", precision) print("Recall:", recall) print("F1:", f1) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值