Go语言实现大模型分词器tokenizer

news2025/1/23 3:25:01

文章目录

  • 前言
  • 核心结构体定义
  • 构造函数
  • 文本初始处理
  • 组词
  • 构建词组索引
  • 训练数据
  • 编码
  • 解码
  • 打印状态信息
  • 运行效果
  • 总结

前言

大模型的tokenizer用于将原始文本输入转化为模型可处理的输入形式。tokenizer将文本分割成单词、子词或字符,并将其编码为数字表示。大模型的tokenizer通常基于词表进行编码,使用词嵌入将单词映射为向量表示。tokenizer还可以将输入文本进行填充和截断,以确保所有输入序列的长度一致,以便于模型的批量处理。

这篇博客的tokenizer分析器使用纯粹的Go语言标准库实现,不借用任何其它第三方库。用轮子是生活,造轮子是信仰。

核心结构体定义

type BytePairEncoder struct {
	wsToken  string
	unkToken string
	// k: word, v: tokens
	wordToken map[string]*[]string
	// k: word, v: count
	wordCount map[string]int
	// k: token, v: count
	tokenCount map[string]int
	// k: id, v: token
	idToken map[int]string
	// k: token, v: id
	tokenId map[string]int
}

构造函数

func DefaultBytePairEncoder() *BytePairEncoder {
	return NewBytePairEncoder("_", " ")
}

func NewBytePairEncoder(wsToken, unkToken string) *BytePairEncoder {
	return &BytePairEncoder{
		wsToken:    wsToken,
		unkToken:   unkToken,
		wordToken:  make(map[string]*[]string),
		wordCount:  make(map[string]int),
		tokenCount: make(map[string]int),
		idToken:   make(map[int]string),
		tokenId:   make(map[string]int),
	}
}

文本初始处理

func (e *BytePairEncoder) wordToTokens(word string) *[]string {
	parts := []rune(word)
	n := len(parts)
	res := make([]string, n)
	for i := 0; i < n; i++ {
		token := string(parts[i])
		e.tokenCount[token]++
		res[i] = token
	}
	return &res
}

func (e *BytePairEncoder) preprocess(text string) []string {
	text = strings.TrimSpace(text)
	return strings.Fields(text)
}

func (e *BytePairEncoder) processWord(word string) {
	e.wordToken[word] = e.wordToTokens(word)
	e.wordCount[word]++
}

func (e *BytePairEncoder) initState(text string) {
	words := e.preprocess(text)
	for _, word := range words {
		e.processWord(e.wsToken + word)
	}
}

组词

func (e *BytePairEncoder) mergePair() {
	// k: token, v: count
	m := make(map[string]int)
	for word, tokens := range e.wordToken {
		n := len(*tokens) - 1
		for i := 0; i < n; i++ {
			m[(*tokens)[i]+(*tokens)[i+1]] += e.wordCount[word]
		}
	}

	maxToken := ""
	maxCount := 0
	for k, v := range m {
		if v > maxCount {
			maxToken = k
			maxCount = v
		}
	}

	if maxCount < 2 {
		return
	}

	e.tokenCount[maxToken] = maxCount

	for _, tokens := range e.wordToken {
		n := len(*tokens) - 1
		for i := 0; i < n; i++ {
			if (*tokens)[i]+(*tokens)[i+1] == maxToken {
				e.tokenCount[(*tokens)[i]]--
				e.tokenCount[(*tokens)[i+1]]--
				post := (*tokens)[i+1:]
				post[0] = maxToken
				*tokens = (*tokens)[:i]
				*tokens = append((*tokens), post...)
				*tokens = (*tokens)[:len(*tokens)]

				i--
				n -= 2
			}
		}
	}
}

func (e *BytePairEncoder) merge(steps int) {
	for i := 0; i < steps; i++ {
		e.mergePair()
	}
}

构建词组索引

