NLP基础及其代码-tokenizer

news2024/11/25 8:20:08

基础知识

NLP-分词器:SentencePiece【参考Chinese-LLaMA-Alpaca在通用中文语料上训练的20K中文词表并与原版LLaMA模型的32K词表进行合并的代码】_sentencepiece 中文训练-CSDN博客

【OpenLLM 008】大模型基础组件之分词器-万字长文全面解读LLM中的分词算法与分词器(tokenization & tokenizers):BPE/WordPiece/ULM & beyond - 知乎 (zhihu.com)

BPE

Byte Pair Encoding

步骤:

1.语料库
2.确定词表大小
3.为每个单词添加分割符
4.从字母开始迭代合并出现频率高的字符串

"""
2024/9/9
bpe.py
wang_yi
"""
import re, collections

def get_vocab(filename):
    vocab = collections.defaultdict(int)  # 对于 defaultdict(int) 创建的字典来说,任何不存在的键在访问时都会自动被赋值为 0
    with open(filename, 'r', encoding='utf-8') as fhand:
        for line in fhand:
            words = line.strip().split()
            for word in words:
                vocab[' '.join(list(word)) + ' </w>'] += 1
    return vocab

def get_stats(vocab):
    pairs = collections.defaultdict(int)
    for word, freq in vocab.items():
        symbols = word.split()
        for i in range(len(symbols)-1):
            pairs[symbols[i],symbols[i+1]] += freq
    return pairs

def merge_vocab(pair, v_in):
    v_out = {}
    bigram = re.escape(' '.join(pair))  # re.escape确保特殊字符被转义,以便在正则表达式中按照字面意义进行匹配
    p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')  # ?!负向前瞻断言,表示匹配位置前(<)或后面不是某种模式。\S: 表示任何非空白字符
    for word in v_in:
        w_out = p.sub(''.join(pair), word)  # 替换结合字符对,保留原来字符串中的其他部分
        v_out[w_out] = v_in[word]  # 将处理后的字符串 w_out 作为 v_out 字典的键,并将 v_in 字典中对应 word 的值赋给 v_out 字典中这个键
    return v_out

def get_tokens(vocab):
    tokens = collections.defaultdict(int)
    for word, freq in vocab.items():
        word_tokens = word.split()
        for token in word_tokens:
            tokens[token] += freq
    return tokens

# vocab = {'l o w </w>': 5, 'l o w e r </w>': 2, 'n e w e s t </w>': 6, 'w i d e s t </w>': 3}

# Get free book from Gutenberg
# wget http://www.gutenberg.org/cache/epub/16457/pg16457.txt
vocab = get_vocab('pg16457.txt')

print('==========')
print('Tokens Before BPE')
tokens = get_tokens(vocab)
print('Tokens: {}'.format(tokens))
print('Number of tokens: {}'.format(len(tokens)))
print('==========')

num_merges = 1000
for i in range(num_merges):
    pairs = get_stats(vocab)
    if not pairs:
        break
    best = max(pairs, key=pairs.get)
    vocab = merge_vocab(best, vocab)
    print('Iter: {}'.format(i))
    print('Best pair: {}'.format(best))
    tokens = get_tokens(vocab)
    print('Tokens: {}'.format(tokens))
    print('Number of tokens: {}'.format(len(tokens)))
    print('==========')

单词以</w>结尾

 



新分词

打印结果

