searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

深入解析 BERT 的 WordPiece 子词分割机制:理论与实践

2025-01-07 09:29:17
6
0

笔者看书时,看到书中提到的这个 WordPiece,只是短短的一句带过。

于是查了些资料,做了深入了解。

BERT 使用的 WordPiece 词元化方法是一种基于子词分割的技术,其目的是有效地解决自然语言处理中的词汇表问题,同时提高模型的泛化能力。本文将详细阐述 WordPiece 的工作原理,并通过实例与代码进行深度解析。

WordPiece 的背景与目标

在自然语言处理中,词汇表的大小直接影响模型的性能与效率。传统的词级别词元化方法存在两个主要问题:

  1. 词汇表过大:直接以单词为单位构建词汇表可能导致存储和计算成本显著增加。
  2. 词汇覆盖率不足:由于语言的多样性和词形变化,训练过程中难以覆盖所有可能的单词,导致模型遇到未登录词时表现不佳。

WordPiece 通过将单词进一步分解为子词单元,从而在保持词汇表规模适中的同时,显著提高了词汇覆盖率。

WordPiece 的核心思想

WordPiece 基于子词单元构建词汇表。其基本假设是:频繁共现的字符序列更有可能构成有意义的单元。通过频率统计和合并规则,WordPiece 逐步生成最优的子词分割方案。

WordPiece 的分割过程可以总结为以下几个步骤:

  1. 初始化词汇表:将所有字符(Unicode)作为初始词汇表的基本单元。
  2. 统计字符对:对训练语料中的每个单词,统计相邻字符对的出现频率。
  3. 合并最频繁字符对:将最频繁的字符对作为一个新单元加入词汇表,并更新训练语料中的分割方式。
  4. 重复上述过程:直到词汇表达到预设的大小或频率不再增加显著。

这种方法确保了高频单词能够保留整体单元,而低频单词则被分解为子词甚至单字符,从而提高了模型对未登录词的处理能力。

具体案例解析

为说明 WordPiece 的工作机制,以下是一个实际示例,展示如何从零构建一个简单的词汇表。

假设的语料

假设训练语料包含以下单词:

["low", "lower", "lowest", "new", "newer", "newest"]

目标是利用 WordPiece 构建一个子词词汇表。

初始化词汇表

初始词汇表包含所有可能的字符:

["l", "o", "w", "e", "r", "n", "s", "t"]

第一步:统计字符对

在语料中统计每个单词的字符对(包括空格作为分隔符):

  • "low": l-o, o-w
  • "lower": l-o, o-w, w-e, e-r
  • "lowest": l-o, o-w, w-e, e-s, s-t
  • "new": n-e, e-w
  • "newer": n-e, e-w, w-e, e-r
  • "newest": n-e, e-w, w-e, e-s, s-t

字符对的频率统计如下:

字符对 频率
l-o 3
o-w 3
w-e 4
e-r 2
e-s 2
s-t 2
n-e 3
e-w 3

第二步:合并最频繁字符对

找到频率最高的字符对 w-e,将其作为新单元 we 加入词汇表,同时更新语料:

更新后的单词:

["low", "lower", "lowest", "new", "newer", "newest"]

分割形式变为:

  • "low": l-o, o-w
  • "lower": l-o, o-w, we-r
  • "lowest": l-o, o-w, we-s, s-t
  • "new": n-e, e-w
  • "newer": n-e, e-w, we-r
  • "newest": n-e, e-w, we-s, s-t

更新后的字符对频率:

字符对 频率
l-o 3
o-w 3
we-r 2
e-r 0
we-s 2
s-t 2
n-e 3
e-w 3

重复上述过程,逐步生成最终的词汇表。

Python 实现代码

以下代码展示了 WordPiece 的简单实现:

from collections import Counter, defaultdict

# 初始化语料
corpus = ["low", "lower", "lowest", "new", "newer", "newest"]

# 将语料分割为字符
def split_to_chars(word):
    return list(word) + ["#"]  # 添加特殊字符标记子词结尾

# 构建初始语料
split_corpus = [split_to_chars(word) for word in corpus]

# 统计字符对频率
def get_pair_statistics(corpus):
    pairs = Counter()
    for word in corpus:
        for i in range(len(word) - 1):
            pairs[(word[i], word[i + 1])] += 1
    return pairs

