deepseek后推理技术:大模型的文本处理技术详解
前面我们把大模型当做一个黑盒子处理,因为我们主要关心在模型完成训练后,如何通过后训练来增加模型在特定领域的推理能力。然而后训练要顺利推进,我们依然需要对大模型本身的构造做一些修改,因此不理解基于attention
和transformer机制的大模型算法原理就能以继续推进后训练技术的理解和掌握,因此从这几节开始,我们先着手理解attention和transfomer架构的基本原理,首先我们从文本嵌入算法的原理入手。
文本或者文字不能直接输入给大模型进行处理。文本无法通过数学运算对其进行推导修改,这就需要将其转变为其他形式的数据以便支持相应的数学运算。通常情况下,大模型会把任何无法进行数学运算的数据先转换为一维向量,通过对
向量进行数学推导和运算形成新的向量后再将其转换为对应原来的数据格式。不止文本需要这么处理,音频,视频,图像等数据都需要转换为向量后大模型才能识别并进行推理,这种把特定数据转换为向量的方式也叫embedding,也就是嵌入。
本节主要看如何把文本转换为向量。当我们把非结构化的数据转换为向量时,通常情况下向量的长度越长,那么就越能抓住原始数据的信息,但代价是计算的成本就会越高。文本转换向量的第一步是将文本分割成多个词元的组合。前面我们看过,
对于英语句子或者中文句子,将其切割成多个词元时,并不是简单的一个单词对应一个词源或者一个文字对应一个词源,有些时候一个单词需要分割成多个词元,例如isn’t可能就会分割成isn和’t两部分,然后分别对应两个词源。对于中文可能就是
多个文字对应一个词源,例如前面我们看过的"中华"可能就是一个词元。
下面我们将把一个给定文本(https://github.com/yi-chen-16514/deepseek-post-training-algorithm/blob/main/the-verdict.txt)的内容分割成多个词元的组合,然后把每个词源再转换为可以计算的向量。首先我们先通过代码获取文本内容,然后查看一下文本的一些信息:
import urllib.request
url = "https://raw.githubusercontent.com/yi-chen-16514/deepseek-post-training-algorithm/refs/heads/main/the-verdict.txt"
file_path = "the-verdict.txt"
urllib.request.urlretrieve(url, file_path)
with open("the-verdict.txt", "r", encoding="utf-8") as f:
raw_text = f.read()
print(f"文本包含的总共字符数: {len(raw_text)}")
print(raw_text[:100])
上面代码运行后结果如下:
文本包含的总共字符数: 20479
I HAD always thought Jack Gisburn rather a cheap genius--though a good fellow enough--so it was no g
文本总共有20479个字符,首先我们要把字符分割成对应的单词,接着单词转换为词源,最后是词源转换为向量。接下来我们使用正则表达式把文本切割成多个单词的组合:
import re
preprocessed = re.split(r'([,.:;?!"()\']|--|\s)', raw_text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
print(len(preprocessed))
print(preprocessed[:30])
上面代码运行后所得结果如下:
['I', 'HAD', 'always', 'thought', 'Jack', 'Gisburn', 'rather', 'a', 'cheap', 'genius', '--', 'though', 'a', 'good', 'fellow', 'enough', '--', 'so', 'it', 'was', 'no', 'great', 'surprise', 'to', 'me', 'to', 'hear', 'that', ',', 'in']
上面代码使用正则表达式的方式把文档中的字符分割成单词,其实这种方法并不科学但好在简单,我们先用着。接着就把上面的单词依次进行编号,方法也简单,我们把单词根据字母来排序,单词对应的排序后的序号就是他的编号。相应代码如下:
all_words = sorted(set(preprocessed))#将单词以字母排序,排序后的序号就是单词的编号
vocab_size = len(all_words)
print(vocab_size)
上面代码运行后所得结果如下:
1139
也就是说我们从文档中分割出了1139个单词,每个单词或分割出来的单元就是一个词元。由于每个单词排序后都对应有序号,序号就是词源的编号或者说是它的身份证,我们打印前面30个词源看看:
#顺序打印若干个单词,序号就是词源编号
vocab = {token: integer for integer , token in enumerate(all_words)}
for i, item in enumerate(vocab.items()):
print(item)
if i >= 30:
break
上面代码运行后给出结果如下:
('!', 0)
('"', 1)
("'", 2)
('(', 3)
(')', 4)
(',', 5)
('--', 6)
('.', 7)
(':', 8)
(';', 9)
('?', 10)
('A', 11)
('Ah', 12)
('Among', 13)
('And', 14)
('Are', 15)
('Arrt', 16)
('As', 17)
('At', 18)
('Be', 19)
('Begin', 20)
('Burlington', 21)
('But', 22)
('By', 23)
('Carlo', 24)
('Chicago', 25)
('Claude', 26)
('Come', 27)
('Croft', 28)
('Destroyed', 29)
('Devonshire', 30)
上面我们把单词映射成了数字,在后续应用中我们还需要把数字回复为单词,因此我们把这些功能集合到一个类中:
class TokenizerV1:
def __init__(self, vocab):
#词源映射为数字
self.str_to_int = vocab
#数字转换为词源
self.int_to_str = {i: s for s, i in vocab.items()}
def encode(self, text):
#将文本转换为单词的集合
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text)
#去除单词中包含的空格
preprocessed = [item.strip() for item in preprocessed if item.strip()]
#将传入文本中的每个单词找到其对应的数字编号
ids = [self.str_to_int[s] for s in preprocessed]
return ids
def decode(self, ids):
text = " ".join([self.int_to_str[i] for i in ids])
text = re.sub(r'\s+([,.?!"()\'])', r'\1', text)
return text
接下来我们使用上面的词源转换器将一个句子转换为数字集合:
#将一段文字转换为数字编码的集合
tokenizer = TokenizerV1(vocab)
text = """
It's the last he painted, you know
"""
ids = tokenizer.encode(text)
print(ids)
print(tokenizer.decode(ids))
上面代码运行后结果如下:
[56, 2, 859, 997, 612, 544, 756, 5, 1135, 606]
It' s the last he painted, you know
需要注意的是,不是任何单词都能转换为数字,如果给定单词没有出现在前面我们下载的文本中,那么对应单词就没办法进行数字转换,例如"hello"这个单词不在前面下载的verdict文档里,于是尝试对其进行转换就会出错:
#转换器无法处理不在前面文档里面的单词
tokenizer.encode("hello")
上面代码运行后结果如下
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
/tmp/ipykernel_4163/2209685986.py in <cell line: 0>()
1 #转换器无法处理不在前面文档里面的单词
----> 2 tokenizer.encode("hello")
/tmp/ipykernel_4163/1669621308.py in encode(self, text)
12 preprocessed = [item.strip() for item in preprocessed if item.strip()]
13 #将传入文本中的每个单词找到其对应的数字编号
---> 14 ids = [self.str_to_int[s] for s in preprocessed]
15 return ids
16
KeyError: 'hello'
为了让上面的情况,我们可以使用某些特殊的字符串(“unk”)来表示任何无法找到对应编号的词源,同时增加一个新的字符串(“endoftext”)用来表示输出结束,所以代码修改如下:
all_tokens = sorted(list(set(preprocessed)))
#添加两个特殊字符串作为特殊含义的词源
all_tokens.extend(["<|endoftext|>","<|unk|>"])
vocab = {token: integer for integer, token in enumerate(all_tokens)}
#修改词源转换器,将所有对应不上编号的单词或词源全部对应成<|unk|>
class TokenizerV2:
def __init__(self, vocab):
#词源映射为数字
self.str_to_int = vocab
#数字转换为词源
self.int_to_str = {i: s for s, i in vocab.items()}
def encode(self, text):
#将文本转换为单词的集合
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text)
#去除单词中包含的空格
preprocessed = [item.strip() for item in preprocessed if item.strip()]
#如果单词没有对应的编号,那么将其编号设置为"<|unk|>"对应编号
preprocessed = [item if item in self.str_to_int else "<|unk|>" for item in preprocessed]
#将传入文本中的每个单词找到其对应的数字编号
ids = [self.str_to_int[s] for s in preprocessed]
return ids
def decode(self, ids):
text = " ".join([self.int_to_str[i] for i in ids])
text = re.sub(r'\s+([,.?!"()\'])', r'\1', text)
return text
tokenizer2=TokenizerV2(vocab)
id=tokenizer2.encode("hello")
print(id)
print(tokenizer2.decode(id))
上面代码运行后所得结果如下:
[1140]
<|unk|>
从上面运行结果可以看到,前面我们对文本中没有出现过的单词"hello"进行编码时代码会出错,现在我们可以编码文本里没有的单词,但是我们统一把所有没见过的单词统统映射成特殊字符串"<|unk|>"的编号。
更多推荐


所有评论(0)