融合创新:HAI推动Pytorch2.0 AI框架新时代

腾讯云 高性能应用服务 HAI 推动 Pytorch2.0 AI框架新时代

1、背景介绍:

腾讯云高性能应用服务 HAI 是为开发者量身打造的澎湃算力平台。无需复杂配置,便可享受即开即用的GPU云服务体验。在 HAI 中,根据应用智能匹配并推选出最适合的GPU算力资源,以确保您在数据科学、LLM、AI作画等高性能应用中获得最佳性价比。

HAI 服务优势

智能选型 :根据应用匹配推选GPU算力资源,实现最高性价比。同时,打通必备云服务组件,大幅简化云服务配置流程。

一键部署 :分钟级自动构建 AI框架、LLM、AI作画等应用环境。提供多种预装模型环境,包含如 Pytorch2.0 、StableDiffusion、ChatGLM2等热门模型。

可视化界面 :友好的图形界面,AI调试更为简单

场景介绍

基于HAI部署的AI框架 Pytorch2.0 快速体验机器学习工作流程

基于HAI部署的AI框架 Pytorch2.0 快速体验使用字符级循环神经网络(RNN) 生成姓名

基于HAI部署的AI框架 Pytorch2.0 快速体验视频抠像神器 RobustVideoMatting

2、 实验介绍

本次我们使用 腾讯云高性能应用服务 HAI 体验快速搭建并使用AI框架 Pytorch2.0 ,实现思路如下:

1、开发者体验 高性能应用服务HAI 一键部署 PyTorch 2.0

2、开发者体验 高性能应用服务HAI 学习 PyTorch 基础知识,通过 JupyterLab 实现的完整的机器学习工作流程。

3、开发者体验 高性能应用服务HAI PyTorch 2.0 使用字符级循环神经网络(RNN) 生成姓名

4、开发者体验 高性能应用服务HAI PyTorch 2.0 强大的视频抠图 (RVM)

3、效果展示

使用 JupyterLab 体验完整的机器学习工作流程

使用 JupyterLab 体验字符级循环神经网络(RNN) 生成姓名

使用 JupyterLab 体验视频抠像神器RobustVideoMatting

4、实操指导

4.1 申请高性能应用服务 HAI

4.1.1 点击链接进入 高性能应用服务 HAI 申请体验资格

4.1.2 等待审核通过后,进入 高性能应用服务 HAI

4.1.3 点击前往体验HAI,登录 高性能应用服务 HAI 控制台

4.1.4 点击 新建 选择 AI框架,选择算力方案、输入实例名称、选择数量 后立即购买

4.1.5 查看实例创建状态

4.1.6 完成创建,查看运行状态

4.2 使用 JupyterLab 体验完整的机器学习工作流程

4.2.1 创建工作空间

算力管理页面 点击 算力连接 ,选择 jupyter_lab

认识 JupyterLab 操作界面,这里 选择 Notebook

4.2.2 数据处理

PyTorch 中有两个处理数据的基元:Torch.utils.data.DataLoader 和 Torch.utils.data.Dataset。

Dataset 存储样本及其相应的标签,而 DataLoader 则围绕 Dataset 包装了一个可迭代的数据。

PyTorch 提供了特定领域的库,如 TorchText、TorchVision 和 TorchAudio,它们都包括数据集。在本教程中,我们将使用一个 TorchVision 数据集。

torchvision.datasets 模块包含了许多真实世界的视觉数据的 Dataset 对象,如 CIFAR、COCO。每个 TorchVision 中的 Dataset 对象都包括两个参数:transform 和 target_transform,分别用来修改样本和标签。在本教程中,我们使用 FashionMNIST 数据集。

由于外网环境下载数据集缓慢,这里我们使用清华大学的镜像加载本地数据集

mkdir dataset-fashion-mnist #创建文件夹 dataset-fashion-mnist
cd dataset-fashion-mnist
wget https://mirror.tuna.tsinghua.edu.cn/raspberry-pi-os/raspbian/pool/main/d/dataset-fashion-mnist/dataset-fashion-mnist_0.0~git20200523.55506a9.orig.tar.xz #下载数据集
tar -xf dataset-fashion-mnist_0.0~git20200523.55506a9.orig.tar.xz --strip-components=1
cd data && mkdir FashionMNIST && mkdir FashionMNIST/raw 

执行创建命令后:

点击目录 依次进入 root/dataset-fashion-mnist/data/fashion

按住 ctrl 键 选中四个数据集文件并拷贝至 /FashionMNIST/raw/ 文件夹

依次进入 /FashionMNIST/raw/ 文件夹并粘贴文件

