手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)
手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)
目录
- 手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)
- Stable Diffusion 原理图
- Stable Diffusion的原理解释
- Stable Diffusion 和Diffusion 的Unet对比
- Lora 微调原理
- Stable Diffusion 添加lora微调代码
- Part1 添加lora.py文件 - 用于设置lora层以及替换
- 1. 引入相关库函数
- 2. 定义LoraLayer的类
- 3. lora层的替换
- Part2 添加lora_finetune.py,用于参数微调训练得到lora参数.pt文件
- 1. 引入相关库函数
- 2. 替换模型中的注意力机制里面的Wq, Wk, Wv,替换线性层
- Part3 修改 denoise.py,修改测试的时候的lora参数加入
- 1. 引入相关库函数
- 2. 定义去噪的函数
- 3. 测试-去噪
- 参考
Stable Diffusion 原理图
Stable Diffusion的原理解释
Stable Diffusion的网络结构图如下图所示:
- 改动1:利用 AE,VAE,VQVAE 等自编码器,进行了图像特征提取,利用正确提取特征后的图像作为自己原本在Diffusion中的图像。
- 改动2:在训练过程中,额外添加了一些引导信息,促使图像生成,往我们所希望的方向去走,这里添加信息的方式主要是利用交叉注意力机制(这里我看图应该是只用交叉注意力就行,但是我看视频博主用的代码以及参照的Stable-Diffusion Unet图上都是利用的Transoformer的编码器,也就是得到注意力值之后还得进行一个feedforward层)。
- **改动3:**利用 AE,VAE,VQVAE 等自编码器进行解码。(这个实质上和第一点是重复的)
- **注意:**本次的代码改动先只改动第二个,也就是添加引导信息,对于编码器用于减少计算量,本次改进先不参与(555~,因为视频博主没教),后续可能会进行添加(因为也比较简单)。
Stable Diffusion 和Diffusion 的Unet对比
- 我们可以发现,两者之间的区别主要在于,在卷积完了之后添加了一个Transformer的模块,也就是其编码器将两个信息进行了融合,其他并没有改变。
- 所以主要区别在卷积后的那一部分,如下图。
- 这个ResnetBlock就是之前的卷积模块,作为右边的残差部分,所以这里写成 了ResnetBlock。
- 因此,如果我们将Tranformer模块融入到Restnet模块里面,并且保持其输入卷积的图像和transformer输出的图像形状一致的话,那么就其他部分完全不需要改变了,只不过里面多添加了一些引导信息(MNIST数据集中是label,但是也可以添加文本等等引导信息) 而已。
Lora 微调原理
- LoRA算法
- 算法过程:对于原先的参数不改变,通过右边添加一个参数矩阵来进行微调,也就是利用新的参数矩阵来微调拟合新领域的参数和初始参数的差距。也就是ΔW。
理论:预训练大型语言模型在适应新任务时具有较低的“内在维度” , 所以当对于一个预训练模型来说,原先的参数是有非常多的冗余的,因此我们可以利用低维空间(也就是降维)去表示目标参数和原先参数之间的距离。因此ΔW是相对W来说维度非常小的,减少了非常多的参数量。
-
因为要保证输入和输出的维度和原本的参数W一样,所以一般参数输入的维度还是相同的,但是中间的维度小很多,从而达到减少参数量的结果。比如原本是100x100的参数量,现在变为100x5(r)x2,减少了10倍。
- 其中r就是低秩的那个秩数。可以自定义。
o u t p u t = n e t ( x ) + t o r c h . m a t m u l ( x , t o r c h . m a t m u l ( l o r a a , l o r a b ) ∗ a l p h a ( 可能这里也会除以 r ) output=net(x)+torch.matmul(x,torch.matmul(lora_a,lora_b)*alpha(可能这里也会除以r) output=net(x)+torch.matmul(x,torch.matmul(loraa,lorab)∗alpha(可能这里也会除以r)
alpha
或者alpha/r
是一个缩放因子,用于调整组合结果(原始模型输出加上低秩自适应)的大小。这平衡了预训练模型的知识和新的特定于任务的适应——默认情况下,alpha
通常设置为 1。另请注意,虽然W A被初始化为小的随机权重,但WB被初始化为 0,因此训练开始时ΔW = WAxWB = 0 ,这意味着我们以原始权重开始训练。
Stable Diffusion 添加lora微调代码
Part1 添加lora.py文件 - 用于设置lora层以及替换
1. 引入相关库函数
# 该模块主要是实现lora类,实现lora层的alpha和beta通路,把输入的x经过两条通路后的结果,进行联合输出。
# 然后添加一个函数,主要是为了实现将原本的线性层换曾lora层。'''
# Part1 引入相关的库函数
'''
import torch
from torch import nn
from config import *
2. 定义LoraLayer的类
'''
# Part2 设计一个类,实现lora_layer
'''class LoraLayer(nn.Module):def __init__(self, target_linear_layer, feature_in, feature_out, r, alpha):super().__init__()# 第一步,初始化lora的一些参数,包含a矩阵,b矩阵,r秩.比例系数等等。self.lora_a = nn.Parameter(torch.empty(feature_in, r), requires_grad=True)self.lora_b = nn.Parameter(torch.zeros(r, feature_out), requires_grad=True)self.alpha = alphaself.r = r# 第二步对alpha进行初始化nn.init.kaiming_uniform_(self.lora_a)# 第三步,初始化原本的目标线性层self.net = target_linear_layerdef forward(self, x):output1 = self.net(x)output2 = torch.matmul(x, torch.matmul(self.lora_a, self.lora_b)) * (self.alpha / self.r) # 得到结果后,乘上比例系数(alpha/r)return output2 + output1
3. lora层的替换
'''
# Part3 定义一个函数,实现lora层的替换
'''def inject_lora(module, name, target_linear_layer): # 输入完整的模型,目标线性层的位置,目标线性层name_list = name.split('.') # 按照.进行拆分路径# 获取到目标线性层的模型的上一层所有参数和模型{模型name1:模型,模型name2:模型}for i, item in enumerate(name_list[:-1]):module = getattr(module, item)# 初始化需要替换进入的lora层lora_layer = LoraLayer(target_linear_layer,feature_in=target_linear_layer.in_features, feature_out=target_linear_layer.out_features,r=LORA_R, alpha=LORA_ALPHA)# 替换对应的层setattr(module, name_list[-1], lora_layer)
Part2 添加lora_finetune.py,用于参数微调训练得到lora参数.pt文件
1. 引入相关库函数
# 该模块主要实现对于模型的一些模块进行微调训练,只对lora里面的新增参数进行训练。
'''
# Part 1 引入相关的库函数
'''
import osimport torch
from torch import nn
from dataset import minist_train
from torch.utils import data
from diffusion import forward_diffusion
from config import *
from unet import Unet
from lora import inject_lora
2. 替换模型中的注意力机制里面的Wq, Wk, Wv,替换线性层
if __name__ =='__main__':'''# Part2 对需要训练的模型参数进行设置,将需要替换的线性层进行lora替换,并且只对lora进行训练'''# 首先第一步得先下载网络net = torch.load('unet_epoch0.pt')# 开始对所需的部分进行替换。# 首先,我们要对线性层进行lora替换,所以需要,输入inject_lora的参数包含(整个模型,路径,layer)for name, layer in net.named_modules():name_list = name.split('.')target = ['Wq', 'Wk', 'Wv']for i in target:if i in name_list and isinstance(layer, nn.Linear):# 替换inject_lora(net, name, layer)# 替换完之后,先看看需不需要添加之前的参数try:# 先下载参数lora_para=torch.load('lora_para_epoch0.pt')# 再填充到模型里面net.load_state_dict(lora_para,strict=False)except:pass# 替换完之后,需要对所有的参数进行设置,不是lora的参数梯度设置为Falsefor name, para in net.named_parameters():name_list = name.split('.')lora_para_list = ['lora_a', 'lora_b']if name_list[-1] in lora_para_list:para.requires_grad = Falseelse:para.requires_grad = True'''# Part3 进行训练'''epoch = 5batch_size = 50minist_loader = data.DataLoader(dataset=minist_train, batch_size=batch_size, shuffle=True)# 初始化模型loss = nn.L1Loss()opt = torch.optim.Adam(net.parameters(), lr=1e-3)n_iter = 0net.train()for i in range(epoch):for imgs, labels in minist_loader:imgs = imgs * 2 - 1# 先随机初始化batch_tbatch_t = torch.randint(0, T, size=(imgs.size()[0],))# 首先对清晰图像进行加噪,得到batch_x_tbatch_x_t, batch_noise = forward_diffusion(imgs, batch_t)# 预测对应的噪声batch_noise_pre = net(batch_x_t, batch_t, labels)# 计算损失l = loss(batch_noise, batch_noise_pre)# 清除梯度opt.zero_grad()# 损失反向传播l.backward()# 更新参数opt.step()# 累加损失last_loss = l.item()# 更新迭代次数n_iter += 1print('当前的iter为{},当前损失为{}'.format(n_iter, last_loss))print('当前的epoch为{},当前的损失为{}'.format(i, last_loss))# 保存训练好的lora参数,但是得先找到lora_dic = {}# 遍历net的参数for name, para in net.named_parameters():name_list = name.split('.')need_find = ['lora_a', 'lora_b']# 如果最后一个名字在需要找的参数里面if name_list[-1] in need_find:# 在存储的字典里面添加参数和名字lora_dic[name] = para# 先存储为临时文件torch.save(lora_dic, 'lora_para_epoch{}.pt.tmp'.format(i))# 然后改变路径,形成最终的参数(主要是为了防止写入出错)os.replace('lora_para_epoch{}.pt.tmp'.format(i), 'lora_para_epoch{}.pt'.format(i))
Part3 修改 denoise.py,修改测试的时候的lora参数加入
1. 引入相关库函数
# 该模块主要实现的是对图像进行去噪的测试。
'''
# 首先第一步,引入相关的库函数
'''import torch
from torch import nn
from config import *
from diffusion import alpha_t, alpha_bar
from dataset import *
import matplotlib.pyplot as plt
from diffusion import forward_diffusion
from lora import inject_lora
from lora import LoraLayer
2. 定义去噪的函数
'''
# 第二步定义一个去噪的函数
'''def backward_denoise(net, batch_x_t, batch_labels):# 首先计算所需要的数据,方差variance,也就公式里面的beta_talpha_bar_late = torch.cat((torch.tensor([1.0]), alpha_bar[:-1]), dim=0)variance = (1 - alpha_t) * (1 - alpha_bar_late) / (1 - alpha_bar)# 得到方差后,开始去噪net.eval() # 开启测试模式# 记录每次得到的图像steps = [batch_x_t]for t in range(T - 1, -1, -1):# 初始化当前每张图像对应的时间状态batch_t = torch.full(size=(batch_x_t.size()[0],), fill_value=t) # 表示此时的时间状态 (batch,)# 预测噪声# 修改第十四处batch_noise_pre = net(batch_x_t, batch_t, batch_labels) # (batch,channel,iamg,imag)# 开始去噪(需要注意一个点,就是去噪的公式,在t不等于0和等于0是不一样的,先进行都需要处理部分也就是添加噪声前面的均值部分)# 同时记得要统一维度,便于广播reshape_size = (batch_t.size()[0], 1, 1, 1)# 先取出对应的数值alpha_t_batch = alpha_t[batch_t]alpha_bar_batch = alpha_bar[batch_t]variance_batch = variance[batch_t]# 计算前面的均值batch_mean_t = 1 / torch.sqrt(alpha_t_batch).reshape(*reshape_size) \* (batch_x_t - (1 - alpha_t_batch.reshape(*reshape_size)) * batch_noise_pre / torch.sqrt(1 - alpha_bar_batch.reshape(*reshape_size)))# 分类,看t的值,判断是否添加噪声if t != 0:batch_x_t = batch_mean_t \+ torch.sqrt(variance_batch.reshape(*reshape_size)) \* torch.randn_like(batch_x_t)else:batch_x_t = batch_mean_t# 对每次得到的结果进行上下限的限制batch_x_t = torch.clamp(batch_x_t, min=-1, max=1)# 添加每步的去噪结果steps.append(batch_x_t)return steps
3. 测试-去噪
# 开始测试
if __name__ == '__main__':# 加载模型model = torch.load('unet_epoch0.pt')model.eval()is_lora = Trueis_hebing = False# 如果是利用lora,需要把微调的也加进去模型进行推理if is_lora:for name, layer in model.named_modules():name_list = name.split('.')target_list = ['Wk', 'Wv', 'Wq']for i in target_list:if i in name_list and isinstance(layer, nn.Linear):inject_lora(model, name, layer)# 加载权重参数try:para_load = torch.load('lora_para_epoch0.pt')model.load_state_dict(para_load, strict=False)except:pass# 如果需要合并,也就是把lora参数添加到原本的线性层上面的话,也就是把插入重新实现一遍,这次是把lora_layer换成linear。if is_lora and is_hebing:for name, layer in model:name_list = name.split('.')if isinstance(layer, LoraLayer):# 找到了对应的参数,把对应的lora参数添加到原本的参数上# 为什么要确定参数位置的上一层,因为setattr只能在上一层用,不能层层进入属性。cur_layer=modelfor n in name_list[:-1]:cur_layer=getattr(cur_layer,n)# 首先计算lora参数lora_weight = torch.matmul(layer.lora_a, layer.lora_b) * layer.alpha / layer.r# 把参数进行添加,线性层的权重矩阵通常是 (out_features, in_features),所以需要对lora矩阵进行转置layer.net.weight = nn.Parameter(layer.net.weight.add(lora_weight.T))setattr(cur_layer, name_list[-1], layer)# 生成噪音图batch_size = 10batch_x_t = torch.randn(size=(batch_size, 1, IMAGE_SIZE, IMAGE_SIZE)) # (5,1,48,48)batch_labels = torch.arange(start=0, end=10, dtype=torch.long) # 引导词promot# 逐步去噪得到原图# 修改第十五处steps = backward_denoise(model, batch_x_t, batch_labels)# 绘制数量num_imgs = 20# 绘制还原过程plt.figure(figsize=(15, 15))for b in range(batch_size):for i in range(0, num_imgs):idx = int(T / num_imgs) * (i + 1)# 像素值还原到[0,1]final_img = (steps[idx][b] + 1) / 2# tensor转回PIL图final_img = TenosrtoPil_action(final_img)plt.subplot(batch_size, num_imgs, b * num_imgs + i + 1)plt.imshow(final_img)plt.show()
参考
视频讲解:Lora微调代码实现_哔哩哔哩_bilibili
原理博客:手撕Diffusion系列 - 第九期 - 改进为Stable Diffusion(原理介绍)-CSDN博客,自学资料 - LoRA - 低秩微调技术-CSDN博客
相关文章:

手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)
手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码) 目录 手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)Stable Diffusion 原理图Stable Diffusion的原理解释Stable Diffusion 和Di…...

