神经网络模型
- CNN(Convolutional Neural Network)
- convolution层:
- 二维情况
- 多维情况
- Max pooling层:
- full connected全连接层
- RNN(Rerrent Neural Network)
- LSTM
- encode
- 数据训练过程
- decode
- Bi-directional LSTM
- GRU(Gate Recurrent Unit)
- 本次实验数据:
- 储备知识📚
- datetime.fromtimestamp⌚️
- 切片[ start:stop:step ]:
- nn.Linear
- nn.Embedding
- nn.conv2d🧻
- torch.cat()🐱
CNN(Convolutional Neural Network)
一般用在图片处理领域
过程:convolution—>max pooling—>…—>convolution—>max pooling—>full connected
convolution层:
卷积层主要作用是提取特征,用卷积核进行卷积
缺点:
(1)边缘的像素“参与感”低
(2)image越算越小
解决办法:(padding)进行填充
(二维情况举例)
(1)vaid convolution: 没有进行填充
image(n * n)✖️filter(f * f) 🟰 matrix (n-f+1)*(n-f+1)
(2)same convolution:使得input与output维度相同
当p=(f-1)/2时,input等于output维度
image(n * n)✖️filter(f * f) 🟰matrix [(n-f+2p)+1] * [(n-f+2p)/s+1]
二维情况
(黑白图片就是二维的)
带步长的卷积:strided convolution
image(n * n)✖️filter(f * f)【卷积核】 🟰matrix【通道数】 [floor「(n-f+2p)/s+1」* floor 「(n-f+2p)/s+1」]【注⚠️】(1)p为填充长度(为了保证对称一般用2p表示填充大小);(2)s是步长(3)filter的长度常取3、5、7,总是会取基数,使得除法可以整除
# encoder
self.enc_embeddings = nn.Embedding(enc_v_dim, emb_dim) #可以生成权重矩阵,是一个自学习的矩阵 [批次大小=size, 词数量=en_v_dim, 每个词维度=emb]
self.enc_embeddings.weight.data.normal_(0, 0.1)
#输入通道数为1,输出通道数为16也可以理解为filter的数量,
# n 的取值为 2、3、4,表示卷积核的高度;emb_dim 则用于表示卷积核的宽度。
# 不进行填充操作,n表示卷积核的宽度,emb_dim表示嵌入层的维度
self.conv2ds = [nn.Conv2d(1, 16, (n, emb_dim), padding=0) for n in range(2, 5)] #[32,1,8,16]----->[32,16,]
#最大池化窗口的大小分别为(7,1)(6,1)(5,1)
self.max_pools = [nn.MaxPool2d((n, 1)) for n in [7, 6, 5]]
#16 * 3 表示输入特征的大小
# units 表示输出特征的大小,即线性层的神经元数量。
self.encoder = nn.Linear(16 * 3, units)
def encode(self, x):
#enc_embeddings 可以生成权重矩阵,是一个自学习的矩阵
embedded = self.enc_embeddings(x) # 输入(32, 8)【句子维度】形状为 [32, 8, 16]【词的维度】
o = torch.unsqueeze(embedded, 1) # [32, 1, 8, 16]
#卷积层对象应用激活函数(卷积层的操作)
co = [relu(conv2d(o)) for conv2d in self.conv2ds]
co = [self.max_pools[i](co[i]) for i in range(len(co))] # 进行最大池化[32, 16, 1, 1]
#两次调用 squeeze 函数,分别在第 3 维和第 2 维进行维度压缩,得到一组形状为 [32, 16] 的特征向量
co = [torch.squeeze(torch.squeeze(c, dim=3), dim=2) for c in co] # [32, 16] * 3
o = torch.cat(co, dim=1) # 将co 沿着第 1 维进行拼接,得到一个形状为 [32, 16*3] 的特征向量
h = self.encoder(o)
# [32, 32]
return [h, h]
多维情况
彩色图片属于多维,由(R、G、B)三维构成
三维:height * width * channel
【注⚠️】:filter和image的channel层一样
image(n * n * n‘)✖️filter(f * f* n’) 🟰matrix (n-f+1)*(n-f+1) * num(filter)
Max pooling层:
池化窗口的目的是提取卷积特征中的最显著信息
将矩阵分成多个,每个里面取最大的;其中超参数是filter size=f和stride=s是自己设置的,这层没有要学习的参数
得到的结果矩阵为 matrix [floor「(n-f)/s+1」* floor 「(n-f)/s+1」]
full connected全连接层
【注⚠️】:
(1)pooling层没有参数
(2)convolution层的参数和输入输出都会越来越少
将卷积用在自然处理领域中,常将卷积神经网络与编码器-解码器结构相结合,例如使用卷积神经网络来作为encode,将输入序列映射到一个隐藏表示,然后使用RNN/LSTM/GRU 作为解码器部分来生成目标序列。这种结合称为卷积编码器-循环解码器(Convolutional Encoder-Decoder)模型,旨在结合CNN和RNN的优势;
RNN(Rerrent Neural Network)
RNN:循环神经网络
【注⚠️】ot得到的是一个分布情况,再用beam search等方法得到输出;只要有输出就会有loss损失函数;损失函数是各输出的损失加和;
class RNN:
def step(self, x):
# update the hidden state
self.h = np.tanh(np.dot(self.W_hh, self.h) + np.dot(self.W_xh, x))
# compute the output vector
y = np.dot(self.W_hy, self.h)
return y
优点:(1)可以处理不同长度的数据(2)考虑了语序局限性:(1)梯度爆炸exploding gradient(2)梯度消尽vanishing gradient
【注】J为损失函数
(1)解决梯度爆炸exploding gradient
(2)梯度消尽vanishing gradient:LSTM模型
RNN的应用场景:
LSTM
LSTM与RNN在“外形”上没有什么差别,只是hidden层内部不一样,LSTM多了三个Gate
- Input Gate:输入门,在每一时刻从输入层输入的信息会首先经过输入门,输入门的开关会决定这一时刻是否会有信息输入到Memory Cell
- Output Gate:输出门,每一时刻是否有信息从Memory Cell输出取决于这一道门
- Forget Gate:遗忘门,每一时刻Memory Cell里的值是否被遗忘,由该门控制的
class SigmoidActivator(object):
def forward(self, weighted_input):
return 1.0 / (1.0 + np.exp(-weighted_input))
def backward(self, output):
return output * (1 - output)
class TanhActivator(object):
def forward(self, weighted_input):
return 2.0 / (1.0 + np.exp(-2 * weighted_input)) - 1.0
def backward(self, output):
return 1 - output * output
class LSTM(object):
def forward(self, x):
'''
根据式1-式6进行前向计算
'''
self.times += 1
# 遗忘门
fg = self.calc_gate(x, self.Wfx, self.Wfh,
, self.gate_activator)
self.f_list.append(fg)
# 输入门
ig = self.calc_gate(x, self.Wix, self.Wih,
, self.gate_activator)
self.i_list.append(ig)
# 输出门
og = self.calc_gate(x, self.Wox, self.Woh,
, self.gate_activator)
self.o_list.append(og)
# 即时状态
ct = self.calc_gate(x, self.Wcx, self.Wch,
self.bc, self.output_activator)
self.ct_list.append(ct)
# 单元状态
c = fg * self.c_list[self.times - 1] + ig * ct
self.c_list.append(c)
# 输出
h = og * self.output_activator.forward(c)
self.h_list.append(h)
def calc_gate(self, x, Wx, Wh, b, activator):
'''
计算门
'''
h = self.h_list[self.times - 1] # 上次的LSTM输出
net = np.dot(Wh, h) + np.dot(Wx, x) + b
gate = activator.forward(net)
return gate
说白了,就是两个输入h,x;可以得到c和h的输出;
hidden的参数:(num_layers * num_directions , batch_size , hidden_size)
class Seq2Seq(nn.Module):#enc_v_dim =27, dec_v_dim=27, emb_dim=16, units=32, max_pred_len=11
def __init__(self, enc_v_dim, dec_v_dim, emb_dim, units, max_pred_len, start_token, end_token):
super().__init__()
self.units = units
self.dec_v_dim = dec_v_dim
# encoder
self.enc_embeddings = nn.Embedding(enc_v_dim, emb_dim)
self.enc_embeddings.weight.data.normal_(0, 0.1)
# emb_dim是LSTM的输入的维度;units是LSTM的隐藏状态的个数,即LSTM输出的维度;1表示只使用一个LSTM层;
# batch_first=True表示输入数据的维度顺序为(batch_size, seq_len, input_size)
self.encoder = nn.LSTM(emb_dim, units, 1, batch_first=True)
# decoder
self.dec_embeddings = nn.Embedding(dec_v_dim, emb_dim)
self.dec_embeddings.weight.data.normal_(0, 0.1)
self.decoder_cell = nn.LSTMCell(emb_dim, units) #用于定义解码器的一个循环单元
self.decoder_dense = nn.Linear(units, dec_v_dim)
# 建了一个Adam优化器,用于训练模型的参数。学习率设置为0.001
self.opt = torch.optim.Adam(self.parameters(), lr=0.001)
self.max_pred_len = max_pred_len
self.start_token = start_token
self.end_token = end_token
encode
将输入序列进行嵌入操作,通过一个 LSTM 编码器对嵌入后的序列进行编码,得到编码器的输出序列和最后一个时间步长的隐藏状态和细胞状态作为编码结果
# encoder
self.enc_embeddings = nn.Embedding(enc_v_dim, emb_dim) #enc_v_dim=27,emb_dim = 16
self.enc_embeddings.weight.data.normal_(0, 0.1)
# emb_dim是LSTM的输入的维度;units是LSTM的隐藏状态的个数,即LSTM输出的维度;1表示只使用一个LSTM层;
# batch_first=True表示输入数据的维度顺序为(batch_size, seq_len, input_size)
self.encoder = nn.LSTM(emb_dim, units, 1, batch_first=True)#emb_dim = 16,units=32
def encode(self, x): # [32,8]
embedded = self.enc_embeddings(x) # [n, step, emb]= [32,8,16]
#传入两个形状为 (1, x.shape[0], self.units) 的全零张量。
hidden = (torch.zeros(1, x.shape[0], self.units), torch.zeros(1, x.shape[0], self.units))
o, (h, c) = self.encoder(embedded, hidden) #获取输出和最后一个隐藏状态
return h, c
数据训练过程
train_logit得到每层输出结果,step根据预测结果y‘和目标序列y计算损失,并进行反向传播来更新模型的参数。
#将输入序列经过编码器编码后,再通过解码器进行逐步解码,可以得到对应时间步的预测输出
def train_logit(self, x, y): # x=[32,8] y=[32,11]
hx, cx = self.encode(x) #返回最后一个隐藏层的状态
# hx[0] 和 cx[0] 是从隐藏状态张量中提取出第一个元素,
# 即将形状从 [1, 32, 32] 变为 [32, 32],只需要保留最后一个时间步的隐藏状态作为编码器的输出
hx, cx = hx[0], cx[0] #[32,32][32,32]
dec_in = y[:, :-1] #删去最后的结束标签 [32,10]
dec_emb_in = self.dec_embeddings(dec_in) #[32,10,16]
dec_emb_in = dec_emb_in.permute(1, 0, 2) #[10,32,16]
output = []
for i in range(dec_emb_in.shape[0]):
# 使用当前时间步的嵌入输入 dec_emb_in[i] 和上一个时间步的隐藏状态 (hx, cx) 来更新解码器单元的隐藏状态
hx, cx = self.decoder_cell(dec_emb_in[i], (hx, cx))
o = self.decoder_dense(hx) #[32,27]
output.append(o)
output = torch.stack(output, dim=0) #[10,32,27]
return output.permute(1, 0, 2) #[32,10,27]
def step(self, x, y):
self.opt.zero_grad()
# batch_size = x.shape[0]
logit = self.train_logit(x, y) # [32,10,27]
dec_out = y[:, 1:] #将y的开始标记去掉[32,10],y是目标输出结果
loss = cross_entropy(logit.reshape(-1, self.dec_v_dim), dec_out.reshape(-1))
loss.backward()
self.opt.step()
return loss.detach().numpy()
decode
得到预测的输出
# decoder
self.dec_embeddings = nn.Embedding(dec_v_dim, emb_dim)
self.dec_embeddings.weight.data.normal_(0, 0.1)
self.decoder_cell = nn.LSTMCell(emb_dim, units) #用于定义解码器的一个循环单元
self.decoder_dense = nn.Linear(units, dec_v_dim) #32----27
def inference(self, x): #[1,8]
self.eval()
hx, cx = self.encode(x) #从encoder得到隐藏状态(hx, cx)[1,1,32]
hx, cx = hx[0], cx[0] #[1,32]
#初始化得到start
start = torch.ones(x.shape[0], 1) #创建一个形状为(x.shape[0], 1)的张量start[1,1]
start[:, 0] = torch.tensor(self.start_token) #start所有元素设置为解码器的起始标记
start = start.type(torch.LongTensor)
dec_emb_in = self.dec_embeddings(start) #[1,1,16]
dec_emb_in = dec_emb_in.permute(1, 0, 2) #[1,1,16],permute函数对维度进行调整,保证维度顺序是正确的
dec_in = dec_emb_in[0] #[1,16]
output = [] #存放每一步的输出结果
for i in range(self.max_pred_len):
hx, cx = self.decoder_cell(dec_in, (hx, cx))#得到新的状态值,作为下一次的输入
o = self.decoder_dense(hx) #[1,27]
o = o.argmax(dim=1).view(-1, 1) # [1,1],选取概率最大的值作为输出结果:argmax(dim=1) 是一个张量的方法,它会返回指定维度上最大值的索引
dec_in = self.dec_embeddings(o).permute(1, 0, 2)[0] #[1,1,16]----[1,1,16]----[1,16]
output.append(o)
output = torch.stack(output, dim=0) #[11,1,1]
self.train()
return output.permute(1, 0, 2).view(-1, self.max_pred_len) #[1,11,1]----[1,11]
Bi-directional LSTM
GRU(Gate Recurrent Unit)
GRU是循环神经网络(Recurrent Neural Network, RNN)的一种。和LSTM一样,也是为了解决长期记忆和反向传播中的梯度等问题而提出来的。
与LSTM相比,GRU内部少了一个”门“,参数比LSTM少,却能达到与LSTM相当的功能,我们常常觉得GRU更加”实用“!
【注⚠️】:
更新门🚪:t时刻要接受多少新信息放到ht中
记忆门🚪:要保留/忘记多少信息(类似于LSTM中的遗忘门🚪)
本次实验数据:
- date_cn = [] :存储中文日期数据 ,一个数据站一个元素的位置 # 89-06-30
- date_en = [] :存储英文日期数据 (正确数据) #30/Jun/1989
- vocab:存放索引[大小为27]
#[‘< PAD>’, ‘-’, ‘/’, ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘< EOS>’, ‘< GO>’, ‘Apr’, ‘Aug’, ‘Dec’, ‘Feb’, ‘Jan’, ‘Jul’, ‘Jun’, ‘Mar’, ‘May’, ‘Nov’, ‘Oct’, ‘Sep’]- x []:存放中文日期📅的向量表达
- y[]:存放英文日期📅的向量表达 #[14(的向量)…13(的向量)]
【⚠️】英文日期的向量表达总是以14开始,13结束
class DateData(tDataset): #tDataset类的主要作用是生成日期数据集
def __init__(self, n):
np.random.seed(1) #固定随机数生成器的状态,使得每次生成的随机数序列都相同
self.date_cn = [] #存储中文日期数据
self.date_en = [] #存储英文日期数据
for timestamp in np.random.randint(143835585, 2043835585, n):
date = datetime.datetime.fromtimestamp(timestamp) #将给定的时间戳转换为对应的日期时间对象的格式
self.date_cn.append(date.strftime("%y-%m-%d")) #形成一个格式化的日期时间字符串
self.date_en.append(date.strftime("%d/%b/%Y")) #格式为 dd/MMM/YYYY
self.vocab = set(
[str(i) for i in range(0, 10)] + ["-", "/", "<GO>", "<EOS>"] + [i.split("/")[1] for i in self.date_en]
) #构建词汇表(vocab):包括数字0-9、连字符"-"、斜杠"/"、起始标记"<GO>"、结束标记"<EOS>"以及英文日期序列中的月份(27)
self.v2i = {v: i for i, v in enumerate(sorted(list(self.vocab)), start=1)} #将词汇表按字母顺序排序,并转换为字典形式
self.v2i["<PAD>"] = PAD_ID #"<PAD>"的索引值设为预定义的PAD_ID。
self.vocab.add("<PAD>")
self.i2v = {i: v for v, i in self.v2i.items()} #键为词汇,值为索引(与v2i相反)
self.x, self.y = [], [] #存储索引序列,用于存储编码器输入序列和解码器输出序列
for cn, en in zip(self.date_cn, self.date_en):#中文和英文对应数据进行打包,
self.x.append([self.v2i[v] for v in cn]) #将得到的中文格式数据转换成向量表达,放到x[]中
self.y.append([self.v2i["<GO>"], ] + [self.v2i[v] for v in en[:3]] + [
self.v2i[en[3:6]]] + [self.v2i[v] for v in en[6:]] + [self.v2i["<EOS>"], ]) ##将得到的英文格式数据转换成向量表达,放到y[]中
self.x, self.y = np.array(self.x), np.array(self.y)
self.start_token = self.v2i["<GO>"]
self.end_token = self.v2i["<EOS>"]
输出看一下
def train():
dataset = utils.DateData(4000)
print("Chinese time order: yy/mm/dd ", dataset.date_cn[:3], "\nEnglish time order: dd/M/yyyy", dataset.date_en[:3])
print("Vocabularies: ", dataset.vocab)
print(f"x index sample: \n{dataset.idx2str(dataset.x[0])}\n{dataset.x[0]}",
f"\ny index sample: \n{dataset.idx2str(dataset.y[0])}\n{dataset.y[0]}")
loader = DataLoader(dataset, batch_size=32, shuffle=True)
'''
Chinese time order: yy/mm/dd ['31-04-26', '04-07-18', '33-06-06']
English time order: dd/M/yyyy ['26/Apr/2031', '18/Jul/2004', '06/Jun/2033']
Vocabularies: {'Feb', 'Mar', '8', '4', 'Jul', '1', '<EOS>', 'Jan', 'Aug', 'Apr', 'Jun', '6', '3', '/', '-', '<PAD>', '7', '0', 'May', '9', 'Sep', 'Nov', '<GO>', '5', 'Oct', 'Dec', '2'}
x index sample:
31-04-26
[6 4 1 3 7 1 5 9]
y index sample:
<GO>26/Apr/2031<EOS>
[14 5 9 2 15 2 5 3 6 4 13]
'''
储备知识📚
datetime.fromtimestamp⌚️
它用于将一个时间戳转换为对应的日期时间对象。接受一个时间戳【从某个参考时间(通常是一个固定的起始时间)到目标时间经过的时间长度】作为参数,并返回一个表示该时间戳所对应的日期和时间的 datetime 对象
切片[ start:stop:step ]:
start 表示开始位置,stop 表示结束位置(不包括该位置的元素),step 表示步长(默认为 1)
a[0, 1:-1] 将返回该数组中第 0 行的从第 1 列到倒数第 2 列(不包括最后一列)的子序列。
nn.Linear
nn.Linear需要两个参数:输入特征的数量(input features)和输出特征的数量(output features)。它会自动创建一个与这两个参数匹配的权重矩阵和偏置向量。
output = input * weight^T + bias
【注⚠️】input是输入数据,weight是权重矩阵,bias是偏置向量,^T表示权重矩阵的转置
nn.Embedding
nn.Embedding()产生一个权重矩阵weight
输入:
两个参数:(batch_size, sequence_length)
其中batch_size是批次大小,sequence_length是序列长度
返回值:形状为(batch_size, sequence_length, embedding_dim)
其中embedding_dim是嵌入维度,表示每个索引对应的嵌入向量的长度
nn.conv2d🧻
nn.Conv2d是PyTorch中的一个二维卷积层类。
构造方法的参数说明如下:
·in_channels:输入数据的通道数。
·out_channels:输出特征图的通道数,也就是卷积核的数量
·kernel_size:卷积核filter的大小。可以是一个整数(表示正方形卷积核的边长),或者是一个元组或列表(表示矩形卷积核的高和宽)。·stride:卷积操作的步长。可以是一个整数(表示横向和纵向步长相同),或者是一个元组或列表(表示横向和纵向步长分别不同)。
·padding:输入数据的填充大小。可以是一个整数(表示在每个边上都进行相同大小的填充),或者是一个元组或列表(表示在横向和纵向分别进行不同的填充大小)。
·dilation:卷积核中各个元素之间的间距。
·groups:输入和输出之间连接的通道的数目。默认为1,表示使用所有通道进行连接。
·bias:是否使用偏置项,默认为True。
·forward(),可以通过调用该函数实现输入数据在卷积层上的前向计算。
torch.cat()🐱
当 tensors 的形状在拼接维度(dim)之外的维度完全一致时,torch.cat() 将按照指定的维度将这些张量拼接在一起。