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

在 AMD GPU 上构建解码器 Transformer 模型

Building a decoder transformer model on AMD GPU(s) — ROCm Blogs

2024年3月12日 作者 Phillip Dang.

在这篇博客中,我们展示了如何使用 PyTorch 2.0 和 ROCm 在单个节点上的单个和多个 AMD GPU 上运行Andrej Karpathy’s beautiful PyTorch re-implementation of GPTGPT PyTorch 重新实现Andrej Karpathy’s beautiful PyTorch re-implementation of GPT。

GPT 模型的核心是仅解码器的 transformer 架构。这种架构以自回归的方式一次生成一个输出标记(在我们的情况下是字符),这意味着每个生成的标记都依赖于之前生成的标记。如需深入了解该模型的工作原理,我们强烈推荐查看以下内容:

  • Andrej Karpathy的 Let’s build GPT: 从头开始,用代码详细阐述 视频

  • Attention Is All You Need 论文。

我们首先使用 PyTorch 和 ROCm 在单个 GPU 上训练我们的模型,然后稍微修改代码以使用 to run on two GPUs using PyTorch 的分布式数据并行(Distributed Data Parallel, DDP)在两个 GPU 上运行。

在多 GPU 上进行分布式数据并行性的工作原理是:

  1. 将当前全局训练批次拆分为每个 GPU 上的小局部批次。例如,如果你有 8 个 GPU,且全局批次设置为 32 个样本,则每个 8 个 GPU 将具有 4 个样本的局部批次大小。

  2. 将模型复制到每个设备,以便每个设备可以独立处理其局部批次。

  3. 运行前向传播,然后是反向传播,并输出模型权重相对于局部批次损失的渐变。这会在多个设备上并发发生。

  4. 同步每个设备计算的局部梯度并将它们结合起来以更新模型权重。更新后的权重会重新分发到每个设备。

如需深入了解 PyTorch 中的分布式训练,请参阅 使用 DDP 进行多 GPU 训练。

先决条件

要跟随本博客的内容进行操作,您需要具备以下软件:

  • ROCm

  • PyTorch

  • Linux OS

接下来,通过运行以下代码确保您的系统能够识别两块 AMD GPU:

! rocm-smi --showproductname

输出结果应如下所示:

================= ROCm System Management Interface ================
========================= Product Info ============================
GPU[0] : Card series: Instinct MI210
GPU[0] : Card model: 0x0c34
GPU[0] : Card vendor: Advanced Micro Devices, Inc. [AMD/ATI]
GPU[0] : Card SKU: D67301
GPU[1] : Card series: Instinct MI210
GPU[1] : Card model: 0x0c34
GPU[1] : Card vendor: Advanced Micro Devices, Inc. [AMD/ATI]
GPU[1] : Card SKU: D67301
===================================================================
===================== End of ROCm SMI Log =========================

确保 PyTorch 也能识别这些 GPU:

import torch
import torch.nn as nn
from torch.nn import functional as F
print(f"number of GPUs: {torch.cuda.device_count()}")
print([torch.cuda.get_device_name(i) for i in range(torch.cuda.device_count())])

输出结果应如下所示:

number of GPUs: 2
['AMD Radeon Graphics', 'AMD Radeon Graphics']

加载数据

我们使用 tiny_shakespeare 数据集,该数据集包含来自莎士比亚各种剧作的40,000行文本。让我们加载它并查看前200个字符。

! wget https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt
with open('input.txt', 'r', encoding='utf-8') as f:text = f.read()
print(text[:200])

输出:

First Citizen:
Before we proceed any further, hear me speak.All:
Speak, speak.First Citizen:
You are all resolved rather to die than to famish?All:
Resolved. resolved.First Citizen:
First, you

接下来,我们从文本中的唯一字符创建我们的词汇表,并建立字符与整数之间的映射。

chars = sorted(list(set(text)))
vocab_size = len(chars)
# create a mapping from characters to integers
stoi = { ch:i for i,ch in enumerate(chars) }
itos = { i:ch for i,ch in enumerate(chars) }
encode = lambda s: [stoi[c] for c in s] # encoder: take a string, output a list of integers
decode = lambda l: ''.join([itos[i] for i in l]) # decoder: take a list of integers, output a string

