2024年8月

Ubuntu 16.04.4 通过docker安装单机fastdfs

前言

很久没有写技术播客了,这是一件很不应该的事情,做完了事情应该有沉淀的。

我先说一点前情提要,公司的fastdfs突然就挂了,做过的操作就是日志文件太大了,所以把日志文件给删了,理论上这个动作应该不影响程序运行才对。

然后tracker怎么都启动不起来了。会报下面这个错误。

file: tracker_service.c, line: 2079, client ip: 47.xxx.xxx.52, group_name: group1, new ip address 172.xxx.xxx.217 != client ip address 47.xxx.xxx.52

我的tracker和storage都部署在同一台服务器上,其中47开头的是同一台服务器的公网IP,172开头的是这台服务器的内网IP。

然后tracker、storage和client的配置文件,我都改过一遍,都改成内网、都改成公网、一个是公网、一个是内网。反正就是不行。

不过我后来在搜索后续答案的过程中有受到一点启发,就是直接改一下host文件,给ip随便分配一个域名,这样后期的配置都用域名,有可能可以解决这个问题。

因为存储的数据都是从其他地方拉取过来的,只是过一个临时的中转,所以,里面的数据丢了也无所谓,于是,我就想,我索性重新再装一个fastdfs好了。

但是fastdfs并不能通过一个命令直接卸载,所以,我直接删除了它相关的各种文件。

但是有可能我删的不是很干净,在我装最新版本fastdfs的时候,编译
libserverframe
的时候,报下面这个错。

Makefile:59: recipe for target 'fdfs_monitor' failed

搜索了一圈,提到这个问题的人并不多,然后我试了直接从git上clone代码的方式,去git上下载压缩包再解压的方式,都编译不过去。

初步判断是依赖库的版本冲突了,或者有缺失。

这里再提一个题外话,我们在使用
make intall
命令的时候,应该习惯性加上
--prefix
参数,配置文件的安装路径,如下:

make install --prefix=/opt/application

如果不配的话,安装后可执行文件默认放在
/usr/local/bin
,库文件默认放在
/usr/local/lib
,配置文件默认放在
/usr/local/etc
,其它的资源文件放在
/usr/local/share

安装一时爽,卸载火葬场。

因为可能原因是版本冲突,然后我现在也卸载不干净了,于是我就想到了docker,docker是另外开辟出来的一个独立的环境,不受当前主环境影响,而且服务器上还有其他正在跑的服务,我也不可能重置服务器。不得不说docker真是一个好东西啊,是哪个小天才发明的。

fastdfs镜像选择

fastdfs并没有提供官方的镜像,所以只能用网友们自主创建的。

https://hub.docker.com/search?q=fastdfs

收藏最多的是一个season的镜像,不过,我看它的版本很老了,好像才1.2版,更新时间也是9年前了。所以我没有用这个,我看到好几篇文章里都用的是delron的镜像,所以我也用的这一个。当然这个也挺老了,6年前更新的。

但我急于解决问题,也没有心思去好好研究和挑选了,总之,亲测它单机好使,使用起来也比较方便,集成了nginx,可以直接通过浏览器访问资源。

镜像的拉取

如果你的这台机器,之前没有用过docker的话,还是一个老生常谈的问题,你很有可能连接超时,然后拉取失败,所以要配置镜像。

如果你还没有安装过docker,也可以先安装一下:

apt install docker.io

修改镜像配置文件 :

 vim /etc/docker/daemon.json

把各种镜像的地址加上(没有缩进的那几个就是):

image-20240829094121066

下面是从网上搜到的镜像:

DaoCloud	https://docker.m.daocloud.io
阿里云	https://<your_code>.mirror.aliyuncs.com(阿里云提供了镜像源:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 登录后你会获得一个专属的地址)
Docker镜像代理	https://dockerproxy.com
百度云	https://mirror.baidubce.com
南京大学	https://docker.nju.edu.cn
中科院	https://mirror.iscas.ac.cn

"https://yxzrazem.mirror.aliyuncs.com",
"http://hub-mirror.c.163.com",
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"

来一份示例方便粘贴

{
    "registry-mirrors": [
        "https://do.nark.eu.org",
        "https://dc.j8.work",
        "https://docker.m.daocloud.io",
        "https://dockerproxy.com",
        "https://docker.mirrors.ustc.edu.cn",
        "https://docker.nju.edu.cn",
        "https://ustc-edu-cn.mirror.aliyuncs.com/",
        "https://hub-mirror.c.163.com",
        "https://mirror.baidubce.com"
    ]
}

重新加载配置,并重启docker:

systemctl daemon-reload
systemctl restart docker

运行docker的tracker服务:

# 先创建文件夹,文件夹随便定义,我这里是为了方便,直接延用了别人播客的定义(其中-p是可以直接创建多级目录)
mkdir -p /mydata/fastdfs/tracker
cd /mydata/fastdfs/tracker
# 执行docker命令
docker run -d --name tracker --network=host -v /mydata/fastdfs/tracker:/var/fdfs delron/fastdfs tracker
# 注意:tracker服务默认的端口为22122

运行docker的storage服务:

# 创建文件夹
mkdir -p /mydata/fastdfs/storage
cd /mydata/fastdfs/storage
# 执行命令
docker run -d --name storage --network=host  -e TRACKER_SERVER=x.x.x.x:22122 -v /mydata/fastdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage
# 注意:其中TRACKER_SERVER中的ip要修改为你的Tracker服务所在的服务IP地址
# storage默认端口为23000

