PyTorch深度学习实战(17)——多任务学习

PyTorch深度学习实战(17)——多任务学习

    • 0. 前言
    • 1. 多任务学习
      • 1.1 多任务学习基本概念
      • 1.2 多任务学习优势
    • 2. 模型与数据集分析
      • 2.1 模型分析
      • 2.2 数据集介绍
    • 3. 实现年龄估计和性别分类
    • 小结
    • 系列链接

0. 前言

多任务学习( Multi-Task Learning, MTL )是一种常见的机器学习方法,用于同时处理和学习多个相关任务。在传统的单任务学习中,通常需要为每个任务训练一个独立的模型,而多任务学习则通过共享模型的特征表示来同时学习多个任务。例如,根据我们在猫狗分类和关键点检测中所学习的,我们能够训练神经网络对给定图像中的人物年龄或性别进行单独的预测,但是,我们尚未考虑如何根据单个图像同时预测年龄和性别。根据同一图像进行多项预测在实际场景中非常重要,因为同一张照片可能被用于同时进行多项预测。在本节中,将介绍多任务学习的基本概念,并构建模型同时执行多个不同类型的任务,包括预测人物的性别和年龄。

1. 多任务学习

1.1 多任务学习基本概念

多任务学习能够同时处理和学习多个相关任务,将多个互相关联的任务联合训练,从而提高模型的泛化能力和效果,使用一个或多个输入用于预测几个不同输出。在多任务学习中,多个不同的任务使用同一网络模型,模型同时学习多个任务的知识,从而更好地利用训练数据进行训练。例如,在自动驾驶中,模型需要识别障碍物、规划路线、提供适量的油门/制动和转向等,通过考虑相同的输入集(来自多个传感器)实时完成这些任务。

1.2 多任务学习优势

多任务学习的优势在于任务之间的相互促进和共享知识。通过将任务之间的相关性和依赖关系引入到共享模型中,可以提高整体性能、泛化能力和数据效率:

  • 提升性能:通过共享底层特征表示,模型可以从不同任务中学习到更通用的特征,可以通过共享知识来提高任务的性能
  • 泛化能力增强:多任务学习可以通过在不同但相关的任务上进行联合训练,帮助模型学习到更广泛、更全局的特征,从而提高其泛化能力
  • 数据效率提高:当我们面对数据稀缺的情况时,多任务学习可以通过共享模型来利用任务之间的数据共享,提高数据的有效利用率
  • 鲁棒性增强:多任务学习可以通过在不同任务之间共享特征,提高模型的鲁棒性和抗干扰能力。当一个任务面临异常或缺失数据时,其他任务可以提供额外的信息帮助模型更好地应对

多任务学习的应用领域广泛,如自然语言处理、计算机视觉、语音识别等,通过多任务学习可以共享底层的语义表示,提高任务的整体效果。

2. 模型与数据集分析

2.1 模型分析

在本节中,我们将学习如何在单个前向传递中同时进行连续值预测和离散值(类别)预测。构建模型策略如下:

  • 导入相关库
  • 获取包含人物图像、性别和年龄信息的数据集
  • 预处理数据并创建训练和测试数据集
  • 构建模型:
    • 特征提取层使用预训练VGG19模型
    • 创建两个分支独立层,其中一层对应于年龄估计,另一层对应于性别分类
    • 每个输出分支都有不同的损失函数,年龄是一个连续值(计算 msemae 损失),而性别是一个分类值(计算交叉熵损失)
    • 对年龄估计损失和性别分类损失加权求
    • 通过反向传播优化权重值来最小化整体损失
  • 训练模型并预测新图像

2.2 数据集介绍

为了构建多分类模型,我们将使用 FairFace 数据集,FairFace 数据集是一个用于面部分析和人脸识别的多样性人脸数据集,由 FairFace 团队于 2019 年发布。FairFace 数据集的目标是提供一个包含多种族、性别和年龄的真实世界人脸图像集合,以便研究人员可以训练和评估面部识别算法在多样性群体中的表现。该数据集包含了 87,022 个标记的人脸图像,下载使用此数据集,提取码:cr6n

3. 实现年龄估计和性别分类

