Python打卡第42天

article/2025/8/5 10:00:00

@浙大疏锦行

知识点回顾

  1. 回调函数
  2. lambda函数
  3. hook函数的模块钩子和张量钩子
  4. Grad-CAM的示例

回调函数

Hook本质是回调函数,所以我们先介绍一下回调函数

回调函数是作为参数传递给其他函数的函数,其目的是在某个特定事件发生时被调用执行。这种机制允许代码在运行时动态指定需要执行的逻辑,实现了代码的灵活性和可扩展性。
回调函数的核心价值在于:

1. 解耦逻辑:将通用逻辑与特定处理逻辑分离,使代码更模块化。
2. 事件驱动编程:在异步操作、事件监听(如点击按钮、网络请求完成)等场景中广泛应用。
3. 延迟执行:允许在未来某个时间点执行特定代码,而不必立即执行。

其中回调函数作为参数传入,所以在定义的时候一般用callback来命名,在 PyTorch 的 Hook API 中,回调参数通常命名为 hook

# 定义一个回调函数
def handle_result(result):"""处理计算结果的回调函数"""print(f"计算结果是: {result}")# 定义一个接受回调函数的函数
def calculate(a, b, callback): # callback是一个约定俗成的参数名"""这个函数接受两个数值和一个回调函数,用于处理计算结果。执行计算并调用回调函数"""result = a + bcallback(result)  # 在计算完成后调用回调函数# 使用回调函数
calculate(3, 5, handle_result)  # 输出: 计算结果是: 8

lamda匿名函数

在hook中常常用到lambda函数,它是一种匿名函数(没有正式名称的函数),最大特点是用完即弃,无需提前命名和定义。它的语法形式非常简约,仅需一行即可完成定义,格式如下:
lambda 参数列表: 表达式

参数列表:可以是单个参数、多个参数或无参数。

表达式:函数的返回值(无需 return 语句,表达式结果直接返回)。

# 定义匿名函数:计算平方
square = lambda x: x ** 2# 调用
print(square(5))  # 输出: 25

Hook 函数

它可以在不干扰模型正常计算流程的情况下,插入到模型的特定位置,以便获取或修改中间层的输出或梯度。PyTorch 提供了两种主要的 hook:

1. Module Hooks:用于监听整个模块的输入和输出
2. Tensor Hooks:用于监听张量的梯度

下面我们将通过具体的例子来学习这两种 hook 的使用方法。

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt# 设置随机种子,保证结果可复现
torch.manual_seed(42)
np.random.seed(42)

模块钩子 (Module Hooks)

模块钩子允许我们在模块的输入或输出经过时进行监听。PyTorch 提供了两种模块钩子:

- `register_forward_hook`:在前向传播时监听模块的输入和输出
- `register_backward_hook`:在反向传播时监听模块的输入梯度和输出梯度

前向钩子 (Forward Hook)

前向钩子是一个函数,它会在模块的前向传播完成后立即被调用。这个函数可以访问模块的输入和输出,但不能修改它们。让我们通过一个简单的例子来理解前向钩子的工作原理。

