当前位置: 首页 > news >正文

Transformer核心概念I-token

2017年Transformer的诞生宣告了人工智能大模型阶段的开启,目前已经进入了大模型阶段的中期了,基于Transformer的算法创新不断推陈出新。在这过程中,对Transformer核心概念的理解显得尤为重要。接下来对Transformer模型中的核心概念逐一进行介绍。

1. 什么是Token

根据个人对Transformer模型的理解,对Token作如下定义:
Token是输入Transformer模型的数据的基本单位。
对Token的理解:
(1) Token是对现实对象重新离散化的数据单位。
现实对象要能够进入计算机中进行处理必须要转换为相应的数据格式,如何将现实对象转换为相应的数据格式这个问题已经被解决的比较彻底了,例如,图像数据以3个二维矩阵表示采集到的每个点的颜色,文字采用Unicode表示各个国家不同的文字和符号,温度采用二进制离散化进行表示,这些已经属于不是的问题的问题了。
在无论是在统计学习模型中还是在深度学习模型中,这些现实对象都是直接将对应的计算机数据直接输入到相应的模型中,也就是在统计学习模型和深度学习模型输入的数据是静态的,是直接将现实对象对应的计算机数据进行处理的。
在Transformer的模型中,实现对象对应的计算机数据并不是直接就输入到模型中,需要进行Tokenization操作从而得到Token,进行Tokenization操作的算法或是过程叫做Tokenizer。因此Transformer接收的数据Token是动态的。
(2) Token可以更好的揭示现实对象中的内部关系。
Token是通过Tokenizer算法依据原有数据集整体的条件下得到的,在这个过程中Tokenizer算法对数据根据某种特性重新进行关联与组合,这使得Token本身把现实对象分成了不同的部分。需要进一步说明的是,Token的产生既取决于Tokenizer算法还取决于数据集整体的情况。
在这里举一个简单的例子:英语“lower,higher”这两个单词组成的语料库,转换为模型可以处理的数据的过程
(1) 在使用传统的数据表示方式下:将把单词中每个字母的unicode编码作为数据,也就是1个字母占据一个字节,也就是“lower”一共5个字节,“higher”一共6个字节。
(2) 使用Token的方式下:采用目前比较主流的BPE(后续还会提到)算法作为Tokenizer算法,根据语料库的出现频率高低,将“lower,higher”分割为3个token:

low
high
er

然后针对分割后结果对这3个token分别进行编码,例如编码后的结果是:

low    00
high   01
er     10

但是,如果语料库是“lower,lowest”则分割为3个token

low
est
er

这里分割后的结果是不一样的。
(3) 得到Token的过程不属于Transformer,但又是Transformer所必须的。
Transformer模型的定义中没有包含Tokenization操作,但是Transformer需要将数据转换为Token,因此在使用Transformer的过程中,需要将数据转换为Token,然后再输入到Transformer中进行处理。

2. 实现Token的过程

前面讲的是Token的基本概念,如何实现Token才是核心问题。因为Token是需要处理数据的,而现实对象却是纷繁复杂,因此对不同性质的现实对象,实现Token的方式也是差别很大的。目前,NLP和CV是人工智能处理的主要的两种数据,下面就针对这两种数据,说明一下实现Token的过程。

NLP实现Token的方法

关于NLP实现Token的方法,目前主要有3类方法,如下图所示:
在这里插入图片描述
在这3类方法当中,目前大模型主要采用的是子词分词法,前两类由于各种限制,目前依据不再是主流方法了。
对于子词分词法,主要的分词过程如下图所示
在这里插入图片描述
这里需要说明一下,虽然这里列举了分词的主要步骤,最核心的就是第3步分词模型训练,不同分词算法其训练得到的模型是不一样的,也就使得分词的结果不一样。要真正的理解分词的处理过程,以及分词与Token的关系,读者需要具体分析分词算法,由于本文主要是讲解Token的主要概念,所以不对具体的分词算法进行介绍。

CV实现Token的方法

在CV中使用Transformer衍生出了ViT(Vision Transformer)算法,而在CV中实现Token的方法要比NLP实现Token要简单很多,直接通过分割为小块的方式就可以实现。如下图所示。
在这里插入图片描述

3. NLP分词(Token)的代码实现

为了更好的理解Token这个概念,接下来用一个基于Transformer的英语与德语之间机器翻译的典型实例,说明NLP是如何实现token的
该实例代码来源于github,由于这个实例的代码量比较大,在这里只给出了涉及tokenization的代码。

代码展示

