3.19
开始复工复产了
发现poe网站上可以免费用chatgpt,用了两天就彻底产生依赖性了
继续看d2l,之前看到第三章结束,今天从4.1看到4.6
第四章讲mlp,老生常谈,各种激活函数ReLU、sigmoid、tanh
然后是防止过拟合,需要做正则化:比如在损失函数中加入正则项,也叫权重衰减法(没想到这玩意上学期上课就讲过了),或者是通过dropout来提升模型的泛化能力(也叫暂退法)
这两玩意儿实现都很简单(pytorch的高级接口早就留好了)
权重衰减法:(直接在SGD后面加入一个weight_decay参数,这个是直接作用在所有的W权重上面的,听gpt说一般给1e-4~1e-5)
dropout:(直接在激活层后面加入dropout层,里面填写drop的概率,只作用于训练过程)
如果想有更细化的规则,比如让某些层的W惩罚权重更高,这种就只能自己多写一个超参数,不过d2l上面也讲了怎么具体实现。
另外还发现dropout的效果比权重衰减的效果更好一些,不过实验数据有限,还是具体情况具体分析更好。
此外还有一些初始化的方法,比如Xavier初始化,可以对tanh和sigmoid激活函数比较友好,避免梯度消失和梯度爆炸的情况(感谢d2l上网友的分享)
除了看d2l,之前在B站还看到了一个自动打原的项目,感觉十分有趣,开始看强化学习DQN的内容,然后用python写了一个获取屏幕数据的demo,测试了四种录屏的python库,优化来优化去最高也只有30帧率,然后被队内大佬喷了,说为什么不用C++写 :(((
PIL、pygetwindow、mss、pyscreenshot四个库,测出来mss是最快的
晚上把d2l第四章基本推完了
学习了前向传播计算图,实际上这个就是一个有向无环图,用于帮助我们理清计算偏导数的思路
看了Xavier初始化的证明,发现了一个比较NB的论文https://arxiv.org/pdf/1806.05393.pdf,通过给定合适的初始值来训练10000层CNN,现代参数初始化的方向还是又很多可以研究的东西。
最后看了环境和分布偏移的相关问题,有三种分布偏移:
协变量偏移(P(x)分布改变,即输入数据内容占比发生改变)、
标签偏移(P(y)分布改变,即输入数据的标签分布发生改变)、
概念偏移(P(y|x)分布改变,即输入到输出的对应关系的分布发生改变)
这个确实在之前遇到过,例如识别物体时,训练集中物体在远处和近处的分布如果与实际测试时的分布不同,就会导致误识别,这个应该属于协变量偏移。
有一些修正偏移的手段,除了概念偏移无法修正,只能重新训练之外,协变量偏移和标签偏移都可以用一些办法修正,具体讲起来很复杂,但d2l里面还是写得很详细,可以看一下。
最后还发现一个神奇的论文:https://arxiv.org/pdf/2004.05988.pdf 把控制理论PID的那一套东西搬到了超参数调整上,有空可以看看。
3.20
上课,课上划水把Kaggle预测房价的比赛打了,发现很容易过拟合,最后只用了一层神经网络,感觉就挺奇怪的,调了一下学习率和迭代轮数,最后是0.14+
学到了一些通用的数据预处理方法:
# 删除编号和结果
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
# 规范化数值特征
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
all_features[numeric_features] = all_features[numeric_features].apply(lambda x: (x - x.mean())/(x.std())).fillna(0)
# 独热编码
all_features = pd.get_dummies(all_features, dummy_na=True)
学了一下K折叠交叉验证(这个主要是用来验证模型泛化性的,最后还要把整体数据重新做一次训练)
继续看d2l第五章
自定义块
class xxx(nn.Module): 需在类中重载__init__和forward函数
可以手动关闭nn.Linear(默认开启requires_grad)的反向传播梯度计算
linear_layer.weight.requires_grad_(False)
使用.state_dict()访问模型中的参数列表与值
也可以直接通过名称weight和bias访问linear层的参数tensor
使用.named_parameters()获取模型中所有的参数名称与类型的迭代器
print([(name,param.shape) for name, param in net.named_parameters()])
还可以直接用print(net)获取网络结构,相当智能,并且会自动为嵌套内容命名
自定义初始化函数,然后使用.apply()运用到整个网络中的每一个单元模块(会访问嵌套在内层的子模块)
import torch
from torch import nn
from d2l import torch as d2l
class MymoduleA(nn.Module):
def __init__(self,input_n):
super().__init__()
self.hidden1=nn.Linear(input_n,10)
self.out=nn.Linear(10,1)
def forward(self, X):
return self.out(nn.ReLU(self.hidden1(X)))
def init(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight,mean=0,std=0.01)
nn.init.zeros_(m.bias)
net = nn.Sequential(nn.Linear(3,5),nn.Sigmoid(),MymoduleA(5))
# print([(name,param.shape) for name, param in net.named_parameters()])
print(net)
print(net.state_dict())
net.apply(init)
print(net.state_dict())
还可以直接对任意参数设置指定的初始值
如果需要让两层之间的参数相同,则在实例化的时候使用相同的实例就可以了
延后初始化可以使得模型更灵活,可以在有输入数据时再进行网络的初始化,torch.nn.LazyLinear
自定义层,通过nn.Parameters注册参数(?),定义weight和bias,在forward中定义运算规则
使用torch.save(net.state_dict(),‘文件名’),和net.load_state_dict(torch.load('文件名'))来保存/读取模型参数,但是似乎也可以直接保存模型torch.save(net,'net.pt') net=torch.load('net.pt')
倒腾了一下GPU和CUDA,发现自己Pytorch装的CPU版本,明天重配一下conda环境再试试。
3.21
清算,终将到来
上午把d2l第六章看完了,因为之前是做视觉方向的,所以过得很快,只记录一些不熟悉的点
· 严格来说神经网络里面的卷积层是互相关运算
· 多通道输入:每一个通道都对应一个卷积核,卷积结果相加得到最终结果
多通道输出:每一个输出通道对应一组卷积核,这组卷积核各自对输入的通道进行卷积然后相加,最终得到一个输出通道的结果
若有N通道输入、M通道输出、卷积核大小均为Kn*Km,那么该层卷积网络参数量为N*M*Kn*Km
当然也可以自定义各个通道的卷积核形式,写一个自定义层即可
· 1*1卷积层,相当于只对图像上的一个像素的多个通道进行线性组合,从而得到新的图像,类似于色彩空间变换、以及对图片饱和度、明度、色相等属性的自动特征组合提取。
· 池化层(好像也叫汇聚层?)的默认步长为池化核的大小
上午听了两个报告,第二个完全听不懂,第一个讲的是预训练语言模型DNA BERT和bioBERT用这两个计算DNA和疾病的节点嵌入,然后与GCN的结果进行拼接之后做一个RNA到疾病的异构网络链接预测,想法中规中矩,就是直接把RNA逆转录回DNA可能会有内含子缺失,不过这个误差应该无伤大雅,估计后面还会与训练RNA相关的语言模型,就好解决一些。(不过我连预训练都不会
然后就是清算进度了
中午把torch重装了GPU版本,下午测了一下,发现GPU嘎嘎猛
import torch
import time
from torch import nn
from d2l import torch as d2l
X = torch.randn(10000,10000)
st = time.time()
for i in range(100):
X = X*X
et = time.time()
print('CPU',et-st)
gpu = d2l.try_gpu()
X = torch.randn(10000,10000,device=gpu)
st = time.time()
for i in range(100):
X = X*X
et = time.time()
print('GPU',et-st)
上面这个应该是对应乘积,下面这个是矩阵乘法的测试
import torch
import time
from torch import nn
from d2l import torch as d2l
X = torch.randn(5000,5000)
st = time.time()
for i in range(10):
X = torch.mm(X,X)
et = time.time()
print('CPU',et-st)
gpu = d2l.try_gpu()
X = torch.randn(5000,5000,device=gpu)
st = time.time()
for i in range(10):
X = torch.mm(X,X)
et = time.time()
print('GPU',et-st)
性能相当优越啊
下午照着d2l搓了一下LeNet,发现数据载入出现了一些奇奇怪怪的问题,搞了半天发现是内存爆掉了,pytorch换了cuda版本之后会加载很多很大的dll文件,导致把页面文件撑爆,在高级系统设置里面调整虚拟内存大小之后就好了
改完之后继续训LeNet,acc43%,差点没把我人送走,结果是自己在最后一层加了一个Sigmoid,删掉之后就好很多了
import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(nn.Conv2d(1,6,kernel_size=5,padding=2),nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2),
nn.Conv2d(6,16,kernel_size=5),nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2),
nn.Flatten(),
nn.Linear(16*5*5,120),nn.Sigmoid(),
nn.Linear(120,84),nn.Sigmoid(),
nn.Linear(84,10),
)
print(net)
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
"""使用GPU计算模型在数据集上的精度"""
if isinstance(net, nn.Module):
net.eval() # 设置为评估模式
if not device:
device = next(iter(net.parameters())).device
# 正确预测的数量,总预测的数量
metric = d2l.Accumulator(2)
with torch.no_grad():
for X, y in data_iter:
if isinstance(X, list):
# BERT微调所需的(之后将介绍)
X = [x.to(device) for x in X]
else:
X = X.to(device)
y = y.to(device)
metric.add(d2l.accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
"""用GPU训练模型(在第六章定义)"""
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
# 训练损失之和,训练准确率之和,样本数
metric = d2l.Accumulator(3)
net.train()
for i, (X, y) in enumerate(train_iter):
timer.start()
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % num_batches == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches, (train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, 'f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec 'f'on {str(device)}')
lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
d2l.plt.show()
晚上听了个招聘会,回来继续看d2l第七章,简单记录一下学到的知识
AlexNet中使用了Dropout和ReLU,实际上比LeNet也没多几层,但是参数量有点大,我LeNet都会被训练爆内存,感觉在自己电脑上训练还是算了。之后在服务器上配个环境再来跑跑。
VGG块,包含n个3*3的卷积层(ReLU激活)和一个2*2的max池化层,最后用全连接4096->4096->1000来分类,论文表示用深层的窄卷积核比用浅层的宽卷积核有效
NiN块,一个k*k卷积层加两个1*1卷积层(均为ReLU激活),最后的通道数控制为10,对每一个通道单独做一个全局平均池化来获得结果向量。评论区中讨论删掉一个1*1卷积层会在小图像上的表现更好。
GoogLeNet,Inception块,就是把不同大小的卷积核、池化层组合一下,把各个组合输出结果按照通道叠到一起,但是每个卷积的通道数是精心设计过的(这里引用一下d2l原文,设计相当复杂,各个模块输出的比例也是非常讲究的)
一下子学太多容易头大,明天再来
3.22
Stay Hungry, Stay Foolish
继续学第七章
正则化(规范化),统一参数的量级,避免训练时参数运算的数量级不同导致一个学习率无法适应多个层级的训练,规范化层的运算就是B(x)=k((x-均值)/标准差)+b,这里的均值和方差是通过一个小批量的数据来统计的(所以叫批量规范化,一般适用于50~100的batch_size),k是对正则化后的tensor做一个对应乘法,只在单个数据上操作,不涉及数据间的组合。
标准差估计值需要加上一个微小的噪声,避免除以0的情况
批量规范化层一般加载全连接、卷积之后
nn.BatchNorm2d(通道数)
残差块(引用一下d2l的代码),之前一直很好奇是怎么做残差的,其实就是卷积结果和最初输入直接相加
ResNet堆叠了一些残差块,残差块内部也包含3*3卷积、批量规范化、ReLU,在最后也通过全局平均池化来获取分类信息。
残差块设计理论为嵌套函数理论,在搜索最优函数过程中,必须保证扩大的函数集包含原本函数级,否则可能会导致函数集合偏移的情况,让训练更容易陷入局部最优解,对训练是不利的
这个直接相加的运算就可以保证,在堆叠残差块的过程中,堆叠前的函数集合可以包含在堆叠后的函数集合中(因为如果新堆叠的块参数均为0,就和原本块的函数集合相同了,Y=F(X)+X,若F(x)=0,则Y=X)
稠密块,直接把原数据堆叠到当前层输出之后,然后利用1*1卷积和平均池化来控制规模
DenseNet,里面是先做BatchNorm,再做ReLU,最后做Conv,感觉也没什么问题?
之前的结构CBRCBRCBR……,现在的结构BRCBRCBR……循环节是一样的,只不过让输入的原始数据先直接进行了一次规范化和ReLU而已。
第八章
马尔可夫模型与隐马尔可夫模型
简单来说,马尔可夫模型就是一条状态链,确定了每一步的转移都只与前面的状态有关(类比于线上的动态规划,递推方程之类的)。当然也有简化版的,只和前一步有关的一阶马尔可夫模型。
隐马尔可夫模型是的N个状态可以构成一个完全图,有初始的状态概率分布,有状态间转移的概率矩阵,以及每个状态表达的M种结果也是一个分布,记为HMM(Π,A,B),这个模型的运用比较广泛。(类似于随机游走plus版?)
例如我们只能得到表达结果的序列,可以以此逆推出可能的状态转移链,利用一些搜索算法可以找到概率最大的状态转移链。比如Viterbi算法
也可以通过初始状态,来推算接下来的状态转移链和表达结果的序列(直接递推就可以了)
也可以通过收集表达结果的序列,训练Π、A、B参数,来使用HMM拟合随机过程。
参考隐马尔可夫模型(HMM)详解 - 知乎
使用马尔可夫模型的时候要注意因果关系
MLP也可以用于序列预测,但是预测误差会不断累计导致后续误差偏大
晚上回去摆烂了,所以没更新
3.23
文本预处理,主要是词元化和词表编号,把高于一定词频的单词进行编号,低于该词频的就直接作为未知词元处理。
语言模型。一般通过词频来计算一个单词B出现在另一个单词A之后的概率,但是在语料库中,可能两个单词连续出现的数量会很少,所以我们可以做一个拉普拉斯平滑,即根据单词B的出现概率乘上一个ε来扩展语料库。(但是拉普拉斯平滑实际上并不适合用于语言建模)
(公式来自d2l)
一元语法、二元语法……n元语法,即当前语言模型使用的是0阶、一阶、n-1阶马尔可夫模型
停用词过滤,对于中文需要额外考虑分词
词频统计曲线(去除停用词之后)在对数层面上是一个线性函数(称为满足齐普夫定律)
ni表示词频第i高的单词的出现次数
(公式来自d2l)
齐普夫定律告诉我们通过平滑和计数统计来建模单词是不可靠的,会破坏词频分布的特性(?
并且通过分析,单词序列(A,B,……)的频率也满足齐普夫定律的分布,越长的单词序列拥有更小的α。
设计网络的输入数据,需要对语料库进行采样,分为随机采样和顺序分区
随机采样就是随机提取若干个定长的序列(时间步为n)
顺序分区就是从0号位开始,每走n步划分一个子序列
由于我们的任务是预测句子,所以我们网络的输出也是一个长度为n的序列(也就是时间步为n)
我们的数据集就是:X为[L~R]的一个序列,标签y为[L+1~R+1]的一个序列,这样我们也可以在原始语料库中获得标签(为什么标签y不设计成[R+1]呢?)
循环神经网络
无循环:
有循环:,
由于我们输入的是一个序列了,所以Xt是表示序列中的第t位的tensor,输出层O也是变成了多个输出
另外有一个小技巧可以减少矩阵乘法的次数,设Xt为n*d的矩阵,则W1为d*h的矩阵,W2为h*h的矩阵,H为n*h的矩阵,正常来说W1*Xt+W2*Ht-1需要两次矩阵乘法和以此矩阵加法,但如果先将Xt和Ht-1在列方向上拼起来变成n*(d+h)的矩阵,然后把W1、W2在行方向上拼起来变成(d+h)*h的矩阵,这样就只需要一次矩阵乘法就可以得到结果了。
困惑度
如果没算错的话,困惑度应该是整个语句链的概率开N次根的倒数
如果整个语句链的概率很小,那么困惑度就会很大,由于开了N次根,实际上是每个单词出现的条件概率的几何平均数,这样受到序列长度的影响也被削弱了(这个指标设计还是可以的)