知识补全(不定期更新)

拉普拉斯矩阵(Laplacian Matrix)

对于一张图G=(V,E)G=(V,E),其拉普拉斯矩阵LL的定义为:L=DAL=D-A,其中DD是图的度矩阵,AA是邻接矩阵。举个例子,如下图:

image-20220801150939353

其邻接矩阵为:

A=(010010101010010100001011110100000100)A = \left(\begin{array}{llllll} 0 & 1 & 0 & 0 & 1 & 0 \\ 1 & 0 & 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 & 1 & 1 \\ 1 & 1 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 \end{array}\right)

度矩阵的计算方式,就是按照邻接矩阵中的每一列进行相加,然后将结果放到这一列在矩阵中的正对角线位置,如果是有向图的话,通常只考虑入度或出度。上面例子的度矩阵为

D=(200000030000002000000300000030000001)D=\left(\begin{array}{llllll} 2 & 0 & 0 & 0 & 0 & 0 \\ 0 & 3 & 0 & 0 & 0 & 0 \\ 0 & 0 & 2 & 0 & 0 & 0 \\ 0 & 0 & 0 & 3 & 0 & 0 \\ 0 & 0 & 0 & 0 & 3 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 \end{array}\right)

最后可以得到拉普拉斯矩阵:

L=DA=(210010131010012100001311110130000101)L = D-A=\left(\begin{array}{rrrrrr} 2 & -1 & 0 & 0 & -1 & 0 \\ -1 & 3 & -1 & 0 & -1 & 0 \\ 0 & -1 & 2 & -1 & 0 & 0 \\ 0 & 0 & -1 & 3 & -1 & -1 \\ -1 & -1 & 0 & -1 & 3 & 0 \\ 0 & 0 & 0 & -1 & 0 & 1 \end{array}\right)

拉普拉斯矩阵通常是一个对称矩阵,还有一种更常用的正则化拉普拉斯矩阵(Symmetric normalized Laplacian)其定义为:

Lsym :=D1/2LD1/2=ID1/2AD1/2L^{\text {sym }}:=D^{-1 / 2} L D^{-1 / 2}=I-D^{-1 / 2} A D^{-1 / 2}

这个矩阵中的元素由下面的式子给出(deg(vi)deg(v_i)为节点viv_i的度)

