2024年9月

前言

本文介绍一款使用 C# 与 WPF 开发的音频播放器,其界面简洁大方,操作体验流畅。该播放器支持多种音频格式(如 MP4、WMA、OGG、FLAC 等),并具备标记、实时歌词显示等功能。

另外,还支持换肤及多语言(中英文)切换。核心音频处理采用 FFmpeg 组件,获得了广泛认可,目前 GitHub 上已收获 1.8K 星标。

项目介绍

Dopamine 是一款简单好用的、功能强大的、完全免费且无广告的本地音乐播放器。它有绿色便携版(30MB)和可安装版(34MB),目前仅支持 Windows 平台。

它使用 C# 编写并由 CSCore 音频库提供支持。播放器使用了根据 LGPLv2.1 许可的 FFmpeg 代码。

项目页面

首次启动时,它会让我们选择主题颜色、显示语言、库文件(音乐文件所在的目录)。

基本配置好了,就可以看到软件界面

它的界面非常干净,如下图所示:

用户界面与动画效果

这款软件不仅界面美观,还拥有许多令人着迷的动画效果。例如,切换歌曲时软件左下角的动画以及音乐可视化效果等,都显得流畅而自然。

音频格式支持与封面模式

它支持播放 MP3、WMA、OGG、FLAC、M4A、AAC、WAV、APE 和 OPUS 等格式的音频文件。

点击右上角的【切换播放器】按钮,可以开启【封面模式】。

在这个模式下,仅显示歌曲封面和少量控制按钮。

在【封面模式下】,鼠标右键点击它的界面的任意位置,可以打开【简洁模式】和【迷你模式】:

任务栏模式

最小化主窗口后,点击任务栏上的图标可以进入【任务栏模式】

歌词功能

它支持歌词滚动,既可以导入本地歌词文件,也支持从网易云、虾米音乐ChartLyrics、LoloLyrics 和 MetroLyrics 等平台自动获取歌词。

更多功能探索

本文仅展示了部分功能,实际上还有许多其他设置和功能等待你的发现。如果感兴趣,欢迎下载并亲自体验。

项目源码

可以下载 Dopamine 的对应源代码进行学习,以深入了解其设计原理和实现细节。

可以帮助我们提升编程技能,还能为我们提供一个参考案例。

项目结构具体如下:

项目地址

GitHub:
https://github.com/digimezzo/dopamine-windows

下载地址:
https://github.com/digimezzo/dopamine-windows/releases

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号
[DotNet技术匠]
社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!

Android
平台从上到下,无需ROOT/解锁/刷机,应用级拦截框架的最后一环 ——
SVC
系统调用拦截。

☞ Github ☜

由于我们虚拟化产品的需求,需要支持在普通的
Android
手机运行。我们需要搭建覆盖应用从上到下各层的应用级拦截框架,而
Abyss
作为系统
SVC
指令的调用拦截,是我们最底层的终极方案。

01. 说明

tracee:

ptrace
附加的进程,通常为目标应用进程。

tracer:
用来
ptrace
其他进程的进程,在该进程里处理系统调用。

本框架利用
Android

Provider
组件启动拦截处理的服务进程,进程启动后创建独立的一个线程循环处理所有拦截的系统调用回调。由于本工程只是演示方案的可行性并打印日志,所以业务逻辑处理比较简单,可以根据需要的自行扩展。

若要接入具体业务,可能需要改成多线程的方式进行处理,提升稳定性。不过我们实测多线切换也有一定损耗,性能提升有限,但确实稳定性有提升,防止某个处理耗时导致应用所有进程阻塞。

02. 处理流程

应用进程
tracee
被附加流程如下:

tracer
过程如下:

说明:
使用
fork()
的目的是为了让工作线程去附加。
ptrace
有严格的限制,只有执行附加
attach
的线程才有权限操作对应
tracee
的寄存器。

03. 系统调用处理

03.01 忽略库机制

由于业务的需要,为了提升性能,我们需要忽略某些库中的系统调用,如:
libc.so


find_libc_exec_maps()
中找到
libc.so
可执行代码在
maps
中的内存地址区间,需要处理的系统调用:

//enable_syscall_filtering()    
FilteredSysnum internal_sysnums[] = {
    { PR_ptrace,		FILTER_SYSEXIT },
    { PR_wait4,		FILTER_SYSEXIT },
    { PR_waitpid,		FILTER_SYSEXIT },
    { PR_execve,		FILTER_SYSEXIT },
    { PR_execveat,		FILTER_SYSEXIT },
    {PR_readlinkat,   FILTER_SYSEXIT}, //暂时没有处理
};

set_seccomp_filters
针对不同的
arch
,设置系统调用的
ebpf
。不同架构的
ebpf
语句会填充到一起,
ebpf
的组成伪代码如下:

for (每一种架构) {
	start_arch_section;
	for (每一个当前架构下的系统调用)
    	add_trace_syscall;
   end_arch_section;
}
finalize_program_filter;

start_arch_section;// 架构相关处理的ebpf,包括libc筛选的语句
add_trace_syscall;// 增加匹配要处理系统调用的ebpf语句
end_arch_section;// 尾部的ebpf语句(语句含义:匹配到系统调用则返回)
finalize_program_filter;// 最后面的ebpf语句,杀死其他异常情况下的线程

最终,调用如下语句,设置
ebpf

status = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program);

03.02 PR_ptrace

因为一个
tracee
只能有一个
tracer
,所以需要处理该系统调用,在应用本身使用了
ptrace
的时候进行仿真。

系统调用进入前,将系统调用替换为
PR_void
,不做真正的
ptrace
,后续仿真。

退出系统调用后,针对
ptrace
的仿真。针对请求是
PTRACE_ATTACH

PTRACE_TRACEME
等做各种不同的处理。同时也处理
PTRACE_SYSCALL

PTRACE_CONT

PTRACE_SETOPTIONS

PTRACE_GETEVENTMSG
等各种
ptrace
操作。

ptrace
有各种各样的请求,完整的处理逻辑比较复杂(我们还在消化中)。

03.03 PR_wait4、PR_waitpid

