一文看明白Transformer微调过程中嵌入向量的变化

news2024/11/25 3:03:57

在这里插入图片描述

TL;DR

微调在图像分类中显著影响嵌入向量。微调前的嵌入向量提供通用性表征,而微调后的嵌入向量捕获任务特定的特征。这种区别可能导致在异常检测和其他任务中的不同结果。微调前和微调后的嵌入向量各有其独特优势,应结合使用以实现图像分类和分析任务中的全面分析。
请查看本文的 CIFAR-10 数据集【3】在线演示之一。
点击跳转

1 引言

在图像分类中,先使用如 ImageNet 这样的大型数据集上预训练模型,随后对特定目标数据集进行微调,已成为默认方法。然而,在处理现实世界的目标数据集时,考虑其固有噪声非常重要,包括异常值、标签错误和其他异常。数据集的交互式探索在全面理解数据中发挥着关键作用,通过数据丰富化,能够识别和解决关键数据段。
在分析非结构化图像数据时,嵌入向量扮演着关键角色。它们提供高层次的语义信息,支持数据分析、洞察生成和异常检测等各种任务。通过在低维空间中表示图像,嵌入向量使得探索数据内的相似性和差异性变得更加容易,并允许使用 t-SNE 或 UMAP 等技术创建相似性映射。我们将使用 Spotlight 来交互式探索我们创建的丰富数据集:

免责声明:本文作者也是 Spotlight 的开发者之一。本文中的部分代码片段也可在 Spotlight 仓库中找到。

在本文中,我们将深入探讨微调前后嵌入向量的差异,并特别关注异常检测。虽然重要的是要注意,使用经过微调的模型的嵌入向量并不总是为异常检测带来最佳结果(我们也可以使用概率),但这仍然是一种引人入胜的方法。嵌入向量的可视化为分析过程增添了一种视觉上的吸引力。

为了评估嵌入向量在异常检测任务中的性能和有效性,我们将检查在图像分类中广泛使用的典型数据集。此外,我们将使用两种常见的基础模型。通过这种探索,我们旨在深入了解模型微调对嵌入向量的影响,更好地理解它们的能力和局限。

2.环境准备

安装所需要的python包

!pip install renumics-spotlight datasets torch pandas cleanlab annoy

2.1 提取嵌入向量

