2024年1月



1 GFSK 背景知识

调制分类如下:
[2]

GFSK 是 FSK 的扩展,其中调制信号的频率不会在二进制数据的每个符号周期开始时立即改变。
因此,bit 0-> 1 或反之变得更加平滑。换句话说,与传统的 FSK 相比,调制信号的幅度和相位变化将相对较小。原则上,FSK 是使用独立的本地振荡器分别用于同相和正交分量来实现的,并且振荡器将在每个符号周期开始时切换以生成用于调制的载波频率。一般而言,所有独立振荡器在符号周期开始时不会处于相同的幅度和相位,因此这会导致传输信号的每个比特变化的频率突然变化。因此,调制后的 FSK 信号将非常宽,并且具有不可忽略的旁瓣。下图说明了这一点。

图 1:给定数字信号的 FSK 调制信号

信号被正确解码的前提是:在每个符号周期之后具有精确和准确的幅度和相位值。
如果接收端收到这样的信号,将会造成信道失真、干扰、热再生等,从而降低 FSK 的性能。

在 GFSK 调制方案中,需要在基带波形信号前引入高斯滤波器。典型的高斯滤波器是脉冲响应为高斯函数的滤波器(如下图为高斯脉冲响应)

图 2:高斯滤波器的脉冲响应

时阈上的高斯函数在频阈上仍是高斯函数,因此该滤波器的频率响应非常窄。当输入信号通过此类滤波器时,与不涉及滤波的 FSK 方案相比,所得到的滤波信号的频谱宽度会减小。
因此,FSK 中频率的任何突然变化都会被滤除,这使得每个符号周期开始时的转换比 FSK 相对平滑
(下图是 GFSK 信号的响应)

图 3:高斯滤波信号和 GFSK 调制信号响应

该滤波器的优点是降低边带功率,减少对相邻信道的干扰,但代价是增加码间干扰 (ISI)。
因此,仔细设计具有最佳截止频率的高斯滤波器对于确保最小化 ISI 相关影响非常重要
。此外,还可以使用其他稳健的信号处理和信道均衡技术来克服 ISI 的影响。

调制信号之前的这个滤波阶段也称为脉冲整形,因为数据脉冲被滤除以产生具有急剧上升和下降时间的干净输出信号,这有助于准确确定接收信号的载波频率。GFSK 技术非常有用,广泛用于无线系统和技术,例如改进 Layer 2 protocol,、蓝牙、IEEE 802.15.4 和 Z-wave。


2 GNU Radio GFSK 模块参数详解

GFSK Mod 的输入是字节流 byte stream,输出是基带复杂调制信号。

参数 意义 解释
Samples/Symbol Samples per baud >= 2 (integer)
Default value = 2
这有个链接介绍的比较清楚

Sensitivity Given to the Frequency Mod
Default value = 1.0
Frequency Mod
BT Gaussian filter bandwidth * symbol time (float)
Default value = 0.35
这有个 matlab 关于高斯滤波器的 BT 参数对滤波效果的影响文章

Verbose Prints the value of bits per symbol and BT
Default value = Off
Log Prints the following modulation data to .dat files:
* Chunks to Symbol data is written to "nrz.dat"
* Output of Gaussian filter is written to "gaussian_filter.dat"
* Output of frequency modulator is written to "fmmod.dat"
Default value = Off
Unpack (depreciated in GNU Radio 3.8) Unpack input byte stream?


GFSK Demod 的输入是基带复杂调制信号,输出是 a stream of bits unpacked, 1 bit per byte (the LSB)

参数 意义 解释
Samples/Symbol Samples per baud >= 2 (integer)
Sensitivity Given to the Quadrature Demod
Gain Mu Controls rate of mu adjustment
Mu Fractional delay [0.0, 1.0]
Omega Relative Limit Sets max variation in omega (float, typically 0.000200 (200 ppm))
Freq Error Bit rate error as a fraction
Verbose Print information about modulator?
Log Print modualtion data to files? (bool)


3 GNU Radio GFSK 模块简示例

我们对 9 位长的比特流
000666666011
进行 GFSK 调制,然后对其进行 GFSK 解调:


运行后效果如下:

解释:我这里采样率故意设置为 10KHz,这样在时序图中每个采样占用 0.1 MS 方便观察。从图中可以看到信号 2 和信号 3 是一致的,说明我们经过 GFSK 调制解调后数据和原来保持一致。(这里有个 delay 滑动条,用来平移原始信号,方便错开观察的,其数值的意义是延迟 n 个样本)


4 本文视频教程




参考链接

[1].什么是 GFSK 调制?
[2].Frequency-shift keying
[3].GFSK vs FSK
[4].Digital GFSK Carrier Synchronization-IEEE
[5].Samples Per Symbol
[6].FIR Gaussian Pulse-Shaping Filter Design - Matlab
[7].数字和模拟滤波器 — 示例
[8].Frequency Mod
[9].GFSK Demod


