生成对抗网络(GAN)入门与编程实现

生成对抗网络(Generative Adversarial Networks, 简称 GAN)自 2014 年由 Ian Goodfellow 等人提出以来,迅速成为机器学习和深度学习领域的重要工具之一。GAN 以其在图像生成、风格转换、数据增强等领域的出色表现,吸引了广泛的研究兴趣和应用探索。本文将介绍 GAN 的基本概念、工作原理以及如何通过代码实现一个简单的 GAN 模型。

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

GAN 是一种生成模型,旨在通过学习数据的潜在分布,生成与真实数据相似的样本。它由两个核心部分组成:

  • 生成器(Generator):输入一个随机噪声向量,通过一系列的变换生成假数据,目标是让生成的假数据尽可能接近真实数据。
  • 判别器(Discriminator):输入真实数据和生成器生成的假数据,输出判断其真实性的概率,目标是尽可能准确地区分真实数据和生成数据。
    二者在训练过程中相互对抗,形成一个博弈过程。

在这里插入图片描述

GAN 的工作原理

GAN 的训练过程可以看作是生成器和判别器之间的"零和博弈":

  1. 生成器:
  • 输入随机噪声向量 z z z(通常服从正态分布)。
  • 输出生成的样本 G ( z ) G(z) G(z)
  • 目标是让判别器无法区分 G ( z ) G(z) G(z) 和真实数据。
  1. 判别器:
  • 输入真实样本 x x x 和生成器生成的假样本 G ( z ) G(z) G(z)
  • 输出区分真假样本的概率。
  • 目标是最大化对真实样本和生成样本的区分能力。

通过对模型进行训练,生成器逐渐生成更接近真实分布的样本,而判别器也不断提高其判别能力,直到达到平衡。
在这里插入图片描述
完整的训练过程如下:
在这里插入图片描述

GAN 的代码实现

接下来,我们通过 PyTorch 实现一个简单的 GAN 模型,生成 MNIST 手写数字图片。

  1. 数据加载与预处理
    MNIST 是一个常用的手写数字数据集,每张图片的大小为 28x28,灰度范围为 0-1。
# data_loader
transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.5), std=(0.5))
])
train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('data', train=True, download=True, transform=transform),
    batch_size=batch_size, shuffle=True)

使用 torchvision 的 datasets.MNIST 下载MNIST数据集。之后,将图片转换为Tensor格式,并对像素值进行归一化(均值0.5,标准差0.5)。

  1. 构建生成器与判别器
    生成器和判别器都是多层全连接神经网络。
# G(z)
class generator(nn.Module):
    # initializers
    def __init__(self, input_size=32, n_class = 10):
        super(generator, self).__init__()
        self.fc1 = nn.Linear(input_size, 256)
        self.fc2 = nn.Linear(self.fc1.out_features, 512)
        self.fc3 = nn.Linear(self.fc2.out_features, 1024)
        self.fc4 = nn.Linear(self.fc3.out_features, n_class)

    # forward method
    def forward(self, input):
        x = F.leaky_relu(self.fc1(input), 0.2)
        x = F.leaky_relu(self.fc2(x), 0.2)
        x = F.leaky_relu(self.fc3(x), 0.2)
        x = F.tanh(self.fc4(x))
        x = x.squeeze(-1)

        return x

class discriminator(nn.Module):
    # initializers
    def __init__(self, input_size=32, n_class=10):
        super(discriminator, self).__init__()
        self.fc1 = nn.Linear(input_size, 1024)
        self.fc2 = nn.Linear(self.fc1.out_features, 512)
        self.fc3 = nn.Linear(self.fc2.out_features, 256)
        self.fc4 = nn.Linear(self.fc3.out_features, n_class)

    # forward method
    def forward(self, input):
        x = F.leaky_relu(self.fc1(input), 0.2)
        x = F.dropout(x, 0.3)
        x = F.leaky_relu(self.fc2(x), 0.2)
        x = F.dropout(x, 0.3)
        x = F.leaky_relu(self.fc3(x), 0.2)
        x = F.dropout(x, 0.3)
        x = F.sigmoid(self.fc4(x))
        x = x.squeeze(-1)
        return x
        