storage服务中默认安装了nginx,端口为8888

服务测试

1.在/mydata/fastdfs/storage下上传一张图片,1.png

2.进入storage容器,上传图片

# 进入storage容器
docker exec -it storage bash

# 进入容器内文件夹
cd /var/fdfs/

#执行上传命令
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf 1.png

得到文件路径:
group1/M00/00/00/rBA12WbOG4OAW8SKAAM9zD7woDQ406.png

3.浏览器访问图片:

http://47.xxx.xxx.52:8888/group1/M00/00/00/rBA12WbOG4OAW8SKAAM9zD7woDQ406.png

能访问,说明上传下载一切正常。

参考(未必全)

https://rqsir.github.io/2019/04/13/linux-make-install的安装与卸载/

https://blog.csdn.net/weixin_50160384/article/details/139861337

https://www.cnblogs.com/likecoke/p/17495358.html

https://www.cnblogs.com/hequanbao/p/17035045.html

https://blog.csdn.net/zhouzaig/article/details/131412872

https://docs.tanmantang.com/docs/docker/FastDFS.html


递归和for循环(迭代法)很像,都是通过循环去完成一件事。


采用Top-Down思维去设计的递归结构,又会比for多一些不同的能力。
多什么能力?

递归的基础

先复习一下递归,递归的定义:递归(英语:Recursion),又译为递回,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。

image

递归的本质就在:
递去

归来
两个流程中。 初学者刚接触会有点抽象,下面通过一些案例来认识。

假设需要你实现一个阶乘的计算函数,
阶乘的定义:
5的阶乘是 5*4*3*2*1=120

def factorial(number):
    if number == 1:
        return 1
    else:
        return number * factorial(number - 1)

print(factorial(5))
// 120

递归需要考虑三个条件:

  1. 将问题拆分成一个重复使用的子问题;
  2. 注意,这个子问题和问题的规模无关;
  3. 必须包含终止条件 (终止条件即初始状态)。

递归的底层实现(不是重点)

递归的底层实现不是本文的重点,了解一下就行。

递归在编程语言的底层实现通常依赖于调用栈(call stack):

  • 调用栈


    • 每次函数调用时,程序会将函数的参数、局部变量和返回地址等信息压入栈帧。
    • 当递归函数调用自身时,会创建新的栈帧压入栈中。
    • 函数执行完毕后,栈帧被弹出,返回控制权给调用者。
  • 基线条件


    • 递归必须有终止条件,否则会导致栈溢出(stack overflow)。
    • 每次递归调用都应该向基线条件靠近。
  • 内存管理


    • 调用栈通常在内存的“栈区”分配。栈的大小有限制,过多的递归调用可能导致栈溢出。
    • 有些语言提供机制来增加栈的大小,但一般不推荐依赖深层递归。

总结:

  • 递归实现的效率和安全性与具体语言的特性和编译器的优化密切相关。

  • 递归的底层实现,就是把相关变量数据(缓存)处理后,一层一层的压入栈,等到了基准条件后,在逐层拿出处理。

计算机眼里的递归:
计算机使用栈来记录正在调用的函数,叫
调用栈

image

有个局部变量 number 记录当前值。

递归的应用场景

  • 处理任意多层事情的场景,都可以考虑用递归。

  • 当问题和子问题具有递推关系,比如杨辉三角、计算阶乘。

  • 具有递归性质的数据结构,比如链表、树、图。

  • 反向性问题,比如取反操作。

编程中 两种解决问题的思维

这个才是本文重点要学习的。

当面对未来未知的情况时,考虑使用使用自上而下解决问题的思维。

两种编写计算函数的方法:

  • 自下而上(Bottom - Up)
    类似循环
  • 自上而下 (Top - Down)
    递归思想

两者区别?
在计算函数时,自下而上和自上而下是两种不同的思维方式和实现策略:

在计算函数时,特别是像阶乘这样的递归函数,可以使用两种主要的实现方式来实现递归计算:
自下而上
(bottom-up)和
自上而下
(top-down)。这些方法各有优缺点,理解它们有助于选择适合的实现方式来解决特定的问题。以下是对这两种实现方式的解释:

自下而上(Bottom-Up)

自下而上
方法,也称为
迭代方法

动态规划方法
,是指从最小的子问题开始逐步构建解决方案,直到解决原始问题。这种方法通常用于动态规划算法中,但也可以用于一些简单的递归问题。

实现思路:

  1. 定义最小问题
    : 从问题的最小子问题开始解决。例如,在计算阶乘时,可以从
    0!

    1!
    开始。
  2. 逐步构建
    : 使用迭代或循环逐步计算更大的问题的解。
  3. 更新和存储
    : 将每个子问题的解存储起来,以便后续使用。

还是以计算阶乘的案例去介绍,自下而上实现方式:

def factorial_bottom_up(n):
    if n < 0:
        raise ValueError("Factorial is not defined for negative numbers")
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

# 示例用法
print(factorial_bottom_up(5))  # 输出 120

解释:

  • 从 1 开始迭代计算 2 到 n 的阶乘。
  • 通过逐步乘以每个数字来更新结果,最终得到
    n!

自上而下(Top-Down)

自上而下
方法也称为
递归方法
,是指从解决问题的最上层开始,递归地解决较小的子问题。这种方法在处理递归问题时非常自然(但可能存在重复计算的子问题,有些可以优化)。