配合
PR_ptrace
使用,如果当前的
tracee
不是一个
tracer
,则不处理直接透传给系统。或者
wait
的第一个参数不为
-1
,则去集合里找看等待的这个线程是否存在并且是否是当前处理线程的
tracee
,如果不是,则不处理直接透传给系统。

处理的逻辑如下:

系统调用进入前,将系统调用替换为
PR_void
,不实际传给内核。

退出系统调用后,仿真
tracer

wait
的处理逻辑。主要为基于当前处理的这个
tracer
(代码里定义为
ptracer
),去遍历它的
tracee
,看是否有事件需要被处理,如有,则填充好寄存器,唤醒当前正在被处理的这个
tracer

03.04 PR_execve、PR_execveat

主要是在
USE_LOADER_EXE
开启时,将
native
程序替换为使用一个固定的
loader
来加载程序。

03.05 拦截日志

E INTERCEPT/SYS: vpid 2: got event 7057f
E INTERCEPT: vpid 2,secomp_enabled 0,
E INTERCEPT/SYS: (null) info: vpid 2: sysenter start: openat(0xffffff9c, 0xb4000073c72fcd60, 0x0, 0x0, 0xb4000073c72fcd88, 0xb4000073c72fcde8) = 0xffffff9c [0x7367d45e80, 0]
E INTERCEPT/SYS: vpid 2: open path:/system/fonts/NotoSansMalayalamUI-VF.ttf
E INTERCEPT/SYS: syscall_number:216
E INTERCEPT/SYS: vpid 2,openat: /system/fonts/NotoSansMalayalamUI-VF.ttf
E INTERCEPT/SYS: (null) info: vpid 2: sysenter end: openat(0xffffff9c, 0xb4000073c72fcd60, 0x0, 0x0, 0xb4000073c72fcd88, 0xb4000073c72fcde8) = 0xffffff9c [0x7367d45e80, 0]
E INTERCEPT/SYS: vpid 2: open path:/system/fonts/NotoSansMalayalamUI-VF.ttf
E INTERCEPT/SYS: (null) info: vpid 2: restarted using 7, signal 0, tracee pid 32222,app_pid 32162

E/INTERCEPT/SYS: (null) info: vpid 3: sysenter start: close(0x90, 0x0, 0x7492d0d088, 0x6, 0x73b7b82860, 0x73b7b82880) = 0x90 [0x73633faae0, 0]
E/INTERCEPT/SYS: syscall_number:41
E/INTERCEPT/SYSW: noting to do,sn:41
E/INTERCEPT/SYS: (null) info: vpid 3: sysenter end: close(0x90, 0x0, 0x7492d0d088, 0x6, 0x73b7b82860, 0x73b7b82880) = 0x90 [0x73633faae0, 0]
E/INTERCEPT/SYS: (null) info: vpid 3: restarted using 7, signal 0, tracee pid 32223,app_pid 32162
E/INTERCEPT/SYS: vpid 3: got event 7057f

04. 附

额外模块:

由于本框架会在原应用中增加一个处理进程,并且会
trace
到应用进程中,因此在实际使用时,还需要对新增进程和
trace
痕迹进行隐藏,防止与应用检测模块冲突,支持完整的应用自身
trace
调用的仿真。

这是附加的应用对抗模块,后面会作为单独文章分享给大家。

参考项目:

https://github.com/proot-me/proot

https://github.com/termux/proot

论文深入探讨了层级注意力与一般注意力机制之间的区别,并指出现有的层级注意力方法是在静态特征图上实现层间交互的。这些静态层级注意力方法限制了层间上下文特征提取的能力。为了恢复注意力机制的动态上下文表示能力,提出了一种动态层级注意力(
DLA
)架构。
DLA
包括双路径,其中前向路径利用一种改进的递归神经网络块用于上下文特征提取,称为动态共享单元(
DSU
),反向路径使用这些共享的上下文表示更新特征。最后,注意力机制应用于这些动态刷新后的层间特征图。实验结果表明,所提议的
DLA
架构在图像识别和目标检测任务中优于其他最先进的方法。

来源:晓飞的算法工程笔记 公众号,转载请注明出处

论文: Strengthening Layer Interaction via Dynamic Layer Attention

Introduction


众多研究强调了增强深度卷积神经网络(
DCNNs
)中层级间交互的重要性,这些网络在各种任务中取得了显著进展。例如,
ResNet
通过在两个连续层之间引入跳跃连接,提供了一种简单而高效的实现方式。
DenseNet
通过回收来自所有前置层的信息,进一步改善了层间交互。与此同时,注意力机制在
DCNNs
中的作用越来越重要。注意力机制在
DCNNs
中的演变经历了多个阶段,包括通道注意力、空间注意力、分支注意力以及时空注意力。

最近,注意力机制已成功应用于另一个方向(例如,
DIANet

RLANet

MRLA
),这表明通过注意力机制增强层间交互是可行的。与
ResNet

DenseNet
中简单的交互方式相比,引入注意力机制使得层间交互变得更加紧密和有效。
DIANet
在网络的深度上采用了一个参数共享的
LSTM
模块,以促进层间交互。
RLANet
提出了一个层聚合结构,用于重用前置层的特征,从而增强层间交互。
MRLA
首次引入了层级注意力的概念,将每个特征视为一个标记,通过注意力机制从其他特征中学习有用的信息。

然而,论文发现现有的层级注意力机制存在一个共同的缺点:它们以静态方式应用,限制了层间信息交互。在通道和空间注意力中,对于输入
\(\boldsymbol{x} \in \mathbb{R}^{C \times H \times W}\)
,标记输入到注意力模块,所有这些标记都是从
\(\boldsymbol{x}\)
同时生成的。然而,在现有的层级注意力中,从不同时间生成的特征被视为标记并传入注意力模块,如图
1(a)
所示。由于早期生成的标记一旦产生就不会改变,因此输入的标记相对静态,这导致当前层与前置层之间的信息交互减少。


2(a)
可视化了在
CIFAR-100
上训练的
ResNet-56
的第
3
阶段的
MRLA
注意力分数。当前
5
层通过静态层级注意力重用来自前置层的信息时,只有一个特定层的关键值被激活,几乎没有其他层被分配注意力。这一观察验证了静态层级注意力削弱了层间信息交互的效率。

