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

ScratchLLMStepByStep:SFT之分类微调

1. 引言

前面我们花了三节内容来介绍预训练,包括如何从零搭建、如何加速运算、如何分布式加速训练,本节开始我们将进入监督微调(SFT)阶段。

常见语言模型的微调任务有两类,分类微调和指令微调。

  • 分类微调模型通常是一个专用模型,它只能用来进行特定分类标签的预测,例如:输入一封邮件文本,输出这封邮件是垃圾邮件、非垃圾邮件。
  • 指令微调模型通常是一个多任务模型,它可以同时在多个任务上表现良好,训练的难度也更大。

在这两类微调任务中,分类微调更为专注单一任务的优化,它在处理特定应用场景(例如情感分析、主题分类或意图识别等任务时)时,能够表现出更高的准确率,同时对训练数据量的要求也更少。并且,由于是单一任务,分类微调训练时不需要指令,直接输入数据进行训练,如下所示:
在这里插入图片描述

注:分类微调在传统的机器学习中非常常见,例如经典的手写数字识别,只不过手写数字识别是对图片进行分类,我们这里是对文本进行分类。

本节我们将以垃圾邮件检测这个任务为例,来介绍如何对预训练模型进行分类微调。

2. 数据准备

2.1 数据下载

先下载数据集:

!curl -o ../../dataset/minigpt/smsspam.txt "https://archive.ics.uci.edu/static/public/228/sms+spam+collection.zip"

下载完后进行解压,数据集是一个文本文件,可以通过pandas库来预览下载后的数据。

import pandas as pddf = pd.read_csv("/data2/minigpt/dataset/sft/smsspam.txt", sep='\t', header=None, names=["label", "text"])
df.head()
labeltext
0hamGo until jurong point, crazy.. Available only ...
1hamOk lar... Joking wif u oni...
2spamFree entry in 2 a wkly comp to win FA Cup fina...
3hamU dun say so early hor... U c already then say...
4hamNah I don't think he goes to usf, he lives aro...

可以看到,数据集比较简单,每个数据只有两个信息:文本(text)和标签分类(label),标签分类也只有两个类别:ham表示正常邮件,spam表示垃圾邮件。

2.2 数据处理

下面,我们来查看下两个标签分类的数据分布。

df['label'].value_counts()
label
ham     4825
spam     747
Name: count, dtype: int64

可以看到,两种标签的数据分布是不均衡的,正常邮件ham的数据量远比垃圾邮件spam类别要多。这不利于模型的训练学习,需要对数据分布作平衡操作。

数据平衡通常有两种方式:

  1. 对偏多的数据做欠采样;
  2. 对偏少的数据做过采样。

我们本节主要是为了演示,所以采用比较简单的方式,对ham类别的数据进行欠采样,使之与spam类别的数据量相等,均为747条。

注:如果想要进行过采样,可以参考这篇文章中介绍的方法:over_sampling

def balance_dataset(df):spam_set = df[df["label"] == "spam"]ham_sub_set = df[df["label"] == "ham"].sample(spam_set.shape[0], random_state=123)return pd.concat([spam_set, ham_sub_set])balanced_df = balance_dataset(df)
balanced_df["label"].value_counts()
label
spam    747
ham     747
Name: count, dtype: int64

将字符串分类转换为数字分类0和1,便于模型预测。

balanced_df['label'] = balanced_df['label'].map({'spam': 1, 'ham': 0})
balanced_df['label'].value_counts()
label
1    747
0    747
Name: count, dtype: int64

接下来,我们写一个切分函数random_split,采用8:1:1的比例,随机的将数据集分割为训练集、验证集和测试集。

def random_split(df, train_ratio, eval_ratio):df = df.sample(frac=1, random_state=123).reset_index(drop=True)train_len = int(len(df) * train_ratio)eval_len = train_len + int(len(df) * eval_ratio)return df[:train_len], df[train_len: eval_len], df[eval_len:]train_df, eval_df, test_df = random_split(balanced_df, 0.8, 0.1)
len(train_df), len(eval_df), len(test_df)
(1195, 149, 150)
2.3 数据加载器

