2024年2月5日-2月11日周报

论文阅读

  • 1. 本周计划
  • 2. 完成情况
    • 2.1 论文摘要
    • 2.2 网络结构
    • 2.3 损失函数
    • 2.4 优化器
    • 2.5 代码
      • 2.5.1 代码结果
      • 2.5.2 代码大致流程
  • 4. 总结及收获
  • 4. 下周计划

1. 本周计划

阅读论文《Data-Driven Seismic Waveform Inversion: A Study on the Robustness and Generalization》并实现论文代码,学习代码套路

2. 完成情况

2.1 论文摘要

Full-waveform inversion is an important and widely used method to reconstruct subsurface velocity images. Waveform inversion is a typical nonlinear and ill-posed inverse problem. Existing physics-driven computational methods for solving waveform inversion suffer from the cycle-skipping and local-minima issues, and do not mention that solving waveform inversion is computationally expensive. In recent years, data-driven methods become a promising way to solve the waveform-inversion problem. However, most deep-learning frameworks suffer from the generalization and overfitting issue. In this article, we developed a real-time data-driven technique and we call it VelocityGAN, to reconstruct accurately the subsurface velocities. Our VelocityGAN is built on a generative adversarial network (GAN) and trained end to end to learn a mapping function from the raw seismic waveform data to the velocity image. Different from other encoder–decoder-based data-driven seismic waveform-inversion approaches, our VelocityGAN learns regularization from data and further imposes the regularization to the generator so that inversion accuracy is improved. We further develop a transfer-learning strategy based on VelocityGAN to alleviate the generalization issue. A series of experiments is conducted on the synthetic seismic reflection data to evaluate the effectiveness, efficiency, and generalization of VelocityGAN. We not only compare it with the existing physics-driven approaches and datadriven frameworks but also conduct several transfer-learning experiments. The experimental results show that VelocityGAN achieves the state-of-the-art performance among the baselines and can improve the generalization results to some extent.

在摘要部分,论文作者提出了解决FWI的物理驱动计算方法主要面临的问题

  • 周期跳跃问题
  • 局部最小值问题
  • 计算成本很高

论文中提出了一种实时的数据驱动技术VelocityGAN,利用生成对抗网络(GAN)从原始地震波形数据到速度图像的映射函数,论文主要的创新点:

  • 实时数据驱动技术
  • 从数据中学习正则化,并将正则化施加给生成器,
  • 基于VelocityGAN的迁移学习策略来缓解泛化问题

2.2 网络结构

VelocityGan 由两部分组成:生成器和判别器;其中生成器是一种编码器-解码器结构,将原始地震数据映射到速度图像;鉴别器是一个卷积神经网络(CNN),对真实或假的速度图像进行分类.如下是网络结构图:
在这里插入图片描述

  • 生成器Generator
    在论文中生成器的结构如下表TABLE I所示,这个结构其实和InversionNet的网络结构很相似
    在这里插入图片描述
    在这里插入图片描述
    但是在最新的代码中,输入的图像为 5 ∗ 1000 ∗ 70 5*1000*70 5100070,输出为 1 ∗ 70 ∗ 70 1*70*70 17070 为啥要改变尺寸大小? 为啥要改变尺寸大小? 为啥要改变尺寸大小?

  • 判别器Generator

Discriminator: Similar to Radford et al. [35], we adapt our discriminator from a CNN architecture. In particular, it consists of five convolution blocks, a global average pooling layer, and fully connected layers. Each convolutional block involves a combination of Convolutional, BatchNormalization, LeakyReLU, and MaxPooling layers. We apply the “PatchGAN” classifier [18] in the discriminator to capture local style statistics. We set the patch size as 4 and calculate the mean loss value of all patches in an image

在论文中,判别器主要由五个卷积块、一个全局平均池化层和全连接层组成,还应用了PatchGAN分类器,而在现在的代码中发现已经取消了PathGan.也没由全连接层和平均池化层了。 这貌似就和论文中的出入较大。(patchGan: 主要用在判别器上,比如的GAN网络输入为一张图像输出一个单一的判别结果true or false, 而patchGan使用了局部感受野,转为对图像局部区域进行判别,若输入一张图像,输出就是一个N*N的矩阵。)
在这里插入图片描述

