TYS的博客

算法小白努力学习中

0%

MMCV组件Runner

Runner负责所有框架的训练过程调度。

配合各类的Hook,对外提供灵活的扩展能力。

1
2
3
4
5
6
7
8
9
10
11
12
def val(self, data_loader, **kwargs):
self.model.eval()
self.mode = 'val'
self.data_loader = data_loader
self.call_hook('before_val_epoch')
time.sleep(2) # Prevent possible deadlock during epoch transition
for i, data_batch in enumerate(self.data_loader):
self._inner_iter = i
self.call_hook('before_val_iter')
self.run_iter(data_batch, train_mode=False)
self.call_hook('after_val_iter')
self.call_hook('after_val_epoch')

在测是流程中,call_hook函数来按照优先级执行hook的不同阶段(e.g. after_val_epoch)的功能。

1
2
3
def call_hook(self, fn_name):
for hook in self._hooks:
getattr(hook, fn_name)(self)

如何修改mmdetection中的验证流程代码

在配置中默认validate变量为true

注册eval hooks,Hook类为EvalHook

在runner的流程中,在一个epoch后会执行EvalHook的after_train_epoch代码,其调用_do_evaluate

1
2
3
4
5
def _do_evaluate(self, runner):
"""perform evaluation and save ckpt."""
results = self.test_fn(runner.model, self.dataloader)
runner.log_buffer.output['eval_iter_num'] = len(self.dataloader)
key_score = self.evaluate(runner, results)

之后调用runner类的evaluate函数,调用datasets的evaluate函数,并将结果写入到runner.logger中,其是一个OrderedDict()字典结构(按照key插入顺序输出),并将ready状态设置为True

修改自己数据集的evalute函数,如imagenet数据集的evaluate函数

mmclassification训练

train

生成imagenet数据集

修改模型配置文件resnext50_32x4d.py修改输出类别

修改datasets imagenet.py CLASSES列表变量

1
CLASSES = ['Prim', 'Lym', 'Mono', 'Plas', 'Red', 'Promy', 'Myelo', 'Late', 'Rods', 'Lobu', 'Eosl']
1
python tools/train.py configs/resnet/resnet50_b32x8_imagenet.py  --work-dir work_dirs
swin_transformer
1
python tools/train.py configs/swin_transformer/swin_small_224_b16x64_300e_imagenet.py  --work-dir work_dirs_swin

test

1
python tools/test.py configs/resnet/resnet50_b32x8_imagenet.py work_dirs/latest.pth --out result_test/resnet.pkl --metrics precision --out-items all

分析结果

1
python tools/analysis_tools/analyze_results.py configs/swin_transformer/swin_small_224_b16x64_300e_imagenet.py result_test/resnet.pkl --out-dir result_test

训练swin_transformer

1
python tools/train.py configs/swin_transformer/swin_small_224_b16x64_300e_imagenet.py  --work-dir work_dir_swin --resume-from work_dirss/latest.pth

测试swin_transformer

1
python tools/test.py configs/swin_transformer/swin_small_224_b16x64_300e_imagenet.py work_dirs_swin/epoch_122.pth --out result_test/swin.pkl --metrics precision --out-items all

分析结果

1
python tools/analysis_tools/analyze_results.py configs/swin_transformer/swin_small_224_b16x64_300e_imagenet.py result_test/swin.pkl --out-dir result_test

训练在mmdetection上修改vit-transformer,尝试修改成为transformer-FG

1
python tools/train.py configs/vision_transformer/vit-base-p16_ft-64xb64_in1k-384.py  --work-dir work_dir/vit_fine_grained --resume-from work_dir/vit/latest.pth

骨髓血细胞分类标准

嗜酸、碱、中主要是针对细胞染色而言的,

嗜酸性:组织和细胞成分对酸性染料(如伊红)的亲和性

嗜碱性:组织和细胞成分对碱性染料(含有阳离子着色基团的染料,如苏木精、结晶紫、美蓝等)的亲和性。

淋巴细胞,胞体小,胞质少,胞核规则,染色质致密。

单核细胞 胞体较大,胞核有折叠,染色质偏细,较疏松

中英文对照

Neutrophils Eosinophlis basophils myeloblast promyelocyte myelocyte metamyelocyte
嗜中性粒细胞 嗜酸性粒细胞 嗜碱性粒细胞 原始粒细胞 早幼粒细胞 中幼粒细胞 晚幼粒细胞

https://www.bilibili.com/video/BV1tT4y1F76L?p=1

loss函数

BCE Loss

适用于二分类
$$
-ylog(\hat{y}) - (1 - y) log(1 - \hat{y})
$$

CE loss

当是多分类时,经过softmax后,只需要让GT对应类别输出尽量为1即可

Focal loss

mmdet/models/losses/focal_loss.py

Pytorch 卷积与梯度反向传播

pytorch计算图

https://zhuanlan.zhihu.com/p/33378444

https://towardsdatascience.com/pytorch-autograd-understanding-the-heart-of-pytorchs-magic-2686cd94ec95

