Standford CS336 | Assignment 1 - Implementing the tokenizer

cs336系列(1) 第一步先克隆到本地如果你的环境中没有下载uv,先安装一下然后激活下载需要的库和包,如果下载速度慢可以设置镜像源uv run。 阅读详情
AI摘要

本文围绕Stanford CS336课程首个作业,详细介绍分词器(Tokenizer)的实现。此前已完成字节级BPE分词器训练,得到词汇表和合并规则。此次作业需实现编码、解码功能,支持从文件加载、迭代式编码及特殊符号处理。文中阐述了Tokenizer类各方法实现及测试情况。

所有关于 assignment1 的代码已开源在:
https://github.com/ACEEE-1222/Standford-CS336-Assignment-1
如果对你有帮助的话,记得顺手点个star喔!

在之前的内容中,我们完成了字节级 BPE 分词器的训练部分,得到了词汇表(vocab)和合并规则(merges)。而要让分词器能够实际应用,还需要实现编码和解码功能。本文将详细介绍 Stanford CS336 课程第一个作业中分词器(Tokenizer)的实现过程,包括其核心方法和关键细节。

一、作业要求概述

本次作业的核心任务是实现一个 Tokenizer 类,该类基于训练得到的词汇表(vocab)和合并规则(merges),能够将文本编码为整数 ID 序列,以及将整数 ID 序列解码为文本。同时,需要支持用户提供的特殊符号。

具体要求如下:
在这里插入图片描述

  • 编码(encode):将输入的文本字符串转换为 token ID 列表。过程需先进行预分词,再应用 BPE 合并规则。
  • 解码(decode):将 token ID 列表转换为文本字符串。对于无效的 token ID,需用 Unicode 替换字符(U+FFFD)处理。
  • 支持从文件加载:实现 from_files 类方法,从文件加载词汇表和合并规则。
  • 支持迭代式编码:实现 encode_iterable 方法,支持对大型文件进行内存高效的编码。
  • 特殊符号处理:若特殊符号不在词汇表中,需将其添加进去,并在编码中保持独立。

二、Tokenizer 类的实现

1. 类的初始化(__init__ 方法)

def __init__(self, vocab: dict[int, bytes], merges: list[tuple[bytes, bytes]], special_tokens: list[str] | None = None):
    self.vocab = vocab
    self.vocab_reversed = {v: k for k, v in self.vocab.items()}
    self.merges = merges
    self.special_tokens = sorted(special_tokens or [], key=lambda x: -len(x))

初始化方法中我们做了如下工作:

  • 存储词汇表、合并规则和特殊符号;
  • 构建词汇表的反向映射 vocab_reversed,便于编码时查表;
  • 对特殊符号按长度降序排序,防止短符号干扰长符号匹配。(在测试样例中有条就是关于这个的)

2. 从文件加载(from_files 类方法)

@classmethod
def from_files(cls, vocab_filepath: str, merges_filepath: str, special_tokens: list[str] | None = None) -> "Tokenizer":
    vocab: dict[int, bytes] = {}
    with open(vocab_filepath, "r", encoding="utf-8") as f:
        for line in f:
            id_str, token_str = line.strip().split("\t")
            vocab[int(id_str)] = token_str.encode("utf-8")

    merges: list[tuple[bytes, bytes]] = []
    with open(merges_filepath, "r", encoding="utf-8") as f:
        for line in f:
            parts = line.strip().split()
            if len(parts) == 2:
                merges.append((parts[0].encode("utf-8"), parts[1].encode("utf-8")))

    return cls(vocab=vocab, merges=merges, special_tokens=special_tokens)

工作流程:

  • 加载 vocab.tsv,将每一行的 token ID 和字符串转为 dict[int, bytes];
  • 加载 merges.txt,将每一行的合并对转为 tuple[bytes, bytes];
  • 返回 Tokenizer 实例。

3. 编码方法(encode)

def encode(self, text: str) -> list[int]:
    token_ids = []
    pre_tokens_list = process_chunk((text, self.special_tokens, True))
    for tokens in pre_tokens_list:
        for pair in self.merges:
            a, b = pair
            new_tok = a + b
            new_tokens: list[bytes] = []
            i = 0
            while i < len(tokens):
                if i < len(tokens) - 1 and (tokens[i], tokens[i+1]) == pair:
                    new_tokens.append(new_tok)
                    i += 2
                else:
                    new_tokens.append(tokens[i])
                    i += 1
            tokens = new_tokens
        for token in tokens:
            token_ids.append(self.vocab_reversed.get(token))
    return token_ids

