当前位置: 首页 > article >正文

自然语言处理:文本规范化

介绍

大家好!很高兴又能在这儿和大家分享自然语言处理相关的知识了。在上一篇发布于自然语言处理:初识自然语言处理-CSDN博客为大家初步介绍了自然语言处理的基本概念。而这次,我将进一步深入这个领域,和大家聊聊自然语言处理中一个关键的基础环节:文本规范化。好了,我们直接进入正题。

文本规范化

概念

自然语言处理中的文本规范化,是指对原始文本进行一系列处理操作,使其具有统一、标准的格式和表达形式,以提高后续自然语言处理任务(如文本分类、信息检索、机器翻译等)的准确性和效率。

常见的文本规范化操作和任务包含以下内容:

  • 大小写转换:将文本中的所有字母统一为大写或小写形式。
  • 去除标点符号:移除文本中的标点符号,如句号、逗号、问号、感叹号等。因为标点符号通常对文本的语义理解贡献较小,且可能干扰文本处理算法,所以将其去除。
  • 分词:将连续的文本按照一定规则切分成单个的词或词组。对于英文,词与词之间通常由空格分隔,但也存在一些特殊情况(如缩写、复合词等)需要特殊处理;对于中文,由于词与词之间没有明显的分隔符,分词难度较大,需要借助专门的分词算法和工具。
  • 词形还原:将单词的不同形式(如动词的时态、名词的单复数等)还原为其基本形式(原形)。
  • 去除停用词:停用词是指在文本中频繁出现但对文本语义表达贡献较小的词,如英文中的“the”“and”“is”等,中文中的“的”“了”“在”等。去除停用词可以减少文本中的冗余信息,突出关键词,提高处理效率。
  • 数字归一化:将文本中的数字进行统一表示。
  • 特殊字符处理:处理文本中的特殊字符,如HTML标签、表情符号、特殊符号等。

分词

分词是自然语言处理(NLP)中的一项基础且关键的任务,指的是将连续的自然语言文本按照一定的规则或算法切分成一个个独立的词或词组的过程。不同语言的分词存在差异,例如:

  • 中文:汉语中词与词之间没有明显的分隔标记(不像英语单词间有空格),所以分词相对复杂。例如,“我喜欢自然语言处理”,需要借助特定的分词算法和工具,将其切分为 “我 / 喜欢 / 自然语言 / 处理”。常用的中文分词方法有基于词典的方法、基于统计的方法和基于深度学习的方法等。
  • 英文:虽然英文单词之间以空格作为天然的分隔,但也存在一些特殊情况需要处理,如缩写(“it's”需处理为“it”“'s”)、复合词(“mother-in-law”)、连字符连接的词(“self-esteem”)等。有时也需要对英文文本进行进一步的词形还原等操作,以便更好地进行后续的自然语言处理任务。

分词作为词性标注、句法分析、语义理解、文本分类、信息检索、机器翻译等众多自然语言处理任务的前置步骤,其准确性对后续任务的质量和效率影响重大,它通过精确划分文本,为后续对文本的深入分析和处理提供基础。

空格与标点符号的分词

在印欧语系里,英语是具有代表性的语言。在这一语系的大多数语言中,普遍采用空格字符来划分单词。所以,一种极为简便的分词方法便是依据空格来进行分词:

空格分词

我们使用Python字符串对象的一个内置方法split(),其功能是将字符串按照指定的分隔符进行分割,然后返回一个由分割后的子字符串组成的列表。

完整代码
# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续分词操作
        original_sentence = ("I'm currently researching the field of Natural Language Processing, and it's really "
                             "fascinating.")
        # 使用空格作为分隔符对原始句子进行分词,结果存储在segmented_sentence中
        segmented_sentence = original_sentence.split(' ')
        # 打印原始句子
        print(f'原句:{original_sentence}')
        # 打印分词结果
        print(f"分词结果:{segmented_sentence}")


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
原句:I'm currently researching the field of Natural Language Processing,and it's really fascinating.
分词结果:["I'm", 'currently', 'researching', 'the', 'field', 'of', 'Natural', 'Language', 'Processing,and', "it's", 'really', 'fascinating.']

进程已结束,退出代码为 0

运行结果我们发现,这种最为简易的基于空格进行分词的方式,没办法把单词和紧随其后的标点符号分离开来。倘若标点符号对于后续的任务(比如文本分类工作)而言并非关键要素,那么可以先将这些标点符号予以剔除,而后再开展更为深入的分词操作。那么这时候,我们需要引入正则表达式的功能。

正则分词

我们借助Pythonre模块实现字符串替换,用正则表达式模式 (r'\.|\,', '') 用于匹配字符串里的句号(.)逗号(,),并讲将匹配到的句号逗号替换为空字符串

完整代码
# 导入Python的re模块,该模块提供了正则表达式操作的功能
import re


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续分词操作
        original_sentence = ("I'm currently researching the field of Natural Language Processing, and it's really "
                             "fascinating.")
        # 打印原始句子,方便查看初始文本内容
        print(f'原句:{original_sentence}')
        # 使用正则表达式替换,去除句子中的句号(.)和逗号(,)
        sentence = re.sub(r'\.|\,', '', original_sentence)
        # 基于空格对处理后的句子进行分词,将其拆分为单词列表
        segmented_sentence = sentence.split(' ')
        # 打印分词结果,展示分词后的单词列表
        print(f"分词结果:{segmented_sentence}")


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
原句:I'm currently researching the field of Natural Language Processing, and it's really fascinating.
分词结果:["I'm", 'currently', 'researching', 'the', 'field', 'of', 'Natural', 'Language', 'Processing', 'and', "it's", 'really', 'fascinating']

进程已结束,退出代码为 0

正则表达式的分词

正则表达式能够借助一个单独的字符串(一般被叫做 “模式”,也就是pattern),用该字符串去对相应文本里所有符合特定规则的字符串进行描述和匹配操作。 我们同样能够运用正则表达式来达成基于空格的分词处理。

空格分词

我们使用正则表达式模式 (r"\w+") 。其主要功能是从文本中找出由一个或多个连续的字母、数字或下划线组成的字符串。

完整代码
# 导入Python的re模块,该模块提供了正则表达式操作的功能
import re


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续使用正则表达式进行分词处理
        original_sentence = ("Did you pay €6.99 on researchgate.net for access to that full-text paper? "
                             "No, I got it through our university library! It's really convenient...")
        # 定义正则表达式模式,\w+表示匹配一个或多个字母、数字或下划线
        pattern = r"\w+"
        # 使用re模块的findall方法,根据模式从原始句子中找出所有匹配项并打印结果
        print(re.findall(pattern, original_sentence))


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
['Did', 'you', 'pay', '6', '99', 'on', 'researchgate', 'net', 'for', 'access', 'to', 'that', 'full', 'text', 'paper', 'No', 'I', 'got', 'it', 'through', 'our', 'university', 'library', 'It', 's', 'really', 'convenient']

进程已结束,退出代码为 0
含标点分词

我们可以定义一个正则表达式模式(r"\w+|\S\w*")\w+匹配一个或多个字母、数字或下划线,\S\w*匹配以非空白字符开头,后接零个或多个字母、数字或下划线的字符串。这样文本中的单词、标点符号以及带有特殊符号的话题标签都被成功提取出来。

