SONUB
ZH English 한국어 日本語 中文 हिन्दी বাংলা اردو Español Português Français Deutsch Русский العربية Bahasa Indonesia Tiếng Việt ไทย Türkçe

如何开启通知

此浏览器或网址无法使用通知。请使用支持通知的浏览器,通过安全连接访问SONUB。

如果通知被屏蔽,请在浏览器设置中允许通知。

  1. 打开地址栏旁的站点设置或浏览器的站点设置,将此网站的通知设为允许。
  2. 在设备设置中允许浏览器发送通知,并检查勿扰模式或专注模式。
  3. 返回SONUB并刷新页面。通知开启后,铃铛将不再显示。

在iPhone或iPad上,将SONUB添加到主屏幕,从主屏幕打开后允许通知。

登录 退出登录 显示主题 默认主题 深色主题 粉色主题 聊天 1:1 聊天 群聊 论坛 公告 新闻区 建议区 自我介绍 主菜单

现在,让我们打造属于你自己的人工智能吧。

之前,先亲手创建、和设置文件。建立 ~/xslm,写好 settings.json 和 prepare.py,再把问答文本分成和。

一排词语积木的最后是一个空格。空格上方有三块候选积木,各带一根高低不同的柱子;其中一块被选中,接到这一排的末尾,一个箭头又绕回去,再选下一个。
3

项目结构

源码放~/xslm,资料放,放checkpoints。始终在执行。

每行一个Q:问题与A:答案。prepare.py去重后每五条留一条验证;验证资料不参与。使用有权使用的文字和虚构示例。

文件夹结构——每个文件由谁、何时生成

~/xslm 文件夹全部做完后是这样的。

  • settings.json —— 自己写。所有都读取的设置。
  • data/raw.txt —— 自己写。原始问答。
  • data/train.txt、data/val.txt —— 由 prepare.py 生成。训练用和验证用。
  • data/tokenizer.json —— 由 tokenizer.py 生成。和。
  • checkpoints/model.json —— 由 train.py 生成,每次训练都会更新。权重和训练状态。
  • runner.log、runner.pid —— 在后台启动 时产生。输出记录和编号。
  • 其余 .py 文件 —— 自己写。程序源码。
  • lab/ —— 各阶段的验证实验(可选)。

把源码、资料和结果分开放,就清楚哪些可以删。data/、checkpoints/ 中生成的文件随时能重新生成,但自己写的源码和 raw.txt 无法重新生成,请用导出保存。

运行顺序与需要重做的部分——改了什么就要重做什么

文件从前往后相连,所以改了前面,后面的都要重新生成。

  • 改了 raw.txt:prepare.py → tokenizer.py → 移走检查点再运行 train.py。
  • settings.json 有任何改动(merges、width、、steps、 都算):改的是 merges 就从 tokenizer.py 开始,否则直接移走检查点再运行 train.py。
  • 只想多训练:settings.json 保持不变,像 train.py --steps 50 这样用选项指定次数。会从检查点继续训练。
  • 多训练之后:要把正在运行的 Runner 关掉再启动,才会读取新权重。

train.py 会亲自检查这条规则。检查点中保存的全部设置、分词器、资料指纹,只要有一个和现在不同,就用 停止。因为用改过的资料或设置继续训练旧权重,的含义或形状会错位,悄无声息地坏掉。想重新开始,就像 checkpoints/model.json checkpoints/old.json 这样把旧文件移走。

数据格式造就能力——Q: … | A: …

raw.txt 的一行是 "Q: 问题 | A: 答案"。prepare.py 会在 " | A:" 前,所以在训练资料里变成两行。

  • Q: hi
  • A: hi friend.

反复看到这种形状,学会"Q: 行的下一行以 A: 开头,A: 后面接像答案的文字,然后换行"。生成时在 Q: 问题的下一行放上 "A: ",模型就会按学到的习惯接着写答案。

格式可以自由决定。也可以用 [QUESTION]…[/QUESTION]、[ANSWER]…[/ANSWER] 这样的标记。要遵守的只有一致性。

  • 每一行都用相同的标记和相同的顺序。
  • 同样的意思用同样的词。例如把电话、Tel、☎ 全部统一为 TEL:。
  • 改了格式,就同时改使用该格式的。 中 generate.py 的默认和 xchat.py 的 "Q: … A: " 都绑定在这个格式上。

资料中从输入到输出的形状要多,模型才能学会那件事。只罗列文字就学续写,问题→答案就学回答,文档→总结就学总结。

好资料与坏资料——数量、质量、权利

