线性回归
1. 数据集名词
training set
sample
label
feature
2. 均方差损失函数
均值差的平方损失
(a-b)**2 / 2 在 batch 中求平均, 即 sum / len(batch)
为什么单个要除以 2 ? 其实标准的也可以不 / 2
为什么均方差用的这么多? 好处坏处, 别的?
均方差叫 MSE, mean squared error, RMSE , MAE
3. 随机梯度下降
一个小的启发, 如果 training set 的 batch, 经过梯度回传以后, 再算一遍 loss, 理应比梯度回传前的小, 如果不小, 说明模型拟合 能力不够. 在单个 batch 一定会变小, 不变小说明 lr 取得大了, 整体 loss 均值可能波动或不变, 因为此起彼伏, 到达瓶颈了, 此时应该怎么弄?
这一部分不是太懂.
4. 矢量计算
从 C 语言思维抽离, 或者等价, 只是需要记忆 torch 的向量数据类型和他的操作符, 并且数据角度从单个数升级为向量和矩阵.
下面代码测出可以视为向量本身的操作是忽略不计的, 和 float 近似一样(实际使用中还不知道), 这样快个 1000和10000 倍也是很容易的, 1e6*1e6 也可以不计时间秒出 (0.002), 矩阵操作先不测, 反正就是要准备好矩阵和向量, 然后数学操作, 操作单位不用 for 循环, 而是最小的为 torch.Tensor.
代码片段 A.1
import torch
import time
# init variable a, b as 100000 dimension vector
n = 10000
# use float test real speed
a = torch.randn(n)
b = torch.randn(n)
# a = torch.ones(n)
# b = torch.ones(n)
c = torch.zeros(n)
s = time.time()
for i in range(n):
c[i] = a[i] * b[i]
print(time.time() - s)
s = time.time()
d = a * b
print(time.time() - s)
print(c, d)
# test *, mul, mm
c2 = torch.mul(a, b)
a3 = torch.randn(2, 3)
b3 = torch.randn(3, 1)
c3 = torch.mm(a3, b3)
print(c2, c3)
讲解:
1.torch.ones(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
其中 size: 一个数, 或者 ones(2, 3) 就是多维的了; 相似的有 torch.zeros, torch.ones_like(input). 同时注意到 c 类C语言声明的, 而一般像 d 这样直接通过 a, b 两个向量相乘得到, 体现了单元操作是向量/矩阵
2.torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
其中 size 一样, 可以使 3, 也可以 (4, 5) 就是多维了, 返回值是 N(0,1). 其他的目前先不急, 还有数据类型先不管.
3. 乘号和mul是一致的, 类比 np 拓展的对应位置乘法; 而mm 是 mat-mat-multipy, 严格要求是 23 mm 31 这样的. 剩下的乘法以后再说.
5. 线性数据集随机生成和展示
生成的数据要有噪声加上去, 同时噪声还不能太大. 对 Norm(0, sigma) 的理解, 建立在 sigma 原则上, 大致就是噪声会和 sigma 一个量级, 比如 sigma = 0.01, 那么噪声就是 ± 0.01 类似, 1-sigma 是 60%多, 而且基本符合均匀分布, 除了中间均值比较多一点点, 3-sigma 是 9544, 3-sigma 是9974, 即几乎 ± 0.02 以内就得了. 而本题用的 labels 大约是0~20都有的, 可以加这个噪声.
代码片段 A.2
%matplotlib inline
import torch
# display can display image, and latex math, just add this
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random
print(torch.__version__)
num_inputs = 2
num_examples = 10
true_w = torch.tensor([[2], [-3.4]])
true_b = 4.2
features = torch.randn(num_examples, num_inputs,
dtype=torch.float32)
# labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels = torch.mm(features, true_w) + true_b
noise = torch.tensor(np.random.normal(0, 0.01, size=labels.size()),
dtype=torch.float32)
labels += noise
true_w = torch.squeeze(true_w)
labels = torch.squeeze(labels)
print('features:', features)
print('labels:', labels)
print('noise:', noise)
print('weight and bias:', true_w, true_b)
print(labels.size(), type(labels.size()), labels.shape, type(labels.shape))
def set_figsize(figsize=(3.5, 2.5)):
plt.rcParams['figure.figsize'] = figsize
set_figsize()
plt.scatter(features[:, 0].numpy(), labels.numpy(), 1)
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
讲解:
1.display 不管他, 反正要用.
2.以 Tensor 角度思考, 特征是一个量, 即特征量, 只不过有多个维度, 如上面的两维, 所以他相加是有权重的, 参数 w 也是有两维度, 而 torch.randn(100, 2) 正是理解为有100个数据, 然后多一个最后的特征维, 是2.
3.Tensor 的维度切片调用和 np 一样, 都是 a[:, :, 0]这样的.
4.代码改了点, 强行使用了矩阵运算, 或者说是把构造用的 true_w 也必须和 features 整体操作, 原来是维度像 3 一样维度拆开的, 而不拆开一是可是用我上面的, 1002 mm 21, 全是矩阵, 或者用向量对应位置乘法, 再特征维度求和. 不过不用较真.
5.torch.squeeze(input, dim=None, out=None) → Tensor
所有的 1 维度都去掉并且: The returned tensor shares the storage with the input tensor, so changing the contents of one will change the contents of the other. 所以就是原来的等于原来的去用, 别 y = x, 然后改了 y, 又去用 x, 就说不清了. 多起个名字就好.
6.torch.tensor(data, dtype=None, device=None, requires_grad=False, pin_memory=False) → Tensor
注意这个方法会完全拷贝送进去的 data, 以及想要有 gradient, 需要人为指定, 以及注意 GPU 的指定. 不知道为什么强调那么多 copy, detach, leaf 等, 不懂.
7.np.random.normal(0, 0.01, size=labels.size()). 目前看来 labels 是 Tensor, 而 size() 是 <class ‘torch.Size’>, 可以默认转换为 numpy.shape. 记住就好了.
8.plt.rcParams[‘figure.figsize’], 可以设置大小, 变为 100 * 100 的超大的, 可以看清楚, 但是不知道对应的单位是什么, 不过意义也不大.
6. 读取数据集
涉及到分 batch, 以及随机读取, 还有一次 epoch 读完后干什么
代码片段 A.3
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices) # 样本的读取顺序是随机的
for i in range(0, num_examples, batch_size):
j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # 最后一次可能不足一个batch
print('what?', j)
# yield features[j], labels[j]
# yield features[j, :], labels[j]
yield features.index_select(0, j), labels.index_select(0, j)
batch_size = 10
steps_per_epoch = 0
for X, y in data_iter(batch_size, features, labels):
# print(X, '\n', y)
steps_per_epoch += 1
print('batch num:', steps_per_epoch)
解析:
1.data_iter 接口就这么固定了, 最简化的 feature 结构为 [num, X, X…], labels 为 [num, Y, Y…], 然后就可以避免占用内存过大, 使用 yield 机制来绕过. 缺点是速度慢, 实际还是得开 feeder 多线程. 到时候再说.
2.features.index_select(维度, [2, 4, 5…]) 和 切片直接取区别不大, 不知道为什么. 关键是他是拷贝出来了一份, 跟原有的 Tensor 无关, 而且需要 index 是 LongTensor.
3.torch.LongTensor, 就是整数 Tensor 的意思, 记住 torch.LongTensor 这个转换.
Tensor.long() 也是可以的.
7. 定义model, loss, opt
关键是 pytorch 的 Tensor 是真正的变量, 在 def 之内的, 之后求到最后, 再返回来求梯度时, 会提前被销毁, 只是算了一遍而已 (当然, 是以目前我接触的 Pytorch 而言, 下一节课会怎样特别说明如 def 临时变量也可记录, 则与 Tensorflow 先刻好板子就有些像了, 即不管变量在哪里, 变量只负责刻板子, 得到一个板子即是静态图, 那么动态图又是什么呢?)
代码片段 A.4
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
def test(x):
bb = torch.ones(1) * 2
return x * bb
x = torch.ones(1)
y = test(x)
# print(b)
print(y)
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
def linreg(X, w, b):
return torch.mm(X, w) + b
# def linreg(X):
# global w, b
# return torch.mm(X, w) + b
def squared_loss(y_hat, y):
return (y_hat - y.view(y_hat.size())) ** 2 / 2
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data
1.np.random.normal(0, 0.01, (num_inputs, 1)) 是很常用的初始化方式, 大家都喜欢用 (2, 3, 5) 这样的表示维度, 不过看上去形式不同, 如上面的 size=labels.size(), 以及 torch.ones(2, 3).
2.requires_grad_(requires_grad=True) 自己构造的 Tensor 变量, 一是需要提前声明在全局, 而是需要指定求梯度, 完全和 C 语言是一样的. 比如可以静态开辟要用的每层的神经元 Tensor, 然后 def 中调用, 注意通过参数穿进去, 不然也得用 global, 看看哪个方便吧.
3.linreg(X, w, b) 返回 Y, 作为模块记下来.
4.squared 是平方的意思, 同时也可理解为正方形, 广场; 而 squared root 是平方的跟, 简写 sqrt; 所以 squared, square 这些都是平方的意思, 出现 root 根的时候, 才是平方根, 即开平方, sqrt. 先相减, 再平方, 再相加, 不是标准差级别了, 是原数平方级别, 也就是方差了, 不过因为该加的已经加完了, 所以不用再开回去.
5.Tensor.view传入 shape 或者 size(), 取出数据的按照这样形式来看的, 注意是同一块数据, 更改后会影响的.
6.sgd, Stochastic gradient descent, 最简单的梯度下降, 每次梯度反传 * lr, 其实它的精髓在于每次随机的 batch, 然后求得 loss 是这个 batch 的和, 其实不是和也一样, 然后各个 sample 在 batch 中成分平均, 所以不妨 sum loss, 之后 梯度上 /batch_size, 但是其实导数和原数的大小上并没有直接关系, 因此其实常数 lr 不太好, 不过很小的 lr 多次迭代总是可以. 当然结合 loss 当前的值, 梯度, lr 算出来的新梯度会是更好的, 不过还没看这方面的文章.
7.遍历所有的 params, 注意到目前随手声明的变量都是不可求梯度的, 除非指明, 以后也记一个哪些天生就可以求梯度, 或者反之, 总而言之别把 feature 和 labels 给改了.
8.Tensor 只要是 requires_grad=True 都有, 但是要在 backward 以后, 之前是 None; 不过没弄明白 is_leaf 定义是啥用, 以及如果 requires_grad=False 时情况, 是 None 吗?
9.明确改的 params.data, 看来 python 要改等号左边的东西, 而且还要复用的时候要小心了, 因为除了看上去的数据还有很多别的属性, 是真的一个类. 这是我猜的, 还需要验证. 所以尽量代码一顺下来, 而有 for 循环的地方名字继承下来加 id, 如果真的得一直用, 保证等号右边可以全部给左边, 或者用 data, 反正就是小心, 具体看实战吧.
8. 训练模型以及查看模型
关键是 w, b 变量的传导.
代码片段 A.5
lr = 0.03
num_epochs = 10
net = linreg
loss = squared_loss
for epoch in range(num_epochs): # 训练模型一共需要num_epochs个迭代周期
# 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。X
# 和y分别是小批量样本的特征和标签
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y).sum() # l是有关小批量X和y的损失
l.backward() # 小批量的损失对模型参数求梯度
sgd([w, b], lr, batch_size) # 使用小批量随机梯度下降迭代模型参数
# 不要忘了梯度清零
w.grad.data.zero_()
b.grad.data.zero_()
train_l = loss(net(features, w, b), labels)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
print(true_w, '\n', w)
print(true_b, '\n', b)
解析:
1.net = linreg, loss = squared_loss, python 函数也是变量, 不过我不习惯用…就不用了… 名字而已, 改改.
2.手搓 loss.sum(), 要记住.
3.sgd 中手挑更新梯度的参数, 组成 list, 果然平时 [] 是好看的, 但是与 () 混的传参就偷懒用元组.
4.backward 简而言之就是目前值往前推的梯度, 具体的语言意义还不特别清楚, 但是可以通过查看各个 param 的 gradient 确认. 而且梯度求得默认累计, 需要用w.grad.data.zero_() 清零.
5.原来教程是 100 个点, 3 次 epoch 效果就很好, 当我设置为 200 个点, 发现 3 次 epoch 竟然不如 100 个, 设置 10 才更好. 或许可以通过一些简单的数据构造和结构, 来理解结构设计, 超参选取, 训练技巧.
9. 线性回归手搓版OJ题目
题目 B.1
其实就是告诉你用线性回归模型来拟合数据, 数据已经给你了, 然后要求在 test 集上算出来结果, 并且这个结果的 loss 要比较小, 而且预测的 W, b 啥的跟之前的类似. 其实自己脑补下…并没有做成真的 loss 的判定程序.
import torch
import numpy as np
import random
# first problem: 100 * 3 D-N(0, 1)
# W, b
W = torch.tensor([2, 3, 4])
b = 10
num_n = 100
features = torch.tensor(np.random.normal(0, 1, (num_n, 3)))
labels = (features * W).sum(-1, keepdim=False)
labels = labels + torch.tensor(np.random.normal(0, 0.01, size=labels.size()))
print('features', features[:2])
print('lables', labels[:2])
test_features = torch.tensor(np.random.normal(0, 1.1, (num_n // 3, 3)))
test_labels = (test_features * W).sum(-1, keepdim=False)
test_labels = test_labels + torch.tensor(np.random.normal(0, 0.009, size=test_labels.size()))
print('test_features', test_features[:2])
print('test_lables', test_labels[:2])
# end of data
# start to no use W, b to predict test_labels. must use LinearRegression
解答 B.2
第一次真的手写神经网络, 很开心. 跟 C 语言一样嘛, 就是类型不能强制好麻烦, 以及发现特征增多以后, 目标结果比如是一个值, 那么参数空间太大, 如果数据量不够, 那么会容易陷入局部最优, 或者压根有多个比较合理的解, 只能通过增大数据量. 不过这个存疑.
不不不, 上面的说法是不准确的, 我把 num_n = 100, 跑 40 个epoch, 结果也差不多好了. 所以数据一样的时候, 看 epoch, 数据不同时, 看迭代次数, 还有 loss 是不是还在降. 以及增添数据的边际效应究竟是什么?
# start to no use W, b to predict test_labels. must use LinearRegression
# first think not this dataLoader, but code this here is more easy to review
def dataLoader(features, labels, batch_size):
index = list(range(len(features)))
random.shuffle(index)
for i in range(0, len(features), batch_size):
s = i
t = min(i+batch_size, len(features))
yield features[index[s:t]], labels[index[s:t]]
# start to no use W, b to predict test_labels. must use LinearRegression
W = np.random.normal(0, 0.01, (3, 1))
# print(W.dtype)
W = torch.tensor(W)
# print(W.dtype)
b = torch.zeros(1)
# this to sentence is hard to bei.....
W.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
def Linear(X, W, b):
# X (-1, 3), W (3, 1), b (1) => (-1, 1)
# print(X, type(W), W.dtype)
Y = torch.mm(X, W) + b
# (-1, 1) => (-1)
Y = torch.squeeze(Y, dim=-1)
return Y
def loss(y_hat, y):
# y_hat (-1), y (-1) => (-1) => sum all (1)
# print(y_hat, y)
res = ((y_hat - y)**2 / 2).sum()
return res
def opt_sgd(grad, lr, batch_size):
# grad mat (x, x)
return grad * lr / batch_size
def add_opt(params, lr, batch_size):
for p in params:
p.data -= opt_sgd(p.grad, lr, batch_size)
# start train
tot_epoch = 20
batch_size = 10
lr = 0.01
training_loss_cur = 0
for _ in range(tot_epoch):
training_loss_cur = 0
for bs_features, bs_labels in dataLoader(features, labels, batch_size):
labels_hat = Linear(bs_features, W, b)
l = loss(labels_hat, bs_labels)
l.backward()
# make opt => sgd, then:
add_opt([W, b], lr, batch_size)
# do not forget!
W.grad.data.zero_()
b.grad.data.zero_()
# print('training loss:', l.item()/batch_size)
training_loss_cur += l.item()
test_l = loss(Linear(test_features, W, b),test_labels)
print('training:', training_loss_cur/len(features), 'testing loss:', test_l.item()/len(test_features))
print(W, b)
# use num_n = 200, then W and b will be right.
10. 关于超参数和网络设计的问题
在群里讨论之后, 觉得应该开一个帖子单独来弄. 明天开帖子, 然后链接放到这里.
11.线性回归 nn.Module 实现
import torch
from torch import nn
import numpy as np
torch.manual_seed(1)
print(torch.__version__)
torch.set_default_tensor_type('torch.FloatTensor')
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
import torch.utils.data as Data
batch_size = 10
# 将训练数据的特征和标签组合
dataset = Data.TensorDataset(features, labels)
# 把 dataset 放入 DataLoader
data_iter = Data.DataLoader(
dataset=dataset, # torch TensorDataset format
batch_size=batch_size, # mini batch size
shuffle=True, # 要不要打乱数据 (打乱比较好)
num_workers=2, # 多线程来读数据
)
for X, y in data_iter:
print(X, '\n', y)
break
class LinearNet(nn.Module):
def __init__(self, n_feature):
super(LinearNet, self).__init__()
self.linear = nn.Linear(n_feature, 1)
def forward(self, x):
y = self.linear(x)
return y
net = LinearNet(num_inputs)
print(net) # 使用print可以打印出网络的结构
# 写法一
net = nn.Sequential(
nn.Linear(num_inputs, 1)
# 此处还可以传入其他层
)
# 写法二
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......
# 写法三
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
('linear', nn.Linear(num_inputs, 1))
# ......
]))
print(net)
print(net[0])
print(net.parameters)
print(net.parameters())
for param in net.parameters():
print(param, param.name)
print(net[0].weight)
from torch.nn import init
init.normal_(net[0].weight, mean=0.0, std=0.01)
init.constant_(net[0].bias, val=0.0) # 也可以直接修改bias的data: net[0].bias.data.fill_(0)
for param in net.parameters():
print(param)
loss = nn.MSELoss()
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
num_epochs = 3
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()))
dense = net[0]
print(true_w, dense.weight.data)
print(true_b, dense.bias.data)
解析:
1.torch.manual_seed(1) 让神经网络 torch 部分随机固定下来.
2.torch.set_default_tensor_type(‘torch.FloatTensor’) 是 float32, 之前遇到的 float64 问题应该可以通过这个解决.
3.dataset = Data.TensorDataset(features, labels), 然后 data_iter = Data.DataLoader(), 再 for X, y in data_iter: 常规操作.
4.继承 nn.Module, 之后 def init(self, n_feature): 要包含 super(LinearNet, self).init(), 都是常规操作.
5.self.linear = nn.Linear(n_feature, 1), 然后用的时候 y = self.linear(x); 也就是说先是参数 unit 的在 init 时的传入, 然后再加 () 以后才是 call, 每一层神经网络, 或者每一个神经网络模块, 它是一个类.
6.def forward(self, x): 就理解为 torch 中带有名字特色的 call, 就行了. 用的时候和 call 一样, output = net(X) 直接调用就好了, 不过之前 net 要通过 构造函数以及 init 的参数来构建.
7.nn.Sequential, 一个有序的容器,神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行. 也可以追加 net.add_module.
8.nn.Sequential(OrderedDict([])) 不知道为什么普通的 list 不行, 不过直接在括号里写就行了, 不用额外加 [], 也不知道这个有什么用.
9.for param in net.parameters(): 常规操作, 但是没有名字. 这一点和 tf 不同.
10.初始化 tensor, 用 from torch.nn import init, 然后 init.normal_(net[0].weight, mean=0.0, std=0.01).
11.optimizer = optim.SGD(net.parameters(), lr=0.03) 参数传进去, opt 其实是对参数以及梯度的加工, 和自己写的一样, 然后 optimizer.zero_grad(); l.backward(); optimizer.step().
12.线性回归 nn.Module 版OJ题目
差不多, 时间不够, 略去…sorry
13.错题
1
y_hat的形状是[n, 1],而y的形状是[n],两者相减得到的结果的形状是[n, n], 这个要注意.
以及 view 的用法
疑问:
def squared_loss(y_hat, y):
return (y_hat - y.view(y_hat.size())) ** 2 / 2
最后是 (N, 1), 不用再求和变为 scalar 吗?
Softmax与分类模型
1.知识点
softmax 用于分类.
softmax运算符(softmax operator)解决了以上两个问题。它通过下式将输出值变换成值为正且和为1的概率分布:
y1,y2,y^3=softmax(o1,o2,o3)
交叉熵: 描述两个概率相似度.
分类概念. 忘了当时写的目的了, 猜着是: 计算算出来的数和概率产生关系, 然后通过 one-hot 形式又很好的表达出来, 这样的设计, 不一定最好, 不过很好. 还有没有别的分类方法, 离散的?
略…sorry
2.代码理解
def softmax(X):
X_exp = X.exp()
partition = X_exp.sum(dim=1, keepdim=True)
# print("X size is ", X_exp.size())
# print("partition size is ", partition, partition.size())
return X_exp / partition # 这里应用了广播机制
略. sorry
3.OJ题目
比较简单, 比如分类要求都正确.
略…sorry
5.错题
1
softmax([100, 101, 102])的结果等于以下的哪一项
[-2, -1, 0]
数学上很容易明白, 但是这隐含了 softmax 什么样的机理呢? 相当于均值随便减, 然后距离均值的偏差, 再取能量吗? 不明白.
多层感知机
1.知识点
ReLU函数只能在隐藏层中使用.
由于梯度消失问题,有时要避免使用sigmoid和tanh函数, 不过是因为他们值本身绝对值 <= 1呢, 还是梯度问题?
计算上 ReLU 也最快.
多层感知机就是含有至少一个隐藏层的由全连接层组成的神经网络, 不是从单隐层感知机然后叠加, 而是只重复隐藏层, 因为其它的叠加不经过激活函数没有意义.
略…sorry
2.代码理解
num_inputs, num_outputs, num_hiddens = 784, 10, 256
net = nn.Sequential(
d2l.FlattenLayer(),
nn.Linear(num_inputs, num_hiddens),
nn.ReLU(),
nn.Linear(num_hiddens, num_outputs),
)
for params in net.parameters():
init.normal_(params, mean=0, std=0.01)
略. sorry
3.OJ题目
比较简单, 比如仍然用分类任务, 要求都正确.
不过 MLP 就可以代表很大部分神经网络的性质了, 就可以做各种测试实验了. 拓展连接以后放过来.
略…sorry
5.错题
1
sigmoid和tanh的异同
数学上两者可以简单线性变换得到, 那为什么在这么强大的神经网络中还要分这两个呢?
答案一: sigmoid 仍是 [0, 1], 用于特别的控制
答案二: tanh 梯度比 sigmoid 梯度陡峭, 想要更大的梯度, 用前者
答案三: 都不用, 使用 relu
其实还是不懂, 需要讨论.
文本预处理
小重点记录列出
1.lines = [re.sub(’[^a-z]+’, ’ ', line.strip().lower()) for line in f]
2.collections.Counter(tokens) # 返回一个字典,记录每个词的出现次数
3.多用类思维
class Vocab(object):
def __init__(self, tokens, min_freq=0, use_special_tokens=False):
counter = count_corpus(tokens) # :
self.token_freqs = list(counter.items())
self.idx_to_token = []
if use_special_tokens:
# padding, begin of sentence, end of sentence, unknown
self.pad, self.bos, self.eos, self.unk = (0, 1, 2, 3)
self.idx_to_token += ['', '', '', '']
else:
self.unk = 0
self.idx_to_token += ['']
self.idx_to_token += [token for token, freq in self.token_freqs
if freq >= min_freq and token not in self.idx_to_token]
self.token_to_idx = dict()
for idx, token in enumerate(self.idx_to_token):
self.token_to_idx[token] = idx
def __len__(self):
return len(self.idx_to_token)
def __getitem__(self, tokens):
if not isinstance(tokens, (list, tuple)):
return self.token_to_idx.get(tokens, self.unk)
return [self.__getitem__(token) for token in tokens]
def to_tokens(self, indices):
if not isinstance(indices, (list, tuple)):
return self.idx_to_token[indices]
return [self.idx_to_token[index] for index in indices]
def count_corpus(sentences):
tokens = [tk for st in sentences for tk in st]
return collections.Counter(tokens) # 返回一个字典,记录每个词的出现次数
5.有一些现有的工具可以很好地进行分词,我们在这里简单介绍其中的两个:spaCy和NLTK。
错题
1.
无论use_special_token参数是否为真,都会使用的特殊token是____,作用是用来____。
unk标记,表示未登录词
解析: 其实 padding, eos, begin 这些都能够被替换或重复前面后面或用 mask 代替掉, 只是有他们更合理.
关键在于 unk 标记 一定要有, 比如我语音合成时有没见过的 symbol, 以前的做法是直接忽略, 但实际上这次提了个醒, 应该触发 unk 标记
语言模型
小重点记录
1.一段自然语言文本可以看作是一个离散时间序列,给定一个长度为 T 的词的序列 w1,w2,…,wT ,语言模型的目标就是评估该序列是否合理; 这样可以预测下一个序列是什么, 比如 decoder 自回归. 不过 encoder 中的 RNN 是什么意思呢? 以后看看, 能不能用简化的 n-gram 模型作为 Tacotron 的 encoder?
2.idx_to_char = list(set(corpus_chars)) # 去重,得到索引到字符的映射
char_to_idx = {char: i for i, char in enumerate(idx_to_char)} # 字符到索引的映射
3.类似于我预处理文本的步骤
def load_data_jay_lyrics():
with open('/home/kesci/input/jaychou_lyrics4703/jaychou_lyrics.txt') as f:
corpus_chars = f.read()
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[0:10000]
idx_to_char = list(set(corpus_chars))
char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])
vocab_size = len(char_to_idx)
corpus_indices = [char_to_idx[char] for char in corpus_chars]
return corpus_indices, char_to_idx, idx_to_char, vocab_size
4.时序数据的采样
在训练中我们需要每次随机读取小批量样本和标签。与之前章节的实验数据不同的是,时序数据的一个样本通常包含连续的字符。假设时间步数为5,样本序列为5个字符,即“想”“要”“有”“直”“升”。该样本的标签序列为这些字符分别在训练集中的下一个字符,即“要”“有”“直”“升”“机”,即 X =“想要有直升”, Y =“要有直升机”. 文本这样直接把要预测的是同档次的东西, 没有 condition 的话, 就是平级的东西. 那 Tacotron 其实对上一帧 mel 进行 dropout 处理不应该太严重, 要不违背了 RNN 最基础的预测下一帧能力的框架.
5.训练语言模型, 会定义 num_steps, 或者可以等价于 n-gram 中的 n ? 通过这个来构造 Tacotron 的训练会得到什么? mel 意义下的语言模型? 结合随机采样和相邻采样来理解.
错题
1.
相邻采样这种办法我还没用过, 需要和大家讨论. 感觉丢一些上下文没那么重要吧…
循环神经网络基础
小重点记录
1.基本的 rnn 公式, 包含 hidden 和 output Ht=ϕ(XtWxh+Ht−1Whh+bh).
Ot=HtWhq+bq.
2.scatter_
def one_hot(x, n_class, dtype=torch.float32):
result = torch.zeros(x.shape[0], n_class, dtype=dtype, device=x.device) # shape: (n, n_class)
result.scatter_(1, x.long().view(-1, 1), 1) # result[i, x[i, 0]] = 1
return result
3.rnn其实就一个一组 for 循环, 只不过每次的 output 都收集起来, 以及规定了state的格式, 以及 (state, ), 元组的使用比较灵活风骚罢了.
def rnn(inputs, state, params):
# inputs和outputs皆为num_steps个形状为(batch_size, vocab_size)的矩阵
W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
for X in inputs:
H = torch.tanh(torch.matmul(X, W_xh) + torch.matmul(H, W_hh) + b_h)
Y = torch.matmul(H, W_hq) + b_q
outputs.append(Y)
return outputs, (H,)
def init_rnn_state(batch_size, num_hiddens, device):
return (torch.zeros((batch_size, num_hiddens), device=device), )
state = init_rnn_state(X.shape[0], num_hiddens, device)
inputs = to_onehot(X.to(device), vocab_size)
params = get_params()
outputs, state_new = rnn(inputs, state, params)
4.裁剪梯度, 疑问是是每个单独范数还是所有参数范数平均值, 以及 rnn 的时间上的消逝可以这样解决掉吗? 只有爆炸可以避免吧?
def grad_clipping(params, theta, device):
norm = torch.tensor([0.0], device=device)
for param in params:
norm += (param.grad.data ** 2).sum()
norm = norm.sqrt().item()
if norm > theta:
for param in params:
param.grad.data *= (theta / norm)
5.RNN的 torch 版 class
class RNNModel(nn.Module):
def __init__(self, rnn_layer, vocab_size):
super(RNNModel, self).__init__()
self.rnn = rnn_layer
self.hidden_size = rnn_layer.hidden_size * (2 if rnn_layer.bidirectional else 1)
self.vocab_size = vocab_size
self.dense = nn.Linear(self.hidden_size, vocab_size)
def forward(self, inputs, state):
# inputs.shape: (batch_size, num_steps)
X = to_onehot(inputs, vocab_size)
X = torch.stack(X) # X.shape: (num_steps, batch_size, vocab_size)
hiddens, state = self.rnn(X, state)
hiddens = hiddens.view(-1, hiddens.shape[-1]) # hiddens.shape: (num_steps * batch_size, hidden_size)
output = self.dense(hiddens)
return output, state
char_to_idx):
state = None
output = [char_to_idx[prefix[0]]] # output记录prefix加上预测的num_chars个字符
for t in range(num_chars + len(prefix) - 1):
X = torch.tensor([output[-1]], device=device).view(1, 1)
(Y, state) = model(X, state) # 前向计算不需要传入模型参数
if t < len(prefix) - 1:
output.append(char_to_idx[prefix[t + 1]])
else:
output.append(Y.argmax(dim=1).item())
return ''.join([idx_to_char[i] for i in output])
错题
1
采用相邻采样仅在每个训练周期开始的时候初始化隐藏状态是因为相邻的两个批量在原始数据上是连续的
这样的训练办法还没用过