接下来,使用 PyTorch 实现多任务学习模型。

(1) 导入相关库:

import torch
import numpy as np, cv2, pandas as pd, time
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, models
device = 'cuda' if torch.cuda.is_available() else 'cpu'

(2) 加载下载完成的数据集,查看数据结构:

trn_df = pd.read_csv('fairface-labels-train.csv')
val_df = pd.read_csv('fairface-labels-val.csv')
print(trn_df.head())

输出结果如下所示:

          file  age  gender        race  service_test
0  train/1.jpg   59    Male  East Asian          True
1  train/2.jpg   39  Female      Indian         False
2  train/3.jpg   11  Female       Black         False
3  train/4.jpg   26  Female      Indian          True
4  train/5.jpg   26  Female      Indian          True

(3) 构建 GenderAgeClass 类,以文件名作为输入并返回相应的图像、性别和年龄。其中,年龄需要缩放,因为这是一个连续的数字,缩放数据以避免梯度消失,然后在后处理期间重新进行还原。

__init__ 方法中以图像的文件路径作为输入:

IMAGE_SIZE = 224
class GenderAgeClass(Dataset):def __init__(self, df, tfms=None):self.df = dfself.normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])

使用 __len__ 方法返回输入中图像数量:

    def __len__(self):return len(self.df)

定义 __getitem__ 方法,获取给定位置 ix 的图像信息:

    def __getitem__(self, ix):f = self.df.iloc[ix].squeeze()file = f.filegen = f.gender == 'Female'age = f.ageim = cv2.imread(file)im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)return im, age, gen

编写图像预处理函数,包括调整图像大小、调整图像通道以及图像归一化:

    def preprocess_image(self, im):im = cv2.resize(im, (IMAGE_SIZE, IMAGE_SIZE))im = torch.tensor(im).permute(2,0,1)im = self.normalize(im/255.)return im[None]

创建 collate_fn 方法,该方法用于对批数据执行以下预处理:

  • 使用 process_image 方法处理每个图像
  • 缩放年龄(除以数据集中存在的最大年龄值——80),令所有值都介于 01 之间
  • 将性别转换为浮点值
  • 将图像、年龄和性别转换为张量对象并返回
    def collate_fn(self, batch):'preprocess images, ages and genders'ims, ages, genders = [], [], []for im, age, gender in batch:im = self.preprocess_image(im)ims.append(im)ages.append(float(int(age)/80))genders.append(float(gender))ages, genders = [torch.tensor(x).to(device).float() for x in [ages, genders]]ims = torch.cat(ims).to(device)return ims, ages, genders

(4) 定义训练和验证数据集以及数据加载器。

创建数据集:

trn = GenderAgeClass(trn_df)
val = GenderAgeClass(val_df)

构建数据加载器:

train_loader = DataLoader(trn, batch_size=32, shuffle=True, drop_last=True, collate_fn=trn.collate_fn)
test_loader = DataLoader(val, batch_size=32, collate_fn=val.collate_fn)
a,b,c, = next(iter(train_loader))
print(a.shape, b.shape, c.shape)
# torch.Size([32, 3, 224, 224]) torch.Size([32]) torch.Size([32])

(5) 定义模型、损失函数和优化器。

在函数 get_model() 中,加载预训练 VGG16 模型:

def get_model():model = models.vgg16(pretrained = True)

冻结加载的模型(指定参数 param.requires_grad = False):

    for param in model.parameters():param.requires_grad = False

使用自定义网络层替换 avgpool 层:

    model.avgpool = nn.Sequential(nn.Conv2d(512,512, kernel_size=3),nn.MaxPool2d(2),nn.ReLU(),nn.Flatten())

构建名为 ageGenderClassifier 的神经网络类,以创建包含两个输出分支的神经网络:

    class ageGenderClassifier(nn.Module):def __init__(self):super(ageGenderClassifier, self).__init__()

定义中间层 intermediate

            self.intermediate = nn.Sequential(nn.Linear(2048,512),nn.ReLU(),nn.Dropout(0.4),nn.Linear(512,128),nn.ReLU(),nn.Dropout(0.4),nn.Linear(128,64),nn.ReLU(),)