模型训练时一般都会使用小批量梯度下降的方法,这要求同一批训练数据必须具有相同的序列长度,我们可以使用前面训练的分词器,对前10条文本进行序列化查看下长度分布。

from transformers  import AutoTokenizertokenizer_path = "/data2/minigpt/models/tokenizer_v3"
tokenizer = AutoTokenizer.from_pretrained(tokenizer_path, use_fast=False)
inputs = [tokenizer.encode(text) for text in train_df['text']]
for i, v in enumerate(inputs[:10]):print(f"seq {i} length: {len(v)}")
seq 0 length: 42
seq 1 length: 45
seq 2 length: 11
seq 3 length: 23
seq 4 length: 5
seq 5 length: 49
seq 6 length: 53
seq 7 length: 6
seq 8 length: 79
seq 9 length: 12

可以看到,训练数据集中每条文本序列化后的长度基本都不相同。我们想要每个小批量都具有相同的长度,就只能有两种做法:

  1. 长序列变短:将一个数据集或小批量中的所有文本都切割成与最短文本的序列长度相同;
  2. 短序列变长:将一个数据集或小批量中的所有文本都填充到与最长文本的序列长度相同;

通常情况下,第二种方法用的更多,它能很好保留每条文本的内容完整。下面是使用<|endoftext|>作为填充token的长度填充示例。
在这里插入图片描述

下面自定义一个数据集类,来封装文本的序列化和长度填充逻辑,以pytorch中标准化的形式来提供数据集迭代功能,每次迭代时会自动调用__getitem__内置方法,返回一条输入序列和一个目标分类标签。

from torch.utils.data import Datasetclass SpamDataset(Dataset):def __init__(self, raw_df, tokenizer, max_tokens=1024):self.labels = raw_df['label']self.tokenizer = tokenizerpad_token_id = tokenizer.unk_token_idinputs = [tokenizer.encode(text)[:max_tokens] for text in raw_df['text']]max_length = max([len(item) for item in inputs])self.inputs = [item + [pad_token_id] * (max_length - len(item)) for item in inputs]def __len__(self):return len(self.inputs)def __getitem__(self, i):return (torch.tensor(self.inputs[i], dtype=torch.int64), torch.tensor(self.labels.iloc[i], dtype=torch.int64))

接下来,我们将前面切割后的训练集、验证集、测试集分别用SpamDataset封装,得到train_seteval_settest_set三个标准数据集,并以此为基础为训练、验证、则试三个场景分别创建三个小批量数据加载器train_loadereval_loadertest_loader

import torch
from torch.utils.data import DataLoadertorch.manual_seed(123)
batch_size = 8train_set = SpamDataset(train_df, tokenizer)
eval_set = SpamDataset(eval_df, tokenizer)
test_set = SpamDataset(test_df, tokenizer)train_loader = DataLoader(train_set, shuffle=True, batch_size=batch_size)
eval_loader = DataLoader(eval_set, shuffle=True, batch_size=batch_size)
test_loader = DataLoader(test_set, shuffle=True, batch_size=batch_size)

对DataLoader进行迭代时,会自动按照batch_size指定的小批量大小,一次性从Dataset中取多条数据(如下所示inputs和labels分别是8条)。

inputs, labels = next(iter(train_loader))
inputs, labels
(tensor([[ 1148,  3476,  2948,  ...,     0,     0,     0],[ 8150,    68,  6409,  ...,     0,     0,     0],[12203,  6527,  1017,  ...,     0,     0,     0],...,[17647,  2511,   276,  ...,     0,     0,     0],[   40, 14387,    39,  ...,     0,     0,     0],[   43,  2341,   290,  ...,     0,     0,     0]]),tensor([1, 0, 1, 1, 1, 0, 1, 0]))

3. 准备模型

在这一部分,我们需要对之前预训练的模型的输出头进行改造,用二分类代替词表长度的多分类。

3.1 加载模型状态

首先,加载之前预训练的模型,来看下模型的结构。