import os
from urllib.request import urlretrieveimport sentencepiece
import tensorflow as tf
from sklearn.model_selection import train_test_split
from tqdm import tqdmclass DataLoader:DIR = NonePATHS = {}BPE_VOCAB_SIZE = 0MODES = ['source', 'target']dictionary = {'source': {'token2idx': None,'idx2token': None,},'target': {'token2idx': None,'idx2token': None,}}CONFIG = {'wmt14/en-de': {'source_lang': 'en','target_lang': 'de','base_url': 'https://nlp.stanford.edu/projects/nmt/data/wmt14.en-de/','train_files': ['train.en', 'train.de'],'vocab_files': ['vocab.50K.en', 'vocab.50K.de'],'dictionary_files': ['dict.en-de'],'test_files': ['newstest2012.en', 'newstest2012.de','newstest2013.en', 'newstest2013.de','newstest2014.en', 'newstest2014.de','newstest2015.en', 'newstest2015.de',]}}BPE_MODEL_SUFFIX = '.model'BPE_VOCAB_SUFFIX = '.vocab'BPE_RESULT_SUFFIX = '.sequences'SEQ_MAX_LEN = {'source': 100,'target': 100}DATA_LIMIT = NoneTRAIN_RATIO = 0.9BATCH_SIZE = 16source_sp = Nonetarget_sp = Nonedef __init__(self, dataset_name, data_dir, batch_size=16, bpe_vocab_size=32000, seq_max_len_source=100,seq_max_len_target=100, data_limit=None, train_ratio=0.9):if dataset_name is None or data_dir is None:raise ValueError('dataset_name and data_dir must be defined')self.DIR = data_dirself.DATASET = dataset_nameself.BPE_VOCAB_SIZE = bpe_vocab_sizeself.SEQ_MAX_LEN['source'] = seq_max_len_sourceself.SEQ_MAX_LEN['target'] = seq_max_len_targetself.DATA_LIMIT = data_limitself.TRAIN_RATIO = train_ratioself.BATCH_SIZE = batch_sizeself.PATHS['source_data'] = os.path.join(self.DIR, self.CONFIG[self.DATASET]['train_files'][0])self.PATHS['source_bpe_prefix'] = self.PATHS['source_data'] + '.segmented'self.PATHS['target_data'] = os.path.join(self.DIR, self.CONFIG[self.DATASET]['train_files'][1])self.PATHS['target_bpe_prefix'] = self.PATHS['target_data'] + '.segmented'def load(self, custom_dataset=False):if custom_dataset:print('#1 use custom dataset. please implement custom download_dataset function.')else:            print('#1 download data')self.download_dataset()print('#2 parse data')source_data = self.parse_data_and_save(self.PATHS['source_data'])target_data = self.parse_data_and_save(self.PATHS['target_data'])print('#3 train bpe')self.train_bpe(self.PATHS['source_data'], self.PATHS['source_bpe_prefix'])self.train_bpe(self.PATHS['target_data'], self.PATHS['target_bpe_prefix'])print('#4 load bpe vocab')self.dictionary['source']['token2idx'], self.dictionary['source']['idx2token'] = self.load_bpe_vocab(self.PATHS['source_bpe_prefix'] + self.BPE_VOCAB_SUFFIX)self.dictionary['target']['token2idx'], self.dictionary['target']['idx2token'] = self.load_bpe_vocab(self.PATHS['target_bpe_prefix'] + self.BPE_VOCAB_SUFFIX)print('#5 encode data with bpe')source_sequences = self.texts_to_sequences(self.sentence_piece(source_data,self.PATHS['source_bpe_prefix'] + self.BPE_MODEL_SUFFIX,self.PATHS['source_bpe_prefix'] + self.BPE_RESULT_SUFFIX),mode="source")target_sequences = self.texts_to_sequences(self.sentence_piece(target_data,self.PATHS['target_bpe_prefix'] + self.BPE_MODEL_SUFFIX,self.PATHS['target_bpe_prefix'] + self.BPE_RESULT_SUFFIX),mode="target")print('source sequence example:', source_sequences[0])print('target sequence example:', target_sequences[0])if self.TRAIN_RATIO == 1.0:source_sequences_train = source_sequencessource_sequences_val = []target_sequences_train = target_sequencestarget_sequences_val = []else:(source_sequences_train,source_sequences_val,target_sequences_train,target_sequences_val) = train_test_split(source_sequences, target_sequences, train_size=self.TRAIN_RATIO)if self.DATA_LIMIT is not None:print('data size limit ON. limit size:', self.DATA_LIMIT)source_sequences_train = source_sequences_train[:self.DATA_LIMIT]target_sequences_train = target_sequences_train[:self.DATA_LIMIT]print('source_sequences_train', len(source_sequences_train))print('source_sequences_val', len(source_sequences_val))print('target_sequences_train', len(target_sequences_train))print('target_sequences_val', len(target_sequences_val))print('train set size: ', len(source_sequences_train))print('validation set size: ', len(source_sequences_val))train_dataset = self.create_dataset(source_sequences_train,target_sequences_train)if self.TRAIN_RATIO == 1.0:val_dataset = Noneelse:val_dataset = self.create_dataset(source_sequences_val,target_sequences_val)return train_dataset, val_datasetdef get_test_data_path(self, index):source_test_data_path = os.path.join(self.DIR, self.CONFIG[self.DATASET]['test_files'][index * 2])target_test_data_path = os.path.join(self.DIR, self.CONFIG[self.DATASET]['test_files'][index * 2 + 1])return source_test_data_path, target_test_data_pathdef download_dataset(self):for file in (self.CONFIG[self.DATASET]['train_files']+ self.CONFIG[self.DATASET]['vocab_files']+ self.CONFIG[self.DATASET]['dictionary_files']+ self.CONFIG[self.DATASET]['test_files']):self._download("{}{}".format(self.CONFIG[self.DATASET]['base_url'], file))def _download(self, url):path = os.path.join(self.DIR, url.split('/')[-1])if not os.path.exists(path):with TqdmCustom(unit='B', unit_scale=True, unit_divisor=1024, miniters=1, desc=url) as t:urlretrieve(url, path, t.update_to)def parse_data_and_save(self, path):print('load data from {}'.format(path))with open(path, encoding='utf-8') as f:lines = f.read().strip().split('\n')if lines is None:raise ValueError('Vocab file is invalid')with open(path, 'w', encoding='utf-8') as f:f.write('\n'.join(lines))return linesdef train_bpe(self, data_path, model_prefix):model_path = model_prefix + self.BPE_MODEL_SUFFIXvocab_path = model_prefix + self.BPE_VOCAB_SUFFIXif not (os.path.exists(model_path) and os.path.exists(vocab_path)):print('bpe model does not exist. train bpe. model path:', model_path, ' vocab path:', vocab_path)train_source_params = "--inputs={} \--pad_id=0 \--unk_id=1 \--bos_id=2 \--eos_id=3 \--model_prefix={} \--vocab_size={} \--model_type=bpe ".format(data_path,model_prefix,self.BPE_VOCAB_SIZE)sentencepiece.SentencePieceTrainer.Train(train_source_params)else:print('bpe model exist. load bpe. model path:', model_path, ' vocab path:', vocab_path)def load_bpe_encoder(self):self.dictionary['source']['token2idx'], self.dictionary['source']['idx2token'] = self.load_bpe_vocab(self.PATHS['source_bpe_prefix'] + self.BPE_VOCAB_SUFFIX)self.dictionary['target']['token2idx'], self.dictionary['target']['idx2token'] = self.load_bpe_vocab(self.PATHS['target_bpe_prefix'] + self.BPE_VOCAB_SUFFIX)def sentence_piece(self, source_data, source_bpe_model_path, result_data_path):sp = sentencepiece.SentencePieceProcessor()sp.load(source_bpe_model_path)if os.path.exists(result_data_path):print('encoded data exist. load data. path:', result_data_path)with open(result_data_path, 'r', encoding='utf-8') as f:sequences = f.read().strip().split('\n')return sequencesprint('encoded data does not exist. encode data. path:', result_data_path)sequences = []with open(result_data_path, 'w') as f:for sentence in tqdm(source_data):pieces = sp.EncodeAsPieces(sentence)sequence = " ".join(pieces)sequences.append(sequence)f.write(sequence + "\n")return sequencesdef load_bpe_vocab(self, bpe_vocab_path):with open(bpe_vocab_path, 'r') as f:vocab = [line.split()[0] for line in f.read().splitlines()]token2idx = {}idx2token = {}for idx, token in enumerate(vocab):token2idx[token] = idxidx2token[idx] = tokenreturn token2idx, idx2tokendef texts_to_sequences(self, texts, mode='source'):if mode not in self.MODES:ValueError('not allowed mode.')sequences = []for text in texts:text_list = ["<s>"] + text.split() + ["</s>"]sequence = [self.dictionary[mode]['token2idx'].get(token, self.dictionary[mode]['token2idx']["<unk>"])for token in text_list]sequences.append(sequence)return sequencesdef sequences_to_texts(self, sequences, mode='source'):if mode not in self.MODES:ValueError('not allowed mode.')texts = []for sequence in sequences:if mode == 'source':if self.source_sp is None:self.source_sp = sentencepiece.SentencePieceProcessor()self.source_sp.load(self.PATHS['source_bpe_prefix'] + self.BPE_MODEL_SUFFIX)text = self.source_sp.DecodeIds(sequence)else:if self.target_sp is None:self.target_sp = sentencepiece.SentencePieceProcessor()self.target_sp.load(self.PATHS['target_bpe_prefix'] + self.BPE_MODEL_SUFFIX)text = self.target_sp.DecodeIds(sequence)texts.append(text)return textsdef create_dataset(self, source_sequences, target_sequences):new_source_sequences = []new_target_sequences = []for source, target in zip(source_sequences, target_sequences):if len(source) > self.SEQ_MAX_LEN['source']:continueif len(target) > self.SEQ_MAX_LEN['target']:continuenew_source_sequences.append(source)new_target_sequences.append(target)source_sequences = tf.keras.preprocessing.sequence.pad_sequences(sequences=new_source_sequences, maxlen=self.SEQ_MAX_LEN['source'], padding='post')target_sequences = tf.keras.preprocessing.sequence.pad_sequences(sequences=new_target_sequences, maxlen=self.SEQ_MAX_LEN['target'], padding='post')buffer_size = int(source_sequences.shape[0] * 0.3)dataset = tf.data.Dataset.from_tensor_slices((source_sequences, target_sequences)).shuffle(buffer_size)dataset = dataset.batch(self.BATCH_SIZE)dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)return dataset