为了解决层级注意力的静态问题,论文提出了一种新颖的动态层级注意力(
DLA
)架构,以改善层间的信息流动,其中前置层的信息在特征交互过程中可以动态修改。如图
2(b)
所示,在重用前置层信息的过程中,当前特征的注意力从专注于某一特定层逐渐转变为融合来自不同层的信息。
DLA
促进了信息的更全面利用,提高了层间信息交互的效率。实验结果表明,所提的
DLA
架构在图像识别和目标检测任务中优于其他最先进的方法。

本文的贡献总结如下:

  1. 提出了一种新颖的
    DLA
    架构,该架构包含双路径,其中前向路径使用递归神经网络(
    RNN
    )提取层间的上下文特征,而后向路径则利用这些共享的上下文表示在每一层刷新原始特征。

  2. 提出了一种新颖的
    RNN
    模块,称为动态共享单元(
    DSU
    ),它被设计为
    DLA
    的适用组件。它有效地促进了
    DLA
    内部信息的动态修改,并且在逐层信息集成方面表现出色。

Dynamic Layer Attention


首先重新审视当前的层级注意力架构并阐明其静态特性,随后再介绍动态层级注意力(
DLA
),最后将呈现一个增强型
RNN
插件模块,称为动态共享单元(
DSU
),它集成在
DLA
架构中。

Rethinking Layer Attention

层级注意力由
MRLA
定义,并如图
1(a)
所示,其中注意力机制增强了层级间的交互。
MRLA
致力于降低层级注意力的计算成本,提出了递归层级注意力(
RLA
)架构。在
RLA
中,来自不同层的特征被视为标记并进行计算,最终产生注意力输出。

设第
\(l\)
层的特征输出为
\(\boldsymbol{x}^l \in \mathbb{R}^{C \times W \times H}\)
。向量
\(\boldsymbol{Q}^l\)

\(\boldsymbol{K}^l\)

\(\boldsymbol{V}^l\)
可以按如下方式计算:

\[\begin{equation}
\left\{
\begin{aligned}
\boldsymbol{Q}^l &= f^l_q(\boldsymbol{x}^l)\\
\boldsymbol{K}^l &= \text{Concat}\left[f^1_k(\boldsymbol{x}^1), \ldots, f^l_k(\boldsymbol{x}^l)\right] \\
\boldsymbol{V}^l &= \text{Concat}\left[f^1_v(\boldsymbol{x}^1), \ldots, f^l_v(\boldsymbol{x}^l)\right],
\end{aligned}
\right.
\end{equation}
\]

其中
\(f_q\)
是一个映射函数,用于从第
\(l\)
层提取信息,而
\(f_k\)

\(f_v\)
是相应的映射函数,分别用于从第
\(1\)
层到第
\(l\)
层提取信息。注意力输出
\(\boldsymbol{o}^l\)
的计算公式如下:

\[\begin{equation}
\begin{aligned}
\boldsymbol{o}^l &= \text{Softmax}\left(\frac{\boldsymbol{Q}^l (\boldsymbol{K}^l)^\text{T}}{\sqrt{D_k}}\right) \boldsymbol{V}^l \\
&=\sum^l_{i=1} \text{Softmax}\left(\frac{\boldsymbol{Q}^l \left[f_k^i(\boldsymbol{x}^i)\right]^\text{T}}{\sqrt{D_k}}\right) f_v^i(\boldsymbol{x}^i),
\end{aligned}
\end{equation}
\]

其中
\(D_k\)
作为缩放因子。

为了降低计算成本,轻量级版本的
RLA
通过递归方式更新注意力输出
\(\boldsymbol{o}^l\)
,具体方法如下:

\[\begin{equation}
\boldsymbol{o}^l = \boldsymbol{\lambda}_o^l \odot \boldsymbol{o}^{l-1} + \text{Softmax}\left(\frac{\boldsymbol{Q}^l \left[f_k^l(\boldsymbol{x}^l)\right]^\text{T}}{\sqrt{D_k}}\right) f_v^l(\boldsymbol{x}^l),
\end{equation}
\]

其中
\(\boldsymbol{\lambda}^{l}_o\)
是一个可学习的向量,
\(\odot\)
表示逐元素相乘。通过多头结构设计,引入了多头递归层级注意力(
MRLA
)。

Motivation

MRLA
成功地将注意力机制整合进层间交互中,有效地解决了计算成本问题。然而,当
MRLA
应用于第
\(l\)
层时,前面
\(m\)
层 ( $ m<l $ ) 已经生成了特征输出
\(\boldsymbol{x}^m\)
,且没有后续变化。因此,
MRLA
处理的信息包括来自前几层的固定特征。相比之下,广泛使用的基于注意力的模型,如通道注意力、空间注意力和
Transformers
,都会将生成的标记同时传递到注意力模块中。将注意力模块应用于新生成的标记之间,可以确保每个标记始终学习到最新的特征。因此,论文将
MRLA
归类为静态层注意力机制,限制了当前层与较浅层之间的交互。

在一般的自注意力机制中,特征
\(\boldsymbol{x}^m\)
有两个作用:传递基本信息和表示上下文。当前层提取的基本信息使其与其他层区分开来。同时,上下文表示捕捉特征沿时间轴的变化和演变,这是决定特征新鲜度的关键方面。在一般的注意力机制中,每一层都会生成基本信息,而上下文表示会转移到下一层以计算注意力输出。相比之下,在层注意力中,一旦生成标记,就会用固定的上下文表示计算注意力,这降低了注意力机制的效率。因此,本文旨在建立一种新方法来恢复上下文表示,确保输入层注意力的信息始终保持动态。

Dynamic Layer Attention Architecture

为了解决
MRLA
的静态问题,论文提出使用动态更新规则来提取上下文表示,并及时更新前面层的特征,从而形成了动态层注意力(
DLA
)架构。如图
1
(b) 所示,
DLA
包括两个路径:前向路径和后向路径。在前向路径中,采用递归神经网络(
RNN
)进行上下文特征提取。定义
RNN
块表示为
Dyn
,初始上下文表示为
\(\boldsymbol{c}^0\)
,其中
\(\boldsymbol{c}^0\)
被随机初始化。给定输入
\(\boldsymbol{x}^m \in \mathbb{R}^{ C\times W\times H}\)
,其中
\(m < l\)
,对
\(m\)
层应用全局平均池化(
GAP
)以提取全局特征,如下所示:

\[\begin{equation}
\boldsymbol{y}^m = \text{GAP}(\boldsymbol{x}^m),\ \boldsymbol{y}^m \in \mathbb{R}^{C}.
\end{equation}
\]

上下文表示的提取方式如下:

\[\begin{equation}
\boldsymbol{c}^m = \text{Dyn}(\boldsymbol{y}^m, \boldsymbol{c}^{m-1}; \theta^l).
\end{equation}
\]

其中,
\(\theta^l\)
表示
Dyn
的共享可训练参数。一旦计算出上下文
\(\boldsymbol{c}^l\)
,每一层的特征将在后向路径中同时更新,如下所示:

\[\begin{equation}
\left\{
\begin{aligned}
\boldsymbol{d}^m &= \text{Dyn}(\boldsymbol{y}^m, \boldsymbol{c}^l; \theta^l)\\
\boldsymbol{x}^m &\leftarrow \boldsymbol{x}^m \odot \boldsymbol{d}^m
\end{aligned}\right.
\end{equation}
\]

参考公式
5
,前向上下文特征提取是一个逐步过程,其计算复杂度为
\(\mathcal{O}(n)\)
。与此同时,公式
6
中的特征更新可以并行进行,计算复杂度为
\(\mathcal{O}(1)\)
。在更新
\(\boldsymbol{x}^m\)
后,
DLA
的基础版本使用公式
2
来计算层注意力,简称
DLA-B
。对于
DLA
的轻量级版本,简单地更新
\(\boldsymbol{o}^{l-1}\)
,然后使用公式
3
来获得
DLA-L

  • Computation Efficiency

DLA
在结构设计上具有几个优点:

  1. 全局信息被压缩以计算上下文信息,这一功能已经在
    Dianet
    中得到验证。
  2. DLA

    RNN
    模块内使用了共享参数。
  3. 上下文
    \(\boldsymbol{c}^l\)
    在每一层中以并行的方式单独输入到特征图中,前向和后向路径在整个网络中共享相同的参数并引入了一个高效的
    RNN
    模块用于计算上下文表示。

通过这些高效设计的结构规则,计算成本和网络容量得到了保障。

Dynamic Sharing Unit

LSTM
,如图
3(a)
所示,设计用于处理序列数据和学习时间特征,使其能够捕捉和存储长序列中的信息。然而,在将
LSTM
嵌入
DLA
作为递归块时,
LSTM
中的全连接线性变换显著增加了网络容量。为了缓解这种容量增加,
Dianet
提出了一种变体
LSTM
块,称为
DIA
单元,如图
3(b)
所示。在将数据输入网络之前,
DIA
首先利用线性变换和
ReLU
激活函数来降低输入维度。此外,
DIA
在输出层将
Tanh
函数替换为
Sigmoid
函数。

LSTM

DIA
生成两个输出,包括一个隐藏向量
\(\boldsymbol{h}^m\)
和一个
cell
状态向量
\(\boldsymbol{c}^m\)
。通常,
\(\boldsymbol{h}^m\)
用作输出向量,而
\(\boldsymbol{c}^m\)
作为记忆向量。
DLA
专注于从不同层中提取上下文特征,其中
RNN
模块不需要将其内部状态特征传递到外部。因此,论文舍弃了输出门,并通过省略
\(\boldsymbol{h}^m\)
来合并记忆向量和隐藏向量。

论文提出的简化
RNN
模块被称为动态共享单元(
Dynamic Sharing Unit

DSU
),工作流程如图
3(c)
所示。具体而言,在添加
\(\boldsymbol{c}^{m-1}\)

\(\boldsymbol{y}^m\)
之前,首先使用激活函数
\(\sigma(\cdot)\)

\(\boldsymbol{c}^{m-1}\)
进行归一化。在这里,选择
Sigmoid
函数 (
\(\sigma(z) = 1 /(1 + e^{-z})\)
)。因此,
DSU
的输入被压缩如下:

\[\begin{equation}
\boldsymbol{s}^m = \text{ReLU}\left(\boldsymbol{W}_1\left[ \sigma(\boldsymbol{c}^{m-1}), \boldsymbol{y}^m \right] \right).
\end{equation}
\]

隐藏变换、输入门和遗忘门可以通过以下公式表示:

\[\begin{equation}
\left\{
\begin{aligned}
\boldsymbol{\tilde{c}}^m &= \text{Tanh}(\boldsymbol{W}_2^c \cdot \boldsymbol{s}^m + b^c) \\
\boldsymbol{i}^m &= \sigma(\boldsymbol{W}_2^i \cdot \boldsymbol{s}^m + b^i ) \\
\boldsymbol{f}^m &= \sigma(\boldsymbol{W}_2^f \cdot \boldsymbol{s}^m + b^f )
\end{aligned}
\right.
\end{equation}
\]

随后,得到

\[\begin{equation}
\boldsymbol{c}^m = \boldsymbol{f}^m \odot \boldsymbol{c}^{m-1} + \boldsymbol{i}^m \odot \boldsymbol{\tilde{c}}^m
\end{equation}
\]

为了减少网络参数,令
\(\boldsymbol{W}_1\in \mathbb{R}^{\frac{C}{r}\times 2C}\)

\(\boldsymbol{W}_2\in \mathbb{R}^{C\times \frac{C}{r}}\)
,其中
\(r\)
是缩减比率。
DSU
将参数减少到
\(5C^2/r\)
,比
LSTM

\(8C^2\)

DIA

\(10C^2/r\)
更少。

Experiments




如果本文对你有帮助,麻烦点个赞或在看呗~
更多内容请关注 微信公众号【晓飞的算法工程笔记】

work-life balance.

说明

该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。

该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。

说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。

结合上一篇文章使用,味道更佳:
.net core8 使用Swagger(附当前源码)

有兴趣的朋友,请关注我吧(*^▽^*)。

第一步:安装最新Jwt包

包名:Microsoft.AspNetCore.Authentication.JwtBearer

第二步:appsettings.json中配置jwt

 /*jwt鉴权*/
 "JwtSetting": {"Issuer": "微信公众号:不只是码农", //发行人
   "Audience": "微信公众号:不只是码农", //订阅人
   "ExpireSeconds": 120, //过期时间,默认分钟
   "ENAlgorithm": "HS256", //秘钥算法
   "SecurityKey": "bzsmn=Start20240913EndOverallAuth-WebApi" //秘钥构成
 }

第三步:创建jwt解析模型

在OverallAuth-WebApi项目的目录下创建文件夹【model】,并创建一个类文件JwtSettingModel.cs

OverallAuth-WebApi结构,见上一篇文章:
.net core8 使用Swagger(附当前源码)

 /// <summary>
 ///jwt配置模型/// </summary>
 public classJwtSettingModel
{
/// <summary> ///密钥/// </summary> public string SecurityKey { get; set; }/// <summary> ///加密算法/// </summary> public string ENAlgorithm { get; set; }/// <summary> ///颁发者/// </summary> public string Issuer { get; set; }/// <summary> ///接收者/// </summary> public string Audience { get; set; }/// <summary> ///过期时间 单位:秒/// </summary> public int ExpireSeconds { get; set; }
}

目录结构如下:

第四步:创建Jwt、AppSettings插件

目录结构如下:

上图可以看到,我们创建了JwtPlugInUnit和AppSettingsPlugInUnit2个插件,它分别对应jwt和AppSettings配件文件的解析。

那么我们看下,这2个类里面的具体内容。

JwtPlugInUnit如下:

/// <summary>
///jwt插件/// </summary>
public static classJwtPlugInUnit
{
/// <summary> ///初始化JWT/// </summary> /// <param name="services"></param> public static void InitJWT(thisIServiceCollection services)
{
var jwtsetting = AppSettingsPlugInUnit.GetNode<JwtSettingModel>("JwtSetting");
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o
=>{
o.TokenValidationParameters
= newTokenValidationParameters()
{
ValidateIssuerSigningKey
= true,
ValidIssuer
=jwtsetting.Issuer,
ValidAudience
=jwtsetting.Audience,
IssuerSigningKey
= newSymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtsetting.SecurityKey)),
ValidateIssuer
= true,
ValidateAudience
= true,
ValidateLifetime
= true,
ClockSkew
=TimeSpan.Zero
};
});
}
}

