大模型参数高效微调PEFT的理解和应用

news2025/2/28 15:26:16

简介

近年的大型语言模型(也被称作基础模型),大多是采用大量资料数据和庞大模型参数训练的结果,比如常见的ChatGPT3有175B的模型参数量。随着Large Language Model(LLM)的横空出世,网络模型对常见问题的解答有了很强的泛化能力。但是如果将LLM应用到特定专业场景,如律师、医生,却仍表现的不尽如人意。即使可以使用few-shot learning或finetuning的技术进行迭代更新,但是模型参数的更新需要昂贵的机器费用。因此近年来,学术界大量研究人员开始从事高效Finetuning的工作,称作Effective Parameter Fine-Tuning(PEFT)。本次从方法构造的区别,可以将现有的PEFT方法分为Adapter、LoRA、Prefix Learning和Soft Prompt。学习过程很大程度上借鉴了李宏毅老师分享的2022 AACL-IJCNLP课件,有兴趣的读者可以翻阅原文链接。

方法

虽然LLM有很好的泛化能力,但如果需要应用到特定的场景任务,常常需要对LLM进行模型微调。问题是LLM的模型参数非常庞大,特定任务的微调需要昂贵的显卡资源,那么如何解决这样的问题呢?很显然,降低微调的模型参数量就是最简单的方法。试验表明,当每个特定任务微调时,只训练模型的一小部分参数,也能得到不错的效果。

让我们回到模型微调的真实含义,如图所示,h表示每一层隐藏层的输出。

模型微调就是通过数据更新隐藏层的输出结果,更好的拟合输入数据的分布,对下游任务有更佳的表现,我们将隐藏层输出成为hidden representation(h)。
模型微调的结果就是更新hidden representation,用数学语言可以表示为:
h’ = h + 𝚫h
接下来介绍4种不同的方法,通过减少模型参数更新的数量,高效的更新𝚫h。

Adapter方法

Adapter方法通常是在网络模型中增加小型的模型块,通过冻结LLM的参数,仅更新Adapter模块的方式进行模型微调。

如图所示,Adapter应用在Transformer的结构中,在Multi-headed attention和Feed-forward网路层后紧接Adapter子模块,模型训练的时候冻结Transformer的参数,仅更新Adapter的参数。

LoRA方法

LoRA提出的想法是,既然LLM可以泛化用于不同的NLP任务,那么说明不同的任务有不同的神经元来处理,我们只要针对下游任务找到合适的那一批神经元,并对他们的权重进行强化,那么对下游任务也有显著的效果。

LoRA方法假设下游任务只需要低秩矩阵就可以找到大模型中对应的权重,然后仅更新小部分的模型参数,就可以在下游任务中表现不错。

如图所示,LoRA将𝚫h的计算方式更改为两个低秩矩阵的乘法,r表示矩阵秩的大小。那么模型的更新过程可以用数学方式表示为:
W= W + 𝚫W = W + BA, r << min(d_ffw, d_model)

Prefix Learning方法

Prefix Learning方法就是在网络层中,将网络层中扩展可训练的前缀。

这里以Self-Attention为例,先回忆一下Self-Attention的结构。
Self-Attention的数学表示为:
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k V ) Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}} V) Attention(Q,K,V)=softmax(dk QKTV)
首先初始化W_k, W_q, W_v参数,通过 q 1 = x 1 ∗ W q , k 1 = x 1 ∗ W k , v 1 = x 1 ∗ W v q_1=x_1*W_q, k_1=x_1* W_k, v_1=x_1 * W_v q1=x1Wq,k1=x1Wk,v1=x1Wv的方式得到QKV矩阵;
通过 α 1 , 1 = q 1 ∗ k 1 , α 1 , 2 = q 1 ∗ k 2 … \alpha1,1 = q_1 * k_1, \alpha1,2=q_1 * k_2… α1,1=q1k1,α1,2=q1k2 的方式获取𝛼矩阵;
通过 z 1 , 1 = s o f t m a x ( α 1 , 1 ∗ v 1 ) … z1,1=softmax(\alpha1,1 * v1)… z1,1=softmax(α1,1v1)的方式得到x1对其他token的注意力
最后累加计算得出 x 1 ′ x'_1 x1的结果,如此循环计算下一个时刻输出。