2.3 损失函数

  • 生成器损失函数
    def criterion_g(pred, gt, model_d=None): # 预测值和真实值?loss_g1v = l1loss(pred, gt)loss_g2v = l2loss(pred, gt)loss = args.lambda_g1v * loss_g1v + args.lambda_g2v * loss_g2vif model_d is not None:loss_adv = -torch.mean(model_d(pred)) loss += args.lambda_adv * loss_advreturn loss, loss_g1v, loss_g2v

生成器损失函数公式:
在这里插入图片描述

  • L1loss函数是MAE(MAE loss performs better on revealing the geological interfaces); L2loss函数是MSE(MSE loss is good at capturing the geological faults)

  • loss 总损失值是根据权重系数计算得到。(平衡损失项的重要性)

  • loss_adv:在生成器损失函数中,若提供了判别器,则需要计算对抗损失项loss_adv,首先需要将预测值输入到判别器模型中得到判别结果。再将判别结果取负均值。 这样的目的是最大化生成器生成图像被判别器判别为真的概率。即负均值越接近0,则说明生成的图像被判别为真实图像的概率越高,从而生成器的性能越好。并最后将这个损失函数加入到总损失函数 中。
    最后返回生成器相关的损失函数

  • 判别器损失函数

class Wasserstein_GP(nn.Module):def __init__(self, device, lambda_gp):super(Wasserstein_GP, self).__init__()self.device = deviceself.lambda_gp = lambda_gpdef forward(self, real, fake, model):gradient_penalty = self.compute_gradient_penalty(model, real, fake)loss_real = torch.mean(model(real))loss_fake = torch.mean(model(fake))loss = -loss_real + loss_fake + gradient_penalty * self.lambda_gpreturn loss, loss_real-loss_fake, gradient_penaltydef compute_gradient_penalty(self, model, real_samples, fake_samples):alpha = torch.rand(real_samples.size(0), 1, 1, 1, device=self.device) # 生成一个张量值,值是在 [0, 1) 范围内随机interpolates = (alpha * real_samples + ((1 - alpha) * fake_samples)).requires_grad_(True) # 线性插值生成样本并跟踪这个张量的梯度d_interpolates = model(interpolates) #获取插值样本在生成器上的,以便计算梯度惩罚gradients = autograd.grad(outputs=d_interpolates,inputs=interpolates,grad_outputs=torch.ones(real_samples.size(0), d_interpolates.size(1)).to(self.device),create_graph=True,retain_graph=True,only_inputs=True,)[0]gradients = gradients.view(gradients.size(0), -1)gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean()return gradient_penalty

判别器的损失函数在论文中描述到

A Wasserstein GAN (WGAN) with gradient penalty [14] has been proven to be robust of a wide variety of generator architectures. Considering the modified structure in our generator, we use Wasserstein loss with a gradient penalty to distinguish the real and generated velocity maps

  • WGAN(Wasserstein GAN)
    wasserstein距离:度量两个概率分布之间差异的距离度量。参考文章
  • 判别器损失函数
    在这里插入图片描述
    注:在GAN训练中,若把判别器训练得太好反而在实验中生成器会完全学不动。

P g P_g Pg生成器速度模型预测, P r P_r Pr真实速度模型预测, P x ^ P_{\hat{x}} Px^ P g P_g Pg P r P_r Pr之间的随机样本, E D ( ⋅ ) ED(·) ED()代表判别器输出的期望值

  • loss_real 真实样本在判别器上的损失的平均值
  • loss_fake 生成样本在判别器上的损失的平均值
  • gradient_penalty 梯度惩罚项
    最后计判别器的损失函数