AppSettingsPlugInUnit如下:

 /// <summary>
 ///AppSettings配置文件插件/// </summary>
 public classAppSettingsPlugInUnit
{
/// <summary> ///声明配置属性/// </summary> public static IConfiguration Configuration { get; set; }/// <summary> ///构造函数/// </summary> staticAppSettingsPlugInUnit()
{
Configuration
= newConfigurationBuilder()
.Add(
new JsonConfigurationSource { Path = "appsettings.json", ReloadOnChange = true})
.Build();
}
/// <summary> ///获得配置文件的对象值/// </summary> /// <param name="jsonPath">文件路径</param> /// <param name="key"></param> /// <returns></returns> public static string GetJson(string jsonPath, stringkey)
{
if (string.IsNullOrEmpty(jsonPath) || string.IsNullOrEmpty(key)) return null;
IConfiguration config
= new ConfigurationBuilder().AddJsonFile(jsonPath).Build();//json文件地址 return config.GetSection(key).Value;//json某个对象 }/// <summary> ///获取数据库连接字符串/// </summary> /// <returns></returns> public static stringGetMysqlConnection()
{
return Configuration.GetConnectionString("MySql").Trim();
}
/// <summary> ///根据节点名称获取配置模型/// </summary> /// <typeparam name="T"></typeparam> /// <param name="Node"></param> /// <returns></returns> public static T GetNode<T>(string Node) where T : new()
{
T model
= Configuration.GetSection(Node).Get<T>();returnmodel;

}
}

第五步:让jwt遵守Swagger协议

因为我们系统使用到了Swagger,所以要让jwt遵守Swagger协议,因此我们要在Swagger中添加如下代码。

/// <summary>
///初始化Swagger/// </summary>
/// <param name="services"></param>
public static void InitSwagger(thisIServiceCollection services)
{
//添加swagger services.AddSwaggerGen(optinos =>{typeof(ModeuleGroupEnum).GetEnumNames().ToList().ForEach(version =>{
optinos.SwaggerDoc(version,
newOpenApiInfo()
{
Title
= "权限管理系统",
Version
= "V2.0",
Description
= "求关注,求一键三连",
Contact
= new OpenApiContact { Name = "微信公众号作者:不只是码农 b站作者:我不是码农呢", Url = new Uri("http://www.baidu.com") }
});

});
//反射获取接口及方法描述 var xmlFileName = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
optinos.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFileName),
true);

//使用jwt
optinos.AddSecurityDefinition(
"Bearer", newOpenApiSecurityScheme
{
Description
= "请在下方输入框子输入Bearer Token 开启JWT鉴权",
Name
= "Authorization", //默认名称,不能修改 In =ParameterLocation.Header,
Type
=SecuritySchemeType.ApiKey,
Scheme
= "Bearer"});//让swagger遵守jwt协议 optinos.AddSecurityRequirement(newOpenApiSecurityRequirement
{
{
newOpenApiSecurityScheme
{
Reference
= newOpenApiReference
{
Type
=ReferenceType.SecurityScheme,
Id
= "Bearer"}
},
new List<string>()
}
});

});
}

说明:InitSwagger方法是初始化Swagger的方法,在上一篇文章:
.net core8 使用Swagger(附当前源码)
中有讲到。

第六步:初始化Jwt

在Program中添加一下代码,初始化Jwt

第七步:验证Jwt

做好以上步骤,jwt就可以正常使用。

当你看到图中标识时,就表示jwt初始化成功,就可以在系统中使用jwt鉴权等操作。

使用【[Authorize]】、【 [AllowAnonymous]】特性测试鉴权。

以下2个接口一个需要验证、一个不需要验证,我们来测试下。

[Authorize]开启验证测试

CheckJwt接口:开启验证,不传token

可以看到,开启jwt验证的,接口在没有传入token的情况下,访问失败。