完整代码
# 导入Python的re模块,该模块提供了正则表达式操作的功能
import re


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续使用正则表达式进行分词处理
        original_sentence = ("Did you pay €6.99 on researchgate.net for access to that full-text paper? "
                             "No, I got it through our university library! It's really convenient...")
        # 定义正则表达式模式,\w+匹配一个或多个字母、数字或下划线,\S\w*匹配以非空白字符开头,后接零个或多个字母、数字或下划线的字符串
        pattern = r"\w+|\S\w*"
        # 使用re模块的findall方法,根据模式从原始句子中找出所有匹配项并打印结果
        print(re.findall(pattern, original_sentence))


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
['Did', 'you', 'pay', '€6', '.99', 'on', 'researchgate', '.net', 'for', 'access', 'to', 'that', 'full', '-text', 'paper', '?', 'No', ',', 'I', 'got', 'it', 'through', 'our', 'university', 'library', '!', 'It', "'s", 'really', 'convenient', '.', '.', '.']

进程已结束,退出代码为 0
匹配连字符词

我们定义一个正则表达式模式(r"\w+(?:[-']\w+)*"),其作用是匹配文本中的单词,既可以匹配普通单词(由一个或多个字母、数字或下划线组成),也能匹配包含连字符 - 或单引号 ' 的复合单词。该模式先匹配一个或多个连续的字母、数字或下划线,之后可选择零次或多次匹配以连字符或单引号开头,后接一个或多个字母、数字或下划线的部分。

完整代码
# 导入Python的re模块,该模块提供了正则表达式操作的功能
import re


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass
    
    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续使用正则表达式进行分词处理
        original_sentence = ("Did you pay €6.99 on researchgate.net for access to that full-text paper? "
                             "No, I got it through our university library! It's really convenient...")
        # 定义正则表达式模式,用于匹配普通单词及包含连字符或单引号的复合单词
        pattern = r"\w+(?:[-']\w+)*"
        # 使用re模块的findall方法,根据模式从原始句子中找出所有匹配项并打印结果
        print(re.findall(pattern, original_sentence))


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
['Did', 'you', 'pay', '6', '99', 'on', 'researchgate', 'net', 'for', 'access', 'to', 'that', 'full-text', 'paper', 'No', 'I', 'got', 'it', 'through', 'our', 'university', 'library', "It's", 'really', 'convenient']

进程已结束,退出代码为 0
匹配连字符词和标点

大家看到这,我相信大家肯定会想到:如果把匹配含标点词和匹配连字符词的模式放到一起,是不是既可以匹配到标点又可以匹配到连字符词?嗯,是这样的。我们用代码来演示下。

完整代码
# 导入Python的re模块,该模块提供了正则表达式操作的功能
import re


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续使用正则表达式进行分词处理
        original_sentence = ("Did you pay €6.99 on researchgate.net for access to that full-text paper? "
                             "No, I got it through our university library! It's really convenient...")
        # 定义正则表达式模式,可匹配普通单词、含连字符或单引号的复合单词以及以非空白字符开头的字符串
        pattern = r"\w+(?:[-']\w+)*|\S\w*"
        # 使用re模块的findall方法,根据模式从原始句子中找出所有匹配项并打印结果
        print(re.findall(pattern, original_sentence))


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
['Did', 'you', 'pay', '€6', '.99', 'on', 'researchgate', '.net', 'for', 'access', 'to', 'that', 'full-text', 'paper', '?', 'No', ',', 'I', 'got', 'it', 'through', 'our', 'university', 'library', '!', "It's", 'really', 'convenient', '.', '.', '.']

进程已结束,退出代码为 0
匹配简写和网址

在英文简写和网址里,我们经常会用到 “.”,例如上述代码里的researchgate.net,它和英文里的句号是同一个符号,那么我们怎么来区分它到底是英文中的句号,还是网址里的域名点呢?

正则表达式模式(r"(?:\w+\.)+\w+(?:\.)*")用于匹配特定格式的字符串,常适用于英文缩写词、网址等场景。

我们定义一个新的正则模式 r"(?:\w+\.)+\w+(?:\.)*",然后再与(r"\w+(?:[-']\w+)*|\S\w*")连接起来形成一个新的正则表达式模式。

完整代码
# 导入Python的re模块,该模块提供了正则表达式操作的功能
import re


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续使用正则表达式进行分词处理
        original_sentence = ("Did you pay €6.99 on researchgate.net for access to that full-text paper? "
                             "No, I got it through our university library! It's really convenient...")
        # 定义正则表达式模式,可匹配普通单词、含连字符或单引号的复合单词以及以非空白字符开头的字符串
        pattern = r"\w+(?:[-']\w+)*|\S\w*"
        # 定义新的正则表达式模式,用于匹配类似英文简写和网址的字符串
        web_pattern = r"(?:\w+\.)+\w+(?:\.)*"
        # 将新的正则表达式模式和原模式通过逻辑或连接,更新为新的匹配模式
        pattern = web_pattern + r"|" + pattern
        # 导入re模块,使用re.findall方法根据模式从原始句子中找出所有匹配项并打印结果
        print(re.findall(pattern, original_sentence))


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
['Did', 'you', 'pay', '€6', '.99', 'on', 'researchgate.net', 'for', 'access', 'to', 'that', 'full-text', 'paper', '?', 'No', ',', 'I', 'got', 'it', 'through', 'our', 'university', 'library', '!', "It's", 'really', 'convenient', '.', '.', '.']

进程已结束,退出代码为 0
匹配价格符号

在全球众多的语言体系里,当表达货币金额或者百分比数值时,货币符号(比如美元符号 “$”、欧元符号 “€” 等)以及百分比符号 “%”,往往是直接和数字紧密连接在一起的。若要在文本中找出符合这种直接相连形式的内容,就可以借助一个特定的正则表达式来实现,该正则表达式模式为:r"\$?\d+(?:\.\d+)?%?"

完整代码
# 导入Python的re模块,该模块提供了正则表达式操作的功能
import re


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续使用正则表达式进行分词处理
        original_sentence = ("Did you pay €6.99 on researchgate.net for access to that full-text paper? "
                             "No, I got it through our university library! It's really convenient...")
        # 定义正则表达式模式,可匹配普通单词、含连字符或单引号的复合单词以及以非空白字符开头的字符串
        pattern = r"\w+(?:[-']\w+)*|\S\w*"
        # 定义用于匹配网址的正则表达式模式,可匹配如example.com等形式的网址
        web_pattern = r"(?:\w+\.)+\w+(?:\.)*"
        # 定义用于匹配价格符号相关内容的正则表达式模式,可匹配如$12.34、12%等形式
        price_symbol_pattern = r"\$?\d+(?:\.\d+)?%?"
        # 将价格符号模式、网址模式和之前的模式通过逻辑或连接,形成新的匹配模式
        pattern = price_symbol_pattern + r"|" + web_pattern + r"|" + pattern
        # 使用re.findall方法根据最终模式从原始句子中找出所有匹配项并打印结果
        print(re.findall(pattern, original_sentence))


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
['Did', 'you', 'pay', '€6', '.99', 'on', 'researchgate.net', 'for', 'access', 'to', 'that', 'full-text', 'paper', '?', 'No', ',', 'I', 'got', 'it', 'through', 'our', 'university', 'library', '!', "It's", 'really', 'convenient', '.', '.', '.']

进程已结束,退出代码为 0
匹配省略号

省略号在文本里有着自身独特的表意功能,能够传达出诸如意犹未尽、停顿、沉默等特定的含义。正因为它具备这样不可忽视的语义价值,所以在对文本进行分词处理时,我们需要将其完整地保留下来,不能遗漏或忽略。而为了能够在文本中准确地找到并识别出省略号,以便实现对它的保留操作,我们可以运用一个专门设计的正则表达式来进行匹配,具体的正则表达式模式为:r"\.\.\."