2.4 优化器

  • Adam优化器
    Adam算法是自适应学习率的优化算法,尤其在Transforms架构出现后,调整学习率是很困难的,使用SGD类似算法可能效果没有那么有用,采用Adam算法,结合了一阶矩阵动量和二阶矩阵动量来调整学习率。
    input : γ (lr) , β 1 , β 2 (betas) , θ 0 (params) , f ( θ ) (objective) λ (weight decay) , amsgrad , maximize initialize : m 0 ← 0 ( first moment) , v 0 ← 0 (second moment) , v 0 ^ m a x ← 0 for t = 1 to … do if maximize : g t ← − ∇ θ f t ( θ t − 1 ) else g t ← ∇ θ f t ( θ t − 1 ) if λ ≠ 0 g t ← g t + λ θ t − 1 m t ← β 1 m t − 1 + ( 1 − β 1 ) g t v t ← β 2 v t − 1 + ( 1 − β 2 ) g t 2 m t ^ ← m t / ( 1 − β 1 t ) v t ^ ← v t / ( 1 − β 2 t ) if a m s g r a d v t ^ m a x ← m a x ( v t ^ m a x , v t ^ ) θ t ← θ t − 1 − γ m t ^ / ( v t ^ m a x + ϵ ) else θ t ← θ t − 1 − γ m t ^ / ( v t ^ + ϵ ) r e t u r n θ t \begin{aligned} &\rule{110mm}{0.4pt} \\ &\textbf{input} : \gamma \text{ (lr)}, \beta_1, \beta_2 \text{ (betas)},\theta_0 \text{ (params)},f(\theta) \text{ (objective)} \\ &\hspace{13mm} \lambda \text{ (weight decay)}, \: \textit{amsgrad}, \:\textit{maximize} \\ &\textbf{initialize} : m_0 \leftarrow 0 \text{ ( first moment)}, v_0\leftarrow 0 \text{ (second moment)},\: \widehat{v_0}^{max}\leftarrow 0\\[-1.ex] &\rule{110mm}{0.4pt} \\ &\textbf{for} \: t=1 \: \textbf{to} \: \ldots \: \textbf{do} \\ &\hspace{5mm}\textbf{if} \: \textit{maximize}: \\ &\hspace{10mm}g_t \leftarrow -\nabla_{\theta} f_t (\theta_{t-1}) \\ &\hspace{5mm}\textbf{else} \\ &\hspace{10mm}g_t \leftarrow \nabla_{\theta} f_t (\theta_{t-1}) \\ &\hspace{5mm}\textbf{if} \: \lambda \neq 0 \\ &\hspace{10mm} g_t \leftarrow g_t + \lambda \theta_{t-1} \\ &\hspace{5mm}m_t \leftarrow \beta_1 m_{t-1} + (1 - \beta_1) g_t \\ &\hspace{5mm}v_t \leftarrow \beta_2 v_{t-1} + (1-\beta_2) g^2_t \\ &\hspace{5mm}\widehat{m_t} \leftarrow m_t/\big(1-\beta_1^t \big) \\ &\hspace{5mm}\widehat{v_t} \leftarrow v_t/\big(1-\beta_2^t \big) \\ &\hspace{5mm}\textbf{if} \: amsgrad \\ &\hspace{10mm}\widehat{v_t}^{max} \leftarrow \mathrm{max}(\widehat{v_t}^{max}, \widehat{v_t}) \\ &\hspace{10mm}\theta_t \leftarrow \theta_{t-1} - \gamma \widehat{m_t}/ \big(\sqrt{\widehat{v_t}^{max}} + \epsilon \big) \\ &\hspace{5mm}\textbf{else} \\ &\hspace{10mm}\theta_t \leftarrow \theta_{t-1} - \gamma \widehat{m_t}/ \big(\sqrt{\widehat{v_t}} + \epsilon \big) \\ &\rule{110mm}{0.4pt} \\[-1.ex] &\bf{return} \: \theta_t \\[-1.ex] &\rule{110mm}{0.4pt} \\[-1.ex] \end{aligned} input:γ (lr),β1,β2 (betas),θ0 (params),f(θ) (objective)λ (weight decay),amsgrad,maximizeinitialize:m00 ( first moment),v00 (second moment),v0 max0fort=1todoifmaximize:gtθft(θt1)elsegtθft(θt1)ifλ=0gtgt+λθt1mtβ1mt1+(1β1)gtvtβ2vt1+(1β2)gt2mt mt/(1β1t)vt vt/(1β2t)ifamsgradvt maxmax(vt max,vt )θtθt1γmt /(vt max +ϵ)elseθtθt1γmt /(vt +ϵ)returnθt
    γ \gamma γ:学习率; β 1 , β 2 \beta_1, \beta_2 β1,β2 第一个,第二个动量的指数衰减率; θ 0 \theta_0 θ0:模型参数的初始值; λ \lambda λ 权重衰减参数(控制参数更新的正则化项); maximize \textit{maximize} maximize:是否为最大化问题
    初始化一阶动量 m 0 m_0 m0和二阶动量 v 0 v_0 v0为0, 在每次迭代中,计算梯度 g t g_t gt,若 λ \lambda λ 不为0将其添加到梯度中,更新一阶动量 m t m_t mt和二阶动量 v t v_t vt,再对一阶动量 m t m_t mt和二阶动量 v t v_t vt进行偏差矫正。(为什么要进行偏差矫正? 因为在初始化时一阶动量 m 0 m_0 m0和二阶动量 v 0 v_0 v0为0,会导致一阶动量和二阶动量的估计存在较大的偏差,影响到优化算法的收敛速度和稳定性,故加入了偏差矫正项 1 − β 1 t 1-\beta_{1}^{t} 1β1t 1 − β 2 t 1-\beta_{2}^{t} 1β2t, 随着迭代次数增加,偏差矫正项逐渐减小)
  • AdamW优化器
    AdamW算法相比于Adam算法,引入更加稳定的权重衰减方式,AdamW 在计算梯度时会将权重衰减项直接加到梯度上,而不是像 Adam 算法那样在参数更新时再单独处理。在VelocityGan中,优化器采用了AdamW优化器进行参数优化
    input : γ (lr) , β 1 , β 2 (betas) , θ 0 (params) , f ( θ ) (objective) , ϵ (epsilon) λ (weight decay) , amsgrad , maximize initialize : m 0 ← 0 (first moment) , v 0 ← 0 ( second moment) , v 0 ^ m a x ← 0 for t = 1 to … do if maximize : g t ← − ∇ θ f t ( θ t − 1 ) else g t ← ∇ θ f t ( θ t − 1 ) θ t ← θ t − 1 − γ λ θ t − 1 m t ← β 1 m t − 1 + ( 1 − β 1 ) g t v t ← β 2 v t − 1 + ( 1 − β 2 ) g t 2 m t ^ ← m t / ( 1 − β 1 t ) v t ^ ← v t / ( 1 − β 2 t ) if a m s g r a d v t ^ m a x ← m a x ( v t ^ m a x , v t ^ ) θ t ← θ t − γ m t ^ / ( v t ^ m a x + ϵ ) else θ t ← θ t − γ m t ^ / ( v t ^ + ϵ ) r e t u r n θ t \begin{aligned} &\rule{110mm}{0.4pt} \\ &\textbf{input} : \gamma \text{(lr)}, \: \beta_1, \beta_2 \text{(betas)}, \: \theta_0 \text{(params)}, \: f(\theta) \text{(objective)}, \: \epsilon \text{ (epsilon)} \\ &\hspace{13mm} \lambda \text{(weight decay)}, \: \textit{amsgrad}, \: \textit{maximize} \\ &\textbf{initialize} : m_0 \leftarrow 0 \text{ (first moment)}, v_0 \leftarrow 0 \text{ ( second moment)}, \: \widehat{v_0}^{max}\leftarrow 0 \\[-1.ex] &\rule{110mm}{0.4pt} \\ &\textbf{for} \: t=1 \: \textbf{to} \: \ldots \: \textbf{do} \\ &\hspace{5mm}\textbf{if} \: \textit{maximize}: \\ &\hspace{10mm}g_t \leftarrow -\nabla_{\theta} f_t (\theta_{t-1}) \\ &\hspace{5mm}\textbf{else} \\ &\hspace{10mm}g_t \leftarrow \nabla_{\theta} f_t (\theta_{t-1}) \\ &\hspace{5mm} \theta_t \leftarrow \theta_{t-1} - \gamma \lambda \theta_{t-1} \\ &\hspace{5mm}m_t \leftarrow \beta_1 m_{t-1} + (1 - \beta_1) g_t \\ &\hspace{5mm}v_t \leftarrow \beta_2 v_{t-1} + (1-\beta_2) g^2_t \\ &\hspace{5mm}\widehat{m_t} \leftarrow m_t/\big(1-\beta_1^t \big) \\ &\hspace{5mm}\widehat{v_t} \leftarrow v_t/\big(1-\beta_2^t \big) \\ &\hspace{5mm}\textbf{if} \: amsgrad \\ &\hspace{10mm}\widehat{v_t}^{max} \leftarrow \mathrm{max}(\widehat{v_t}^{max}, \widehat{v_t}) \\ &\hspace{10mm}\theta_t \leftarrow \theta_t - \gamma \widehat{m_t}/ \big(\sqrt{\widehat{v_t}^{max}} + \epsilon \big) \\ &\hspace{5mm}\textbf{else} \\ &\hspace{10mm}\theta_t \leftarrow \theta_t - \gamma \widehat{m_t}/ \big(\sqrt{\widehat{v_t}} + \epsilon \big) \\ &\rule{110mm}{0.4pt} \\[-1.ex] &\bf{return} \: \theta_t \\[-1.ex] &\rule{110mm}{0.4pt} \\[-1.ex] \end{aligned} input:γ(lr),β1,β2(betas),θ0(params),f(θ)(objective),ϵ (epsilon)λ(weight decay),amsgrad,maximizeinitialize:m00 (first moment),v00 ( second moment),v0 max0fort=1todoifmaximize:gtθft(θt1)elsegtθft(θt1)θtθt1γλθt1mtβ1mt1+(1β1)gtvtβ2vt1+(1β2)gt2mt mt/(1β1t)vt vt/(1β2t)ifamsgradvt maxmax(vt max,vt )θtθtγmt /(vt max +ϵ)elseθtθtγmt /(vt +ϵ)returnθt
    ϵ \epsilon ϵ 用于数值稳定性的小常数,通常取一个很小的值(主要是避免避免分母为零的情况)
    对于Adam算法,权重衰减项是在计算梯度后与梯度相加的;对于AdamW算法,权重衰减项是在更新模型参数之前与当前模型参数相乘。