UnCheckJwt接口:不开启验证。

以上就是.net core8 使用jwt系统鉴权的配置过程。

感谢你的耐心观看。

如果对你有帮助,请关注我微信公众号吧(*^▽^*)。

源代码地址:https://gitee.com/yangguangchenjie/overall-auth2.0-web-api

帮我Star,谢谢。

有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界

无数据知识蒸馏能够利用大型教师网络所学到的知识,来增强较小型学生网络的训练,而无需访问原始训练数据,从而避免在实际应用中的隐私、安全和专有风险。在这方面的研究中,现有的方法通常遵循一种反演蒸馏的范式,在预训练教师网络指导下实时训练生成对抗网络来合成一个大规模的样本集用于知识蒸馏。论文重新审视了这种常见的无数据知识蒸馏范式,表明通过“小规模逆向数据进行知识蒸馏”的视角,整体训练效率有相当大的提升空间。根据三个经验观察结果,这些观察结果表明在数据逆向和蒸馏过程中平衡类别分布在合成样本多样性和难度上的重要性,论文提出了小规模无数据知识蒸馏(
SSD-KD
)。在形式化上,
SSD-KD
引入了一个调节函数来平衡合成样本,并引入了一个优先采样函数来选择合适的样本,通过动态回放缓冲区和强化学习策略进行辅助。
SSD-KD
可以在极小规模的合成样本(例如,比原始训练数据规模小10
\(\times\)
)条件下进行蒸馏训练,使得整体训练效率比许多主流方法快一个或两个数量级,同时保持卓越或竞争性的模型性能,这一点在流行的图像分类和语义分割基准上得到了验证。

来源:晓飞的算法工程笔记 公众号,转载请注明出处

论文: Small Scale Data-Free Knowledge Distillation

Introduction


对于资源受限设备上的计算机视觉应用,如何学习便携的神经网络同时保持满意的预测准确性是关键问题。知识蒸馏(
KD
)利用预训练的大型教师网络的信息来促进在相同训练数据上的较小目标学生网络的训练,已经成为一种主流解决方案。传统的
KD
方法假设原始训练数据始终可用。然而,由于潜在的隐私、安全、专有或数据规模等问题,实际中通常无法访问教师网络训练时使用的源数据集。为了解决训练数据的限制,数据无关的知识蒸馏最近引起了越来越多的关注。

数据无关知识蒸馏(
D-KD
)的基本思想是基于预训练的教师网络构建合成样本进行知识蒸馏,这些合成样本应当匹配原始训练数据的潜在分布。现有的顶级
D-KD
方法通常采用对抗性反演和蒸馏范式。

  1. 在反演过程中,通过将预训练的教师网络作为判别器来训练生成器。
  2. 在随后的知识蒸馏过程中,实时学习的生成器将合成伪样本用于训练学生网络。

然而,对抗性
D-KD
方法通常需要生成大量的合成样本(与原始训练数据集的规模相比),以确保可信的知识蒸馏。这给训练资源消耗带来了沉重负担,抑制了其在实际应用中的使用。在最近的一项工作中,作者提出了一种有效的元学习策略,该策略寻找共同特征并将其作为初始先验以减少生成器收敛所需的迭代步骤。尽管可以实现更快的数据合成,但仍然需要生成足够多的合成样本以确保有效的知识蒸馏。这忽略了后续知识蒸馏过程的效率,成为整体训练效率的主要瓶颈。总而言之,目前尚未有研究工作通过共同考虑数据反演和知识蒸馏过程来提高
D-KD
的整体训练效率。

为了弥补这一关键差距,论文提出首个完全高效的
D-KD
方法,即小规模无数据知识蒸馏(
SSD-KD
)。从一种新颖的“小数据规模”视角出发,提高了对抗反演和蒸馏范式的整体训练效率。在这项工作中,“数据规模”指的是在一个训练周期内用于知识蒸馏的总反演样本数量。

SSD-KD
的提出主要源于三个观察的启发。

首先,论文观察到,当在不同的教师-学生网络对上将合成样本和原始样本的数据规模显著缩减到相同水平(例如,源训练数据集大小的
10%
)时,训练于合成样本的学生网络相比于训练于原始样本的对应网络显示出更好的性能,如图
1
所示。需要注意的是,合成样本是在全源数据集上预训练的教师网络的指导下生成的,这自然反映了原始数据分布的不同视角。在足够小的数据规模下,这使得合成样本在拟合整个源数据集的基础分布方面优于原始样本。这一启发性的观察表明,如果能够构建一个小规模的高质量合成样本集,将创造出一种通向完全高效
D-KD
的有希望的方法。从原则上讲,论文认为一个高质量的小规模合成数据集应该在合成样本的多样性和难度方面具有良好的类别分布平衡。

然而,论文其他两个观察表明,现有的
D-KD
方法,包括传统的和最有效的设计,都没有在小数据规模下平衡上述两个方面的类别分布的良好能力,如图
2
所示。需要注意的是,已经存在一些
D-KD
方法来增强合成样本的多样性,但合成样本在样本难度方面的多样性尚未被探讨。

基于上述观察和分析,论文提出了
SSD-KD
,引入了两个相互依赖的模块以显著提高主要的对抗性反演和蒸馏范式的整体训练效率。
SSD-KD
的第一个模块依赖于一种新颖的调制函数,该函数定义了一个多样性感知项和一个难度感知项,以明确的方式共同平衡合成样本在数据合成和知识蒸馏过程中的类别分布。第二个模块定义了一种新颖的优先采样函数,这一函数通过强化学习策略来实现,从候选样本中选择少量适当的合成样本,这些样本存储在一个动态重放缓冲区中用于知识蒸馏,从而进一步提高端到端的训练效率。得益于这两个模块,
SSD-KD
具有两个吸引人的优点。一方面,
SSD-KD
可以在极小规模的合成样本(比原始训练数据规模小
10
倍)上进行蒸馏训练,使得整体训练效率比许多主流的
D-KD
方法快一个或两个数量级,同时保持竞争力的模型性能。另一方面,
SSD-KD
在学生模型准确性方面获得了大幅度的提升,并且在将合成样本的数据规模放宽到相对较大的数量时(仍然小于现有
D-KD
方法的规模)保持了整体训练效率。

