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

news2024/11/18 1:46:53

一、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

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

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

相关文章

vue不同页面切换的方式(Vue动态组件)

v-if实现 <!--Calender.vue--> <template><a-calendar v-model:value"value" panelChange"onPanelChange" /></template> <script setup> import { ref } from vue; const value ref(); const onPanelChange (value, mod…

Oracle EBS AP发票验证-计税期间出现意外错误解决方法

系统版本 RDBMS : 12.1.0.2.0 Oracle Applications : 12.2.6 问题症状: **打开发票题头或发票行“税详细信息”**错误提示如下: 由于以下原因而无法针对"税"窗口中所做的修改更新 Oraclee Payables信息: 尚未为税率或帐户来源税率设置可退回税/应纳税额帐户。请…

【Linux】The server quit without updating PID file的几种解决方案

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。 &#x1f913; 同时欢迎大家关注其他专栏&#xff0c;我将分享Web前后端开发、人工智能、机器学习、深…

简单聊下服务器防病毒

在当今数字化时代&#xff0c;服务器作为数据存储、处理与传输的核心设备&#xff0c;其安全性显得尤为关键。服务器防病毒工作&#xff0c;不仅是保障企业信息安全的重要一环&#xff0c;更是维护用户数据隐私的关键举措。以下&#xff0c;我们将从多个方面&#xff0c;简单探…

29网课交单平台 epay.php SQL注入漏洞复现

0x01 产品简介 29网课交单平台是一个专注于在线教育和知识付费领域的交单平台。该平台基于PHP开发,通过全开源修复和优化,为用户提供了高效、稳定、安全的在线学习和交易环境。作为知识付费系统的重要组成部分,充分利用了互联网的优势,为用户提供了便捷的支付方式、高效的…

Java使用OpenCV计算两张图片相似度

业务&#xff1a;找出两个表的重复的图片。 图片在表里存的是二进制值&#xff0c;存在大量由于一些特殊情况例如扫描有差异&#xff0c;导致图片存的二进制值不同&#xff0c;但图片其实是一样来的。 所以找出两个表重复相同的图片&#xff0c;不可能只是单纯的比较二进制值…

若依RuoYi-Vue分离版—增加通知公告预览及缩放功能

若依RuoYi-Vue分离版—增加通知公告预览及缩放功能 前言开发通知公告 前言 若依分离版的通知公告没有预览功能&#xff0c;想开发通知公告功能 开发通知公告 效果如下 具体开发内容 修改若依notice代码如下。 <template><div class"app-container"&g…

三十五篇:数字化转型的引擎:赋能企业的ERP系统全景

数字化转型的引擎&#xff1a;赋能企业的ERP系统全景 1. 引言 在这个快速变化的数字时代&#xff0c;现代企业面临着前所未有的挑战和机遇。企业资源计划&#xff08;ERP&#xff09;系统&#xff0c;作为数字化转型的核心&#xff0c;扮演着至关重要的角色。它不仅是企业运营…

霸气的短视频:成都科成博通文化传媒公司

霸气的短视频&#xff1a;瞬间的力量与魅力 在数字化浪潮中&#xff0c;短视频以其独特的魅力迅速崛起&#xff0c;成为社交媒体的新宠。而在众多短视频中&#xff0c;那些充满霸气、让人热血沸腾的作品&#xff0c;总能引起广泛的关注和讨论。成都科成博通文化传媒公司将从内…

Linux文本处理三剑客之awk命令

官方文档&#xff1a;https://www.gnu.org/software/gawk/manual/gawk.html 什么是awk&#xff1f; Awk是一种文本处理工具&#xff0c;它的名字是由其三位创始人&#xff08;Aho、Weinberger和Kernighan&#xff09;的姓氏首字母组成的。Awk的设计初衷是用于处理结构化文本数…

SSM整合总结

一.核心问题 (一)两个容器 web容器 web相关组件&#xff08;controller,springmvc核心组件&#xff09; root容器 业务和持久层相关组件&#xff08;service,aop,tx,dataSource,mybatis,mapper等&#xff09; 父容器&#xff1a;root容器&#xff0c;盛放service、mapper、…

前端 JS 经典:打印对象的 bug

1. 问题 相信这个 console 打印语句的 bug&#xff0c;其实小伙伴们是遇到过的&#xff0c;就是你有一个对象&#xff0c;通过 console&#xff0c;打印一次&#xff0c;然后经过一些处理&#xff0c;再通过 console 打印&#xff0c;发现两次打印的结果是一样的&#xff0c;第…

算法金 | 这次终于能把张量(Tensor)搞清楚了!

大侠幸会&#xff0c;在下全网同名[算法金] 0 基础转 AI 上岸&#xff0c;多个算法赛 Top [日更万日&#xff0c;让更多人享受智能乐趣] 1. 张量&#xff08;Tensor&#xff09;基础概念 1.1 张量的定义与重要性 张量是深度学习中用于表示数据的核心结构&#xff0c;它可以视…

国标GB/T 28181详解:国标GBT28181-2022的客户端主动发起历史视音频回放流程

目录 一、定义 二、作用 1、提供有效的数据回顾机制 2、增强监控系统的功能性 3、保障数据传输与存储的可靠性 4、实现精细化的操作与控制 5、促进监控系统的集成与发展 三、历史视音频回放的基本要求 四、命令流程 1、流程图 2、流程描述 五、协议接口 1、会话控…

Paper速读-[Visual Prompt Multi-Modal Tracking]-Dlut.edu-CVPR2023

文章目录 简介关于具体的思路问题描述算法细节 实验结果模型的潜力模型结果 论文链接&#xff1a;Visual Prompt Multi-Modal Tracking 开源代码&#xff1a;Official implementation of ViPT 简介 这篇文章说了个什么事情呢&#xff0c;来咱们先看简单的介绍图 简单来说&am…

从零开始学习Linux(9)----文件系统

目录 1.前言 1.铺垫 2.C语言文件接口-对比重定向 3.文件fd 4.缓冲区问题 2.文件系统 1.磁盘的物理存储 2.磁盘的逻辑存储 3.软硬链接 1.硬链接 2.软链接 1.前言 1.铺垫 a.文件内容属性 b.访问文件之前&#xff0c;都得先打开&#xff0c;修改文件&#xff0c;都是通…

ElementUI中date-picker组件,怎么把大写月份改为阿拉伯数字月份(例如:一月、二月,改为1月、2月)

要将 Element UI 的 <el-date-picker> 组件中的月份名称从中文大写&#xff08;如 "一月", "二月"&#xff09;更改为阿拉伯数字&#xff08;如 "1月", "2月"&#xff09;&#xff0c;需要进行一些定制化处理。可以通过国际化&a…

LIUNX系统编程:信号(3)

目录 3.信号的处理 3.1信号是什么时候被处理的 read系统调用 3.2信号是怎样被处理的 内核态和用户态 3.3操作系统是如何运行处理信号的呢&#xff1f; 中断技术 什么让操作系统运行起来的 3.4捕捉信号的其他方式 ​编辑 demo代码 3.信号的处理 3.1信号是什么时候被处…

[自学记录09*]Unity Shader:在Unity里渲染一个黑洞

一、前言 记得很久很久以前&#xff0c;在ShaderToy上看过一个黑洞的效果&#xff0c;当时感觉太*8帅了&#xff0c;于是这几天就尝试自己弄了一个。 Gargantua With HDR Bloom (shadertoy.com) 下面是我自己实现的黑洞 可以看到还是略逊一筹&#xff08;感觉略逊百筹&#x…

【Unity游戏制作】地精寻宝Gnome‘s Well That Ends Well卷轴动作游戏【一】场景搭建

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 专栏交流&#x1f9e7;&…