Pytorch 从零实现 Transformer

news2024/11/24 21:20:44

前言

之前虽然了解过 Transformer 架构,但是没有自己实现过。

最近阅读 transformers 库中 Llama 模型结构,于是想试着亲手实现一个简单的 Transformer。

在实现过程中加深了理解,同时发现之前阅读 Llama 中一些错误的地方,因此做一个记录。

笔者小白,如果实现过程中存在错误,请不吝指出。

Embedding

Embedding 可以将高维的离散文本数据映射到低维的连续向量空间。这不仅减小了输入数据的维度,也有助于减少数据的稀疏性,提高模型的性能和效率。

同时,词嵌入可以捕捉单词之间的语义关系,相似的单词在嵌入空间中会更接近。

使用 Pytorch 可以很方便定义出 Embedding 模型:

class Embedder(nn.Module):
    def __init__(self, vocab_size: int, d_model: int) -> None:
        super().__init__()
        self.embed = nn.Embedding(vocab_size, d_model)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.embed(x)

Positional Encoding

Transformer 中没有类似 RNN 的循环机制,需要通过位置编码记录单词的位置和顺序。

其计算位置编码的公式如下:

PE(pos,2i)=sin(pos100002i/dmodel)PE_{(pos,2i)}=sin(\frac{pos}{10000^{2i/d_{model}}})PE(pos,2i)​=sin(100002i/dmodel​pos​)

PE(pos,2i+1)=cos(pos100002i/dmodel)PE_{(pos,2i+1)}=cos(\frac{pos}{10000^{2i/d_{model}}})PE(pos,2i+1)​=cos(100002i/dmodel​pos​)

其中 pospospos 是位置,而 iii 是维度。

Pytorch 实现位置编码器代码如下:

class PositionalEncoder(nn.Module):
    def __init__(
        self, d_model: int = 512, max_seq_len: int = 2048, base: int = 10000
    ) -> None:
        super().__init__()
        self.d_model = d_model

        inv_freq_half = 1.0 / (
            base ** (torch.arange(0, d_model, 2, dtype=torch.float) / d_model)
        )
        inv_freq = torch.arange(0, d_model, dtype=inv_freq_half.dtype)
        inv_freq[..., 0::2] = inv_freq_half
        inv_freq[..., 1::2] = inv_freq_half

        pos = torch.arange(max_seq_len, dtype=inv_freq.dtype)

        pe = torch.einsum("i, j -> ij", pos, inv_freq)
        pe[..., 0::2] = pe[..., 0::2].sin()
        pe[..., 1::2] = pe[..., 1::2].cos()

        self.register_buffer("pe", pe)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # 使 embedding 相对大一些
        x = x * math.sqrt(self.d_model)
        seq_len = x.shape[1]
        pe = self.pe[:seq_len].to(dtype=x.dtype)
        return x + pe

在 PyTorch 中,nn.Module 类中的 register_buffer() 方法用于将一个张量(或缓冲区)注册为模型的一部分。

注册的缓冲区不会参与模型的梯度计算,但会在模型的保存和加载时保持状态。

register_buffer() 的主要作用是在模型中保留一些不需要梯度更新的状态。

在前向传播中加入位置编码前扩大 embedding 的值,是为了保证原始语言信息不会因为加入位置信息而丢失。

Mask

Mask 在 Transformer 中有很重要的作用:

  • 在 Encoder 和 Decoder 中,Mask 会遮住用于 Padding 的位置。
  • 在 Decoder 中,Mask 会遮住预测剩余位置,防止 Dcoder 提前得到信息。

Multi-Headed Attention

多头注意力是 Transformer 中的核心模块,它们网络结构如下:

在多头注意力中,会将 embedding 分割为 hhh 个头,每个头的维度为 dmodel/hd_{model} / hdmodel​/h。

In this work we employ h=8h = 8h=8 parallel attention layers, or heads. For each of these we use dkd_kdk​ = dvd_vdv​ = dmodel/hd_{model}/hdmodel​/h = 64.

