大模型基础——从零实现一个Transformer(1)

一、Transformer模型架构图

主要模块:

   embedding层:

  • Input/Output Embedding: 将每个标记(token)转换为对应的向量表示。

  • Positional Encoding:由于没有时序信息,需要额外加入位置编码。

   

    N个 block堆叠:

  • Multi-Head Attention: 利用注意力机制对输入进行交互,得到具有上下文信息的表示。根据其所处的位置有不同的变种:邻接解码器嵌入位置是掩码多头注意力,特点是当前位置只能注意本身以及之前位置的信息;掩码多头注意力紧接的多头注意力特点是Key和Value来自编码器的输出,而Query来自底层的输出,目的是在计算输出时考虑输入信息。

  • Layer Normalization: 作用于Transformer块内部子层的输出表示上,对表示序列进行层归一化。

  • Residual connection :作用于Transformer块内部子层输入和输出上,对其输入和输出进行求和。

  • Position-wise Feedforward Network: 通过多层全连接网络对表示序列进行非线性变换,提升模型的表达能力。

二、分词器BPE 

2.1 原理

字节对编码(BPE, Byte Pair Encoder),又称 digram coding 双字母组合编码,是一种数据压缩 算法,用来在固定大小的词表中实现可变⻓度的子词。

BPE 首先将词分成单个字符,然后依次用另一个字符替换频率最高的一对字符 ,直到循环次数结束。

BPE训练算法的步骤如下:

  1. 初始化语料库

  2. 将语料库中每个单词拆分成字符作为子词,并在单词结尾增加一个</w>字符

  3. 将拆分后的子词构成初始子词词表

  4. 在语料库中统计单词内相邻子词对的频次

  5. 合并频次最高的子词对,合并成新的子词,并将新的子词加入到子词词表

  6. 重复步骤4和5直到进行了设定的合并次数或达到了设定的子词词表大小

2.2 tokenize实现

from collections import defaultdict
import jieba
from  typing import *