import torch
import torch.nn as nn# 定义一个简单的卷积神经网络模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()# 定义卷积层:输入通道1,输出通道2,卷积核3x3,填充1保持尺寸不变self.conv = nn.Conv2d(1, 2, kernel_size=3, padding=1)# 定义ReLU激活函数self.relu = nn.ReLU()# 定义全连接层:输入特征2*4*4,输出10分类self.fc = nn.Linear(2 * 4 * 4, 10)def forward(self, x):# 卷积操作x = self.conv(x)# 激活函数x = self.relu(x)# 展平为一维向量,准备输入全连接层x = x.view(-1, 2 * 4 * 4)# 全连接分类x = self.fc(x)return x# 创建模型实例
model = SimpleModel()# 创建一个列表用于存储中间层的输出
conv_outputs = []# 定义前向钩子函数 - 用于在模型前向传播过程中获取中间层信息
def forward_hook(module, input, output):"""前向钩子函数,会在模块每次执行前向传播后被自动调用参数:module: 当前应用钩子的模块实例input: 传递给该模块的输入张量元组output: 该模块产生的输出张量"""print(f"钩子被调用!模块类型: {type(module)}")print(f"输入形状: {input[0].shape}") #  input是一个元组,对应 (image, label)print(f"输出形状: {output.shape}")# 保存卷积层的输出用于后续分析# 使用detach()避免追踪梯度,防止内存泄漏conv_outputs.append(output.detach())# 在卷积层注册前向钩子
# register_forward_hook返回一个句柄,用于后续移除钩子
hook_handle = model.conv.register_forward_hook(forward_hook)# 创建一个随机输入张量 (批次大小=1, 通道=1, 高度=4, 宽度=4)
x = torch.randn(1, 1, 4, 4)# 执行前向传播 - 此时会自动触发钩子函数
output = model(x)# 释放钩子 - 重要!防止在后续模型使用中持续调用钩子造成意外行为或内存泄漏
hook_handle.remove()# # 打印中间层输出结果
# if conv_outputs:
#     print(f"\n卷积层输出形状: {conv_outputs[0].shape}")
#     print(f"卷积层输出值示例: {conv_outputs[0][0, 0, :, :]}")
钩子被调用!模块类型: <class 'torch.nn.modules.conv.Conv2d'>
输入形状: torch.Size([1, 1, 4, 4])
输出形状: torch.Size([1, 2, 4, 4])
# 让我们可视化卷积层的输出
if conv_outputs:plt.figure(figsize=(10, 5))# 原始输入图像plt.subplot(1, 3, 1)plt.title('输入图像')plt.imshow(x[0, 0].detach().numpy(), cmap='gray') # 显示灰度图像# 第一个卷积核的输出plt.subplot(1, 3, 2)plt.title('卷积核1输出')plt.imshow(conv_outputs[0][0, 0].detach().numpy(), cmap='gray')# 第二个卷积核的输出plt.subplot(1, 3, 3)plt.title('卷积核2输出')plt.imshow(conv_outputs[0][0, 1].detach().numpy(), cmap='gray')plt.tight_layout()plt.show()

 反向钩子 (Backward Hook)

反向钩子与前向钩子类似,但它是在反向传播过程中被调用的。反向钩子可以用来获取或修改梯度信息。

# 定义一个存储梯度的列表
conv_gradients = []# 定义反向钩子函数
def backward_hook(module, grad_input, grad_output):# 模块:当前应用钩子的模块# grad_input:模块输入的梯度# grad_output:模块输出的梯度print(f"反向钩子被调用!模块类型: {type(module)}")print(f"输入梯度数量: {len(grad_input)}")print(f"输出梯度数量: {len(grad_output)}")# 保存梯度供后续分析conv_gradients.append((grad_input, grad_output))# 在卷积层注册反向钩子
hook_handle = model.conv.register_backward_hook(backward_hook)# 创建一个随机输入并进行前向传播
x = torch.randn(1, 1, 4, 4, requires_grad=True)
output = model(x)# 定义一个简单的损失函数并进行反向传播
loss = output.sum()
loss.backward()# 释放钩子
hook_handle.remove()
反向钩子被调用!模块类型: <class 'torch.nn.modules.conv.Conv2d'>
输入梯度数量: 3
输出梯度数量: 1

张量钩子 (Tensor Hooks)

除了模块钩子,PyTorch 还提供了张量钩子,允许我们直接监听和修改张量的梯度。张量钩子有两种:

- `register_hook`:用于监听张量的梯度
- `register_full_backward_hook`:用于在完整的反向传播过程中监听张量的梯度(PyTorch 1.4+)