实现思路:

  1. 定义主问题
    : 从问题的最上层开始解决。
  2. 递归分解
    : 将主问题递归地分解为较小的子问题。
  3. 基本情况
    : 定义递归的基本情况,以停止递归。

还是以计算阶乘的案例,展示自上而下实现:

def factorial_top_down(n):
    if n < 0:
        raise ValueError("Factorial is not defined for negative numbers")
    if n == 0 or n == 1:
        return 1
    return n * factorial_top_down(n - 1)

# 示例用法
print(factorial_top_down(5))  # 输出 120

解释:


  • n
    开始,递归调用
    factorial_top_down(n - 1)
    ,直到达到基本情况。
  • 在基本情况时返回
    1
    ,逐层返回乘积结果。

总结

  • 自下而上
    :通常是迭代的方法,逐步构建解决方案,适用于动态规划和需要避免重复计算的情况。它通常较为高效,尤其是在解决子问题重复计算时。

  • 自上而下
    :通常是递归的方法,直观地解决问题,但可能会有较高的时间复杂度和空间复杂度,尤其在处理大规模问题时。(可以通过记忆化(Memoization)来优化性能)

自上而下的思考过程——求和案例

一般来说,自下而上的实现过程比较好理解,所以这里多列举一些自上而下的案例帮助思考,

自上而下的编程思考过程:

  1. 把你正在写的函数想象成是别人实现过的函数。
  2. 辨别子问题。
  3. 看看你在子问题上调用函数时会发生什么,然后以此为基础继续。

求和案例
假设我们要写一个 sum 函数,计算数组中所有数的和。如果给函数传入数组[1,2,3,4,5],那么它会返回这些数的和15。

我们需要做的第一件事就是
想象
已经有人实现了 sum 函数。(当然,你可能会有点难以接受,毕竟写函数是我们自己,怎么能假设别人写好了呢! 但可以试着忘掉这一点,先假装 sum 函数已经实现好了。)

接下来,来辨别子问题。
比起科学,这个过程更像是艺术,只要你多练习就能进步。
在这个例子中,可以认为子问题是数组[2,3,4,5],即原数组中除第一个数以外的元素。

最后,来看看在子问题上调用 sum 函数会发生什么 ?
如果 sum 函数“已被正确实现”并且子问题是 [2,3,4,5],那么调用 sum([2,3,4,5])时会发生什么呢?会得到2+3+4+5的和,也就是14。

而要求[1,2,3,4,5]的和,只需向 sum([2,3,4,5])的结果再加上1即可。

请使用编程语言实现一下:

mylist = [1, 2, 3, 4, 5]
def mysum(alist):

    if len(alist) == 1:
        return alist[0]
    else:
		# alist[-1] = alist[len(alist)-1]
        alist[0] += alist[-1]

    return mysum(alist[0:len(alist) - 1])

print(mysum(mylist))
# 15

台阶问题 案例

为什么需要用递归?
image

image

请写出代码:

todo

易位构词生成 案例

这个是一个很实用的案例,之前想将多个pyload 以不同位置组合成一个整体,就遇到这个难题。

image

请写出代码:

todo

基于像素级分类器的单图像超分辨率方法(
PCSR
)是一种针对大图像高效超分辨率的新方法,在像素级别分配计算资源,处理不同的恢复难度,并通过更精细的粒度减少冗余计算。它还在推断过程中提供可调节性,平衡性能和计算成本而无需重新训练。此外,还提供了使用K均值聚类进行自动像素分配以及后处理技术来消除伪影。

来源:晓飞的算法工程笔记 公众号

论文: Accelerating Image Super-Resolution Networks with Pixel-Level Classification

Introduction


单图像超分辨率(
SISR
)是一项专注于从低分辨率(
LR
)图像恢复高分辨率(
HR
)图像的任务。这一任务在数字摄影、医学成像、监控和安全等多个领域有广泛的实际应用。随着这些重要需求的增长,特别是在深度神经网络(
DNNs
)的推动下,
SISR
在过去几十年中取得了显著进展。

然而,随着新的
SISR
模型的推出,模型的容量和计算成本往往增加,这使得在具有有限资源的实际应用或设备中应用这些模型变得困难。因此,这导致了朝向设计更简单、高效的轻量级模型的转变,这些模型考虑了性能和计算成本之间的平衡。此外,还进行了大量研究,旨在减少现有模型的参数大小和/或浮点运算(
FLOPs
)数量,同时不影响其性能。

与此同时,对于高效的超分辨率(
SR
)技术的需求正在增加,尤其是随着为用户提供大规模图像的平台的兴起,例如先进的智能手机、高清电视或支持从
2K

8K
分辨率的专业显示器。然而,对大图像进行超分辨率处理是具有挑战性的;由于计算资源的限制,无法在单次处理中处理大图像(即每图像处理)。因此,处理大图像的常见方法是将给定的低分辨率(
LR
)图像分割成重叠的块,将超分辨率模型分别应用于每个块,然后合并输出以获得超分辨率图像。多项研究探讨了这种按块处理的方法,旨在提高现有模型的效率同时保持其性能。这些研究观察到,每个块的恢复难度各不相同,因此需要为每个块分配不同的计算资源。