创建一个 Notebook 文件

粘贴代码并点击菜单栏 RunRun Selected Cell 执行选中代码

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor

# 加载本地训练数据
training_data = datasets.FashionMNIST(
    root="/root/dataset-fashion-mnist/data",
    train=True,
    download=True,
    transform=ToTensor(),
)
# 加载本地测试数据
test_data = datasets.FashionMNIST(
    root="/root/dataset-fashion-mnist/data",
    train=False,
    download=True,
    transform=ToTensor(),
)

代码截图:

新建一个控制台页面来配置使用 腾讯云 提供的镜像服务并安装依赖

选择默认使用 腾讯云 提供的镜像服务

pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple
pip config set global.trusted-host mirrors.cloud.tencent.com

pip install matplotlib

返回之前的 Notebook 页面

我们可以像列表一样手动索引 Dataset:training_data[index]。我们可以使用 matplotlib 来可视化数据中的一些样本。

import matplotlib.pyplot as plt

labels_map = {
    0: "T-Shirt",
    1: "Trouser",
    2: "Pullover",
    3: "Dress",
    4: "Coat",
    5: "Sandal",
    6: "Shirt",
    7: "Sneaker",
    8: "Bag",
    9: "Ankle Boot",
}
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols * rows + 1):
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    img, label = training_data[sample_idx]
    figure.add_subplot(rows, cols, i)
    plt.title(labels_map[label])
    plt.axis("off")
    plt.imshow(img.squeeze(), cmap="gray")
plt.show()

可视化样本:

我们将 Dataset 作为参数传递给 DataLoader。这会在我们的数据集上包裹一个可迭代的对象,并支持自动批处理、采样、顺序打乱和多进程数据加载。在这里,我们定义了一个64的批处理大小,即 dataloader 可迭代的每个元素将返回64个特征和标签的批次。

粘贴代码并点击菜单栏 RunRun Selected Cell 执行选中代码

batch_size = 64

# 创建 dataloader
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)

for X, y in test_dataloader:
    print(f"Shape of X [N, C, H, W]: {X.shape}")
    print(f"Shape of y: {y.shape} {y.dtype}")
    break

执行结果截图:

4.2.3 创建模型

为了在 PyTorch 中定义一个神经网络,我们创建一个继承自 nn.Module 的类。我们在__init__函数中定义网络的层,并在 forward 函数中指定数据将如何通过网络。为了加速神经网络的操作,我们将其移到GPU上。

粘贴代码并点击菜单栏 RunRun Selected Cell 执行选中代码

# 获取用于训练的 cpu 或 gpu 设备
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")

# 定义模型
class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10)
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork().to(device)
print(model)

执行结果截图:

4.2.4 优化模型参数

为了训练模型,我们需要一个损失函数和一个优化器。

loss_fn = nn.CrossEntropyLoss() 
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

在一个训练循环中,模型将对训练数据集(分批送入)进行预测,并反向传播预测误差以调整模型的参数。

def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    model.train()
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        # 计算预测误差
        pred = model(X)
        loss = loss_fn(pred, y)

        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if batch % 100 == 0:
            loss, current = loss.item(), (batch + 1) * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")

我们还要根据测试数据集检查模型的性能。

def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

训练过程是通过几个迭代(epochs)进行的。在每个迭代中,模型学习参数以做出更好的预测。我们在每个迭代中打印模型的准确度和损失;我们希望看到准确度在每个迭代中逐渐增加,损失在每个迭代中逐渐减少。

epochs = 5
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
print("Done!")

执行截图:

4.2.5 保存模型

保存模型的一个常见方法是序列化内部状态字典(包含模型参数)。

torch.save(model.state_dict(), "/root/model.pth")
print("Saved PyTorch Model State to model.pth")

执行截图:

4.2.6 加载模型并预测

加载模型的过程包括重新创建模型结构和加载内部状态字典。

model = NeuralNetwork()
model.load_state_dict(torch.load("/root/model.pth"))

保存并加载本地模型截图:

现在使用这个模型可以用来进行预测:

classes = [
    "包",
    "上衣",
    "套头衫",
    "连衣裙",
    "外套",
    "凉鞋",
    "衬衫",
    "运动鞋",
    "包",
    "短靴",
] #定义了一个包含类别标签的 classes 列表

model.eval() #将 PyTorch 模型设置为评估模式
x, y = test_data[1][0], test_data[1][1]#测试数据 test_data 中获取样本 分别为图像数据和其真实标签
with torch.no_grad():
    pred = model(x)
    predicted, actual = classes[pred[0].argmax(0)], classes[y]#选择具有最高概率的类别索引
    print(f'Predicted: "{predicted}", Actual: "{actual}"') #模型的预测结果 predicted 与真实标签 actual 进行比较,并使用 print 函数输出这两个值。
