惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
有赞技术团队
有赞技术团队
Simon Willison's Weblog
Simon Willison's Weblog
人人都是产品经理
人人都是产品经理
L
LINUX DO - 最新话题
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
A
Arctic Wolf
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
小众软件
小众软件
Jina AI
Jina AI
The Cloudflare Blog
P
Palo Alto Networks Blog
AWS News Blog
AWS News Blog
阮一峰的网络日志
阮一峰的网络日志
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
NISL@THU
NISL@THU
G
GRAHAM CLULEY
Project Zero
Project Zero
博客园_首页
博客园 - 三生石上(FineUI控件)
罗磊的独立博客
Spread Privacy
Spread Privacy
WordPress大学
WordPress大学
Hugging Face - Blog
Hugging Face - Blog
Latest news
Latest news
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tor Project blog
S
Securelist
V
Vulnerabilities – Threatpost
T
The Exploit Database - CXSecurity.com
C
CERT Recently Published Vulnerability Notes
IT之家
IT之家
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Last Watchdog
The Last Watchdog
T
Tenable Blog
宝玉的分享
宝玉的分享
S
Secure Thoughts
P
Privacy & Cybersecurity Law Blog
量子位
大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Security Archives - TechRepublic
Security Archives - TechRepublic

K.I.S.S

身在 Kimi 的 800 天 写在 Kimi K2 发布之后:再也不仅仅是 ChatBot macOS 按域名切换 DNS 解析 | K.I.S.S 为什么说 GPT 是无损压缩 | K.I.S.S 握着你的手训一个类GPT语言模型 (二) | K.I.S.S Rosetta in Linux Virtual Machine on Apple Silicon Fit an Overfit with MegEngine 把 CUPS 扔进 docker 里 ASUS Chromebook Flip | K.I.S.S GPG 与 SSH Agent 转发 使用 dnsmasq 和 ipset 的策略路由 YubiKey 4 简介与配置 | K.I.S.S 定制GH60机械键盘 | K.I.S.S Debian 网络安装内核参数 | K.I.S.S 我的 Vim 配置 | K.I.S.S Libinput 与 Udev | K.I.S.S 给妹子看的 Arch Linux 桌面日常安装 | K.I.S.S 修复GTK3 CSD外观 | K.I.S.S 握着你的手教你画哀女王 | K.I.S.S R.I.P Google Reader | K.I.S.S Richard Stallman仍然是对的 | K.I.S.S Scala 自定义控制结构 | K.I.S.S 入手Wacom Bamboo CTL470 | K.I.S.S 转个型? | K.I.S.S Wish List | K.I.S.S pulseaudio音量问题 | K.I.S.S 入手yubikey,一点小心得 | K.I.S.S 迁移到Octopress | K.I.S.S 自动更新DNSPod记录 | K.I.S.S Linux 用作 IPv6 网关 | K.I.S.S Conky从豆瓣获取MPD专辑封面 | K.I.S.S 我和Linus大学时那些事儿 | K.I.S.S Micro$oft依然是那副老德行 | K.I.S.S 用PulseAudio TCP Forwarding实现网络传声 | K.I.S.S 利用tmpfs加速gnome-shell overview模式Applications索引 | K.I.S.S 新的短网址 | K.I.S.S 紧张的一个月 | K.I.S.S 5月11日活动有感 | K.I.S.S 西电开源社区2011年Tee 最终版 | K.I.S.S 多头多尾的Linux | K.I.S.S Linux的中文名称怎么翻译呢? | K.I.S.S iPhone使用OpenVPN | K.I.S.S 我的人人网敏感词 | K.I.S.S 纪念钱立生老师 | K.I.S.S twitter键盘控:Twittperator | K.I.S.S 天生的暴力倾向? | K.I.S.S The Open Source Spirit | K.I.S.S Libreoffice | K.I.S.S Hello world! | K.I.S.S Python 调用gnuplot的例子 | K.I.S.S The Beginning | K.I.S.S About Me | K.I.S.S
握着你的手训一个类GPT语言模型 (一) | K.I.S.S
2023-03-12 · via K.I.S.S

这段时间 ChatGPT 大有 AI 奇点将至的势头,它展现的各种能力也的确很惊人,让人忍不住想要复刻一个。不过我从来没搞过 NLP 相关领域,机缘巧合在 youtube 上看见 Andrew Karpathy 的视频 Let’s build GPT: from scratch, in code, spelled out ,非常详细地从零开始展示了如何实现一个 Decoder-Only 的 Transformer 语言模型。

原视频用的是 TinyShakespeare 语料库,训完之后的语言模型可以鹦鹉学舌输出莎翁文风的文字。我周末花了几小时复现了一下原视频的工作,并把语料库换成《水浒传》《三体》之类的中文小说,也颇能像模像样地胡说八道一些东西出来。 Karpathy 有个开源项目叫 NanoGPT,我这个小作业更小一些,就起名叫 PicoGPT :)

本文主要就是对原视频的简要概括,同时讲讲复现过程。

文字的向量化表示

Linus 有言: “烂程序员关心的是代码。好程序员关心的是数据结构和它们之间的关系。” 研究一个问题,先从研究对象的数据表示开始。假设 input.txt 是一份语料库,例如 TinyShapespeare,里面就是一个个英文字母和标点符号,因此可以我们可以搞一种最 Naïve 的数据表示: 字符序号表示。

with open("input.txt") as f:
    text = f.read()
vocabulary = sorted(list(set(text)))
vocab_size = len(vocabulary)
char2idx = {c: idx for idx, c in enumerate(vocabulary)}
idx2char = {idx: c for idx, c in enumerate(vocabulary)}

