学途智助
首页
分类
标签
关于网站
登录
eeettt123
2025-08-21
15
作者编辑
CS336 作业1 第一 二部分 Assignment Overview|BPE Tokenize
--- title: 'CS336 作业1 第一 二部分 Assignment Overview|BPE Tokenizer...' category: '/小书匠/收集/知乎问答/Alannimoon/86ba73d79c7ea44df6ee6acba0003365' slug: 'https://zhuanlan.zhihu.com/p/1927397109025473129' createDate: '2025-7-12 17:9:21' grammar_mathjax: false grammar_footnote: false grammar_ins: false emoji: 'A' tags: '' --- [toc] 发布于: 河南 创建时间: 2025-7-12 17:9:21 点赞总数: 9 评论总数: 0 收藏总数: 19 喜欢总数: 0 ## 1 Assignment Overview 在本作业中,你将从头开始构建训练标准Transformer语言模型(LM)所需的所有组件,并训练若干模型。 **What you will implement** 1. 字节对编码(BPE)分词器 2. Transformer语言模型 3. 交叉熵损失函数与AdamW优化器 4. 支持模型和优化器状态序列化/加载的训练循环 **What you will run** 1. 在TinyStories数据集上训练BPE分词器 2. 使用训练好的分词器将数据集转换为整数ID序列 3. 在TinyStories数据集上训练Transformer语言模型 4. 使用训练好的模型生成样本并评估困惑度 5. 在OpenWebText上训练模型,并将获得的困惑度提交至排行榜 **What you can use** 要求从头实现各组件。特别禁止使用torch.nn、torch.nn.functional或torch.optim中的定义,除以下例外: - torch.nn.Parameter - torch.nn中的容器类(如Module、ModuleList、Sequential等) - torch.optim.Optimizer基类 其他PyTorch定义均可使用。若不确认某个函数/类是否允许使用,可在Slack上询问。如有疑虑,请考虑是否违背作业"从头实现"的基本原则。 **Statement on AI tools** 允许使用ChatGPT等大型语言模型解决底层编程问题或语言模型相关的高层概念问题,但禁止直接用于解题。强烈建议在完成作业时禁用IDE中的AI自动补全功能(如Cursor Tab、GitHub CoPilot),不过非AI的自动补全(如函数名补全)完全允许。我们发现AI自动补全会显著降低对课程内容的深入理解。 **What the code looks like** 所有作业代码及本文档可在GitHub获取:[http://github.com/stanford-cs336/assignment1-basics](http://github.com/stanford-cs336/assignment1-basics)。请通过git clone获取仓库。如有更新将通过通知提醒,需执行git pull同步最新内容。 1. cs336\_basics/\* 你的代码实现目录。该目录初始为空——你可以完全从头开始构建! 2. adapters.py 定义必须实现的功能接口。对每个功能组件(如缩放点积注意力),只需在对应函数(如run\_scaled\_dot\_product\_attention)中调用你的实现即可。注意:该文件不应包含核心逻辑,仅作为粘合代码。 3. test\_\*.py 包含所有需通过的测试用例(如test\_scaled\_dot\_product\_attention),这些测试将调用adapters.py定义的接口。请勿修改测试文件。 **How to submit** 需向Gradescope提交以下文件: - writeup.pdf:回答所有书面问题,请使用排版工具撰写 - code.zip:包含你编写的所有代码 如需提交排行榜成绩,需向以下仓库提交PR:[http://github.com/stanford-cs336/assignment1-basics-leaderboard](http://github.com/stanford-cs336/assignment1-basics-leaderboard)。具体提交指南详见排行榜仓库的README.md文件。 **Low-Resource/Downscaling Tip: Init** 在本课程的所有作业材料中,我们将为没有或只有有限GPU资源的同学提供操作建议。例如,有时会建议 **降级** 处理数据集或模型规模,或说明如何在MacOS集成GPU或CPU上运行训练代码。这些"低资源提示"会以蓝色框形式呈现(如本提示框)。即使你是能使用课程机器的斯坦福在校生,这些建议也能帮助你更快迭代并节省时间,因此建议阅读所有提示! **Low-Resource/Downscaling Tip: Assignment 1 on Apple Silicon or CPU** 使用课程组提供的解决方案代码,我们能在配备36GB RAM的Apple M3 Max芯片上,于5分钟内在Metal GPU(MPS)上训练出能生成基本流畅文本的语言模型,CPU耗时约30分钟。若这些术语对你而言较陌生也无需担心!只需知道只要你有较新的笔记本电脑,且实现正确高效,就能训练出能生成流畅儿童故事的小型语言模型。 后续作业中将具体说明在CPU或MPS环境下需要进行的调整。 ## 2 Byte-Pair Encoding (BPE) Tokenizer 在作业的第一部分,我们将训练并实现一个字节级字节对编码(BPE)分词器\[Sennrich等人,2016;Wang等人,2019\]。具体而言,我们将把任意(Unicode)字符串表示为字节序列,并在这个字节序列上训练我们的BPE分词器。之后,我们将使用这个分词器将文本(字符串)编码为用于语言建模的token(整数序列)。 ### 2.1 The Unicode Standard Unicode是一个将字符映射为整数 _代码点_ 的文本编码标准。截至Unicode 16.0(2024年9月发布),该标准定义了168种文字中的154,998个字符。例如,字符"s"的代码点是115(通常记为U+0073,其中U+是约定前缀,0073是十六进制的115),字符"牛"的代码点是29275。在Python中,你可以使用ord()函数将单个Unicode字符转换为其整数表示,chr()函数则将整数Unicode代码点转换为对应的字符串。 ```python3 >>> ord('牛') 29275 >>> chr(29275) '牛' ``` **Problem (unicode1): Understanding Unicode (1 point)** (a) chr(0)返回什么Unicode字符? **交付要求** :一句话回答。 (b) 该字符的字符串表示与打印表示有何不同? **交付要求** :一句话回答。 (c) 当该字符出现在文本中会发生什么?在Python解释器中尝试以下代码可能有助于验证你的预期: ```python3 >>> chr(0) >>> print(chr(0)) >>> "this is a test" + chr(0) + "string" >>> print("this is a test" + chr(0) + "string") ``` **交付要求** :一句话回答。 ### 2.2 Unicode Encodings 虽然Unicode标准定义了从字符到代码点(整数)的映射,但直接在Unicode代码点上训练分词器并不实际,因为词汇表会过大(约15万项)且稀疏(许多字符非常罕见)。因此,我们将使用Unicode编码将Unicode字符转换为字节序列。Unicode标准本身定义了三种编码:UTF-8、UTF-16和UTF-32,其中UTF-8是互联网主导编码(超过98%的网页使用)。 在Python中,可以使用encode()函数将Unicode字符串编码为UTF-8。要访问Python bytes对象的底层字节值,可以对其进行迭代(例如调用list())。最后,可以使用decode()函数将UTF-8字节串解码为Unicode字符串。 ```python3 >>> test_string = "hello! こんにちは!" >>> utf8_encoded = test_string.encode("utf-8") >>> print(utf8_encoded) b'hello! \xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81x1a1\xe3\x81xaf1' >>> print(type(utf8_encoded)) <class 'bytes'> >>> # 获取编码字符串的字节值(0到255的整数) >>> list(utf8_encoded) [104, 101, 108, 108, 111, 33, 32, 227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175, 33] >>> # 一个字节不一定对应一个Unicode字符! >>> print(len(test_string)) 13 >>> print(len(utf8_encoded)) 23 >>> print(utf8_encoded.decode("utf-8")) hello! こんにちは! ``` 通过将Unicode代码点转换为字节序列(例如通过UTF-8编码),我们实际上是将代码点序列(0到154,997范围内的整数)转换为字节值序列(0到255范围内的整数)。256长度的字节词汇表更易于处理。使用字节级分词时,我们不需要担心词汇表外的token,因为任何输入文本都可以表示为0到255的整数序列。 **Problem (unicode2): Unicode Encodings (3 points)** (a) 为什么我们更倾向于在UTF-8编码的字节上训练分词器,而不是UTF-16或UTF-32?比较这些编码对不同输入字符串的输出可能有所帮助。 **交付要求** :一至两句话的回答。 (b) 考虑以下(错误的)函数,其目的是将UTF-8字节串解码为Unicode字符串。为什么这个函数是错误的?提供一个会产生错误结果的输入字节串示例。 ```python3 def decode_utf8_bytes_to_str_wrong(bytesting: bytes): return "".join([bytes([b]).decode("utf-8") for b in bytestring]) >>> decode_utf8_bytes_to_str_wrong("hello".encode("utf-8")) 'hello' ``` 通过将Unicode代码点转换为字节序列(例如通过UTF-8编码),我们实际上是将代码点序列(0到154,997范围内的整数)转换为字节值序列(0到255范围内的整数)。256长度的字节词汇表更易于处理。使用字节级分词时,我们不需要担心词汇表外的token,因为任何输入文本都可以表示为0到255的整数序列。 **交付要求** :一个使decode\_utf8\_bytes\_to\_str\_wrong产生错误输出的输入字节串示例,并用一句话解释该函数为何错误。 (c) 给出一个不能解码为任何Unicode字符的两字节序列。 **交付要求** :一个示例,并用一句话解释。 ### 2.3 Subword Tokenization 虽然字节级分词可以缓解词级分词器面临的词汇表外问题,但将文本分词为字节会导致输入序列过长。这会减慢模型训练速度——一个10个单词的句子在词级语言模型中可能只有10个token,但在字符级模型中可能长达50个或更多token(取决于单词长度)。处理这些更长的序列需要在模型的每个步骤进行更多计算。此外,字节序列上的语言建模很困难,因为更长的输入序列会在数据中创建长期依赖关系。 子词分词是词级分词器和字节级分词器之间的折中方案。字节级分词器的词汇表有256个条目(字节值为0到255),而子词分词器通过增大词汇表规模来更好地压缩输入字节序列。例如,如果字节序列b'the'经常出现在原始文本训练数据中,将其分配为词汇表中的一个条目可以将这个3字节序列缩减为单个token。 我们如何选择这些子词单元添加到词汇表中?Sennrich等人[2016]提出使用字节对编码(BPE;Gage,1994),这是一种通过迭代替换("合并")最频繁的字节对为单个新索引的压缩算法。该算法通过添加子词token来最大化输入序列的压缩效率——如果一个单词在输入文本中出现足够多次,它将被表示为单个子词单元。 通过BPE构建词汇表的子词分词器通常称为BPE分词器。在本作业中,我们将实现一个字节级BPE分词器,其词汇表项是字节或合并的字节序列,这让我们在词汇表外处理和可管理的输入序列长度方面都能获得优势。构建BPE分词器词汇表的过程被称为"训练"BPE分词器。 ### 2.4 BPE Tokenizer Training BPE分词器的训练过程包含三个主要步骤: **Vocabulary initialization** 分词器词汇表是从字节串token到整数ID的一对一映射。由于我们训练的是字节级BPE分词器,初始词汇表就是所有字节的集合。由于存在256个可能的字节值,我们的初始词汇表大小为256。 **Pre-tokenization** 有了词汇表后,原则上可以统计文本中相邻字节的出现频率,并从最频繁的字节对开始合并。但这种方法计算成本很高,因为每次合并都需要完整遍历语料库。此外,直接跨语料库合并字节可能导致仅标点不同的token(如dog! vs. dog.)获得完全不同的token ID,尽管它们具有高度语义相似性。 为避免这种情况,我们对语料库进行预分词。这可以理解为一种粗粒度的分词方法,帮助我们统计字符对的出现频率。例如单词'text'可能作为预分词单元出现10次。这样统计相邻字符't'和'e'时,可以直接为'text'中的这对字符增加10次计数,而无需遍历语料库。由于训练的是字节级BPE模型,每个预分词单元都表示为UTF-8字节序列。 Sennrich等人[2016]的原始BPE实现仅通过空白符分割进行预分词(即s.split(" "))。而我们将采用GPT-2(Radford等人,2019)使用的基于正则表达式的预分词器(来自[http://github.com/openai/tiktoken/pull/234/files](http://github.com/openai/tiktoken/pull/234/files)): ```python3 PAT = r"""'(?:[sdmt][ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\n{L}\p{N}]+|\s+(?|\S)|\s+""" ``` 可以通过交互式分割文本来更好地理解这个预分词器的行为: ```python3 >>> # 需要安装`regex`包 >>> import regex as re >>> re.findall(PAT, "some text that i'll pre-tokenize") ['some', 'text', 'that', 'i', "'ll", 'pre', '-', 'tokenize'] ``` 在实际代码中,应使用re.finditer来避免在构建预分词到计数的映射时存储所有预分词结果。 **Compute BPE merges** 将输入文本转换为预分词并表示为UTF-8字节序列后,就可以计算BPE合并(即训练BPE分词器)。BPE算法会迭代统计每个字节对,找出最高频的对("A","B"),然后将所有出现的最频繁对("A","B")合并为新的token"AB"。这个新合并的token会被加入词汇表,因此BPE训练后的最终词汇表大小是初始词汇表大小(本文是256)加上训练期间执行的BPE合并操作次数。为了提高BPE训练效率,不考虑跨越预分词边界的字节对。当合并频率相同的对时,按字典序优先合并较大的对。例如,如果("A","B")、("A","C")、("B","ZZ")和("BA","A")都具有最高频率,则合并("BA","A"): ```python3 >>> max([("A", "B"), ("A", "C"), ("B", "ZZ"), ("BA", "A")]) ('BA', 'A') ``` **Special tokens** 某些字符串(如<|endoftext|>)通常用于编码元数据(如文档边界)。在文本编码时,需要将这些字符串视为"特殊token"——它们永远不会被分割成多个token(即始终保留为单个token)。例如,序列结束字符串<|endoftext|>应始终作为单个token(即单个整数ID)保留,以便我们知道何时停止语言模型生成。这些特殊token必须添加到词汇表中,以获得对应的固定token ID。 Sennrich等人[2016]的算法1包含了一个低效的BPE分词器训练实现(基本遵循上述步骤)。作为初步练习,实现并测试该函数可能有助于理解。 **Example (bpe\_example): BPE training example** 以下是Sennrich等人[2016]的风格化示例。考虑由以下文本组成的语料库: ```text low low low low lower lower widest widest widest newest newest newest newest newest ``` 且词汇表包含特殊token <|endoftext|>。 **Vocabulary** 我们使用特殊token <|endoftext|>和256个字节值初始化词汇表。 **Pre-tokenization** 为简化并聚焦合并过程,本例假设预分词仅按空白符分割。预分词并统计后得到频率表:{low: 5, lower: 2, widest: 3, newest: 6} 我们可以方便地使用dict\[tuple[bytes], int\]类型表示,例如{(1,o,w): 5...}。注意在Python中即使单个字节也是bytes对象,Python没有单独的byte类型表示单个字节,就像没有char类型表示单个字符一样。 **Merges** 首先统计所有连续字节对的频率总和:{10:7, ow:7, we:8, er:2, vi:3, id:3, de:3, es:9, st:9, ne:6, ev:6}。当('es')和('st')频率相同时,按字典序选择更大的对('st')进行合并。合并后预分词结果变为{(1,o,w):5, (1,o,w,e,r):2, (w,i,d,e,st):3, (n,e,w,e,st):6}。 第二轮合并中,(e,st)成为最高频对(计数9),合并结果为{(1,o,w):5, (1,o,w,e,r):2, (w,i,d,est):3, (n,e,w,est):6}。最终得到的完整合并序列为:\['s t', 'e st', 'o w', 'l ow', 'w est', 'n e', 'ne west', 'w i', 'wi d', 'wid est', 'low e', 'love r'\]。 如果执行6次合并得到\['s t', 'e st', 'o w', 'l ow', 'w est', 'n e'\],则词汇表包含:\[<|endoftext|>, ...256字节字符\], st, est, ow, low, west, ne。使用此词汇表时,单词"newest"将被分词为\[ne, west\]。 ### 2.5 Experimenting with BPE Tokenizer Training 现在我们在TinyStories数据集上训练字节级BPE分词器。数据集获取指南参见第1节。开始前建议先查看TinyStories数据集以了解数据内容。 **Parallelizing pre-tokenization** 预分词步骤是主要性能瓶颈。可以使用multiprocessing库并行加速。具体实现时,建议在确保分块边界位于特殊token起始处的前提下对语料分块。可直接使用以下链接中的示例代码获取分块边界,用于跨进程分配任务:[https://github.com/stanford-cs336/assignment1-basics/blob/main/cs336\_basics/pretokenization\_example.py](https://github.com/stanford-cs336/assignment1-basics/blob/main/cs336_basics/pretokenization_example.py) 这种分块方式始终有效,因为我们从不在文档边界处执行合并操作。在本作业中,你可以始终采用这种方式分块。无需考虑极端情况——接收到的超大语料库中不包含<|endoftext|>标记的情形。 **Removing special tokens before pre-tokenization** 在使用正则表达式模式(通过re.finditer)执行预分词前,应先从语料库(或分块)中移除所有特殊token。必须确保在特殊token处 **分割** 文本,避免跨越它们所界定的文本范围进行合并。例如,对于形如[文档1]<|endoftext|>[文档2]的语料(或分块),应在特殊token <|endoftext|>处分割,分别对[文档1]和[文档2]进行预分词,从而防止跨文档边界合并。这可以通过re.split实现,使用"|".join(special\_tokens)作为分隔符(需注意对re.escape的谨慎使用,因为特殊token可能包含"|"字符)。测试用例test\_train\_bye\_special\_tokens将验证这一点。 **Optimizing the merging step** 前述示例中的朴素BPE训练实现效率较低,因为每次合并都需要遍历所有字节对来识别最高频对。实际上,每次合并后只有与已合并对重叠的字节对计数会发生变化。因此,可以通过建立所有字节对的计数索引并增量更新这些计数(而非显式遍历统计),显著提升BPE训练速度。虽然这种缓存机制能带来显著加速,但需要注意BPE训练的合并步骤在Python中 **无法** 并行化。 **Low-Resource/Downscaling Tip: Profiling** 建议使用cProfile或scalene等性能分析工具来识别实现中的瓶颈,并集中优化这些关键部分。 **Low-Resource/Downscaling Tip: “Downscaling”** 不要直接在整个TinyStories数据集上训练分词器,我们建议先在小规模数据子集("调试数据集")上进行训练。例如可以使用TinyStories验证集(包含2.2万篇文档,而非完整的212万篇)进行分词器训练。这展示了一个通用开发策略:尽可能通过降级加速开发过程,包括使用更小的数据集、更小的模型规模等。选择调试数据集规模或超参数配置时需要谨慎权衡:调试集应足够大以复现完整配置的瓶颈特征(确保优化措施具有通用性),但也不宜过大导致运行时间过长。 **Problem (train\_bpe): BPE Tokenizer Training (15 points)** **交付要求** :编写一个函数,根据输入的文本文件路径训练(字节级)BPE分词器。你的BPE训练函数应至少处理以下输入参数: input\_path: str BPE分词器训练数据文本文件的路径。 vocab\_size: int 定义最终词汇表最大大小的正整数(包括初始字节词汇表、合并产生的词汇表项和任何特殊token)。 special\_tokens: list[str] 需要添加到词汇表中的字符串列表。这些特殊token不会影响BPE训练过程。 你的BPE训练函数应返回最终的词汇表和合并记录: vocab: dict\[int, bytes\] 分词器词汇表,一个从整数(词汇表中的token ID)到字节(token字节)的映射。 merges: list\[tuple\[bytes, bytes\]\] 训练产生的BPE合并记录列表。每个列表项是一个字节元组(<token1>, <token2>),表示<token1>与<token2>被合并。合并记录应按创建顺序排列。 要测试你的BPE训练函数是否符合我们提供的测试用例,你需要先实现测试适配器\[adapters.run\_train\_bye\],然后运行uv run pytest tests/test\_train\_bye.py。你的实现应能通过所有测试。可选地(这可能耗时较多),你可以用系统级语言(如C++,可考虑cppyy;或Rust,使用PyO3)实现训练方法的关键部分。如果这样做,请注意区分需要复制和直接从Python内存读取的操作,并确保提供构建说明或仅通过pyproject.toml即可构建。另外请注意,GPT-2的正则表达式在大多数正则引擎中支持不佳,支持的引擎也往往速度太慢。我们已验证Oniguruma速度合理且支持负向预查,但Python的regex包速度更快。 **Problem (train\_bpe\_tinystories): BPE Training on TinyStories (2 points)** (a) 在TinyStories数据集上训练一个字节级BPE分词器,最大词汇表大小为10,000。确保将TinyStories的特殊token <|endoftext|>加入词汇表。将最终词汇表和合并记录序列化到磁盘以供检查。训练耗时多少小时?内存占用多少?词汇表中最长的token是什么?这合理吗? 资源要求:≤30分钟(无GPU),≤30GB内存 提示:通过预分词阶段的并行处理及以下两个事实,训练时间应能控制在2分钟以内: 1. 数据文件中<|endoftext|> token用于分隔文档 2. <|endoftext|> token在BPE合并前作为特殊情况处理 **交付要求** :一至两句话的回答 (b) 分析代码性能。分词器训练过程中哪个步骤最耗时? **交付要求** :一至两句话的回答 接下来我们将在OpenWebText数据集上训练字节级BPE分词器。和之前一样,建议先查看数据集以了解其内容。 **Problem (train\_bpe\_expts\_owt): BPE Training on OpenWebText (2 points)** (a) 在OpenWebText数据集上训练字节级BPE分词器,最大词汇表大小为32,000。将最终词汇表和合并记录序列化到磁盘。词汇表中最长的token是什么?这合理吗? 资源要求:≤12小时(无GPU),≤100GB内存 **交付要求** :一至两句话的回答 (b) 对比分析TinyStories和OpenWebText训练得到的分词器。 **交付要求** :一至两句话的回答 ### 2.6 BPE Tokenizer: Encoding and Decoding 完成BPE分词器训练函数后,我们现在要实现一个能加载给定词汇表和合并记录的BPE分词器,用于文本与token ID之间的编码和解码。 **2.6.1 Encoding text** BPE文本编码过程与训练过程相呼应,主要包含以下步骤: **Step 1: Pre-tokenize.** 首先对文本进行预分词,并将每个预分词单元表示为UTF-8字节序列(与训练阶段相同)。我们将在每个预分词单元内部合并这些字节为词汇表元素,各预分词单元独立处理(不跨越预分词边界合并)。 **Step 2: Apply the merges.** 按照BPE训练时创建的合并记录顺序,将这些合并操作应用到预分词结果上。 **Example (bpe\_encoding): BPE encoding example** 例如,假设我们的输入字符串是'the cat ate',词汇表是{0: b' ', 1: b'a', 2: b'c', 3: b'e', 4: b'h', 5: b't', 6: b'th', 7: b' c', 8: b' a', 9: b'the', 10: b' at'},学习到的合并记录是\[(b't', b'h'), (b' ', b'c'), (b' ', 'a'), (b'th', b'e'), (b' a', b't')\]。首先,预分词器会将这个字符串分割为\['the', ' cat', ' ate'\]。然后,我们查看每个预分词单元并应用BPE合并。 第一个预分词单元'the'初始表示为\[b't', b'h', b'e'\]。查看合并记录列表,第一个适用的合并是(b't', b'h'),应用后预分词单元变为\[b'th', b'e'\]。然后再次查看合并记录列表,下一个适用的合并是(b'th', b'e'),将预分词单元变为[b'the']。最后查看合并记录列表,发现没有更多可应用的合并(因为整个预分词单元已合并为单个token),因此合并过程结束。对应的整数序列是[9]。 对剩余预分词单元重复这个过程:预分词单元' cat'应用BPE合并后表示为\[b' c', b'a', b't'\],对应整数序列\[7, 1, 5\]。预分词单元' ate'应用BPE合并后表示为\[b' at', b'e'\],对应整数序列[10, 3]。因此,输入字符串的最终编码结果是\[9, 7, 1, 5, 10, 3\]。 **Special tokens.** 分词器在编码文本时应能正确处理用户定义的特殊token(在构建分词器时提供)。 **Memory considerations.** 当需要处理无法完全加载到内存的大型文本文件时,应将其分割为可管理的块进行流式处理,使内存复杂度保持恒定而非随文本大小线性增长。在此过程中,必须确保token不会跨越块边界,否则将得到与全内存分词不同的结果。 **2.6.2 Decoding text** 将整数token ID序列解码回原始文本时,只需在词汇表中查找每个ID对应的字节序列,拼接后解码为Unicode字符串。注意输入的ID序列不一定对应有效的Unicode字符串(用户可能输入任意整数序列)。若解码出现无效字节,应使用官方Unicode替换字符U+FFFD替代。通过bytes.decode的errors参数(设为'replace')可自动实现这种替换处理。 **Problem (tokenizer): Implementing the tokenizer (15 points)** **交付要求** :实现一个Tokenizer类,根据给定的词汇表和合并记录列表,能够将文本编码为整数ID序列,并将整数ID序列解码为文本。该分词器还应支持用户提供的特殊token(若词汇表中不存在则自动添加)。我们推荐以下接口设计: ```python3 def __init__(self, vocab, merges, special_tokens=None) ## 构造函数,接收以下参数创建分词器: vocab: dict[int, bytes] merges: list[tuple[bytes, bytes]] special_tokens: list[str] | None = None def from_files(cls, vocab_filepath, merges_filepath, special_tokens=None) ## 类方法,从序列化的词汇表文件和合并记录文件(格式应与BPE训练代码输出一致)构造并返回Tokenizer实例, ## 接收参数: vocab_filepath: str merges_filepath: str special_tokens: list[str] | None = None def encode(self, text: str) -> list[int] ## 将输入文本编码为token ID序列 def encode_iterable(self, iterable: Iterable[str]) -> Iterator[int] ## 接收字符串可迭代对象(如Python文件句柄),返回惰性生成token ID的生成器。 ## 该方法用于高效处理无法直接加载到内存的大文件 def decode(self, ids: list[int]) -> str ##将token ID序列解码为文本 ``` 测试前需先实现测试适配器\[adapters.get\_tokenizer\],然后运行`uv run pytest tests/test_tokenizer.py`。你的实现应能通过所有测试。 ### 2.7 Experiments **Problem (tokenizer\_experiments): Experiments with tokenizers (4 points)** (a) 从TinyStories和OpenWebText中各采样10篇文档。使用先前训练的TinyStories(10K词汇表)和OpenWebText(32K词汇表)分词器将其编码为整数ID序列。计算每个分词器的压缩率(字节数/token数)? **交付要求** :一至两句话的回答 (b) 若使用TinyStories分词器处理OpenWebText样本会发生什么?比较压缩率和/或定性描述现象。 **交付要求** :一至两句话的回答 (c) 评估你的分词器吞吐量(如字节/秒)。处理Pile数据集(825GB文本)需要多长时间? **交付要求** :一至两句话的回答 (d) 使用TinyStories和OpenWebText分词器分别编码对应的训练集和开发集为整数token ID序列(建议序列化为uint16类型的NumPy数组)。为何uint16是合适的选择? **交付要求** :一至两句话的回答 ## 任务总结(共42分) **2.1 (unicode1): Understanding Unicode (1 point)** **2.2 (unicode2): Unicode Encodings (3 points)** **2.5 (train\_bpe): BPE Tokenizer Training (15 points)** **2.5 (train\_bpe\_tinystories): BPE Training on TinyStories (2 points)** **2.5 (train\_bpe\_expts\_owt): BPE Training on OpenWebText (2 points)** **2.6 (tokenizer): Implementing the tokenizer (15 points)** **2.7 (tokenizer\_experiments): Experiments with tokenizers (4 points)** 原文地址:[CS336 作业1 第一 二部分 Assignment Overview|BPE Tokenizer...](https://zhuanlan.zhihu.com/p/1927397109025473129)
Python
赞
博客信息
作者
eeettt123
发布日期
2025-08-21
其他信息 : 其他三字母的人名首字母都是其他同学发布的哦