class BPETokenizer:
    def __init__(self,special_tokens=[]) -> None:
        '''

        :param sepcial_tokens: 额外添加的特殊token,
        '''
        self.word_freqs = defaultdict(int)
        self.merges = {}
        self.token_to_id =  {}
        self.id_to_token = {}

        if special_tokens is None:
            special_tokens = []

        special_tokens = ['<PAD>', '<UNK>', '<BOS>', '<EOS>'] +  special_tokens
        for token in special_tokens:
            self._add_token(token)
        self.unk_token = "<UNK>"
        self.unk_token_id = self.token_to_id.get(self.unk_token)



    def _add_token(self,token:str) -> None:
        '''
        将token添加到词表中
        :param token:
        :return:
        '''
        # 新添加的token添加到最后,所以idx默认是当前词表的长度
        if token not in self.token_to_id:
            idx = len(self.token_to_id)
            self.token_to_id[token] = idx
            self.id_to_token[idx] = token


    @property
    def vobcab_size(self) -> int:
        return len(self.token_to_id)


    ## 以下是训练bpe相关函数 start
    def _learn_vocab(self,corpus: list[str]) -> None:
        '''
        统计词频
        :param corpus:
        :return:
        '''
        for sentence in corpus:
            sentence = sentence.lower()

            # 分词统计词频
            words = [w for w in jieba.cut(sentence) if w != " "]
            for word in words:
                self.word_freqs[word] += 1

    def _compute_pair_freqs(self,splits) -> dict[Tuple, int]:
        '''
        统计相邻字符的共现频率
        :param splits:
        :return:
        '''
        pair_freqs = defaultdict(int)

        ## 遍历word里面的相关的子字符,统计共现频率
        for word,freq in self.word_freqs.items():
            split = splits[word]
            if len(split) == 1:
                continue

            for i in range(len(split) - 1):
                pair = (split[i], split[i + 1])
                pair_freqs[pair] += freq

        return pair_freqs

    def _merge_pair(self,a:str,b:str,splits):
        '''
        合并字符并跟新split里面word对应的字符组成
        比如 splits里面有个单词是 “hello”:['h','e','l','l','o']
        如果合并了字符h和字符e,那么就要变成“hello”:['he','l','l','o']
        :param a:
        :param b:
        :param splits:
        :return:
        '''
        for word in self.word_freqs:
            split = splits[word]
            if len(split) == 1:
                continue

            i = 0
            while i < len(split) -1:
                if split[i] == a and split[i+1] == b:
                    split = split[:i] + [a + b] + split[i+2:]
                else:
                    i += 1
            splits[word] = split

        return  splits

    def _merge_vocab(self,vocab_size,splits):
        '''

        :param vocab_size: 预期的词表的大小,当达到词表大小,bpe算法就结束
        :param splits:
        :return:
        '''
        merges = {}
        while self.vobcab_size < vocab_size:
            # 先统计共现频率
            pari_freqs = self._compute_pair_freqs(splits)
            best_pair = None
            max_freq = 0

            for pair,freq in pari_freqs.items():
                if max_freq < freq:
                    best_pair = pair
                    max_freq = freq

            # 合并新词
            splits = self._merge_pair(*best_pair,splits)

            # 将新词加入词表
            merges[best_pair] = best_pair[0] + best_pair[1]
            self._add_token(best_pair[0] + best_pair[1])
        return merges

    def train(self,corpus,vocab_size):
        '''
        bpe训练代码
        :param corpus: 文本语料
        :param vocab_size: 期望的词表大小
        :return:
        '''

        self._learn_vocab(corpus)
        splits = {word: [c for c in word] for word in self.word_freqs.keys()}

        for split in splits.values():
            for c in split:
                self._add_token(c)

        self.merges = self._merge_vocab(vocab_size, splits)

    ## 训练bpe相关函数 end

    def tokenize(self,text):
        '''
        bpe分词
        :param text:
        :return:
        '''
        text = text.lower()
        words = [w for w in jieba.cut(text) if w != " "]
        splits = [[c for c in word] for word in words]

        for pair,merge in self.merges.items():
            for idx,split in enumerate(splits):
                i = 0
                while i < len(split) -1:
                    if split[i] == pair[0] and split[i+1] == pair[1]:
                        split = split[:i] + [merge] + split[i + 2:]
                    else:
                        i += 1
                splits[idx] = split

        return sum(splits,[])

    def _convert_token_to_id(self,token:str):
        '''
        将token转换为具体的id
        :param token:
        :return:
        '''
        return self.token_to_id.get(token,self.unk_token_id)

    def _convert_id_to_token(self,index:int) -> str:
        return self.id_to_token.get(index,self.unk_token)

    def _convert_ids_to_tokens(self,token_ids: list[int]) -> list[str]:
        return [self._convert_id_to_token(index) for index in token_ids]


    def _convert_tokens_to_ids(self, tokens: list[str]) -> list[int]:
        return [self._convert_token_to_id(token) for token in tokens]

    def encode(self, text: str) -> list[int]:
        tokens = self.tokenize(text)

        return self._convert_tokens_to_ids(tokens)

    def clean_up_tokenization(self, out_string: str) -> str:
        out_string = (
            out_string.replace(" .", ".")
            .replace(" ?", "?")
            .replace(" !", "!")
            .replace(" ,", ",")
            .replace(" ' ", "'")
            .replace(" n't", "n't")
            .replace(" 'm", "'m")
            .replace(" 's", "'s")
            .replace(" 've", "'ve")
            .replace(" 're", "'re")
        )
        return out_string

    def decode(self, token_ids: list[int]) -> str:
        tokens = self._convert_ids_to_tokens(token_ids)
        return self.clean_up_tokenization(" ".join(tokens))





if __name__ == '__main__':
    corpus = [
        "This is the Hugging Face Course.",
        "This chapter is about tokenization.",
        "This section shows several tokenizer algorithms.",
        "Hopefully, you will be able to understand how they are trained and generate tokens.",
    ]

    tokenizer = BPETokenizer()
    tokenizer.train(corpus, 50)
    print(tokenizer.tokenize("This is not a token."))
    print(tokenizer.vobcab_size)
    token_ids = tokenizer.encode("This is not a token.")
    print(token_ids)
    # this is not a token.

    #由于分词的时候没有 对单词的尾部/w处理,导致decode的时候不知道单词是否结束
    print(tokenizer.decode(token_ids))







三. Embedding层