尽管在块级别上自适应分配计算资源可以显著提高效率,但它存在两个限制可能阻碍其充分发挥更高效率的潜力:

  1. 由于超分辨率是一个低级视觉任务,即使是单个块也可能包含恢复难度不同的像素。当为包含简单像素的块分配大量计算资源时,可能会导致计算资源的浪费。相反,如果一个分配较少计算资源的块包含难处理的像素,会对性能产生负面影响。
  2. 这些所谓的块分配方法在较大块大小时效率降低,因为这些块更可能包含易处理和难处理像素的平衡混合。这引入了一个两难情境:我们可能希望使用较大的块,因为这不仅可以减少重叠区域中的冗余操作,还可以通过利用更多的上下文信息来增强性能。

本文的主要目标是增强现有的单幅图像超分辨率(
SISR
)模型的效率,特别是针对较大的图像。为了克服前述的块分配方法所面临的限制,论文提出了一种名为像素级分类器单幅图像超分辨率(
PCSR
)的新方法,专门设计用于在像素级别自适应分配计算资源。模型包括三个主要部分:骨干网络、像素级分类器和一组容量不同的像素级上采样器。该模型的运作如下:1)骨干网络接受低分辨率输入并生成低分辨率特征图。2)对于
HR
空间中的每个像素,像素级分类器使用低分辨率特征图和像素的相对位置预测将其分配给特定的上采样器的概率。3)每个像素都根据需要自适应地分配给适当大小的像素级上采样器来预测其
RGB
值。4)通过聚合每个像素的
RGB
值获得超分辨率输出。

这是首个在大图像高效
SR
背景下应用像素级分配方法的方法。通过像素级别地减少冗余计算,可以进一步提升块分配方法的效率,如图
1
所示。在推断阶段,为用户提供调节性,可以在不需要重新训练的情况下权衡性能和计算成本。虽然该方法使用户能够管理这种权衡,但论文还提供了一个额外的功能,根据
K-means
聚类算法自动分配像素,从而简化用户体验。最后,论文引入了一种后处理技术,有效消除可能由像素级计算分配引起的伪影。实验证明,论文的方法在多个基准测试中(包括
Test2K
/
4K
/
8K

Urban100
)在各种
SISR
模型的
PSNR-FLOP
权衡方面优于现有的块分配方法。论文还与基于每幅图像处理的方法进行了比较,后者是将图像作为整体而不是分解为块来处理。

Method


Preliminary

单图像超分辨率(
SISR
)是一项任务,旨在从单个低分辨率(
LR
)输入图像生成高分辨率(
HR
)图像。在神经网络的框架内,
SISR
模型旨在发现一个映射函数
\(F\)
,将给定的
LR
图像
\(I^{LR}\)
转换为
HR
图像
\(I^{HR}\)
。可以用以下方程表示:

\[\begin{equation}
I^{HR} = F(I^{LR}; \theta),
\end{equation}
\]

其中
\(\theta\)
是模型参数集合。典型模型可以分解为两个主要组件:1)从
\(I^{LR}\)
提取特征的骨干网络
\(B\)
,和2)利用这些特征重构
\(I^{HR}\)
的上采样器
\(U\)
。因此,这个过程可以进一步表示为:

\[\begin{align}
Z = B(I^{LR}; \theta_B), \quad
I^{HR} = U(Z; \theta_U).
\end{align}
\]

在这里,
\(\theta_B\)

\(\theta_U\)
分别是骨干网络和上采样器的参数,
\(Z\)
是提取的特征。在基于卷积神经网络(
CNN
)的上采样器中,除了卷积层之外,还会使用多种操作来增加正在处理的图像的分辨率。这些操作范围从简单的插值到更复杂的方法,如反卷积或子像素卷积。与使用基于
CNN
的上采样器不同,可以采用基于多层感知器(
MLP
)的上采样器以像素级方式操作。

Network Architecture

PCSR
的概述如图
3
所示,一个模型由一个骨干网络和一组上采样器组成。此外,使用一个分类器来衡量在高分辨率空间中恢复目标像素(即查询像素)的难度。低分辨率输入图像被送入骨干网络,并生成相应的低分辨率特征。然后,分类器确定每个查询像素的恢复难度,通过相应的上采样器计算其输出的
RGB
值。

  • Backbone

论文提出了一种像素级计算分布方法,用于高效的大图像超分辨率。可以使用任何现有的深度超分辨率网络作为骨干网络,以适应所需的模型大小。例如,可以采用小尺寸的
FSRCNN
,中等尺寸的
CARN
,大尺寸的
SRResNet
,以及其他模型。

  • Classifier

引入了一个基于
MLP
网络的轻量级分类器,以像素级方式获取属于每个上采样器(或类别)的概率。对于给定的查询像素坐标
\(x_q\)
,分类器根据分类概率将其分配给相应的上采样器,以预测其
RGB
值。通过将简单的像素适当地分配给轻量级上采样器而不是重型上采样器,可以在最小性能损失的情况下节省计算资源。

定义一个低分辨率输入为
\(X \in \mathbb{R}^{h \times w \times 3}\)
,其对应的高分辨率图像为
\(Y \in \mathbb{R}^{H \times W \times 3}\)
。设
\(\{y_i\}_{i=1...HW}\)
是高分辨率图像
\(Y\)
中每个像素的坐标,
\(\{Y(y_i)\}_{i=1...HW}\)
是相应的
RGB
值。首先,通过骨干网络从低分辨率输入计算出一个低分辨率特征
\(Z \in \mathbb{R}^{h \times w \times D}\)
。然后,给定类别数
\(M\)
,分类器
\(C\)
给出分类概率
\(p_i \in \mathbb{R}^M\)