PreFix Tuning的做法是对self-attention增加一部分参数,计算𝚫h的结果。
如图所示,增加了3个参数量,模型训练的时候只更新这3个用到的的参数。

Soft Prompt方法

Soft Prompt的做法比较简单,直接在Embedding输出,插入一部分Prefix embedding信息。

Soft Prompt的简化版本是直接在input sequence句首插入文本。

小结

了解4种PEFT的方法后,可以发现PEFT有非常多的好处。
首先,PEFT可以极大的降低finetune的参数量。

如图所示,Adapter训练参数之占模型的5%,LoRa、Prefix Tuning和Soft Prompt的训练参数甚至小于0.1%。
其次,由于训练参数的减少,PEFT更不容易造成模型的过拟合,某种意义也是一种Dropout方法。
最后,由于需要更新的参数少,基础模型在小数据集上有不错的表现。

实践

我们以最受欢迎的LoRA为例,搭建一个简易的demo理解如何使用LoRA微调模型。
Demo的有2个不同分布的数据,分别是均匀分布和高斯分布;然后构造3层的ReLU-MLP对均匀分布数据进行训练;最后通过LoRA的对高斯分布数据进行微调。

首先是构造数据,分别生成均匀分布和高斯分布的数据集,lable范围都是{-1,1}。

import numpy as np
import torch
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset, DataLoader
import random
from collections import OrderedDict
import math
import torch.nn.functional as F
random.seed(42)

def generate_uniform_sphere(n, d):
    normal_data = np.random.normal(loc=0.0, scale=1.0, size=[n, d])
    lambdas = np.sqrt((normal_data * normal_data).sum(axis=1))
    data = np.sqrt(d) * normal_data / lambdas[:, np.newaxis]
    return data

# data type could be "uniform" or "gaussian"
def data_generator(data_type='uniform', inp_dims=100, sample_size=10000):
    if data_type == 'uniform':
        data = generate_uniform_sphere(sample_size, inp_dims)
    elif data_type == 'gaussian':
        var = 1.0 / inp_dims
        data = np.random.normal(loc=0.0, scale=var, size=[sample_size, inp_dims])

    labels = np.sign(np.mean(data, -1))
    for i in range(sample_size):
        if labels[i] == 0:
            labels[i] = 1
    return data, labels

第二步,构造3层的MLP网络模型,ReLU作为激活函数。

class ReluNN(torch.nn.Module):
    def __init__(self, inp_dims, h_dims, out_dims=1):
        super(ReluNN, self).__init__()
        self.inp_dims = inp_dims
        self.h_dims = h_dims
        self.out_dims = out_dims
        
        # build model
        self.layer1 = torch.nn.Linear(self.inp_dims, self.h_dims)
        self.layer2 = torch.nn.Linear(self.h_dims, self.h_dims)
        self.last_layer = torch.nn.Linear(self.h_dims, self.out_dims, bias=False)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = torch.flatten(x, start_dim=1)
        x = self.layer1(x)
        x = torch.nn.ReLU()(x)
        x = self.layer2(x)
        x = torch.nn.ReLU()(x)
        x = self.last_layer(x)
        return x

def calc_loss(logits, labels):
    return torch.mean(torch.log(1 + torch.exp(-1 * torch.mul(labels.float(), logits.T))))

def evaluate(labels, loss=None, output=None, iter=0, training_time=True):
    correct_label_count = 0
    for i in range(len(output)):
        if (output[i] * labels[i] > 0).item():
            correct_label_count += 1
    if training_time:
        print('Iteration: ', iter, ' Loss: ', loss, ' Correct label: ', correct_label_count, '/', len(output))
    else:
        print('Correct label:  ', correct_label_count, '/', len(output), ', Accuracy: ', correct_label_count / len(output))

第三步,开始训练均匀分布数据。

# input dimensions
inp_dims = 16
# hidden dimensions in our model
h_dims = 32
# training sample size
n_train = 2000
# starting learning rate (I didn't end up using any learning rate scheduler. So this will be constant. )
starting_lr = 1e-4
batch_size = 256