Method


Preliminaries: D-KD


\({f_t(\cdot;\theta_t)}\)
为一个在原始任务数据集上预训练的教师模型,而该数据集现在已不可访问。
D-KD
的目标是首先通过反演教师模型学到的数据分布信息来构建一组合成训练样本
\(x\)
,然后在这些样本上训练一个目标学生模型
\({f_s(\cdot;\theta_s)}\)
,迫使学生模型模拟教师的功能。现有的
D-KD
方法大多使用生成对抗网络
\(g(;\theta_g)\)
来生成合成训练样本
\(x=g(z;\theta_g)\)
,其中
\(z\)
为潜在噪声输入,该生成对抗网络的训练是通过将教师模型作为鉴别器来进行的。

D-KD
的优化包含一个常见的蒸馏正则化项,用于最小化教师-学生模型之间的功能差异
\(\mathcal{L}_{\text{KD}}({x})=D_\text{KD}(f_t(x; \theta_t)\| f_s(x; \theta_s))\)
,该差异基于
KL
散度。此外,还包含一个任务导向的正则化项
\(\mathcal{L}_{\text{Task}}({x})\)
,例如,使用教师模型的预测作为真实标签的交叉熵损失。除此之外,由于
D-KD
主要基于教师模型经过预训练后能够捕捉源训练数据分布的假设,近年来的
D-KD
方法引入了额外的损失项,以正则化数据反演过程中训练数据分布的统计量(批量归一化(
BN
)参数)。

\[\begin{equation}
\mathcal{L}_{\text{BN}}({x})= \sum_l \big\|\mu_l(x)-\mathbb{E}(\mu_l)\big\|_2+\big\| \sigma_l^2(x)-\mathbb{E}(\sigma_l^2)\big\|_2,
\end{equation}
\]

其中,
\(\mu_l(\cdot)\)

\(\sigma_l(\cdot)\)
分别表示第
\(l\)
层特征图的批量均值和方差估计;
\(\mathbb{E}(\cdot)\)
对批量归一化(
BN
)统计量的期望可以通过运行均值或方差进行近似替代。

D-KD
的有效性在很大程度上依赖于从利用预训练教师模型知识反演得到的合成样本的质量。现行的对抗性
D-KD
范式包含两个过程,即数据反演和知识蒸馏。从效率和效果两个角度来看,一方面,数据反演过程在很大程度上影响学生模型的优化性能;另一方面,知识蒸馏的训练时间成本成为
D-KD
整体训练效率的一个重要限制因素。

Our Design: SSD-KD

SSD-KD
专注于通过“用于知识蒸馏的小规模反演数据”这一视角来改进对抗性
D-KD
范式,着重于利用预训练教师模型和知识蒸馏过程的反馈来指导数据反演过程,从而显著加快整体训练效率。根据前述中的符号,
SSD-KD
的优化目标定义为

\[\begin{align}
\min\limits_{f_s} \max\limits_{g} \mathbb{E}_{x = \delta \circ g\left(z\right)}\big(\mathcal{L}_{\text{BN}}({x})+\mathcal{L}_{\text{KD}}({x})+\phi(x)\mathcal{L}_{\text{Task}}({x})\big),
\label{eq:loss}
\end{align}
\]

其中,

  1. 采用了一种多样性感知的调制函数
    \(\phi(x)\)
    ,该函数根据教师模型对每个合成样本的预测类别分配不同的优先级。


  2. BN
    估计的约束下,通过
    \(\phi(x)\)
    鼓励生成器尽可能探索对教师模型来说更具挑战性的合成样本。

  3. 不采用随机采样策略来选择样本进行知识蒸馏,而是采用重加权策略来控制采样过程。符号
    \(\circ\)
    来表示应用基于优先级采样函数
    \(\delta\)

  4. 每个合成样本不仅由其调制函数
    \(\phi(x)\)
    进行优先级排序,而且在采样阶段还会重新加权,该阶段重用了与
    \(\phi(x)\)
    相同的中间值。

虽然
D-KD
流水线允许合成训练样本并用于在同一任务上训练学生模型,但存在大量数据冗余,这妨碍了
D-KD
方法的训练效率。
SSD-KD
是一种完全高效的
D-KD
方法,能够使用极小规模的合成数据,同时与现有的
D-KD
方法相比,达到具有竞争力的性能。

SSD-KD
的流程总结在算法
1
中,现有对抗性
D-KD
方法的优化流程(包括传统家族和更高效家族)与我们的
SSD-KD
的比较如图
3
所示。

Data Inversion with Distribution Balancing


2
展示了
D-KD
中由于合成数据的大量不平衡导致的数据冗余。右侧两个子图描绘了教师模型预测的类别分布,显示了数据类别的显著不平衡,而左侧两个子图展示了不同预测难度下的样本数量(难度通过教师模型预测的概率来衡量)。对于
D-KD
来说,这表明仅仅根据教师-学生差异来生成样本会导致样本难度分布非常不均,并倾向于获得容易预测的数据样本。对于数据样本全部合成的
D-KD
任务,数据生成过程需要同时考虑教师-学生差异和教师自身的预训练知识,基于此,
SSD-KD
提出了考虑多样性和难度的数据合成方法。

  • Diversity-aware balancing

首先在数据反演过程中解决样本难度不平衡的问题。具体来说,维护一个回放缓冲区
\(\mathcal{B}\)
,它存储一定数量(记作
\(|\mathcal{B}|\)
)的合成数据样本。对于
\(\mathcal{B}\)
中的每个数据样本
\(x\)
,惩罚与
\(x\)
共享相同预测类别(由教师模型预测)的样本总量。为实现这一点,采用了一个考虑多样性的平衡项,该项鼓励生成器合成具有不常见类别的样本。

  • Difficulty-aware balancing

借鉴在对象检测领域中使用焦点损失来处理高度不平衡样本的经验,进一步对每个样本
\(x\)
引入了一个基于预测概率
\(p_T(x)\)
的难度感知平衡项。在这里,困难的合成样本被认为是那些由教师模型预测信心较低的样本,这些样本会受到难度感知平衡项的鼓励。