# 创建一个需要计算梯度的张量
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2
z = y ** 3# 定义一个钩子函数,用于修改梯度
def tensor_hook(grad):print(f"原始梯度: {grad}")# 修改梯度,例如将梯度减半return grad / 2# 在y上注册钩子
hook_handle = y.register_hook(tensor_hook)# 计算梯度
z.backward()print(f"x的梯度: {x.grad}")# 释放钩子
hook_handle.remove()
原始梯度: tensor([48.])
x的梯度: tensor([96.])

Grad-CAM

Grad-CAM (Gradient-weighted Class Activation Mapping) 算法是一种强大的可视化技术,用于解释卷积神经网络 (CNN) 的决策过程。它通过计算特征图的梯度来生成类激活映射(Class Activation Mapping,简称 CAM ),直观地显示图像中哪些区域对模型的特定预测贡献最大。

Grad-CAM 的核心思想是:通过反向传播得到的梯度信息,来衡量每个特征图对目标类别的重要性。

1. 梯度信息:通过计算目标类别对特征图的梯度,得到每个特征图的重要性权重。
2. 特征加权:用这些权重对特征图进行加权求和,得到类激活映射。
3. 可视化:将激活映射叠加到原始图像上,高亮显示对预测最关键的区域。

import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image# 设置随机种子确保结果可复现
# 在深度学习中,随机种子可以让每次运行代码时,模型初始化参数、数据打乱等随机操作保持一致,方便调试和对比实验结果
torch.manual_seed(42)
np.random.seed(42)# 加载CIFAR-10数据集
# 定义数据预处理步骤,先将图像转换为张量,再进行归一化操作
# 归一化的均值和标准差是(0.5, 0.5, 0.5),这里的均值和标准差是对CIFAR-10数据集的经验值,使得数据分布更有利于模型训练
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])# 加载测试集,指定数据集根目录为'./data',设置为测试集(train=False),如果数据不存在则下载(download=True),并应用上述定义的预处理
testset = torchvision.datasets.CIFAR10(root='./data', train=False,download=True, transform=transform
)# 定义类别名称,CIFAR-10数据集包含这10个类别
classes = ('飞机', '汽车', '鸟', '猫', '鹿', '狗', '青蛙', '马', '船', '卡车')# 定义一个简单的CNN模型
class SimpleCNN(nn.Module):def __init__(self):super(SimpleCNN, self).__init__()# 第一个卷积层,输入通道为3(彩色图像),输出通道为32,卷积核大小为3x3,填充为1以保持图像尺寸不变self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)# 第二个卷积层,输入通道为32,输出通道为64,卷积核大小为3x3,填充为1self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)# 第三个卷积层,输入通道为64,输出通道为128,卷积核大小为3x3,填充为1self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)# 最大池化层,池化核大小为2x2,步长为2,用于下采样,减少数据量并提取主要特征self.pool = nn.MaxPool2d(2, 2)# 第一个全连接层,输入特征数为128 * 4 * 4(经过前面卷积和池化后的特征维度),输出为512self.fc1 = nn.Linear(128 * 4 * 4, 512)# 第二个全连接层,输入为512,输出为10(对应CIFAR-10的10个类别)self.fc2 = nn.Linear(512, 10)def forward(self, x):# 第一个卷积层后接ReLU激活函数和最大池化操作,经过池化后图像尺寸变为原来的一半,这里输出尺寸变为16x16x = self.pool(F.relu(self.conv1(x)))  # 第二个卷积层后接ReLU激活函数和最大池化操作,输出尺寸变为8x8x = self.pool(F.relu(self.conv2(x)))  # 第三个卷积层后接ReLU激活函数和最大池化操作,输出尺寸变为4x4x = self.pool(F.relu(self.conv3(x)))  # 将特征图展平为一维向量,以便输入到全连接层x = x.view(-1, 128 * 4 * 4)# 第一个全连接层后接ReLU激活函数x = F.relu(self.fc1(x))# 第二个全连接层输出分类结果x = self.fc2(x)return x# 初始化模型
model = SimpleCNN()
print("模型已创建")# 如果有GPU则使用GPU,将模型转移到对应的设备上
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = model.to(device)# 训练模型(简化版,实际应用中应该进行完整训练)
def train_model(model, epochs=1):# 加载训练集,指定数据集根目录为'./data',设置为训练集(train=True),如果数据不存在则下载(download=True),并应用前面定义的预处理trainset = torchvision.datasets.CIFAR10(root='./data', train=True,download=True, transform=transform)# 创建数据加载器,设置批量大小为64,打乱数据顺序(shuffle=True),使用2个线程加载数据trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,shuffle=True, num_workers=2)# 定义损失函数为交叉熵损失,用于分类任务criterion = nn.CrossEntropyLoss()# 定义优化器为Adam,用于更新模型参数,学习率设置为0.001optimizer = torch.optim.Adam(model.parameters(), lr=0.001)for epoch in range(epochs):running_loss = 0.0for i, data in enumerate(trainloader, 0):# 从数据加载器中获取图像和标签inputs, labels = data# 将图像和标签转移到对应的设备(GPU或CPU)上inputs, labels = inputs.to(device), labels.to(device)# 清空梯度,避免梯度累加optimizer.zero_grad()# 模型前向传播得到输出outputs = model(inputs)# 计算损失loss = criterion(outputs, labels)# 反向传播计算梯度loss.backward()# 更新模型参数optimizer.step()running_loss += loss.item()if i % 100 == 99:# 每100个批次打印一次平均损失print(f'[{epoch + 1}, {i + 1}] 损失: {running_loss / 100:.3f}')running_loss = 0.0print("训练完成")# 训练模型(可选,如果有预训练模型可以加载)
# 取消下面这行的注释来训练模型
# train_model(model, epochs=1)# 或者尝试加载预训练模型(如果存在)
try:# 尝试加载名为'cifar10_cnn.pth'的模型参数model.load_state_dict(torch.load('cifar10_cnn.pth'))print("已加载预训练模型")
except:print("无法加载预训练模型,使用未训练模型或训练新模型")# 如果没有预训练模型,可以在这里调用train_model函数train_model(model, epochs=1)# 保存训练后的模型参数torch.save(model.state_dict(), 'cifar10_cnn.pth')# 设置模型为评估模式,此时模型中的一些操作(如dropout、batchnorm等)会切换到评估状态
model.eval()# Grad-CAM实现
class GradCAM:def __init__(self, model, target_layer):self.model = modelself.target_layer = target_layerself.gradients = Noneself.activations = None# 注册钩子,用于获取目标层的前向传播输出和反向传播梯度self.register_hooks()def register_hooks(self):# 前向钩子函数,在目标层前向传播后被调用,保存目标层的输出(激活值)def forward_hook(module, input, output):self.activations = output.detach()# 反向钩子函数,在目标层反向传播后被调用,保存目标层的梯度def backward_hook(module, grad_input, grad_output):self.gradients = grad_output[0].detach()# 在目标层注册前向钩子和反向钩子self.target_layer.register_forward_hook(forward_hook)self.target_layer.register_backward_hook(backward_hook)def generate_cam(self, input_image, target_class=None):# 前向传播,得到模型输出model_output = self.model(input_image)if target_class is None:# 如果未指定目标类别,则取模型预测概率最大的类别作为目标类别target_class = torch.argmax(model_output, dim=1).item()# 清除模型梯度,避免之前的梯度影响self.model.zero_grad()# 反向传播,构造one-hot向量,使得目标类别对应的梯度为1,其余为0,然后进行反向传播计算梯度one_hot = torch.zeros_like(model_output)one_hot[0, target_class] = 1model_output.backward(gradient=one_hot)# 获取之前保存的目标层的梯度和激活值gradients = self.gradientsactivations = self.activations# 对梯度进行全局平均池化,得到每个通道的权重,用于衡量每个通道的重要性weights = torch.mean(gradients, dim=(2, 3), keepdim=True)# 加权激活映射,将权重与激活值相乘并求和,得到类激活映射的初步结果cam = torch.sum(weights * activations, dim=1, keepdim=True)# ReLU激活,只保留对目标类别有正贡献的区域,去除负贡献的影响cam = F.relu(cam)# 调整大小并归一化,将类激活映射调整为与输入图像相同的尺寸(32x32),并归一化到[0, 1]范围cam = F.interpolate(cam, size=(32, 32), mode='bilinear', align_corners=False)cam = cam - cam.min()cam = cam / cam.max() if cam.max() > 0 else camreturn cam.cpu().squeeze().numpy(), target_class
Files already downloaded and verified
模型已创建
无法加载预训练模型,使用未训练模型或训练新模型
Files already downloaded and verified
[1, 100] 损失: 1.876
[1, 200] 损失: 1.509
[1, 300] 损失: 1.395
[1, 400] 损失: 1.293
[1, 500] 损失: 1.218
[1, 600] 损失: 1.157
[1, 700] 损失: 1.106
训练完成
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
# 设置中文字体支持
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
# 选择一个随机图像
# idx = np.random.randint(len(testset))
idx = 102  # 选择测试集中的第101张图片 (索引从0开始)
image, label = testset[idx]
print(f"选择的图像类别: {classes[label]}")# 转换图像以便可视化
def tensor_to_np(tensor):img = tensor.cpu().numpy().transpose(1, 2, 0)mean = np.array([0.5, 0.5, 0.5])std = np.array([0.5, 0.5, 0.5])img = std * img + meanimg = np.clip(img, 0, 1)return img# 添加批次维度并移动到设备
input_tensor = image.unsqueeze(0).to(device)# 初始化Grad-CAM(选择最后一个卷积层)
grad_cam = GradCAM(model, model.conv3)# 生成热力图
heatmap, pred_class = grad_cam.generate_cam(input_tensor)# 可视化
plt.figure(figsize=(12, 4))# 原始图像
plt.subplot(1, 3, 1)
plt.imshow(tensor_to_np(image))
plt.title(f"原始图像: {classes[label]}")
plt.axis('off')# 热力图
plt.subplot(1, 3, 2)
plt.imshow(heatmap, cmap='jet')
plt.title(f"Grad-CAM热力图: {classes[pred_class]}")
plt.axis('off')# 叠加的图像
plt.subplot(1, 3, 3)
img = tensor_to_np(image)
heatmap_resized = np.uint8(255 * heatmap)
heatmap_colored = plt.cm.jet(heatmap_resized)[:, :, :3]
superimposed_img = heatmap_colored * 0.4 + img * 0.6
plt.imshow(superimposed_img)
plt.title("叠加热力图")
plt.axis('off')plt.tight_layout()
plt.savefig('grad_cam_result.png')
plt.show()# print("Grad-CAM可视化完成。已保存为grad_cam_result.png")
选择的图像类别: 青蛙

 从图中可以看到,模型比较关注青蛙身体的一些部分,说明模型在判断这张图像是青蛙时,依据的是这些被关注区域的特征 

 