# network
G = generator(input_size=100, n_class=28*28)
D = discriminator(input_size=28*28, n_class=1)
  • 生成器 (generator):

    • 输入:一个大小为100的噪声向量。
    • 结构:包含4个全连接层(fc1到fc4),每层后面跟随一个激活函数:
      • 前三层使用 LeakyReLU 激活函数,最后一层使用 tanh。
      • 输出大小为 28×28(MNIST图片的尺寸)。
    • 功能:将随机噪声映射为类似于手写数字的图片。
  • 判别器 (discriminator):

    • 输入:展平的MNIST图片(大小为 28×28)。
    • 结构:包含4个全连接层(fc1到fc4),每层后面跟随:
      • LeakyReLU 激活函数和 Dropout(用于防止过拟合)。
      • 最后一层使用 sigmoid 激活函数。
    • 输出:一个介于0和1之间的值,表示输入是“真实图片”的概率。
  1. 定义训练参数以及损失函数和优化器
# training parameters
batch_size = 256
lr = 0.0002
train_epoch = 200
device = torch.cuda.is_available()
if device:
    print("running on GPU!")
    
# Binary Cross Entropy loss
BCE_loss = nn.BCELoss()

#move to cuda
if device:
    G.cuda()
    D.cuda()
    BCE_loss = BCE_loss.cuda()

# Adam optimizer
G_optimizer = optim.Adam(G.parameters(), lr=lr)
D_optimizer = optim.Adam(D.parameters(), lr=lr)
4. 训练过程
在训练过程中,我们交替训练判别器和生成器。
train_hist = {}
train_hist['D_losses'] = []
train_hist['G_losses'] = []

for epoch in range(train_epoch):
    D_losses = []
    G_losses = []
    #生成任务,不需要标签
    for x_, _ in train_loader:
        #训练图像展平
        x_ = x_.view(-1, 28 * 28)

        mini_batch = x_.size()[0]

        y_real_ = torch.ones(mini_batch)
        y_fake_ = torch.zeros(mini_batch)

        # train discriminator D
        D.zero_grad()
        z_ = torch.randn((mini_batch, 100))
        
        if device:
            x_, y_real_, y_fake_ = x_.cuda(), y_real_.cuda(), y_fake_.cuda()
            z_ = z_.cuda()

        #真数据loss
        D_result = D(x_)
        D_real_loss = BCE_loss(D_result, y_real_)
        D_real_score = D_result

        #假数据loss
        G_result = G(z_)
        D_result = D(G_result)
        D_fake_loss = BCE_loss(D_result, y_fake_)
        D_fake_score = D_result

        D_train_loss = D_real_loss + D_fake_loss

        D_train_loss.backward()
        D_optimizer.step()

        D_losses.append(D_train_loss.item())

        # train generator G
        G.zero_grad()
        # z_ = torch.randn((mini_batch, 100))
        # if device:
        #     z_ = z_.cuda()
        G_result = G(z_)
        D_result = D(G_result)

        G_train_loss = BCE_loss(D_result, y_real_)
        G_train_loss.backward()
        G_optimizer.step()

        G_losses.append(G_train_loss.item())

    print('[%d/%d]: loss_d: %.3f, loss_g: %.3f' % (
        (epoch + 1), train_epoch, torch.mean(torch.FloatTensor(D_losses)), torch.mean(torch.FloatTensor(G_losses))))

    if epoch %10 == 0:
        p = 'MNIST_GAN_results/Random_results/MNIST_GAN_' + str(epoch + 1) + '.png'
        fixed_p = 'MNIST_GAN_results/Fixed_results/MNIST_GAN_' + str(epoch + 1) + '.png'
        show_result((epoch+1), save=True, path=p, isFix=False)
        show_result((epoch+1), save=True, path=fixed_p, isFix=True)
        train_hist['D_losses'].append(torch.mean(torch.FloatTensor(D_losses)))
        train_hist['G_losses'].append(torch.mean(torch.FloatTensor(G_losses)))

采用交叉熵损失函数(BCE)计算Loss,即
在这里插入图片描述
其中判别器的loss计算如下:
在这里插入图片描述
生成器的loss计算如下:
在这里插入图片描述

  1. 保存模型及数据
    将生成器和判别器的模型参数进行保存,保存训练过程的loss数据。