模型只模仿见过的东西。就像只听日语长大的人不会说中文一样。所以资料就是模型。

  • 编码:必须用 保存。用别的编码会出现乱码。
  • 好资料:格式一致的问答,含有信息的自然句子。
  • 坏资料:只有代码或的文字,表情符号或网址过多的文字,残缺的句子,同一句话反复出现。
  • 整理:去掉首尾空格和空行,相同的行只保留一次。prepare.py 负责这件事。
  • 多样性:同一个答案用多种问法写(hi、hello、hey)。这样才有可能回答没见过的问题。
  • 权利与个人信息:只用自己有权使用的文字,用虚构例句代替真实姓名和电话号码。

需要的数量比想象中多。粗略来说,问答约 3,000 对时开始模仿格式,约 1 万对时格式稳定。XSLM 的 15 行是用来确认整个过程真的能跑的极小练习资料。一开始先用这样的小型假资料跑完全程,再增加资料。

训练资料与验证资料——留一份没见过的考卷

只用训练资料打分,就分不清是背下来的还是学会的。所以拿出一部分作为不用于训练的验证资料()。训练下降而上升,就是开始死记()的信号。

prepare.py 这样划分。

  • 给去重后的行从 0 开始编号。
  • 编号除以 5 余 0 的行(第 0、5、10 行)作为验证用,其余作为训练用。默认资料 15 行分成训练 12 行、验证 3 行。
  • 在转换成之前,把一对问答整体划分。如果按字符切分,一对的前半进入训练、后半进入验证,考题就泄露了()。
  • 分词器也只看训练资料来学习。验证资料直到最后的考试都没人看。

资料非常大时,也会把全部词元末尾的 1% 切出来作为验证用。资料小时,像 XSLM 这样按问答对划分更安全。

大资料要事先转成词元编号

XSLM 每次运行 train.py 都把整个 train.txt 转成词元编号。资料只有几百个字符,瞬间完成。资料到了几十 ,每次转换就是浪费,所以只转换一次并存成文件。

  • 把词元编号存成 uint16(2 整数)。词表少于 65,536 个就能放进 2 字节。
  • 不是文字而是数字数组,所以成为 train.bin、val.bin 这样的。
  • 训练时用 numpy.memmap,不把文件全部载入,只读需要的部分。

资料再大,就分成多个文件依次训练。流程相同:文字 → 词元编号 → 固定长度片段 → 训练。

换成自己的资料——用中文问答试试

完成一遍课程后,把 raw.txt 换成自己的资料试试。

  • 每行写一个 "Q: 问题 | A: 答案",写五行以上互不相同的行。prepare.py 的 assert 会检查格式和行数。
  • 答案写短一些。 是 12 个词元,模型只看最后 12 个词元。在基于字符的 中,汉字一开始一个字就是一个词元,问题和答案太长就看不到前面的部分。
  • 重新生成:python3 prepare.py → python3 tokenizer.py → mv checkpoints/model.json checkpoints/old.json → python3 train.py。
  • 训练资料中没有的字,分词器会换成未知字符(0 号)。提问要用的字,也要放进资料里。

像中文这样字符种类多的语言,词表会变大,相同的对重复得少,合并容易提前停止。这时就增加行数、增加 merges 试试。

动手输入并实验

先建,再输入下方各文件:.py文件用,settings.json和raw.txt用。保存数据和脚本后执行prepare.py,注意标点。

mkdir -p ~/xslm/data ~/xslm/checkpoints
cd ~/xslm
nano settings.json
nano data/raw.txt
micro prepare.py
python3 prepare.py
wc -l data/train.txt data/val.txt
在终端中练习
settings.json需要亲手编写的完整源码

width是宽度,context是上下文长度,learning_rate是更新幅度。续训保持设置一致。

{
    "width": 8,
    "context": 12,
    "merges": 12,
    "steps": 20,
    "learning_rate": 0.03,
    "seed": 7,
    "tokens": 24,
    "max_tokens": 64,
    "port": 8765
}
data/raw.txt需要亲手编写的完整源码

每行一个问答对。这些简短虚构英文可以换成自己的资料。

Q: hello | A: hello friend.
Q: hi | A: hi friend.
Q: name | A: i am xslm.
Q: sun | A: the sun is warm.
Q: cat | A: a cat can nap.
Q: dog | A: a dog can run.
Q: red | A: red is a color.
Q: blue | A: blue is a color.
Q: learn | A: i learn from text.
Q: code | A: i can learn code.
Q: moon | A: the moon is in the sky.
Q: rain | A: rain is water.
Q: book | A: i can read a book.
Q: tea | A: tea is warm.
Q: bye | A: bye friend.
prepare.py需要亲手编写的完整源码

