头像

Okamasa店老板




离线:15小时前


最近来访(507)
用户头像
TRswag
用户头像
礼堂腚疹
用户头像
ssssver
用户头像
LZH0217
用户头像
胡尔摩斯
用户头像
sealt
用户头像
雪雾怪是是
用户头像
jieHeEternity
用户头像
tianyangw
用户头像
安半愚
用户头像
yao4246
用户头像
梦惊醒
用户头像
vvvvjvvvv
用户头像
封禁用户
用户头像
情断青丝
用户头像
cityskyline原始人
用户头像
0x3f_study
用户头像
搁氿材
用户头像
og_
用户头像
Cauchy160


<<< $\color{blue}{ (●’◡’●) 点赞 <(- ●’◡’●) }$

    /\\       //>>>> 
   /__\\     //        关注加RP,AC更容易!
  /    \\   //>>>>>

<<< $\color{blue}{ (●’◡’●) 收藏 <(- ●’◡’●) }$


使用GPU训练

使用GPU训练。只考虑单卡训练的模式。使用device可以选择训练的显卡。
首先在系统上通过nvidia-smi确认显卡信息。通过CUDA_VISUABLE_DEVICES控制当前终端下可见的显卡。
pytorch.device模块控制是否使用GPU,nn.model.to, tensor.to方法控制数据或模型存储的设备(GPU/CPU)。

device = pytorch.device('cuda:0') // GPU 第一张显卡
device = pytorch.device('cpu') // CPU

net = torch.nn.Model()
net.to(device) // 指定设备上训练

multitask训练

多个模型共用一个backbone时,可以多个任务联合训练

backbone = BackBoneModel()
head1 = Task1Model()
head2 = Task2Model()
loss1 = Loss1()
loss2 = Loss2()
opt1 = optim.SomeMethod(head1.parameters(), ...)
opt2 = optim.SomeMethod(head2.parameters(), ...)
opt_backbone = optim.SomeMethod(backbone.parameters(), ..)
for X, y in data:
   feature = backbone(X)
   y1 = head1(feature)
   y2 = head2(feature)
   loss_a = loss1(y1, y)
   loss_b = loss2(y1, y)
   tot_loss = loss_a + loss_b
   head1.zero_grad()
   head2.zero_grad()
   back_bone.zero_grad()
   tot_loss.backward()
   opt1.step()
   opt2.step()
   opt_backbone.step()

也可以将两个head合进backbone,用同样的opt优化;

预训练,微调,冻结训练

【预训练】指的是在开源数据已经训练好的模型,利用其中提取特征的结构直接提取特征;
这样在目标任务数据集较小的时候可以不必从头训练,用较小的数据量达到较好的效果。
如果不希望backbone的参数改变,可以采用【冻结训练】。

载入预训练参数代码:

model_dict = model.state_dict()
pretrained_dict = torch.load(model_path, map_location = device)
pretrained_dict = {k: v for k, v in pretrained_dict.items() if np.shape(model_dict[k]) == np.shape(v)}
model_dict.update(pretrained_dict)
model.load_state_dict(model_dict)

冻结训练时可以利用parametersrequires_grad = false属性,停止后向传播;

for param in model.named_parameters():
    if param[0] in need_frozen_list:
        param[1].requires_grad = False
optimizer = torch.optim.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr=args.lr,momentum=args.momentum, weight_decay=args.weight_decay)

也可以用with torch.no_grad()API, 在模型定义中包住backbone的代码。

class xxnet(nn.Module):
    def __init__():
        ....
        self.layer1 = xx
        self.layer2 = xx
        self.fc = xx

    def forward(self.x):
        with torch.no_grad():
            x = self.layer1(x)
            x = self.layer2(x)
        x = self.fc(x)
        return x



<<< $\color{blue}{ (●’◡’●) 点赞 <(- ●’◡’●) }$

    /\\       //>>>> 
   /__\\     //        关注加RP,AC更容易!
  /    \\   //>>>>>

<<< $\color{blue}{ (●’◡’●) 收藏 <(- ●’◡’●) }$


这里实现几个经典的pytorch模型,并在FASION-MNIST数据集中进行测试和验证

数据集及输入输出

// 数据载入
import torchvision

mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())