现在,我们将数据在编码字符之后分成训练集和测试集(即将每个字符转换为一个整数)。

data = torch.tensor(encode(text), dtype=torch.long)
n = int(0.9*len(data)) # first 90% will be train, rest val
train_data = data[:n]
val_data = data[n:]

让我们来看一个输入和目标的例子。

block_size = 8
x = train_data[:block_size]
y = train_data[1:block_size+1]
for t in range(block_size):context = x[:t+1]target = y[t]print(f"when input is {context} the target: {target}")

输出:

when input is tensor([18]) the target: 47
when input is tensor([18, 47]) the target: 56
when input is tensor([18, 47, 56]) the target: 57
when input is tensor([18, 47, 56, 57]) the target: 58
when input is tensor([18, 47, 56, 57, 58]) the target: 1
when input is tensor([18, 47, 56, 57, 58,  1]) the target: 15
when input is tensor([18, 47, 56, 57, 58,  1, 15]) the target: 47
when input is tensor([18, 47, 56, 57, 58,  1, 15, 47]) the target: 58

创建解码器transformer模型

让我们设置一些超参数,用于构建和训练模型。因为我们能够使用强大的AMD GPU,可以扩展我们的网络,并设置与Andrej在其视频教程中使用的相同的超参数。这包括增加我们的批量大小、块大小、层数和头数以及嵌入大小。我们希望这将使我们的损失值更低。

# 超参数
batch_size = 64 # 我们将并行处理多少个独立序列?
block_size = 256 # 预测的最大上下文长度是多少?
max_iters = 5000
eval_interval = 100
learning_rate = 3e-4
device = 'cuda' if torch.cuda.is_available() else 'cpu'
eval_iters = 200
n_embd = 384
n_head = 6
n_layer = 6
dropout = 0.2
# ------------torch.manual_seed(1337) # 设置手动种子以保证可重复性

解码器Transformer架构

以下是我们的主要模型类,它创建了一个解码器Transformer架构。其组件包括:

  • 自注意力机制:允许解码器在生成输出时权衡输入序列的不同部分。

  • 掩码自注意力:通过掩盖后续位置,防止解码器在训练期间看到未来的令牌。

  • 解码器层:由多层子层组成,如多头注意力和前馈神经网络,促进信息处理和生成。

Transformer decoder process