编码流程:

  1. 调用 process_chunk 进行预分词;(注意,在上篇文章的基础上,加入了 keep_special_tokens参数,来决定是否在预分词时保留special tokens )
  2. 对每个预分词 token 应用合并规则;
  3. 将合并后的 token 映射为 ID。

4. 可迭代对象编码(encode_iterable)

def encode_iterable(self, iterable: list[str]) -> iter:
    for line in iterable:
        token_ids = self.encode(line)
        yield from token_ids
  • 用于大规模文本处理,如按行读取大文件;
  • 每次处理一行文本,节省内存。

5. 解码方法(decode)

def decode(self, ids: list[int]) -> str:
    tokens = bytes()
    vocab_size = len(self.vocab)
    replacement_char = "\uFFFD"
    for token_id in ids:
        if token_id < vocab_size:
            token = self.vocab[token_id]
        else:
            token = bytes(replacement_char, encoding='utf-8')
        tokens += token
    decoded = tokens.decode(encoding='utf-8', errors='replace')
    return decoded
  • 拼接所有字节;
  • 使用 errors='replace' 容错解码;
  • 超出词表范围的 ID 使用 U+FFFD 替换。

6. 预分词函数(process_chunk)

def process_chunk(args: tuple[str, list[str], bool]) -> list[list[bytes]]:
    chunk, special_tokens, keep_special_tokens = args
    pattern = "|".join(re.escape(tok) for tok in special_tokens)
    if keep_special_tokens and pattern:
        pattern = f"({pattern})"
    segments = re.split(pattern, chunk) if pattern else [chunk]

    pre_tokens_bytes: list[list[bytes]] = []
    PAT = r"""'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""

    for segment in segments:
        if keep_special_tokens and segment in special_tokens:
            token_bytes = [segment.encode("utf-8")]
            pre_tokens_bytes.append(token_bytes)
        else:
            tokens = [match.group(0).encode("utf-8") for match in re.finditer(PAT, segment)]
            for token in tokens:
                token_bytes = [bytes([b]) for b in token]
                pre_tokens_bytes.append(token_bytes)
    return pre_tokens_bytes

功能说明:

  • 正则方式匹配特殊符号与普通 token;
  • 普通 token 按字节切分;
  • 保证不跨 token 合并。

7. 完整代码

class Tokenizer:
    def __init__(self, vocab: dict[int, bytes], merges: list[tuple[bytes, bytes]], special_tokens: list[str] | None = None):
        """
        Initialize the Tokenizer with a vocabulary, BPE merge rules, and optional special tokens.

        Args:
            vocab (dict[int, bytes]): A mapping from token IDs to byte-encoded tokens.
            merges (list[tuple[bytes, bytes]]): A list of merge operations as tuples of byte pairs.
            special_tokens (list[str] | None): Optional list of user-defined special tokens to include.
        """
        self.vocab = vocab
        self.vocab_reversed = {v: k for k, v in self.vocab.items()}  # bytes: int
        self.merges = merges
        self.special_tokens = sorted(special_tokens or [], key=lambda x: -len(x))

    @classmethod
    def from_files(cls, vocab_filepath: str, merges_filepath: str, special_tokens: list[str] | None = None) -> "Tokenizer":
        """
        Construct a Tokenizer from serialized vocabulary and merges files.

        Args:
            vocab_filepath (str): Path to the vocabulary file (from BPE training).
            merges_filepath (str): Path to the merges file (from BPE training).
            special_tokens (list[str] | None): Optional list of special tokens to include.

        Returns:
            Tokenizer: A Tokenizer instance initialized with the given files.
        """
        vocab: dict[int, bytes] = {}
        with open(vocab_filepath, "r", encoding="utf-8") as f:
            for line in f:
                id_str, token_str = line.strip().split("\t")
                vocab[int(id_str)] = token_str.encode("utf-8")  # store as bytes

        merges: list[tuple[bytes, bytes]] = []
        with open(merges_filepath, "r", encoding="utf-8") as f:
            for line in f:
                parts = line.strip().split()
                if len(parts) == 2:
                    merges.append((parts[0].encode("utf-8"), parts[1].encode("utf-8")))

        return cls(vocab=vocab, merges=merges, special_tokens=special_tokens)

    def encode(self, text: str) -> list[int]:
        """
        Encode an input string into a list of token IDs using the BPE algorithm.

        Args:
            text (str): The input text to tokenize.

        Returns:
            list[int]: A list of token IDs representing the encoded text.
        """
        # byte_special_tokens = [token.encode('utf-8') for token in self.special_tokens]
        token_ids = []
        pre_tokens_list = process_chunk((text, self.special_tokens, True))
        for tokens in pre_tokens_list:
            for pair in self.merges:
                a, b = pair
                new_tok = a + b
                new_tokens: list[bytes] = []
                i=0
                while i < len(tokens):
                    if i < len(tokens) -1 and (tokens[i],tokens[i+1]) == pair:
                        new_tokens.append(new_tok)
                        i += 2
                    else:
                        new_tokens.append(tokens[i])
                        i += 1
                tokens = new_tokens
            
            for i in range(len(tokens)):
                token_ids.append(self.vocab_reversed.get(tokens[i]))
        
        return token_ids


    def encode_iterable(self, iterable: list[str]) -> iter:
        """
        Lazily encode an iterable of strings into a stream of token IDs.

        Useful for memory-efficient tokenization of large datasets.

        Args:
            iterable (list[str]): An iterable of strings (e.g., lines from a file).

        Returns:
            iter: A generator that yields token IDs one at a time.
        """
        for line in iterable:
            token_ids = self.encode(line)
            yield from token_ids

    def decode(self, ids: list[int]) -> str:
        """Decode a sequence of token IDs into text."""
        tokens = bytes()
        vocab_size = len(self.vocab)
        replacement_char = "\uFFFD"

        for token_id in ids:
            if token_id < vocab_size:
                token = self.vocab[token_id]  
            else:
                token = bytes(replacement_char, encoding='utf-8')   # Replace tokens with Unicode replacement characters if index out of bounds

            tokens += token
        decoded = tokens.decode(encoding='utf-8', errors='replace')

        return decoded 

三、测试情况

完成 Tokenizer 实现后,可通过官方测试文件进行验证:

uv run pytest tests/test_tokenizer.py

在这里插入图片描述

测试全部通过后,说明实现功能符合预期。

如对实现细节或测试思路有疑问,欢迎评论交流。

斯坦福 CS336 动手大语言模型 Assignment1 BPE Tokenizer & TransformerLM 所有代码更新至 https://github.com/WangYuHang-cmd/CS336/tree/main/assignment1-basics作业文件结构:├── tests/ # 测试文件目录│ ├── adapters.py # 适配器测试│ ├── conftest.py # pytest配置│ ├──.py # 包初始化。 阅读详情

相关推荐

CS336-Project:罗格斯CS336数据库项目

CS336-项目 罗格斯CS336数据库项目

Stanford CS336 | Assignment 1 - BPE Tokenizer Training 实现

本次任务目标是在 TinyStories 数据集上训练一个字节级 BPE 分词器。如图所示,需要完成函数train_bpe。初始化:以所有可能的字节(0-255)作为初始词汇表;预处理:将文本分割为初始令牌(字节序列);合并:统计所有字节对的频率,合并最频繁的对并更新词汇表,重复此过程直至达到目标词汇量。

Bug_makerACE的博客 2251

pytorch实现手语识别

连续手语识别项目,中文数据集CSL

masteryi-0018的博客 1万+

【免费下载】 Stanford CS336 Assignment 1: 语言模型从零开始的最佳实践

本项目是基于斯坦福大学CS336课程的第一份作业,旨在从零开始实现语言模型。通过该作业,学生可以学习到如何处理自然语言数据、构建基本的语言模型,并对其进行训练和评估。项目采用Python语言开发,依赖uv环境管理,确保了代码的可复现性、可移植性和易用性。 ## 2. 项目快速启动 ### 环境准备 首先,确保你已经安装了Python环境。然后安装uv工具,推荐使用以下命令: ```shel

gitblog_00692的博客 1793

Deepseek 10分钟化身理性荐股神器:触碰程序员不敢想的天花板

在这个消息疯狂、波动频繁的市场,“理性”两字显得弥足珍贵。通过Deepseek+AkShare+Aktools+Dify四位一体的方案,能帮我们降低信息搜集成本,并在短期投资领域搭建出一套“可解释、低门槛、易定制”的量化荐股流程。不要再盲目听小道消息或依赖情绪驱动,不妨给自己一个机会,让数据说话。而要注意的是,“理性荐股”并非万能,它只是工具,最终的收益还得依赖投资者自身对风险和机遇的权衡。祝大家都能在这股市里收获理性的底气,寻得更精准的入场时机。

qqxdh的博客 3753

CS231n+assignment2(一)

cs231n作业二详解+代码

qq_56551150的博客 2411

cs336 assignment1 作业环境配置

cs336 assignment1 前期环境配置

m0_46464899的博客 3004

Matlab实战:如何用MMG方程预测船舶轨迹(附完整代码)

本文提供了一份基于MMG方程的Matlab实战指南,详细讲解了如何从零构建船舶轨迹预测模型。内容涵盖MMG模型的核心原理、Matlab代码框架设计、动力学函数实现、参数调优与验证方法,并附有完整代码示例,帮助工程师和研究者快速掌握这一船舶操纵性预测的关键技术。

v6b7n8m9q0的博客 520

Stanford CS336 Language Models from Scratch-Assignment 1 OVerview

前些天计划看Stanford 新开的 CS336,顺便记录下,发到这里,大家一起交流。

nuowenyadelunwen的博客 835

爬虫瑞数5.5案例:欧冶钢材交易官网,带后缀K5nOZLud(面向对象补环境)

瑞数动态安全 Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态令牌等技术对服务器网页底层代码持续动态变换,增加服务器行为的“不可预测性”,实现了从用户端到服务器端的全方位“主动防护”,为各类 Web、HTML5 提供强大的安全保护。由于之前某通信的瑞数已经调整,这里再出一篇文章,瑞数特点以及请求执行的流程已在之前的文章中分析过,这里不再特别讲解;

老衲爱洗头的博客 3043

VS2022编译xlnt库

xlnt是一个用于读写Excel文件的C++库,本文介绍如何用源代码编译静态或动态xlnt库

QQVQQ的博客 7288

CS336作业一笔记:Transformer 全模块 + 公式汇总

cs336作业一,transformer涉及的所有模块,公式总结

qq_41697157的博客 593

PLC为西门子的S7-1200,舵机为 DS3218,PLC控制舵机旋转程序实现?

🏆 本文收录于《全栈Bug调优(实战版)》专栏,致力于分享我在项目实战过程中遇到的各类Bug及其原因,并提供切实有效的解决方案。无论你是初学者还是经验丰富的开发者,本文将为你指引出一条更高效的Bug修复之路,助你早日登顶,迈向财富自由的梦想🚀!同时,欢迎大家关注、收藏、订阅本专栏,更多精彩内容正在持续更新中。让我们一起进步,Up!Up!Up!    备注: 部分问题/难题源自互联网,经过精心筛选和整理,结合数位十多年大厂实战经验资深大佬经验总结所得,数条可行方案供所需之人参考。

**My Coding Family** 999

assignment1-basics:掌握编程基础,提升技能第一步

assignment1-basics:掌握编程基础,提升技能第一步 项目介绍 在计算机科学教育中,基础知识的掌握至关重要。assignment1-basics 是一个开源项目,旨在帮助学习者通过实际编码任务来深化对基础编程概念的理解。该项目作为CS336课程的第一个作业,提供了一个实践的平台,让学生在解决问题的过程中巩固Python编程的基础。 项目技术分析 assignment1-basics ...

gitblog_00657的博客 455

【SpringBoot 3.x 第57节】Spring Boot 3 启动快得不讲武德?揭秘 Spring AOT 编译引擎背后的真相!

🏆本文收录于《滚雪球学SpringBoot 3》,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。    本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot3教程导航帖】,你想学习的都被收集在内,快速投入学习!!两不误。    若还想学习更多,可直接前往《滚雪球学SpringBoot(全版本合集)》,涵盖SpringBoot所有版本教学文章

**My Coding Family** 1760

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

cs336 作业1 注意事项

会将“.......!\n\n<|endoftext|>” 中两个连续的\n识别成两个单词,没有找到较好的解决方法,只能按照<|endoftext|>把整个文本切割开分别处理。而在“......!\n\n”中将两个连续\n识别到一个单词去。关于特殊字符\n\n<|endoftext|>的识别问题。

huachenyuyu1的博客 228

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

开源项目启动和配置教程

本项目是基于斯坦福大学CS336课程的第一份作业,主要涉及语言模型从零开始的实现。以下是项目的目录结构及其简要介绍: - `./`: 项目根目录 - `./cs336_basics`: 包含项目的主要代码文件 - `./data`: 存储项目所需的数据文件 - `./tests`: 包含单元测试的代码 - `./tests/adapters.py`: 用于连接实现代码与测试代码的

gitblog_00067的博客 377

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

斯坦福大学 | CS336 | 从零开始构建语言模型 | Spring 2025 | 笔记 | Assignment 1: BPE Tokenizer Implement

斯坦福大学 | CS336 | 从零开始构建语言模型 | Spring 2025 | 笔记 | Assignment 1: BPE Tokenizer Implement

周同学的博客 1690

斯坦福大学 | CS336 | 从零开始构建语言模型 | Spring 2025 | 笔记 | Assignment 1: BPE Tokenizer

斯坦福大学 | CS336 | 从零开始构建语言模型 | Spring 2025 | 笔记 | Assignment 1: BPE Tokenizer

周同学的博客 1383

Stanford CS336 | Assignment 2 - FlashAttention-v2 Pytorch & Triotn实现

算法层面:理解了分块计算和LogSumExp技巧如何将注意力的显存复杂度从O(T²d)降至O(Td),为处理长序列(如8k、16k)提供了可能;工程层面:掌握了Triton框架的核心用法——通过块指针高效访问内存、利用共享内存减少全局内存访问、设计合理的分块策略适配GPU硬件;性能层面:验证了FlashAttention在长序列场景下的显著优势,为Transformer模型的工程落地提供了关键优化手段。扩展方向支持多头注意力的融合计算(当前版本为单头,多头可通过维度拆分实现);

Bug_makerACE的博客 1312

Stanford CS336 | Assignment 1 - Transformer Language Model Architecture

本文介绍了基于Transformer的语言模型实现细节,重点剖析了核心组件与训练流程。主要内容包括: 模型架构:采用仅解码器的Transformer结构,包含RoPE位置编码、RMSNorm归一化和SwiGLU前馈网络等现代设计。 关键组件实现: 基础层:无偏置线性层、词嵌入层和RMSNorm归一化 注意力机制:旋转位置编码(RoPE)增强位置感知 前馈网络:SwiGLU激活函数提升表达能力 训练支持: 自定义AdamW优化器 学习率调度和梯度裁剪 断点续训功能 该实现从底层构建了完整的Transforme

Bug_makerACE的博客 1567

《斯坦福CS336》作业1开源,从0手搓大模型|代码复现+免环境配置

人工智能领域,被许多学习者视为“大模型学习的必修课”,不仅因为课程本身由一流学者讲授,更因为它以为主线,让学习者真正看清楚大模型的底层运作机制。:课程作业要求从零实现完整的模型组件,对基础和细节的掌握缺一不可;:公开的讲义和参考代码碎片化严重,缺乏连贯的学习路径;:很多人被运行环境和训练成本卡住。也正因如此,不少学习者虽然知道CS336的价值,但很难坚持学完。为了解决这些痛点,把课程作业完整地实现并公开,降低学习门槛;让学习者可以直接在社区中运行、修改、交流;配备视频讲解,更加直观易懂。

Rikki1013的博客 2191

斯坦福大学 | CS336 | 从零开始构建语言模型 | Spring 2025 | 笔记 | Assignment 1: Training Loop Implement

斯坦福大学 | CS336 | 从零开始构建语言模型 | Spring 2025 | 笔记 | Assignment 1: Training Loop Implement

周同学的博客 1167

CS336-assignment1-Training

本文介绍了Transformer语言模型训练中的关键组件实现。在损失函数部分,详细讲解了交叉熵损失的计算方法,包括数值稳定性的处理技巧。优化器部分实现了AdamW算法,分析了其相对于SGD的优势,并给出了完整的代码实现。学习率调度部分阐述了余弦退火调度器的三个阶段及其数学原理,并澄清了调度器与优化器学习率的关系。最后介绍了梯度裁剪的实现方法及其作用。这些组件共同构成了模型训练的基础框架。

fasac的博客 407

辅导 CS3331 – Assignment 1辅导 C/C++语言

(f) L = {w ∈ {a, b}* | #a (w) 三 2#b (w) (mod 3)} (do not give a reg. exp. here).

Ccio0612的博客 1062

斯坦福大学 | CS336 | 从零开始构建语言模型 | Spring 2025 | 笔记 | Assignment 1: Transformer LM Architecture

斯坦福大学 | CS336 | 从零开始构建语言模型 | Spring 2025 | 笔记 | Assignment 1: Transformer LM Architecture

周同学的博客 1042
上一篇: Stanford CS336 | Assignment 1 - BPE Tokenizer Training 实现
下一篇: Stanford CS336 | Assignment 1 - Transformer Language Model Architecture
ACEEE-1222
博客等级 码龄4年 432粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值