Tokens Before BPE
Tokens: defaultdict(<class 'int'>, {'T': 1607, 'h': 26103, 'e': 59190, '</w>': 101849, 'P': 780, 'r': 29562, 'o': 35007, 'j': 858, 'c': 13900, 't': 44238, 'G': 300, 'u': 13723, 'n': 32498, 'b': 7426, 'g': 8752, 'B': 1162, 'k': 2732, 'f': 10463, 'A': 1379, 'l': 20619, 'd': 17581, 'M': 1204, 'i': 31414, 's': 28311, 'a': 36695, 'y': 8828, 'w': 8155, 'U': 178, 'S': 865, 'm': 9751, 'p': 8030, 'v': 4878, '.': 4061, 'Y': 250, ',': 8065, '-': 1063, 'L': 426, 'I': 1428, ':': 201, 'J': 78, 'V': 102, 'E': 895, 'R': 369, '6': 73, '2': 160, '0': 402, '5': 124, '[': 32, '#': 1, '1': 291, '4': 99, '7': 60, ']': 32, 'D': 322, 'C': 862, 'K': 41, 'O': 510, '/': 31, '*': 22, 'F': 419, 'H': 688, 'N': 793, '"': 4064, '!': 1214, 'W': 576, '3': 104, "'": 1236, 'Q': 33, 'X': 49, 'Z': 10, '?': 651, '8': 73, '9': 36, '_': 1426, 'à': 3, 'x': 937, 'z': 364, '°': 41, 'q': 575, ';': 561, '(': 53, ')': 53, '{': 23, '}': 16, 'è': 2, 'é': 14, '+': 2, '=': 3, 'ö': 2, 'ê': 5, 'â': 1, 'ô': 1, 'Æ': 3, 'æ': 2, '—': 2, '™': 57, '“': 11, '”': 11, '•': 4, '%': 1, '‘': 1, '’': 6, '$': 2})
Number of tokens: 103
==========
Iter: 0
Best pair: ('e', '</w>')
Tokens: defaultdict(<class 'int'>, {'T': 1607, 'h': 26103, 'e</w>': 17758, 'P': 780, 'r': 29562, 'o': 35007, 'j': 858, 'e': 41432, 'c': 13900, 't': 44238, '</w>': 84091, 'G': 300, 'u': 13723, 'n': 32498, 'b': 7426, 'g': 8752, 'B': 1162, 'k': 2732, 'f': 10463, 'A': 1379, 'l': 20619, 'd': 17581, 'M': 1204, 'i': 31414, 's': 28311, 'a': 36695, 'y': 8828, 'w': 8155, 'U': 178, 'S': 865, 'm': 9751, 'p': 8030, 'v': 4878, '.': 4061, 'Y': 250, ',': 8065, '-': 1063, 'L': 426, 'I': 1428, ':': 201, 'J': 78, 'V': 102, 'E': 895, 'R': 369, '6': 73, '2': 160, '0': 402, '5': 124, '[': 32, '#': 1, '1': 291, '4': 99, '7': 60, ']': 32, 'D': 322, 'C': 862, 'K': 41, 'O': 510, '/': 31, '*': 22, 'F': 419, 'H': 688, 'N': 793, '"': 4064, '!': 1214, 'W': 576, '3': 104, "'": 1236, 'Q': 33, 'X': 49, 'Z': 10, '?': 651, '8': 73, '9': 36, '_': 1426, 'à': 3, 'x': 937, 'z': 364, '°': 41, 'q': 575, ';': 561, '(': 53, ')': 53, '{': 23, '}': 16, 'è': 2, 'é': 14, '+': 2, '=': 3, 'ö': 2, 'ê': 5, 'â': 1, 'ô': 1, 'Æ': 3, 'æ': 2, '—': 2, '™': 57, '“': 11, '”': 11, '•': 4, '%': 1, '‘': 1, '’': 6, '$': 2})
Number of tokens: 104
==========
Iter: 1
Best pair: ('t', 'h')
Tokens: defaultdict(<class 'int'>, {'T': 1607, 'h': 12062, 'e</w>': 17758, 'P': 780, 'r': 29562, 'o': 35007, 'j': 858, 'e': 41432, 'c': 13900, 't': 30197, '</w>': 84091, 'G': 300, 'u': 13723, 'n': 32498, 'b': 7426, 'g': 8752, 'B': 1162, 'k': 2732, 'f': 10463, 'A': 1379, 'l': 20619, 'd': 17581, 'th': 14041, 'M': 1204, 'i': 31414, 's': 28311, 'a': 36695, 'y': 8828, 'w': 8155, 'U': 178, 'S': 865, 'm': 9751, 'p': 8030, 'v': 4878, '.': 4061, 'Y': 250, ',': 8065, '-': 1063, 'L': 426, 'I': 1428, ':': 201, 'J': 78, 'V': 102, 'E': 895, 'R': 369, '6': 73, '2': 160, '0': 402, '5': 124, '[': 32, '#': 1, '1': 291, '4': 99, '7': 60, ']': 32, 'D': 322, 'C': 862, 'K': 41, 'O': 510, '/': 31, '*': 22, 'F': 419, 'H': 688, 'N': 793, '"': 4064, '!': 1214, 'W': 576, '3': 104, "'": 1236, 'Q': 33, 'X': 49, 'Z': 10, '?': 651, '8': 73, '9': 36, '_': 1426, 'à': 3, 'x': 937, 'z': 364, '°': 41, 'q': 575, ';': 561, '(': 53, ')': 53, '{': 23, '}': 16, 'è': 2, 'é': 14, '+': 2, '=': 3, 'ö': 2, 'ê': 5, 'â': 1, 'ô': 1, 'Æ': 3, 'æ': 2, '—': 2, '™': 57, '“': 11, '”': 11, '•': 4, '%': 1, '‘': 1, '’': 6, '$': 2})
Number of tokens: 105
==========
Iter: 2
Best pair: ('t', '</w>')
Tokens: defaultdict(<class 'int'>, {'T': 1607, 'h': 12062, 'e</w>': 17758, 'P': 780, 'r': 29562, 'o': 35007, 'j': 858, 'e': 41432, 'c': 13900, 't</w>': 9280, 'G': 300, 'u': 13723, 't': 20917, 'n': 32498, 'b': 7426, 'g': 8752, '</w>': 74811, 'B': 1162, 'k': 2732, 'f': 10463, 'A': 1379, 'l': 20619, 'd': 17581, 'th': 14041, 'M': 1204, 'i': 31414, 's': 28311, 'a': 36695, 'y': 8828, 'w': 8155, 'U': 178, 'S': 865, 'm': 9751, 'p': 8030, 'v': 4878, '.': 4061, 'Y': 250, ',': 8065, '-': 1063, 'L': 426, 'I': 1428, ':': 201, 'J': 78, 'V': 102, 'E': 895, 'R': 369, '6': 73, '2': 160, '0': 402, '5': 124, '[': 32, '#': 1, '1': 291, '4': 99, '7': 60, ']': 32, 'D': 322, 'C': 862, 'K': 41, 'O': 510, '/': 31, '*': 22, 'F': 419, 'H': 688, 'N': 793, '"': 4064, '!': 1214, 'W': 576, '3': 104, "'": 1236, 'Q': 33, 'X': 49, 'Z': 10, '?': 651, '8': 73, '9': 36, '_': 1426, 'à': 3, 'x': 937, 'z': 364, '°': 41, 'q': 575, ';': 561, '(': 53, ')': 53, '{': 23, '}': 16, 'è': 2, 'é': 14, '+': 2, '=': 3, 'ö': 2, 'ê': 5, 'â': 1, 'ô': 1, 'Æ': 3, 'æ': 2, '—': 2, '™': 57, '“': 11, '”': 11, '•': 4, '%': 1, '‘': 1, '’': 6, '$': 2})
Number of tokens: 106
==========

 可以保存生成的分词,根据分词列表对输入的文本进行分词