定义 age_classifiergender_classifier

            self.age_classifier = nn.Sequential(nn.Linear(64, 1),nn.Sigmoid())self.gender_classifier = nn.Sequential(nn.Linear(64, 1),nn.Sigmoid())

在以上代码中,年龄预测层 age_classifier 和性别预测层 gender_classifier 均使用 sigmoid 激活,因为年龄输出是一个介于 01 之间的值,且性别输出是 01

定义前向传递方法 forward,使用网络层 age_classifiergender_classifier

        def forward(self, x):x = self.intermediate(x)age = self.age_classifier(x)gender = self.gender_classifier(x)return gender, age

使用自定义网络替换 VGG16 预训练模型分类器模块:

    model.classifier = ageGenderClassifier()

定义性别分类(二元交叉熵损失)和年龄预测( L1 损失)的损失函数。定义优化器并返回模型、损失函数和优化器:

    gender_criterion = nn.BCELoss()age_criterion = nn.L1Loss()loss_functions = gender_criterion, age_criterionoptimizer = torch.optim.Adam(model.parameters(), lr= 1e-4)return model.to(device), loss_functions, optimizer

调用 get_model() 函数初始化变量中的值:

model, loss_functions, optimizer = get_model()

(6) 定义函数在训练数据集上进行训练并在测试数据集上进行验证。train_batch 方法将图像、性别、年龄、模型、优化器和损失函数的实际值作为输入来计算总损失。

使用适当的输入参数定义 train_batch() 方法:

def train_batch(data, model, optimizer, criteria):

指定训练模型,将优化器重置为 zero_grad,并计算年龄和性别的预测值:

    model.train()ims, age, gender = dataoptimizer.zero_grad()pred_gender, pred_age = model(ims) 

在计算年龄估计和性别分类对应的损失之前,获取用于年龄估计和性别分类的损失函数:

    gender_criterion, age_criterion = criteriagender_loss = gender_criterion(pred_gender.squeeze(), gender)age_loss = age_criterion(pred_age.squeeze(), age)

通过将 gender_lossage_loss 相加来计算整体损失,并通过优化模型的可训练权重执行反向传播以减少整体损失:

    total_loss = gender_loss + age_losstotal_loss.backward()optimizer.step()return total_loss

validate_batch() 方法将图像、模型和损失函数以及年龄和性别的实际值作为输入,计算年龄和性别的预测值以及损失值。

使用所需的输入参数定义 vaidate_batch 函数:

def validate_batch(data, model, criteria):

指定模型处于评估阶段,因此不需要进行梯度计算:

    model.eval()ims, age, gender = datawith torch.no_grad():pred_gender, pred_age = model(ims)

计算年龄和性别预测对应的损失值( gender_lossage_loss)。压缩预测形状 (batch size, 1),以便将其整形为与目标值相同的形状( batch size):

    gender_criterion, age_criterion = criteriagender_loss = gender_criterion(pred_gender.squeeze(), gender)age_loss = age_criterion(pred_age.squeeze(), age)

计算整体损失,最终预测的性别类别( pred_gender)、性别预测准确率和年龄估计误差:

    total_loss = gender_loss + age_losspred_gender = (pred_gender > 0.5).squeeze()gender_acc = (pred_gender == gender).float().sum()age_mae = torch.abs(age - pred_age).float().sum()return total_loss, gender_acc, age_mae

(7) 训练模型。

定义用于存储训练和测试损失值的列表,并指定训练 epoch 数:

model, criteria, optimizer = get_model()
val_gender_accuracies = []
val_age_maes = []
train_losses = []
val_losses = []n_epochs = 10
best_test_loss = 1000
start = time.time()

在每个 epoch 开始时重新初始化训练和测试损失值:

for epoch in range(n_epochs):epoch_train_loss, epoch_test_loss = 0, 0val_age_mae, val_gender_acc, ctr = 0, 0, 0_n = len(train_loader)

遍历训练数据加载器( train_loader )并训练模型:

    for ix, data in enumerate(train_loader):# if ix == 100: breakloss = train_batch(data, model, optimizer, criteria)epoch_train_loss += loss.item()