func (e *BytePairEncoder) buildIndex() {
	e.tokenId[e.unkToken] = 0
	e.idToken[0] = e.unkToken
	i := 1
	for token := range e.tokenCount {
		e.tokenId[token] = i
		e.idToken[i] = token
		i++
	}
}

训练数据

func (e *BytePairEncoder) Train(text string, steps int) {
	e.initState(text)
	e.merge(steps)
	e.buildIndex()
}

编码

func (e *BytePairEncoder) segment(words []string) []int {
	res := make([]int, 0)
	for _, word := range words {
		parts := []rune(word)
	NEXT:
		for i := len(parts); i >= 1; i-- {
			if code, ok := e.tokenId[string(parts[:i])]; ok {
				parts = parts[i:]
				res = append(res, code)
				goto NEXT
			}
		}
		if len(parts) == 0 {
			continue
		}
		code := e.tokenId[string(parts[0])]
		res = append(res, code)
		parts = parts[1:]
		if len(parts) != 0 {
			goto NEXT
		}
	}

	return res
}

func (e *BytePairEncoder) Encode(text string) []int {
	words := e.preprocess(text)
	return e.segment(words)
}

解码

func (e *BytePairEncoder) Decode(codes []int) []string {
	res := make([]string, 0)
	for _, code := range codes {
		res = append(res, e.idToken[code])
	}

	return res
}

打印状态信息

func (e *BytePairEncoder) Dump() {
	fmt.Println("===== dump state ======")
	fmt.Println("===> dump wordToken <===")
	for word, tokens := range e.wordToken {
		fmt.Println(word, "=>", *tokens)
	}
	fmt.Println()
	fmt.Println("===> dump wordcnt <===")
	for word, count := range e.wordCount {
		fmt.Println(word, "=>", count)
	}
	fmt.Println()
	fmt.Println("===> dump tokenCount <===")
	for token, count := range e.tokenCount {
		fmt.Println(token, "=>", count)
	}
	fmt.Println()
	fmt.Println("===> dump idTokens <===")
	for code, token := range e.idToken {
		fmt.Println(code, "=>", token)
	}
	fmt.Println()
	fmt.Println("===> dump tokenIds <===")
	for token, code := range e.tokenId {
		fmt.Println(token, "=>", code)
	}
	fmt.Println()
}

运行效果

我们的tokenizer已经开发完毕,现在可以运行我们的tokenizer,看看是否能达到我们想要的效果

package main

import (
	"fmt"
	"os"
	"tokenizer"
)

func main() {
	trainData, err := os.ReadFile("./data.txt")
	if err != nil {
		panic(err)
	}
	steps := 50
	enc := tokenizer.DefaultBytePairEncoder()
	enc.Train(string(trainData), steps)
	input := "提取数据特征进行预测"
	codes := enc.Encode(input)
	tokens := enc.Decode(codes)
	fmt.Println(codes)
	fmt.Println(tokens)
}

输入数据集
data.txt

机器学习、深度学习和强化学习是人工智能领域中的三个重要技术方向。以下是它们的区别:
机器学习:机器学习是一种通过从数据中自动学习模式和规律来进行预测和决策的方法。它涉及到使用算法和统计模型,从数据中提取特征并进行模型训练,进而对未知数据进行预测或分类。机器学习的重点在于自动学习和泛化能力,它不需要明确的指令或规则来执行任务,而是通过数据和经验来改善性能。
深度学习:深度学习是机器学习的一个分支,它使用包含多个神经网络层的深度神经网络进行学习和预测。深度学习模型通过层层堆叠的方式,从原始数据中学习到多个抽象层次的特征表示。深度学习的优势在于可以自动提取复杂的特征,并通过大规模数据的训练来优化模型性能。它被广泛应用于计算机视觉、自然语言处理和语音识别等领域。
强化学习:强化学习是一种机器学习的方法,旨在让机器学习从环境中的交互中通过试错来改善性能。它通过不断与环境进行交互,观察环境状态并执行动作,然后从环境的反馈中学习如何在给定环境中做出最优的决策。强化学习的目标是通过学习最优的策略来最大化累积奖励。强化学习在游戏、机器人控制和优化问题等领域有着广泛应用。
总的来说,机器学习是从数据中学习模式和规律,深度学习是机器学习的一种方法,使用深度神经网络来提取复杂的特征表示,强化学习是通过试错学习从环境中改善性能。