代码解读

在该实例中,tokenization对应的代码集成在DataLoader类中的 load方法中,具体的代码解读如下:
(1) 对语料库进行分词
在这里插入图片描述
(2) 根据分词结果使用BPE算法进行分词模型训练
在这里插入图片描述
(3) 根据BPE得到最终分词结果(也就是Token)得到相应的Token编号
在这里插入图片描述
(4) 根据BPE得到的分词模型对语料库进行分割
在这里插入图片描述
其中对sentence_piece的解读如下:
在这里插入图片描述
(5) 根据语料库分割结果生成训练集和测试集
在这里插入图片描述

总结

本文介绍了Transformer模型中Token概念的内涵,并通过NLP的实例说明了Token是如何构造的。

http://www.xdnf.cn/news/1352611.html

相关文章:

  • SpringBoot 快速上手:从环境搭建到 HelloWorld 实战
  • Excel 条件高亮工具,秒高亮显示符合筛选条件的行数据
  • 「数据获取」《中国能源统计年鉴》(1986-2023)(获取方式看绑定的资源)
  • 蓝桥杯算法之基础知识(2)——Python赛道
  • 【51单片机学习】直流电机驱动(PWM)、AD/DA、红外遥控(外部中断)
  • mmdetection:记录算法训练配置文件
  • A Large Scale Synthetic Graph Dataset Generation Framework的学习笔记
  • Mysql EXPLAIN详解:从底层原理到性能优化实战
  • 如何在Ubuntu中删除或修改已有的IP地址设置?
  • C语言---数据类型
  • PyTorch生成式人工智能——VQ-VAE详解与实现
  • TypeScript 的泛型(Generics)作用理解
  • Kafka 概念与概述
  • 在TencentOS3上部署OpenTenBase:从入门到实战的完整指南
  • 【Java学习笔记】18.反射与注解的应用
  • 遥感机器学习入门实战教程|Sklearn案例⑧:评估指标(metrics)全解析
  • tcpdump命令打印抓包信息
  • 【golang】ORM框架操作数据库
  • 2-5.Python 编码基础 - 键盘输入
  • STM32CubeIDE V1.9.0下载资源链接
  • 醋酸镨:催化剂领域的璀璨新星
  • LangChain4J-基础(整合Spring、RAG、MCP、向量数据库、提示词、流式输出)
  • 信贷模型域——信贷获客模型(获客模型)
  • 温度对直线导轨的性能有哪些影响?
  • 小白向:Obsidian(Markdown语法学习)快速入门完全指南:从零开始构建你的第二大脑(免费好用的笔记软件的知识管理系统)、黑曜石笔记
  • 数字经济、全球化与5G催生域名新价值的逻辑与实践路径
  • 快速掌握Java非线性数据结构:树(二叉树、平衡二叉树、多路平衡树)、堆、图【算法必备】
  • vue3 - 组件间的传值
  • 【小沐学GIS】基于Godot绘制三维数字地球Earth(Godot)
  • 计算机网络 TLS握手中三个随机数详解