%run transformer.py
device = 'cpu'
checkpoint_path = "/data2/minigpt/models/20241015/checkpoint-180000.pth"model = MiniGPT(GPTConfig(flash_attn=True))
checkpoint = torch.load(checkpoint_path, map_location=device, weights_only=False)
model.load_state_dict(checkpoint['model_state'])
model
MiniGPT((token_emb): Embedding(32000, 768)(drop_emb): Dropout(p=0.1, inplace=False)(decode_layers): ModuleList((0-11): 12 x TransformerBlock((atten): FlashMultiHeadAttention((Wq): Linear(in_features=768, out_features=768, bias=False)(Wk): Linear(in_features=768, out_features=768, bias=False)(Wv): Linear(in_features=768, out_features=768, bias=False)(Wo): Linear(in_features=768, out_features=768, bias=True)(dropout): Dropout(p=0.1, inplace=False))(ffn): FeedForward((layers): Sequential((0): Linear(in_features=768, out_features=3072, bias=True)(1): GELU(approximate='none')(2): Linear(in_features=3072, out_features=768, bias=True)))(drop): Dropout(p=0.1, inplace=False)(layernorm1): LayerNorm()(layernorm2): LayerNorm()))(final_norm): LayerNorm()(out_head): Linear(in_features=768, out_features=32000, bias=True)
)

目前的输出头out_head将嵌入维度768映射到32000(词表大小),但是在这个垃圾分类任务中我们只需要二个目标分类01,因此,我们需要自己定义一个二分类输出头并替换掉原来的out_head,如下所示。

3.2 自定义输出头
torch.manual_seed(123)
classes = 2model.out_head = nn.Linear(in_features=model.out_head.in_features, out_features=classes)
model.out_head
Linear(in_features=768, out_features=2, bias=True)

理论上来讲,只需要训练新的输出头out_head,但业界一些实验表明,训练更多的层有助于提高性能。因此,我们将最后一个transformer block和连接它的layernorm设置为可训练,其它层设置为不可训练(效果如下图和代码所示)。
在这里插入图片描述

for param in model.parameters():param.requires_grad = Falsefor param in model.decode_layers[-1].parameters():param.requires_grad = Truefor param in model.final_norm.parameters():param.requires_grad = Truefor param in model.out_head.parameters():param.requires_grad = Truetrainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad==True)
all_params = sum(p.numel() for p in model.parameters())
trainable_params, all_params
(7088642, 109605890)

如上打印信息所示,需要训练的参数总共有708万,占所有参数109605890的比例约6.47%

3.3 模型测试

拿一个输入文本来测试二分类输出。

input_text = "Do you have time?"
input_tokens = tokenizer.encode(input_text)
inputs = torch.tensor(input_tokens).unsqueeze(0)
inputs.shape
torch.Size([1, 5])
with torch.no_grad():outputs = model(inputs)outputs.shape
torch.Size([1, 5, 2])

可以看到,输入与之前并没有什么区别,是一个batch_size=1,seq_len=5的单条序列,输出与之前有所不同,变成了维度为2的二分类输出。

根据因果注意力掩码的特性,每个token都只能看到此token本身以及当前位置之前的token,而最后一个token会是唯一一个能包含所有token上下文的向量,因此,我们只用最后一个token的向量来计算最终的输出分类结果。

probs = torch.softmax(outputs[:, -1, :], dim=-1)
preds = torch.argmax(probs, dim=-1)
probs, preds
(tensor([[0.4616, 0.5384]]), tensor([1]))

可以看到,在未经训练情况下,模型预测的分类为是1(垃圾邮件),但实际上这个示例文本Do you have time?与垃圾邮件并无关系,这正是我们要通过微调模型来改善的地方。

接下来,我们编写一个函数calc_accuracy,用来计算模型在指定数据集上的分类准确率。它通过收集正确预测的数据条数占比来实现。

import torch.nn.functional as flabels = torch.tensor([0])
loss = f.cross_entropy(outputs[:, -1, :], labels)
loss.item()
0.7731215953826904
def calc_accuracy(model, dataloader, device):num_datas = 0num_corrects = 0for inputs, labels in dataloader:inputs, labels = inputs.to(device), labels.to(device)with torch.no_grad():logits = model(inputs)[:, -1, :]probs = torch.softmax(logits, dim=-1)preds = torch.argmax(probs, dim=-1)num_datas += len(labels)num_corrects += (preds == labels).sum().item()return num_corrects / num_datas