多头注意力公式如下:

MultiHead(Q,K,V)=Concat(head1,…,headn)WOMultiHead(Q,K,V)=Concat(head_1,…,head_n)W^OMultiHead(Q,K,V)=Concat(head1​,…,headn​)WO

headi=Attention(QWiQ,KWiK,VWiV)head_i=Attention(QW_iQ,KW_iK,VW_i^V)headi​=Attention(QWiQ​,KWiK​,VWiV​)

多头注意力代码如下:

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model: int, heads: int = 8, dropout: int = 0.1) -> None:
        super().__init__()

        self.d_model = d_model
        self.heads = heads
        self.d_k = self.d_model // self.heads

        if self.heads * self.d_k != self.d_model:
            raise ValueError(
                f"d_model must be divisible by heads (got `d_model`: {self.d_model}"
                f" and `heads`: {self.heads})."
            )

        self.q_proj = nn.Linear(d_model, d_model)
        self.k_proj = nn.Linear(d_model, d_model)
        self.v_proj = nn.Linear(d_model, d_model)

        self.dropout = nn.Dropout(dropout)
        self.o_proj = nn.Linear(d_model, d_model)

    def forward(
        self,
        q: torch.Tensor,
        k: torch.Tensor,
        v: torch.Tensor,
        mask: Optional[torch.Tensor] = None,
    ):
        bsz = q.shape[0]

        # translate [bsz, seq_len, d_model] to [bsz, seq_len, heads, d_k]
        q = self.q_proj(q).view(bsz, -1, self.heads, self.d_k)
        k = self.k_proj(k).view(bsz, -1, self.heads, self.d_k)
        v = self.v_proj(v).view(bsz, -1, self.heads, self.d_k)

        # translate [bsz, seq_len, heads, d_k] to [bsz, heads, seq_len, d_k]
        q = q.transpose(1, 2)
        k = k.transpose(1, 2)
        v = v.transpose(1, 2)

        # calculate attention
        scores = attention(q, k, v, self.d_k, mask, self.dropout)

        # cat multi-heads
        concat = scores.transpose(1, 2).contiguous().view(bsz, -1, self.d_model)
        output = self.o_proj(concat)
        return output

注意力计算公式为:

Attention(Q,K,V)=softmax(QKTdk)VAttention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})VAttention(Q,K,V)=softmax(dk​​QKT​)V

其中计算注意力的代码如下:

def attention(
    q: torch.Tensor,
    k: torch.Tensor,
    v: torch.Tensor,
    d_k: int,
    mask: Optional[torch.Tensor] = None,
    dropout: Optional[nn.Dropout] = None,
) -> torch.Tensor:
    # calculate the scores
    # q: [bsz, heads, seq_len, d_k]
    # k: [bsz, heads, d_k, seq_len]
    scores = torch.matmul(q, k.transpose(-1, -2)) / torch.sqrt(d_k)

    if mask is not None:
        # tanslate [bsz, seq_len, seq_len] to [bsz, 1, seq_len, seq_len]
        mask = mask.unsqueeze(1)
        scores = scores.masked_fill(mask == 0, -1e9)
    scores = F.softmax(scores, dim=-1)

    if dropout is not None:
        scores = dropout(scores)

    output = torch.matmul(scores, v)
    return output

The Feed-Forward Network

Feed-Forward 由两个线性变换和一个激活函数构成。

This consists of two linear transformations with a ReLU activation in between.

其公式如下:

FFN=max(0,xW1+b1)W2+b2FFN=max(0,xW_1+b_1)W_2+b_2FFN=max(0,xW1​+b1​)W2​+b2​

该网络中输入输出维度为 512,中间线性层维度为 2048。

The dimensionality of input and output is dmodel=512d_{model} = 512dmodel​=512, and the inner-layer has dimensionality dff=2048d_{ff} = 2048dff​=2048.

实现代码如下:

class FeedForward(nn.Module):
    def __init__(
        self, d_model: int = 512, d_ff: int = 2048, dropout: float = 0.1
    ) -> None:
        super().__init__()

        self.linear_1 = nn.Linear(d_model, d_ff)
        self.dropout = nn.Dropout(dropout)
        self.linear_2 = nn.Linear(d_ff, d_model)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.dropout(F.relu(self.linear_1(x)))
        x = self.linear_2(x)
        return x

Norm

正则化可以防止数据在不同网络中流动时范围差距过大,保证模型稳定性。

实现代码如下:

class Norm(nn.Module):
    def __init__(self, d_model: int, eps: float = 1e-6) -> None:
        super().__init__()

        self.dim = d_model

        self.alpha = nn.Parameter(torch.ones(self.dim))
        self.bias = nn.Parameter(torch.zeros(self.dim))

        self.eps = eps

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        norm = (
            self.alpha
            * (x - x.mean(dim=-1, keepdim=True))
            / (x.std(dim=-1, keepdim=True) + self.eps)
            + self.bias
        )
        return norm

Assemble

Transformer 由多个 EncoderLayer 和 DecoderLayer 组合在一起,首先实现 EncoderLayer。

[注意] 在每个子层输出和下一个子层输入以及正则化前,有一层 dropout。

We apply dropout [33] to the output of each sub-layer, before it is added to the sub-layer input and normalized. In addition, we apply dropout to the sums of the embeddings and the positional encodings in both the encoder and decoder stacks. For the base model, we use a rate of Pdrop = 0.1

EncoderLayer 实现代码如下:

class EncoderLayer(nn.Module):
    def __init__(
        self, d_model: int = 512, heads: int = 8, d_ff: int = 2048, dropout: float = 0.1
    ) -> None:
        super().__init__()

        self.attn = MultiHeadAttention(d_model, heads, dropout)
        self.dropout_1 = nn.Dropout(dropout)
        self.norm_1 = Norm(d_model)

        self.ffn = FeedForward(d_model, d_ff, dropout)
        self.dropout_2 = nn.Dropout(dropout)
        self.norm_2 = Norm(d_model)

    def forward(
        self, x: torch.Tensor, mask: Optional[torch.Tensor] = None
    ) -> torch.Tensor:
        x = x + self.dropout_1(self.attn(x, x, x, mask))
        x = self.norm_1(x)

        x = x + self.dropout_2(self.ffn(x))
        x = self.norm_2(x)
        return x

DecoderLayer 实现代码如下:

class DecoderLayer(nn.Module):
    def __init__(
        self, d_model: int = 512, heads: int = 8, d_ff: int = 2048, dropout: float = 0.1
    ) -> None:
        super().__init__()

        self.attn_1 = MultiHeadAttention(d_model, heads, dropout)
        self.dropout_1 = nn.Dropout(dropout)
        self.norm_1 = Norm(d_model)

        self.attn_2 = MultiHeadAttention(d_model, heads, dropout)
        self.dropout_2 = nn.Dropout(dropout)
        self.norm_2 = Norm(d_model)

        self.ffn = FeedForward(d_model, d_ff, dropout)
        self.dropout_3 = nn.Dropout(dropout)
        self.norm_3 = Norm(d_model)

    def forward(
        self,
        x: torch.Tensor,
        enc_output: torch.Tensor,
        src_mask: torch.Tensor,
        tgt_mask: torch.Tensor,
    ) -> torch.Tensor:
        x = x + self.dropout_1(self.attn_1(x, x, x, tgt_mask))
        x = self.norm_1(x)

        x = x + self.dropout_2(self.attn_2(x, enc_output, enc_output, src_mask))
        x = self.norm_2(x)

        x = x + self.dropout_3(self.ffn(x))
        x = self.norm_3(x)
        return x

Encoder 和 Decoder 分别由 N 个 EncoderLayer 和 DecoderLayer 组成。

代码实现如下:

class Encoder(nn.Module):
    def __init__(
        self,
        vocab_size: int,
        N: int = 6,
        d_model: int = 512,
        max_seq_len: int = 2048,
        heads: int = 8,
        d_ff: int = 2048,
        dropout: float = 0.1,
    ) -> None:
        super().__init__()

        self.N = N
        self.embed = Embedder(vocab_size, d_model)
        self.pe = PositionalEncoder(d_model, max_seq_len)
        self.layers = nn.ModuleList(
            [EncoderLayer(d_model, heads, d_ff, dropout) for _ in range(N)]
        )

    def forward(self, src: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
        x = self.embed(src)
        x = self.pe(x)
        for layer in self.layers:
            x = layer(x, mask)
        return x

class Decoder(nn.Module):
    def __init__(
        self,
        vocab_size: int,
        N: int = 6,
        d_model: int = 512,
        max_seq_len: int = 2048,
        heads: int = 8,
        d_ff: int = 2048,
        dropout: float = 0.1,
    ) -> None:
        super().__init__()

        self.N = N
        self.embed = Embedder(vocab_size, d_model)
        self.pe = PositionalEncoder(d_model, max_seq_len)
        self.layers = nn.ModuleList(
            [DecoderLayer(d_model, heads, d_ff, dropout) for _ in range(N)]
        )

    def forward(
        self,
        tgt: torch.Tensor,
        enc_output: torch.Tensor,
        src_mask: torch.Tensor,
        tgt_mask: torch.Tensor,
    ) -> torch.Tensor:
        x = self.embed(tgt)
        x = self.pe(x)
        for layer in self.layers:
            x = layer(x, enc_output, src_mask, tgt_mask)
        return x

最后组装成 Transformer!

class Transformer(nn.Module):
    def __init__(
        self,
        src_vocab: int,
        tgt_vocab: int,
        N: int = 6,
        d_model: int = 512,
        max_seq_len: int = 2048,
        heads: int = 8,
        d_ff: int = 2048,
        dropout: float = 0.1,
    ) -> None:
        super().__init__()

        self.encoder = Encoder(src_vocab, N, d_model, max_seq_len, heads, d_ff, dropout)
        self.decoder = Decoder(tgt_vocab, N, d_model, max_seq_len, heads, d_ff, dropout)
        self.out = nn.Linear(d_model, tgt_vocab)

    def forward(
        self,
        src: torch.Tensor,
        tgt: torch.Tensor,
        src_mask: torch.Tensor,
        tgt_mask: torch.Tensor,
    ) -> torch.Tensor:
        enc_output = self.encoder(src, src_mask)
        dec_output = self.decoder(tgt, enc_output, src_mask, tgt_mask)
        output = F.softmax(self.out(dec_output), dim=-1)
        return output

Test

测试一下代码能不能运行,按照如下配置测试:

from transformer_scratch import Transformer
import torch

bsz = 4
max_seq_len = 1024
src_vocab = 128
tgt_vocab = 64
N = 3
d_ff = 512

model = Transformer(src_vocab, tgt_vocab, N=N, max_seq_len=max_seq_len, d_ff=d_ff)

src = torch.randint(low=0, high=src_vocab, size=(bsz, max_seq_len))
tgt = torch.randint(low=0, high=tgt_vocab, size=(bsz, max_seq_len))
src_mask = torch.ones(size=(bsz, max_seq_len, max_seq_len))
tgt_mask = torch.ones(size=(bsz, max_seq_len, max_seq_len))

res = model(src, tgt, src_mask, tgt_mask)
print(f"Output data shape is: {res.shape}")

输出:Output data shape is: torch.Size([4, 1024, 64])

Reference

在编写过程中参考下面的博客,感谢大佬分享自己的经验。

那么,我们该如何学习大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、大模型全套的学习路线

学习大型人工智能模型,如GPT-3、BERT或任何其他先进的神经网络模型,需要系统的方法和持续的努力。既然要系统的学习大模型,那么学习路线是必不可少的,下面的这份路线能帮助你快速梳理知识,形成自己的体系。

L1级别:AI大模型时代的华丽登场

L2级别:AI大模型API应用开发工程

L3级别:大模型应用架构进阶实践

L4级别:大模型微调与私有化部署

一般掌握到第四个级别,市场上大多数岗位都是可以胜任,但要还不是天花板,天花板级别要求更加严格,对于算法和实战是非常苛刻的。建议普通人掌握到L4级别即可。

以上的AI大模型学习路线,不知道为什么发出来就有点糊,高清版可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

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

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

相关文章

在Anaconda中安装keras-contrib库

文章目录 1. 有git2. 无git2.1 步骤12.2 步骤22.3 步骤3 1. 有git 如果环境里有git,直接运行以下命令: pip install githttps://www.github.com/farizrahman4u/keras-contrib.git2. 无git 2.1 步骤1 打开网址:https://github.com/keras-tea…

Application UI

本节包含关于如何用DevExpress控件模拟许多流行的应用程序ui的教程。 Windows 11 UI Windows 11和最新一代微软Office产品启发的UI。 Office Inspired UI Word、Excel、PowerPoint和Visio等微软Office应用程序启发的UI。 如何:手动构建Office风格的UI 本教程演示…

微服务第二轮

学习文档 背景 由于每个微服务都有不同的地址或端口,入口不同 请求不同数据时要访问不同的入口,需要维护多个入口地址,麻烦 前端无法调用nacos,无法实时更新服务列表 单体架构时我们只需要完成一次用户登录、身份校验&#xff…

Uber 提升 Presto 集群稳定性的 GC 调优方法

Presto at Uber Uber 利用开源的 Presto 查询各种数据源,无论是流式还是归档数据。Presto 的多功能性赋予我们做出基于数据的明智商业决策的能力。我们在两个地区运行了大约20个 Presto 集群,总共超过10,000个节点。我们有大约12,000个每周活跃用户&…

【python010】获取任意多边形区域内的经纬度点并可视化

1.熟悉、梳理、总结项目研发实战中的Python开发日常使用中的问题、知识点等,如获取任意多边形区域内的经纬度点并可视化,找了N篇文章没发现有效的解决方案。 2.欢迎点赞、关注、批评、指正,互三走起来,小手动起来! 3.欢…

Here Doucument

一、Here Document概述 1.概念 使用I/0重定向的方式将命令列表提供给交互式程序 标准输入的一种替代品 2.语法格式 命令 <<标记 标记 3.注意事项 标记可以使用任意合法字符&#xff08;通常为EOF&#xff09; 结尾的标记一定要顶格写&#xff0c;前面不能有任何字符…

【爬虫实战项目一】Python爬取豆瓣电影榜单数据

目录 一、环境准备 二、编写代码 2.1 分页分析 2.2 编码 一、环境准备 安装requests和lxml pip install requests pip install lxml 二、编写代码 2.1 分页分析 编写代码前我们先看看榜单的url 我们假如要爬取五页的数据&#xff0c;那么五个url分别是&#xff1a; htt…

关于智慧校园建设的几点建议

随着科技的迅猛发展&#xff0c;智慧校园建设已成为现代教育的重要组成部分&#xff0c;对于提升教育质量、改善学生学习环境具有重要意义。为此&#xff0c;我提出以下几点建议&#xff0c;以帮助智慧校园建设更加有效和可持续。 首先&#xff0c;应注重基础设施建设。智慧校园…

java 实现导出word 自定义word 使用aspose教程包含图片 for 循环 自定义参数等功能

java 实现导出word 主要有一下几个知识点 1&#xff0c;aspose导入 jar包 和 java编写基础代码下载使用 aspose-words jar包导入 aspose jar 包 使用 maven导入java代码编写 2&#xff0c;if判断 是否显示2&#xff0c;显示指定值3&#xff0c;循环显示List 集合列表 使用 fore…

【CTF MISC】XCTF GFSJ0290 reverseMe Writeup(图像处理)

reverseMe 暂无 解法 导入 Photoshop。 水平翻转&#xff0c;得到 flag。 Flag flag{4f7548f93c7bef1dc6a0542cf04e796e}声明 本博客上发布的所有关于网络攻防技术的文章&#xff0c;仅用于教育和研究目的。所有涉及到的实验操作都在虚拟机或者专门设计的靶机上进行&#xf…

住宿管理系统 java+jsp+web三件套

文章目录 1、简要介绍2、数据库设计3、中间遇到的困难一、数据问题二、文件问题 4、项目 写了将近3周&#xff0c;人都写麻了 记录下&#xff0c;第一个 ss 代码 仅仅使用了layui作为前端UI框架&#xff0c;因为另一个项目用的也是他&#xff0c;感觉一些组件比较好用 后端是j…

Leetcode:三数之和

题目链接&#xff1a;15. 三数之和 - 力扣&#xff08;LeetCode&#xff09; 普通版本&#xff08;排序 双指针法&#xff09; 分析&#xff1a; 1、我们可以通过三个循环嵌套找到符合题目要求的三元组组合 2、但由于题目要求中的三元组i、j、k并不要求连续&#xff0c;所以会…

大模型的发展历程

1、早期模型的探索与局限性 1.1早期模型的探索与局限性 从早期的符号逻辑到现代的深度学习 1 模型&#xff0c;AI 领域经历了数十年的探索和迭代&#xff0c;为后续突破打下了坚实基础。随着大数据的发展和 AI 计算能力的爆炸式增长&#xff0c;深度学习模型的崛起显得尤为突出…

Spring运维之业务层测试数据回滚以及设置测试的随机用例

业务层测试数据回滚 我们之前在写dao层 测试的时候 如果执行到这边的代码 会在数据库 里面留下数据 运行一次留一次数据 开发有开发数据库&#xff0c;运行有运行数据库 我们先连数据库 在pom文件里引入mysql的驱动和mybatis-plus的依赖 在数据层写接口 用mybatis-plus进…

使用Python在Word中创建和提取表格

目录 安装Python Word库 使用Python在Word中创建预定义行和列的表格 使用Python在Word中动态创建表格 使用Python在Word中提取表格数据 Word 文档中的表格是一种强大且灵活的数据组织和展示工具&#xff0c;它能将信息以行和列的形式有序地排列&#xff0c;使文档内容更加清…

SQL Developer迁移第三方数据库单表到Oracle

在SQL Developer中&#xff0c;除可用Migration Wizard迁移第三方数据库到Oracle外&#xff0c;单表的迁移可以用Copy To Oracle ...菜单。右键单击源表即可。 本例的源表为MySQL数据库employees中的表&#xff1a;departments。 Options页面&#xff1a;指定目标库&#xff…

如何将 Windows图片查看器的背景颜色改成浅色(灰白色)?

现在大家基本都在使用Win10系统&#xff0c;我们在双击查看图片时&#xff0c;系统默认使用系统自带的图片&#xff08;照片&#xff09;查看器去打开图片。图片查看器的背景色默认是黑色的&#xff0c;如下所示&#xff1a;&#xff08;因为大家可能会遇到同样的问题&#xff…

pxe自动装机与无人值守

一、pxe与无人值守 pxe&#xff1a;c/s 模式&#xff0c;允许客户端通过网络从远程服务器&#xff08;服务端&#xff09;下载引导镜像&#xff0c;加载安装文件&#xff0c;实现自动化安装操作系统。 pxe的优点&#xff1a; 1、规模化 同时装配多台服务器&#xff08;20多&…

Hive知识体系保姆级教程

一. Hive概览 1.1 hive的简介 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供类SQL查询功能。 其本质是将SQL转换为MapReduce/Spark的任务进行运算&#xff0c;底层由HDFS来提供数据的存储&#xff0c;说白了h…

【wiki知识库】06.文档管理页面的添加--前端Vue部分

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、&#x1f525;今日目标 二、&#x1f43b;前端Vue模块的改造 BUG修改 1.wangeditor无法展示问题 2.弹窗无法正常关闭问题 2.1 添加admin-doc.vue 2.1.1 点击admin-ebook中的路由跳转到admin-doc 2.2.2 进入…