NLP-词典分词

  • 一、环境安装pyhanlp
  • 二、hanlp词典获取
  • 三、完全切分
  • 四、正向最长匹配(两种方式)
  • 五、逆向最长匹配
  • 六、双向最长匹配



一、环境安装pyhanlp

conda install -c conda-forge openjdk python=3.8 jpype1=0.7.0 -y
pip install pyhanlp

二、hanlp词典获取

def load_dictionary():
    """
    加载HanLP中的mini词库
    :return: 一个set形式的词库
    """
    IOUtil = JClass('com.hankcs.hanlp.corpus.io.IOUtil')
    path = HanLP.Config.CoreDictionaryPath.replace('.txt', '.mini.txt')
    dic = IOUtil.loadDictionary([path])
    return set(dic.keySet())

# 测试
dic = load_dictionary()
print(len(dic))
print(list(dic)[0])

三、完全切分

找出一段文本中所有单词,并不是标准意义上的分词。

def fully_segment(text, dic):
    word_list = []
    for i in range(len(text)):                  # i 从 0 到text的最后一个字的下标遍历
        for j in range(i + 1, len(text) + 1):   # j 遍历[i + 1, len(text)]区间
            word = text[i:j]                    # 取出连续区间[i, j]对应的字符串
            if word in dic:                     # 如果在词典中,则认为是一个词
                word_list.append(word)
    return word_list

四、正向最长匹配(两种方式)

优先输出更长的单词,扫描顺序由前向后。

def forward_segment(text, dic):
    word_list = []
    i = 0
    while i < len(text):
        longest_word = text[i]                      # 当前扫描位置的单字
        for j in range(i + 1, len(text) + 1):       # 所有可能的结尾
            word = text[i:j]                        # 从当前位置到结尾的连续字符串
            if word in dic:                         # 在词典中
                if len(word) > len(longest_word):   # 并且更长
                    longest_word = word             # 则更优先输出
        word_list.append(longest_word)              # 输出最长词
        i += len(longest_word)                      # 正向扫描
    return word_list


def forward_segment_other(text, dic):
    word_list = []
    i = 0
    while i < len(text):
        for j in range(len(text) + 1, i, -1):
            word = text[i:j]                        # 从当前位置到结尾的连续字符串
            if word in dic or len(word) == 1:       # 在词典中或长度为1
                word_list.append(word)              # 输出最长词
                i += len(word)                      # 正向扫描
                break

    return word_list

五、逆向最长匹配

优先输出更长的单词,扫描顺序由后向前。

def backward_segment_other(text, dic):
    word_list = []
    i = len(text)
    while i > 0:
        longest_word = text[i-1]                    # 当前扫描位置的单字
        for j in range(i-1, -1, -1):                # 所有可能的结尾
            word = text[j:i]                        # 从当前位置到结尾的连续字符串
            if word in dic:                         # 在词典中
                if len(word) > len(longest_word):   # 并且更长
                    longest_word = word             # 则更优先输出
        word_list.append(longest_word)              # 输出最长词
        i -= len(longest_word)                      # 正向扫描
    return word_list[::-1]


def backward_segment(text, dic):
    word_list = []
    i = len(text) - 1
    while i >= 0:                                   # 扫描位置作为终点
        longest_word = text[i]                      # 扫描位置的单字
        for j in range(0, i):                       # 遍历[0, i]区间作为待查询词语的起点
            word = text[j: i + 1]                   # 取出[j, i]区间作为待查询单词
            if word in dic:
                if len(word) > len(longest_word):   # 越长优先级越高
                    longest_word = word
                    break
        word_list.insert(0, longest_word)           # 逆向扫描,所以越先查出的单词在位置上越靠后
        i -= len(longest_word)
    return word_list

六、双向最长匹配

正向最大匹配法和逆向最大匹配法,都有其局限性。双向最大匹配法:两种算法都切一遍,然后根据大颗粒度词越多越好,非词典词和单字词越少越好的原则,选取其中一种分词结果输出。

def count_single_char(word_list: list):  # 统计单字成词的个数
    return sum(1 for word in word_list if len(word) == 1)


def bidirectional_segment(text, dic):
    f = forward_segment(text, dic)
    b = backward_segment(text, dic)
    if len(f) < len(b):                                  # 词数更少优先级更高
        return f
    elif len(f) > len(b):
        return b
    else:
        if count_single_char(f) < count_single_char(b):  # 单字更少优先级更高
            return f
        else:
            return b                                     # 都相等时逆向匹配优先级更高