计算模型在验证数据集上的分类准确率。

calc_accuracy(model, eval_loader, 'cpu')
0.5637583892617449

计算模型在测试数据集上的分类准确率。

calc_accuracy(model, test_loader, 'cpu')
0.5733333333333334

可以看到,未经过微调的模型,不论是在验证数据集还是在测试数据上准确率都不是很好,这也是我们需要通过微调模型来改善的地方。

4. 微调

4.1 训练代码准备

在微调开始之前,我们需要编写几个函数,以实现单步训练、模型评估和主训练循环。

单步训练和前面预训练阶段的实现基本是一致的,步骤基本固定为以下这几步:

  • 梯度清零
  • 模型推理
  • 损失计算
  • 反向传播
  • 更新参数
import torch.nn.functional as fdef train_step(model, optimizer, X, Y):# 清零梯度optimizer.zero_grad()# 模型调用收集logitslogits = model(X)# print(f"logits.shape: {logits.shape}, target.shape: {Y.shape}")# 计算损失loss = f.cross_entropy(logits[:, -1, :], Y.flatten())# 反向传播loss.backward()# 更新参数optimizer.step()return loss.item()
模型评估与预训练阶段也基本类似,统计模型在整个数据集上的平均损失。
def evaluate(model, dataloader, device):model.eval()num_batches = len(dataloader)total_loss = 0with torch.no_grad():for (X, Y) in dataloader:X, Y = X.to(device), Y.to(device)logits = model(X)loss = f.cross_entropy(logits[:, -1, :], Y.flatten())total_loss += loss.item()model.train()return total_loss/num_batches

主训练方法的实现也与前面大同小异,对所有的数据训练num_epochs指定的轮数,并在每轮训练中跟踪训练损失和验证损失的变化。

def train(model, optimizer, train_loader, eval_loader, device, num_epochs):train_losses, eval_losses = [], []for epoch in range(num_epochs):model.train()for inputs, targets in train_loader:train_loss = train_step(model, optimizer, inputs.to(device), targets.to(device))train_losses.append(train_loss)eval_loss = evaluate(model, eval_loader, device)eval_losses.append(eval_loss)print(f"Epoch: {epoch}, Train Loss: {train_loss:.4f}, Eval Loss: {eval_loss:.4f}")return train_losses, eval_losses
4.2 开始训练
%%time
device = 'cuda:0'
model.to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0001, weight_decay=0.01)
train(model, optimizer, train_loader, eval_loader, device, 20)
    Epoch: 0, Train Loss: 0.3042, Eval Loss: 0.4855Epoch: 1, Train Loss: 0.0237, Eval Loss: 0.2542Epoch: 2, Train Loss: 0.2716, Eval Loss: 0.2975Epoch: 3, Train Loss: 0.0011, Eval Loss: 0.2413Epoch: 4, Train Loss: 0.0008, Eval Loss: 0.2194Epoch: 5, Train Loss: 0.7194, Eval Loss: 0.2193Epoch: 6, Train Loss: 0.0016, Eval Loss: 0.2332Epoch: 7, Train Loss: 0.2460, Eval Loss: 0.2788Epoch: 8, Train Loss: 0.0699, Eval Loss: 0.3474Epoch: 9, Train Loss: 0.0037, Eval Loss: 0.3063Epoch: 10, Train Loss: 0.0004, Eval Loss: 0.2577Epoch: 11, Train Loss: 0.0004, Eval Loss: 0.4328Epoch: 12, Train Loss: 0.0935, Eval Loss: 0.2229Epoch: 13, Train Loss: 0.0000, Eval Loss: 0.2417Epoch: 14, Train Loss: 0.0022, Eval Loss: 0.5061Epoch: 15, Train Loss: 0.0369, Eval Loss: 0.2403Epoch: 16, Train Loss: 0.0000, Eval Loss: 0.6432Epoch: 17, Train Loss: 0.0008, Eval Loss: 0.2384Epoch: 18, Train Loss: 0.0000, Eval Loss: 0.2711Epoch: 19, Train Loss: 0.0000, Eval Loss: 0.3409CPU times: user 1min 7s, sys: 647 ms, total: 1min 8sWall time: 1min 8s

