【深度学习】sentencepiece工具之BPE训练使用

本文介绍BPE(Byte Pair Encoding)在机器翻译领域的应用,详细解释了BPE的工作原理,即通过迭代合并字符串中出现频率最高的子串来简化文本。文章提供了使用sentencepiece库进行BPE模型训练的代码示例,包括数据预处理、模型训练和数据分割的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么要使用BPE,BPE是什么

BPE:迭代的将字符串里出现频率最高的子串进行合并
训练过程
在这里插入图片描述
在这里插入图片描述

使用教程

代码使用的语料在这里

# -*- coding: utf-8 -*-
#/usr/bin/python3


import os
import errno
import sentencepiece as spm
import re
import logging

logging.basicConfig(level=logging.INFO)

def prepro(hp):
    print("# Check if raw files exist")
    train1 = "iwslt2016/de-en/train.tags.de-en.de"
    train2 = "iwslt2016/de-en/train.tags.de-en.en"
    eval1 = "iwslt2016/de-en/IWSLT16.TED.tst2013.de-en.de.xml"
    eval2 = "iwslt2016/de-en/IWSLT16.TED.tst2013.de-en.en.xml"
    test1 = "iwslt2016/de-en/IWSLT16.TED.tst2014.de-en.de.xml"
    test2 = "iwslt2016/de-en/IWSLT16.TED.tst2014.de-en.en.xml"
    for f in (train1, train2, eval1, eval2, test1, test2):
        if not os.path.isfile(f):
            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), f)

    print("# Preprocessing")
    # train
    _prepro = lambda x:  [line.strip() for line in open(x, mode='r',encoding="utf-8").read().split("\n") \
                      if not line.startswith("<")]
    prepro_train1, prepro_train2 = _prepro(train1), _prepro(train2)
    assert len(prepro_train1)==len(prepro_train2), "Check if train source and target files match."

    # eval
    _prepro = lambda x: [re.sub("<[^>]+>", "", line).strip() \
                     for line in open(x, mode='r',encoding="utf-8").read().split("\n") \
                     if line.startswith("<seg id")]
    prepro_eval1, prepro_eval2 = _prepro(eval1), _prepro(eval2)
    assert len(prepro_eval1) == len(prepro_eval2), "Check if eval source and target files match."

    # test
    prepro_test1, prepro_test2 = _prepro(test1), _prepro(test2)
    assert len(prepro_test1) == len(prepro_test2), "Check if test source and target files match."

    print("Let's see how preprocessed data look like")
    print("prepro_train1:", prepro_train1[0])
    print("prepro_train2:", prepro_train2[0])
    print("prepro_eval1:", prepro_eval1[0])
    print("prepro_eval2:", prepro_eval2[0])
    print("prepro_test1:", prepro_test1[0])
    print("prepro_test2:", prepro_test2[0])

    print("# write preprocessed files to disk")
    os.makedirs("iwslt2016/prepro", exist_ok=True)
    def _write(sents, fname):
        with open(fname, mode='w',encoding="utf-8") as fout:
            fout.write("\n".join(sents))

    _write(prepro_train1, "iwslt2016/prepro/train.de")
    _write(prepro_train2, "iwslt2016/prepro/train.en")
    _write(prepro_train1+prepro_train2, "iwslt2016/prepro/train")
    _write(prepro_eval1, "iwslt2016/prepro/eval.de")
    _write(prepro_eval2, "iwslt2016/prepro/eval.en")
    _write(prepro_test1, "iwslt2016/prepro/test.de")
    _write(prepro_test2, "iwslt2016/prepro/test.en")

    print("# Train a joint BPE model with sentencepiece")
    os.makedirs("iwslt2016/segmented", exist_ok=True)
    train = '--input=iwslt2016/prepro/train --pad_id=0 --unk_id=1 \
             --bos_id=2 --eos_id=3\
             --model_prefix=iwslt2016/segmented/bpe --vocab_size={} \
             --model_type=bpe'.format(hp.vocab_size)
    spm.SentencePieceTrainer.Train(train)

    print("# Load trained bpe model")
    sp = spm.SentencePieceProcessor()
    sp.Load("iwslt2016/segmented/bpe.model")

    print("# Segment")
    def _segment_and_write(sents, fname):
        with open(fname,mode= "w",encoding="utf-8") as fout:
            for sent in sents:
                pieces = sp.EncodeAsPieces(sent)
                fout.write(" ".join(pieces) + "\n")

    _segment_and_write(prepro_train1, "iwslt2016/segmented/train.de.bpe")
    _segment_and_write(prepro_train2, "iwslt2016/segmented/train.en.bpe")
    _segment_and_write(prepro_eval1, "iwslt2016/segmented/eval.de.bpe")
    _segment_and_write(prepro_eval2, "iwslt2016/segmented/eval.en.bpe")
    _segment_and_write(prepro_test1, "iwslt2016/segmented/test.de.bpe")

    print("Let's see how segmented data look like")
    print("train1:", open("iwslt2016/segmented/train.de.bpe",mode='r',encoding="utf-8").readline())
    print("train2:", open("iwslt2016/segmented/train.en.bpe", mode='r',encoding="utf-8").readline())
    print("eval1:", open("iwslt2016/segmented/eval.de.bpe", mode='r',encoding="utf-8").readline())
    print("eval2:", open("iwslt2016/segmented/eval.en.bpe", mode='r',encoding="utf-8").readline())
    print("test1:", open("iwslt2016/segmented/test.de.bpe", mode='r',encoding="utf-8").readline())