class Head(nn.Module):""" 一个自注意力头  """def __init__(self, head_size):super().__init__()self.key = nn.Linear(n_embd, head_size, bias=False)self.query = nn.Linear(n_embd, head_size, bias=False)self.value = nn.Linear(n_embd, head_size, bias=False)self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))self.dropout = nn.Dropout(dropout)def forward(self, x):# 输入大小 (batch, time-step, channels)# 输出大小 (batch, time-step, head size)B,T,C = x.shapek = self.key(x)   # (B,T,hs)q = self.query(x) # (B,T,hs)# 计算注意力分数 ("affinities")wei = q @ k.transpose(-2,-1) * k.shape[-1]**-0.5 # (B, T, hs) @ (B, hs, T) -> (B, T, T)# 使用上三角矩阵技巧创建掩码用于自注意力机制# 该掩码确保在训练期间,解码器只能关注到当前生成的令牌之前的位置,防止看到未来的令牌,即仅从左到右注意wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf')) # (B, T, T)wei = F.softmax(wei, dim=-1) # (B, T, T)wei = self.dropout(wei)#  执行值的加权聚合v = self.value(x) # (B,T,hs)out = wei @ v # (B, T, T) @ (B, T, hs) -> (B, T, hs)return outclass MultiHeadAttention(nn.Module):""" 并行的多头自注意力机制 """def __init__(self, num_heads, head_size):super().__init__()self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])self.proj = nn.Linear(head_size * num_heads, n_embd)self.dropout = nn.Dropout(dropout)def forward(self, x):out = torch.cat([h(x) for h in self.heads], dim=-1)out = self.dropout(self.proj(out))return outclass FeedFoward(nn.Module):""" 一个简单的线性层及其非线性激活 """def __init__(self, n_embd):super().__init__()self.net = nn.Sequential(nn.Linear(n_embd, 4 * n_embd),nn.ReLU(),nn.Linear(4 * n_embd, n_embd),nn.Dropout(dropout),)def forward(self, x):return self.net(x)class Block(nn.Module):""" Transformer块: 通信后是计算  """def __init__(self, n_embd, n_head):# n_embd: 嵌入维度,n_head: 多头数量super().__init__()head_size = n_embd // n_headself.sa = MultiHeadAttention(n_head, head_size)self.ffwd = FeedFoward(n_embd)self.ln1 = nn.LayerNorm(n_embd)self.ln2 = nn.LayerNorm(n_embd)def forward(self, x):x = x + self.sa(self.ln1(x))x = x + self.ffwd(self.ln2(x))return xclass GPTLanguageModel(nn.Module):def __init__(self):super().__init__()# 每个令牌直接从查找表中读取下一个令牌的logitsself.token_embedding_table = nn.Embedding(vocab_size, n_embd)self.position_embedding_table = nn.Embedding(block_size, n_embd)self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])self.ln_f = nn.LayerNorm(n_embd) # final layer normself.lm_head = nn.Linear(n_embd, vocab_size)# 更好的权重初始化,原始GPT视频中未涵盖,但很重要,将在后续视频中介绍self.apply(self._init_weights)def _init_weights(self, module):if isinstance(module, nn.Linear):torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)if module.bias is not None:torch.nn.init.zeros_(module.bias)elif isinstance(module, nn.Embedding):torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)def forward(self, idx, targets=None):B, T = idx.shape# idx 和 targets 都是 (B, T) 的整数张量tok_emb = self.token_embedding_table(idx) # (B,T,C)pos_emb = self.position_embedding_table(torch.arange(T, device=device)) # (T,C)x = tok_emb + pos_emb # (B,T,C)x = self.blocks(x) # (B,T,C)x = self.ln_f(x) # (B,T,C)logits = self.lm_head(x) # (B,T,vocab_size)if targets is None:loss = Noneelse:B, T, C = logits.shapelogits = logits.view(B*T, C)targets = targets.view(B*T)loss = F.cross_entropy(logits, targets)return logits, lossdef generate(self, idx, max_new_tokens):# idx 是 (B, T) 当前上下文的索引数组for _ in range(max_new_tokens):# 将 idx 裁剪为最后 block_size 个令牌idx_cond = idx[:, -block_size:]# 获取预测结果logits, loss = self(idx_cond)# 只关注最后一个时间步logits = logits[:, -1, :] # becomes (B, C)# 应用 softmax 获得概率probs = F.softmax(logits, dim=-1) # (B, C)# 从概率分布中采样idx_next = torch.multinomial(probs, num_samples=1) # (B, 1)# 将采样得到的索引追加到运行的序列中idx = torch.cat((idx, idx_next), dim=1) # (B, T+1)return idx

实用函数

在训练我们的模型之前,我们需要两个实用函数:

  • 在训练我们的模型之前,我们需要两个实用函数:

  • 一个是获取随机批量数据的函数

为了估计损失:

@torch.no_grad()
def estimate_loss(model):out = {}model.eval()for split in ['train', 'val']:losses = torch.zeros(eval_iters)for k in range(eval_iters):X, Y = get_batch(split)logits, loss = model(X, Y)losses[k] = loss.item()out[split] = losses.mean()model.train()return out

为了获取一个小批量的数据:

# 数据加载
def get_batch(split):# 数据加载data = train_data if split == 'train' else val_dataix = torch.randint(len(data) - block_size, (batch_size,))x = torch.stack([data[i:i+block_size] for i in ix])y = torch.stack([data[i+1:i+block_size+1] for i in ix])x, y = x.to(device), y.to(device)return x, y

训练和推理 

现在我们已经准备好所有的部分,让我们实例化我们的模型,进行训练,并运行一些推理来生成我们(希望)类似莎士比亚风格的文本。

这是我们的主函数:

def main():model = GPTLanguageModel()model = model.to(device)# 输出模型的参数数量print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters')# 创建一个 PyTorch 优化器optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)for iter in range(max_iters):# 定期评估训练集和验证集的损失if iter % eval_interval == 0 or iter == max_iters - 1:losses = estimate_loss(model)print(f"step {iter}: train loss {losses['train']:.4f}, val loss {losses['val']:.4f}")# 抽取一个数据批次xb, yb = get_batch('train')# 计算损失logits, loss = model(xb, yb)optimizer.zero_grad(set_to_none=True)loss.backward()optimizer.step()# 使用模型进行推理以生成文本context = torch.zeros((1, 1), dtype=torch.long, device=device)print(decode(model.generate(context, max_new_tokens=2000)[0].tolist()))

我们创建了一个名为 gpt_single_gpu.py 的脚本,包含所有必要的命令。要运行它,请使用以下代码:

python3 gpt_single_gpu.py

以下是脚本的预期输出:

10.788929 M parameters
step 0: train loss 4.2221, val loss 4.2306
step 100: train loss 2.4966, val loss 2.5012
step 200: train loss 2.4029, val loss 2.4295
...
step 4900: train loss 0.8676, val loss 1.5644
step 4999: train loss 0.8598, val loss 1.5677

以下是我们生成的文本的前几行:

Thou fellow'dst idst the game of his names;
And yet since was Menenius, one would thrident again
That Anne. But where shall do become me injuries?JULIET:
O though often thee cortainted matter,--
A better with him he gone hath
A colder-balm equal-deniving,
Of what a peril the people, when he did make me
Disobedition, become him to see
That conceive on earth fitting his finger,

在验证损失为1.5677时,生成的文本看起来几乎像英语,并且模型能够学习莎士比亚输入的对话风格。

每次运行推理时,我们期望的结果会有所不同。这是因为模型从所有可能的标记分布中进行采样,其中每个标记的概率由 softmax 函数给出。

分布式训练在多 GPU 上

为了在 PyTorch 中使用多个 GPU 在单个节点上训练我们的模型,我们将使用 PyTorch 的分布式数据并行 (Distributed Data Parallel)。 为此,我们只需要对当前代码进行一点点修改。

首先,让我们从 PyTorch 导入一些所需的函数:

import torch.multiprocessing as mp
from torch.distributed import init_process_group, destroy_process_group
from torch.nn.parallel import DistributedDataParallel as DDP

接下来,让我们设置分布式数据并行组。 通常,每个 GPU 运行一个进程,因此我们需要设置一个组,以便所有进程和 GPU 之间可以相互通信。 让我们创建一个执行此操作的小函数。

def ddp_setup(rank, world_size):"""world_size: GPU 的数量rank: GPU 的 ID,从 0 到 world_size - 1"""os.environ['MASTER_ADDR'] = 'localhost'os.environ['MASTER_PORT'] = '12355'# 初始化进程组backend = "nccl"init_process_group(backend=backend, rank=rank, world_size=world_size)torch.cuda.set_device(rank)

在实例化模型之前,我们需要调用这个设置函数。 接下来,我们对主函数进行些许修改,使其能够在多个 GPU 上运行。 请注意,主函数现在接收两个参数:rank(GPU 的 ID)和 world_size(GPU 的数量)。

def main(rank:int, world_size:int):print(f"Training DDP model on rank/gpu {rank}.")ddp_setup(rank, world_size)# 每个 gpu/进程获得不同的种子torch.manual_seed(1337 + rank)model = GPTLanguageModel()model.to(rank)model = DDP(model, device_ids=[rank])... # 剩下的训练过程与单进程主函数的训练过程相同# 运行推理if rank == 0: # 仅在主进程上运行推理。没有这个 if 语句,每个进程都会运行自己的预测print('generating text')context = torch.zeros((1, 1), dtype=torch.long, device=device)# 因为模型现在是一个分布式模型,我们需要通过添加 "module" 来解包它print(decode(model.module.generate(context, max_new_tokens=500)[0].tolist()))#  一旦模型训练完成,销毁进程以干净退出destroy_process_group()

完整代码

我们创建了一个脚本 gpt_multiple_gpus.py ,其中包含所有必需的命令。要运行该脚本,请使用以下代码:

python3 gpt_multiple_gpus.py

以下是该脚本的预期输出。