读取UTF-8、去重、按完整记录划分训练和验证,避免同样例子同时出现。

from pathlib import Path


lines = [line.strip() for line in Path('data/raw.txt').read_text(encoding='utf-8').splitlines() if line.strip()]
lines = list(dict.fromkeys(lines))
assert len(lines) >= 5, 'write at least five different Q/A lines'
assert all(' | A:' in line and line.startswith('Q:') for line in lines)
train = [line for i, line in enumerate(lines) if i % 5 != 0]
val = [line for i, line in enumerate(lines) if i % 5 == 0]
for name, rows in [('train', train), ('val', val)]:
    text = '\n'.join(row.replace(' | A:', '\nA:') for row in rows) + '\n'
    Path('data/' + name + '.txt').write_text(text, encoding='utf-8')
    print(name, len(rows))

逐行拆解代码

把亲手输入的源码分成小段来读。左边的数字是文件中真实的行号。可以对照里的 line 数字来读。

prepare.py 读取原始资料并去重

from pathlib import Path


lines = [line.strip() for line in Path('data/raw.txt').read_text(encoding='utf-8').splitlines() if line.strip()]
lines = list(dict.fromkeys(lines))

用 Path(…).read_text(encoding='utf-8') 把整个文件读成文字,用 splitlines() 按行分开。推导式 [line.strip() for line in … if line.strip()] 去掉首尾空格并丢弃空行。dict.fromkeys(lines) 把行作为放入来去重, 保持插入顺序,所以最先出现的顺序原样保留。用 set 的话,顺序每次运行都可能不同,训练和验证的划分可能会变。

prepare.py 格式检查

assert len(lines) >= 5, 'write at least five different Q/A lines'
assert all(' | A:' in line and line.startswith('Q:') for line in lines)

两个 assert 检查资料。要有五行以上互不相同的行,才能拿出至少一行作为验证用。all(…) 只有在每一行都以 Q: 开头并包含 " | A:" 时才为真。只要有一行不对,训练格式就会混乱,所以在这里停下更好。第二个 assert 没有消息,只会显示 AssertionError。这时请检查 raw.txt 的格式。

prepare.py 划分训练与验证

train = [line for i, line in enumerate(lines) if i % 5 != 0]
val = [line for i, line in enumerate(lines) if i % 5 == 0]

enumerate(lines) 依次给出(编号, 行)。i % 5 是编号除以 5 的余数,所以第 0、5、10…行是 val,其余是 train。15 行就是 12 行和 3 行。不随机挑选而按规则划分,所以运行多少次都得到相同的划分。

prepare.py 保存为两行格式

for name, rows in [('train', train), ('val', val)]:
    text = '\n'.join(row.replace(' | A:', '\nA:') for row in rows) + '\n'
    Path('data/' + name + '.txt').write_text(text, encoding='utf-8')
    print(name, len(rows))

对 train、val 分别处理:把每行的 " | A:" 换成换行 + "A:",让问题和答案变成两行;把各对用换行连接,最后再加一个换行后保存。最后一个答案也必须以换行结束,模型才能在所有答案中学到"答案之后是换行"。 显示写了多少对。

检查理解

用与检查。示例分为12条训练、3条验证记录。

遇到问题时

时检查 ~/xslm及ls data。用四空格。~/xslm-reference仅供对照,不自动填入项目。

常见问题

15 行真的能训练吗?

用来确认流程能跑足够了,用于像样的对话远远不够。训练资料只有 12 对,模型很快就想原样背下来,验证损失反而上升(第6阶段"过拟合")。行数越多,学规则而不是死记的空间越大。

为什么分词器训练也要排除验证资料?

分词器也是一种训练。如果只在验证句子里出现的字符组合进入合并规则,验证句子就会被转成比训练句子更短、更整齐的词元,考试就变容易了。要模拟真实服务中收到没见过的文字的情况,验证资料必须从所有训练环节中排除。

出现 assert 错误 "write at least five different Q/A lines"。

prepare.py 要有五行以上互不相同的行,才能分成训练和验证。空行和重复行不计。如果是格式 assert 报错,检查每一行是否都以 Q: 开头并包含 " | A:"。连空格都要一样。

学习指南

人工智能

智能体工具

Vibe 编程

AI 应用

AI证书

AI课堂

IT 基础

聊天

论坛

创作

社区

更多

术语词典

选一个你不认识的词吧,可以看到简单的解释和相关术语。

还没有匹配的术语。

选择一个术语即可查看解释。