print("Training finish!... save training results")
torch.save(G.state_dict(), "MNIST_GAN_results/generator_param.pkl")
torch.save(D.state_dict(), "MNIST_GAN_results/discriminator_param.pkl")
with open('MNIST_GAN_results/train_hist.pkl', 'wb') as f:
    pickle.dump(train_hist, f)
  1. 数据可视化
show_train_hist(train_hist, save=True, path='MNIST_GAN_results/MNIST_GAN_train_hist.png')

images = []
for e in range(train_epoch):
    img_name = 'MNIST_GAN_results/Fixed_results/MNIST_GAN_' + str(e + 1) + '.png'
    images.append(imageio.imread(img_name))
imageio.mimsave('MNIST_GAN_results/generation_animation.gif', images, fps=5)
  1. 完整代码
import os
import matplotlib.pyplot as plt
import itertools
import pickle
import imageio
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
# from torch.autograd import Variable

# G(z)
class generator(nn.Module):
    # initializers
    def __init__(self, input_size=32, n_class = 10):
        super(generator, self).__init__()
        self.fc1 = nn.Linear(input_size, 256)
        self.fc2 = nn.Linear(self.fc1.out_features, 512)
        self.fc3 = nn.Linear(self.fc2.out_features, 1024)
        self.fc4 = nn.Linear(self.fc3.out_features, n_class)

    # forward method
    def forward(self, input):
        x = F.leaky_relu(self.fc1(input), 0.2)
        x = F.leaky_relu(self.fc2(x), 0.2)
        x = F.leaky_relu(self.fc3(x), 0.2)
        x = F.tanh(self.fc4(x))
        x = x.squeeze(-1)

        return x

class discriminator(nn.Module):
    # initializers
    def __init__(self, input_size=32, n_class=10):
        super(discriminator, self).__init__()
        self.fc1 = nn.Linear(input_size, 1024)
        self.fc2 = nn.Linear(self.fc1.out_features, 512)
        self.fc3 = nn.Linear(self.fc2.out_features, 256)
        self.fc4 = nn.Linear(self.fc3.out_features, n_class)

    # forward method
    def forward(self, input):
        x = F.leaky_relu(self.fc1(input), 0.2)
        x = F.dropout(x, 0.3)
        x = F.leaky_relu(self.fc2(x), 0.2)
        x = F.dropout(x, 0.3)
        x = F.leaky_relu(self.fc3(x), 0.2)
        x = F.dropout(x, 0.3)
        x = F.sigmoid(self.fc4(x))
        x = x.squeeze(-1)
        return x

fixed_z_ = torch.randn((5 * 5, 100))    # fixed noise
with torch.no_grad():
    fixed_z_ = fixed_z_.cuda()

def show_result(num_epoch, show = False, save = False, path = 'result.png', isFix=False):
    z_ = torch.randn((5*5, 100))
    with torch.no_grad():
        z_ = z_.cuda()
    # z_ = Variable(z_.cuda(), volatile=True)

    G.eval()
    if isFix:
        test_images = G(fixed_z_)
    else:
        test_images = G(z_)
    G.train()

    size_figure_grid = 5
    fig, ax = plt.subplots(size_figure_grid, size_figure_grid, figsize=(5, 5))
    for i, j in itertools.product(range(size_figure_grid), range(size_figure_grid)):
        ax[i, j].get_xaxis().set_visible(False)
        ax[i, j].get_yaxis().set_visible(False)

    for k in range(5*5):
        i = k // 5
        j = k % 5
        ax[i, j].cla()
        ax[i, j].imshow(test_images[k, :].cpu().data.view(28, 28).numpy(), cmap='gray')

    label = 'Epoch {0}'.format(num_epoch)
    fig.text(0.5, 0.04, label, ha='center')
    plt.savefig(path)

    if show:
        plt.show()
    else:
        plt.close()

def show_train_hist(hist, show = False, save = False, path = 'Train_hist.png'):
    x = range(len(hist['D_losses']))

    y1 = hist['D_losses']
    y2 = hist['G_losses']

    plt.plot(x, y1, label='D_loss')
    plt.plot(x, y2, label='G_loss')

    plt.xlabel('Epoch')
    plt.ylabel('Loss')

    plt.legend(loc=4)
    plt.grid(True)
    plt.tight_layout()

    if save:
        plt.savefig(path)

    if show:
        plt.show()
    else:
        plt.close()

