目录
Part1 视频学习:
1 ShuffleNet V1 & V2
在ShuffleNet网络中使用了两个创新的操作:
- pointwise group convolution(逐点组卷积)
- channle shuffle(通道混洗)
逐点组卷积降低了逐点卷积(也即是1*1卷积)的计算复杂度; 同时为了消除多个组卷积堆叠产生的副作用,采用通道混洗的操作来改善跨特征通道的信息流动。使得ShuffleNet网络在保持准确率的情况下,极大的降低了计算成本。ShuffleNet网络在ImageNet竞赛和MS COCO竞赛中均表现了比其他移动端先进网络更优越的性能。
组卷积是在输入特征图的通道方向执行分组;逐点组卷积本只是组卷积的一种特殊形式,特殊的地方在于它的卷积核的核大小为1*1。如下图所示。
但是,多个组卷积堆叠在一起,会产生一个副作用:某个通道的输出结果,仅来自于一小部分输入通道。解决方法:通道混洗。
如图所示。通过对比图(a)和图(b),我们在第一个逐点组卷积之后,对输出的结果的通道次序进行打乱,比如原始在通道维度上的索引是0,1,2,3,4,5,6,7,8;那么打乱后变为了0,3,6,1,4,7,2,5,8。
经过这样打乱之后,输出通道就不再仅仅来自于是一小部分输入通道,也会来自其他的通道。即输出通道和输入通道完全的关联了。形成的效果如图(c)所示。
通道混洗的操作实现了多组卷积层的跨组信息流动。
利用逐点组卷积和通道打乱的操作,我们可以建立一个针对于小型网络特别设计的ShuffleNet unit。
最初设计是一个残差块(residual block)如图(a)所示。然后在(a)的残差分支中,对于其中的3 * 3卷积层,我们应用一个计算成本低的3 * 3 的DW卷积。然后我们替换了第一个逐点卷积,改为逐点组卷积,后面跟一个通道混洗操作。如图(b)所示。图(b)中的第二个逐点组卷积的目的是为了恢复输出通道数量(升维),从而和输入通道数量一致,以便能够实现与捷径分支的输出结果进行相加操作。(相加要求两个分支的特征图在宽度、高度和深度均一致)这就形成了一个完整的ShuffleNet unit了。
此外,我们知道卷积神经网络都需要有降采样操作,一种实现方式是采用最大池化层,另一种做法是使用stride=2的卷积来实现。
在ShuffleNet unit中,同样是采用的stride=2的卷积。如图(c)所示。具体做法是分别在捷径分支的分支设置stride=2和主分支的3*3 DW卷积中设置stride=2,从而既能够实现降采样的操作,同时又能够实现两个分支输出结果的融合。
这里还需要注意的两点是:
- 捷径分支上它采样的是3 * 3的平均池化。
- 融合没有采用相加的方法,而是通道方向的拼接。文中介绍到这样是为了更容易以很少的计算量来扩大通道维度。
2 EfficientNetV2
- 引入新的网络(EfficientNetV2),该网络在训练速度以及参数数量上都优于先前的一些网络。
- 提出了改进的渐进学习方法,该方法会根据训练图像的尺寸动态调节正则方法(例如dropout、data augmentation和mixup)。通过实验展示了该方法不仅能够提升训练速度,同时还能提升准确率。
- 通过实验与先前的一些网络相比,训练速度提升11倍,参数数量减少为 1 6.8 \frac{1}{6.8} 6.81。
3 Transformer里的 multi-head self-attention
3.1 Self-Attention
假设输入的序列长度为2,输入就两个节点
x
1
,
x
2
x_1, x_2
x1,x2,然后通过Input Embedding也就是图中的f ( x ) f(x)f(x)将输入映射到
a
1
,
a
2
a_1, a_2
a1,a2。紧接着分别将
a
1
,
a
2
a_1, a_2
a1,a2分别通过三个变换矩阵
W
q
,
W
k
,
W
v
W_q, W_k, W_v
Wq,Wk,Wv
(这三个参数是可训练的,是共享的)得到对应的
q
i
,
k
i
,
v
i
q^i, k^i, v^i
qi,ki,vi。
其中,
- q 代表query,后续会去和每一个 k 进行匹配
- k 代表key,后续会被每个 q 匹配
- v 代表从 a 中提取得到的信息
- 后续 q 和 k 匹配的过程可以理解成计算两者的相关性,相关性越大对应 v 的权重也就越大
假设
a
1
=
(
1
,
1
)
,
a
2
=
(
1
,
0
)
,
W
q
=
(
1
,
1
0
,
1
)
a_1=(1, 1), a_2=(1,0), W^q= \binom{1, 1}{0, 1}
a1=(1,1),a2=(1,0),Wq=(0,11,1),
那么:
q
1
=
(
1
,
1
)
(
1
,
1
0
,
1
)
=
(
1
,
2
)
,
q
2
=
(
1
,
0
)
(
1
,
1
0
,
1
)
=
(
1
,
1
)
\mathrm{q}^{1}=(1,1)\left(\begin{array}{l} 1,1 \\ 0,1 \end{array}\right)=(1,2), \quad \mathrm{q}^{2}=(1,0)\left(\begin{array}{l} 1,1 \\ 0,1 \end{array}\right)=(1,1)
q1=(1,1)(1,10,1)=(1,2),q2=(1,0)(1,10,1)=(1,1)
又因为Transformer是可以并行化的,所以可以直接写成:
(
q
1
q
2
)
=
(
1
,
1
1
,
0
)
(
1
,
1
0
,
1
)
=
(
1
,
2
1
,
1
)
\left(\begin{array}{l} \mathrm{q}^{1} \\ \mathrm{q}^{2} \end{array}\right)=\left(\begin{array}{l} 1,1 \\ 1,0 \end{array}\right)\left(\begin{array}{l} 1,1 \\ 0,1 \end{array}\right)=\left(\begin{array}{l} 1,2 \\ 1,1 \end{array}\right)
(q1q2)=(1,11,0)(1,10,1)=(1,21,1)
同理我们可以得到
(
k
1
k
2
)
\left(\begin{array}{c} k^{1} \\ k^{2} \end{array}\right)
(k1k2)和
(
v
1
v
2
)
\left(\begin{array}{c} v^{1} \\ v^{2} \end{array}\right)
(v1v2),那么求得的
(
q
1
q
2
)
\left(\begin{array}{c} q^{1} \\ q^{2} \end{array}\right)
(q1q2)就是就是原论文中的Q,
(
k
1
k
2
)
\left(\begin{array}{c} k^{1} \\ k^{2} \end{array}\right)
(k1k2)就是K,
(
v
1
v
2
)
\left(\begin{array}{c} v^{1} \\ v^{2} \end{array}\right)
(v1v2)就是V。接着先拿
q
1
q^1
q1和每个k进行match,点乘操作,接着除以
d
\sqrt{\mathrm{d}}
d 得到对应的
α
\alpha
α ,其中
d
\mathrm{d}
d 代表向量
k
i
\mathrm{k}^{\mathrm{i}}
ki 的长度,在本示例中等于2 ,除以
d
\sqrt{\mathrm{d}}
d 的原因在论文中的解释是“进行点乘后的数值很 大,导致通过softmax后梯度变的很小",所以通过除以
d
\sqrt{\mathrm{d}}
d 来进行缩放。比如计算
α
1
,
i
:
\alpha_{1, \mathrm{i}} :
α1,i:
α
1
,
1
=
q
1
⋅
k
1
d
=
1
×
1
+
2
×
0
2
=
0.71
α
1
,
2
=
q
1
⋅
k
2
d
=
1
×
0
+
2
×
1
2
=
1.41
\begin{array}{l} \alpha_{1,1}=\frac{\mathrm{q}^{1} \cdot \mathrm{k}^{1}}{\sqrt{\mathrm{d}}}=\frac{1 \times 1+2 \times 0}{\sqrt{2}}=0.71 \\ \alpha_{1,2}=\frac{\mathrm{q}^{1} \cdot \mathrm{k}^{2}}{\sqrt{\mathrm{d}}}=\frac{1 \times 0+2 \times 1}{\sqrt{2}}=1.41 \end{array}
α1,1=dq1⋅k1=21×1+2×0=0.71α1,2=dq1⋅k2=21×0+2×1=1.41
同理拿
q
2
q^2
q2去匹配所有的k能得到
α
2
,
i
\alpha_{2, i}
α2,i,统一写成矩阵乘法形式:
(
α
1
,
1
α
1
,
2
α
2
,
1
α
2
,
2
)
=
(
q
1
q
2
)
(
k
1
k
2
)
T
d
\left(\begin{array}{ll} \alpha_{1,1} & \alpha_{1,2} \\ \alpha_{2,1} & \alpha_{2,2} \end{array}\right)=\frac{\left(\begin{array}{l} \mathrm{q}^{1} \\ \mathrm{q}^{2} \end{array}\right)\left(\begin{array}{l} \mathrm{k}^{1} \\ \mathrm{k}^{2} \end{array}\right)^{\mathrm{T}}}{\sqrt{\mathrm{d}}}
(α1,1α2,1α1,2α2,2)=d(q1q2)(k1k2)T
接着对每一行即
(
α
1
,
1
,
α
1
,
2
)
\left(\alpha_{1,1}, \alpha_{1,2}\right)
(α1,1,α1,2) 和
(
α
2
,
1
,
α
2
,
2
)
\left(\alpha_{2,1}, \alpha_{2,2}\right)
(α2,1,α2,2)分别进行softmax处理得到
(
α
^
1
,
1
,
α
^
1
,
2
)
\left(\hat{\alpha}_{1,1}, \hat{\alpha}_{1,2}\right)
(α^1,1,α^1,2) 和
(
α
^
2
,
1
,
α
^
2
,
2
)
\left(\hat{\alpha}_{2,1}, \hat{\alpha}_{2,2}\right)
(α^2,1,α^2,2) ,这里的
α
^
\hat{\alpha}
α^ 相当于计算得到针对每个 v 的权 重。到这我们就完成了
A
t
t
e
n
t
i
o
n
(
Q
,
K
,
V
)
Attention (\mathrm{Q}, \mathrm{K}, \mathrm{V})
Attention(Q,K,V) 公式中
softmax
(
Q
K
d
k
)
\operatorname{softmax}\left(\frac{\mathrm{QK}}{\sqrt{\mathrm{d}_{\mathrm{k}}}}\right)
softmax(dkQK) 部分。
上面已经计算得到
α
\alpha
α,即针对每个v的权重,接着进行加权得到最终结果:
统一写成矩阵乘法形式:
(
b
1
b
2
)
=
(
α
^
1
,
1
α
^
1
,
2
α
^
2
,
1
α
^
2
,
2
)
(
v
1
v
2
)
\binom{b_1}{b_2} = \binom{\hat\alpha_{1, 1} \ \ \hat\alpha_{1, 2}}{\hat\alpha_{2, 1} \ \ \hat\alpha_{2, 2}}\binom{v^1}{v^2}
(b2b1)=(α^2,1 α^2,2α^1,1 α^1,2)(v2v1)
总结下来就是一个公式: Attention ( Q , K , V ) = softmax ( Q K T d k ) V \operatorname{Attention}(\mathrm{Q}, \mathrm{K}, \mathrm{V})=\operatorname{softmax}\left(\frac{\mathrm{QK}^{\mathrm{T}}}{\sqrt{\mathrm{d}_{\mathrm{k}}}}\right) \mathrm{V} Attention(Q,K,V)=softmax(dkQKT)V
3.2 Multi-Head Attention
实际使用中基本使用的还是Multi-Head Attention模块。
首先还是和Self-Attention模块一样将
a
i
a_i
ai分别通过
W
q
,
W
k
,
W
v
W_q, W_k, W_v
Wq,Wk,Wv得到对应的
q
i
,
k
i
,
v
i
q^i, k^i, v^i
qi,ki,vi。然后再根据使用的head的数目 h 进一步把得到的
q
i
,
k
i
,
v
i
q^i, k^i, v^i
qi,ki,vi均分成 h 份。比如下图中假设 h = 2 然后
q
1
\mathrm{q}^{1}
q1 拆分成
q
1
,
1
\mathrm{q}^{1,1}
q1,1 和
q
1
,
2
\mathrm{q}^{1,2}
q1,2 ,那么
q
1
,
1
\mathrm{q}^{1,1}
q1,1 就属于head1,
q
1
,
2
\mathrm{q}^{1,2}
q1,2属于head2。
接着将每个head得到的结果进行concat拼接,比如下图中
b
1
,
1
(
h
e
a
d
1
\mathrm{b}_{1,1} (head_{1}
b1,1(head1 得到的
b
1
)
\left.\mathrm{b}_{1}\right)
b1) 和
b
1
,
2
(
head
2
\mathrm{b}_{1,2}\left(\operatorname{head}_{2}\right.
b1,2(head2 得到的
b
1
)
\left.\mathrm{b}_{1}\right)
b1) 拼接在一起,
b
2
,
1
b_{2,1}
b2,1 (
h
e
a
d
1
head_1
head1得到的
b
2
b_2
b2)和
b
2
,
2
b_{2,2}
b2,2 (
h
e
a
d
2
head_2
head2得到的
b
2
b_2
b2)拼接在一起。
接着将拼接后的结果通过
W
O
W^O
WO(可学习的参数)进行融合,如下图所示,融合后得到最终的结果
b
1
,
b
2
b_1, b_2
b1,b2。
总结下来就是论文中的两个公式:
M
u
l
t
i
H
e
a
d
(
Q
,
K
,
V
)
=
C
o
n
c
a
t
(
h
e
a
d
1
,
…
,
h
e
a
d
h
)
W
O
w
h
e
r
e
h
e
a
d
i
=
A
t
t
e
n
t
i
o
n
(
Q
W
i
Q
,
K
W
i
K
,
V
W
i
V
)
MultiHead (\mathrm{Q}, \mathrm{K}, \mathrm{V})= Concat \left(\right. head _{1}, \ldots , head \left._{\mathrm{h}}\right) \mathrm{W}^{\mathrm{O}} \\ where\text{ }head { }_{\mathrm{i}}= Attention \left(\mathrm{QW}_{\mathrm{i}}^{\mathrm{Q}}, \mathrm{KW}_{\mathrm{i}}^{\mathrm{K}}, \mathrm{V} \mathrm{W}_{\mathrm{i}}^{\mathrm{V}}\right)
MultiHead(Q,K,V)=Concat(head1,…,headh)WOwhere headi=Attention(QWiQ,KWiK,VWiV)
Part2 代码练习
4 使用VGG模型进行猫狗大战
5 AI艺术鉴赏挑战赛 - 看画猜作者
代码
数据集处理:makedataset.py
import torch.utils.data as Data
from PIL import Image
import pandas as pd
# 读取训练数据和预测数据
def read_images(root=None, train=True):
if train is True: # 代表是训练数据
if root is None:
print('cannot find a root')
return None, None
image_list = pd.read_csv(root)
for i in range(image_list.shape[0]):
image_list.loc[i, 'filename'] = './train/' + image_list.loc[i, 'filename']
image_list.loc[i, 'label'] = image_list.loc[i, 'label']
data = image_list['filename'].to_list() # 感觉这边转list可能效率不太高
label = image_list['label'].to_list()
print(data)
print(label)
return data, label
else: # 代表是预测数据
data = []
for i in range(800): # test文件夹的照片数量是274,很直接(其实是我不知道有没有别的高端方法)
data.append('./test/' + str(i) + '.jpg')
print(data)
return data, None
class NewDataset(Data.Dataset):
def __init__(self, root, train, transforms):
self.transforms = transforms
self.data, self.label = read_images(root=root, train=train)
def __getitem__(self, index):
img = self.data[index]
img = Image.open(img).convert('RGB')
img = self.transforms(img)
if self.label is not None: # 代表是训练数据
label = self.label[index]
return img, label # 训练数据有image和label
return img # 测试数据只有image
def __len__(self):
return len(self.data)
# 测试
# read_images(root='./train1.csv', train=True)
# read_images(root=None, train=False)
train.py
import os
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import makedataset
from torch import nn, optim
import numpy as np
import pandas as pd
import torchvision
def auto_train_test():
batch_size = 20
monkey_train = makedataset.NewDataset(root='./train1.csv', train=True, transforms=transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
]))
monkey_train = DataLoader(monkey_train, batch_size=batch_size, shuffle=True)
monkey_test = makedataset.NewDataset(root=None, train=False, transforms=transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
]))
monkey_test = DataLoader(monkey_test, batch_size=batch_size, shuffle=True)
x, label = iter(monkey_train).next()
print('x: ', x.shape, ' label: ', label.shape)
print(x)
print(label)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
net = torchvision.models.resnet34() ####
model_weight_path = "./resnet34-pre.pth"
assert os.path.exists(model_weight_path), "file {} does not exist.".format(model_weight_path)
net.load_state_dict(torch.load(model_weight_path, map_location='cpu'));
for param in net.parameters():
param.requires_grad = False
in_channel = net.fc.in_features
net.fc = nn.Linear(in_channel, 49) # 修改最后一层全连接层
net.to(device) ####
# define loss function
loss_function = nn.CrossEntropyLoss()
# construct an optimizer
params = [p for p in net.parameters() if p.requires_grad]
optimizer = optim.Adam(params, lr=0.0001)
# model = ResNet18().to(device)
# criteon = nn.CrossEntropyLoss().to(device)
# optimizer = optim.Adam(model.parameters(), lr=1e-3)
# print(model)
for epoch in range(10):
net.train() #设置位train模式
running_loss = 0.0
for batchidx, (x, label) in enumerate(monkey_train):
# x [b,3,32,32]
# label [b]
# print(x,label)
x, label = x.to(device), label.to(device)
logits = net(x)
# logits: [b,10]
# label: [b]
# loss: tensor scalar
loss = loss_function(logits, label)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# print statistics
running_loss += loss.item()
print(epoch, loss.item())
net.eval() # 设置为测试模式
acc = 0.0 # accumulate accurate number / epoch
total = np.array([], dtype=np.int64)
with torch.no_grad():
# test
for x in monkey_test:
x = x.to(device)
# [b, 10]
logits = net(x)
# [b]
pred = logits.argmax(dim=1)[1] # 返回dim=1的那个格子是1的index
# acc += torch.eq(pred, val_labels.to(device)).sum().item()
# [b] -> scalar tensor
pred = pred.cpu()
pred = pred.detach().numpy()
total = np.append(total, pred) # 把预测值都存到total里面
# print(total)
return total
if __name__ == '__main__':
result = auto_train_test()
df = pd.DataFrame(result)
df.to_csv('./result.csv', header=False)