运行效果
在这里插入图片描述

可以根据情况使用Dump函数打印状态信息查看更多细节

总结

恭喜你已经制作了一个属于自己的tokenizer分词器,我们实现的相对粗糙一点,但是对于初学者是难得的实战项目,麻雀虽小,五脏俱全。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1268500.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows 10和11的一个专用的设置菜单,让清理空间变得方便快捷

需要在Windows电脑上释放一些磁盘空间吗?Windows 10和Windows 11都提供了一个专用的设置菜单,使过程更容易。从该菜单中,你可以查看设备上使用了多少空间以及内容类型。 Windows中的“存储”设置还允许你快速清除空间,并启用“存储感知”自动删除临时文件和回收站项目。这…

uniapp 打包的 IOS打开白屏 uniapp打包页面空白

uniapp的路由跟vue一样,有hash模式和history模式, 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。 如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。…

小间距LED屏幕需要解决的五大芯片问题

随着微距LED电子显示屏的像素间距逐渐缩小&#xff0c;对封装技术提出了更高的要求&#xff0c;LED灯珠和芯片尺寸也需要进一步减小。由此引发的显示性能、产品品质、一次性通过率、亮度和灰度等问题都需要通过先进芯片技术来解决。那么&#xff0c;什么是微距LED显示屏&#x…

【Android知识笔记】架构专题(一)

什么是 MVC 其实我们日常开发中的Activity,Fragment和XML界面就相当于是一个MVC的架构模式,但往往Activity中需要处理绑定UI,用户交互,以及数据处理。 这种开发方式的缺点就是业务量复杂的时候一个Activity过于臃肿。但是页面结构不复杂的情况下使用这种方式就会显得很简…

C++基础 -16- 类的继承

类的继承格式 派生可以通过构造函数给基类的私有成员赋值 #include "iostream"using namespace std;class person {public:person(int a,int b):a(a),b(b){cout << "person-build" << endl;}protected:int a;int b; };class newperson:publi…

面试:说一下深拷贝,浅拷贝,引用拷贝吧;Object类中的clone是哪种呢?

目录 深拷贝、浅拷贝、引用拷贝Object类的clone()方法 深拷贝、浅拷贝、引用拷贝 ● 浅拷贝&#xff1a; 对基本数据类型进行值传递&#xff1b; 对引用类型&#xff0c;复制了一份引用类型的变量 里面存储的内存地址一样 指向的对象也一样。 ● 深拷贝&#xff1a;对基本数据…