# training parameters
batch_size = 256
lr = 0.0002
train_epoch = 200
device = torch.cuda.is_available()
if device:
    print("running on GPU!")

# data_loader
transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.5), std=(0.5))
])
train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('data', train=True, download=True, transform=transform),
    batch_size=batch_size, shuffle=True)

# network
G = generator(input_size=100, n_class=28*28)
D = discriminator(input_size=28*28, n_class=1)

# Binary Cross Entropy loss
BCE_loss = nn.BCELoss()

#move to cuda
if device:
    G.cuda()
    D.cuda()
    BCE_loss = BCE_loss.cuda()

# Adam optimizer
G_optimizer = optim.Adam(G.parameters(), lr=lr)
D_optimizer = optim.Adam(D.parameters(), lr=lr)

# results save folder
if not os.path.isdir('MNIST_GAN_results'):
    os.mkdir('MNIST_GAN_results')
if not os.path.isdir('MNIST_GAN_results/Random_results'):
    os.mkdir('MNIST_GAN_results/Random_results')
if not os.path.isdir('MNIST_GAN_results/Fixed_results'):
    os.mkdir('MNIST_GAN_results/Fixed_results')

train_hist = {}
train_hist['D_losses'] = []
train_hist['G_losses'] = []

for epoch in range(train_epoch):
    D_losses = []
    G_losses = []
    #生成任务,不需要标签
    for x_, _ in train_loader:
        #训练图像展平
        x_ = x_.view(-1, 28 * 28)

        mini_batch = x_.size()[0]

        y_real_ = torch.ones(mini_batch)
        y_fake_ = torch.zeros(mini_batch)

        # train discriminator D
        D.zero_grad()
        z_ = torch.randn((mini_batch, 100))
        
        if device:
            x_, y_real_, y_fake_ = x_.cuda(), y_real_.cuda(), y_fake_.cuda()
            z_ = z_.cuda()

        #真数据loss
        D_result = D(x_)
        D_real_loss = BCE_loss(D_result, y_real_)
        D_real_score = D_result

        #假数据loss
        G_result = G(z_)
        D_result = D(G_result)
        D_fake_loss = BCE_loss(D_result, y_fake_)
        D_fake_score = D_result

        D_train_loss = D_real_loss + D_fake_loss

        D_train_loss.backward()
        D_optimizer.step()

        D_losses.append(D_train_loss.item())

        # train generator G
        G.zero_grad()
        # z_ = torch.randn((mini_batch, 100))
        # if device:
        #     z_ = z_.cuda()
        G_result = G(z_)
        D_result = D(G_result)

        G_train_loss = BCE_loss(D_result, y_real_)
        G_train_loss.backward()
        G_optimizer.step()

        G_losses.append(G_train_loss.item())

    print('[%d/%d]: loss_d: %.3f, loss_g: %.3f' % (
        (epoch + 1), train_epoch, torch.mean(torch.FloatTensor(D_losses)), torch.mean(torch.FloatTensor(G_losses))))

    if epoch %10 == 0:
        p = 'MNIST_GAN_results/Random_results/MNIST_GAN_' + str(epoch + 1) + '.png'
        fixed_p = 'MNIST_GAN_results/Fixed_results/MNIST_GAN_' + str(epoch + 1) + '.png'
        show_result((epoch+1), save=True, path=p, isFix=False)
        show_result((epoch+1), save=True, path=fixed_p, isFix=True)
    
    train_hist['D_losses'].append(torch.mean(torch.FloatTensor(D_losses)))
    train_hist['G_losses'].append(torch.mean(torch.FloatTensor(G_losses)))


print("Training finish!... save training results")
torch.save(G.state_dict(), "MNIST_GAN_results/generator_param.pkl")
torch.save(D.state_dict(), "MNIST_GAN_results/discriminator_param.pkl")
with open('MNIST_GAN_results/train_hist.pkl', 'wb') as f:
    pickle.dump(train_hist, f)

show_train_hist(train_hist, save=True, path='MNIST_GAN_results/MNIST_GAN_train_hist.png')

images = []
for e in range(train_epoch):
    img_name = 'MNIST_GAN_results/Fixed_results/MNIST_GAN_' + str(e + 1) + '.png'
    images.append(imageio.imread(img_name))