教程列表

基础教程:


综合教程:


视频和博客



:
如果觉得不错,帮忙点个支持哈~

本文全面探讨了生成学习的理论与实践,包括对生成学习与判别学习的比较、详细解析GANs、VAEs及自回归模型的工作原理与结构,并通过实战案例展示了GAN模型在PyTorch中的实现。

关注TechLead,分享AI全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人

file

一、生成学习概述

生成学习(Generative Learning)在机器学习领域中占据了重要的位置。它通过学习数据分布的方式生成新的数据实例,这在多种应用中表现出了其独特的价值。本节将深入探讨生成学习的核心概念,明确区分生成学习与判别学习,并探索生成学习的主要应用场景。

生成学习与判别学习的区别

生成学习和判别学习是机器学习中两种主要的学习方式,它们在处理数据和学习任务时有本质的区别。

判别学习(Discriminative Learning)

  • 目标
    :直接学习决策边界或输出与输入之间的映射关系。
  • 应用
    :分类和回归任务,如逻辑回归、支持向量机(SVM)。
  • 优势
    :通常在特定任务上更加高效,因为它们专注于区分数据类别。

生成学习(Generative Learning)

  • 目标
    :学习数据的整体分布,能够生成新的数据实例。
  • 应用
    :数据生成、特征学习、无监督学习等,如生成对抗网络(GANs)和变分自编码器(VAEs)。
  • 优势
    :能够捕捉数据的内在结构和分布,适用于更广泛的任务,如数据增强、新内容的创造。

生成学习的应用场景

生成学习由于其能力在模拟和学习数据的分布方面,使其在许多场景中都非常有用。

图像和视频生成

  • 概述
    :生成学习模型能够产生高质量、逼真的图像和视频内容。
  • 实例
    :GANs在这一领域尤其突出,能够生成新的人脸图像、风景图片等。

语音和音乐合成

  • 概述
    :模型可以学习音频数据的分布,生成自然语言语音或音乐作品。
  • 实例
    :深度学习技术已被用于合成逼真的语音(如语音助手)和创造新的音乐作品。

数据增强

  • 概述
    :在训练数据有限的情况下,生成学习可以创建额外的训练样本。
  • 实例
    :在医学图像分析中,通过生成新的图像来增强数据集,提高模型的泛化能力。

异常检测

  • 概述
    :模型通过学习正常数据的分布来识别异常或偏离标准的数据。
  • 实例
    :在金融领域,用于识别欺诈交易;在制造业,用于检测产品缺陷。

文本生成

  • 概述
    :生成模型能够编写逼真的文本,包括新闻文章、诗歌等。
  • 实例
    :一些先进的模型(如GPT系列)在这一领域显示了惊人的能力。

二、生成学习模型概览

file
在机器学习的众多领域中,生成学习模型因其能够学习和模拟数据的分布而显得尤为重要。这类模型的核心思想是理解和复制输入数据的底层结构,从而能够生成新的、类似的数据实例。以下是几种主要的生成学习模型及其关键特性的综述。

生成对抗网络(GANs)

生成对抗网络(GANs)是一种由两部分组成的模型:一个生成器(Generator)和一个判别器(Discriminator)。生成器的目标是产生逼真的数据实例,而判别器的任务是区分生成的数据和真实数据。这两部分在训练过程中相互竞争,生成器努力提高生成数据的质量,而判别器则努力更准确地识别真伪。通过这种对抗过程,GANs能够生成高质量、高度逼真的数据,尤其在图像生成领域表现出色。

变分自编码器(VAEs)

变分自编码器(VAEs)是一种基于神经网络的生成模型,它通过编码器将数据映射到一个潜在空间(latent space),然后通过解码器重建数据。VAEs的关键在于它们的重建过程,这不仅仅是一个简单的复制,而是对数据分布的学习和理解。VAEs在生成图像、音乐或文本等多种类型的数据方面都有出色的表现,并且由于其结构的特点,VAEs在进行特征学习和数据降维方面也显示了巨大的潜力。

自回归模型

自回归模型在生成学习中占有一席之地,尤其是在处理序列数据(如文本或时间序列)时。这类模型基于先前的数据点来预测下一个数据点,因此它们在理解和生成序列数据方面表现出色。例如,PixelRNN通过逐像素方式生成图像,每次生成下一个像素时都考虑到之前的像素。这种方法使得自回归模型在生成图像和文本方面表现出细腻且连贯的特性。

三、生成对抗网络(GANs)模型技术全解

file
生成对抗网络(GANs)是一种引人注目的深度学习模型,以其独特的结构和生成高质量数据的能力而著称。在这篇解析中,我们将深入探讨GANs的核心概念、结构、训练方法和关键技术点。