轻量封装WebGPU渲染系统示例<39>- emissive和arm纹理支持(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/DynamicShaderBuilding2.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下&#xff1a; export class DynamicShaderBuildi…

uniapp ios 授权弹窗 uniapp弹出框怎么实现

新版本的信息弹窗组件 可以弹出很多条信息&#xff0c;并单独控制消失时间、点击消失。 用循环来生成很多个弹窗&#xff0c;用this.$refs来传值&#xff0c;并添加数组。 1.布局 2.js 具体流程。需要一个弹窗&#xff0c;基本信息传入组件&#xff0c;处理后添加入数组&am…

Android关于杀掉进程的方案

《风波莫听穿林打叶声》—— 苏轼 〔宋代〕 三月七日&#xff0c;沙湖道中遇雨&#xff0c;雨具先去&#xff0c;同行皆狼狈&#xff0c;余独不觉。已而遂晴&#xff0c;故作此词。 莫听穿林打叶声&#xff0c;何妨吟啸且徐行。 竹杖芒鞋轻胜马&#xff0c;谁怕&#xff1f;一蓑…

“前端已死”?从ChatGPT与低代码平台看未来编程之路

每隔一段时间&#xff0c;“前端已死”的论调就会如潮水般重新涌现&#xff0c;引发行业内外的热议。这种论调背后&#xff0c;除了对于新技术&#xff0c;如ChatGPT、GPT-4对前端开发者影响的担忧&#xff0c;还反映了人们对于技术变革的焦虑。 作为前端开发者&#xff0c;我坚…

华清远见嵌入式学习——C++——作业2

作业要求&#xff1a; 代码&#xff1a; #include <iostream>using namespace std;class Rect { private:int width;int height;public:void init(int w,int h);void set_w(int w);void set_h(int h);void show(); };void Rect::init(int w,int h) {width w;height h;…

Elk:filebeat 日志收集工具和logstash

Elk:filebeat 日志收集工具和logstash Filebeat是一个轻量级的日志手机工具,所使用的系统资源比logstash部署和启动时使用的资源要小得多 Filebeat可以在非java环境使用&#xff0c;他可以代理logstash在非java环境上收集日志 缺点 Filebeat无法实现数据的过滤,一般是结合l…

修复 MyBatis 中空值引起的 SQL 语法错误

修复 MyBatis 中空值引起的 SQL 语法错误 背景 最近在查看别人的项目代码时&#xff0c;遇到了一个问题&#xff1a;数据库中的数据为空。在调试过程中&#xff0c;发现问题出现在调用 MyBatis 中的方法&#xff1a;listByIds(Collection<? extends Serializable> idL…

90基于matlab的无迹卡尔曼滤波器参数估计的非线性最小二乘优化

基于matlab的无迹卡尔曼滤波器参数估计的非线性最小二乘优化&#xff0c;数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 90matlab无迹卡尔曼滤波器参数估计 (xiaohongshu.com)

详解Python中print和return的作用及区别

文章目录 关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道 print的作用是输出数据到控制端,就是打印在你能看到的…

【接口技术】实验3:可编程并行接口8255

实验3 可编程并行接口8255实验 一、实验目的 1&#xff1a;了解8255芯片结构及编程方法。 2&#xff1a;了解8255输入/输出实验方法。 3&#xff1a;掌握8255控制键盘及显示电路的基本功能及编程方法。 4&#xff1a;掌握一般键盘和显示电路的工作原理。 二、实验内容 1&…

实验一 SAS 基本操作和数据表的导入 2023-11-29

一、上机目的 熟悉SAS的集成环境并掌握它的基本操作。理解SAS程序的结构&#xff0c;理解其中的过程&#xff0c;过程选项&#xff0c;语句&#xff0c;语句选项等概念&#xff0c;掌握SAS编程技术。 二、上机内容 主要有SAS操作界面、SAS窗口操作、SAS菜单操作、SAS按钮操作…

LLM三阶段训练代码汇总

在进行大模型的阶段训练时,从头编写代码有点浪费时间。为了更高效地实现这一目标,我们可以利用GitHub上已有的现成代码。下面对现成的代码库进行总结。 欢迎关注公众号 1. LLaMA-Factory LLaMA Factory: 轻松的大模型训练与评估 https://github.com/hiyouga/LLaMA-Factory …

Stable Diffusion绘画系列【7】:极致东方美学

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

Python中对数组连续赋值的问题

问题描述 在python中&#xff0c;首先用两个等号对两个数组进行初始化并赋值。之后&#xff0c;对任何一个数组进行赋值&#xff0c;都会将其赋予相同值。 import numpy as np Array1 Array2 np.empty(2) Array1[0],Array2[0]70,80 print(Array1[0],Array2[0])80.0 80.0 …