# generate data and build pytorch data pipline using TensorDataset module
data_train, labels_train = data_generator(data_type="uniform", inp_dims=inp_dims, sample_size=n_train)
dataset = TensorDataset(torch.Tensor(data_train), torch.Tensor(labels_train))
loader = DataLoader(dataset, batch_size=batch_size, drop_last=True)

# Build the model and define optimiser
model = ReluNN(inp_dims, h_dims)
optimizer = torch.optim.Adam(model.parameters(), lr=starting_lr)

# ---------------------
# Training loop here
# ---------------------

its = 0
epochs = 400
print_freq = 200

for epoch in range(epochs):
    for batch_data, batch_labels in loader:
        optimizer.zero_grad()
        output = model(batch_data)
        loss = calc_loss(output, batch_labels)
        loss.backward()
        optimizer.step()
        
        if its % print_freq == 0:
            correct_labels = evaluate(labels=batch_labels, loss=loss.detach().numpy(), 
                                      output=output, iter=its, training_time=True)

        its += 1

获取训练输出结果:

Iteration:  0  Loss:  0.6975772  Correct label:  126 / 256
Iteration:  200  Loss:  0.6508794  Correct label:  164 / 256
Iteration:  400  Loss:  0.52307904  Correct label:  215 / 256
Iteration:  600  Loss:  0.34722215  Correct label:  240 / 256
Iteration:  800  Loss:  0.21760023  Correct label:  251 / 256
Iteration:  1000  Loss:  0.19394015  Correct label:  241 / 256
Iteration:  1200  Loss:  0.124890685  Correct label:  250 / 256
Iteration:  1400  Loss:  0.10578571  Correct label:  250 / 256
Iteration:  1600  Loss:  0.07651  Correct label:  252 / 256
Iteration:  1800  Loss:  0.05156578  Correct label:  256 / 256
Iteration:  2000  Loss:  0.045886587  Correct label:  256 / 256
Iteration:  2200  Loss:  0.04692286  Correct label:  256 / 256
Iteration:  2400  Loss:  0.06285152  Correct label:  254 / 256
Iteration:  2600  Loss:  0.03973126  Correct label:  254 / 256

在均匀分布和高斯分布测试集分别进行测试:

print('-------------------------------------------------')
print('Test model performance on uniformly-distributed data (the data we trained our model on)')
data_test, labels_test = data_generator(data_type="uniform", inp_dims=inp_dims, sample_size=1024)
data_test = torch.Tensor(data_test)
labels_test = torch.Tensor(labels_test)

output = model(data_test)
correct_labels = evaluate(labels=labels_test, loss=0.0, output=output, iter=0, training_time=False)

print('-------------------------------------------------')
print('Test model performance on normally-distributed data')
data_test, labels_test = data_generator(data_type="gaussian", inp_dims=inp_dims, sample_size=1024)
data_test = torch.Tensor(data_test)
labels_test = torch.Tensor(labels_test)

output = model(data_test)
correct_labels = evaluate(labels=labels_test, loss=0.0, output=output, iter=0, training_time=False)
print('-------------------------------------------------')

获得输出结果为,均匀分布表现远高于高斯分布,这是理所当然的。

-------------------------------------------------
Test model performance on uniformly-distributed data (the data we trained our model on)
Correct label:   1007 / 1024 , Accuracy:  0.9833984375
-------------------------------------------------
Test model performance on normally-distributed data
Correct label:   832 / 1024 , Accuracy:  0.8125
-------------------------------------------------

第四步,实现LoRA改造网络模型,LoRA代码实现来自https://github.com/microsoft/LoRA/tree/main。

class LoRALinear(torch.nn.Linear):
    def __init__(
        self, 
        layer: torch.nn.Linear, 
        r: int, 
        lora_alpha: float, 
        in_features: int, 
        out_features: int,
        **kwargs, 
    ):
        torch.nn.Linear.__init__(self, in_features, out_features, **kwargs)

        # trainable parameters
        self.weight = layer.weight
        
        self.r = r
        if self.r > 0:
            # lora_A matrix has shape [number of ranks, number of input features]
            self.lora_A = torch.nn.Parameter(self.weight.new_zeros((in_features, r)))
            
            # lora_A matrix has shape [number of output features, number of ranks]
            self.lora_B = torch.nn.Parameter(self.weight.new_zeros((r, out_features)))
            
            self.scaling = lora_alpha / r
            
            # Freezing the pre-trained weight matrix
            self.weight.requires_grad = False
            
        self.reset_parameters()
        
    def reset_parameters(self):
        if hasattr(self, 'lora_A'):
            torch.nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
            torch.nn.init.zeros_(self.lora_B)

    def forward(self, x: torch.Tensor):
        if self.r > 0:
            result = F.linear(x, self.weight, bias=self.bias)            
            result += (x @ self.lora_A @ self.lora_B) * self.scaling
            return result
        else:
            return F.linear(x, self.weight, bias=self.bias)