GANs的核心概念

GANs由两个主要部分组成:生成器(Generator)和判别器(Discriminator)。生成器的目的是创建逼真的数据实例,而判别器则试图区分真实数据和生成器产生的数据。这两部分在GANs的训练过程中形成一种对抗关系,相互竞争,从而推动整个模型的性能提升。

生成器(Generator)

  • 目标
    :学习数据的分布,生成逼真的数据实例。
  • 方法
    :通常使用一个深度神经网络,通过随机噪声作为输入,输出与真实数据分布相似的数据。

判别器(Discriminator)

  • 目标
    :区分输入数据是来自真实数据集还是生成器。
  • 方法
    :同样使用深度神经网络,输出一个概率值,表示输入数据是真实数据的可能性。

GANs的结构

GANs的核心在于其生成器和判别器的博弈。生成器试图生成尽可能逼真的数据以“欺骗”判别器,而判别器则努力学习如何区分真伪。这种结构创造了一个动态的学习环境,使得生成器和判别器不断进化。

网络结构

  • 生成器
    :通常是一个反卷积网络(Deconvolutional Network),负责从随机噪声中生成数据。
  • 判别器
    :通常是一个卷积网络(Convolutional Network),用于判断输入数据的真实性。

GANs的训练方法

GANs的训练过程是一个迭代过程,其中生成器和判别器交替更新。

训练过程

  1. 判别器训练
    :固定生成器,更新判别器。使用真实数据和生成器生成的数据训练判别器,目标是提高区分真假数据的能力。
  2. 生成器训练
    :固定判别器,更新生成器。目标是生成更加逼真的数据,以使判别器更难以区分真伪。

损失函数

  • 判别器损失
    :通常使用交叉熵损失函数,量化判别器区分真实数据和生成数据的能力。
  • 生成器损失
    :同样使用交叉熵损失函数,但目标是使生成的数据被判别器误判为真实数据。

GANs的关键技术点

训练稳定性

GANs的训练过程可能会非常不稳定,需要仔细调整超参数和网络结构。常见的问题包括模式崩溃(Mode Collapse)和梯度消失。

模式崩溃

当生成器开始产生有限类型的输出,而忽略了数据分布的多样性时,就会发生模式崩溃。这通常是因为判别器过于强大,导致生成器找到了欺骗判别器的“捷径”。

梯度消失

在GANs中,梯度消失通常发生在判别器过于完美时,生成器的梯度

变得非常小,导致学习停滞。

解决方案

  • 架构调整
    :如使用深度卷积GAN(DCGAN)等改进的架构。
  • 正则化和惩罚
    :如梯度惩罚(Gradient Penalty)。
  • 条件GANs
    :通过提供额外的条件信息来帮助生成器和判别器的训练。

四、变分自编码器(VAEs)模型技术全解

file
变分自编码器(VAEs)是一种强大的生成模型,在机器学习和深度学习领域中得到了广泛的应用。VAEs通过学习数据的潜在表示(latent representation)来生成新的数据实例。本节将全面深入地探讨VAEs的工作原理、网络结构、训练方法及其在实际应用中的价值。

VAEs的工作原理

VAEs的核心思想是通过潜在空间(latent space)来表示数据,这个潜在空间是数据的压缩表示,捕捉了数据的关键特征。VAEs由两个主要部分组成:编码器(Encoder)和解码器(Decoder)。

编码器(Encoder)

编码器的作用是将输入数据映射到潜在空间。它输出潜在空间中的两个参数:均值(mean)和方差(variance)。这些参数定义了一个概率分布,从中可以抽取潜在表示。

解码器(Decoder)

解码器的任务是从潜在表示重构数据。它接收潜在空间中的点并生成与原始输入数据相似的数据。

VAEs的网络结构

VAEs的网络结构通常包括多层全连接层或卷积层,具体结构取决于输入数据的类型。对于图像数据,通常使用卷积层;对于文本或序列数据,则使用循环神经网络(RNN)或变换器(Transformer)。

潜在空间

潜在空间是VAEs的关键,它允许模型捕捉数据的内在结构。在这个空间中,相似的数据点被映射到靠近的位置,这使得生成新数据变得可行。

VAEs的训练方法

VAEs的训练涉及最大化输入数据的重构概率的同时,确保潜在空间的分布接近先验分布(通常是正态分布)。

重构损失

重构损失测量解码器生成的数据与原始输入数据之间的差异。这通常通过均方误差(MSE)或交叉熵损失来实现。

KL散度

KL散度用于量化编码器输出的概率分布与先验分布之间的差异。最小化KL散度有助于保证潜在空间的平滑和连续性。

VAEs的价值和应用

VAEs在多种领域都有显著的应用价值。

数据生成