遍历测试数据加载器并计算性别及年龄预测准确率:

    for ix, data in enumerate(test_loader):# if ix == 10: breakloss, gender_acc, age_mae = validate_batch(data, model, criteria)epoch_test_loss += loss.item()val_age_mae += age_maeval_gender_acc += gender_accctr += len(data[0])

计算年龄预测和性别分类的整体准确率:

    val_age_mae /= ctrval_gender_acc /= ctrepoch_train_loss /= len(train_loader)epoch_test_loss /= len(test_loader)

打印每个 epoch 结束时模型性能指标:

    elapsed = time.time()-startbest_test_loss = min(best_test_loss, epoch_test_loss)print('{}/{} ({:.2f}s - {:.2f}s remaining)'.format(epoch+1, n_epochs, time.time()-start, (n_epochs-epoch)*(elapsed/(epoch+1))))info = f'''Epoch: {epoch+1:03d}\tTrain Loss: {epoch_train_loss:.3f}\tTest: {epoch_test_loss:.3f}\tBest Test Loss: {best_test_loss:.4f}'''info += f'\nGender Accuracy: {val_gender_acc*100:.2f}%\tAge MAE: {val_age_mae:.2f}\n'print(info)

存储每个 epoch 中测试数据集的年龄和性别预测准确率:

    val_gender_accuracies.append(val_gender_acc)val_age_maes.append(val_age_mae)

(8) 绘制年龄估计和性别预测训练过程中的准确率变化:

epochs = np.arange(1,len(val_gender_accuracies)+1)
fig,ax = plt.subplots(1,2,figsize=(10,5))
ax = ax.flat
ax[0].plot(epochs, val_gender_accuracies, 'bo')
ax[1].plot(epochs, val_age_maes, 'r')
ax[0].set_xlabel('Epochs')
ax[1].set_xlabel('Epochs')
ax[0].set_ylabel('Accuracy')
ax[1].set_ylabel('MAE')
ax[0].set_title('Validation Gender Accuracy')
ax[0].set_title('Validation Age Mean-Absolute-Error')
plt.show()

模型性能
在年龄预测方面平均与真实年龄相差了 6 岁左右,在性别预测方面的准确率约为 84%

(9) 随机选择测试图像,预测图中人物年龄和性别。

获取并加载图像,将其输入到 trn 对象中的 preprocess_image 方法中:

im = cv2.imread('4.jpeg')
im = trn.preprocess_image(im).to(device)

通过训练好的模型传递图像:

gender, age = model(im)
pred_gender = gender.to('cpu').detach().numpy()
pred_age = age.to('cpu').detach().numpy()

绘制图像并打印真实值和预测值:

im = cv2.imread('4.jpeg')
im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
plt.imshow(im)
plt.show()
print('predicted gender:',np.where(pred_gender[0][0]<0.5,'Male','Female'), '; Predicted age', int(pred_age[0][0]*80))
# predicted gender: Female ; Predicted age 26

综上,可以看到,我们能够一次性同时预测年龄和性别。但是,需要注意,本节中构建的模型非常不稳定,年龄值会随着图像的光照条件有很大差异,可以通过使用数据增强观察模型性能改善情况。

小结

多任务学习可以同时处理和学习多个相关任务,在实践中,通过在多个任务上进行联合训练,模型可以学习到更通用的特征表示,从而改善每个任务的性能,这种共享知识的方式可以减少对大量任务特定数据的需求,使得训练更加高效,同时任务之间的相互促进和共享知识可以帮助模型更好地理解数据的内在结构和模式。

系列链接

PyTorch深度学习实战(1)——神经网络与模型训练过程详解
PyTorch深度学习实战(2)——PyTorch基础
PyTorch深度学习实战(3)——使用PyTorch构建神经网络
PyTorch深度学习实战(4)——常用激活函数和损失函数详解
PyTorch深度学习实战(5)——计算机视觉基础
PyTorch深度学习实战(6)——神经网络性能优化技术
PyTorch深度学习实战(7)——批大小对神经网络训练的影响
PyTorch深度学习实战(8)——批归一化
PyTorch深度学习实战(9)——学习率优化
PyTorch深度学习实战(10)——过拟合及其解决方法
PyTorch深度学习实战(11)——卷积神经网络
PyTorch深度学习实战(12)——数据增强
PyTorch深度学习实战(13)——可视化神经网络中间层输出
PyTorch深度学习实战(14)——类激活图
PyTorch深度学习实战(15)——迁移学习
PyTorch深度学习实战(16)——面部关键点检测

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

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