注:从损失数据中可以看到,虽然训练了20轮,但跑到第6轮之后,就有些过拟合了,表现为训练损失很低,但验证损失不降反升的现象,虽然是我们的数据量太少,又重复训练了多轮的缘故。

4.3 准确率评估

下面来统计下模型在不同数据集上的准确率。

train_acc = calc_accuracy(model, train_loader, device)
eval_acc = calc_accuracy(model, eval_loader, device)
test_acc = calc_accuracy(model, test_loader, device)
print(f"train_acc: {train_acc}, eval_acc: {eval_acc}, test_acc: {test_acc}")
train_acc: 0.999163179916318, eval_acc: 0.9328859060402684, test_acc: 0.9133333333333333

结果分析如下:

  • 在训练数据集准确率接近1.0,不太正常,原因在于训练轮数过多,过度拟合了训练数据集。
  • 在验证数据集上0.9328%,较为正常。
  • 在测试数据集上准确率为0.913,这是模型从未见过的数据集,这个数字最为客观。

注:基于前面的损失下降数据,模型性能最好的点理论上应该是在epoch=5(eval_loss=0.2193)的时候,但是这个点的模型状态我们未能保存下来,这里主要为了演示,就没有再次训练,你如果感兴趣,可以修改代码将每个epoch的结果都保存下来,来找到准确率表现最佳的模型状态。

下面我们将模型状态保存下来,以备后面使用。

import os
output_dir = "/data2/minigpt/models/classify/"
os.makedirs(output_dir, exist_ok=True)
torch.save(model.state_dict(), os.path.join(output_dir, "model_classify_v1.pth"))
4.4 模型使用

我们先编写一个predict函数,用于对文本进行垃圾邮件分类预测,并将结果转换为人类可以理解的文本形式spamnot spam

def predict(text, model, tokenizer, device, max_length):token_ids = tokenizer.encode(text)input_ids = torch.tensor(token_ids).unsqueeze(0).to(device)with torch.no_grad():logits = model(input_ids)probs = torch.softmax(logits[:, -1, :], dim=-1)pred = torch.argmax(probs, dim=-1)return "spam" if pred.item() == 1 else "not spam"

我们使用刚微调的垃圾邮件分类模型,对下面两个样例文本进行实际预测。

texts = ["You are a winner you have been specially","selected to receive $1000 cash or a $2000 award.",
]for text in texts:print(text, " --> ", predict(text, model, tokenizer, device, 131))
You are a winner you have been specially  -->  not spam
selected to receive $1000 cash or a $2000 award.  -->  spam

可以看到,对于这两个样例文本,在微调后的模型上分类均是正确的,基本达到了分类训练的目标。

小结:本节我们主要介绍了基于预训练模型进行分类微调的实施过程,先对数据进行预处理并封装小批量数据加载器,再对模型结构进行改造,替换了一个二分类输出头,最后对模型进行微调训练,得到一个在测试数据集上准确率为91%的垃圾邮件分类模型,这个准确率应该还可以继续提高,有兴趣可以按照自己的想法动手修改验证。

相关阅读

  • 预训练模型之分布式训练加速
  • 带你构建MiniGPT

相关文章:

ScratchLLMStepByStep:SFT之分类微调

1. 引言 前面我们花了三节内容来介绍预训练&#xff0c;包括如何从零搭建、如何加速运算、如何分布式加速训练&#xff0c;本节开始我们将进入监督微调&#xff08;SFT&#xff09;阶段。 常见语言模型的微调任务有两类&#xff0c;分类微调和指令微调。 分类微调模型通常是…...

人工智能知识分享第十天-机器学习_聚类算法

聚类算法 1 聚类算法简介 1.1 聚类算法介绍 一种典型的无监督学习算法&#xff0c;主要用于将相似的样本自动归到一个类别中。 目的是将数据集中的对象分成多个簇&#xff08;Cluster&#xff09;&#xff0c;使得同一簇内的对象相似度较高&#xff0c;而不同簇之间的对象相…...