"""
2024/9/9
bpe_code.py
wang_yi
"""
import re, collections


def get_vocab(filename):
    vocab = collections.defaultdict(int)
    with open(filename, 'r', encoding='utf-8') as fhand:
        for line in fhand:
            words = line.strip().split()
            for word in words:
                vocab[' '.join(list(word)) + ' </w>'] += 1

    return vocab


def get_stats(vocab):
    pairs = collections.defaultdict(int)
    for word, freq in vocab.items():
        symbols = word.split()
        for i in range(len(symbols) - 1):
            pairs[symbols[i], symbols[i + 1]] += freq
    return pairs


def merge_vocab(pair, v_in):
    v_out = {}
    bigram = re.escape(' '.join(pair))
    p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
    for word in v_in:
        w_out = p.sub(''.join(pair), word)
        v_out[w_out] = v_in[word]
    return v_out


def get_tokens_from_vocab(vocab):
    tokens_frequencies = collections.defaultdict(int)
    vocab_tokenization = {}
    for word, freq in vocab.items():
        word_tokens = word.split()
        for token in word_tokens:
            tokens_frequencies[token] += freq
        vocab_tokenization[''.join(word_tokens)] = word_tokens
    return tokens_frequencies, vocab_tokenization


def measure_token_length(token):
    if token[-4:] == '</w>':
        return len(token[:-4]) + 1
    else:
        return len(token)