相关文章

双向控制舵机(树莓派版)

文章目录 前言1、舵机参数2、功能测试参考文献 前言 为了实现垃圾分类和倾倒功能&#xff0c;于是购买了180度舵机 避坑提示&#xff1a;360度舵机无法像180度舵机一样控制角度&#xff0c;它只能控制旋转方向和速度&#xff0c;所以别买360度的舵机。 1、舵机参数 我买的舵机…

194、SpringBoot -- 下载和安装 Erlang 、 RabbitMQ

本节要点&#xff1a; 一些命令&#xff1a; 小黑窗输入&#xff1a; rabbitmq-plugins enable rabbitmq_management 启动控制台插件 rabbitmq-server 启动rabbitMQ服务器 管理员启动小黑窗&#xff1a; rabbitmq-service install 添加rabbitMQ为本地服务 启动浏览器访问“h…

gym_unity学习笔记

最近学了一段时间gym_unity&#xff0c;把一些资料留在这里 实例 实例gym_unity训练RollerBall&#xff1a;https://blog.csdn.net/alibutter/article/details/120908687实例gyn_unity训练3DBall&#xff1a;https://zhuanlan.zhihu.com/p/554927641?utm_id0 源码&#xff1…

基于香橙派和SU-03T 使用Linux实现语音控制刷抖音

硬件介绍 SU-03T之前在小车的时候使用过&#xff0c;详见&#xff1a;语音小车---6 最终整合_mjmmm的博客-CSDN博客 按照下图进行接线&#xff1a; 项目需求 通过语音指令来控制安卓手机刷抖音&#xff0c;可以实现视频切换和点赞等功能&#xff1a; 1. 开机播报“你好&a…

Go 并发可视化解释 - sync.Mute

在学习 Go 编程语言时&#xff0c;您可能会遇到这句著名的格言&#xff1a;“不要通过共享内存来进行通信&#xff1b;相反&#xff0c;通过通信来共享内存。” 这句话构成了 Go 强大并发模型的基础&#xff0c;其中通道&#xff08;channels&#xff09;作为协程之间的主要通信…

6条优势,anzo capital昂首资本相信MT5替代MT4的原因

投资者都知道MT5是在MT4基础上升级换代的多资产平台&#xff0c;MT5于2010年6月首次发布。anzo capital昂首资本认为MT5将完全取代MT4&#xff0c;就像MT4取代之前版本一样&#xff0c;因为有以下6条优势&#xff1a; 一.市场深度(DOM)数据。在MT4中&#xff0c;DOM几乎没有用…

什么是生成对抗网络 (GAN)?

什么是生成对抗网络 &#xff08;GAN&#xff09;&#xff1f; 钦吉兹赛义德贝利 一、说明 GAN&#xff08;Generative Adversarial Network&#xff09;网络是一种深度学习模型&#xff0c;由两个神经网络——生成器和判别器组成。生成器负责生成虚假的数据&#xff0c;而判别…

yarn安装依赖时报错 error An unexpected error occurred:

一切起因是因为前一天安装了volta管理node&#xff0c;第二天启动项目&#xff0c; 显示error An unexpected error occurred: “https://registry.npmmirror.com/webpack-aliyun-oss/-/webpack-aliyun-oss-0.2.6.tgz: Request failed “404 Not Found””. 项目启动时发现报错…

【WSN】基于蚁群算法的WSN路由协议(最短路径)消耗节点能量研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【虚拟化】虚拟机vcpu绑核物理机