由于VAEs能够捕捉数据的潜在分布,它们可以用于生成新的、逼真的数据实例,如图像、音乐等。

特征提取和降维

VAEs在潜在空间中提供了数据的紧凑表示,这对特征提取和降维非常有用,尤其是在复杂数据集中。

异常检测

VAEs可以用于异常检测,因为异常数据点通常不会被映射到潜在空间的高密度区域。

五、自回归模型技术全解

file
自回归模型在生成学习领域中占据了独特的位置,特别是在处理序列数据如文本、音乐或时间序列分析等方面。这些模型的关键特性在于利用过去的数据来预测未来的数据点。在本节中,我们将全面深入地探讨自回归模型的工作原理、结构、训练方法及其应用价值。

自回归模型的工作原理

自回归模型的核心思想是利用之前的数据点来预测下一个数据点。这种方法依赖于假设:未来的数据点与过去的数据点有一定的相关性。

序列数据的处理

对于序列数据,如文本或时间序列,自回归模型通过学习数据中的时间依赖性来生成或预测接下来的数据点。这意味着模型的输出是基于先前观察到的数据序列。

自回归模型的网络结构

自回归模型可以采用多种网络结构,具体取决于应用场景和数据类型。

循环神经网络(RNNs)

对于时间序列数据或文本,循环神经网络(RNNs)是常用的选择。RNN能够处理序列数据,并且能够记忆先前的信息,这对于捕捉时间序列中的长期依赖关系至关重要。

卷积神经网络(CNNs)

在处理像素数据时,如图像生成,卷积神经网络(CNNs)也可以用于自回归模型。例如,PixelCNN通过按顺序生成图像中的每个像素来创建完整的图像。

自回归模型的训练方法

自回归模型的训练通常涉及最大化数据序列的条件概率。

最大似然估计

自回归模型通常使用最大似然估计来训练。这意味着模型的目标是最大化给定之前观察到的数据点后,生成下一个数据点的概率。

序列建模

在训练过程中,模型学习如何根据当前序列预测下一个数据点。这种方法对于文本生成或时间序列预测尤其重要。

自回归模型的价值和应用

自回归模型在许多领域都显示出了其独特的价值。

文本生成

在自然语言处理(NLP)中,自回归模型被用于文本生成任务,如自动写作和语言翻译。

音乐生成

在音乐生成中,这些模型能够基于已有的音乐片段来创建新的旋律。

时间序列预测

在金融、气象学和其他领域,自回归模型用于预测未来的数据点,如股票价格或天气模式。

六、GAN模型案例实战

在本节中,我们将通过一个具体的案例来演示如何使用PyTorch实现一个基础的生成对抗网络(GAN)。这个案例将重点放在图像生成上,展示如何训练一个GAN模型以生成手写数字图像,类似于MNIST数据集中的图像。

场景描述

目标
:训练一个GAN模型来生成看起来像真实手写数字的图像。

数据集
:MNIST手写数字数据集,包含0到9的手写数字图像。

输入
:生成器将接收一个随机噪声向量作为输入。

输出
:生成器输出一张看起来像真实手写数字的图像。

处理过程

  1. 数据准备
    :加载并预处理MNIST数据集。
  2. 模型定义
    :定义生成器和判别器的网络结构。
  3. 训练过程
    :交替训练生成器和判别器。
  4. 图像生成
    :使用训练好的生成器生成图像。

PyTorch实现

1. 导入必要的库

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt

2. 数据准备

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)

3. 定义模型

生成器

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(100, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 1024),
            nn.LeakyReLU(0.2),
            nn.Linear(1024, 784),
            nn.Tanh()
        )

    def forward(self, x):
        return self.model(x).view(-1, 1, 28, 28)

判别器

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(784, 1024),
            nn.LeakyReLU(0.2),
            nn.Dropout(0.3),
            nn.Linear(1024, 512),
            nn.LeakyReLU(0.2),
            nn.Dropout(0.3),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )

    def forward(self, x):
        x = x.view(x.size(0), -1)
        return self.model(x)

4. 初始化模型和优化器

generator = Generator()
discriminator = Discriminator()

optimizer_G = optim.Adam(generator.parameters(), lr=0.0002)
optimizer_D = optim.Adam(discriminator.parameters(), lr=0.0002)

criterion = nn.BCELoss()

5. 训练模型