\[\begin{equation}
p_i = \sigma(C(Z, y_i; \theta_C)),
\end{equation}
\]

其中,
\(\sigma\)

softmax
函数。基于
MLP
的分类器操作方式类似于一个上采样器,主要区别在于其输出维度为
\(M\)

  • Upsampler

采用
LIIF
作为上采样器,适用于像素级处理。首先,将先前定义的
\(y_i\)
从高分辨率空间归一化,将其映射到低分辨率空间中的坐标
\(\hat{y}_i \in \mathbb{R}^2\)
。给定低分辨率特征
\(Z\)
,将最接近
\(\hat{y}_i\)
(通过欧氏距离)的特征表示为
\(z_i^* \in \mathbb{R}^D\)
,并将其对应的坐标表示为
\(v_i^* \in \mathbb{R}^2\)
。然后,上采样过程总结如下:

\[\begin{align}
\label{eq:liif1}
I^{SR}(y_i) = U(Z, y_i ; \theta_U) = U([z_i^*, \hat{y}_i-v_i^*]; \theta_U),
\end{align}
\]

其中,
\(I^{SR}(y_i) \in \mathbb{R}^3\)
是在
\(y_i\)
处的
RGB
值,[
\(\cdot\)
] 表示连接操作。通过查询每个
\(\{y_i\}_{i=1...HW}\)
处的
RGB
值并组合它们,可以得到最终的输出
\(I^{SR}\)
。在论文提出的方法中,可以利用
\(M\)
个并行的上采样器
\(\{U_0, U_1, ..., U_{M-1}\}\)
来处理各种恢复难度(即从重到轻的容量)。

Training

在训练阶段,将一个查询像素通过所有
\(M\)
个上采样器进行前向传播,并将输出聚合,以有效地反向传播梯度,具体操作如下:

\[\begin{equation}
\label{eq:pcsr2}
\hat{Y}(y_i) = \sum_{j=0}^{M-1} p_{i,j} \times U_j(Z,y_i;\theta_{U_j}),
\end{equation}
\]

其中,
\(\hat{Y}(y_i) \in \mathbb{R}^3\)
是在
\(y_i\)
处的
RGB
输出,
\(p_{i,j}\)
是该查询像素位于上采样器
\(U_j\)
中的概率。

然后,利用两种类型的损失函数:重构损失
\(L_{recon}\)
以及类似于
ClassSR
中使用的平均损失
\(L_{avg}\)
。重构损失定义为预测输出的
RGB
值与目标之间的
L1
损失,目标视为
GT
高分辨率(
HR
)块与双线性上采样的低分辨率(
LR
)输入块之间的差异。这是希望分类器能够通过强调高频特征来很好地执行分类任务,即使容量非常小。因此,损失可以表达为:

\[\begin{equation}
L_{recon} = \sum_{i=1}^{HW} \lvert (Y(y_{i}) - upX(y_{i})) - \hat{Y}(y_{i}) \rvert,
\end{equation}
\]

其中,
\(upX(y_i)\)
是双线性上采样的低分辨率(
LR
)输入块在位置
\(y_i\)
处的
RGB
值。对于平均损失,通过以下方式定义损失,以鼓励每个类别内像素的均匀分配:

\[\begin{equation}
\label{eq:loss2}
L_{avg} = \sum_{j=1}^{M} \lvert \sum_{n=1}^{N} \sum_{i=1}^{HW} p_{n,i,j} - \frac{NHW}{M} \rvert,
\end{equation}
\]

其中,
\(p_{n,i,j}\)
是第
\(n\)
张高分辨率图像(即批处理维度,批量大小为
\(N\)
)中第
\(i\)
个像素属于第
\(j\)
类的概率。在这里,将每个类别的概率视为分配给该类别的像素数量的有效数量。设置目标为
\(\frac{NHW}{M}\)
,希望在总共
\(NHW\)
个像素中,将相同数量的像素分配给每个类别(或者上采样器)。

最后,总损失
\(L\)
定义为:

\[\begin{equation}
L = w_{recon} \times L_{recon} + w_{avg} \times L_{avg}.
\end{equation}
\]

由于从头开始联合训练所有模块(即主干
\(B\)
,分类器
\(C\)
,以及上采样器
\(U_{j \in [0,M)}\)
)可能导致训练不稳定,所以采用多阶段训练策略。假设上采样器的容量从
\(U_0\)
逐渐减少到
\(U_{M-1}\)
,模型性能的上限由主干
\(B\)
和最重的上采样器
\(U_0\)
决定。因此,最初仅使用重构损失训练
\(\{B,U_0\}\)
。然后,从
\(j=1\)

\(j=M-1\)
,重复以下过程:先冻结已经训练好的
\(\{B, U_0, ..., U_{j-1}\}\)
,将
\(U_j\)
连接到主干(对于
\(j=1\)
还要新连接
\(C\)
),最后联合使用总损失训练
\(\{U_j, C\}\)

Inference


PCSR
的推断阶段,整体过程与训练类似,但查询像素根据预测的分类概率被分配给唯一的上采样器分支。虽然可以将像素分配给具有最高概率的分支,但论文为用户提供了在不重新训练的情况下控制计算性能平衡的能力。为此,论文考虑了
FLOP
(浮点运算数)计数在决策过程中的影响,定义并预先计算了每个上采样器
\(U_{j \in [0,M)}\)