def tokenize_word(string, sorted_tokens, unknown_token='</u>'):
    if string == '':
        return []
    if sorted_tokens == []:
        return [unknown_token]

    string_tokens = []
    for i in range(len(sorted_tokens)):
        token = sorted_tokens[i]
        token_reg = re.escape(token.replace('.', '[.]'))

        matched_positions = [(m.start(0), m.end(0)) for m in re.finditer(token_reg, string)]
        if len(matched_positions) == 0:
            continue
        substring_end_positions = [matched_position[0] for matched_position in matched_positions]

        substring_start_position = 0
        for substring_end_position in substring_end_positions:
            substring = string[substring_start_position:substring_end_position]
            string_tokens += tokenize_word(string=substring, sorted_tokens=sorted_tokens[i + 1:],
                                           unknown_token=unknown_token)
            string_tokens += [token]
            substring_start_position = substring_end_position + len(token)
        remaining_substring = string[substring_start_position:]
        string_tokens += tokenize_word(string=remaining_substring, sorted_tokens=sorted_tokens[i + 1:],
                                       unknown_token=unknown_token)
        break
    return string_tokens


# vocab = {'l o w </w>': 5, 'l o w e r </w>': 2, 'n e w e s t </w>': 6, 'w i d e s t </w>': 3}

vocab = get_vocab('pg16457.txt')

print('==========')
print('Tokens Before BPE')
tokens_frequencies, vocab_tokenization = get_tokens_from_vocab(vocab)
print('All tokens: {}'.format(tokens_frequencies.keys()))
print('Number of tokens: {}'.format(len(tokens_frequencies.keys())))
print('==========')

num_merges = 100
for i in range(num_merges):
    pairs = get_stats(vocab)
    if not pairs:
        break
    best = max(pairs, key=pairs.get)
    vocab = merge_vocab(best, vocab)
    print('Iter: {}'.format(i))
    print('Best pair: {}'.format(best))
    tokens_frequencies, vocab_tokenization = get_tokens_from_vocab(vocab)
    print('All tokens: {}'.format(tokens_frequencies.keys()))
    print('Number of tokens: {}'.format(len(tokens_frequencies.keys())))
    print('==========')

# Let's check how tokenization will be for a known word
word_given_known = 'mountains</w>'
word_given_unknown = 'Ilikeeatingapples!</w>'

sorted_tokens_tuple = sorted(tokens_frequencies.items(), key=lambda item: (measure_token_length(item[0]), item[1]),
                             reverse=True)  # 根据 measure_token_length(item[0]) 排序,如果长度相同,则按 item[1](值)排序。reverse=True 降序排序
sorted_tokens = [token for (token, freq) in sorted_tokens_tuple]

print(sorted_tokens)

word_given = word_given_known

print('Tokenizing word: {}...'.format(word_given))
if word_given in vocab_tokenization:
    print('Tokenization of the known word:')
    print(vocab_tokenization[word_given])
    print('Tokenization treating the known word as unknown:')
    print(tokenize_word(string=word_given, sorted_tokens=sorted_tokens, unknown_token='</u>'))
else:
    print('Tokenizating of the unknown word:')
    print(tokenize_word(string=word_given, sorted_tokens=sorted_tokens, unknown_token='</u>'))

word_given = word_given_unknown

print('Tokenizing word: {}...'.format(word_given))
if word_given in vocab_tokenization:
    print('Tokenization of the known word:')
    print(vocab_tokenization[word_given])
    print('Tokenization treating the known word as unknown:')
    print(tokenize_word(string=word_given, sorted_tokens=sorted_tokens, unknown_token='</u>'))