MySQL和Hive中的行转列、列转行

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 MySQL1.行转列2.列转行 Hive1.行转列2.列转行(1)侧窗(2)union MySQL 1.行转列 把多行转成列。直接group&#xff0c;sum(if()) 2.列转行 Hive 1.行转列 select name,sum(if(kmshuxu…...

汽车供应链关键节点:物流采购成本管理全解析

在汽车行业&#xff0c;供应链管理是一项至关重要的任务。汽车制造从零部件的生产到整车的交付&#xff0c;涉及多个环节&#xff0c;其中物流、采购与成本管理是核心节点。本文将深入分析这些关键环节&#xff0c;探讨如何通过供应商管理系统及相关工具优化供应链管理。 一、…...

USB 驱动开发 --- Gadget 设备连接 Windows 免驱

环境信息 测试使用 DuoS(Arm CA53&#xff0c; Linux 5.10) 搭建方案验证环境&#xff0c;使用 USB sniff Wirekshark 抓包分析&#xff0c;合照如下&#xff1a; 注&#xff1a;左侧图中设备&#xff1a;1. 蓝色&#xff0c;USB sniff 非侵入工 USB 抓包工具&#xff1b;2. …...

计算机网络之---数据链路层的功能与作用

数据链路层概念 数据链路层&#xff08;Data Link Layer&#xff09;是计算机网络中的第二层&#xff0c;它位于物理层和网络层之间&#xff0c;主要负责数据在物理链路上的可靠传输。其基本功能是将网络层传来的数据分成帧&#xff0c;并负责在物理链路上可靠地传输这些数据帧…...

前端 图片上鼠标画矩形框,标注文字,任意删除

效果&#xff1a; 页面描述&#xff1a; 对给定的几张图片&#xff0c;每张能用鼠标在图上画框&#xff0c;标注相关文字&#xff0c;框的颜色和文字内容能自定义改变&#xff0c;能删除任意画过的框。 实现思路&#xff1a; 1、对给定的这几张图片&#xff0c;用分页器绑定…...

为什么HTTP请求后面有时带一个sign参数(HTTP请求签名校验)

前言 最近在开发过程中&#xff0c;发现前端有很多的接口发送请求时都会携带signxxxx参数&#xff0c;但是后端明明没有写&#xff0c;也不需要这个参数&#xff0c;后面才知道&#xff0c;这个前面是为了给http请求签名&#xff0c;主要是为了防止请求体和请求参数被拦截篡改…...

第二十八周机器学习笔记:PINN求正反解求PDE文献阅读——反问题、动手深度学习

第二十八周周报 一、文献阅读题目信息摘要Abstract网络架构实验——Data-driven discovery of partial differential equations&#xff08;偏微分方程的数据驱动发现&#xff09;1. Continuous time models&#xff08;连续时间模型&#xff09;例子&#xff1a;(Navier–Stok…...

计算机毕业设计hadoop+spark知网文献论文推荐系统 知识图谱 知网爬虫 知网数据分析 知网大数据 知网可视化 预测系统 大数据毕业设计 机器学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

C#Struct堆栈

Struct若其内部含有堆对象&#xff0c;Struct的该对象放在堆上&#xff1b; Struct当做参数传递时&#xff0c;其堆属性作为引用传递&#xff0c;值属性还是作为值传递&#xff1b; struct TS { public int[] t1; public int t2; } public void TF1(TS t) { int[] t1 t.t1; …...

页面转 PDF 功能的实现思路与使用方法

引言 在 Web 开发中&#xff0c;有时我们需要将页面的特定部分转换为 PDF 格式&#xff0c;以便用户下载和保存。本文将详细介绍如何使用 html2canvas 和 jspdf 这两个强大的库来实现这一功能&#xff0c;并且结合实际代码讲解其实现思路与使用方法。完整源码&#xff08;src/…...

【保姆级教程】基于OpenCV+Python的人脸识别上课签到系统

【保姆级教程】基于OpenCVPython的人脸识别上课签到系统 一、软件安装及环境配置1. 安装IDE&#xff1a;PyCharm2. 搭建Python的环境3. 新建项目、安装插件、库 二、源文件编写1. 采集人脸.py2. 训练模型.py3. 生成表格.py4. 识别签到.py5. 创建图形界面.py 三、相关函数分析1.…...

docker-compose部署下Fastapi中使用sqlalchemy和Alembic

本篇介绍使用Fastapi sqlalchemy alembic 来完成后端服务的数据库管理&#xff0c;并且通过docker-compose来部署后端服务和数据库Mysql。包括&#xff1a; 数据库创建&#xff0c;数据库用户创建数据库服务发现Fastapi 连接数据库Alembic 连接数据库服务健康检查 部署数据…...

Oracle:ORA-00904: “10“: 标识符无效报错详解

1.报错Oracle语句如下 SELECT YK_CKGY.ID,YK_CKGY.DJH,YK_CKGY.BLRQ,YK_CKGY.ZBRQ,YK_CKGY.SHRQ,YK_CKGY.YT,YK_CKGY.ZDR,YK_CKGY.SHR,YK_CKGY.BZ,YK_CKGY.JZRQ,YK_CKGY.ZT,YK_CKGY.CKLX,(case YK_CKGY.CKLXwhen 09 then药房调借when 02 then科室退药when 03 then损耗出库when…...

C语言#define定义宏

目录 一、什么是宏以及宏的声明方式 1.宏常量&#xff1a; 2.宏函数&#xff1a; 二、宏的替换原则 三、宏设计的易犯错误 ERROR1&#xff1a;尾部加分号&#xff08;当然有些特定需要加了分号&#xff0c;这里说明一般情况&#xff09; ERROR2&#xff1a;宏函数定义时&…...

SpringBoot操作spark处理hdfs文件

SpringBoot操作spark处理hdfs文件 1、导入依赖 <!-- spark依赖--><dependency><groupId>org.apache.spark</groupId><artifactId>spark-core_2.12</artifactId><version>3.2.2</version></dependency><depend…...

消息队列架构、选型、专有名词解释

私人博客传送门 消息队列专有名词解释 | 魔筝炼药师 MQ选型 | 魔筝炼药师 MQ架构 | 魔筝炼药师 MQ顺序消息 | 魔筝炼药师...

用OpenCV实现UVC视频分屏

分屏 OpencvUVC代码验证后话 用OpenCV实现UVC摄像头的视频分屏。 Opencv opencv里有很多视频图像的处理功能。 UVC Usb 视频类&#xff0c;免驱动的。视频流格式有MJPG和YUY2。MJPG是RGB三色通道的。要对三通道进行分屏显示。 代码 import cv2 import numpy as np video …...

Allure 集成 pytest

Allure 是一个强大的测试报告工具&#xff0c;与 pytest 集成可以生成详细的测试报告&#xff0c;包括测试步骤、测试数据、截图、错误堆栈等。 1. 安装 Allure 和相关依赖 安装 pytest-allure-adaptor 插件&#xff1a; pip install allure-pytest确保本地已安装 Allure 工具。…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

深入浅出Diffusion模型:从原理到实践的全方位教程

I. 引言&#xff1a;生成式AI的黎明 – Diffusion模型是什么&#xff1f; 近年来&#xff0c;生成式人工智能&#xff08;Generative AI&#xff09;领域取得了爆炸性的进展&#xff0c;模型能够根据简单的文本提示创作出逼真的图像、连贯的文本&#xff0c;乃至更多令人惊叹的…...

鸿蒙HarmonyOS 5军旗小游戏实现指南

1. 项目概述 本军旗小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;采用DevEco Studio实现&#xff0c;包含完整的游戏逻辑和UI界面。 2. 项目结构 /src/main/java/com/example/militarychess/├── MainAbilitySlice.java // 主界面├── GameView.java // 游戏核…...

C++--string的模拟实现

一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现&#xff0c;其目的是加强对string的底层了解&#xff0c;以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量&#xff0c;…...

StarRocks 全面向量化执行引擎深度解析

StarRocks 全面向量化执行引擎深度解析 StarRocks 的向量化执行引擎是其高性能的核心设计&#xff0c;相比传统行式处理引擎&#xff08;如MySQL&#xff09;&#xff0c;性能可提升 5-10倍。以下是分层拆解&#xff1a; 1. 向量化 vs 传统行式处理 维度行式处理向量化处理数…...