batch_size = 256
if sys.platform.startswith('win'):
    num_workers = 0  # 0表示不用额外的进程来加速读取数据
else:
    num_workers = 4
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=1)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=1)
// 训练函数
def train(model, loss, opt, max_iter=10):
    cnt = 0
    for epoch in range(max_iter):
        train_l_sum = 0
        batch_count = 0
        for X, y in train_iter:
            y_hat = net(X)
            l = loss(y_hat, y)
            opt.zero_grad()
            l.backward()
            opt.step()
            train_l_sum += l.item()
            batch_count += 1
        print('epoch %d, loss %.4f'
              % (epoch + 1, train_l_sum / batch_count))

第一个卷积神经网络:LeNet

5.5_lenet.png

网络结构如上图所示.

网络分为卷积层和全连接层两部分。
卷积层部分,根据图片,第一层的维度变化:(1, 32, 32) -> (6, 28, 28)
可以知道这是个655的卷积操作;然后是subsample,可以使用22的池化操作进行降采样。
每个节点使用sigmoid作为激活函数。
全连接层部分,第一层将所有维度”摊平”到120维,它的输入维度与输入数据有关。假设输入数据维度为N
N,
则这一层输入维度为(16, ((N -4) / 2 - 4) / 2, ((N - 4) / 2 - 4) / 2)