else:
    print('Tokenizating of the unknown word:')
    print(tokenize_word(string=word_given, sorted_tokens=sorted_tokens, unknown_token='</u>'))

字节对编码 - 毛雷日志 (leimao.github.io) 
彻底搞懂BPE(Byte Pair Encode)原理(附代码实现)_bpe实现-CSDN博客

问题:

一个词可能存在多种拆分方式,对于算法来说,难以评估使用那个拆分方式比较合理,可以组合的列表中的优先级无法确定,通常会直接取第一个。如:

encode一个句子"linear algebra", 那么存在的划分方法有以下几种:

linear = li + near 或者 li + n + ea + r

algebra = al + ge + bra 或者 al + g + e + bra

在这个具体的case中,每个单词都有两种拆分方法, 那么整个句子就有4中拆分方法。

解决方式——>在merge的时候考虑merge前后的影响到底有多大

wordpiece

WordPiece基于概率生成新的subword而不是最高频字节对。

WordPiece和BPE的区别就在每次merge的过程中, BPE是通过合并最高频次的字节对WordPiece选择能够提升语言模型概率最大的相邻子词加入词表

假设句子S=(t1,t2,...,tn),是由n个子词组成,ti表示子词,且假设各个子词之间是独立存在的,则句子S的语言模型似然值等价与所有子词概率的乘积:

\log P(S) = \sum_{i=1}^n \log P(t_i)

把相邻位置的x和y两个子词进行合并,合并后产生的子词为z,此时句子S似然值的变化可表示为

选择让似然概率最大的值,具体的计算使用合并后的概率值,除以合并前的概率值,举个例子, 在考虑将"e"和"s"合并的时候除了会考虑"es"的概率值,还会考虑"e"和"s"的概率值。或者说,"es"的合并是通过考虑合并带来的价值。

步骤:

1.语料库
2.确定词表大小
3.为每个单词添加分割符
4.从字母开始迭代合并前后概率变化最大的字符串

在编码时从头开始查找最长的子词

"""
2024/9/10
wordpiece.py
wang_yi
"""
corpus = [
    "This is the Hugging Face course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
from transformers import AutoTokenizer
from collections import defaultdict
# 在進行預標記化時計算語料庫中每個單詞的頻率:
tokenizer = AutoTokenizer.from_pretrained(r"G:\llm_model\bert-base-chinese")

word_freqs = defaultdict(int)
for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

print(word_freqs)
# 字母表是由單詞的所有第一個字母組成的唯一集合,以及出現在前綴為 ## 的其他字母:
alphabet = []
for word in word_freqs.keys():
    if word[0] not in alphabet:
        alphabet.append(word[0])
    for letter in word[1:]:
        if f"##{letter}" not in alphabet:
            alphabet.append(f"##{letter}")

alphabet.sort()
# alphabet
print(alphabet)
# 在該詞彙表的開頭添加了模型使用的特殊標記。在使用 BERT 的情況下,它是列表 ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]:
vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy()
# 需要拆分每個單詞, 所有不是第一個字母的字母都以 ## 為前綴:
splits = {
    word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)]
    for word in word_freqs.keys()
}

# 計算每對的分數
def compute_pair_scores(splits):
    letter_freqs = defaultdict(int)
    pair_freqs = defaultdict(int)
    for word, freq in word_freqs.items():
        split = splits[word]
        if len(split) == 1:
            letter_freqs[split[0]] += freq
            continue
        for i in range(len(split) - 1):
            pair = (split[i], split[i + 1])
            letter_freqs[split[i]] += freq
            pair_freqs[pair] += freq
        letter_freqs[split[-1]] += freq

    scores = {
        pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]])
        for pair, freq in pair_freqs.items()
    }
    return scores


pair_scores = compute_pair_scores(splits)
for i, key in enumerate(pair_scores.keys()):
    print(f"{key}: {pair_scores[key]}")
    if i >= 5:
        break

best_pair = ""
max_score = None
for pair, score in pair_scores.items():
    if max_score is None or max_score < score:
        best_pair = pair
        max_score = score