epochs = 50
for epoch in range(epochs):
    for i, (images, _) in enumerate(train_loader):
        # 真实图像标签是1,生成图像标签是0
        real_labels = torch.ones(images.size(0), 1)
        fake_labels = torch.zeros(images.size(0), 1)

        # 训练判别器
        outputs = discriminator(images)
        d_loss_real = criterion(outputs, real_labels)
        real_score = outputs

        z = torch.randn(images.size(0), 100)
        fake_images = generator(z)
        outputs = discriminator(fake_images.detach())
        d_loss_fake = criterion(outputs, fake_labels)
        fake_score = outputs

        d_loss = d_loss_real + d_loss_fake
        optimizer_D.zero_grad()
        d_loss.backward()
        optimizer_D.step()

        # 训练生成器
        outputs = discriminator(fake_images)
        g_loss = criterion(outputs, real_labels)



        optimizer_G.zero_grad()
        g_loss.backward()
        optimizer_G.step()
        
    print(f'Epoch [{epoch+1}/{epochs}], d_loss: {d_loss.item():.4f}, g_loss: {g_loss.item():.4f}')

6. 生成图像

z = torch.randn(1, 100)
generated_images = generator(z)
plt.imshow(generated_images.detach().numpy().reshape(28, 28), cmap='gray')
plt.show()

七、总结

在深入探讨了生成学习的核心概念、主要模型、以及实际应用案例后,我们可以对这一领域有一个更加全面和深入的理解。生成学习不仅是机器学习的一个分支,它更是开启了数据处理和理解新视角的关键。

生成学习的多样性和灵活性

生成学习模型,如GANs、VAEs和自回归模型,展示了在不同类型的数据和应用中的多样性和灵活性。每种模型都有其独特的特点和优势,从图像和视频的生成到文本和音乐的创作,再到复杂时间序列的预测。这些模型的成功应用证明了生成学习在捕捉和模拟复杂数据分布方面的强大能力。

创新的前沿和挑战

生成学习领域正处于不断的创新和发展之中。随着技术的进步,新的模型和方法不断涌现,推动着这一领域的边界不断扩展。然而,这也带来了新的挑战,如提高模型的稳定性和生成质量、解决训练过程中的问题(如模式崩溃),以及增强模型的解释性和可控性。

跨学科的融合和应用

生成学习在多个学科之间架起了桥梁,促进了不同领域的融合和应用。从艺术创作到科学研究,从商业智能到社会科学,生成学习的应用为这些领域带来了新的视角和解决方案。这种跨学科的融合不仅推动了生成学习技术本身的进步,也为各领域的发展提供了新的动力。

未来发展的趋势

未来,我们可以预见生成学习将继续在模型的复杂性、生成质量、以及应用领域的广度和深度上取得进步。随着人工智能技术的发展,生成学习将在模仿和扩展人类创造力方面发挥越来越重要的作用,同时也可能带来关于伦理和使用的新讨论。

关注TechLead,分享AI全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人

如有帮助,请多关注
TeahLead KrisChang,10+年的互联网和人工智能从业经验,10年+技术和业务团队管理经验,同济软件工程本科,复旦工程管理硕士,阿里云认证云服务资深架构师,上亿营收AI产品业务负责人。


Redis24篇集合

1 啥是Redis Module

Redis Module是Redis的一种扩展模块,从 4.0版本开始,允许用户自定义扩展模块,在Redis内部实现新的数据类型和功能,使用统一的调用方式和传输协议格式扩展Redis的能力。它本身的设计目的就是在不同版本的Redis中运行,因此无需重新编译模块即可与特定版本(Redis > 4.0)一起运行。

通过使用Redis Module,可以在Redis中添加新的命令和数据类型,以实现更高级的功能。例如,一些第三方模块支持全文搜索、JSON数据的存储和查询、自定义分布式锁、时间序列数据的存储和查询等。我们也可以基于 Redis 去定制开发属于自己的 Module,来支撑自己的业务发展。

2 有哪些常见的Redis Module

目前,被 Redis 官方推荐的 Module 有:

2.1 RediSearch

一个功能齐全,可实现 快速检索、二次索引和全文搜索的搜索引擎模块
image
地址:
https://github.com/RediSearch/RediSearch

2.2 RedisJSON

RedisJSON是一个用于处理 JSON 数据的模块,它实现了JSON数据交换标准,允许从Redis 文档中存储、更新和获取JSON值。
地址:
https://github.com/RedisJSON/RedisJSON

2.3 RedisTimeSeries

RedisTimeSeries是Redis的一个时间序列数据库(TSDB)管理模块。RedisTimeSeries可以保存多个时间序列,每个时间序列都可以通过一个Redis键访问(类似于任何其他Redis数据结构)。
地址:
https://github.com/RedisTimeSeries/RedisTimeSeries

2.4 RedisGraph

用于实现图形数据库的模块
https://github.com/RedisGraph/RedisGraph?tab=readme-ov-file#trying-redisgraph

2.5 RedisBloom

RedisBloom为Redis增加了一组概率数据结构,包括Bloom filter(布隆过滤器), Cuckoo filter, Count-min sketch, Top-K和t-digest。使用此功能,您可以查询流数据,而无需存储流的所有元素。每种概率数据结构都应对不同的业务场景。
地址:
https://github.com/RedisBloom/RedisBloom