总之,论文引入了一个调节函数
\(\phi(x)\)
来根据来自预训练教师模型的预测反馈调整生成器的优化。
\(\phi(x)\)
旨在平衡类别分布,并动态地区分容易和困难的合成样本,从而使容易的样本不会过度主导蒸馏过程。形式上,对于合成数据样本
\(x\in\mathcal{B}\)
,其调节函数
\(\phi(x)\)
的计算公式为

\[\begin{equation}
\phi (x) = \underbrace {\Big(1 - \frac{1}{|\mathcal{B}|}\sum_{x'\in\mathcal{B}}\mathbb{I}_{c_T(x')=c_T(x)}\Big)}_{\text{diversity-aware balancing}} \underbrace{{\Big(1 - p_T(x)\Big)}^\gamma }_{\text{difficulty-aware balancing}}
\label{eq:phi}
\end{equation}
\]

其中,
\(c_T(x)\)

\(p_T(x)\)
分别指代预训练教师模型预测的类别索引和概率(或置信度);
\(\mathbb{I}_{c_T(x')=c_T(x)}\)
表示一个指示函数,如果
\(x'\)
的预测类别与
\(x\)
相同,则该函数等于
\(1\)
,否则为
\(0\)

\(\gamma\)
是一个超参数。

强调调节函数
\(\phi(x)\)
的两个特性:

  1. 对于在教师模型中预测置信度较高的数据样本
    \(x\)
    (即被视为容易样本),
    \(\phi(x)\)
    接近于低值,从而对任务相关损失
    \(\mathcal{L}_{\text{Task}}({x})\)
    在公式 (`ref`{eq:loss}) 中的影响较小。
  2. 当教师网络预测的合成数据样本在
    \(\mathcal{B}\)
    中的类别分布严重不平衡时,与
    \(\mathcal{B}\)
    中更多样本共享类别的样本
    \(x\)
    会受到惩罚,从而
    \(\phi(x)\)
    会削弱相应的
    \(\mathcal{L}_{\text{Task}}({x})\)

尽管公式表明调节函数
\(\phi(x)\)
的值部分由当前的重放缓冲区
\(\mathcal{B}\)
决定,但需要注意的是,
\(\mathcal{B}\)
是动态变化的,并且也受到
\(\phi(x)\)
的影响。这是因为公式中的项
\(\phi(x)\mathcal{L}_{\text{Task}}({x})\)
直接优化了生成器,该生成器合成数据样本以组成
\(\mathcal{B}\)
。从这个意义上说,考虑到
\(\mathcal{B}\)

\(\phi(x)\)
的相互影响,在训练过程中保持了类别多样性的平衡。在数据逆向过程中,平衡数据样本的类别尤为重要。

如图
2
所示,借助这两个平衡项,
SSD-KD
在样本类别和难度方面生成了温和的样本分布。

Distillation with Priority Sampling

原始的优先经验重放方法更频繁地重用重要知识的转移,从而提高学习效率。与此不同的是,论文的优先采样方法并不从环境中获得奖励,而是设计为适应无数据的知识蒸馏并从框架自身获取反馈。换句话说,优先采样方法在无数据知识蒸馏方法中扮演了相反的角色:它专注于训练一小部分高度优先的样本,而不是均匀采样,从而加速训练过程。

从当前的重放缓冲区
\(\mathcal{B}\)
中采样合成数据
\(x\)
,论文提出了一种名为优先采样(
Priority Sampling
,
PS
)的采样策略来调节采样概率,而不是均匀采样。
PS
的基本功能是衡量
\(\mathcal{B}\)
中每个样本
\(x\)
的重要性,因此引入了优先采样函数
\(\delta_{i}(x)\)

\[\begin{equation}
\delta_{i}(x) = w_{i-1}(x) KL(f_t(x;\theta_t)||f_s(x;\theta_s)),
\end{equation}
\]

\(KL\)
表示
softmax
输出
\(f_t(x;\theta_t)\)

\(f_s(x;\theta_s)\)
之间的
KL
散度;
\(\theta_t\)

\(\theta_s\)
依赖于训练步骤
\(i\)

\(w_{i}(x)\)
是用于归一化
\(\mathcal{B}\)
中样本的校准项,如下面的公式所形式化,特别地,当
\(i=0\)
时,
\(w_{-1}(x)=1\)

使用随机更新进行知识蒸馏的训练,依赖于这些更新与其期望值具有相同的分布。优先采样数据会引入偏差,因为它可能会改变数据分布,从而影响估计值所收敛的解。因此,通过引入一个重要性采样(
Importance Sampling
,
IS
)权重
\(w_i(x)\)
来校正这种偏差,用于数据样本
\(x\)

\[\begin{equation}
\label{eq_wi}
w_{i}(x)=(N \cdot P_{i}(x))^{-\beta},
\end{equation}
\]

其中
\(\beta\)
是一个超参数;
\(P_{i}(x)\)
是定义为的采样过渡概率

\[\begin{equation}
\label{pi}
P_{i}(x)=\frac{\big(\left|\delta_{i}(x)\right|+\epsilon\big)^\alpha}{\sum_{x'\in\mathcal{B}}\big(\left|\delta_{i}(x')\right|+\epsilon\big)^\alpha},
\end{equation}
\]

其中
\(\epsilon\)
是一个小的正数,用于防止在优先级为零时转移不被选择的极端情况。

优先采样函数
\(\delta(x)\)
具有两个显著特性:

  1. 随着
    delta
    值的增加,
    \(\delta(x)\)
    反映了当前
    \(\mathcal{B}\)
    中合成样本的教师模型与学生模型之间的更大信息差异。因此,学生模型应从信息差异更大的样本中进行优化,这有助于更快地获取教师模型。
  2. \(\delta(x)\)
    会随着学生模型和生成模型的每次更新迭代而动态变化。因此,当学生模型在某些样本上获得了教师模型的能力时,它会继续从相对于教师模型差异更大的样本中学习,基于新的样本分布。这进一步提升了学生模型的性能。

Experiment




如果本文对你有帮助,麻烦点个赞或在看呗~
更多内容请关注 微信公众号【晓飞的算法工程笔记】

work-life balance.