【Block总结】OutlookAttention注意力,捕捉细节和局部特征|即插即用
论文信息 标题: VOLO: Vision Outlooker for Visual Recognition作者: Li Yuan, Qibin Hou, Zihang Jiang, Jiashi Feng, Shuicheng Yan代码链接: https://github.com/sail-sg/volo论文链接: https://arxiv.org/pdf/2106.13112 创新点 前景注意力机制: VOLO引入了一种称为“…...

网络攻防实战指北专栏讲解大纲与网络安全法
专栏 本专栏为网络攻防实战指北,大纲如下所示 进度:目前已更完准备篇、HTML基础 计划:所谓基础不牢,地动山摇。所以下一步将持续更新基础篇内容 讲解信息安全时,结合《中华人民共和国网络安全法》(以下简…...

【已解决】windows7虚拟机安装VMtools频繁报错
为了在虚拟机VMware中安装win7,题主先在网上下载了windows7 professional版本的镜像,在vmware中安装vmtools时报错,信息如下 (安装程序无法继续,本程序需要您将此虚拟机上安装的操作系统更新到SP1) 然后就…...

蓝桥杯模拟算法:多项式输出
P1067 [NOIP2009 普及组] 多项式输出 - 洛谷 | 计算机科学教育新生态 这道题是一道模拟题,我们需要分情况讨论,我们需要做一下分类讨论 #include <iostream> #include <cstdlib> using namespace std;int main() {int n;cin >> n;for…...

冲刺蓝桥杯之速通vector!!!!!
文章目录 知识点创建增删查改 习题1习题2习题3习题4:习题5: 知识点 C的STL提供已经封装好的容器vector,也可叫做可变长的数组,vector底层就是自动扩容的顺序表,其中的增删查改已经封装好 创建 const int N30; vecto…...

知识管理平台在数字经济时代推动企业智慧决策与知识赋能的路径分析
内容概要 在数字经济时代,知识管理平台被视为企业智慧决策与知识赋能的关键工具。其核心作用在于通过高效地整合、存储和分发企业内部的知识资源,促进信息的透明化与便捷化,使得决策者能够在瞬息万变的市场环境中迅速获取所需信息。这不仅提…...

IT服务管理平台(ITSM):构建高效运维体系的基石
IT服务管理平台(ITSM):构建高效运维体系的基石 在数字化转型浪潮的推动下,企业对IT服务的依赖日益加深,如何高效管理和优化IT服务成为企业面临的重要课题。IT服务管理平台(ITSM)应运而生,以其系统化的管理方法和工具,助力企业实现IT服务的规范化、高效化和智能化。本…...

[EAI-026] DeepSeek-VL2 技术报告解读
Paper Card 论文标题:DeepSeek-VL2: Mixture-of-Experts Vision-Language Models for Advanced Multimodal Understanding 论文作者:Zhiyu Wu, Xiaokang Chen, Zizheng Pan, Xingchao Liu, Wen Liu, Damai Dai, Huazuo Gao, Yiyang Ma, Chengyue Wu, Bin…...

深度学习:基于MindNLP的RAG应用开发
什么是RAG? RAG(Retrieval-Augmented Generation,检索增强生成) 是一种结合检索(Retrieval)和生成(Generation)的技术,旨在提升大语言模型(LLM)生…...

【C语言】static关键字的三种用法
【C语言】static关键字的三种用法 C语言中的static关键字是一个存储类说明符,它可以用来修饰变量和函数。static关键字的主要作用是控制变量或函数的生命周期和可见性。以下是static关键字的一些主要用法和含义: 局部静态变量: 当static修饰…...

STM32 PWMI模式测频率占空比
接线图: PWMI基本结构 代码配置: 与上一章输入捕获代码一样,根据结构体,需要在输入捕获单元再配置一个通道。我们调用一个函数 这个函数可以给结构体赋值,当我们定义了一遍结构体参数,再调用这个函数&…...
神经网络|(四)概率论基础知识-古典概型
【1】引言 前序学习了线性回归的基础知识,了解到最小二乘法可以做线性回归分析,但为何最小二乘法如此准确,这需要从概率论的角度给出依据。 因此从本文起,需要花一段时间来回顾概率论的基础知识。 【2】古典概型 古典概型是我…...

ubuntu20.04.6下运行VLC-Qt例子simple-player
下载examples-master.zip(https://github.com/vlc-qt/examples),编译运行simple-player 参考链接: https://blog.csdn.net/szn1316159505/article/details/143743735 本文运行环境 Qt 5.15.2 Qt creator 5.0.2 主要步骤…...

低代码产品插件功能一览
下图是统计的目前市面上流行的低代码、零代码产品的插件功能。 产品名称 产品类型 官方插件数量 支持拓展 官方插件功能 宜搭 零代码 3 暂不支持 云打印、CAD看图、打印表单详情 微搭 低代码 1 暂不支持 小程序 明道云 低代码 2 支持 视图、工作流节点 简道…...

Blazor-@bind
数据绑定 带有 value属性的标记都可以使用bind 绑定,<div>、<span>等非输入标记,无法使用bind 指令的,默认绑定了 onchange 事件,onchange 事件是指在输入框中输入内容之后,当失去焦点时执行。 page &qu…...

RK3568中使用QT opencv(显示基础图像)
文章目录 一、查看对应的开发环境是否有opencv的库二、QT使用opencv 一、查看对应的开发环境是否有opencv的库 在开发板中的/usr/lib目录下查看是否有opencv的库: 这里使用的是正点原子的ubuntu虚拟机,在他的虚拟机里面已经安装好了opencv的库。 二、…...

[答疑]DDD伪创新哪有资格和仿制药比
DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 远航 2025-1-24 10:40 最近的热门话题仿制药,想到您经常批评的伪创新,这两者是不是很像? UMLChina潘加宇 伪创新哪有资格和仿制药比。 仿制药的…...

C#,入门教程(05)——Visual Studio 2022源程序(源代码)自动排版的功能动画图示
上一篇: C#,入门教程(04)——Visual Studio 2022 数据编程实例:随机数与组合https://blog.csdn.net/beijinghorn/article/details/123533838https://blog.csdn.net/beijinghorn/article/details/123533838 新来的徒弟们交上来的C#代码&#…...

DIY QMK量子键盘
最近放假了,趁这个空余在做一个分支项目,一款机械键盘,量子键盘取自固件名称QMK(Quantum Mechanical Keyboard)。 键盘作为计算机或其他电子设备的重要输入设备之一,通过将按键的物理动作转换为数字信号&am…...

C++ 堆栈分配的区别
这两种声明方式有什么区别 1.使用 new 关键字动态分配内存 动态分配:使用 new 关键字会在堆(heap)上分配内存,并返回一个指向该内存位置的指针。生命周期:对象的生命周期不会随着声明它的作用域结束而结束࿰…...

范冰冰担任第75届柏林电影节主竞赛单元评委 共鉴电影佳作
近日,备受瞩目的柏林电影节迎来了新一届盛事,而华人演员范冰冰将以主竞赛单元评委身份亮相,引发了广泛关注。此前她已担任过戛纳国际电影节、东京国际电影节、圣塞巴斯蒂安国际电影节等众多电影节主竞赛单元评委。作为国际影坛的知名人物&…...

Pandas进行MongoDB数据库CRUD
在数据处理的领域,MongoDB作为一款NoSQL数据库,以其灵活的文档存储结构和高扩展性广泛应用于大规模数据处理场景。Pandas作为Python的核心数据处理库,能够高效处理结构化数据。在MongoDB中,数据以JSON格式存储,这与Pandas的DataFrame结构可以很方便地互相转换。通过这篇教…...

《DeepSeek 实用集成:大模型能力接入各类软件》
DeepSeek 实用集成 awesome-deepseek-integration/README_cn.md at main deepseek-ai/awesome-deepseek-integration 将 DeepSeek 大模型能力轻松接入各类软件。访问 DeepSeek 开放平台来获取您的 API key。 English/简体中文 应用程序 Chatbox一个支持多种流行LLM模型的桌…...

适配Android16
Android16新特性 Android 16带来了许多新特性和改进,提升了系统的流畅度、用户体验和安全性。对于应用开发者来说,适配Android 16可以确保应用在该版本上的兼容性和性能,同时也可以利用其新特性为用户提供更好的服务。以下是Android 16的一些…...

如何用 Groq API 免费使用 DeepSeek-R1 70B,并通过 Deno 实现国内访问
这几天都被Deepseek刷屏了,而且Deepseek由于异常访问量,这几天都不能愉快的和它玩耍了, 我发现Groq新增了一个Deepseek的70b参数的模型, DeepSeek-R1 70B 作为一款强大的开源模型,提供了卓越的推理能力,而 …...

iperf 测 TCP 和 UDP 网络吞吐量
注:本文为 “iperf 测网络吞吐量” 相关文章合辑。 未整理去重。 使用 iperf3 监测网络吞吐量 Tom 王 2019-12-21 22:23:52 一 iperf3 介绍 (1.1) iperf3 是一个网络带宽测试工具,iperf3 可以擦拭 TCP 和 UDP 带宽质量。iperf3 可以测量最大 TCP 带宽…...

Autogen_core: Model Context
目录 示例代码代码解释另一个例子 示例代码 from dataclasses import dataclassfrom autogen_core import AgentId, MessageContext, RoutedAgent, SingleThreadedAgentRuntime, message_handler from autogen_core.model_context import BufferedChatCompletionContext from …...

SpringBoot AOP 和 事务
SpringBoot 整合 AOP 动态代理技术 JDK 动态代理 JDK 动态代理是 Java 自带的一种代理方式。它要求目标类必须有接口,基于这个接口,JDK 在运行时会动态生成一个代理对象。这个代理对象和目标对象就像 “拜把子” 的兄弟,因为它们都实现了相同…...

Ubuntu全面卸载mysql
如果你已经看到whereis mysql输出了与MySQL相关的路径,说明MySQL仍然存在于系统中。要卸载MySQL,可以按照以下步骤操作,确保完全删除所有相关的文件和配置: 1. 停止MySQL服务 首先,停止MySQL服务: sudo …...