完整代码
# 导入Python的re模块,该模块提供了正则表达式操作的功能
import re


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续使用正则表达式进行分词处理
        original_sentence = ("Did you pay €6.99 on researchgate.net for access to that full-text paper? "
                             "No, I got it through our university library! It's really convenient...")
        # 定义正则表达式模式,可匹配普通单词、含连字符或单引号的复合单词以及以非空白字符开头的字符串
        pattern = r"\w+(?:[-']\w+)*|\S\w*"
        # 定义用于匹配网址的正则表达式模式,可匹配如example.com等形式的网址
        web_pattern = r"(?:\w+\.)+\w+(?:\.)*"
        # 定义用于匹配价格符号相关内容的正则表达式模式,可匹配如$12.34、12%等形式
        price_symbol_pattern = r"\$?\d+(?:\.\d+)?%?"
        # 定义用于匹配省略号的正则表达式模式,可精准匹配文本中的省略号
        ellipsis_pattern = r"\.\.\."
        # 将省略号模式、价格符号模式、网址模式和原模式通过逻辑或连接,形成新的匹配模式
        pattern = ellipsis_pattern + r"|" + price_symbol_pattern + r"|" + web_pattern + r"|" + pattern
        # 使用re.findall方法根据最终模式从原始句子中找出所有匹配项并打印结果
        print(re.findall(pattern, original_sentence))


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
['Did', 'you', 'pay', '€6', '.99', 'on', 'researchgate.net', 'for', 'access', 'to', 'that', 'full-text', 'paper', '?', 'No', ',', 'I', 'got', 'it', 'through', 'our', 'university', 'library', '!', "It's", 'really', 'convenient', '...']

进程已结束,退出代码为 0
NLTK

NLTK(Natural Language Toolkit)即自然语言工具包,是一个广泛用于自然语言处理(NLP)的 Python库。

它提供了大量的工具和资源,涵盖了文本处理的各个方面,如分词(将文本分割成单词或句子)、词性标注(为每个单词标注词性,如名词、动词、形容词等)、命名实体识别(识别文本中的人名、地名、组织机构名等实体)、词干提取(将单词还原为词干形式,如“running”还原为“run”)、词形还原(将单词还原为字典中的标准形式,如“went”还原为“go”)等。

我们可以使用NLTK库来实现上面的功能。在使用NLTK库之前,我们需要先安装该库,安装命令为:

pip install nltk
完整代码
# 从nltk.tokenize模块中导入regexp_tokenize函数,用于根据正则表达式模式进行分词
from nltk.tokenize import regexp_tokenize


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,用于后续使用正则表达式进行分词处理
        original_sentence = ("Did you pay €6.99 on researchgate.net for access to that full-text paper? "
                             "No, I got it through our university library! It's really convenient...")
        # 定义正则表达式模式,可匹配普通单词、含连字符或单引号的复合单词以及以非空白字符开头的字符串
        pattern = r"\w+(?:[-']\w+)*|\S\w*"
        # 定义用于匹配网址的正则表达式模式,可匹配如example.com等形式的网址
        web_pattern = r"(?:\w+\.)+\w+(?:\.)*"
        # 定义用于匹配价格符号相关内容的正则表达式模式,可匹配如$12.34、12%等形式
        price_symbol_pattern = r"\$?\d+(?:\.\d+)?%?"
        # 定义用于匹配省略号的正则表达式模式,可精准匹配文本中的省略号
        ellipsis_pattern = r"\.\.\."
        # 将省略号模式、价格符号模式、网址模式和原模式通过逻辑或连接,形成新的匹配模式
        pattern = ellipsis_pattern + r"|" + price_symbol_pattern + r"|" + web_pattern + r"|" + pattern
        # 使用regexp_tokenize函数,依据指定的正则表达式模式对句子进行分词,并将结果存储在tokens变量中
        tokens = regexp_tokenize(original_sentence, pattern)
        # 打印分词后的结果
        print(tokens)


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
['Did', 'you', 'pay', '€6', '.99', 'on', 'researchgate.net', 'for', 'access', 'to', 'that', 'full-text', 'paper', '?', 'No', ',', 'I', 'got', 'it', 'through', 'our', 'university', 'library', '!', "It's", 'really', 'convenient', '...']

进程已结束,退出代码为 0

基于BPE的词元学习器

BPE(Byte Pair Encoding,字节对编码是)一种数据压缩算法,后来被广泛应用于自然语言处理领域的词元(subword)学习,用于将单词拆分成更小的、有意义的单元(词元)。

BPE的核心思想是从字符级别开始,逐步合并高频的字符对(或字节对),直到达到预设的词表大小或满足一定的停止条件。具体来说,首先将每个单词表示为字符序列(例如,“h i g h e r”),然后统计数据集中所有相邻字符对的出现频率,将频率最高的字符对合并成一个新的单元(比如“h i g h e r”“e”“r”常见,合并为“er”,得到“h i g h er”)。重复这个过程,不断合并高频字符对,最终形成一个包含各种词元的词表。

创建词表

我们用代码的方式来演示一下。首先我们根据语料库来创建初始的词表:

完整代码
# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
                # 定义待处理的文本语料,包含多个城市相关词汇
        input_text_corpus = "shang shang shang shang shang shanghai shanghai weihai weihai weihai weihai weihai weihai haikou haikou haikou kou kou"
        # 将文本语料按空格分割成单词列表
        segmented_words = input_text_corpus.split(' ')

        # 构建基于字符的初始词表
        # 把文本语料中的所有字符放入集合,利用集合去重特性
        char_based_vocab = set(input_text_corpus)
        # 从字符集合中移除空格字符
        char_based_vocab.remove(' ')
        # 向字符集合中添加特殊字符'_'用于标记单词结束
        char_based_vocab.add('_')
        # 将字符集合转换为列表并排序,得到初始的字符词表
        sorted_char_vocab = sorted(list(char_based_vocab))

        # 根据语料构建词表
        # 初始化一个空字典,用于存储每个单词的拆分信息和出现次数
        word_info_dict = {}
        # 遍历分割后的单词列表
        for single_word in segmented_words:
            # 为每个单词添加结束符'_'形成新的键
            word_key = single_word + '_'
            # 检查该键是否不在存储单词信息的字典中
            if word_key not in word_info_dict:
                # 若不在,初始化该单词的信息,包含字符拆分列表和初始计数0
                word_info_dict[word_key] = {"split": list(word_key), "count": 0}
            # 增加该单词的出现次数计数
            word_info_dict[word_key]['count'] += 1

        # 打印语料信息提示
        print(f"语料:")
        # 遍历存储单词信息的字典
        for word_key in word_info_dict:
            # 打印该单词的出现次数和字符拆分情况
            print(word_info_dict[word_key]['count'], word_info_dict[word_key]['split'])
        # 打印初始构建好的字符词表
        print(f"词表:{sorted_char_vocab}")


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
语料:
2 ['a', 'p', 'p', 'l', 'e', '_']
2 ['b', 'a', 'n', 'a', 'n', 'a', '_']
2 ['o', 'r', 'a', 'n', 'g', 'e', '_']
2 ['s', 'h', 'a', 'n', 'g', 'h', 'a', 'i', '_']
3 ['g', 'u', 'a', 'n', 'g', 'z', 'h', 'o', 'u', '_']
2 ['c', 'h', 'e', 'n', 'g', 'd', 'u', '_']
词汇表:['_', 'a', 'b', 'c', 'd', 'e', 'g', 'h', 'i', 'l', 'n', 'o', 'p', 'r', 's', 'u', 'z']

进程已结束,退出代码为 0

通过上面的完整代码运行结果我们看到,我们对预设的文本语料(包含城市名称)进行处理。首先将语料按空格分词,然后构建基于字符的词汇表,去除空格并添加特殊字符 _ 表示词尾,同时对词汇表进行排序。接着,统计语料中每个词语的出现次数,将词语拆分为字符列表并存储相关信息。最后,代码打印出语料中各词语的出现次数与字符拆分情况,以及构建好的词汇表,为后续自然语言处理任务提供基础的语料分析和词汇信息。