文章目录 一、NUMA二、虚拟机xml配置解析 参考文章 第一篇&#xff1a;KVM虚拟化CPU技术总结 第二篇&#xff1a;虚机cpu和mem的配置&#xff08;cputune和numatune&#xff09; 第三篇&#xff1a;libvirt 中cpu, numa 的配置 第四篇&#xff1a;如何提高虚拟机性能&#xff1…

安全学习_开发相关_JavaEE过滤器监听器简单了解

文章目录 Web应用运行流程图 JavaEE-过滤器-Filter过滤器概述&作用过滤器相关安全测试场景 JavaEE-监听器-Listener监听器作用&#xff1a;监听器相关安全测试场景 过滤器和监听器&#xff0c;主要对安全测试有影响的是过滤器&#xff0c;监听器只是在对代码进行逻辑分析时…

2020-2023中国高等级自动驾驶产业发展趋势研究-中国高等级自动驾驶发展近况

1.2 中国高等级自动驾驶发展近况 通过对中国高等级自动驾驶行业的观察和分析&#xff0c;亿欧汽车认为&#xff0c;除技术解决方案提供商外&#xff0c;如今的车企、政府、资本同样在产业链中扮演重要角色。此外&#xff0c;车路协同技术的发展也为高等级自动驾驶的发展提供了更…

Web自动化框架中验证码识别处理全攻略,让测试更得心应手!

前言&#xff1a; 随着Web应用程序的不断发展&#xff0c;自动化测试已成为项目开发中必不可少的一环。然而&#xff0c;验证码的出现却经常会使自动化测试变得更具挑战性。为了解决这个问题&#xff0c;我们需要一种方法来自动识别和处理验证码&#xff0c;从而提高自动化测试…

leetcode面试题0808有重复字符串的排列组合

描述 输入一个长度为 n 字符串&#xff0c;打印出该字符串中字符的所有排列&#xff0c;你可以以任意顺序返回这个字符串数组。 例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。 数据范围&#xff1a;n<10 要求&#xff1a;空间复…

引领初创企业的数字化转型:选择适合的低代码平台

初创企业在初期各项架构都还不完善&#xff0c;对于应用程序的需求多样&#xff0c;但是又要考量成本。所以&#xff0c;低代码平台就是在综合考量成本和需求的情况下的一个突出的选择。下面我们就六个方面为您介绍&#xff1a;初创企业选择的Zoho Creator低代码平台。 1、功能…

机器学习笔记 - k-NN算法的数学表达

一、概述 所有的机器学习算法都是有假设前提的。k-NN算法的假设前提是相似的输入有相似的输出。其分类规则是对于测试输入x,在其k个最相似的训练输入中分配最常见的标签。 k-NN 的正式定义: 对于一个待测试数据。 将的个最近邻的集合表示为 。的正式定义为 ,并且。(意思就是…

Linux第一次作业

一&#xff0c;作业问题&#xff1a; 二&#xff0c;问题解答&#xff1a; 1. 2.文件管理命令练习 3.vi/vim练习 3.1 3.2 3.3 3.4

消除达人游戏小程序开发流程:专业性与创新的结合

在移动互联网时代&#xff0c;达人游戏小程序逐渐成为了用户追逐娱乐的一种方式。为了满足用户对于达人游戏的需求&#xff0c;开发一款专业且具有创新性的达人游戏小程序显得尤为重要。本文将从需求分析、技术选型、系统设计和用户体验等多个方面&#xff0c;深入探讨达人游戏…

2023.9.23-最强实战:Typora+mkdocs构建自己的知识库博客

最强实战&#xff1a;Typoramkdocs构建自己的知识库&博客-2023.9.23 winodws-ecs-rsync-mkdocs-typora-百度网盘同步空间数据维护方案 目录 实验环境 win10 typora v1.7.4 mkdocs, version 1.5.2 vscode v1.82.2 阿里云轻量服务器实验软件 链接&#xff1a;https://pan.…

005:vue2使用vue-type-writer实现打字机效果

Vue Type Writer是一个Vue.js 2打字机效果组件&#xff0c;支持像打字机一样模仿键入文本。 文章目录 1. 效果2. 安装使用 1. 效果 2. 安装使用 npm 安装 npm install vue-type-writer --save完整代码 <template><div class"app-container home"><…