import math
from torch import  nn,Tensor
class Embedding(nn.Module):
    def __init__(self,vocab_size:int, d_model:int) -> None:
        '''

        :param vocab_size: 词库大小
        :param d_model: embedding的向量维度
        '''
        super().__init__()
        self.embed = nn.Embedding(vocab_size,d_model)
        self.sqrt_d_model = math.sqrt(d_model)


    def forward(self,x:Tensor) -> Tensor:
        '''

        :param x: 输入的Tensor,(batch_size,seq_length)
        :return: Tensor,shape为:(batch_size,seq_length,d_model)
        '''

        ## 论文里面,初始化以后要乘以 d_model的平方根
        return self.embed(x) * self.sqrt_d_model

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自然语言处理(NLP)—— 语言检测器

1. 文章概述 1.1 目的 在本篇文章中&#xff0c;我们将构建一个语言检测器&#xff0c;这是一个能够识别文本语言的简单分类器。这是一个能够识别文本是用哪种语言写的程序。想象一下&#xff0c;你给这个程序一段文字&#xff0c;它就能告诉你这是英语、法语还是其他语言。 …

Linux 35.5 + JetPack v5.1.3@FC-Planner编译安装

Linux 35.5 JetPack v5.1.3FC-Planner编译安装 1. 源由2. 编译&安装Step 1&#xff1a;依赖库安装Step 2&#xff1a;克隆工程Step 3&#xff1a;编译工程Step 4&#xff1a;LKH编译Step 5&#xff1a;安装工程 3. 问题汇总3.1 swarm_exploration/plan_env - OpenCV3.2 程…

天才简史——Tamim Asfour与他的H²T实验室

一、Tamim Asfour介绍 Tamim Asfour为KIT人类学和机器人学研究所&#xff08;Institute for Anthropomatics and Robotics&#xff09;的全职教授&#xff0c;并担任高性能人形技术实验室 (High Performance Humanoid Technologies Lab&#xff0c;HT) 负责人。他目前的研究兴…