if __name__ == '__main__':
    hparams = Hparams()
    parser = hparams.parser
    hp = parser.parse_args()
    prepro(hp)
    print("Done")
<think>好的,我现在需要向用户介绍SentencePiece的概念。首先,我得回忆一下自己对这个概念的理解。SentencePiece是一个用于文本分词和子词处理的工具,常用于自然语言处理任务中。它由谷歌开发,支持BPE和Unigram等算法。 用户可能已经了解传统的分词方法,比如基于空格的分词或者更复杂的模型,但SentencePiece的不同之处在于它不依赖于预先定义的词汇或分词规则,而是直接从数据中学习分词模型。这一点很重要,应该提到。 接下来,我需要解释SentencePiece的核心特点。比如,它处理的是原始文本,不需要预处理(如去除空格或特殊字符),并且可以将空格也作为字符处理,这对于某些语言(如中文)可能不太一样,但用户可能关心的是其通用性。 还要提到它支持子词分割,这对于处理未登录词和稀有词很有帮助,能提升模型的泛化能力。比如,像"unhappiness"可能被分成"un", "happiness"或者更小的单元,这样模型可以更好地处理未见过的词汇。 另外,SentencePiece训练过程是数据驱动的,自动确定词汇表大小和子词单元,用户只需指定词汇表的大小,比如16k或32k。这对于用户来说是一个优势,因为不需要手动设计词汇表。 可能用户还关心应用场景,比如在机器翻译、文本生成中的使用,特别是在处理多语言数据时的优势,因为SentencePiece可以统一处理不同语言的分词,减少预处理步骤。 需要确保提到它如何处理空格,比如用特定的符号(如“▁”)代替空格,这样在解码时可以恢复原始文本的结构。这部分可能容易让人困惑,所以要简明扼要地解释。 另外,对比其他分词工具,比如WordPiece(BERT中使用),SentencePiece的优势在于不需要预分词,直接处理原始句子,这在处理无空格语言(如中文、日文)时可能有所不同?或者可能用户这里存在误解,因为中文本身分词方式不同,但SentencePiece其实可以处理任何语言,因为它基于统计模型,不需要依赖空格。 可能还需要提到它的实现方式,比如将文本转换为Unicode字符序列,再进行子词合并,这样能更灵活地处理各种字符。 最后,总结SentencePiece的主要优点:数据驱动、跨语言支持、子词平衡、空格保留等。确保语言通俗易懂,避免过多技术术语,但又要准确。 现在需要检查是否有遗漏的关键点,比如支持的具体算法(BPE和Unigram),训练过程,如何处理大小写,是否支持控制符号等。这些可能对高级用户重要,但如果是介绍性质的内容,可以简要带过。 确保结构清晰,分点或分段说明,让用户容易理解。可能需要先定义,再讲原理,然后特点,最后应用场景。</think>SentencePiece 是一种**跨语言、数据驱动的分词算法库**,主要用于将文本拆分为子词单元(Subword Units),被广泛应用于BERT、T5等预训练模型。其核心特点如下: --- ### 核心概念 1. **不依赖预分词** 传统分词工具(如空格分词)需要预先定义规则,而SentencePiece直接处理**原始文本**(包括空格、标点等),将空格视为普通字符(用特殊符号`▁`表示),因此特别适合中文、日文等无空格语言。 2. **子词拆分** 通过统计学习将罕见词拆分为更小的子词(如“机器学习” → `机器`,`学习`),提升模型对未登录词(OOV)的处理能力。例如: - 英文词 `unhappiness` → `un`, `happiness`(或更细粒度的子词) - 中文词 “深度学习” → `深`, `度`, `学习` 3. **支持多算法** - **BPE (Byte Pair Encoding)**:通过合并高频字符对构建词汇表。 - **Unigram Language Model**:基于概率模型保留最佳子词组合。 --- ### 关键优势 1. **统一处理多语言** 无需为不同语言设计独立的分词规则,尤其适合多语种混合的文本(如翻译语料库)。 2. **数据驱动** 词汇表完全从训练数据中学习,用户只需指定词汇表大小(如32k),无需人工干预。 3. **保留空格与格式** 将空格编码为`▁`符号,解码时可还原原始文本结构,避免信息丢失。 4. **平衡词表效率** 高频词保留完整,低频词拆分为子词,在模型效果与计算效率间取得平衡。 --- ### 典型应用 1. **预训练模型分词** BERT、T5等模型用SentencePiece生成子词词表,减少OOV问题。 2. **处理罕见词与专有名词** 例如将“ChatGPT”拆分为`Chat`, `G`, `PT`,使模型能推理未见过的组合。 3. **统一多语言分词** 在机器翻译中,用单一模型处理中、英、日等混合输入。 --- ### 示例代码(训练过程) ```python import sentencepiece as spm # 训练模型 spm.SentencePieceTrainer.train( input="data.txt", model_prefix="sp_model", vocab_size=32000, model_type="bpe" # 或 "unigram" ) # 使用模型分词 sp = spm.SentencePieceProcessor(model_file="sp_model.model") print(sp.encode("自然语言处理", out_type=str)) # 输出:['▁', '自然', '语言', '处理'] ``` --- 总结:**SentencePiece通过数据驱动的方式,将文本转化为灵活的子词单元,解决了传统分词对规则和预处理的依赖,成为NLP任务中的基础工具。**
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值