https://zhuanlan.zhihu.com/p/69175484 这篇写的比较全,光说不练,假把式;光练不说,傻把式;连说带练~~真!把!式!’”

https://blog.csdn.net/cedi9117/article/details/106955001/ pytorch tensor计算

将梯度想象为小格子,因此同一层节点梯度是相加的,不同层的节点梯度是相乘的

img

理解一下
$$
z = f + g \
dz = k(df + dg) \
z = f * g * h \
z + dz = (f + df)(g + dg)(h + dh) 忽略高阶项
$$
https://zhuanlan.zhihu.com/p/27783097

Pytorch是利用Variable与Function来构建计算图的。Variable就像是计算图中的节点,保存计算结果(包括前向传播的激活值,反向传播的梯度),而Function就像计算图中的边,实现Variable的计算,并输出新的Variable。

torch.autograd.Funtion

矩阵求导

https://zhuanlan.zhihu.com/p/262751195

image-20211004160423050

利用pytorch实现卷积运算 https://zhuanlan.zhihu.com/p/349683405,爱因斯坦求和标记

python代码组织结构

https://blog.csdn.net/weixin_38256474/article/details/81228492

python 模块包(module package),当目录中包含了__init__.py时,当用import导入该目录时,会执行__init__.py中的代码。该文件的作用相当于将自身整个文件夹当作一个包来管理。

from xxx import *是如何实现的,__init__.py中的__all__变量关联了一个模块列表,制定哪些模块会被import进当前的作用域中。

python 命名空间

local namespace 函数或类方法

global namespace 当前模块

build_in namespace build_in空间

对于闭包,若在local namespace找不到变量,则下一个查找目标是父函数的local namespace

模块内置属性

__name__直接运行本模块,值为__main__, import module为模块名字

__file__当前module的绝对路径

__dict__,__doc__,__package__,__path__

python import运行机制

step1:创建一个新的、空的module对象(它可能包含多个module);
step2:将该module对象 插入sys.modules中;
step3:装载module的代码(如果需要,需先编译);
step4:执行新的module中对应的代码。

导入模块分为三类,第一类:导入 Python 内置模块,第二类:导入相关的第三方库模块,第三类:导入程序本地的模块 (即当前应用的模块)。导入不同类别模块时,需要使用空行进行分开。

1
2
3
4
5
6
7
8
9
# 内置模块
import time
import os

# 第三方模块
import flask

# 本地模块
from .xxx import xxx

绝对导入,从项目的根文件夹到要导入模块的完整路径。

相对导入,相对当前模块路径要导入资源的位置。

隐式相对导入(python3中不推荐) import module vs from . import module

python装饰器函数

https://www.cnblogs.com/f-ck-need-u/p/10198247.html

https://www.cnblogs.com/f-ck-need-u/p/10205168.html

闭包

如果想要在内函数中修改外函数的值,需要使用 nonlocal 关键字声明变量,闭包主要用于装饰器的实现。

装饰器 在mmdetection中的使用

装饰器的关键特性是在被装饰的函数被定义之后立刻执行,通常是在导入(import, from xx import xxx),即python模块加载时。

内置属性

__call__ 该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。

__repr__ 会返回和调用者有关的 “类名+object at+内存地址”信息。print(object),类实例化对象的自我描述信息

MMCV官方文档

https://mmcv.readthedocs.io/en/latest/api.html#module-mmcv.cnn

mmdetection中使用了注册机制,使用字典进行字符串向类的映射,在norm.py中,使用了如下的代码,以norm layer为例。

1
2
3
4
5
6
7
8
9
/mmcv/cnn/bricks/registry.py
NORM_LAYERS = Registry('norm layer')

/mmcv/cnn/bricks/norm.py
NORM_LAYERS.register_module('BN', module=nn.BatchNorm2d)
NORM_LAYERS.register_module('BN1d', module=nn.BatchNorm1d)
NORM_LAYERS.register_module('BN2d', module=nn.BatchNorm2d)

registry类实现在mmcv/runner/utils/registry.py文件中

build_norm_layer函数,通过type”BN”返回提前注册的类nn.BatchNorm2d,并用该类根据输入的num_features构造相应的归一化层,默认需要梯度(requires_grad=True)。

Pytorch源码学习

nn.Module

torch.utils.checkpoint 不缓存中间变量,时间换显存空间

ResNet源码阅读

preview

在mmcv的resnet.py文件中,对于resnet18与34,对应的block为BasicBlock,而50层以上的block为Bottleneck
卷积层分为stem与四个stage,stride=(1, 2, 2, 2),stride=2是第一个3x3的卷积层。在reset中,只有第一个块的stride是2,其余均为1。

self.downsample在第一层需要对原输入x进行大小与通道数的调整,后续的block不需要进行该操作。

1
2
if self.downsample is not None:
identity = self.downsample(x)

代码中的dcn为可变形卷积(deformable convlution)

_freeze_stage,将前几个stage的梯度更新设置为false,model设置为eval()模式。