【算法专题--栈】最小栈--高频面试题(图文详解,小白一看就会!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐解题方法--1 ⭐解题方法--2 四、总结 五、共勉 一、前言 最小栈这道题&#xff0c;可以说是--栈专题--&#xff0c;比较经典的一道题&#xff0c;也是在面试中频率较高的一道题目&#xff0c;通常在面试中&#xff0c;面试官可…

码蹄集部分题目(2024OJ赛18期;并查集+ST表+贪心)

1&#x1f40b;&#x1f40b;史莱姆融合&#xff08;钻石&#xff1b;并查集&#xff09; 时间限制&#xff1a;1秒 占用内存&#xff1a;128M &#x1f41f;题目描述 &#x1f41f;题目思路 这道题目使用并查集&#xff0c;同一集合的所有元素的最顶上的祖父节点是统一的。…

SAP ABAP 创建表结构 SE11

目录 一&#xff0c;创建表 &#xff1a;T-code:SE11 二&#xff0c;编辑内容&#xff1a; 1&#xff0c;内容说明&#xff1a;必填项&#xff0c;属性&#xff1a;锁定不可更改 2&#xff0c;出荷と更新 &#xff13;&#xff0c;項目 A&#xff1a;表的第一个项目必须是…

编写程序提示用户输入一个数目(例如:100)、年利率(例如:5)以及月份数(例如:6),然后显示给定月份后账户上的钱数。

(财务应用程序:复利值)假设你每月向银行账户存 100美元&#xff0c;年利率为5%&#xff0c;那么每 月利率是 0.05/12-0.00417。 第一个月之后&#xff0c;账户上的值就变成:100*(10.00417)100.417 第二个月之后&#xff0c;账户上的值就变成(100100.417)*(10.00417)-201.252 第…

【Python报错】已解决ImportError: cannot import name ‘xxx‘

成功解决“ImportError: cannot import name ‘xxx’”错误的全面指南 一、引言 在Python编程中&#xff0c;ImportError是一种常见的异常类型&#xff0c;它通常表明Python解释器在尝试导入某个模块或模块中的某个成员时遇到了问题。当看到错误消息“ImportError: cannot imp…

解密智慧校园解决方案:赋能数字化教育的未来

在当今数字化时代&#xff0c;智慧校园解决方案正以惊人的速度改变着教育界的面貌。随着科技的快速发展&#xff0c;数字化教育已经逐渐成为现代教育的核心。智慧校园解决方案作为一个集技术、教育和创新于一体的综合性项目&#xff0c;为学校提供了许多机遇和挑战。本文将揭示…

嵌入式Linux系统中RTC应用的操作详解

第一:RTC的作用以及时间简介 “RTC”的英文全称是Reul-Time Clock,翻译过来是实时时钟芯片.实时时钟芯片是日常生活中应用最为广泛的电子器件之一,它为人们或者电子系统提供精确的实时时间,实时时钟芯片通过引脚对外提供时间读写接口,通常内部带有电池,保证在外部系统关…

width: 100%和 width: 100vw这两种写法有什么区别

width: 100%; 和 width: 100vw; 是两种不同的 CSS 写法&#xff0c;它们在实际应用中会有不同的效果。以下是这两种写法的主要区别&#xff1a; width: 100%; 定义&#xff1a;将元素的宽度设置为其包含块&#xff08;通常是父元素&#xff09;宽度的 100%。效果&#xff1a;元…

Maven核心功能依赖和构建管理

1.依赖管理和配置 Maven 依赖管理是 Maven 软件中最重要的功能之一。Maven 的依赖管理能够帮助开发人员自动解决软件包依赖问题&#xff0c;使得开发人员能够轻松地将其他开发人员开发的模块或第三方框架集成到自己的应用程序或模块中&#xff0c;避免出现版本冲突和依赖缺失等…

springboot停车微信小程序小程序-计算机毕业设计源码92714

摘 要 在信息飞速发展的今天&#xff0c;网络已成为人们重要的信息交流平台。每天都有大量的农产品需要通过网络发布&#xff0c;为此&#xff0c;本人开发了一个基于springboot停车微信小程序小程序。 对于本停车微信小程序的设计来说&#xff0c;它主要是采用后台采用java语…

Android Webview 详解

一 简介 一个基于webkit引擎、展现web页面的控件 Android 4.4前&#xff1a;Android Webview在低版本 & 高版本采用了不同的webkit版本的内核Android 4.4后&#xff1a;直接使用了Chrome内核 1.1 作用 在 Android 客户端上加载h5页面在本地 与 h5页面实现交互 & …

关于RDMA传输的基本流量控制

Basic flow control for RDMA transfers | The Geek in the Corner (wordpress.com) 文心一言 已经介绍了使用发送/接收操作和RDMA读写操作&#xff0c;那么现在是一个很好的机会来结合这两种方法的元素&#xff0c;并讨论一般的流量控制。还会稍微谈谈RDMA带有立即数据的写操…

《机器学习特征提取》

书籍&#xff1a;Building Feature Extraction with Machine Learning: Geospatial Applications 作者&#xff1a;Bharath.H. Aithal&#xff0c;Prakash P.S. 出版&#xff1a;CRC Press 书籍下载-《机器学习特征提取》这是一本面向专业人士和研究生的实用指南&#xff0c…

uniapp uni-popup内容被隐藏问题

今天开发新需求的时候发现uni-popup 过一会就被隐藏掉只留下遮罩(css被更改了)&#xff0c;作者进行了如下调试。 1.讲uni-popup放入其他节点内 失败&#xff01; 2.在生成dom后在打开 失败&#xff01; 3.uni-popup将该节点在包裹一层 然后将统计设置样式&#xff0c;v-if v-s…

selenium中, quit 和close的区别

close时 """ close和quit的区别 close关闭当前页 (只是关闭了当前) quit离开整个浏览器 &#xff08;走远了&#xff09; """ from selenium import webdriver import time# 创建浏览器驱动对象 from selenium.webdriver.co…

抢人!抢人!抢人! IT行业某岗位已经开始抢人了!

所谓抢滩鸿蒙&#xff0c;人才先行。鸿蒙系统火力全开后&#xff0c;抢人已成鸿蒙市场的主题词&#xff01; 智联招聘数据显示&#xff0c;春节后首周&#xff0c;鸿蒙相关职位数同比增长163%&#xff0c;是去年同期的2.6倍&#xff0c;2023年9-12月鸿蒙相关职位数同比增速为3…

深入理解C++多线程系列——线程基础

概念 在现代计算机中&#xff0c;多线程编程是一种强大的并发执行计数&#xff0c;允许多个线程在单个程序内部并行执行&#xff0c;提高程序的执行效率和响应速度。线程&#xff0c;作为CPU调度的最小单元&#xff0c;它被用来执行程序中的指令。一个线程是进程中的一个单一顺…