#输出图片信息
# 将 x 转换回图像格式
image = x.numpy().transpose(1, 2, 0)
# 显示图像
plt.figure(figsize=(4, 4))
plt.imshow(image)
# 设置中文标题
print('预测结果: "{predicted}", 实际结果: "{actual}"'.format(predicted=predicted, actual=actual))
plt.axis('off')
plt.show()

模型预测截图:

4.3 使用 JupyterLab 体验使用字符级循环神经网络(RNN) 生成姓名

4.3.1 数据准备

新建一个命令窗口,输入以下命令,下载资源包并解压

cd /root
wget https://gitee.com/mmliujc/tencent_gpu/raw/master/data.zip
unzip data

解压文件:

新建一个 Notebook 页面,选择 Python 3

有一堆每行一个名字的纯文本文件 data/names/[Language].txt 。我们将行拆分为一个数组,将 Unicode 转换为 ASCII,最后得到一个字典 {language: [names ...]}

from __future__ import unicode_literals, print_function, division
from io import open
import glob
import os
import unicodedata
import string

all_letters = string.ascii_letters + " .,;'-"
n_letters = len(all_letters) + 1 # Plus EOS marker

def findFiles(path): return glob.glob(path)

# Turn a Unicode string to plain ASCII, thanks to https://stackoverflow.com/a/518232/2809427
def unicodeToAscii(s):
    return ''.join(
        c for c in unicodedata.normalize('NFD', s)
        if unicodedata.category(c) != 'Mn'
        and c in all_letters
    )

# Read a file and split into lines
def readLines(filename):
    with open(filename, encoding='utf-8') as some_file:
        return [unicodeToAscii(line.strip()) for line in some_file]

# Build the category_lines dictionary, a list of lines per category
category_lines = {}
all_categories = []
for filename in findFiles('/root/data/names/*.txt'):
    category = os.path.splitext(os.path.basename(filename))[0]
    all_categories.append(category)
    lines = readLines(filename)
    category_lines[category] = lines

n_categories = len(all_categories)

if n_categories == 0:
    raise RuntimeError('Data not found. Make sure that you downloaded data '
        'from https://download.pytorch.org/tutorial/data.zip and extract it to '
        'the current directory.')

print('# categories:', n_categories, all_categories)
print(unicodeToAscii("O'Néàl"))

4.3.2 创建网络

该网络为类别张量,增加了一个额外的参数,该参数与其他参数连接在一起。类别张量和字母输入一样,是一个 one-hot 向量。

我们将输出解释为下一个字母出现的概率。采样时,最有可能的输出字母被用作下一个输入字母。

添加了第二个线性层 o2o(在隐藏层和输出层合并后),以增强其处理能力。还有一个 dropout 层,它以给定的概率(这里是 0.1)随机将部分输入归零,通常用于模糊输入以防止过拟合。在这里,我们将其用于网络的末端来故意加入一些混乱,增加采样的多样性。

import torch
import torch.nn as nn

class RNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(RNN, self).__init__()
        self.hidden_size = hidden_size

        self.i2h = nn.Linear(n_categories + input_size + hidden_size, hidden_size)
        self.i2o = nn.Linear(n_categories + input_size + hidden_size, output_size)
        self.o2o = nn.Linear(hidden_size + output_size, output_size)
        self.dropout = nn.Dropout(0.1)
        self.softmax = nn.LogSoftmax(dim=1)

    def forward(self, category, input, hidden):
        input_combined = torch.cat((category, input, hidden), 1)
        hidden = self.i2h(input_combined)
        output = self.i2o(input_combined)
        output_combined = torch.cat((hidden, output), 1)
        output = self.o2o(output_combined)
        output = self.dropout(output)
        output = self.softmax(output)
        return output, hidden

    def initHidden(self):
        return torch.zeros(1, self.hidden_size)

4.3.3 训练网络

(1) 训练准备

首先,创建辅助函数用于获取 (category, line) 的随机对:

import random

# Random item from a list
def randomChoice(l):
    return l[random.randint(0, len(l) - 1)]

# Get a random category and random line from that category
def randomTrainingPair():
    category = randomChoice(all_categories)
    line = randomChoice(category_lines[category])
    return category, line

对于每个时间步(即每个训练单词中的每个字母),网络的输入将是 (category, current letter, hidden state),输出将是 (next letter, next hidden state)。因此,对于每个训练集,我们需要类别、一组输入字母,和一组输出/目标字母。