Li,jsym:={1 if i=j and deg(vi)0(自环)1deg(vi)deg(vj) if ij and vi is adjacent to vj0 otherwise. L_{i, j}^{\mathrm{sym}}:=\left\{\begin{array}{ll} 1 & \text { if } i=j \text { and } \operatorname{deg}\left(v_{i}\right) \neq 0 (自环)\\ -\frac{1}{\sqrt{\operatorname{deg}\left(v_{i}\right) \operatorname{deg}\left(v_{j}\right)}} & \text { if } i \neq j \text { and } v_{i} \text { is adjacent to } v_{j} \\ 0 & \text { otherwise. } \end{array}\right.

tensorboard

image-20210629203614177

使用很简单的,看下面代码就懂了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from torch.utils.tensorboard import SummaryWriter
import os

# 创建写入tensorboard的实例对象,传入参数为数据存放的文件夹
test= SummaryWriter('run')

for i in range(10):
# 添加图像的横轴与纵轴
test.add_scalar('Train/loss', i * 100 + 1, i)
test.add_scalar("test", i * 200 + 1, i)
test.close()
# 这句话可以在控制台输入,--logdir参数对应的是之前创建对象时写的文件夹
os.system("tensorboard --logdir=run")

如果是使用ssh连接服务器如何查看tensorboard的图像呢?

连接ssh时,将服务器的6006端口重定到自己的机器上来

1
ssh -L 16006:127.0.0.1:6006 username@ip

其中:16006:127.0.0.1:6006表示自己机器上的16006号端口,6006是服务器上的tensorboard使用的端口

在服务上使用6006端口正常启动tensorboard

1
tensorboard --logdir==xxx --port=6006

在本地浏览器中输入下面的网页地址即可查看tensorboard

1
127.0.0.1:16006

设置固定用哪张卡

torch.cuda.set_device(card_id),card_id是卡的编号

使用这个方法似乎只能针对在某张卡上训练,但是加载模型的时候还是会使用默认卡(暂时不确定是版本问题还是操作系统的问题)

因此在设置好torch.cuda.set_device(card_id)后,在torch.load()时,加入参数map_loaction="cuda:card_id"card_id还是之前卡的编号,这样就能把模型加载到指定的卡上了,当然如果想使用torch.cuda.is_available()确定可以使用在载入显卡时可以使用model.cuda(card_id)这样实现在对应卡中加载模型

在训练中进行验证爆显存问题

使用with torch.no_grad()就可以解决了

1
2
with torch.no_grad():
验证代码

使用字典参数的形式对模型进行修改

核心思想是在模型文件中进行修改,在加载参数时将预训练模型的参数存到字典里,并从其中提取新模型层需要的参数,然后重新加载,保存模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import torch
# 新模型只需要在原文件中进行修改
from models.model_stages import BiSeNet

# 新模型
model = BiSeNet(backbone="STDCNet1446", n_classes=19, pretrain_model=None,
use_boundary_2=False, use_boundary_4=False, use_boundary_8=True,
use_boundary_16=False, use_conv_last=False)
model.eval()
# 原模型的参数文件
originParams = torch.load("./checkpoints/STDC2-Seg/model_maxmIOU75.pth")
# 获取新模型的参数字典
modelDict = model.state_dict()
# 如果只加不删的话可以跳过这行,这里是从旧模型中拉去新模型中有的层
pullDict = {name: value for name, value in originParams.items() if name in modelDict.keys()}
# 更新新模型的参数
modelDict.update(pullDict)
# 向新模型中加载参数
model.load_state_dict(modelDict)
# 保存模型
torch.save(model, "STDC2.pth")
print("save finished!")

万恶的NinJa

问题描述:RuntimeError: Ninja is required to load C++ extension

1
2
3
wget https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip
sudo unzip ninja-linux.zip -d /usr/local/bin/
sudo update-alternatives --install /usr/bin/ninja ninja /usr/local/bin/ninja 1 --force

特征图可视化

先说说特征图可视化到底在可视什么,我们都知道一次卷积计算最后只会产生一张特征图,也就是通道为1的图,但是通常我们的卷积都不止一个通道,实际上是因为卷积核的数量不止一个,所以就可以出现多张特征图叠加的情况。

所以特征图可视化不可能把所有的卷积核出来的结果都显示出来,所以会选择几层进行展示,直接上代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 可视化函数
def visual(module, inputs, outputs):
# 当前模块,该模块的输入,该模块的输出
x = inputs[0][0]
y = outputs[0]
for i in range(10):
plt.imshow(x[i].detach().cpu().numpy())
plt.savefig(f"visiable/featuremap/input{i}.jpg")
plt.imshow(y[i].detach().cpu().numpy())
plt.savefig(f"visiable/featuremap/output{i}.jpg")
# exit()
net = torch.load(model_path, map_location='cpu')
for name, m in net.named_modules():
if isinstance(m, 这里填填要查看模块的类名):
# 这里是要添加一个钩子函数,
m.register_forward_hook(visual)

cv2无法使用imwrite和imread读取中文路径的图片咋办

1
2
3
4
# 替换imread:
img = cv2.imdecode(np.fromfile(self.path, dtype=np.uint8), -1)
# 替换imwrite:
cv2.imencode('.png', img)[1].tofile(self.mask_total_path)

Picgo+Gitee配置图床

image-20211114110822701

打开偏好设置,选择PicGo-Core(command line),然后打开配置文件,然后会打开config.json,文件将下面这个内容复制过去修改一下就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"picBed": {
"uploader": "gitee",
"current": "gitee",
"gitee": {
"repo": "用户名/仓库名",
"branch": "分支",
"token": "Token",
"path": "",
"customPath": "default",
"customUrl": ""
},
"transformer": "path"
},
"picgoPlugins": {
"picgo-plugin-gitee-uploader": true
},
"picgo-plugin-gitee-uploader": {
"lastSync": "2021-11-14 11:05:49"
}
}

指标类

miou

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 设标签宽W,长H
# 混淆矩阵
def fast_hist(res, gt, classes):
# res是转化成一维数组的标签,形状(H×W,);gt是转化成一维数组的标签,形状(H×W,)
k = (res >= 0) & (res < classes)
# np.bincount计算了从0到n**2-1这n**2个数中每个数出现的次数,返回值形状(n, n)
# 返回中,写对角线上的为分类正确的像素点
return torch.bincount(classes * res[k] + gt[k],
minlength=classes**2).reshape(classes, classes)
# iou
def iou_per_class(hist):
# 矩阵的对角线上的值组成的一维数组/矩阵的所有元素之和,返回值形状(n,)
return hist.diag() / (hist.sum(dim=0) + hist.sum(dim=1) - hist.diag())

# miou = torch.mean(iou_per_class(hist))

mae

1
2
3
def mae(res, gt):
# res,gt, (h,w), 0~1之间
return torch.mean(torch.abs(res - gt))

FβF_{\beta}

1
2
3
4
def fb(hist, beta2=0.3):
precision = hist[1, 1] / (hist[1, 1] + hist[0, 1])
recall = hist[1, 1] / (hist[1, 1] + hist[1, 0])
return (1 + beta2) * precision * recall / (beta2 * precision + recall)

NMI(归一化互信息)

互信息可以衡量两个分布之间的依赖程度,判断两种分布的一致性。

假设对10个样本点进行聚类,运用聚类算法得到的结果为:

A = [1,1,1,2,2,3,2,1,3,3]

标准聚类结果为:

B = [1,1,2,3,2,1,1,1,3,2]

令X = unique(A)=[1,2,3],Y = unique(B)=[1,2,3]

互信息(MI)的计算公式为:

M(X,Y)=i=1Xj=1YP(i,j)log(P(i,j)P(i)P(j))\boldsymbol{M}(X, Y)=\sum_{i=1}^{|X|} \sum_{j=1}^{|Y|} \boldsymbol{P}(i, j) \log \left(\frac{\boldsymbol{P}(i, j)}{\boldsymbol{P}(i) P^{`}(j)}\right)

其中P(i,j)\boldsymbol{P}(i,j)是联合概率分布,计算方式为P(i,j)=XiYjNP(i, j)=\frac{\left|X_{i} \cap Y_{j}\right|}{N}

按照上面的例子

P(1,1)=310,P(1,2)=110,P(1,3)=0,P(1,1)=\frac 3{10},P(1,2)=\frac 1{10},P(1,3)=0,

P(2,1)=110,P(2,2)=110,P(2,3)=110,P(2,1)=\frac 1{10},P(2,2)=\frac 1{10},P(2,3)=\frac 1{10},

P(3,1)=110,P(3,2)=110,P(3,3)=110P(3,1)=\frac 1{10},P(3,2)=\frac 1{10},P(3,3)=\frac 1{10}

然后计算分母中的概率函数P(i)=XiNP(i) = \frac {X_i}NP(j)=YiNP^`(j) = \frac {Y_i}N

按照上面的例子

P(1)=410,P(2)=310,P(3)=310P(1)=\frac 4{10},P(2)=\frac 3{10},P(3)=\frac 3{10}

P(1)=510,P(2)=310,P(3)=210P^`(1)=\frac 5{10},P^`(2)=\frac 3{10},P^`(3)=\frac 2{10}

这样就可以算出互信息了

标准化互信息的公式如下:

NMI(X,Y)=2MI(X,Y)H(X)+H(Y)NMI(X,Y) = \frac{2MI(X,Y)}{H(X)+H(Y)}

H(X)H(Y)H(X),H(Y)是交叉熵,其计算公式为:

H(X)=i=1XP(i)log(P(i))H(X)=j=1XP(j)log(P(j))H(X) = -\sum_{i=1}^{|X|}P(i)log(P(i))\\ H(X) = -\sum_{j=1}^{|X|}P^`(j)log(P^`(j))\\

这样就是标准化互信息的计算方式了

实现代码很简单

1
2
3
4
from sklearn import metrics
A = np.array([1,1,1,2,2,3,2,1,3,3])
B = np.array([1,1,2,3,2,1,1,1,3,2])
print(metrics.normalized_mutual_info_score(A,B)) # 直接调用sklearn中的函数

PUR(纯度,Purity)

这个和accuracy相似,是聚类的常见指标。计算公式为Purity=i=1kmimpiPurity=\sum_{i=1}^k\frac{m_i}mp_i

举个例子:

image-20220731174448058

假设有17个样本有3个聚类,在第一个中,x比较多应该是属于x的聚类,正确聚类的有5个;在第二个中○比较多应该是属于○的,正确聚类有4个;在第三个中◇比较多,应该是属于◇的,正确聚类的有3个。于是纯度的计算方式为5+4+317=0.7059\frac {5+4+3}{17}=0.7059

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from sklearn.metrics import accuracy_score
import numpy as np

def purity_score(y_true, y_pred):
"""Purity score
Args:
y_true(np.ndarray): n*1 matrix Ground truth labels
y_pred(np.ndarray): n*1 matrix Predicted clusters

Returns:
float: Purity score
"""
# matrix which will hold the majority-voted labels
y_voted_labels = np.zeros(y_true.shape)
# Ordering labels
## Labels might be missing e.g with set like 0,2 where 1 is missing
## First find the unique labels, then map the labels to an ordered set
## 0,2 should become 0,1
labels = np.unique(y_true)
ordered_labels = np.arange(labels.shape[0])
for k in range(labels.shape[0]):
y_true[y_true==labels[k]] = ordered_labels[k]
# Update unique labels
labels = np.unique(y_true)
# We set the number of bins to be n_classes+2 so that
# we count the actual occurence of classes between two consecutive bins
# the bigger being excluded [bin_i, bin_i+1[
bins = np.concatenate((labels, [np.max(labels)+1]), axis=0)

for cluster in np.unique(y_pred):
hist, _ = np.histogram(y_true[y_pred==cluster], bins=bins)
# Find the most present label in the cluster
winner = np.argmax(hist)
y_voted_labels[y_pred==cluster] = winner

return accuracy_score(y_true, y_voted_labels)

y_true = np.array([0, 0, 0, 1, 1, 1, 2])
y_pre = np.array([1, 1, 1, 2, 2, 2, 2])

print(purity_score(y_true,y_pre))
# 0.8571

参数量与计量获取

1
2
3
4
5
6
7
8
9
from thop import profile
net.eval()
a = torch.rand((1, 3, 320, 320))
flops, params = profile(net, inputs=(a,))
print(flops)
print(params)
print('FLOPs = ' + str(flops / 1000 ** 3) + 'G')
print('Params = ' + str(params / 1000 ** 2) + 'M')
exit(0)

服务器开启jupyter lab

先安装

1
pip install jupyterlab

生成配置文件

1
jupyter lab --generate-config

修改对应的配置文件vi ~/.jupyter/jupyter_lab_config.py 为如下内容

1
2
3
4
5
6
7
c.ServerApp.ip = '*'
c.ServerApp.port = 9999 # jupyter lab要使用的端口
c.ServerApp.open_browser = False
c.ServerApp.root_dir = 'path' # jupyter lab启动后进入的根路径,对根路径下的所有文件都有访问权限
c.ServerApp.password_required = True # 是否需要密码
c.ServerApp.password = 'password' # 这两个好像都是密码,设置成一样的就行
c.NotebookApp.token = 'password'

启动指令

1
jupyter lab

使用ip address查看一下公网或内网ip地址,然后本机访问https:\\服务器ip:jupyter lab端口即可,比如,假设服务器IP为45.16.233.154,则按照上面的配置方式访问的地址就是https:\\45.16.233.154:9999