We have 2 GPUs! Using 2 GPUs
Training DDP model on rank/gpu 1.
Training DDP model on rank/gpu 0.
10.788929 M parameters
10.788929 M parameters
GPU/rank 0 step 0: train loss 4.2221, val loss 4.2306
GPU/rank 1 step 0: train loss 4.2228, val loss 4.2304
GPU/rank 0 step 500: train loss 1.6010, val loss 1.7904
GPU/rank 1 step 500: train loss 1.5984, val loss 1.7871
...
GPU/rank 1 step 4999: train loss 0.5810, val loss 1.7733
GPU/rank 0 step 4999: train loss 0.5807, val loss 1.7723

以下是生成的文本:

HENRY BOLINGBROKE:
Warwick, It say; and he is safe, and whose
With unmorable slaves, they have stafd too:
So say the tidings you for Richmond, with ride?BUSHY:
Marry, my Lord Clarence to my noble lady.GLOUCESTER:
Go, to thee to thy daughter as I may break;
And what do now thy will I, I do me say
My name; it is the king.BUCKINGHAM:
'Twas every stranger:--
Nay, my good son.

输出来自两个不同的GPU(rank 0和1),每个训练/验证损失是不同的。这是因为每个进程有不同的种子,以确保它们不会在相同的数据批次上进行训练。 

相关文章:

在 AMD GPU 上构建解码器 Transformer 模型

Building a decoder transformer model on AMD GPU(s) — ROCm Blogs 2024年3月12日 作者 Phillip Dang. 在这篇博客中,我们展示了如何使用 PyTorch 2.0 和 ROCm 在单个节点上的单个和多个 AMD GPU 上运行Andrej Karpathy’s beautiful PyTorch re-implementation …...

Canvas简历编辑器-选中绘制与拖拽多选交互设计

Canvas简历编辑器-选中绘制与拖拽多选交互设计 在之前我们聊了聊如何基于Canvas与基本事件组合实现了轻量级DOM,并且在此基础上实现了如何进行管理事件以及多层级渲染的能力设计。那么此时我们就依然在轻量级DOM的基础上,关注于实现选中绘制与拖拽多选交…...

简单工厂(Simple Factory)

简单工厂(Simple Factory) 在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。 说明: 简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪…...

ffmpeg拉流分段存储到文件-笔记

通过ffmpeg可以从rtsp网络流拉取数据并存储到本地文件里,如下命令。做个笔记 ffmpeg -rtsp_transport tcp -i rtsp://192.168.1.168:6880/live -c copy -f segment -segment_time 60 stream_piece_%d.mp4这条 ffmpeg 命令的作用是从一个 RTSP 流中捕获视频&#xff…...

Java 实习工资大概是多少?——解读影响薪资的因素

文章目录 1. 城市因素:一线、二线的差距2. 公司类型:互联网公司、外企和传统企业的差别3. 个人能力:经验、技术栈的重要性4. 其他影响因素:学历和实习时间总结推荐阅读文章 Java 开发作为广泛应用的职业方向,实习工资的…...

【Linux】万字详解:Linux文件系统与软硬链接

🌈 个人主页:Zfox_ 🔥 系列专栏:Linux 目录 🚀 前言 一: 🔥 磁盘的物理结构二: 🔥 磁盘的存储结构 三: 🔥 磁盘的逻辑结构 四: &#…...

spacenavd

介绍spacenavd开源项目,主要是因为在斯坦福大学的UMI项目中使用了该项目。在斯坦福大学的 UMI(Universal Manipulation Interface)项目中,Spacenavd 主要用于处理 3D Space Mouse(空间鼠标)的输入&#xf…...

C#WPF的XAML的语法详谈和特性

WPF的XAML(eXtensible Application Markup Language)是一种基于XML的标记语言,用于在.NET框架中定义和描述用户界面。XAML提供了一种声明性的方式来构建应用程序的UI元素,包括窗口、控件、布局、样式、动画和数据绑定等。 XAML的…...

一篇文章讲透数据结构之二叉搜索树

前言 在前面的学习过程中,我们已经学习了二叉树的相关知识。在这里我们再使用C来实现一些比较难的数据结构。 这篇文章用来实现二叉搜索树。 一.二叉搜索树 1.1二叉搜索树的定义 二叉搜索树(Binary Search Tree)是基于二叉树的一种升级版…...