我们将使用基于 google/vit-base-patch16-224-in21k [1] 和 [microsoft/swin-base-patch4-window7-224(https://huggingface.co/microsoft/swin-base-patch4-window7-224) [2] 的模型,这些模型可在 Hugging Faces 上获得,用以提取微调前的嵌入向量以及每个数据集最受欢迎的微调模型:araki/vit-base-patch16-224-in21k-finetuned-cifar10、MazenAmria/swin-tiny-finetuned-cifar100、nateraw/vit-base-beans、farleyknight/mnist-digit-classification-2022-09-04。

case = {
    "cifar10": {
        "base_model_name": "google/vit-base-patch16-224-in21k",
        "ft_model_name": "aaraki/vit-base-patch16-224-in21k-finetuned-cifar10",
    },
    "beans": {
        "base_model_name": "google/vit-base-patch16-224-in21k",
        "ft_model_name": "nateraw/vit-base-beans",
    },
    "mnist": {
        "base_model_name": "google/vit-base-patch16-224-in21k",
        "ft_model_name": "farleyknight/mnist-digit-classification-2022-09-04",
    },
    "cifar100": {
        "base_model_name": "microsoft/swin-base-patch4-window7-224",
        "ft_model_name": "MazenAmria/swin-tiny-finetuned-cifar100",
    },
}

为了加载数据集,我们使用 datasets 模块中的 load_dataset 函数,并为图像分类任务做好准备。你可以从本文测试并报告的数据集中选择 CIFAR-10 [3]、CIFAR-100 [3]、MNIST [4] 和 Beans [5],或尝试使用与之对应的模型,从 Hugging Face 获取不同的图像分类数据集。

import datasets
# 可选 cifar10, cifar100, mnist 或 beans。
# 对应模型将自动选择
DATASET = "cifar10"
ds = datasets.load_dataset(DATASET, split="train").prepare_for_task(
    "image-classification"
)
df = ds.to_pandas()
# df = df.iloc[:1000] # 取消注释以限制数据集大小进行测试

我们定义了 huggingface_embedding 函数,用于从微调模型和基础/基本模型中提取嵌入向量。这些嵌入向量被存储在原始数据框(df)的不同列中(“embedding_ft” 和 “embedding_foundation”)。

import datasets
from transformers import AutoFeatureExtractor, AutoModel
import torch
import pandas as pd

ft_model_name = case[DATASET]["ft_model_name"]
base_model_name = case[DATASET]["base_model_name"]
def extract_embeddings(model, feature_extractor, image_name="image"):
    """
    计算嵌入向量的工具函数。
    参数:
        model: huggingface 模型
        feature_extractor: huggingface 特征提取器
        image_name: 数据集中图像列的名称
    返回:
        计算嵌入向量的函数
    """
    device = model.device
    def pp(batch):
        images = batch[image_name]
        inputs = feature_extractor(
            images=[x.convert("RGB") for x in images], return_tensors="pt"
        ).to(device)
        embeddings = model(**inputs).last_hidden_state[:, 0].cpu()
        return {"embedding": embeddings}
    return pp

def huggingface_embedding(
    df,
    image_name="image",
    modelname="google/vit-base-patch16-224",
    batched=True,
    batch_size=24,
):
    """
    使用 huggingface 模型计算嵌入向量。
    参数:
        df: 含有图像的数据框
        image_name: 数据集中图像列的名称
        modelname: huggingface 模型名称
        batched: 是否批量计算嵌入向量
        batch_size: 批量大小
    返回:
        包含嵌入向量的新数据框
    """
    # 初始化 huggingface 模型
    feature_extractor = AutoFeatureExtractor.from_pretrained(modelname)
    model = AutoModel.from_pretrained(modelname, output_hidden_states=True)
    # 从 df 创建 huggingface 数据集
    dataset = datasets.Dataset.from_pandas(df).cast_column(image_name, datasets.Image())
    # 计算嵌入向量
    device = "cuda" if torch.cuda.is_available() else "cpu"
    extract_fn = extract_embeddings(model.to(device), feature_extractor, image_name)
    updated_dataset = dataset.map(extract_fn, batched=batched, batch_size=batch_size)
    df_temp = updated_dataset.to_pandas()
    df_emb = pd.DataFrame()
    df_emb["embedding"] = df_temp["embedding"]
    return df_emb

embeddings_df = huggingface_embedding(
    df,
    modelname=ft_model_name,
    batched=True,
    batch_size=24,
)
embeddings_df_found = huggingface_embedding(
    df, modelname=base_model_name, batched=True, batch_size=24
)
df["embedding_ft"] = embeddings_df["embedding"]
df["embedding_foundation"] = embeddings_df_found["embedding"]

2.2 计算异常值得分

接下来,我们使用 Cleanlab 来计算基于嵌入向量的微调模型和基础/基本模型的异常值得分。我们利用 OutOfDistribution 类来计算异常值得分。生成的异常值得分将存储在原始数据框(df)中:

from cleanlab.outlier import OutOfDistribution
import numpy as np
import pandas as pd
def outlier_score_by_embeddings_cleanlab(df, embedding_name="embedding"):
    """
    使用 cleanlab 通过嵌入向量计算异常值得分
        参数:
            df: 含有嵌入向量的数据框
            embedding_name: 嵌入向量所在列的名称
        返回:
            新的 df_out: 含有异常值得分的数据框
    """
    embs = np.stack(df[embedding_name].to_numpy())
    ood = OutOfDistribution()
    ood_train_feature_scores = ood.fit_score(features=np.stack(embs))
    df_out = pd.DataFrame()
    df_out["outlier_score_embedding"] = ood_train_feature_scores
    return df_out

df["outlier_score_ft"] = outlier_score_by_embeddings_cleanlab(
    df, embedding_name="embedding_ft"
)["outlier_score_embedding"]
df["outlier_score_found"] = outlier_score_by_embeddings_cleanlab(
    df, embedding_name="embedding_foundation"
)["outlier_score_embedding"]

2.3 寻找最近邻居图像

为了评估异常值,我们使用仅经过微调模型的 Annoy 库计算最近邻居图像。生成的图像将存储在原始数据框(df)中:

from annoy import AnnoyIndex
import pandas as pd
def nearest_neighbor_annoy(
    df, embedding_name="embedding", threshold=0.3, tree_size=100
):
    """
    使用 annoy 查找最近邻居。
    参数:
        df: 含有嵌入向量的数据框
        embedding_name: 嵌入向量列的名称
        threshold: 异常检测的阈值
        tree_size: annoy 的树大小
    返回:
        新的数据框,包含最近邻居信息
    """
    embs = df[embedding_name]
    t = AnnoyIndex(len(embs[0]), "angular")
    for idx, x in enumerate(embs):
        t.add_item(idx, x)
    t.build(tree_size)
    images = df["image"]
    df_nn = pd.DataFrame()
    nn_id = [t.get_nns_by_item(i, 2)[1] for i in range(len(embs))]
    df_nn["nn_id"] = nn_id
    df_nn["nn_image"] = [images[i] for i in nn_id]
    df_nn["nn_distance"] = [t.get_distance(i, nn_id[i]) for i in range(len(embs))]
    df_nn["nn_flag"] = df_nn.nn_distance < threshold
    return df_nn

df_nn = nearest_neighbor_annoy(
    df, embedding_name="embedding_ft", threshold=0.3, tree_size=100
)
df["nn_image"] = df_nn["nn_image"]

2.4 可视化

为了使用 Spotlight 进行可视化,通过使用 lambda 函数将整数标签映射到它们的字符串表示形式,我们在数据框中创建了一个新的“label_str”列。使用 dtypes 字典来指定每列的数据类型,以获得正确的可视化效果,同时布局确定了可视化中的排列和显示的列:

from renumics import spotlight
df["label_str"] = df["labels"].apply(lambda x: ds.features["labels"].int2str(x))
dtypes = {
    "nn_image": spotlight.Image,
    "image": spotlight.Image,
    "embedding_ft": spotlight.Embedding,
    "embedding_foundation": spotlight.Embedding,
}
spotlight.show(
    df,
    dtype=dtypes,
    layout="https://spotlight.renumics.com/resources//layout_pre_post_ft.json",
)

之后会打开一个浏览器的新窗口
在这里插入图片描述
在可视化部分,左上角显示了一个全面的表格,展示了数据集中存在的所有字段。选择了基础模型的嵌入向量识别为异常值的图像。在右上角,你可以观察到两个 UMAP 表示:第一个表示来自基础模型的嵌入向量,而第二个表示来自微调模型的嵌入向量。在底部,选定的图像与数据集中它们的最近邻居一起展示。

3 结果

现在让我们检查所有数据集的结果。你可以通过使用不同的输入数据集重复第2节的所有步骤来复制结果,或者可以使用下面的代码片段加载预处理的数据集。或者你可以查看链接的在线演示。

3. 1 CIFAR-10

加载 CIFAR-10数据集

from renumics import spotlight
import datasets
ds = datasets.load_dataset("renumics/cifar10-outlier", split="train")
df = ds.rename_columns({"img": "image", "label": "labels"}).to_pandas()
df["label_str"] = df["labels"].apply(lambda x: ds.features["label"].int2str(x))
dtypes = {
    "nn_image": spotlight.Image,
    "image": spotlight.Image,
    "embedding_ft": spotlight.Embedding,
    "embedding_foundation": spotlight.Embedding,
}
spotlight.show(
    df,
    dtype=dtypes,
    layout="https://spotlight.renumics.com/resources/layout_pre_post_ft.json",
)

或查看在线演示 https://huggingface.co/spaces/renumics/cifar10-outlier 来检查异常值:
在这里插入图片描述
在这里插入图片描述

微调后嵌入向量的 UMAP 可视化显示了独特的模式,其中某些类别完全与其他所有类别分离,而有些可能只与一两个其他类别相连。
在 CIFAR-10 中,使用微调前嵌入向量检测到的异常值似乎并不特别少见,因为它们与相邻图像相对相似。相比之下,使用微调后嵌入向量识别出的异常值则非常独特且在数据集中极为少见。

3.2 CIFAR-100

加载CIFAR-100数据集

from renumics import spotlight
import datasets
ds = datasets.load_dataset("renumics/cifar100-outlier", split="train")
df = ds.rename_columns({"img": "image", "fine_label": "labels"}).to_pandas()
df["label_str"] = df["labels"].apply(lambda x: ds.features["fine_label"].int2str(x))
dtypes = {
    "nn_image": spotlight.Image,
    "image": spotlight.Image,
    "embedding_ft": spotlight.Embedding,
    "embedding_foundation": spotlight.Embedding,
}
spotlight.show(
    df,
    dtype=dtypes,
    layout="https://spotlight.renumics.com/resources/layout_pre_post_ft.json",
)

或查看在线演示 https://huggingface.co/spaces/renumics/cifar100-outlier 来检查异常值:
在这里插入图片描述
在这里插入图片描述

当检查包含 100 个类别的 CIFAR-100 的嵌入向量时,我们观察到即使在微调之后,与微调前的嵌入向量相比,仍有更多的类别相连。然而,嵌入空间内的结构变得更加明显和有序。
微调前的嵌入向量并未显示出明显突出于其邻近图像的异常值,表明在异常检测方面的效果有限。然而,当使用微调后的嵌入向量时,性能得到提升。在识别出的六个异常值中,前三个被有效地检测为数据集内不常见的异常。

3.3 MNIST

加载MINIST数据集

from renumics import spotlight
import datasets
ds = datasets.load_dataset("renumics/mnist-outlier", split="train")
df = ds.rename_columns({"label": "labels"}).to_pandas()
df["label_str"] = df["labels"].apply(lambda x: ds.features["label"].int2str(x))
dtypes = {
    "nn_image": spotlight.Image,
    "image": spotlight.Image,
    "embedding_ft": spotlight.Embedding,
    "embedding_foundation": spotlight.Embedding,
}
spotlight.show(
    df,
    dtype=dtypes,
    layout="https://spotlight.renumics.com/resources/layout_pre_post_ft.json",
)

或查看在线演示 huggingface.co/spaces/renumics/mnist-outlier 来检查异常值:
在这里插入图片描述
在这里插入图片描述

在 MNIST 的微调过程中,嵌入向量经历了显著变化。微调前,不同数字类别之间可能存在重叠区域,仅凭嵌入向量的邻近性难以区分它们。然而,微调后,嵌入向量展现出数字类别之间更清晰的分离。
微调前的嵌入向量只揭示出一个与邻近图像明显不同的异常值,表明其在异常检测方面的性能中等。然而,当使用微调后的嵌入向量时,异常值的检测性能提高。大约能够识别出 3 到 4 个在数据集中非常罕见的异常值。

3.4 Beans

加载Beans数据集

from renumics import spotlight
import datasets
ds = datasets.load_dataset("renumics/beans-outlier", split="train")
df = ds.to_pandas()
df["label_str"] = df["labels"].apply(lambda x: ds.features["labels"].int2str(x))
dtypes = {
    "nn_image": spotlight.Image,
    "image": spotlight.Image,
    "embedding_ft": spotlight.Embedding,
    "embedding_foundation": spotlight.Embedding,
}
spotlight.show(
    df,
    dtype=dtypes,
    layout="https://spotlight.renumics.com/resources/layout_pre_post_ft.json",
)

或查看在线演示 huggingface.co/spaces/renumics/beans-outlier 来检查异常值:
在这里插入图片描述
在这里插入图片描述
在 Beans 数据集中,经过微调后,大多数嵌入向量展现出三个类别之间的完全分离。然而,仍有一些案例显示出轻微的重叠,可能是由于某些类型的豆类之间的相似性或误分类所致。
使用微调前和微调后的嵌入向量进行的异常检测并未产生显著偏离常态的异常值。识别出的异常值在数据集中并不突出或罕见。

4 结论

总之,微调对图像分类中的嵌入向量有显著影响。微调前,嵌入向量提供通用性表征,而微调后,它们捕获了特定于当前任务的特征。
这种区别在 UMAP 可视化中清晰反映出来,其中微调后的嵌入向量展示出更加结构化的模式,某些类别完全与其他类别分离。
对于异常检测而言,使用微调后的嵌入向量可能更有效。然而,值得注意的是,基于微调所获得的概率计算异常值可能比仅依赖嵌入向量带来更好的结果。
微调前和微调后的嵌入向量各有其独特优势,应结合使用以在图像分类和分析任务中实现全面分析。

参考文献

[1] Alexey Dosovitskiy, Lucas Beyer, Alexander Kolesnikov, Dirk Weissenborn, Xiaohua Zhai, Thomas Unterthiner, Mostafa Dehghani, Matthias Minderer, Georg Heigold, Sylvain Gelly, Jakob Uszkoreit, Neil Houlsby An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale (2020), arXiv

[2] Ze Liu, Yutong Lin, Yue Cao, Han Hu, Yixuan Wei, Zheng Zhang, Stephen Lin, Baining Guo Swin Transformer: Hierarchical Vision Transformer using Shifted Windows (2021), arXiv

[3] Alex Krizhevsky, Learning Multiple Layers of Features from Tiny Images (2009), University Toronto

[4] Yann LeCun, Corinna Cortes, Christopher J.C. Burges, MNIST handwritten digit database (2010), ATT Labs [Online]

[5] Makerere AI Lab, Bean disease dataset (2020), AIR Lab Makerere University

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

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

相关文章

【MySQL 系列】MySQL 语句篇_DDL 语句

DDL&#xff08; Data Definition Language&#xff0c;数据定义语言&#xff09;用在定义或改变表的结构数据类型、表之间的链接和约束等初始化工作上。常用的语句关键字包括 CREATE、 DROP、 ALTER 等。 文章目录 1、MySQL 中的 DQL 语句2、MySQL 中库表的 DQL 语句详解2.1、…

[leetcode~dfs]1261. 在受污染的二叉树中查找元素

给出一个满足下述规则的二叉树&#xff1a; root.val 0 如果 treeNode.val x 且 treeNode.left ! null&#xff0c;那么 treeNode.left.val 2 * x 1 如果 treeNode.val x 且 treeNode.right ! null&#xff0c;那么 treeNode.right.val 2 * x 2 现在这个二叉树受到「污…

LeetCode刷题---即时食物配送 II

LeetCode题解 解题思路: 1.首先先求出每个用户首次订单表&#xff0c;将其命名为表t &#xff08;select customer_id,min(order_date) as order_datefrom Deliverygroup by customer_id&#xff09;as t2.与原表连接&#xff0c;求出在用户首次订单表中即时订单的数量的总和…

遥感云计算的一个拐点

GeoForge&#xff0c;一个值得关注的遥感大数据应用 简介 GeoForge是由Ageospatial公司开发的一个基于大语言模型(GeoLLMs)的地理空间分析平台。GeoForg的目的是使每个人都可以轻松进行地图绘制和地理空间分析&#xff0c;无论您是外行还是专家。 Geo for ChatGPT 作者团队已…

多模态大模型Claude 3正式接入集简云与语聚!对标GPT-4且支持中文

自OpenAI发布GPT-4以来&#xff0c;引发了业务模式与应用使用的巨大变革&#xff0c;掀起了各大企业对于多模态大模型的研究热潮。3月初&#xff0c;AnthropicClaude在官网正式发布Claude 3系列多模态大模型&#xff0c;据了解&#xff0c;该模型在多个维度上超越了GPT-4&#…

Midjourney指控Stability AI夜袭数据,网络风波一触即发

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

03_渲染进程调用node

我们先创建一个文件夹及文件&#xff0c;并且在 html 引入 JS 文件。 在 render.js 里面输入以下内容&#xff1a; let fs require(fs) // let是在当前代码块有效console.log(fs) // 将fs对象的内容打印到控制台供调试和查看 fs 模块&#xff1a;对文件系统进行操作&#xf…

Dubbo:常见的面试题和答案

请关注微信公众号&#xff1a;拾荒的小海螺 1、什么是 Dubbo&#xff1f;它的作用是什么&#xff1f; 答&#xff1a; Dubbo 是一款高性能的 Java RPC 框架&#xff0c;是阿里巴巴公司开源的产品&#xff0c;用于提供高性能的分布式服务框架和面向服务的架构。Dubbo 的主要作…

Pytorch基础(21)-- torch.repeat_interleave()方法

分享一下自己目前在维护的Github项目&#xff0c;由于本人博士阶段接触了一个全新的研究方向-----使用机器学习、强化学习、深度学习等方法解决组合优化问题&#xff0c;维护这个项目的目的&#xff1a; &#xff08;1&#xff09;记录自己阅读过的paper&#xff0c;同时分享一…

bash: mysqldump: command not found

问题&#xff1a;在linux上执行mysql备份的时候&#xff0c;出现此异常 mysqldump命令找不到 解决&#xff1a; 1、找到mysql目录&#xff08;找到mysql可执行命令目录&#xff09; which mysql 有图可知&#xff0c;mysql安装在&#xff1a; /usr1/local/java/mysql 2、my…

Onlyfans年龄验证/无法支付解决方案

很多小伙伴在使用的时候遇到年龄验证&#xff0c;或者需要绑定visa卡&#xff0c;这里需要注意的是提示绑定visa卡直接绑定就好了&#xff0c;记得开好你的环境&#xff0c;要不然也会出现身份验证&#xff0c;对于我们来说验证一般是不过的 1、准备好环境 2、准备好卡&#…

差分逻辑电平 --- LVDS、CML、LVPECL互连

前言 首先了解差分逻辑电平结构&#xff0c;也包括单端逻辑电平 地址&#xff1a;常见的逻辑电平_常用的逻辑电平-CSDN博客 注&#xff1a;ECL >> PECL >> LVPECL演变而来&#xff1b;QDR&#xff08;Quad Data Rate):四倍数据倍率 本文章只涉及差分逻辑电平:LVDS…

[CISCN2019 华北赛区 Day2 Web1]Hack World 不会编程的崽

sql注入之简单的盲注。 为什么很多题目总是怪怪的&#xff1f;&#xff1f;&#xff1f; 输入1返回正常&#xff0c;但是1 and 11显示异常。fuzz大法启动 这些东西被禁用了。实际上"(and)"也被禁用了&#xff0c;看来fuzz字典还是少了很多。 刚好它告诉你表名与列名…

【Java】萌新的Spring纯注解集成Dubbo学习笔记

目录 介绍 Dubbo架构 实现 介绍 Dubbo 是一款高性能的 Java RPC 框架&#xff0c;它提供了完整的服务治理解决方案&#xff0c;包括远程服务调用、负载均衡、容错机制、服务注册和发现等功能。同时&#xff0c;Dubbo 与 Zookeeper 进行集成&#xff0c;利用 Zookeeper 作为…

爬虫之矛---JavaScript基石篇5<JS混淆问题(2)>

前言: 继续上一篇:https://blog.csdn.net/m0_56758840/article/details/136633897 正文: 在这一篇讲混淆的时候,需要先清除一个概念!! 什么是Unicode~ Unicode编码 定义 Unicode是一种字符编码标准&#xff0c;用于表示世界上几乎所有的字符集。它为每个字符分配了一个唯一…

手机备忘录可以设置密码吗 能锁屏加密的备忘录

在繁忙的生活中&#xff0c;手机备忘录成了我随身携带的“小秘书”。那些关于工作的灵感、生活的琐事&#xff0c;甚至深藏心底的小秘密&#xff0c;都被我一一记录在里面。然而&#xff0c;每次当手机离开我的视线&#xff0c;或者需要借给他人使用时&#xff0c;我总会心生担…

使用Anaconda创建Python指定版本的虚拟环境

由于工作的需要和学习的需要&#xff0c;需要创建不同Python版本的虚拟环境。 比如zdppy的框架&#xff0c;主要支持的是Python3.8的版本&#xff0c;但是工作中FastAPI主要使用的是3.11的版本&#xff0c;所以本地需要两套Python环境。 决定使用Anaconda虚拟环境管理的能力&…

【Web世界探险家】打开Web世界的大门

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &…

2024 年 2 月 NFT 行业动态:加密货币飙升,NFT 市场调整

作者&#xff1a;stellafootprint.network 数据来源&#xff1a;NFT 研究页面 - Footprint Analytics 2024 年 2 月&#xff0c;加密货币与 NFT 市场显现出了复杂性。该月&#xff0c;NFT 领域的交易量达到 12 亿美元&#xff0c;环比下降了 3.7%。值得关注的是&#xff0c;包…

鸿蒙原生应用元服务开发-WebGL网页图形库开发无着色器绘制2D图形

无着色器绘制2D图形 使用WebGL开发时&#xff0c;为保证界面图形显示效果&#xff0c;请使用真机运行。 此场景为未使用WebGL绘制的2D图形&#xff08;CPU绘制非GPU绘制&#xff09;。开发示例如下&#xff1a; 1.创建页面布局。index.hml示例如下&#xff1a; <div class…