FLOP
影响,如下:

\[\begin{equation}
cost(U_j) = \sigma(flops(B; (h_0, w_0)) + flops(U_j; (h_0, w_0))),
\end{equation}
\]

其中,
\(\sigma\)

softmax
函数,
\(flops(\cdot)\)
是给定固定分辨率
\((h_0, w_0)\)
的模块的
FLOPs
。像素
\(y_i\)
的分支分配如下确定:

\[\begin{equation}
argmax_j \frac{p_{i,j}}{[cost(U_j)]^k},
\end{equation}
\]

其中,
\(k\)
是一个超参数,
\(p_{i,j}\)
是之前提到的查询像素被分配到
\(U_j\)
的概率。根据定义,设置较低的
\(k\)
值会导致更多像素被分配给更重的上采样器,从而最小化性能下降但增加计算负载。相反,较高的
\(k\)
值会将更多像素分配给较轻的上采样器,以降低计算需求为代价接受性能下降。

  • Adaptive Decision Making (ADM)

尽管论文的方法允许用户管理计算性能平衡,论文还提供了一种额外功能,根据整个图像统计信息的概率值自动分配像素。其步骤如下:对于单个输入图像,给定
\(\forall p_{i,j}\)
并将
\(U_{j \in [0, \lfloor(M+1)/2\rfloor)}\)
视为重型上采样器,计算
\(sum_{0 \leq j<\lfloor(M+1)/2\rfloor} p_{i,j}\)
表示该像素的恢复难度,从而得到总数为
\(i\)
的值。然后使用聚类算法将这些值分组成
\(M\)
个簇。最后,根据每个簇的中心值将每个组分配给从最重的
\(U_0\)
到最轻的
\(U_{M-1}\)
的上采样器。可以采用
K-means
聚类算法来最小化计算负载,由于均匀初始化中心值,该过程具有确定性。

  • Pixel-wise Refinement

由于每个像素的
RGB
值是由独立的上采样器预测的,当相邻像素分配给具有不同容量的上采样器时可能会产生伪影。为了解决这个问题,论文提出了一个简单的解决方案:按容量将下半部分的上采样器视为轻型上采样器,上半部分视为重型上采样器,当相邻像素分配给不同类型的上采样器时则进行细化。具体而言,对于分配给
\(U_{j}\)
的像素,其中
\(\lfloor(M+1)/2\rfloor \leq j < M\)
(即轻型上采样器),如果至少有一个相邻像素被分配给
\(U_{j}\)
,其中
\(0 \leq j<\lfloor(M+1)/2\rfloor\)
(即重型上采样器),则将其
RGB
值替换为
SR
输出中相邻像素(包括自身)的平均值。像素级细化算法无需额外的前向处理,仅通过少量的额外计算有效减少伪影,并对整体性能影响最小。

Experiments




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

work-life balance.

大家好呀,我是summo,最近遇到了一个功能需求,虽然也是CURD,但属于那种比较复杂一点的CURD,话不多说,我们先看一下需求。

需求如下:

有三张表,学生表、课程表、学生课程关联表,关联关系如下图:

要求实现的功能:

  1. 支持输入名称模糊查询,可以是学生名称也可以是课程名称,但只有一个输入框;
  2. 要求以学生为主信息,一个学生所选的多门课程聚合展示;
  3. 支持分页查询。

产品原型大致如下:

原型画的有点丑,不过应该可以看的懂,需求还是合理的,现在压力给到了后端,如何写SQL才能查出这样的数据结构来呢?

首先,我们把表建一下,初始化一些模拟数据。
学生表:t_student