# 合并字符对
def merge_pair(pair, corpus):
    new_corpus = []
    for word in corpus:
        new_word = []
        i = 0
        while i < len(word):
            if i < len(word) - 1 and (word[i], word[i + 1]) == pair:
                new_word.append(word[i] + word[i + 1])
                i += 2
            else:
                new_word.append(word[i])
                i += 1
        new_corpus.append(new_word)
    return new_corpus

# 构建词汇表
def build_vocab(corpus, vocab_size):
    vocab = set()
    for word in corpus:
        vocab.update(word)
    while len(vocab) < vocab_size:
        pairs = get_pair_statistics(corpus)
        if not pairs:
            break
        best_pair = max(pairs, key=pairs.get)
        corpus = merge_pair(best_pair, corpus)
        vocab.update(["".join(best_pair)])
    return vocab

# 构建目标词汇表
vocab = build_vocab(split_corpus, vocab_size=20)
print("Generated Vocabulary:", vocab)

运行上述代码可以观察到 WordPiece 词汇表的逐步生成过程。

真实世界的应用

在 BERT 模型中,WordPiece 不仅优化了词汇表,还极大提高了模型对多语言任务的适应能力。例如,在处理英文和中文混合的句子时,WordPiece 能够自动将未登录词分解为熟悉的子词,从而降低 OOV(Out-of-Vocabulary)问题的影响。

示例分析

输入句子:

"I love natural language processing."

WordPiece 分割结果:

["I", "love", "natural", "lan", "##guage", "pro", "##cessing"]

这种分割方式保留了高频词 "I" 和 "love" 的整体性,同时将低频词 "language" 和 "processing" 分解为子词单元,确保词汇表大小适中。

总结

WordPiece 是 BERT 词元化中的关键技术,其通过高效的子词分割方法,在保证词汇表规模适中的前提下,显著提升了模型的泛化能力。通过分析其分割过程与实现细节,我们可以更深入地理解其工作原理和应用场景。这种方法不仅适用于 BERT,还在其他 NLP 模型中得到了广泛应用。

0条评论
0 / 1000
老程序员
1156文章数
2粉丝数
老程序员
1156 文章 | 2 粉丝
原创

深入解析 BERT 的 WordPiece 子词分割机制:理论与实践

2025-01-07 09:29:17
6
0

笔者看书时,看到书中提到的这个 WordPiece,只是短短的一句带过。

于是查了些资料,做了深入了解。

BERT 使用的 WordPiece 词元化方法是一种基于子词分割的技术,其目的是有效地解决自然语言处理中的词汇表问题,同时提高模型的泛化能力。本文将详细阐述 WordPiece 的工作原理,并通过实例与代码进行深度解析。

WordPiece 的背景与目标

在自然语言处理中,词汇表的大小直接影响模型的性能与效率。传统的词级别词元化方法存在两个主要问题:

  1. 词汇表过大:直接以单词为单位构建词汇表可能导致存储和计算成本显著增加。
  2. 词汇覆盖率不足:由于语言的多样性和词形变化,训练过程中难以覆盖所有可能的单词,导致模型遇到未登录词时表现不佳。

WordPiece 通过将单词进一步分解为子词单元,从而在保持词汇表规模适中的同时,显著提高了词汇覆盖率。

WordPiece 的核心思想

WordPiece 基于子词单元构建词汇表。其基本假设是:频繁共现的字符序列更有可能构成有意义的单元。通过频率统计和合并规则,WordPiece 逐步生成最优的子词分割方案。

WordPiece 的分割过程可以总结为以下几个步骤:

  1. 初始化词汇表:将所有字符(Unicode)作为初始词汇表的基本单元。
  2. 统计字符对:对训练语料中的每个单词,统计相邻字符对的出现频率。
  3. 合并最频繁字符对:将最频繁的字符对作为一个新单元加入词汇表,并更新训练语料中的分割方式。
  4. 重复上述过程:直到词汇表达到预设的大小或频率不再增加显著。

这种方法确保了高频单词能够保留整体单元,而低频单词则被分解为子词甚至单字符,从而提高了模型对未登录词的处理能力。

具体案例解析

为说明 WordPiece 的工作机制,以下是一个实际示例,展示如何从零构建一个简单的词汇表。

假设的语料

假设训练语料包含以下单词:

["low", "lower", "lowest", "new", "newer", "newest"]

目标是利用 WordPiece 构建一个子词词汇表。

初始化词汇表

初始词汇表包含所有可能的字符:

["l", "o", "w", "e", "r", "n", "s", "t"]

第一步:统计字符对

在语料中统计每个单词的字符对(包括空格作为分隔符):

  • "low": l-o, o-w
  • "lower": l-o, o-w, w-e, e-r
  • "lowest": l-o, o-w, w-e, e-s, s-t
  • "new": n-e, e-w
  • "newer": n-e, e-w, w-e, e-r
  • "newest": n-e, e-w, w-e, e-s, s-t

字符对的频率统计如下:

字符对 频率
l-o 3
o-w 3
w-e 4
e-r 2
e-s 2
s-t 2
n-e 3
e-w 3

第二步:合并最频繁字符对

找到频率最高的字符对 w-e,将其作为新单元 we 加入词汇表,同时更新语料:

更新后的单词:

["low", "lower", "lowest", "new", "newer", "newest"]

分割形式变为:

  • "low": l-o, o-w
  • "lower": l-o, o-w, we-r
  • "lowest": l-o, o-w, we-s, s-t
  • "new": n-e, e-w
  • "newer": n-e, e-w, we-r
  • "newest": n-e, e-w, we-s, s-t

更新后的字符对频率:

字符对 频率
l-o 3
o-w 3
we-r 2
e-r 0
we-s 2
s-t 2
n-e 3
e-w 3

重复上述过程,逐步生成最终的词汇表。

Python 实现代码

以下代码展示了 WordPiece 的简单实现:

from collections import Counter, defaultdict

# 初始化语料
corpus = ["low", "lower", "lowest", "new", "newer", "newest"]

# 将语料分割为字符
def split_to_chars(word):
    return list(word) + ["#"]  # 添加特殊字符标记子词结尾

# 构建初始语料
split_corpus = [split_to_chars(word) for word in corpus]

# 统计字符对频率
def get_pair_statistics(corpus):
    pairs = Counter()
    for word in corpus:
        for i in range(len(word) - 1):
            pairs[(word[i], word[i + 1])] += 1
    return pairs

# 合并字符对
def merge_pair(pair, corpus):
    new_corpus = []
    for word in corpus:
        new_word = []
        i = 0
        while i < len(word):
            if i < len(word) - 1 and (word[i], word[i + 1]) == pair:
                new_word.append(word[i] + word[i + 1])
                i += 2
            else:
                new_word.append(word[i])
                i += 1
        new_corpus.append(new_word)
    return new_corpus

# 构建词汇表
def build_vocab(corpus, vocab_size):
    vocab = set()
    for word in corpus:
        vocab.update(word)
    while len(vocab) < vocab_size:
        pairs = get_pair_statistics(corpus)
        if not pairs:
            break
        best_pair = max(pairs, key=pairs.get)
        corpus = merge_pair(best_pair, corpus)
        vocab.update(["".join(best_pair)])
    return vocab

# 构建目标词汇表
vocab = build_vocab(split_corpus, vocab_size=20)
print("Generated Vocabulary:", vocab)

运行上述代码可以观察到 WordPiece 词汇表的逐步生成过程。

真实世界的应用

在 BERT 模型中,WordPiece 不仅优化了词汇表,还极大提高了模型对多语言任务的适应能力。例如,在处理英文和中文混合的句子时,WordPiece 能够自动将未登录词分解为熟悉的子词,从而降低 OOV(Out-of-Vocabulary)问题的影响。

示例分析

输入句子:

"I love natural language processing."

WordPiece 分割结果:

["I", "love", "natural", "lan", "##guage", "pro", "##cessing"]

这种分割方式保留了高频词 "I" 和 "love" 的整体性,同时将低频词 "language" 和 "processing" 分解为子词单元,确保词汇表大小适中。

总结

WordPiece 是 BERT 词元化中的关键技术,其通过高效的子词分割方法,在保证词汇表规模适中的前提下,显著提升了模型的泛化能力。通过分析其分割过程与实现细节,我们可以更深入地理解其工作原理和应用场景。这种方法不仅适用于 BERT,还在其他 NLP 模型中得到了广泛应用。

文章来自个人专栏
SAP 技术
1156 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0