这类没有旁路的网络可以直接用Sequential实现:

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2)
        )
        self.fc = nn.Sequential(
            nn.Linear(16*4*4, 120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output

LeNet训练及结果:

net = LeModel()
loss_func = nn.CrossEntropyLoss()
opt_func = torch.optim.Adam(net.parameters(), lr = 0.001, weight_decay = 0.0005)
train(
    net,
    loss_func,
    opt_func
)

训练结果:

epoch 1, loss 0.6551
epoch 2, loss 0.6172
epoch 3, loss 0.5909
epoch 4, loss 0.5683
epoch 5, loss 0.5511
epoch 6, loss 0.5383
epoch 7, loss 0.5268
epoch 8, loss 0.5167
epoch 9, loss 0.5069
epoch 10, loss 0.4977

测试准确率0.8


残差神经网络ResNet:

ResNet引入了残差块设计,用来解决网络过深时产生的退化问题。结构如下:
res_block.png
具体原理这里不细究,实现如下:

from torch import nn, optim
import torch.nn.functional as F

class ResBlock(nn.Module):
    def __init__(self, input_dim, output_dim, stride, use_1x1 = False):
        super(ResBlock, self).__init__()
        self.conv1 = nn.Conv2d(input_dim, output_dim, 3, padding = 1, stride = stride)
        self.conv2 = nn.Conv2d(output_dim, output_dim, 3, padding = 1)
        if (use_1x1):
            self.conv3 = nn.Conv2d(input_dim, output_dim, 1, stride = stride)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(output_dim)
        self.bn2 = nn.BatchNorm2d(output_dim)
    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        return F.relu(Y + X)

接下来实现传统的ResNet18模型
ResNet-18前两层在输出通道数为64、步幅为2的7×7卷积层后接步幅为2的3×3的最大池化层;使用ReLU作为激活函数。
和Block里一样,每次卷积层后都加上 BatchNorm2d进行归一化。
之后是4个Res块,最后接全局平均池化层。

resnet = nn.Sequential(
    nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
    nn.BatchNorm2d(64),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
resnet.add_module('res_layer1', nn.Sequential(
    ResBlock(64, 64, 1, False),
    ResBlock(64, 64, 1, False))
)
resnet.add_module('res_layer2', nn.Sequential(
    ResBlock(64, 128, 2, True),
    ResBlock(128, 128, 1, False))
    )
resnet.add_module('res_layer3', nn.Sequential(
    ResBlock(128, 256, 2, True),
    ResBlock(256, 256, 1, False))
    )
resnet.add_module('res_layer4', nn.Sequential(
    ResBlock(256, 512, 2, True),
    ResBlock(512, 512, 1, False))
    )
resnet.add_module("global_avg_pool",  nn.AdaptiveAvgPool2d(1))
resnet.add_module("fc", nn.Sequential(Flatten(), nn.Linear(512, 10))) 

Flatten只是做了shape的改变,把上一层BatchNum * 512 * 1 * 1的shape变为BatchNum * 512, 实现如下:

class Flatten(nn.Module):
    def __init__(self):
        super(Flatten, self).__init__()

    def forward(self, X):
        return X.view((X.shape[0], X.shape[1]))

训练:

loss_func = nn.CrossEntropyLoss()
opt_func = torch.optim.Adam(net.parameters(), lr = 0.001, weight_decay = 0.0005)
train(resnet, loss_func, opt_func)

epoch 1, loss 0.4420
epoch 2, loss 0.2978
epoch 3, loss 0.2690
epoch 4, loss 0.2460
epoch 5, loss 0.2284
epoch 6, loss 0.2155
epoch 7, loss 0.2005
epoch 8, loss 0.1923
epoch 9, loss 0.1813
epoch 10, loss 0.1721

测试的准确率为0.90,优于朴素的LeNet




<<< $\color{blue}{ (●’◡’●) 点赞 <(- ●’◡’●) }$

    /\\       //>>>> 
   /__\\     //        关注加RP,AC更容易!
  /    \\   //>>>>>

<<< $\color{blue}{ (●’◡’●) 收藏 <(- ●’◡’●) }$


时间有限,直接从线性回归进入卷积神经网络

卷积神经网络的基本组成

  • 卷积
  • 池化

卷积

卷积操作是滑动窗口,矩阵做点积后相加。
eg:

$$
\left[
\begin{array}{L}
0 & 1 \\
3 & 4
\end{array}
\right] * \left[
\begin{array}{L}
0 & 1 \\
2 & 3
\end{array}
\right] = 19
$$

对应输入输出维度计算公式:

$$
InputDim = {M, N, K} \\
KernelDim = {X, Y, K} \\
OutputDim = {M - X + 1, N - Y + 1, 1}
$$

对应的代码实现:

import torch 

def corr2d(X, K): 
    h, w = K.shape
    Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
    return Y

多输出维度的卷积

卷积在维度上做了累加操作,因此输出的维度都是1;
多输出维度的卷积多个卷积操作的结果堆叠形成。

输入输出维度计算公式:

$$
InputDim = {M, N, Kin} \\
KernelDim = {X, Y, Kin, Kout} \\
OutputDim = {Kout, M - X + 1, N - Y + 1, 1}
$$

def corr2d(X, K): 
    if len(K.shape) == 2:
        h, w = K.shape
        Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
        for i in range(Y.shape[0]):
            for j in range(Y.shape[1]):
                Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
        return Y
    if len(K.shape) == 3:
        s, h, w = K.shape
        Y = torch.zeros((s,X.shape[0] - h + 1, X.shape[1] - w + 1))
        for k in range(s):
            for i in range(Y.shape[1]):
                for j in range(Y.shape[2]):
                    Y[k, i, j] = (X[i:i+h, j:j+w] * K[k]).sum()
        return Y

1*1 卷积

11卷积的结构主要用在降低输出的维度,相当于在通道维上做全连接层。
可以直接用矩阵乘法来实现1
1卷积结构
这在Resnet中是一层重要的结构

代码实现如下:

def corr2d_multi_in_out_1x1(X, K):
    c_i, h, w = X.shape
    c_o = K.shape[0]
    X = X.view(c_i, h * w)
    K = K.view(c_o, c_i)
    Y = torch.mm(K, X)  # 全连接层的矩阵乘法
    return Y.view(c_o, h, w)

池化

池化用于提取局部区域的特征,缓解卷积对位置的过分敏感性。

常见的池化操作为均值池化核和最大值池化,torch中对应的实现是torch.nn.MaxPool2d, torch.nn.AvgPool2d



活动打卡代码 AcWing 4729. 解密

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>

using namespace std;

using LL = long long;

int main() {
    int k;
    cin >> k;
    for (int i = 0; i < k; i++) {
        LL ni, di, ei;
        cin >> ni >> di >> ei;
        LL tmp = ni - (ei * di) + 2;
        LL d = -4 * ni + tmp * tmp ;
        if (d < 0) {
            cout << "NO" << endl;
            continue;
        }
        LL r = sqrt(d);
        if (r * r != d) {
            cout << "NO" << endl;
            continue;
        }
        LL t2 = (tmp + r) / 2;
        LL t1 = (tmp - r) / 2;
        cout << t1 << " " << t2 << endl;
    }
    return 0;
}


活动打卡代码 AcWing 4728. 乘方

#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <iostream>

typedef long long LL;
int main() {
    int a, b;
    std::cin >> a >> b;
    double max_b = std::log(1e9) / std::log(a);
    if (b > ceil(max_b)) {
        std::cout << -1 << std::endl;
        return 0;
    }
    LL p = a; LL ans = 1;
    while (b) {
        if (b & 1) ans *= p;
        p *= p;
        b = b >> 1;
    }
    if (ans > 1e9) {
        std::cout << -1 << std::endl;
    } else {
        std::cout << ans << std::endl;
    }
    return 0;
}


活动打卡代码 AcWing 3422. 左孩子右兄弟

#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iostream>

std::vector<int> v {}, next {}, f {};
void dfs(int x) {
    if (f[x] == -1) {
        v[x] = 0; 
        return;
    }
    int y = f[x];
    int cnt = 0;
    int max_depth = -1;
    while (y != -1) {
        cnt ++;
        dfs(y);
        max_depth = std::max(max_depth, v[y]);
        y = next[y];
    }
    v[x] = max_depth + cnt;
}

int main() {
    int N;
    std::cin >> N;
    v.resize(N, 0); next.resize(N, -1); f.resize(N, -1);
    for (int i = 1; i < N; i++) {
        int t;
        std::cin >> t;
        t --;
        if (f[t] == -1) {
            f[t] = i;
        } else {
            next[i] = f[t];
            f[t] = i;
        }
    }
    dfs(0);
    std::cout << v[0] << std::endl;
    return 0;
}



#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <boost/unordered_set.hpp>
#include <vector>
typedef long long LL;
struct Node {
    int x;
    int y;
    Node operator+ (const Node &n) {
        return Node{n.x + x, n.y + y};
    }
};

int main() {
    int n, s;
    long long L;
    std::cin >> n >> L >> s;
    s++;
    std::vector<Node> map, small_map, not_small_map;
    boost::unordered_set< std::pair<int, int> > hashed;
    map.resize(n);
    small_map.resize(s*s);
    not_small_map.resize(s*s);
    for (int i = 0; i < n; i++) {
        int x, y;
        std::cin >> x >> y;
        map[i] = Node{x, y};
        hashed.insert(std::make_pair(map[i].x, map[i].y));
    }
    int small_cnt = 0, not_small_cnt = 0;
    for (int i = 0; i < s; i++) {
        for (int j = 0; j < s; j++) {
            int v;
            std::cin >> v;
            if (v == 1) {
                small_map[small_cnt++] = Node{s-1-i, j};
            } else {
                not_small_map[not_small_cnt++] = Node{s-1-i, j};
            }
        }
    }
    small_map.resize(small_cnt);
    not_small_map.resize(not_small_cnt);
    int ans = 0;
    for (int i = 0; i < n; i++) {
        bool match = true;
        Node diff {map[i].x, map[i].y};
        for (int j = 0; j < small_cnt; j++) {
            Node new_node = small_map[j] + map[i];
            if (new_node.x < 0 || new_node.x > L 
            || new_node.y < 0 || new_node.y > L) {
                match = false;
                break;
            }
            if (hashed.end() == hashed.find(std::make_pair(new_node.x, new_node.y))) {
                match = false;
                break;
            }
        }
        for (int j = 0; j < not_small_cnt; j++) {
            Node new_node = not_small_map[j] + map[i];
            if (new_node.x < 0 || new_node.x > L 
            || new_node.y < 0 || new_node.y > L) {
                match = false;
                break;
            }
            if (hashed.end() != hashed.find(std::make_pair(new_node.x, new_node.y))) {
                match = false;
                break;
            }
        }
        if (match) {
            ans++;
        }
    }
    std::cout << ans << std::endl;
    return 0;
}



<<< $\color{blue}{ (●’◡’●) 点赞 <(- ●’◡’●) }$

    /\\       //>>>> 
   /__\\     //        关注加RP,AC更容易!
  /    \\   //>>>>>

<<< $\color{blue}{ (●’◡’●) 收藏 <(- ●’◡’●) }$


建立模型需要的四个要素

  • 读取数据
  • 定义模型结构
  • 初始化参数
  • 设置优化算法
  • 设置损失函数

线性回归的模型结构

线性回归可以视为一个全连接的模型,模型图:

Input Output

x1 -----> |——|
x2 -----> |O |
x3 -----> |__|

线性回归的数据

可以先生成一些直线点,然后加上噪声作为线性回归的拟合数据。
假设直线方程是:
$$ y = 2x1 + 3x2 + 4x3 + 2.5 + noise $$

import numpy as np
from time import time
num_inputs = 3
num_examples = 1000
true_w = torch.tensor([2, -3, 4], dtype=torch.float32)
true_b = 5
features = torch.randn(num_examples, num_inputs,
                       dtype=torch.float32)
labels = torch.mm(features, true_w.view(3,1)) + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()),
                       dtype=torch.float32)

feature是随机值,labels是对应的真实值;我们希望通过feature->labels对应关系生成labels

模型定义

pytorch自带了线性网络的结构

class LinearNet(pytorch.nn.Module):
    def __init__(self, n_feature):
        super(LinearNet, self).__init__()
        self.linear = pytorch.nn.Linear(n_feature, 1)
    # forward 定义前向传播
    def forward(self, x):
        y = self.linear(x)
        return y

net = LinearNet(num_inputs)
print(net) # 使用print可以打印出网络的结构

初始化参数

torch.nn.init定义了初始化参数的各种方法,这里初始化为均值为0,标准差为0.01的正态分布

torch.nn.init.normal_(net[0].weight, mean=0, std=0.01)
torch.nn.init.constant_(net[0].bias, val=0)  # 也可以直接修改bias的data: net[0].bias.data.fill_(0)

定义损失函数

torch.nn内将损失函数也定义为特殊的层

loss = torch.nn.MSELoss()

定义优化函数

torch.optim 内实现了常见的梯度优化函数,这里使用普通的梯度下降,学习率设置为0.03

import torch.optim as optim
optimizer = optim.SGD(net[0].parameters(), lr=0.03)

开始训练

训练的一般流程:

  设置最大的迭代次数 max_epoch
  for epoch in range(max_epoch):
     计算输出
     计算损失函数
     后向传播计算梯度
     更新参数

结合上面的设置:

num_epochs = 10
for epoch in range(1, num_epochs + 1):
    for X, y in data_iter:
        output = net(X)
        l = loss(output, y.view(-1, 1))
        optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
        l.backward()
        optimizer.step()
    print('epoch %d, loss: %f' % (epoch, l.item()))

最后输出net的weight和bias,就可以验证模型了。



活动打卡代码 AcWing 4455. 出行计划

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <vector>

using namespace std;

int main() {
    int n, m, k;
    cin >> n >> m >> k;
    std::vector<int> t {};
    t.resize(2e5 + 1, 0);
    int max_t = 0;
    for (int i = 0; i < n; i++) {
        int ti, ci;
        cin >> ti >> ci;
        max_t = max(ti, max_t);
        int start = max(ti - ci + 1, 0);
        t[start] ++, t[ti+1] --;
    }

    for (int i = 1; i <= max_t; i++) {
        t[i] += t[i-1];
    }

    int mi;
    for (int i = 0; i < m; i++) {
        cin >> mi;
        mi += k;
        if (mi > max_t) {
            cout << 0 << endl;
        } else {
            cout << t[mi] << endl;
        }
    }
    return 0;
}


活动打卡代码 AcWing 4700. 何以包邮?

int main() {
    std::vector<int> cost {};
    int n, x;
    std::cin >> n >> x;
    std::vector<int> a {};
    a.resize(n, 0);
    int tot_cost;
    for (int i = 0; i < n; i++) {
        std::cin >> a[i];
        tot_cost += a[i];
    }
    cost.resize(tot_cost+1, 0);
    cost[0] = 1;
    for (int i = 0; i < n; i++) {
        for (int j = tot_cost; j >= a[i]; j--) {
            if (cost[j - a[i]]) cost[j] = 1;
        }
    }
    for (int i = x; i <= tot_cost; i++) {
        if (cost[i]) {
            std::cout << i;
            break;
        }
    }
    return 0;
}