-- 创建表
CREATE TABLE `t_student` (
  `id` bigint NOT NULL COMMENT '物理主键',
  `stu_name` varchar(255) DEFAULT NULL COMMENT '学生名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 添加数据
INSERT INTO `t_student` VALUES (1, '张三');
INSERT INTO `t_student` VALUES (2, '李四');
INSERT INTO `t_student` VALUES (3, '王五');

课程表:t_course

-- 创建表
CREATE TABLE `t_course` (
  `id` bigint NOT NULL COMMENT '物理主键',
  `course_name` varchar(255) DEFAULT NULL COMMENT '课程名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 添加数据
INSERT INTO `t_course` VALUES (1, '语文');
INSERT INTO `t_course` VALUES (2, '数学');
INSERT INTO `t_course` VALUES (3, '英语');

学生课程关联表:t_student_course_rel

-- 创建表
CREATE TABLE `t_student_course_rel` (
  `id` bigint NOT NULL COMMENT '物理主键',
  `stu_id` bigint DEFAULT NULL COMMENT '学生ID',
  `course_id` bigint DEFAULT NULL COMMENT '课程ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- 添加数据
INSERT INTO `t_student_course_rel` VALUES (1, 1, 1);
INSERT INTO `t_student_course_rel` VALUES (2, 1, 2);
INSERT INTO `t_student_course_rel` VALUES (3, 2, 1);
INSERT INTO `t_student_course_rel` VALUES (4, 2, 2);
INSERT INTO `t_student_course_rel` VALUES (5, 2, 3);
INSERT INTO `t_student_course_rel` VALUES (6, 3, 2);
INSERT INTO `t_student_course_rel` VALUES (7, 3, 3);

这里我先不讲原理,直接放答案,大家可以先去试一下看看效果,然后再回来看原理。

聚合查询SQL如下:

SELECT
    t1.id,  -- 学生ID
    t1.stu_name,  -- 学生姓名
    GROUP_CONCAT(t3.id) AS course_ids,  -- 合并该学生所选课程的ID
    GROUP_CONCAT(t3.course_name) AS course_names  -- 合并该学生所选课程的名称
FROM
    t_student t1  -- 主表:学生信息
    LEFT JOIN t_student_course_rel t2 ON t1.id = t2.stu_id  -- 连接学生和课程关系表(左连接)
    LEFT JOIN t_course t3 ON t2.course_id = t3.id  -- 连接课程表(左连接)
WHERE
    t1.stu_name LIKE CONCAT('%', '张', '%')  -- 筛选学生姓名中包含'张'的记录
    OR t3.course_name LIKE CONCAT('%', '张', '%')  -- 或者筛选课程名称中包含'张'的记录
GROUP BY
    t1.id,  -- 按学生ID分组
    t1.stu_name  -- 按学生姓名分组
LIMIT 0,20   -- 分页查询

返回结果

从结果可以看到,course_ids和course_names将张三选择的课程聚合起来了,其中的主要功臣就是
GROUP_CONCAT
,它是 MySQL 中的一个聚合函数,主要用于将多个行的值连接成一个字符串。这在需要将某个列的多个值合并为一个结果时特别有用。比如,查询一个表中的某些记录,并将某个字段的多行值合并在一起,以便于更直观地查看。

  1. 聚合功能:与其他聚合函数(如 SUM、COUNT 等)类似,GROUP_CONCAT 将多个行的结果合并为一个单一的字符串。
  2. 分隔符:默认情况下,多个值之间用逗号 , 作为分隔符,但可以使用 SEPARATOR 关键字指定其他分隔符。
  3. 使用场景:通常用于 GROUP BY 查询中,以便将分组后的字段进行合并。

这篇文章虽然简短,但却是我日常开发的心得笔记。正所谓“千里之行,始于足下;细流汇聚,成就江海”,写作的乐趣正是在于不断积累。随着时间的推移,你会发现这些点滴已成为一笔珍贵的财富。

至此,全文结束,再会!

承接前文关于如何运用 AI 工具生成时序图的内容【1】,今天我们继续探讨 AI 驱动的 PlantUML:高效创建专业的 UML 类图与用例图。

【1】:
https://juejin.cn/post/7407637717206728755

【2】: 案例参照开源项目ruoyi-cloud:
https://gitee.com/y_project/RuoYi-Cloud

AI驱动分析类间关系并通过PlantUML生成类图

一、在Fitten Code中输入如下内容

@workspace 分析下com.ruoyi.system下岗位管理这功能各类之间的关系

得到的结果如下:
为了分析
com.ruoyi.system

岗位管理
功能各类之间的关系,我们需要查看相关的类和它们的方法。以下是基于提供的参考信息进行的分析:

1. 主要类和接口

1.1.
SysPost

  • 描述
    : 岗位实体类,包含岗位的基本信息。

  • 属性
    :


    • postId
      : 岗位ID
    • postCode
      : 岗位编码
    • postName
      : 岗位名称
    • postSort
      : 显示顺序
    • status
      : 状态
    • createBy
      : 创建者
    • createTime
      : 创建时间
    • updateBy
      : 更新者
    • updateTime
      : 更新时间
    • remark
      : 备注

1.2.
SysPostMapper
接口

  • 描述
    : MyBatis Mapper 接口,用于操作岗位数据。

  • 方法
    :


    • selectPostList
      : 查询岗位列表
    • selectPostAll
      : 查询所有岗位
    • selectPostById
      : 根据ID查询岗位
    • selectPostListByUserId
      : 根据用户ID查询岗位列表
    • selectPostsByUserName
      : 根据用户名查询岗位
    • checkPostNameUnique
      : 检查岗位名称是否唯一
    • checkPostCodeUnique
      : 检查岗位编码是否唯一
    • updatePost
      : 更新岗位信息
    • insertPost
      : 插入岗位信息
    • deletePostById
      : 根据ID删除岗位
    • deletePostByIds
      : 批量删除岗位

1.3.
SysPostService
接口

  • 描述
    : 岗位服务接口,定义岗位管理的相关业务逻辑。

  • 方法
    :


    • selectPostList
      : 查询岗位列表
    • selectPostAll
      : 查询所有岗位
    • selectPostById
      : 根据ID查询岗位
    • selectPostListByUserId
      : 根据用户ID查询岗位列表
    • selectPostsByUserName
      : 根据用户名查询岗位
    • checkPostNameUnique
      : 检查岗位名称是否唯一
    • checkPostCodeUnique
      : 检查岗位编码是否唯一
    • insertPost
      : 插入岗位信息
    • updatePost
      : 更新岗位信息
    • deletePostByIds
      : 批量删除岗位
    • export
      : 导出岗位信息

1.4.
SysPostServiceImpl

  • 描述
    :
    SysPostService
    接口的实现类,实现具体的业务逻辑。

  • 方法
    :


    • 实现
      SysPostService
      接口的所有方法

1.5.
SysPostController

  • 描述
    : 岗位管理控制器,处理HTTP请求。

  • 方法
    :


    • list
      : 查询岗位列表
    • export
      : 导出岗位信息
    • getInfo
      : 根据ID查询岗位详细信息
    • add
      : 新增岗位
    • edit
      : 修改岗位
    • remove
      : 删除岗位

2. 类之间的关系

2.1.
SysPost

SysPostMapper

  • SysPost
    是岗位实体类,
    SysPostMapper
    是操作
    SysPost
    数据的 MyBatis Mapper 接口。
  • SysPostMapper
    通过 MyBatis 映射文件 (
    SysPostMapper.xml
    ) 定义了与数据库交互的 SQL 语句。

2.2.
SysPostMapper

SysPostService

  • SysPostService
    接口定义了岗位管理的业务逻辑,
    SysPostServiceImpl
    是其实现类。
  • SysPostServiceImpl
    通过依赖注入
    SysPostMapper
    来访问数据库,实现业务逻辑。

2.3.
SysPostService

SysPostController

  • SysPostController
    是控制器类,处理HTTP请求,调用
    SysPostService
    的方法来完成业务逻辑。
  • SysPostController
    通过依赖注入
    SysPostService
    来访问业务逻辑。

3. 总结

岗位管理功能涉及的主要类和它们之间的关系如下:

  • SysPost
    是岗位实体类。
  • SysPostMapper
    是操作
    SysPost
    数据的 MyBatis Mapper 接口。
  • SysPostService
    是岗位服务接口,定义岗位管理的业务逻辑。
  • SysPostServiceImpl

    SysPostService
    的实现类,通过
    SysPostMapper
    访问数据库。
  • SysPostController
    是控制器类,处理HTTP请求,调用
    SysPostService
    的方法来完成业务逻辑。

这些类和接口通过依赖注入和方法调用,共同实现了岗位管理功能。

二、在Fitten Code中继续输入如下内容

接着上再用plantuml把岗位管理功能相关的类形成类图

分析出的结果如下:

@startuml

class SysPost {
    +Long postId
    +String postCode
    +String postName
    +Integer postSort
    +String status
    +String createBy
    +Date createTime
    +String updateBy
    +Date updateTime
    +String remark
}

interface SysPostMapper {
    +List<SysPost> selectPostList(SysPost post)
    +List<SysPost> selectPostAll()
    +SysPost selectPostById(Long postId)
    +List<Long> selectPostListByUserId(Long userId)
    +List<SysPost> selectPostsByUserName(String userName)
    +SysPost checkPostNameUnique(String postName)
    +SysPost checkPostCodeUnique(String postCode)
    +int updatePost(SysPost post)
    +int insertPost(SysPost post)
    +int deletePostById(Long postId)
    +int deletePostByIds(Long[] postIds)
}

interface SysPostService {
    +List<SysPost> selectPostList(SysPost post)
    +List<SysPost> selectPostAll()
    +SysPost selectPostById(Long postId)
    +List<Long> selectPostListByUserId(Long userId)
    +List<SysPost> selectPostsByUserName(String userName)
    +SysPost checkPostNameUnique(String postName)
    +SysPost checkPostCodeUnique(String postCode)
    +int insertPost(SysPost post)
    +int updatePost(SysPost post)
    +int deletePostByIds(Long[] postIds)
    +void export(SysPost post)
}

class SysPostServiceImpl {
    -SysPostMapper sysPostMapper
    +List<SysPost> selectPostList(SysPost post)
    +List<SysPost> selectPostAll()
    +SysPost selectPostById(Long postId)
    +List<Long> selectPostListByUserId(Long userId)
    +List<SysPost> selectPostsByUserName(String userName)
    +SysPost checkPostNameUnique(String postName)
    +SysPost checkPostCodeUnique(String postCode)
    +int insertPost(SysPost post)
    +int updatePost(SysPost post)
    +int deletePostByIds(Long[] postIds)
    +void export(SysPost post)
}

class SysPostController {
    -SysPostService sysPostService
    +ResponseEntity<TableDataInfo> list(SysPost post)
    +ResponseEntity<AjaxResult> export(SysPost post)
    +ResponseEntity<AjaxResult> getInfo(Long postId)
    +ResponseEntity<AjaxResult> add(SysPost post)
    +ResponseEntity<AjaxResult> edit(SysPost post)
    +ResponseEntity<AjaxResult> remove(Long[] postIds)
}

SysPostMapper <|-- SysPostServiceImpl : 依赖
SysPostService <|-- SysPostServiceImpl : 实现
SysPostService <|-- SysPostController : 依赖

@enduml

三、新建plantUml文件并复制如上内容

类图说明

  • SysPost
    类表示岗位实体,包含岗位的基本信息。
  • SysPostMapper
    接口定义了操作岗位数据的 MyBatis Mapper 方法。
  • SysPostService
    接口定义了岗位管理的业务逻辑。
  • SysPostServiceImpl
    类实现了
    SysPostService
    接口,并通过依赖注入
    SysPostMapper
    来访问数据库。
  • SysPostController
    类是控制器,处理HTTP请求,调用
    SysPostService
    的方法来完成业务逻辑。

通过这个类图,可以清晰地看到岗位管理功能中各个类之间的关系和依赖。
最终效果如下,可以导成图片文件。

四、在Fitten Code中继续输入如下内容

例如岗位管理主要划分为岗位新增、修改、删除和查询功能。通过plantuml生成用例图

与上同样新建一个plantUML,岗位用例图文件,复制生成的内容,岗位管理用例图就生成了,是不是很简单。