在以上代码中,把语料库中的文字按字符去重、排序就得到了词汇表,这样每个字符就有了唯一序号。当我们想要表示一串文本的时候,直接每个文本查表得到一个序号表就行了,这个过程称为 tokenize。

def tokenize(s: str) -> List[int]:
    return [char2idx(c) for c in s]

总之,经过 Tokenize 之后,一串文本就变成了一串整数组成的向量。更高级的 Tokenizer 原理其实也类似,只是编码效率更高、支持更大的词汇表、计算性能也更高,例如 OpenAI 的 Tiktoken

N-Gram 语言模型

给定一个词汇表,从概率角度考虑一个“语言”的建模。假设有一个胡说八道机器,如果它每次都从词汇表中均匀采样输出文本,显然它很容易输出无意义的乱码;但是如果它每次都能从一个“特定语言的概率分布”中完美地采样输出文本,那么每个样本看起来应该都是一段合理的文字。所以,一种简单的概率角度语言建模,就是每个字符的概率粉笔,以及在给定“前文”的情况下,后续字符的概率分布。

N-Gram 是一种经典的语言模型,不加公式地解释就是:“当前位置各字符的分布概率,和它之前的连续N个字符相关”。这样的分布在语料库中有大量的现成样本,写成代码很容易理解:

end = torch.randint(len(text)-1)
context = text[end-N:end]
target = text[end]
print(f"when text is {context}, target is {target}")

任意取一段长度为N的文本 context 和它的后续字符 target,那么 <context, target> 就是 N-gram 模型的一个样本。基于深度学习的语言模型学习,就是从大量这样的样本中,尝试拟合出语言本身的概率分布,进而就可以使用采样方法,一本正经地胡说八道了。

词嵌入

前文提到,经过 Tokenize 之后,每一个字符都变成了一个整数,它的好处是连续、有序并且非常容易索引。但是我们尝尝希望字符的数学表示能有一些更高级的功能,例如:

  • 字符的表示能通过简单的数学计算“连接”起来,表示一个“词”
  • 不论一个“词”由多少字符组成,它都能在同一个空间内做计算,且计算结果也在同一个空间内
  • 意思相近的词,在数学表示上也“近”

一种经典建模方法,就是从字符的整数索引为基础,查表得到一个向量,这样就很容易使用神经网络把一串字符向量处理成任意级别的向量。通过有监督学习的方式,很容易给这些向量赋予训练者想要的“功能”。这种“查表得到向量”的方式就成为嵌入(Embedding),写成 torch 代码:

embedding_table = torch.nn.Paramter(torch.randn((vocab_size, embed_size)))
idxes = tokenize("Some Text")
char_embedding = torch.index_select(embedding_table, dim=0)

最简示例

基于以上内容,就可以做一个最简单朴素的神经网络语言模型了:

from torch import nn

class MinimumModel(nn.Module):

    def __init__(self, vocab_size: int, embedding_size: int = 128):
        super().__init__()
        self.embedding = nn.Embedding((vocab_size, embedding_size))
        self.fc_out = nn.Linear(embedding_size, vocab_size)

    def forward(self, inputs: torch.Tensor) -> torch.Tensor:
        # input: (batch_size, block_size), value ranges in [0, vocab_size)
        # output: (batch_size, block_size, vocab_size)
        token_emb = self.embedding(inputs)  # (batch_size, block_size, embedding_size)
        logits = self.fc_out(token_emb) # (batch_size, block_size, vocab_size)
        return logits

    def loss_fn(self, logits: torch.Tensor, targets: torch.Tensor) -> torch.Tensor:
        # logits: (batch_size, block_size, vocab_size)
        # targets: (batch_size, block_size)
        return F.cross_entropy(logits, targets)

    def sample(self, max_size: int, start_token: int = 0) -> torch.Tensor:
        tokens = torch.tensor(start_token).reshape(1, -1)
        for _ in range(max_size):
            logits = self.forward(tokens)
            logit_next = logits[:, -1:, -1]
            prob = F.softmax(logit_next, dim=-1)
            token_next = torch.multinomial(prob, num_samples=-1)
            tokens = torch.cat([tokens, token_next], dim=1)
        return tokens[0]

上述代码搞了个只有两层的神经网络,embedding 完成前文提到的嵌入索引,fc_out 再把字符向量做一次变换,输出一个维度和字符表相等的向量。在采样(sample方法)时,神经网络每次输出的向量们我们只选最后一个,经过 softmax 归一化成一个概率分布,就认为它是 “下一个字符的概率分布” 了,从该分布中采样,就得到了下一个字符;把生成的字符和输入拼在一起,再次交给神经网络就完成了下一次推理。从一个种子开始,循环不断地推理、采样,一个胡说八道机器就做好了。

由于这个胡说八道机未经任何训练,所以它只会随机输出文本,因此需要从语料库中选取大量的成对样本 <context, target>,先把 context 输入网络,得到输出 logits,再调用 loss_fn 评价它和 target 的距离,使用优化器让 loss 越来越小,那么网络的输出就会逐渐地和 target 接近,渐渐地就会一本正经地胡说八道了。

小结

至此,我们建立了一个最简单的胡说八道机,基本上对应 Karpathy 视频 中前40分钟的内容。在这个最简模型中几乎只有一个“变换”步骤 (fc_out),视频的后续内容介绍了 Transformer 结构,使用更复杂的计算从字符向量中提取有效信息,进而对语言做更准确地建模。

未完待续……