2.5 代码

2.5.1 代码结果

因为是自己电脑上跑的,准备的训练数据集比较少,所以这个结果肯定不是很准确的。
在这里插入图片描述

2.5.2 代码大致流程

训练网络结构(后续代码的一个大致流程):以gan_train.py为例

  • 准备数据集:自定义的数据集FWIDataset,通过读取txt文件中的npy文件路径去下载数据集
  • 建立数据加载器(数据加载器:为网络提供不同的加载形式)
  • 搭建神经网络模型:在本论文的代码中,有两种网络模型,一种是生成器Generate(和InversionNet差不多,代码调用都一样),一种是判别器discriminater(是一种CNN神经网络结构)
  • 创建损失函数 (生成器和判别器损失函数)
  • 创建优化器 (采用的AdamW优化算法)
  • 训练模型
  • 测试模型
  • 模型保存

4. 总结及收获

  1. 更为熟悉网络结构的一个完整流程。而我认为后续自己论文的主要出发点会在网络结构,损失函数和优化器上做创新。
  2. VelocityGan 是基于Gan网络进行的,在读完论文后,首先,VelocityGan的生成器和InversionNet的网络结构很相似。从最开始了解CNN一个传统的神经网络架构,主要就是卷积层,池化层到全连接层。到后续看到的一个FCN网络,将全连接层转换为卷积层,这样就可以保留空间的特征信息,添加反卷积层进行上采样,到后来了解到U-Net网络架构,一个对称的网络结构,也是一个端到端网络,U-Net的提出是在图像领域方面的,而后续看到我们研究内容相关的一些论文也是在这U-Net网络结构上进行创新,受到了很多启发(所以我们的确是可以去探索图像领域方面的优秀架构),如FCNVMB,他则是基于U-Net架构基础上创新。而我们看的这篇论文,他则可以说是在InversionNet网络架构的创新。 其次就是VelocityGan中的损失函数的设计,针对生成器和判别器,损失函数的设计不同,对于生成器,损失函数主要由对抗性损失和内容损失组成,对抗损失主要和判别器的判别结果有关,内容损失是和地质信息有关(主要是MSE和MSE); 对于判别器的损失函数主要是采用带有梯度惩罚的WGAN。最后优化器的选择,我想我更会倾向于ADAM和ADAMW中进行选择。
  3. 对于论文实验中提到各种模型的对比,以及在之前看论文中,都会有对比实验,而对比选择的模型是如何选择的?我不是很清楚。