改造第二层网络层:

ranks = 2
l_alpha = 1.0

# wrap the second layer of the model
lora_layer2 = LoRALinear(model.layer2, r=ranks, lora_alpha=l_alpha, in_features=h_dims, out_features=h_dims)

开始进行微调训练:

# new pipline that contains data generated from Gaussian distribution
ft_data_train, ft_labels_train = data_generator(data_type="gaussian", inp_dims=inp_dims, sample_size=n_train)
ft_data_test, ft_labels_test = data_generator(data_type="gaussian", inp_dims=inp_dims, sample_size=n_train)
dataset = TensorDataset(torch.Tensor(ft_data_train), torch.Tensor(ft_labels_train))
ft_loader = DataLoader(dataset, batch_size=batch_size, drop_last=False)

# Adam now takes the 160 trainable LoRA parameters 
starting_lr = 1e-3
optimizer = torch.optim.Adam(lora_layer2.parameters(), lr=starting_lr)

# ---------------------
# Training starts again
# ---------------------

its = 0
epochs = 400
print_freq = 200

for epoch in range(epochs):
    for batch_data, batch_labels in ft_loader:
        optimizer.zero_grad()
        
        x = model.layer1(batch_data)
        x = torch.nn.ReLU()(x)
        # ---this is the new layer---
        x = lora_layer2(x)
        # ---------------------------
        x = torch.nn.ReLU()(x)
        output = model.last_layer(x)
        
        loss = calc_loss(output, batch_labels)
        loss.backward()
        optimizer.step()
        
        if its % print_freq == 0:
            correct_labels = evaluate(labels=batch_labels, loss=loss.detach().numpy(), 
                                      output=output, iter=its, training_time=True)

        its += 1

微调输出结果为:

Iteration:  0  Loss:  0.41668275  Correct label:  194 / 256
Iteration:  200  Loss:  0.34483075  Correct label:  249 / 256
Iteration:  400  Loss:  0.28130627  Correct label:  247 / 256
Iteration:  600  Loss:  0.18952605  Correct label:  249 / 256
Iteration:  800  Loss:  0.14345655  Correct label:  249 / 256
Iteration:  1000  Loss:  0.117519796  Correct label:  250 / 256
Iteration:  1200  Loss:  0.100797206  Correct label:  251 / 256
Iteration:  1400  Loss:  0.08887711  Correct label:  252 / 256
Iteration:  1600  Loss:  0.07975915  Correct label:  253 / 256
Iteration:  1800  Loss:  0.07250729  Correct label:  253 / 256
Iteration:  2000  Loss:  0.06658038  Correct label:  253 / 256
Iteration:  2200  Loss:  0.061622016  Correct label:  254 / 256
Iteration:  2400  Loss:  0.057407677  Correct label:  254 / 256
Iteration:  2600  Loss:  0.05379172  Correct label:  254 / 256
Iteration:  2800  Loss:  0.050651148  Correct label:  254 / 256
Iteration:  3000  Loss:  0.04789203  Correct label:  255 / 256

最后一步,再次测试高斯分布的数据集。

data_test, labels_test = data_generator(data_type="gaussian", inp_dims=inp_dims, sample_size=1024)
data_test = torch.Tensor(data_test)
labels_test = torch.Tensor(labels_test)

x = model.layer1(data_test)
x = torch.nn.ReLU()(x)
# ---this is the new layer---
x = lora_layer2(x)
# ---------------------------
x = torch.nn.ReLU()(x)
output = model.last_layer(x)
correct_labels = evaluate(labels=labels_test, loss=0.0, output=output, iter=0, training_time=False)

输出结果为:

Correct label:   1011 / 1024 , Accuracy:  0.9873046875

微调效果很明显,准确率从81.2%提升到98.7%。最后再计算LoRA微调的参数量大小。

def count_parameters(model):
    return sum(p.numel() for p in model.parameters() if p.requires_grad)
    
print("Original second layer parameters: ", count_parameters(original_layer2))
print("LoRA layer parameters: ", count_parameters(lora_layer2))

输出结果为:

Original second layer parameters:  1056
LoRA layer parameters:  160

通过实验结果发现,原始模型参数量1056,LoRA参数量160,仅为原来的1/10,但是训练效果从81.2%提升到98.7%。

总结

如果对网络层每一层都要重新实现LoRA的方法,是比较复杂的,推荐使用HuggingFace的封装库peft,覆盖基本的网络模型。

  • 共用基础LLM是未来的趋势,如果需要快速适应特殊的任务,只需要训练LoRA的参数即可,大大降低了GPU的使用量;
  • 当不同任务的切换时,只需要切换不同的LoRA参数;

参考

  • Houlsby, Neil, et al. “Parameter-efficient transfer learning for NLP.” International Conference on Machine Learning. PMLR, 2019.
  • Hu, Edward J., et al. “LoRA: Low-Rank Adaptation of Large Language Models.” International Conference on Learning Representations. 2021.
  • The Power of Scale for Parameter-Efficient Prompt Tuning. Proceedings of the 2021 Conference on Empirical Methods in Natural Language Processing. 2021

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

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

相关文章

分享大数据分析培训就业班课程内容

随着国家重视大数据&#xff0c;政府扶持大数据&#xff0c;大数据在企业中生根发芽根据人社部发布的《新职业--大数据工程技术人员就业景气现状分析报告》预计&#xff0c;未来5年&#xff0c;大数据行业的人才需求将保持30%-40%的增速&#xff0c;人才缺口总量将达到2000万人…

冠达管理股票分析:权益分派前买入好不好?

随着股票市场的不断升温&#xff0c;出资者们也越来越关注买入时机的把握。而其中一个重要的要素就是权益分配。那么&#xff0c;权益分配前买入好不好呢&#xff1f;本文将从多个视点进行剖析。 1、剖析权益分配对股价的影响&#xff0c;推断买入时机的合理性。权益分配是上市…

地图结构 | 图解占据栅格地图原理(附Matlab建图实验)