由于我们在每个时间步预测当前字母的下一个字母,所以字母对是来自该行的连续字母组。例如,对于 ABCD,我们将创建 ("A"、"B")、("B"、"C")、("C"、"D")、("D"、"EOS")。

类别张量是一个大小为 <1 x n_categories> 的 one-hot 张量。在训练时,我们在每个时间步将其输送到网络中——这是一种设计选择,也可以将其包含在初始隐藏状态中,或使用其他策略。

# One-hot vector for category
def categoryTensor(category):
    li = all_categories.index(category)
    tensor = torch.zeros(1, n_categories)
    tensor[0][li] = 1
    return tensor

# One-hot matrix of first to last letters (not including EOS) for input
def inputTensor(line):
    tensor = torch.zeros(len(line), 1, n_letters)
    for li in range(len(line)):
        letter = line[li]
        tensor[li][0][all_letters.find(letter)] = 1
    return tensor

# ``LongTensor`` of second letter to end (EOS) for target
def targetTensor(line):
    letter_indexes = [all_letters.find(line[li]) for li in range(1, len(line))]
    letter_indexes.append(n_letters - 1) # EOS
    return torch.LongTensor(letter_indexes)

为了方便训练,我们将创建一个 randomTrainingExample 函数,该函数获取一个随机的 (category, line) 对,并将它们转换为所需的 (category, input, target) 张量。

# Make category, input, and target tensors from a random category, line pair
def randomTrainingExample():
    category, line = randomTrainingPair()
    category_tensor = categoryTensor(category)
    input_line_tensor = inputTensor(line)
    target_line_tensor = targetTensor(line)
    return category_tensor, input_line_tensor, target_line_tensor

(2)训练网络

与只使用最后一步的输出的分类不同,在每一步中我们都进行了一次预测,因此我们需要在每一步都计算损失。

自动求导的神奇之处在于,您可以简单地将每一步的损失相加,并在最后进行反向传播。

criterion = nn.NLLLoss()

learning_rate = 0.0005

def train(category_tensor, input_line_tensor, target_line_tensor):
    target_line_tensor.unsqueeze_(-1)
    hidden = rnn.initHidden()

    rnn.zero_grad()

    loss = 0

    for i in range(input_line_tensor.size(0)):
        output, hidden = rnn(category_tensor, input_line_tensor[i], hidden)
        l = criterion(output, target_line_tensor[i])
        loss += l

    loss.backward()

    for p in rnn.parameters():
        p.data.add_(p.grad.data, alpha=-learning_rate)

    return output, loss.item() / input_line_tensor.size(0)

为了记录训练所需的时间,我添加了一个 timeSince(timestamp) 函数,它返回一个可读的字符串:

import time
import math

def timeSince(since):
    now = time.time()
    s = now - since
    m = math.floor(s / 60)
    s -= m * 60
    return '%dm %ds' % (m, s)

训练照常进行——多次调用 train 并等待几分钟,在每 print_every 个样本处打印当前时间和损失,同时在 all_losses 中存储每 plot_every 个样本的平均损失,以供稍后绘图使用。

请耐心等待训练完成,预计耗时约21分钟

rnn = RNN(n_letters, 128, n_letters)

n_iters = 100000
print_every = 5000
plot_every = 500
all_losses = []
total_loss = 0 # Reset every ``plot_every`` ``iters``

start = time.time()

for iter in range(1, n_iters + 1):
    output, loss = train(*randomTrainingExample())
    total_loss += loss

    if iter % print_every == 0:
        print('%s (%d %d%%) %.4f' % (timeSince(start), iter, iter / n_iters * 100, loss))

    if iter % plot_every == 0:
        all_losses.append(total_loss / plot_every)
        total_loss = 0

执行完毕,耗时约21分钟

4.3.4 绘制损失

all_losses 中的历史损失绘制成图可以展示网络的学习过程:

在训练完成后的 Notebook 中粘贴代码并点击菜单栏 RunRun Selected Cell 执行选中代码

import matplotlib.pyplot as plt

plt.figure()
plt.plot(all_losses)

查看损失绘制图:

4.3.5 网络采样

进行采样时,向网络中输入一个字母,预测下一个字母是什么,并将其作为下一个输入的字母,重复此过程直到遇到 EOS。

创建输入类别、起始字母和空隐藏状态的张量

使用起始字母创建一个字符串 output_name

在最大输出长度范围内,

注意: 比起要求输入一个起始字母,另一种策略是在训练中加入一个“字符串起始”的标记,并让网络自己选择起始字母。

max_length = 20