2.6 RedisCell

实现分布式限流能力的模块,使用了相对精妙的算法 generic cell rate algorithm (GCRA) 。
地址:
https://github.com/brandur/redis-cell

2.7 RedisAI

RedisAI是一个Redis模块,用于执行深度学习/机器学习模型并管理其数据。它的目的是成为模型服务的“主力军”,为流行的DL/ML框架提供开箱即用的支持和无与伦比的性能。RedisAI坚持数据本地化原则,最大限度地提高了计算吞吐量,减少了延迟。
地址:
https://github.com/RedisAI/RedisAI

上面给出的几个都是GitHub小星星比较高的模块,如果你想要了解很多的内容,可以参考官方文档:
https://redis.io/modules。

3 Redis module 实践指南

3.1 安装Redis

因为这些扩展模块是依赖于Redis服务存在的,所以前提是Redis安装完成。
Redis官方安装指南:
https://redis.io/docs/install/install-redis/

3.2 以RedisJson 为例子

3.2.1 下载RedisJSON

github上下载ReJSON的地址:
https://github.com/RedisJSON/RedisJSON/releases
官方使用文档:
https://redis.io/docs/data-types/json/
可以看到,最新的版本是 v2.6.8
image

3.2.2 安装

1、获取文件

在 redis 安装目录下新建 module 文件夹,把获取到的rejson.so文件 放到 module 文件夹中

# 先进入redis的安装目录
cd /usr/local/soft/redis-6.2.6/

# 然后新建一个module文件夹
mkdir module

# 最后rejson.so文件放到module文件夹即可

2、修改配置

# 进入到module目录下
cd ./module

# 为文件加X,即修改rejson.so为可执行文件
chmod +x rejson.so

# 修改 redis.conf,初始时自动 loadmodule
loadmodule /usr/local/soft/redis-6.2.6/module/rejson.so

# 重启 redis
redis-cli -a 123456 shutdown
redis-server conf/redis.conf
redis-cli -p 6379

3.2.3 RedisJSON操作实践

1、写入JSON.SET

语法解析

JSON.SET <key> <path> <json>  [NX | XX]
  • key:要操作的键。
  • path:JSON路径,用于指定要设置值的对象或数组的路径。
  • json:要设置的JSON值。
  • [NX | XX]:可选参数,用于指定操作的条件。
    • NX:仅当指定的键不存在时,才设置值。
    • XX:仅当指定的键存在时,才设置值。

比如下面,保存两条数据

# 保存两条记录
127.0.0.1:6379> JSON.SET user1 $ '{"name":"Brand", "age":18, "sex":"1"}'
127.0.0.1:6379> JSON.SET user2 $ '{"name":"Candy", "age":17, "sex":"0"}'

2、读取操作JSON.GET

语法解析

JSON.GET <key>
         [INDENT indentation-string]
         [NEWLINE line-break-string]
         [SPACE space-string]
         [path ...]
  • Key:要获取值的键
  • INDENT:查询结果替换掉默认缩进字符
  • NEWLINE:查询结果替换掉默认换行符
  • SPACE:查询结果替换掉默认空格
  • path:允许使用多个path进行查询
  • 获取JSON对象中的属性时需要以.开头

比如下面,获取相关信息

# 得到myjson的所有数据
127.0.0.1:6379> JSON.GET user1
1) '{"name":"Brand", "age":18, "sex":"1"}'

# 得到json数据中的键name的值
127.0.0.1:6379> JSON.GET user1 name
1) "Brand"

3、批量读取操作JSON.MGET

这是一个批量操作的语法,语法解析:

JSON.MGET <key> [key ...] <path>
  • key:是一个列表,指的是可以有多个key
  • path:指所有key的path,即遍历每一个key的path,如果不存在,则返回null

下面的语句演示MGET获取批量数据:

127.0.0.1:6379> JSON.MGET user1 user2 $.name
1) "Brand"
2) "Candy"

4、除操作JSON.DEL
这是删除操作的语法,语法解析:

JSON.DEL <key> [path]
  • path是可选参数,如果没有提供,则默认整个Key删除掉

下面语句中

# 删除整个Json
127.0.0.1:6379> JSON.DEL user1
(integer) 1

# 删除某个字段
127.0.0.1:6379> JSON.DEL user2  $.age
1) "17"

5、其他操作类行
除了上面的几种常见操作,官方还支持如下命令,官方命令地址:
https://redis.io/docs/data-types/json/
常用命令(数字可以递增、相乘):

# JSON.NUMINCRBY,JSON.NUMMULTBY,JSON.STRAPPEND,JSON.STRLEN

> JSON.SET num $ 0
OK
> JSON.NUMINCRBY num $ 1
"[1]"
> JSON.NUMINCRBY num $ 1.5
"[2.5]"
> JSON.NUMINCRBY num $ -0.75
"[1.75]"
> JSON.NUMMULTBY num $ 24
"[42]"