目录 0 专栏介绍1 栅格地图1.1 应用场景1.2 基本概念 2 占据栅格地图2.1 更新模型2.2 截断策略 3 仿真实现3.1 算法流程3.2 Matlab实现 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细介绍全局规划(图搜索…

蚂蚁开源编程大模型,提高开发效率

据悉&#xff0c;日前蚂蚁集团首次开源了代码大模型 CodeFuse&#xff0c;而这是蚂蚁自研的代码生成专属大模型&#xff0c;可以根据开发者的输入提供智能建议和实时支持&#xff0c;帮助开发者自动生成代码、自动增加注释、自动生成测试用例、修复和优化代码等kslouitusrtdf。…

小红书母婴行业文案怎么写,创作方向有哪些?

行业文案顾名思义就是有关于某个行业的文案&#xff0c;不过此类内容一般都具有商业性。随着互联网的深入&#xff0c;怎么对行业文案进行创作&#xff0c;也成为品牌的日常工作。今天为大家分享下小红书母婴行业文案怎么写&#xff0c;创作方向有哪些&#xff1f; 首先&#x…

arppy gis 读取text 并批量添加字段 arcpy.AddField_management

arppy gis 读取text 并批量添加字段 arcpy.AddField_management 例&#xff1a;给“省级行政区域”添加“A、B、C、D” 4个字段。 &#xff08;1&#xff09;用Excel制作出字段及其描述表&#xff0c;定义字段结构&#xff1b; &#xff08;2&#xff09;复制除标题行以为的内…

windows本地验证码识别小工具

windows本地验证码识别小工具 - 可以用在windows系统中&#xff0c;并可以集成在Java或python程序中 演示视频如下&#xff1a;可用于识别4-7位的字母数字组合的验证码&#xff08;识别准确率在70% - 80%&#xff09;。 验证码识别演示 本项目未开源&#xff0c;如需使用请联…

DELL 台式机的内置扬声器如何关闭

DELL 台式机的内置扬声器如何关闭&#xff1f; 点“开始”——控制面板——高清晰音频管理器——右上角“设备高级设置”——“播放设备”——点击“使前部和后部设备播放不同的音频流”前面的小方框——“确认”。ok了。

14-JVM调优实战-常量池详解

上一篇&#xff1a;13-JVM调优实战-3 最后&#xff0c;来一个算是补充拓展知识点&#xff0c;希望可以帮到你。 1.Class常量池与运行时常量池 Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外&#xff0c;还有…

win7安装不了python3.10.8

今天安装python3.10.8&#xff0c;发现安装失败。原来是win7不支持python3.10.8。 如图&#xff1a; 安装出现问题&#xff1a; win7能安装python3哪个版本&#xff0c;我正在测试。到时补充编辑。

Web Components详解-Shadow DOM插槽

前言 插槽实际上也属于组件通信的一种方式&#xff0c;但是由于其强大的api和实用性&#xff0c;我将其单独拆开来介绍。 定义 Slot&#xff08;插槽&#xff09;是Web Components中一个重要的特性&#xff0c;它允许在组件内部定义占位符&#xff0c;以便父组件可以向其中插…

好用的工具推荐

1 chatgpt国内版 ChatMindAI ChatMindAI-智慧学习&#xff0c;办公无忧&#xff0c;AIGC让生活更简单https://x.chatmindai.net/explore

sovits远程访问设置

感谢阅读 把blocks.py的如下部分&#xff0c;false改为true

无涯教程-JavaScript - DOLLARDE函数

描述 DOLLARDE函数将以整数部分和小数部分(如1.02)表示的美元价格转换为以十进制数表示的美元价格。分数美元数字有时用于证券价格。值的小数部分除以您指定的整数。 语法 DOLLARDE (fractional_dollar, fraction)争论 Argument描述Required/OptionalFractional_dollarA nu…

SpringMVC的文件上传文件下载多文件上传---详细介绍

目录 前言&#xff1a; 一&#xff0c;文件上传 1.1 添加依赖 1.2 配置文件上传解析器 1.3 表单设置 1.4 文件上传的实现 二&#xff0c;文件下载 controller层 前端jsp 三&#xff0c;多文件上传 Controller层 运行 前言&#xff1a; Spring MVC 是一个基于 Java …

使用迭代方式解决汉诺塔问题(Java语言)

目录 汉诺塔问题解决 迭代介绍 汉诺塔问题解决 在这个Java示例中&#xff0c;我们使用了一个Stack数据结构来模拟递归调用的过程。hanoiIterative函数接受盘子数量n以及三个柱子的名称作为参数&#xff0c;并在迭代过程中模拟汉诺塔的移动操作。moveDisk函数用于模拟盘子的移…

零钱兑换00

题目链接 零钱兑换 题目描述 注意点 如果没有任何一种硬币组合能组成总金额&#xff0c;返回 -1可以认为每种硬币的数量是无限的 解答思路 动态规划从总金额1开始推出目标金额所需的最少硬币个数&#xff0c;任意某个金额所需的最少硬币个数可以由当前金额减去每种面额的硬…

paddleocr python代码封装成http

方便自己,方便他人,直接上代码 from flask import Flask, request from paddleocr import PaddleOCR, draw_ocr import json import pandas as pd import sys import datetime import numpy as np import numpy_financial as npf from dateutil.relativedelta import relative…

python 随机生成emoji表情

问答板块觉得比较有意思的问题 当时搜了些网上的发现基本都不能用&#xff0c;不知道是版本的问题还是咋的就开始自己研究 python随机生成emoji 问题的产生解决官网文档数据类型实现思路实现前提&#xff1a;具体实现&#xff1a; 其他常见用法插入 Emoji 表情&#xff1a;解析…

数电课程设计

为了帮助大家更好学习FPGA硬件语言&#xff0c;创立此资源 包含文件有&#xff1a;实验报告、仿真文件&#xff0c;资料很全&#xff0c;有问题可以私信 课设一&#xff1a;加减计数器 一、实验内容 1、利用QuartusII和Modelsim实现100进制可逆计数器编码显示实验。 二、实…