# Sample from a category and starting letter
def sample(category, start_letter='A'):
    with torch.no_grad():  # no need to track history in sampling
        category_tensor = categoryTensor(category)
        input = inputTensor(start_letter)
        hidden = rnn.initHidden()

        output_name = start_letter

        for i in range(max_length):
            output, hidden = rnn(category_tensor, input[0], hidden)
            topv, topi = output.topk(1)
            topi = topi[0][0]
            if topi == n_letters - 1:
                break
            else:
                letter = all_letters[topi]
                output_name += letter
            input = inputTensor(letter)

        return output_name

# Get multiple samples from one category and multiple starting letters
def samples(category, start_letters='ABC'):
    for start_letter in start_letters:
        print(sample(category, start_letter))

samples('Russian', 'RUS')

samples('German', 'GER')

samples('Spanish', 'SPA')

samples('Chinese', 'CHI')

返回结果:

4.4 使用 JupyterLab 体验视频抠像神器RobustVideoMatting

4.4.1 安装依赖

打开 File 依次选择 New 选择 Terminal

输入命令下载 RobustVideoMatting 相关源码:

下载源码

下载方式一:https下载方式

git clone https://gitee.com/ai-toys/RobustVideoMatting
# 输入你的gitee账号密码
cd RobustVideoMatting

下载方式二:ssh下载方式

在服务器上生成ssh密钥,在Jupyter终端中依次执行以下命令

ssh-keygen -t ed25519 -C ""  #双引号里面填ssh key,一般填自己邮箱
# 回车一直默认即可,会在家目录生成 ~/.ssh 文件夹,存放密钥信息
cat ~/.ssh/id_ed25519.pub  # 查看生成的公钥

  1. 复制红色区域内容

  2. 打开gitee登录,并添加密钥

  3. 打开gitee登录,并添加密钥

  4. 在Jupyter终端中依次执行以下命令

    git clone git@gitee.com:ai-toys/RobustVideoMatting
    # 输入yes 回车确认
    

代码下载完成后 安装 Python 库:

pip install -r requirements_inference.txt

截图如下:

4.4.2 读取模型

下载模型以及素材文件并完成解压

wget https://gitee.com/mmliujc/tencent_gpu/raw/master/rvm_res.zip
unzip rvm_res.zip

完成后 选择文件 File -> New ->Python File

编写视频抠像处理代码:

import torch
import base64


from model import MattingNetwork
from inference import convert_video

model = MattingNetwork('mobilenetv3').eval().cuda()  # 或 "resnet50"
model.load_state_dict(torch.load('rvm_mobilenetv3.pth'))


convert_video(
    model,                           # 模型,可以加载到任何设备(cpu 或 cuda)
    input_source='1917.mp4',        # 视频文件,或图片序列文件夹
    output_type='video',             # 可选 "video"(视频)或 "png_sequence"(PNG 序列)
    output_composition='output/com.mp4',    # 若导出视频,提供文件路径。若导出 PNG 序列,提供文件夹路径
    output_alpha="output/pha.mp4",          # [可选项] 输出透明度预测
    output_foreground="output/fgr.mp4",     # [可选项] 输出前景预测
    output_video_mbps=4,             # 若导出视频,提供视频码率
    downsample_ratio=None,           # 下采样比,可根据具体视频调节,或 None 选择自动
    seq_chunk=12,                    # 设置多帧并行计算
)

编写完成后 Ctrl+S 保存代码,重命名为 test.py

输入命令 创建我们要输出视频的文件夹并执行Python脚本:

mkdir output
python test.py

4.4.3 展示结果

完成后即可在output文件夹下查看相应文件

这里我们使用notebook查看结果:

输入代码并执行:

import torch
import base64
from IPython.display import HTML

outpath = "com.mp4"
mp4 = open(outpath,'rb').read()
data_url = "data:video/mp4;base64," + base64.b64encode(mp4).decode()
HTML("""
<video width=400 controls>
      <source src="%s" type="video/mp4">
</video>
""" % data_url)

执行完成后:

加载不同的视频文件 查看不同的实验效果:

六、实验小结

本次实验主要是引导大家如何使用 高性能应用服务 HAI 部署 AI框架 Pytorch2.0 运行环境进行机器学习的工作流程,并实际体验在 高性能应用服务 HAI 上实践生成姓名、视频抠像,开箱即用,可以快速上手。最后也欢迎大家一起探索 高性能应用服务 HAI 更多的功能,为工作中赋能增效降本!

七、活动介绍

完成实验后填写调查问卷,将会额外获得10积分奖励

问卷调查地址https://jinshuju.net/f/hrU50x