新手入门c++(8)

到时候了,是时候给你们讲一下其他的定义形式与格式化输入输出了。 1.长整型变量 长整型变量分为两种: ①long类型 在计算机编程中,long 类型是一个整型数据类型,用于存储较大的整数。它的大小和范围取决于操作系统和编译器的实…...

新手铲屎官提问,有哪几款噪音低的宠物空气净化器推荐

相信很多铲屎官都明白的的痛就是猫咪掉毛太严重,所以每次看到满天飞的浮毛时只想赶紧逃离,一点都不想清理。但是家是自己的,猫是自己的,健康也是自己的,不清理也得清理。 为了更有效的清理浮毛,我朋友特意…...

解决RabbitMQ脑裂问题

文章目录 前言一、现象二、解决办法 前言 RabbitMQ脑裂 一、现象 RabbitMQ镜像群出现脑裂现象,各个节点的MQ实例都“各自为政”,数据并不同步。 二、解决办法 # 停止mq sh rabbitmq-server stop_app # 查看mq进程是否存在 ps -ef | grep rabbitmq # …...

经纬恒润AUTOSAR成功适配芯钛科技Alioth TTA8车规级芯片

在汽车电子领域,功能安全扮演着守护者的角色,它确保了车辆在复杂多变的情况下保持稳定可靠的运行。随着汽车电子的复杂性增加,市场对产品功能安全的要求也日益提高。基于此背景,经纬恒润AUTOSAR基础软件产品INTEWORK-EAS-CP成功适…...

4、java random随机数、一维数组、二维数组

目录 Random类与随机数生成数组的概述与使用数组的内存分配与访问数组的常见问题与解决方案一维数组的遍历与操作二维数组的概述与遍历1. Random类与随机数生成 引言 在编程中,我们经常需要生成随机数,比如在游戏、模拟实验或者数据处理中。Java提供了一个非常方便的类Rand…...

C++ 魔法三钥:解锁高效编程的封装、继承与多态

快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。 目录 💯前言 💯封装 1.封装概念 2.封装格式 3.封装的原理 4.封装的作用 💯继承 1.继承的概念 2.继承格式 3.继承的…...

姿态传感器(学习笔记上)

上节我们学的是温湿传感器,这节我们学的是姿态传感器,虽然都是传感器,但是它们还是有很大的区别的,这节的传感器我们通过学习可知,开发板上的姿态传感器型号是QMI8658C,内部集成3轴加速度传感器和3轴陀螺仪…...

labelimg使用教程

快捷键 W:调出标注的十字架,开始标注 A:切换到上一张图片 D:切换到下一张图片 del:删除标注的矩形框 CtrlS:保存标注好的标签 Ctrl鼠标滚轮:按住Ctrl,然后滚动鼠标滚轮,…...

力扣21 : 合并两个有序链表

链表style 描述: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例: 节点大小相同时,l1的节点在前 何解? 1,遍历两个链表,挨个比较节点大小 同时遍…...

【Spring】Spring Boot 配置文件(7)

本系列共涉及4个框架:Sping,SpringBoot,Spring MVC,Mybatis。 博客涉及框架的重要知识点,根据序号学习即可。 有什么不懂的都可以问我,看到消息会回复的,可能会不及时,请见谅!! 1、配置文件作…...

《向量数据库指南》——解锁Wikipedia文章向量的跨语言搜索秘籍

嘿,各位向量数据库和AI应用的小伙伴们,我是你们的老朋友王帅旭,大禹智库的向量数据库高级研究员,也是《向量数据库指南》的作者。今天,咱们来聊聊一个超棒的数据集——百万条 Wikipedia 文章向量,这可是我在研究过程中发现的一个宝藏啊! 首先,咱们得说说这个数据集的来…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes&#xff0…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用

文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...

相关类相关的可视化图像总结

目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系,可直观判断线性相关、非线性相关或无相关关系,点的分布密…...

数据库——redis

一、Redis 介绍 1. 概述 Redis(Remote Dictionary Server)是一个开源的、高性能的内存键值数据库系统,具有以下核心特点: 内存存储架构:数据主要存储在内存中,提供微秒级的读写响应 多数据结构支持&…...