http://www.hkcw.cn/article/NeoDNFYfPn.shtml

相关文章

hysAnalyser --- 逐包分析MPEG-TS的功能说明

前言 hysAnalyser 是一款新颖、独具特色的 MPEG-TS 数据分析工具&#xff0c;定位于 1&#xff09;音视频开发和测试人员&#xff1a;和MEPG-TS有关开发、调试、测试辅助&#xff1b; 2&#xff09;和MPEG-TS相关业务系统的运维人员&#xff1a;如数字电视、OTT、互联网流媒体…

语音转文字工具

平时工作和学习比较忙&#xff0c;可能没时间听讲座&#xff0c;只能看回放&#xff0c;回访也很长&#xff0c;这时&#xff0c;我们可以借助语言转文字&#xff0c;通过阅读文字快速了解讲座的重点&#xff0c;今天给大家分享一个本人经常用的语言转文字工具&#xff0c;改工…

vue3(入门,setup,ref,计算属性,watch)

vue3(入门&#xff0c;setup,ref,计算属性,watch) 项目创建 Vue2&#xff08;选项式api&#xff09; 分散 vue3&#xff08;组合式api&#xff09; setUp&#xff08;&#xff09; setup返回值可以是一个渲染函数 面试题&#xff1a; setup和vue2中的配置项可以同时存在吗&a…

c++ 类型转换函数

测试代码&#xff1a; void testTypeTransfer() { // 测试类型转换函数class Distance {private:int meters;public:// 类型转换函数&#xff0c;int表示转化为int类型operator int() {std::cout << "调用了类型转换函数" << endl;return meters; }Dist…

如何使用 Docker 部署grafana和loki收集vllm日志?

环境: Ubuntu20.04 grafana loki 3.4.1 问题描述: 如何使用 Docker 部署grafana和loki收集vllm日志? 解决方案: 1.创建一个名为 loki 的目录。将 loki 设为当前工作目录: mkdir loki cd loki2.将以下命令复制并粘贴到您的命令行中,以将 loki-local-config.yaml …

汽车安全 2030 预测 (功能安全FuSa、预期功能安全SOTIF、网络安全CyberSecurity):成本、效益与行业影响

汽车安全 2030 预测 (功能安全FuSa、预期功能安全SOTIF、网络安全CyberSecurity)&#xff1a;成本、效益与行业影响 到 2030 年&#xff0c;汽车行业将迎来一场安全技术的深度变革&#xff0c;其中 “三重安全防护”&#xff08;功能安全 FuSa、预期功能安全 SOTIF、网络安全&…

AI视频“入驻”手机,多模态成智能终端的新战场

文&#xff5c;乐乐 今天&#xff0c;无线蓝牙耳机&#xff08;TWS&#xff09;已经成为人人都用得起的产品。 但退回到9年前&#xff0c;苹果AirPods是全球第一款真正意义上的无线蓝牙耳机。靠着自研并申请专利的Snoop监听技术&#xff0c;苹果解决了蓝牙耳机左右延时和能耗…

嵌入式学习笔记 - FreeRTOS v9.0.0 与v10.0.1不同版本占用资源对比

以下为用示例对比freeRTOS v9.0.0版本以及v10.0.1版本占用资源的境况&#xff0c;两者均在运行完全相同的任务包括任务内容与数量的情况进行对比&#xff0c;任务的创建均使用静态内存方式创建&#xff0c;每个任务的任务堆栈均设置相同大小&#xff0c;并且freeRTOSconfig.h文…

Git仓库大文件清理指南

前言 当大文件被提交到 Git 仓库后又删除&#xff0c;但仓库体积仍然很大时&#xff0c;这是因为 Git 保留了这些文件的历史记录。要彻底清理这些文件并减小仓库体积&#xff0c;你需要重写 Git 历史。 注意事项 这会重写历史 - 所有协作者都需要重新克隆仓库 备份你的仓库 …

LLMs之MCP:如何使用 Gradio 构建 MCP 服务器

LLMs之MCP&#xff1a;如何使用 Gradio 构建 MCP 服务器 导读&#xff1a;本文详细介绍了如何使用Gradio构建MCP服务器&#xff0c;包括前提条件、构建方法、关键特性和相关资源。通过一个简单的字母计数示例&#xff0c;演示了如何将Gradio应用转换为LLM可以使用的工具。Gradi…

Redis最佳实践——性能优化技巧之集群与分片

Redis集群与分片在电商应用中的性能优化技巧 一、Redis集群架构模式解析 1. 主流集群方案对比 方案核心原理适用场景电商应用案例主从复制读写分离数据冗余中小规模读多写少商品详情缓存Redis Sentinel自动故障转移监控高可用需求场景订单状态缓存Redis Cluster原生分布式分片…

2025年最新Android Studio汉化教程

首先把idea更新到IntelliJ IDEA 2024.3.5 (Community Edition)&#xff0c;然后关闭AndroidStudio 没有idea可以下载最新的 IntelliJ IDEA – the IDE for Pro Java and Kotlin Development 找到idea的安装路径&#xff0c;找到“\plugins\localization-zh 然后把“localizat…

uniapp实现下载文件到手机(安卓),通过系统分享到其他app

要在UniApp中实现下载文件到安卓手机&#xff0c;我这里使用的是plus.io直接获取文件系统&#xff0c;大家可以找一下dcloud插件或者其他api。以下是一个简单的步骤&#xff1a; 首先&#xff0c;你需要创建一个按钮或者其他触发下载的UI元素&#xff0c;用户点击后触发文件下载…

flutter-渐变色边框和渐变色文字和渐变色背景

文章目录 1. 介绍2. 代码实现2-1. 渐变色背景2-2. 渐变色边框2-3. 宽高由内容撑起的渐变色边框2-4. 渐变色文本 3. 完整例子 1. 介绍 在 flutter 中&#xff0c;渐变有三种&#xff0c;线性渐变 LinearGradient、放射状渐变 RadialGradient、扇形渐变 SweepGradient。一般都是…

记录一次macbook 安装macOS+win11双系统的历程。包括MacBook电脑恢复、绕过win11限制等

一、MacBook恢复macOS系统&#xff0c;或有问题可以重新用此操作 关机状态&#xff0c;同时摁住 optioncommandR 三个键&#xff0c;然后再摁开机键&#xff0c;等出现 一个地球的图标即可松开。 然后正常链接wifi&#xff0c;让它自动下载一些组件即可。 这里对硬盘进行重新…

移动电视盒MGV2000刷安卓及Armbian笔记

我的是mgv2000 JL代工的&#xff0c;配置是四核1G内存8GEMMC&#xff0c;我的目的是把他刷成linux&#xff0c;网上查询资料后&#xff0c;了解到大概分以下两个步骤&#xff1a; #一、先把原来移动自带的系统刷新为适合的安卓系统 #二、在新的安卓系统下&#xff0c;再刷成A…

蚂蚁百宝箱3分钟上手MCP:6步轻松构建智能体应用并发布小程序

蚂蚁百宝箱3分钟上手MCP&#xff1a;6步轻松构建智能体应用并发布小程序 AI 能聊天、能画画&#xff0c;但它能帮你赚钱吗&#xff1f;智能体空有一身本领却难以变现&#xff0c;是不是让你也感到无奈&#xff1f; 别担心&#xff0c;蚂蚁百宝箱「MCP专区」来啦&#xff01;现…

Android Studio 使用WIFI连接手机进行无线调试 adb命令

1.将电脑和手机连接到同一WIFI 2.手机连接usb&#xff0c;连接到AndroidStudio&#xff0c;和平时连线调试一样。 3.打开AndroidStudio下方Terminal便可以开始输入adb命令。 4.输入 adb devices 命令查看设备 adb devices效果如下 5.设置设备端口号命令 adb tcpip 5555 端…

MySQL—使用binlog日志恢复数据

一、binlog日志恢复数据简介 在 MySQL 中&#xff0c;使用二进制日志&#xff08;binlog&#xff09;恢复数据是一种常见的用于故障恢复或数据找回的方法。以下是详细的使用步骤&#xff1a; 确认 binlog 已启用&#xff1a;首先需要确认 MySQL 服务器已经启用了二进制日志功…

Kotlin-类和对象

文章目录 类主构造函数次要构造函数总结 对象初始化 类的继承成员函数属性覆盖(重写)智能转换 类的扩展 类 class Student { }这是一个类,表示学生,怎么才能给这个类添加一些属性(姓名,年龄…)呢? 主构造函数 我们需要指定类的构造函数。构造函数也是函数的一种,但是它专门…