循环纳入

其次,我们采用循环的手段让词元学习器循序渐进地将新的符号进行组合,而后把这些组合所得纳入到词表当中。

完整代码
# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义待处理的文本语料,包含多个城市相关词汇
        input_text_corpus = ("shang shang shang shang shang shanghai shanghai weihai weihai weihai weihai weihai "
                             "weihai haikou haikou haikou kou kou")
        # 将文本语料按空格分割成单词列表
        segmented_words = input_text_corpus.split(' ')

        # 构建基于字符的初始词表
        # 把文本语料中的所有字符放入集合,利用集合去重特性
        char_based_vocab = set(input_text_corpus)
        # 从字符集合中移除空格字符
        char_based_vocab.remove(' ')
        # 向字符集合中添加特殊字符'_'用于标记单词结束
        char_based_vocab.add('_')
        # 将字符集合转换为列表并排序,得到初始的字符词表
        sorted_char_vocab = sorted(list(char_based_vocab))

        # 根据语料构建词表
        # 初始化一个空字典,用于存储每个单词的拆分信息和出现次数
        word_info_dict = {}
        # 遍历分割后的单词列表
        for single_word in segmented_words:
            # 为每个单词添加结束符'_'形成新的键
            word_key = single_word + '_'
            # 检查该键是否不在存储单词信息的字典中
            if word_key not in word_info_dict:
                # 若不在,初始化该单词的信息,包含字符拆分列表和初始计数0
                word_info_dict[word_key] = {"split": list(word_key), "count": 0}
            # 增加该单词的出现次数计数
            word_info_dict[word_key]['count'] += 1

        # 打印语料信息提示
        print(f"语料:")
        # 遍历存储单词信息的字典
        for word_key in word_info_dict:
            # 打印该单词的出现次数和字符拆分情况
            print(word_info_dict[word_key]['count'], word_info_dict[word_key]['split'])
        # 打印初始构建好的字符词表
        print(f"词表:{sorted_char_vocab}")

        # 进行9次循环操作,逐步更新词表
        for iteration in range(9):
            # 设定最大打印步骤数,若想输出所有步骤结果可将其改为999
            max_print_steps = 3
            # 判断是否满足打印当前循环信息的条件
            if iteration < max_print_steps or iteration == 8:
                # 打印当前循环的序号
                print(f"第{iteration + 1}次循环")
            # 初始化一个空字典,用于统计符号组合的出现次数
            symbol_combination_count = {}
            # 遍历存储单词信息的字典
            for word_key in word_info_dict:
                # 获取该单词的字符拆分列表
                char_splits = word_info_dict[word_key]['split']
                # 遍历该单词的字符拆分列表,除最后一个字符
                for i in range(len(char_splits) - 1):
                    # 组合相邻两个字符形成新的符号组合
                    current_combination = char_splits[i] + char_splits[i + 1]
                    # 检查该符号组合是否不在统计字典中
                    if current_combination not in symbol_combination_count:
                        # 若不在,初始化该符号组合的计数为0
                        symbol_combination_count[current_combination] = 0
                    # 增加该符号组合的计数
                    symbol_combination_count[current_combination] += word_info_dict[word_key]['count']

            # 将符号组合及其计数按计数降序排序,存储为元组列表
            sorted_combination_list = [(k, v) for k, v in
                                       sorted(symbol_combination_count.items(), key=lambda item: item[1], reverse=True)]
            # 判断是否满足打印符号组合信息的条件
            if iteration < max_print_steps or iteration == 8:
                # 打印当前最常出现的前 5 个符号组合
                print(f"当前最常出现的前5个符号组合:{sorted_combination_list[:5]}")

            # 取出最常出现的符号组合作为本次要合并的符号
            merge_symbol = sorted_combination_list[0][0]
            # 判断是否满足打印合并符号信息的条件
            if iteration < max_print_steps or iteration == 8:
                # 打印本次循环要合并的符号组合
                print(f"本次循环组合的符号为:{merge_symbol}")
            # 遍历存储单词信息的字典
            for word_key in word_info_dict:
                # 检查该单词是否包含要合并的符号组合
                if merge_symbol in word_key:
                    # 初始化一个空列表,用于存储更新后的字符拆分
                    updated_splits = []
                    # 获取该单词的字符拆分列表
                    char_splits = word_info_dict[word_key]['split']
                    # 初始化索引为 0
                    i = 0
                    # 遍历该单词的字符拆分列表
                    while i < len(char_splits):
                        # 判断是否到达字符拆分列表的最后一个字符
                        if i + 1 >= len(char_splits):
                            # 若到达,将最后一个字符添加到更新后的列表
                            updated_splits.append(char_splits[i])
                            # 索引加 1
                            i += 1
                            # 继续下一次循环
                            continue
                        # 判断相邻两个字符是否组成要合并的符号组合
                        if merge_symbol == char_splits[i] + char_splits[i + 1]:
                            # 若组成,将合并后的符号组合添加到更新后的列表
                            updated_splits.append(merge_symbol)
                            # 索引加 2
                            i += 2
                        else:
                            # 若不组成,将当前字符添加到更新后的列表
                            updated_splits.append(char_splits[i])
                            # 索引加 1
                            i += 1
                    # 更新该单词的字符拆分信息
                    word_info_dict[word_key]['split'] = updated_splits

            # 将本次合并的符号组合添加到字符词表中
            sorted_char_vocab.append(merge_symbol)
            # 判断是否满足打印循环后信息的条件
            if iteration < max_print_steps or iteration == 8:
                # 打印空行用于分隔信息
                print()
                # 打印循环后语料信息提示
                print(f"循环后的语料为:")
                # 遍历存储单词信息的字典
                for word_key in word_info_dict:
                    # 打印该单词的出现次数和更新后的字符拆分情况
                    print(word_info_dict[word_key]['count'], word_info_dict[word_key]['split'])
                # 打印更新后的字符词表
                print(f"词表:{sorted_char_vocab}")
                # 打印空行用于分隔信息
                print()
                # 打印分隔线
                print('***********************************')


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
语料:
5 ['s', 'h', 'a', 'n', 'g', '_']
2 ['s', 'h', 'a', 'n', 'g', 'h', 'a', 'i', '_']
6 ['w', 'e', 'i', 'h', 'a', 'i', '_']
3 ['h', 'a', 'i', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w']
第1次循环
当前最常出现的前5个符号组合:[('ha', 18), ('ai', 11), ('i_', 8), ('sh', 7), ('an', 7)]
本次循环组合的符号为:ha

循环后的语料为:
5 ['s', 'ha', 'n', 'g', '_']
2 ['s', 'ha', 'n', 'g', 'ha', 'i', '_']
6 ['w', 'e', 'i', 'ha', 'i', '_']
3 ['ha', 'i', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w', 'ha']

***********************************
第2次循环
当前最常出现的前5个符号组合:[('hai', 11), ('i_', 8), ('sha', 7), ('han', 7), ('ng', 7)]
本次循环组合的符号为:hai

循环后的语料为:
5 ['s', 'ha', 'n', 'g', '_']
2 ['s', 'ha', 'n', 'g', 'hai', '_']
6 ['w', 'e', 'i', 'hai', '_']
3 ['hai', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w', 'ha', 'hai']

***********************************
第3次循环
当前最常出现的前5个符号组合:[('hai_', 8), ('sha', 7), ('han', 7), ('ng', 7), ('we', 6)]
本次循环组合的符号为:hai_

循环后的语料为:
5 ['s', 'ha', 'n', 'g', '_']
2 ['s', 'ha', 'n', 'g', 'hai_']
6 ['w', 'e', 'i', 'hai_']
3 ['hai', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w', 'ha', 'hai', 'hai_']

***********************************
第9次循环
当前最常出现的前5个符号组合:[('weihai_', 6), ('shang_', 5), ('ko', 5), ('ou', 5), ('u_', 5)]
本次循环组合的符号为:weihai_

循环后的语料为:
5 ['shang', '_']
2 ['shang', 'hai_']
6 ['weihai_']
3 ['hai', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w', 'ha', 'hai', 'hai_', 'sha', 'shan', 'shang', 'we', 'wei', 'weihai_']

***********************************

进程已结束,退出代码为 0

上述代码中我们进行9次循环操作,每次循环统计相邻字符组合成新符号的出现次数,找出最常出现的符号组合并将其作为要合并的符号,更新包含该符号组合的单词的字符拆分信息,同时将合并后的符号添加到词表中,并打印循环中的符号组合信息、合并的符号以及循环后的语料词表情况。 

基于BPE的词元分词器

在获取到通过学习所生成的词表后,若给定一个全新的句子,可借助BPE词元分词器,依据词表中各个符号的学习顺序,以贪心策略对字符进行组合操作。举例来讲,当输入的句子为“shanghai weihai”时,按照上述例子所生成的词表,首先会将字符 “h” 、“a”和“i” 组合成为 “hai”,接着依次组合 “ha”,“sha”…… 直至完成分词。

组合操作

接下来,我们用代码来演示下功能。

完整代码
# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义待处理的文本语料,包含多个城市相关词汇
        input_text_corpus = "shang shang shang shang shang shanghai shanghai weihai weihai weihai weihai weihai weihai haikou haikou haikou kou kou"
        # 将文本语料按空格分割成单词列表
        segmented_words = input_text_corpus.split(' ')

        # 构建基于字符的初始词表
        # 把文本语料中的所有字符放入集合,利用集合去重特性
        char_based_vocab = set(input_text_corpus)
        # 从字符集合中移除空格字符
        char_based_vocab.remove(' ')
        # 向字符集合中添加特殊字符'_'用于标记单词结束
        char_based_vocab.add('_')
        # 将字符集合转换为列表并排序,得到初始的字符词表
        sorted_char_vocab = sorted(list(char_based_vocab))

        # 根据语料构建词表
        # 初始化一个空字典,用于存储每个单词的拆分信息和出现次数
        word_info_dict = {}
        # 遍历分割后的单词列表
        for single_word in segmented_words:
            # 为每个单词添加结束符'_'形成新的键
            word_key = single_word + '_'
            # 检查该键是否不在存储单词信息的字典中
            if word_key not in word_info_dict:
                # 若不在,初始化该单词的信息,包含字符拆分列表和初始计数0
                word_info_dict[word_key] = {"split": list(word_key), "count": 0}
            # 增加该单词的出现次数计数
            word_info_dict[word_key]['count'] += 1

        # 打印语料信息提示
        print(f"语料:")
        # 遍历存储单词信息的字典
        for word_key in word_info_dict:
            # 打印该单词的出现次数和字符拆分情况
            print(word_info_dict[word_key]['count'], word_info_dict[word_key]['split'])
        # 打印初始构建好的字符词表
        print(f"词表:{sorted_char_vocab}")

        # 进行9次循环操作,逐步更新词表
        for iteration in range(9):
            # 设定最大打印步骤数,若想输出所有步骤结果可将其改为999
            max_print_steps = 3
            # 判断是否满足打印当前循环信息的条件
            if iteration < max_print_steps or iteration == 8:
                # 打印当前循环的序号
                print(f"第{iteration + 1}次循环")
            # 初始化一个空字典,用于统计符号组合的出现次数
            symbol_combination_count = {}
            # 遍历存储单词信息的字典
            for word_key in word_info_dict:
                # 获取该单词的字符拆分列表
                char_splits = word_info_dict[word_key]['split']
                # 遍历该单词的字符拆分列表,除最后一个字符
                for i in range(len(char_splits) - 1):
                    # 组合相邻两个字符形成新的符号组合
                    current_combination = char_splits[i] + char_splits[i + 1]
                    # 检查该符号组合是否不在统计字典中
                    if current_combination not in symbol_combination_count:
                        # 若不在,初始化该符号组合的计数为0
                        symbol_combination_count[current_combination] = 0
                    # 增加该符号组合的计数
                    symbol_combination_count[current_combination] += word_info_dict[word_key]['count']

            # 将符号组合及其计数按计数降序排序,存储为元组列表
            sorted_combination_list = [(k, v) for k, v in
                                       sorted(symbol_combination_count.items(), key=lambda item: item[1], reverse=True)]
            # 判断是否满足打印符号组合信息的条件
            if iteration < max_print_steps or iteration == 8:
                # 打印当前最常出现的前 5 个符号组合
                print(f"当前最常出现的前5个符号组合:{sorted_combination_list[:5]}")

            # 取出最常出现的符号组合作为本次要合并的符号
            merge_symbol = sorted_combination_list[0][0]
            # 判断是否满足打印合并符号信息的条件
            if iteration < max_print_steps or iteration == 8:
                # 打印本次循环要合并的符号组合
                print(f"本次循环组合的符号为:{merge_symbol}")
            # 遍历存储单词信息的字典
            for word_key in word_info_dict:
                # 检查该单词是否包含要合并的符号组合
                if merge_symbol in word_key:
                    # 初始化一个空列表,用于存储更新后的字符拆分
                    updated_splits = []
                    # 获取该单词的字符拆分列表
                    char_splits = word_info_dict[word_key]['split']
                    # 初始化索引为 0
                    i = 0
                    # 遍历该单词的字符拆分列表
                    while i < len(char_splits):
                        # 判断是否到达字符拆分列表的最后一个字符
                        if i + 1 >= len(char_splits):
                            # 若到达,将最后一个字符添加到更新后的列表
                            updated_splits.append(char_splits[i])
                            # 索引加 1
                            i += 1
                            # 继续下一次循环
                            continue
                        # 判断相邻两个字符是否组成要合并的符号组合
                        if merge_symbol == char_splits[i] + char_splits[i + 1]:
                            # 若组成,将合并后的符号组合添加到更新后的列表
                            updated_splits.append(merge_symbol)
                            # 索引加 2
                            i += 2
                        else:
                            # 若不组成,将当前字符添加到更新后的列表
                            updated_splits.append(char_splits[i])
                            # 索引加 1
                            i += 1
                    # 更新该单词的字符拆分信息
                    word_info_dict[word_key]['split'] = updated_splits

            # 将本次合并的符号组合添加到字符词表中
            sorted_char_vocab.append(merge_symbol)
            # 判断是否满足打印循环后信息的条件
            if iteration < max_print_steps or iteration == 8:
                # 打印空行用于分隔信息
                print()
                # 打印循环后语料信息提示
                print(f"循环后的语料为:")
                # 遍历存储单词信息的字典
                for word_key in word_info_dict:
                    # 打印该单词的出现次数和更新后的字符拆分情况
                    print(word_info_dict[word_key]['count'], word_info_dict[word_key]['split'])
                # 打印更新后的字符词表
                print(f"词表:{sorted_char_vocab}")
                # 打印空行用于分隔信息
                print()
                # 打印分隔线
                print('***********************************')

        # 生成有序的词表,键为符号,值为其在词表中的索引
        ordered_vocab = {key: index for index, key in enumerate(sorted_char_vocab)}
        # 定义待分词的输入语句
        input_sentence = "shanghai weihai"
        # 打印输入语句
        print(f"输入语句:{input_sentence}")
        # 将输入语句按空格分割成单词列表
        input_words = input_sentence.split(' ')
        # 初始化一个空列表,用于存储分词结果
        tokenized_result = []
        # 遍历输入的单词列表
        for single_word in input_words:
            # 为输入的单词添加结束符'_'形成新的键
            word_key = single_word + '_'
            # 将该单词转换为字符列表
            char_splits = list(word_key)
            # 用于在没有更新的时候跳出循环的标志,初始设为1表示需要更新
            update_flag = 1
            # 当更新标志为1时,继续循环进行更新操作
            while update_flag:
                # 先将更新标志置为0,表示假设本次无更新
                update_flag = 0
                # 初始化一个空字典,用于统计符号组合及其在有序词表中的索引
                combination_index_dict = {}
                # 遍历该单词的字符拆分列表,除最后一个字符
                for i in range(len(char_splits) - 1):
                    # 组合相邻两个字符形成新的符号组合
                    current_combination = char_splits[i] + char_splits[i + 1]
                    # 检查该符号组合是否不在有序词表中
                    if current_combination not in ordered_vocab:
                        # 若不在,跳过本次循环
                        continue
                    # 检查该符号组合是否不在统计字典中
                    if current_combination not in combination_index_dict:
                        # 若不在,将该符号组合及其在有序词表中的索引添加到统计字典
                        combination_index_dict[current_combination] = ordered_vocab[current_combination]
                        # 将更新标志置为1,表示有更新
                        update_flag = 1
                # 判断是否没有更新
                if not update_flag:
                    # 若没有更新,跳出循环
                    continue

                # 将符号组合及其索引按索引升序排序,存储为元组列表
                sorted_combination_index = [(k, v) for k, v in
                                            sorted(combination_index_dict.items(), key=lambda item: item[1])]
                # 取出索引最小(优先级最高)的符号组合
                top_combination = sorted_combination_index[0][0]
                # 初始化一个空列表,用于存储更新后的字符拆分
                new_char_splits = []
                # 初始化索引为0
                i = 0
                # 遍历该单词的字符拆分列表
                while i < len(char_splits):
                    # 判断是否到达字符拆分列表的最后一个字符
                    if i + 1 >= len(char_splits):
                        # 若到达,将最后一个字符添加到更新后的列表
                        new_char_splits.append(char_splits[i])
                        # 索引加1
                        i += 1
                        # 继续下一次循环
                        continue
                    # 判断相邻两个字符是否组成优先级最高的符号组合
                    if top_combination == char_splits[i] + char_splits[i + 1]:
                        # 若组成,将该符号组合添加到更新后的列表
                        new_char_splits.append(top_combination)
                        # 索引加 2
                        i += 2
                    else:
                        # 若不组成,将当前字符添加到更新后的列表
                        new_char_splits.append(char_splits[i])
                        # 索引加 1
                        i += 1
                # 更新该单词的字符拆分列表
                char_splits = new_char_splits
            # 将更新后的字符拆分列表添加到分词结果列表
            tokenized_result += char_splits

        # 打印最终的分词结果
        print(f"分词结果:{tokenized_result}")


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
语料:
5 ['s', 'h', 'a', 'n', 'g', '_']
2 ['s', 'h', 'a', 'n', 'g', 'h', 'a', 'i', '_']
6 ['w', 'e', 'i', 'h', 'a', 'i', '_']
3 ['h', 'a', 'i', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w']
第1次循环
当前最常出现的前5个符号组合:[('ha', 18), ('ai', 11), ('i_', 8), ('sh', 7), ('an', 7)]
本次循环组合的符号为:ha

循环后的语料为:
5 ['s', 'ha', 'n', 'g', '_']
2 ['s', 'ha', 'n', 'g', 'ha', 'i', '_']
6 ['w', 'e', 'i', 'ha', 'i', '_']
3 ['ha', 'i', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w', 'ha']

***********************************
第2次循环
当前最常出现的前5个符号组合:[('hai', 11), ('i_', 8), ('sha', 7), ('han', 7), ('ng', 7)]
本次循环组合的符号为:hai

循环后的语料为:
5 ['s', 'ha', 'n', 'g', '_']
2 ['s', 'ha', 'n', 'g', 'hai', '_']
6 ['w', 'e', 'i', 'hai', '_']
3 ['hai', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w', 'ha', 'hai']

***********************************
第3次循环
当前最常出现的前5个符号组合:[('hai_', 8), ('sha', 7), ('han', 7), ('ng', 7), ('we', 6)]
本次循环组合的符号为:hai_

循环后的语料为:
5 ['s', 'ha', 'n', 'g', '_']
2 ['s', 'ha', 'n', 'g', 'hai_']
6 ['w', 'e', 'i', 'hai_']
3 ['hai', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w', 'ha', 'hai', 'hai_']

***********************************
第9次循环
当前最常出现的前5个符号组合:[('weihai_', 6), ('shang_', 5), ('ko', 5), ('ou', 5), ('u_', 5)]
本次循环组合的符号为:weihai_

循环后的语料为:
5 ['shang', '_']
2 ['shang', 'hai_']
6 ['weihai_']
3 ['hai', 'k', 'o', 'u', '_']
2 ['k', 'o', 'u', '_']
词表:['_', 'a', 'e', 'g', 'h', 'i', 'k', 'n', 'o', 's', 'u', 'w', 'ha', 'hai', 'hai_', 'sha', 'shan', 'shang', 'we', 'wei', 'weihai_']

***********************************
输入语句:shanghai weihai
分词结果:['shang', 'hai_', 'weihai_']

进程已结束,退出代码为 0

通过上述,我们看到,利用前期通过BPE算法构建并更新的词表对新输入句子进行分词的功能。首先,将排序好的字符词表转换为有序词表ordered_vocab,其键为符号,值为该符号在词表中的索引。

接着,定义待分词的输入语句 “shanghai weihai” 并打印,随后将其按空格拆分为单词列表。之后初始化一个空列表用于存储最终分词结果。对每个单词,添加结束符 “_” 并转为字符列表,借助update_flag循环尝试将相邻字符组合成词表中的符号组合。

在遍历字符拆分列表时,若组合在词表中且未统计过,就记录该组合及其索引并标记有更新。对统计的组合按索引升序排序,取索引最小的组合更新字符拆分列表,直至无法组合。最后将更新后的字符拆分列表添加到结果列表,并打印最终的分词结果。 

词规范化

自然语言处理中的词规范化,是对文本中的词汇进行一系列处理,以达到统一、标准的表示形式,从而提高后续自然语言处理任务的效率和准确性。

在原始文本中,同一个词可能有多种不同的书写形式,如大小写差异(“Apple”“apple”)、单复数形式(“book”“books”)、词形变化(“run”“running”“ran”)等,还可能存在拼写错误或不规范的表达。词规范化就是要处理这些情况,通过特定的算法和规则,将这些不同形式的词映射到一个标准形式。例如:

  • 进行大小写转换:把所有单词统一为大写或小写。
  • 进行词干提取:去除词的词缀,将单词还原为词干形式(如“running”还原为“run”)
  • 进行词形还原:根据词性等信息将单词转换为其词典中的基本形式(如“am”“is”“are”还原为“be”);
  • 纠正拼写错误:将错误拼写的单词修正为正确形式。

大小写转换

Python中,字符串类型提供了一些方法来进行大小写转换,常见的方法如下:

lower方法

lower()方法将字符串中的所有大写字母转换为小写字母,其他字符保持不变。

完整代码
# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    def text_normalization(self):
        # 定义原始句子,内容是关于正在研究自然语言处理领域以及对其的感受
        original_sentence = ("I'm currently researching the field of Natural Language Processing, and it's really "
                             "fascinating.")
        # 将原始句子中的所有大写字母转换为小写字母
        lower_original_sentence = original_sentence.lower()
        # 打印转换后的句子
        print(lower_original_sentence)

# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
i'm currently researching the field of natural language processing, and it's really fascinating.

进程已结束,退出代码为 0

我们看到,lower_original_sentence变量中的所有大写字母全部变成了小写字母

upper方法

upper()把字符串中的所有小写字母转换为大写字母,其余字符不受影响。

完整代码
# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 定义原始句子,内容是关于正在研究自然语言处理领域以及对其的感受
        original_sentence = ("I'm currently researching the field of Natural Language Processing, and it's really "
                             "fascinating.")
        # 将原始句子中的所有小写字母转换为大写字母
        lower_original_sentence = original_sentence.upper()
        # 打印转换后的句子
        print(lower_original_sentence)


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
I'M CURRENTLY RESEARCHING THE FIELD OF NATURAL LANGUAGE PROCESSING, AND IT'S REALLY FASCINATING.

进程已结束,退出代码为 0

我们看到,lower_original_sentence变量中的所有大写字母全部变成了小写字母Python中还又一些其他的方法,例如:capitalize()方法,title()方法,swapcase()方法。这些方法请大家自己去做测试,看看结果会是什么样的)。

经过词规范化处理后,文本中的词汇形式更加统一,能够减少词汇的多样性和歧义性,使计算机在进行文本分析、信息检索、机器翻译、情感分析等自然语言处理任务时,能够更准确地理解和处理文本,提高处理结果的质量和一致性。

词目还原

在自然语言处理中,词目还原是一项重要的基础操作,旨在将单词恢复到其在词典中呈现的基本形式,即词元。这一过程并非简单地去除词缀,而是综合考量单词的词性(诸如名词、动词、形容词等)以及上下文语境来精准确定其词元。

比如在英语里,“studies”(动词“study”的第三人称单数形式)、“studying”(现在分词形式)、“studied”(过去式和过去分词形式),经过词目还原后,都会回归到“study”这一基本形式;“geese”(“goose”的复数形式)会被还原为“goose”

匹配还原

在人类学习各类语言的进程里,能够借助词典去探寻单词的原始形态;与之相仿,计算机也能够凭借构建专门的词典,以此来达成词目还原的操作。我们用代码来演示一下:

完整代码
# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass 语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
                # 构建词元映射字典,记录单词及其对应的词元
        word_to_lemma_mapping = {'studied': 'study', 'learned': 'learn', 'achieved': 'achieve', 'passed': 'pass', 'tests': 'test', 'goals': 'goal', 'skills': 'skill', 'improved': 'improve', 'discovered': 'discover'}

        # 定义需要进行处理的原始句子
        original_sentence = ("He studied hard for his tests and learned a lot of new skills. He achieved all his "
                             "goals, passed the exams, and improved his grades. He also discovered a new passion "
                             "for science.")
        # 把原始句子按照空格拆分成单个单词,形成列表
        single_word_list = original_sentence.split(' ')
        # 打印词目还原操作之前的单词列表
        print(f'词目还原前:{single_word_list}')
        # 初始化一个空列表,用来存放经过词目还原后的单词
        lemma_reduced_word_list = []
        # 对拆分后的每个单词进行遍历
        for single_word in single_word_list:
            # 检查当前遍历到的单词是否存在于词元映射字典中
            if single_word in word_to_lemma_mapping:
                # 若存在,将该单词对应的词元添加到存放还原后单词的列表中
                lemma_reduced_word_list.append(word_to_lemma_mapping[single_word])
            else:
                # 若不存在,直接将该单词添加到存放还原后单词的列表中
                lemma_reduced_word_list.append(single_word)

        # 打印经过词目还原操作之后的单词列表
        print(f'词目还原后:{lemma_reduced_word_list}')


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
词目还原前:['He', 'studied', 'hard', 'for', 'his', 'tests', 'and', 'learned', 'a', 'lot', 'of', 'new', 'skills.', 'He', 'achieved', 'all', 'his', 'goals,', 'passed', 'the', 'exams,', 'and', 'improved', 'his', 'grades.', 'He', 'also', 'discovered', 'a', 'new', 'passion', 'for', 'science.']
词目还原后:['He', 'study', 'hard', 'for', 'his', 'test', 'and', 'learn', 'a', 'lot', 'of', 'new', 'skills.', 'He', 'achieve', 'all', 'his', 'goals,', 'pass', 'the', 'exams,', 'and', 'improve', 'his', 'grades.', 'He', 'also', 'discover', 'a', 'new', 'passion', 'for', 'science.']

进程已结束,退出代码为 0
NLTK还原

此外,我们还能够借助自然语言处理工具包NLTK中所内置的词典资源,以此来实现词目还原的操作。我们用代码来演示下:

完整代码
# 导入自然语言处理工具包nltk
import nltk
# 从nltk的tokenize模块导入word_tokenize函数,用于将文本分词
from nltk.tokenize import word_tokenize
# 从nltk的stem模块导入WordNetLemmatizer类,用于词形还原
from nltk.stem import WordNetLemmatizer
# 从nltk的corpus模块导入wordnet,提供词法信息
from nltk.corpus import wordnet


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        # 下载分词所需的punkt_tab资源包,quiet=True表示安静模式,不显示下载详细信息
        nltk.download('punkt_tab', quiet=True)
        # 下载wordnet资源包,用于词形还原等操作,quiet=True表示安静模式
        nltk.download('wordnet', quiet=True)

        # 创建一个WordNetLemmatizer类的实例,用于后续的词形还原操作
        word_lemmatizer = WordNetLemmatizer()
        # 定义一个待进行词目还原处理的原始句子
        original_sentence = ("He studied hard for his tests and learned a lot of new skills. He achieved all his "
                             "goals, passed the exams, and improved his grades. He also discovered a new passion "
                             "for science.")
        # 使用word_tokenize函数对原始句子进行分词,得到单词列表
        tokenized_word_list = word_tokenize(original_sentence)
        # 打印词目还原操作前的单词列表
        print(f'词目还原前:{tokenized_word_list}')

        # 初始化一个空列表,用于存储词目还原后的单词
        lemmatized_result_list = []
        # 遍历分词后得到的单词列表中的每个单词
        for single_word in tokenized_word_list:
            # 对当前单词按动词词性进行词目还原,并添加到结果列表中
            lemmatized_result_list.append(word_lemmatizer.lemmatize(single_word, wordnet.VERB))
        # 打印词目还原操作后的单词列表
        print(f'词目还原后:{lemmatized_result_list}')


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()
运行结果
词目还原前:['He', 'studied', 'hard', 'for', 'his', 'tests', 'and', 'learned', 'a', 'lot', 'of', 'new', 'skills', '.', 'He', 'achieved', 'all', 'his', 'goals', ',', 'passed', 'the', 'exams', ',', 'and', 'improved', 'his', 'grades', '.', 'He', 'also', 'discovered', 'a', 'new', 'passion', 'for', 'science', '.']
词目还原后:['He', 'study', 'hard', 'for', 'his', 'test', 'and', 'learn', 'a', 'lot', 'of', 'new', 'skills', '.', 'He', 'achieve', 'all', 'his', 'goals', ',', 'pass', 'the', 'exams', ',', 'and', 'improve', 'his', 'grade', '.', 'He', 'also', 'discover', 'a', 'new', 'passion', 'for', 'science', '.']

进程已结束,退出代码为 0

综上,我们看到,在自然语言处理应用中,词目还原是不可或缺的。它能够提高特征的质量和稳定性,减少因词汇形式差异带来的噪声干扰,使模型能够更专注于语义信息,进而提升模型的性能和泛化能力,让自然语言处理系统在各种任务中表现得更加出色。 

分句

在自然语言处理中,分句(Sentence Segmentation)是指将一段连续的文本分割成一个个独立的句子,它是自然语言处理的基础任务之一,在文本处理流程中起着关键的前置作用。具体来说,分句有以下特点和作用:

  • 基于标点符号:在许多语言中,最直观的分句依据是标点符号,如句号(.)、问号(?)、感叹号(!)等。
  • 考虑上下文和语言规则:为了更准确地分句,自然语言处理系统需要考虑上下文信息和语言的语法、语义规则。
  • 跨语言差异:不同的语言在标点符号的使用和句子结构上存在差异,这使得分句在不同语言中面临不同的挑战。
  • 应用场景广泛:分句是许多自然语言处理任务的基础,如文本摘要、机器翻译、信息检索、情感分析等。

实现分句的方法通常包括基于规则的方法(利用标点符号和语言规则)、基于统计的方法(通过机器学习算法从大量标注数据中学习分句模式)以及两者结合的方法。我们用代码来演示一下:

完整代码

# 从NLTK库导入regexp_tokenize函数,用于基于正则表达式对文本进行灵活分词
from nltk import regexp_tokenize


# 定义一个名为NLPTextNormalization的类,用于自然语言处理中的文本规范化操作
class NLPTextNormalization:
    # 类的构造方法,目前为空,不执行任何初始化操作
    def __init__(self):
        # pass语句占位,不进行实际操作
        pass

    # 定义一个实例方法text_normalization用于文本规范化处理
    def text_normalization(self):
        sentence_spliter = set([".", "?", '!', '...'])

        # 定义原始句子,用于后续使用正则表达式进行分词处理
        original_sentence = ("Did you pay €6.99 on researchgate.net for access to that full-text paper? "
                             "No, I got it through our university library! It's really convenient...")
        # 定义正则表达式模式,可匹配普通单词、含连字符或单引号的复合单词以及以非空白字符开头的字符串
        pattern = r"\w+(?:[-']\w+)*|\S\w*"
        # 定义用于匹配网址的正则表达式模式,可匹配如example.com等形式的网址
        web_pattern = r"(?:\w+\.)+\w+(?:\.)*"
        # 定义用于匹配价格符号相关内容的正则表达式模式,可匹配如$12.34、12%等形式
        price_symbol_pattern = r"\$?\d+(?:\.\d+)?%?"
        # 定义用于匹配省略号的正则表达式模式,可精准匹配文本中的省略号
        ellipsis_pattern = r"\.\.\."
        # 将省略号模式、价格符号模式、网址模式和原模式通过逻辑或连接,形成新的匹配模式
        pattern = ellipsis_pattern + r"|" + price_symbol_pattern + r"|" + web_pattern + r"|" + pattern
        # 使用regexp_tokenize函数,依据指定的正则表达式模式对句子进行分词,并将结果存储在tokens变量中
        tokens = regexp_tokenize(original_sentence, pattern)

        # 初始化存储分句结果的列表
        segmented_sentence_list = []
        # 初始化存储每个句子起始索引的列表,初始值为0
        sentence_start_index_list = [0]
        # 遍历分词后的标记列表,获取标记索引和标记内容
        for token_index, single_token in enumerate(tokens):
            # 判断当前标记是否为句子分隔符
            if single_token in sentence_spliter:
                # 若为分隔符,将从当前句子起始索引到当前标记的所有标记作为一个句子添加到结果列表
                segmented_sentence_list.append(tokens[sentence_start_index_list[-1]:token_index + 1])
                # 更新下一个句子的起始索引
                sentence_start_index_list.append(token_index + 1)
        # 检查是否还有剩余标记未组成句子
        if sentence_start_index_list[-1] != len(tokens):
            # 若有剩余标记,将其作为一个句子添加到结果列表
            segmented_sentence_list.append(tokens[sentence_start_index_list[-1]:])
        # 打印分句结果提示信息
        print(f"分句结果:")
        # 遍历分句结果列表
        for segmented_sentence in segmented_sentence_list:
            # 打印每个分句的内容
            print(segmented_sentence)


# 程序入口判断,如果当前脚本作为主程序运行
if __name__ == "__main__":
    # 创建NLPTextNormalization类的一个实例
    nlp_text_normalization = NLPTextNormalization()
    # 调用实例的text_normalization方法进行文本规范化处理
    nlp_text_normalization.text_normalization()

运行结果

分句结果:
['Did', 'you', 'pay', '€6', '.99', 'on', 'researchgate.net', 'for', 'access', 'to', 'that', 'full-text', 'paper', '?']
['No', ',', 'I', 'got', 'it', 'through', 'our', 'university', 'library', '!']
["It's", 'really', 'convenient', '...']

进程已结束,退出代码为 0

从上我们看到,分句具有多方面的重要意义。从文本理解角度看,将连续的文本分割成一个个独立的句子,能够帮助计算机更清晰地把握文本的结构和语义层次。对于文本生成任务,如机器翻译、文本摘要等,分句是基础且关键的步骤。

总之,分句作为自然语言处理的基础操作,为后续的各种任务提供了必要的前提和支持,对提升自然语言处理系统的整体效果具有不可忽视的作用。 

结束

好了。以上就是本次分享的全部内容了。大家是否理解并掌握了自然语言处理中的文本规范化本规范化作为自然语言处理的基础环节,对提升语义理解、信息检索等任务的效果至关重要。希望大家能在实际操作中多加运用,不断深化对这一概念的理解和掌握。

我相信大家对文本规范化已有了一定的认识。博主希望大家通过本次分享的知识,将其应用到实际项目中,通过实践来巩固知识。期待大家在后续的学习和工作中,能够熟练运用文本规范化的技巧,为自然语言处理任务的顺利开展奠定坚实基础。

那么本次分享就到这里了。最后,博主还是那句话:请大家多去大胆的尝试和使用,成功总是在不断的失败中试验出来的,敢于尝试就已经成功了一半。如果大家对博主分享的内容感兴趣或有帮助,请点赞和关注。大家的点赞和关注是博主持续分享的动力🤭,博主也希望让更多的人学习到新的知识。


http://www.kler.cn/a/564848.html

相关文章:

  • seacmsv9管理员账号+密码注入
  • 解锁自动驾驶的关键技术:Digital Isolator 如何确保高速、安全与可靠性?
  • 校园快递平台系统(小程序论文源码调试讲解)
  • 泛微e-office sms_page.php sql注入漏洞复现(CNVD-2022-1)(附脚本)
  • Element Plus中el-select选择器的下拉选项列表的样式设置
  • 服务器宕机了怎么办?
  • 20250226-代码笔记04-class CVRP_Encoder AND class EncoderLayer
  • flutter项目构建常见问题
  • iptables核心和简例[NET]
  • 客户端进程突然结束,服务端read是什么行为?
  • STM32基于HAL库(CUBEMX)MPU6050 DMP的移植(新手一看必会)
  • 蓝桥杯备考1
  • 51c自动驾驶~合集52
  • Oracle 查询表空间使用情况及收缩数据文件
  • 基于Spring Boot的乡村养老服务管理系统设计与实现(LW+源码+讲解)
  • 谷云科技iPaaS×DeepSeek:构建企业智能集成的核心底座
  • 如何使用豆包AI来快速提升编程能力?
  • netcore入门案例:netcore api连接mysql的完整记事本接口示例
  • 纯c#字体处理库(FontParser) -- 轻量、极速、跨平台、具有字体子集化功能
  • 火语言RPA--Word打开文档