数组命令:

#  JSON.ARRAPPEND,JSON.ARRINDEX,JSON.ARRINSERT,JSON.ARRLEN,JSON.ARRPOP,JSON.ARRTRIM

> JSON.SET arr $ []
OK
> JSON.ARRAPPEND arr $ 0
1) (integer) 1
> JSON.GET arr $
"[[0]]"
> JSON.ARRINSERT arr $ 0 -2 -1
1) (integer) 3
> JSON.GET arr $
"[[-2,-1,0]]"
> JSON.ARRTRIM arr $ 1 1
1) (integer) 1
> JSON.GET arr $
"[[-1]]"
> JSON.ARRPOP arr $
1) "-1"
> JSON.ARRPOP arr $
1) (nil)

对象命令(对象长度和key值输出):

#  JSON.OBJKEYS,JSON.OBJLEN

> JSON.SET obj $ '{"name":"Leonard Cohen","lastSeen":1478476800,"loggedOut": true}'
OK
> JSON.OBJLEN obj $
1) (integer) 3
> JSON.OBJKEYS obj $
1) 1) "name"
   2) "lastSeen"
   3) "loggedOut"

组件命令:
JSON.TYPE,JSON.DEBUG,JSON.FORGET,JSON.RESP

4 总结

本文介绍了Redis的扩展模块Redis Module,设计目的就是使用加载Redis Module,在Redis中添加新的命令和数据类型,以实现更高级的功能。
文章介绍了目前官方推荐的几个Module,并演示了RedisJSON的效果。

一、前言

大家好,这里是白泽。随着 Go 语言在云原生领域大放异彩,开发者逐渐将目光转移到了这门语言上,而容器则是云原生时代最核心的载体。

《Woodpecker CI 设计分析》系列文章将分析开源 CI 引擎
Woodpecker
的架构设计,探究 Go 协程是如何支持由
Workflow
定义的大量
Task
的频繁创建和调度。


Task
的一切活动都将在容器内进行。因此这个系列的文章也是帮助你开拓 Go 云原生领域编程的一柄利剑。

这是《Woodpecker CI 设计分析》系列的第一篇文章,主要讲解 Woodpecker 的整体架构设计和体验部署使用,后续文章将讲解核心组件源码设计,并从0开始仿写 Woodpecker 核心组件,欢迎追更~

image-20240115204716944

公众号 「白泽talk」,白泽目前正在打造一个氛围良好的行业交流群,文章的更新也会提前预告,欢迎加入:622383022。

我也开源了一个
Go 学习仓库
:包含 Go 各阶段学习文章、读书笔记、电子书、简历模板等,欢迎 star。

二、CI 配置前瞻


一:背景

1. 讲故事

前些天有位朋友找到我,说他的程序几天内存就要爆一次,不知道咋回事,找不出原因,让我帮忙看一下,这种问题分析dump是最简单粗暴了,拿到dump后接下来就是一顿分析。

二:WinDbg 分析

1. 程序为什么会暴

程序既然会爆,可能是虚拟地址受限,也可能是系统内存不足,可以用
!address -summary
观察下。


0:037> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
<unknown>                               866          53577000 (   1.302 GB)  69.38%   65.11%
Image                                  2244          16ee2000 ( 366.883 MB)  19.09%   17.91%
Heap                                    222           8adc000 ( 138.859 MB)   7.23%    6.78%
Free                                    460           7e14000 ( 126.078 MB)            6.16%
Stack                                   255           5150000 (  81.312 MB)   4.23%    3.97%
TEB                                      85             db000 ( 876.000 kB)   0.04%    0.04%
Other                                    20             79000 ( 484.000 kB)   0.02%    0.02%
PEB                                       1              3000 (  12.000 kB)   0.00%    0.00%
...
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT                             2900          64906000 (   1.571 GB)  83.72%   78.57%
MEM_RESERVE                             793          138d6000 ( 312.836 MB)  16.28%   15.28%
MEM_FREE                                460           7e14000 ( 126.078 MB)            6.16%
...

从卦中可以明显的看出,这又是一例经典的32bit程序受到了2G的内存限制,按往期经验来说解决办法比较简单,改成大地址或者x64即可。

哈哈,既然要分享这篇,自然就不是这么简单的事情,这需要我们排查这个溢出是不是程序的bug导致的,如果是那还得继续找原因。

2. 是程序bug导致的吗

要想搞清楚这个问题,需要去分析各处的内存占用,比如托管堆,可以用
!eeheap -gc
观察。


0:037> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x49fd10a8
generation 1 starts at 0x49fd1000
generation 2 starts at 0x03381000
ephemeral segment allocation context: none
 segment     begin  allocated      size