imageio.mimsave('MNIST_GAN_results/generation_animation.gif', images, fps=5)

训练结果

在这里插入图片描述

以上是训练190个epoch后得到的结果,可以看到其中某些图片已经有了数字的模样。这里仅仅是使用了全连接层来搭建模型,如果使用卷积神经网络,效果会有更好的提升,大家可以尝试一下。

遇到的问题

可以适当地提高batch size来提高训练速度,也可以切换更简单的loss函数来提高训练速度。
建议batch size从底到高慢慢调节,若batch size过高,可能导致模型训练出现问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

26、正则表达式

目录 一. 匹配字符 .:匹配除换行符外的任意单个字符。 二. 位置锚点 ^:匹配输入字符串的开始位置。 $:匹配输入字符串的结束位置。 \b:匹配单词边界。 \B:匹配非单词边界。 三. 重复限定符 *:匹配…

电子应用设计方案101:智能家庭AI喝水杯系统设计

智能家庭 AI 喝水杯系统设计 一、引言 智能家庭 AI 喝水杯系统旨在为用户提供个性化的饮水提醒和健康管理服务,帮助用户养成良好的饮水习惯。 二、系统概述 1. 系统目标 - 精确监测饮水量和饮水频率。 - 根据用户的身体状况和活动量,智能制定饮水计划。…

数据表中的数据查询

文章目录 一、概述二、简单查询1.列出表中所有字段2.“*”符号表示所有字段3.查询指定字段数据4.DISTINCT查询 三、IN查询四、BETWEEN ADN查询1.符合范围的数据记录查询2.不符合范围的数据记录查询 五、LIKE模糊查询六、对查询结果排序七、简单分组查询1.统计数量2.统计计算平均…

【HarmonyOS NAPI 深度探索12】创建你的第一个 HarmonyOS NAPI 模块

【HarmonyOS NAPI 深度探索12】创建你的第一个 HarmonyOS NAPI 模块 在本篇文章中,我们将一步步走过如何创建一个简单的 HarmonyOS NAPI 模块。通过这个模块,你将能够更好地理解 NAPI 的工作原理,并在你的应用中开始使用 C 与 JavaScript 的…

步入响应式编程篇(二)之Reactor API

步入响应式编程篇(二)之Reactor API 前言回顾响应式编程Reactor API的使用Stream引入依赖Reactor API的使用流源头的创建 reactor api的背压模式发布者与订阅者使用的线程查看弹珠图查看形成新流的日志 前言 对于响应式编程的基于概念,以及J…

Unity Line Renderer Component入门

Overview Line Renderer 组件是 Unity 中用于绘制连续线段的工具。它通过在三维空间中的两个或两个以上的点的数组,并在每个点之间绘制一条直线。可以绘制从简单的直线到复杂的螺旋线等各种图形。 1. 连续性和独立线条 连续性:Line Renderer 绘制的线条…

使用Chrome和Selenium实现对Superset等私域网站的截图

最近遇到了一个问题,因为一些原因,我搭建的一个 Superset 的 Report 功能由于节假日期间不好控制邮件的发送,所以急需一个方案来替换掉 Superset 的 Report 功能 首先我们需要 Chrome 浏览器和 Chrome Driver,这是执行数据抓取的…

博客搭建 — GitHub Pages 部署

关于 GitHub Pages GitHub Pages 是一项静态站点托管服务&#xff0c;它直接从 GitHub 上的仓库获取 HTML、CSS 和 JavaScript 文件&#xff0c;通过构建过程运行文件&#xff0c;然后发布网站。 本文最终效果是搭建出一个域名为 https://<user>.github.io 的网站 创建…

C++17 新特性解析:Lambda 捕获 this

C17 引入了许多改进和新特性&#xff0c;其中之一是对 lambda 表达式的增强。在这篇文章中&#xff0c;我们将深入探讨 lambda 表达式中的一个特别有用的新特性&#xff1a;通过 *this 捕获当前对象的副本。这个特性不仅提高了代码的安全性&#xff0c;还极大地简化了某些场景下…

Python中采用.add_subplot绘制子图的方法简要举例介绍