print(best_pair, max_score)
# 第一個要學習的合併是 ('a', '##b') -> 'ab', 並且我們添加 'ab' 到詞彙表中:
vocab.append("ab")


def merge_pair(a, b, splits):
    for word in word_freqs:
        split = splits[word]
        if len(split) == 1:
            continue
        i = 0
        while i < len(split) - 1:
            if split[i] == a and split[i + 1] == b:
                merge = a + b[2:] if b.startswith("##") else a + b
                split = split[:i] + [merge] + split[i + 2:]
            else:
                i += 1
        splits[word] = split
    return splits


splits = merge_pair("a", "##b", splits)
print(splits["about"])

vocab_size = 70
while len(vocab) < vocab_size:
    scores = compute_pair_scores(splits)
    best_pair, max_score = "", None
    for pair, score in scores.items():
        if max_score is None or max_score < score:
            best_pair = pair
            max_score = score
    splits = merge_pair(*best_pair, splits)
    new_token = (
        best_pair[0] + best_pair[1][2:]
        if best_pair[1].startswith("##")
        else best_pair[0] + best_pair[1]
    )
    vocab.append(new_token)
print(vocab)


def encode_word(word):
    tokens = []
    while len(word) > 0:
        i = len(word)
        while i > 0 and word[:i] not in vocab:
            i -= 1
        if i == 0:
            return ["[UNK]"]
        tokens.append(word[:i])
        word = word[i:]
        if len(word) > 0:
            word = f"##{word}"
    return tokens


print(encode_word("Hugging"))
print(encode_word("HOgging"))


def tokenize(text):
    pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in pre_tokenize_result]
    encoded_words = [encode_word(word) for word in pre_tokenized_text]
    return sum(encoded_words, [])


tokenize("This is the Hugging Face course!")

标准化和预标记化 - Hugging Face NLP Course

运行结果:

defaultdict(<class 'int'>, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'course': 1, '.': 4, 'chapter': 1, 'about': 1, 'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1, ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1})
['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y']
('T', '##h'): 0.125
('##h', '##i'): 0.03409090909090909
('##i', '##s'): 0.02727272727272727
('i', '##s'): 0.1
('t', '##h'): 0.03571428571428571
('##h', '##e'): 0.011904761904761904
('a', '##b') 0.2
['ab', '##o', '##u', '##t']
['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', 'ab', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', 'Th', '##hm', '##thm', 'Hu', 'Hug', 'Hugg', 'ch', 'cha', 'chap', 'chapt', 'sh', 'th', 'is', '##thms', '##za', '##zat', '##ut', '##ta']
['Hugg', '##i', '##n', '##g']
['[UNK]']
['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', '##e', '[UNK]']

单词中首字母不变,中间分词以##连接 

Unigram Language Model(ULM)

与 BPE 和 WordPiece 相比,Unigram 在另一个方向上工作:它从一个较大的词汇表开始,然后从中删除标记,直到达到所需的词汇表大小。请注意,从不删除基本字符,以确保可以标记任何单词。

在训练的每一步,Unigram 算法都会在给定当前词汇的情况下计算语料库的损失。然后,遍历词汇表中的每个分词,分别计算如果删除该分词,整体损失会增加多少。寻找损失增加最少的分词,这些分词对语料库的整体损失影响较小,因此从某种意义上说,它们“不太需要”并且是移除的最佳候选者。

步骤:

1.建立一个足够大的词表。
2.求当前词表每个分词在语料上的概率。
3.根据词表,计算对语料最优分割下的loss。
4.遍历删除分词,计算删除该分词后对语料最优分割下的loss。
5.根据删除某分词后增加的损失进行排序,按要求比例丢弃对损失无影响或影响较小的分词。单字符不能被丢弃,这是为了避免OOV情况。
6.重复步骤2到5,直到词表大小减少到设定范围。

举个例子:

语料库:

所有分词:

所有子词的出现次数:

给定分词的概率是它在原始语料库中的频率(我们找到它的次数),除以词汇表中所有分词的所有频率的总和(以确保概率总和为 1)。例如, "ug" 在 "hug" 、 "pug" 以及 "hugs" 中,所以它在我们的语料库中的频率为 20。所有频率之和为210, 并且子词 "ug" 出现的概率是 20/210

"pug" 的标记化 ["p", "u", "g"] 的概率为:

标记化 ["pu", "g"] 的概率为:

将一个单词分成尽可能少的分词拥有更高的概率

整体损失函数为-\sum num(x)logp(x)
其中num(x)为当前单词在语料中出现的次数,p(x)为当前单词按某种分词方式分词的概率。

 对于下面的分词方式:

所以损失是:
10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8

对每种分词方式计算去掉该分词后,最优分割方式下整体损失的变化

如将hug分词去掉,

按照hu g分割为最优分割

计算整体loss的变化值=hugs单词个数*(- (-log(0.071428)) +   (-log(0.006802))) = 23.5

将_作为单词开头

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2122660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

链表题目训练

https://leetcode.cn/problems/remove-linked-li​​​​​​st-elements/description/第一题&#xff1a;移除链表元素 https://leetcode.cn/problems/remove-linked-li​​​​​​st-elements/description/ 第二题&#xff1a;反转链表 https://leetcode.cn/problems/reve…

前端JS常见面试题

数据双向绑定 Bug解决 集成工作涉及 版本node 依赖包报错 版本问题&#xff01;&#xff01;&#xff01;ElementUI、Cesium、ant-design 配置、代码和其他 混入 在Vue中&#xff0c;混入&#xff08;Mixins&#xff09;是一种非常有用的功能&#xff0c;它允许你创建可复…

C语言-数据结构 无向图迪杰斯特拉算法(Dijkstra)邻接矩阵存储

在迪杰斯特拉中&#xff0c;相比普利姆算法&#xff0c;是从顶点出发的一条路径不断的寻找最短路径&#xff0c;在实现的时候需要创建三个辅助数组&#xff0c;记录算法的关键操作&#xff0c;分别是Visited[MAXVEX]记录顶点是否被访问&#xff0c;教材上写的final数组但作用是…

springboot请求传参常用模板

注释很详细&#xff0c;直接上代码 项目结构 源码 HelloController package com.amoorzheyu.controller;import com.amoorzheyu.pojo.User; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*;import java.ti…

选择开放式耳机时应该注重哪些?值得入手的四款蓝牙耳机推荐

在选择开放式耳机时&#xff0c;以下这些方面需要重点关注&#xff1a; 舒适度方面&#xff1a; 设计与材质考量&#xff1a;耳挂和耳翼的设计必须合理&#xff0c;能够与不同的耳朵形状及大小相契合&#xff0c;保证佩戴牢固且不会过紧&#xff0c;防止对耳朵造成挤压。例如…

【解决bug之路】npm install node-sass(^4.14.1)连环报错解决!!!(Windows)

有关node-sass的深入分析可参考&#xff1a;又报gyp ERR&#xff01;为什么有那么多人被node-sass 坑过&#xff1f; 主要有如下三方面错误&#xff0c;请自查&#xff1a; 1.node&#xff0c;npm版本需与node-sass版本匹配&#xff0c;像node-sass&#xff08;^4.14.1&#x…

李沐关于大模型应用及职业发展的分享

前几天看了 李沐 在上海交大做的一个 分享 &#xff0c; 主要分享了他对于大模型的一些看法和他个人的经历。 我很喜欢李沐&#xff0c;技术厉害&#xff0c;看起来比较接地气&#xff0c;录制的 课程 也比较容易看懂。 大模型的应用 下面这张图是他对当前大模型应用的看法。…

前端学习笔记-Web APls篇-03

Dom事件进阶 1.事件流 事件流和两个阶段说明 事件流指的是事件完整执行过程中的流动路径 说明&#xff1a;假设页面里有个div&#xff0c;当触发事件时&#xff0c;会经历两个阶段&#xff0c;分别是捕获阶段、冒泡阶段简单来说&#xff1a;捕获阶段是 从父到子【大到小】 …

【鸿蒙开发工具报错】Build task failed. Open the Run window to view details.

Build task failed. Open the Run window to view details. 问题描述 在使用deveco-studio 开发工具进行HarmonyOS第一个应用构建开发时&#xff0c;通过Previewer预览页面时报错&#xff0c;报错信息为&#xff1a;Build task failed. Open the Run window to view details.…

第三部分:5---进程等待、进程终止

目录 进程的两种终止方式&#xff1a; 正常终止——进程退出码&#xff1a; 查看最近一次进程退出的退出码&#xff1a; 自定义退出码对应的文本信息&#xff1a; 退出码和C语言的错误码的关系&#xff1a; 异常终止——操作系统发送信号&#xff1a; —————————…

java基础-IO(6)转换流InputStreamReader、OutputStreamWriter

引入&#xff1a; 从第一节可知&#xff0c;流分为两类&#xff1a;字节流和字符流&#xff0c;转换流就是在两者之间进行转换。 字节流转换为字符流&#xff1b; 字符流转换为字节流。 字符集 字符集&#xff1a;定义了可用字符及其对应的数字编码的集合。常见的字符集有UT…

1.Python解释器和Pycharm安装设定

Python是一种动态的&#xff0c;解释型语言&#xff0c;需要安装Python解释器。安装Python后&#xff0c;可以使用其自带的编码工具来编写代码。也可以使用第三方的工具&#xff0c;这里使用Pycharm,它有很多优点&#xff0c;可以提高代码编写和编码调试效率。 一、Python解释…

nacos 安装 centos7 docker

一、拉取镜像 docker pull nacos/nacos-server二、创建容器 ①一般 docker run -d --name nacos-server -p 8848:8848 -e MODEstandalone nacos/nacos-server②通过配置文件配置相关环境变量 1上传文件 2创建 docker run -d \ --name nacos \ --env-file ./nacos/custom.env …

【C++ Primer Plus习题】14.3

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream> #include "queuetp.h&quo…

151-钓鱼篇邮件钓鱼SPF绕过自建邮件系统SwaksGophish

前置内容: 1、什么是SPF: 发件人策略框架(Sender Policy Framework)电子邮件认证机制中文译为发送方策略框架&#xff0c;主要作用是防止伪造邮件地址。可以把 SPF 记录看成是一个合法 IP 地址的白名单&#xff0c;当进来的邮件来自一个白名单中指定的 IP 地址&#xff0c;SP…

Java中的常用类及包装类

目录 Java中的常用类及包装类 Math类 Math类常用方法 BigInteger类 创建BigInteger类对象 常用方法 BigDecimal类 创建BigDecimal类对象 常用方法 Date日期类 创建Date类对象 常用方法 Calendar类 获取Calendar类实例 常用方法 SimpleDateFormat类 创建SimpleDateFormat类对象 …

燃气涡轮发动机性能仿真程序GSP12.0.4.2使用经验(二):使用GSP建立PG9351FA燃气轮机性能仿真模型

目录 一、PG9351FA燃气轮机简介及热力循环参数二、基于GSP的性能仿真模型设置环境参数设置进气道参数设置压气机参数设置燃烧室参数设置透平&#xff08;涡轮&#xff09;参数设置转子负载参数燃油流量外部控制 三、仿真结果四、其它 一、PG9351FA燃气轮机简介及热力循环参数 …

数据结构10

文章目录 两两交换链表中的节点括号生成I2009 408应用题42题 两两交换链表中的节点 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullp…

Mysql基础练习题 1407.排名靠前的旅行者(力扣)

编写解决方案&#xff0c;报告每个用户的旅行距离。 # 返回的结果表单&#xff0c;以 travelled_distance 降序排列 &#xff0c;如果有两个或者更多的用户旅行了相同的距离, 那么再以 name 升序排列 。 题目链接&#xff1a; https://leetcode.cn/problems/top-travellers/d…

页面水印的实现以及防删除方案

水印相关 引言绘制一个水印输出背景图封装一点点细节图片加水印防止水印删除问题解决方案 引言 在企业里为了防止信息泄露和保护知识产权&#xff0c;通常会在页面和图片上添加水印 前端页面水印的添加一般有这几种方式&#xff1a;dom 元素循环、canvas 输出背景图、svg 实现…