03380000  03381000  0437ff88  0xffef88(16773000)
23e60000  23e61000  24e5ff88  0xffef88(16773000)
0b510000  0b511000  0c50ff88  0xffef88(16773000)
...
7be20000  7be21000  7cbbdb60  0xd9cb60(14273376)
49fd0000  49fd1000  4afcfe08  0xffee08(16772616)
Large object heap starts at 0x04381000
 segment     begin  allocated      size
04380000  04381000  04a67b50  0x6e6b50(7236432)
Total Size:              Size: 0x39738ad4 (963873492) bytes.
------------------------------
GC Heap Size:    Size: 0x39738ad4 (963873492) bytes.

从卦中可以看到,托管堆占用963M,并且产生了很多的16M的segment,这就表明当前的托管堆吃掉了内存,接下来的问题是为什么托管堆吃了那么多的内存呢?那就只能用
!dumpheap -stat
去观察下托管堆的对象布局咯。


0:037> !dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
...
717c8b4c   264594     11642136 System.Threading.ExecutionContext
717cd044   265930     13034088 System.Collections.Hashtable+bucket[]
717ccff4   265854     13824408 System.Collections.Hashtable
71761c34   268005     17152320 System.Threading.OverlappedData
70d73c10   264469     26446900 System.Net.Sockets.OverlappedAsyncResult
717cdd04   280225    293649193 System.Byte[]
013a9f98   269886    540566904      Free
Total 3880354 objects

从卦中可以看到当前托管堆有
26.8w
的 OverlappedData 对象,这是一个非常明显的异常信号,熟悉这块的朋友应该知道,这个东西常常和异步打交道,也就表示当前程序可能有高达 26.8w 的异步请求可能没有得到响应,要想找到这个答案,就需要对 OverlappedData 进行穿刺。

3. OverlappedData 穿刺检查


OverlappedData
穿刺的目的就是要活检内部的
AsyncCallback
回调函数,看看到底是良性还是恶性的,相关命令如下:


0:037> !dumpheap -stat
...
34f38ac4 71761c34       64         
34f39088 71761c34       64   
...
0:037> !mdt 34f39088
34f39088 (System.Threading.OverlappedData)
    m_asyncResult:33e8aafc (System.Net.Sockets.OverlappedAsyncResult)
    m_iocb:03c077a0 (System.Threading.IOCompletionCallback)
    ...
    m_nativeOverlapped:(System.Threading.NativeOverlapped) VALTYPE (MT=7176dfe0, ADDR=34f390b0)
0:037> !mdt 33e8aafc
33e8aafc (System.Net.Sockets.OverlappedAsyncResult)
    m_AsyncObject:03c71d44 (System.Net.Sockets.Socket)
    m_AsyncState:33e8aaec (xxx)
    m_AsyncCallback:03e8f214 (System.AsyncCallback)
    ...
0:037> !mdt 03e8f214
03e8f214 (System.AsyncCallback)
    _target:03c065a8 (xxx)
    _methodPtr:19432480 (System.IntPtr)
0:037> u 19432480
19432480 e933932102      jmp     1b64b7b8
19432485 5f              pop     edi
...
0:037> !ip2md 1b64b7b8
MethodDesc:   131605ac
Method Name:  xxxDevices.ReceiveCallback(System.IAsyncResult)

卦中的信息量还是蛮大的,可以看到这是一个和 Socket 相关的异步函数,并且也成功找到了
xxxDevices.ReceiveCallback
回调函数,接下来就是检查下这个方法附近的业务逻辑,由于代码会涉及到一些隐私,我就多模糊一点,请见谅,截图如下:

仔细阅读这段代码,他是想用异步的方式一次次的用
byte[1024]
去丈量一段可能的大数据,直到这个 Stream 不能再读了,所以用了
if (stream.CanRead)
判断。

对 Socket 编程比较熟悉的朋友相信很快就能发现问题,判断 Stream 中的数据是否读完应该用
DataAvailable
属性,而不是
CanRead
,比如下面这段正确的代码:

最后再贴VS中对
CanRead

DataAvailable
属性的解释。


//
// Summary:
//     Gets a value that indicates whether the System.Net.Sockets.NetworkStream supports
//     reading.
//
// Returns:
//     true if data can be read from the stream; otherwise, false. The default value
//     is true.
public override bool CanRead { get; }

//
// Summary:
//     Gets a value that indicates whether data is available on the System.Net.Sockets.NetworkStream
//     to be read.
//
// Returns:
//     true if data is available on the stream to be read; otherwise, false.
//
public virtual bool DataAvailable { get; }

三:总结

这个事故非常有意思,一个简简单单的
CanRead
误用就对程序造成了毁灭性的打击,这也警示大家在用某个属性某个方法前,一定要先搞清楚它到底是怎么玩的。

图片名称