block 每一个模块有num_blocks,对于blocks之间的连接,只有第一个block是进行残差学习。

在每一个block连接中,inplanes = planes * expansion,在每一个res_layer之间的连接中,planes = base_channels * 2 ** i ,在res_layer结束后,将inplanes设置为当前的planes * expansion

model.eval()

eval将dropout层设置为无效,batchnorm层的running_meanrunning_var不更新。BatchNorm层的均值与方差采用momentum进行更新$\hat{x}_{new} = (1 - momentum) \times \hat{x} + momentum \times x_t$。

drop_out实现原理

1
2
3
4
5
6
7
8
def dropout(X,drop_prob):
X = X.float()
assert 0<=drop_prob<=1
keep_prob = 1-drop_prob
if keep_prob==0:
return torch.torch.zeros_like(X)
mask = (torch.rand(X.shape)<keep_prob).float()
return mask * X / keep_prob

image-20210929231014229.png

比赛的时候是用C++写的,也是按照这个思路,按照行、列、主对角线、次对角线进行分类。这样可以将二维的坐标压缩到一维进行判断,且行,列,对角线的判断方法完全相同。

判断黑棋第一步赢,与白棋第二步赢比较简单。

  • 黑棋第一步赢,则放入后黑棋后,连起来为五个黑子。这样的位置大于等于一个即可
  • 白棋第二步赢,则首先不存在黑棋一步赢的情况,放入白棋后连续白子超过五个,这样的位置需要大于等于两个,因为黑棋第一步可以堵入一个位置。

判断黑棋第三步赢,则相对复杂些

  • 如果白棋存在五连的情况,第一步首先要填入白棋的位置,然后按照黑棋一步赢的情况查找可行位置,不过这种位置需要大于等于两个
  • 填入某个黑色棋子后,可以五连的位置大于等于两个,采用defaultdict(set)来记录某个位置对应可行位置的集合
  • 如果是三或四个棋子,则坐标的max - min一定要小于等于4,这几个棋子才有可能五连。然后枚举所有可行的最小的位置(区间[max - 4, min]),并定义为start,查找不在[start, start + 4]的空位置,按棋子个数为3,4分情况讨论。

比赛时候没有想到上述做法,而是直接进行枚举所有三个,四个棋子的情况。三个棋子时有$C_5^3$共10种情况,代码写的很长,还漏了边界case。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class Solution:
def gobang(self, pieces: List[List[int]]) -> str:
black = [defaultdict(set) for _ in range(4)]
white = [defaultdict(set) for _ in range(4)]
# 行、列、主对角线,次对角线
def add_pieces(x, y, c):
p = black
if c == 1:
p = white
p[0][x].add(y)
p[1][y].add(x)
p[2][x - y].add(y)
p[3][x + y].add(y)

def get_pos(x, y, k):
if k == 0:
return x, y
elif k == 1:
return y, x
elif k == 2:
return x + y, y
else:
return x - y, y


def find_pos(p, q, space):
pos = defaultdict(set)
for k in range(4):
for idx, se in p[k].items():
vec = sorted(list(se))
for i in range(len(vec) - space + 1):
start, end = vec[i], vec[i + space - 1]
if end - start > 4:
continue
# s为所有可能五连的起点
for s in range(end - 4, start + 1):
arr = [v for v in range(s, s + 5) if v not in vec[i:i + space] and v not in q[k][idx]]
if len(arr) == 2 and space == 3:
x1, y1 = get_pos(idx, arr[0], k)
x2, y2 = get_pos(idx, arr[1], k)
pos[(x1, y1)].add((x2, y2))
pos[(x2, y2)].add((x1, y1))
if len(arr) == 1 and space == 4:
x, y = get_pos(idx, arr[0], k)
pos[(x, y)].add((x, y))
return pos

for x, y, c in pieces:
add_pieces(x, y, c)
# 黑棋第一步赢,则可放入位置大于等于1
b1_pos = find_pos(black, white, 4)
if len(b1_pos) >= 1:
return 'Black'
# 白棋第二步赢,则可放入位置大于1
w2_pos = find_pos(white, black, 4)
if len(w2_pos) > 1:
return 'White'
# 白棋如果存在可赢的位置,则需要填入黑棋
if len(w2_pos) == 1:
for x, y in w2_pos.keys():
add_pieces(x, y, 0)
b3_pos = find_pos(black, white, 4)
if len(b3_pos) > 1:
return 'Black'
else:
b3_pos = find_pos(black, white, 3)
for p, cnt in b3_pos.items():
if len(cnt) >= 2:
print(p, cnt)
return 'Black'

return 'None'

https://www.jianshu.com/p/f2d800388d1c

img

神经网络是个系统工程,数据、参数、模型内部结构、训练策略、学习率等等,这些因素不管哪一部分出错,它都不会报错,只是会输出一些不是你想要的结果。Batch Normalization

输出的blob大小为(N, C, H, W),在每一层Normalization就是基于N * H * W个数值求平均,以及方差。