4. 下周计划

  1. 主要任务是看这篇论文《Deep-Learning Full-Waveform Inversion Using Seismic Migration Images》
  2. 去看看图像相关论文的网络架构,学习一下。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TELNET 远程终端协议

远程终端协议 TELNET TELNET 是一个简单的远程终端协议,也是互联网的正式标准。 用户用 TELNET 就可在其所在地通过 TCP 连接注册(即登录)到远地的另一个主机上(使用主机名或 IP 地址)。 TELNET 能将用户的击键传到…

为什么总有人觉得前端很简单?尤其是水平半瓶水的人。

造成这个印象的原因很多,贝格前端工场结合自己的经验,为大家揭开这个谜底。低端的前端确实简单,但是高级阶段确实不简单。 缺乏深入了解: 有些人可能只是对前端开发有一些浅显的了解,没有深入研究过前端开发的技术和知…

T-Sql 也能更新修改查询JSON?

今天看见一个澳洲项目里面使用了 JSON_VALUE 这样的函数解析 JSON 我倍感诧异,我印象当中Sql Server并不支持JOSN的相关操作,他最多只把JSON当成一个字符串来存储,更不要说去解析,查询和更新了 我随后查询了下此函数,…

第三节 zookeeper基础应用与实战2

目录 1. Watch事件监听 1.1 一次性监听方式:Watcher 1.2 Curator事件监听机制 2. 事务&异步操作演示 2.1 事务演示 2.2 异步操作 3. Zookeeper权限控制 3.1 zk权限控制介绍 3.2 Scheme 权限模式 3.3 ID 授权对象 3.4 Permission权限类型 3.5 在控制台…