Python中采用.add_subplot绘制子图的方法简要举例介绍 目录 Python中采用.add_subplot绘制子图的方法简要举例介绍一、Python中绘制子图的方法1.1 add_subplot函数1.2 基本语法&#xff08;1&#xff09;add_subplot的核心语法&#xff08;2&#xff09;add_subplot在中编程中的…

考研408笔记之数据结构(五)——图

数据结构&#xff08;五&#xff09;——图 1. 图的基本概念 1.1 图的定义 1.2 有向图和无向图 在有向图中&#xff0c;使用圆括号表示一条边&#xff0c;圆括号里元素位置互换没有影响。 在无向图中&#xff0c;使用尖括号表示一条边&#xff0c;尖括号里元素位置互换则表示…

研究生阶段 |《最优化方法》

文章目录 一、前言二、章节2.1 绪论2.1.1 最优化数学模型什么是最优化问题?最优化问题的数学模型最优解的一般概念最优化理论和方法?理论和方法有什么区别?最优化问题的分类具体的学习内容 2.1.2 用到的基本数学知识范数与内积方向导数、梯度、子梯度、Hesse矩阵以及Jacobi矩…

【软件测试项目实战 】淘宝网:商品购买功能测试

一、用例设计方法分析 在对淘宝网商品下单功能进行测试时&#xff0c;不同的测试角度和场景适合运用不同的用例设计方法&#xff0c;以下是针对该功能各方面测试所适用方法及其原因的分析&#xff1a; 商品数量相关测试&#xff1a;对于商品数量的测试&#xff0c;主要采用等…

全球化趋势与中资企业出海背景

1. 全球化趋势与中资企业出海背景 1.1 全球经济格局变化 全球经济格局正经历深刻变革&#xff0c;新兴经济体崛起&#xff0c;全球产业链重塑&#xff0c;中资企业出海面临新机遇与挑战。据世界银行数据&#xff0c;新兴市场和发展中经济体在全球 GDP 中占比已超 40%&#xff…

微信小程序web-view打开网页与网页H5跳转微信小程序

1、微信小程序web-view打开网页 目前从小程序进入网页的方法使用web-view 1.1、小程序官网需要配置业务域名 打开官网&#xff0c;选择左侧开发管理&#xff0c;选择开发设置&#xff0c;往下找到业务域名&#xff0c;添加域名。设置时需要下载校验文件&#xff0c;并将文件…

登录认证(4):令牌技术:JWT令牌

如上文所说&#xff08;登录认证&#xff08;1&#xff09;&#xff1a;登录的基本逻辑及实现思路登录&#xff09;&#xff0c;因为 HTTP协议是无状态的协议&#xff0c;我们需要使用会话跟踪技术实现同一会话中不同请求之间的数据共享&#xff0c;但Cookie技术和Session技术都…

Powershell-2

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;powershell&#xff08;2&#xff09;_哔哩哔哩_bilibili 一 、Powershell使用外部命令 在 Powershell 中&#xff0c;可以执行一些外部命令&…

Flowable 管理各业务流程:流程设计器 (获取流程模型 XML)、流程部署、启动流程、流程审批、流程挂起和激活、任务分配

文章目录 引言I 表结构主要表前缀及其用途核心表II 流程设计器(Flowable BPMN模型编辑器插件)Flowable-UIvue插件III 流程部署部署步骤例子:根据流程模型ID部署IV 启动流程启动步骤ACT_RE_PROCDEF:流程定义相关信息例子:根据流程 ID 启动流程V 流程审批审批步骤Flowable 审…

快速入门Flink

Flink是新一代实时计算平台&#xff0c;采用原生的流处理系统&#xff0c;保证了低延迟性&#xff0c;在API和容错上也是做的相当完善&#xff0c;本文将从架构、组件栈、安装、入门程序等进行基础知识的分析&#xff0c;帮助大家快速对Flink有一个了解。 一.简介 1.是什么 Ap…

VOSK实现【离线中文语音】识别

Vosk是一款开源的离线语音识别工具包&#xff0c;具有以下功能&#xff1a; 多语言支持&#xff1a;能够对20多种语言和方言进行语音识别&#xff0c;如中文、英语、德语、法语、西班牙语等&#xff0c;可满足不同用户的语言需求。 模型轻量化&#xff1a;每种语言的模型大小仅…