牛客周赛 Round 32 解题报告 | 珂学家 | 状压 + 前缀和异或map技巧

前言 整体评价 属于补题,大致看了下,题都很典。 欢迎关注 珂朵莉 牛客周赛专栏 珂朵莉 牛客小白月赛专栏 A. 小红的 01 背包 思路: 数学题 v, x, y list(map(int, input().split()))print (v // x * y)B. 小红的 dfs 思路: 枚举 其实横竖都有dfs…

Java编程练习之类的继承

1.创建银行卡类,并分别设计两个储蓄卡和信用卡子类。 import javax.swing.plaf.BorderUIResource;import java.util.Scanner;class Card {int Id; //银行卡;int password; //密码;double balance2000; //账户存款金额;String A…

软件安全测试报告如何编写?权威的安全测试报告如何获取?

软件安全测试报告是一份详尽的文件,它主要通过对软件进行全面、系统的测试,评估软件的安全性,并在测试结束后起草编写的报告。该报告能清晰地展示出软件的各项安全风险以及潜在威胁,为用户提供安全方面的决策依据,并帮…

指针的基本含义及其用法

1.前言 在学习C语言的时候,我们会经常接触一个概念,指针和地址,关于这两个概念很多人并不能理解地十分透彻,接下来我将详细介绍一下这两者的概念 2.地址 我们知道计算机的上CPU(中央处理器)在处理数据的时…

Java常用类与基础API--String的理解与不可变性

文章目录 一、字符串相关类之不可变字符序列:String(1)对String类的理解(以JDK8为例说明)1、环境2、类的声明3、内部声明的属性 (2)String的特性(3)字符串常量的存储位置1、举例2、String的存储…

1. pick gtk dll 程序的制作

文章目录 前言预览细节要点初始窗口尺寸提示音快速提示信息对话框AlertDialog鼠标移入移出事件布局与父子控件关系图片 后续源码及资源 前言 在之前的打包测试中我提到了需要一个挑选dll的程序于是我打算用Gtk来制作这个程序 预览 细节要点 初始窗口尺寸 只有主窗口有set_d…

windows11 MSYS2下载安装教程

MSYS2 可以理解为在windows平台上模拟linux编程环境的开源工具集 当前环境:windows11 1. 下载 官网地址可下载最新版本,需要科学上网 https://www.msys2.org/ 2. 安装 按照正常安装软件流程一路next就可以 打开 3. 配置环境 网上很多教程提到需…

【HarmonyOS 4.0 应用开发实战】ArkTS 快速入门之常用属性

个人名片: 🐼作者简介:一名大三在校生,喜欢AI编程🎋 🐻‍❄️个人主页🥇:落798. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️…