一、反向传播


二、优化器
1. Adam优化器

1.1.Adam优化器相关参数
:学习率,一般取0.001
:一阶矩估计指数衰减率,一般取0.9
:二阶矩估计指数衰减率,一般取0.999
:微小常数(如
)防止除以零
:带有参数
的随机目标函数,是模型训练中需要最小化(或最大化)的函数
:初始参数向量,模型参数的初始值
1.2.Adam优化器算法步骤
1.初始化
:初始化一阶矩向量为0。
:初始化二阶矩向量为0。
:初始化时间步为0。
2.迭代过程
2.1.更新一阶矩和二阶矩估计

2.2.偏差修正 (Bias Correction)

2.3.参数更新

含义 :Adam的更新规则可以直观理解为:用带有动量的梯度作为更新方向,同时用类似RMSProp的
来对每个参数的学习率进行自适应缩放。偏差修正确保了在训练初期,矩估计也是无偏的。
1.3. Adam优化器优势及适用场景
-
实现简单,计算高效,对内存需求少
-
超参数具有很好的解释性,且通常无需调整或仅需很少的微调
-
更新的步长能够被限制在大致的范围内(初始学习率)
-
能够表现出自动调整学习率
-
很适合应用于大规模的数据及参数的场景
-
适用于不稳定目标函数
-
适用于梯度稀疏或梯度存在很大噪声的问题
2. AdamW优化器
在标准的L2正则化中,损失函数会增加一项,其梯度为
。在Adam中,这个正则项梯度会进入
和
的计算,使得权重衰减的效果与梯度的量级耦合在一起,导致对大学习率和较大权重的参数,实际的权重衰减效果会减弱。
AdamW 将权重衰减从梯度计算中解耦出来,直接在参数更新步骤中应用:
1.执行标准的Adam更新 (不含正则项的梯度):

2.独立进行权重衰减:

含义:权重衰减不再影响梯度的移动平均估计。它变成了一个独立的、对所有参数(通常除了Normalization层和Bias)施加的、与其梯度大小无关的"缩放"步骤,更符合其作为正则化手段的初衷。
三、NLP任务
任务:字符串分类:判断字符串中是否出现了指定字符
指定字符:a 正样本:abcd 负样本:bcdb
Step1:字符数值化:每个字符转化为同维度向量

"abcd" -> 4*5矩阵 矩阵形状 = 文本长度 * 向量长度
通过embedding层实现
1.embedding层
embedding矩阵是可训练的参数,一般会在模型构建时随机初始化,也可以使用预训练的词向量来做初始化,此时也可以选择不训练embedding层中的参数
输入的整数序列可以有重复,但取值不能超过Embedding矩阵的列数
核心价值:将离散值转化为向量,在nlp任务和各类特征工程中应用广泛
搭配词表文件
对于中文通常使用字
对于英文使用token
多个语种和符号可以出现在同一份词表中
目的:"abc" --词表--> 0,1,2 --Embedding层--> 3*n的矩阵 --> model
python
import torch
import torch.nn as nn
'''
embedding层的处理
'''
num_embeddings = 6 #通常对于nlp任务,此参数为字符集字符总数
embedding_dim = 5 #每个字符向量化后的向量维度
embedding_layer = nn.Embedding(num_embeddings, embedding_dim)
print("随机初始化权重")
print(embedding_layer.weight)
print("################")
#构造字符表
vocab = {
"a" : 0,
"b" : 1,
"c" : 2,
"d" : 3,
"e" : 4,
"f" : 5,
}
def str_to_sequence(string, vocab):
return [vocab[s] for s in string]
string1 = "abcde"
string2 = "ddccb"
string3 = "fedab"
sequence1 = str_to_sequence(string1, vocab)
sequence2 = str_to_sequence(string2, vocab)
sequence3 = str_to_sequence(string3, vocab)
print(sequence1)
print(sequence2)
print(sequence3)
x = torch.LongTensor([sequence1, sequence2, sequence3])
embedding_out = embedding_layer(x)
print(embedding_out)
Step2:矩阵转化为向量
池化(求平均)

2.池化层
- 降低了后续网络层的输入维度,缩减模型大小,提高计算速度
- 提高了特征的鲁棒性,防止过拟合


Step3:向量到数值
采取最简单的线性公式
Step4:数值归一化
sigmoid函数进行归一化处理,映射到01之间
注意:字母向量和线性层的w和b需要通过训练优化
3.NLP任务Demo
python
import torch
import torch.nn as nn
import numpy as np
import random
import json
import matplotlib.pyplot as plt
"""
基于pytorch的网络编写
实现一个网络完成一个简单nlp任务
判断文本中是否有某些特定字符出现
"""
class TorchModel(nn.Module):
def __init__(self, vector_dim, sentence_length, vocab):
super(TorchModel, self).__init__()
self.embedding = nn.Embedding(len(vocab), vector_dim) #embedding层
self.pool = nn.AvgPool1d(sentence_length) #池化层
self.classify = nn.Linear(vector_dim, 1) #线性层
self.activation = torch.sigmoid #sigmoid归一化函数
self.loss = nn.functional.mse_loss #loss函数采用均方差损失
#当输入真实标签,返回loss值;无真实标签,返回预测值
def forward(self, x, y=None):
x = self.embedding(x) #(batch_size, sen_len) -> (batch_size, sen_len, vector_dim)
x = x.transpose(1, 2) #(batch_size, sen_len, vector_dim) -> (batch_size, vector_dim, sen_len)
x = self.pool(x) #(batch_size, vector_dim, sen_len)->(batch_size, vector_dim, 1)
x = x.squeeze() #(batch_size, vector_dim, 1) -> (batch_size, vector_dim)
x = self.classify(x) #(batch_size, vector_dim) -> (batch_size, 1) 3*5 5*1 -> 3*1
y_pred = self.activation(x) #(batch_size, 1) -> (batch_size, 1)
if y is not None:
return self.loss(y_pred, y) #预测值和真实值计算损失
else:
return y_pred #输出预测结果
#字符集随便挑了一些字,实际上还可以扩充
#为每个字生成一个标号
#{"a":1, "b":2, "c":3...}
#abc -> [1,2,3]
def build_vocab():
chars = "abcdefghijklmnopqrstuvwxyz" #字符集
vocab = {"pad":0}
for index, char in enumerate(chars):
vocab[char] = index+1 #每个字对应一个序号
vocab['unk'] = len(vocab) #26
return vocab
#随机生成一个样本
#从所有字中选取sentence_length个字
#反之为负样本
def build_sample(vocab, sentence_length):
#随机从字表选取sentence_length个字,可能重复
x = [random.choice(list(vocab.keys())) for _ in range(sentence_length)]
#指定哪些字出现时为正样本
if set("abc") & set(x):
y = 1
#指定字都未出现,则为负样本
else:
y = 0
x = [vocab.get(word, vocab['unk']) for word in x] #将字转换成序号,为了做embedding
return x, y
#建立数据集
#输入需要的样本数量。需要多少生成多少
def build_dataset(sample_length, vocab, sentence_length):
dataset_x = []
dataset_y = []
for i in range(sample_length):
x, y = build_sample(vocab, sentence_length)
dataset_x.append(x)
dataset_y.append([y])
return torch.LongTensor(dataset_x), torch.FloatTensor(dataset_y)
#建立模型
def build_model(vocab, char_dim, sentence_length):
model = TorchModel(char_dim, sentence_length, vocab)
return model
#测试代码
#用来测试每轮模型的准确率
def evaluate(model, vocab, sample_length):
model.eval()
x, y = build_dataset(200, vocab, sample_length) #建立200个用于测试的样本
print("本次预测集中共有%d个正样本,%d个负样本"%(sum(y), 200 - sum(y)))
correct, wrong = 0, 0
with torch.no_grad():
y_pred = model(x) #模型预测
for y_p, y_t in zip(y_pred, y): #与真实标签进行对比
if float(y_p) < 0.5 and int(y_t) == 0:
correct += 1 #负样本判断正确
elif float(y_p) >= 0.5 and int(y_t) == 1:
correct += 1 #正样本判断正确
else:
wrong += 1
print("正确预测个数:%d, 正确率:%f"%(correct, correct/(correct+wrong)))
return correct/(correct+wrong)
def main():
#配置参数
epoch_num = 20 #训练轮数
batch_size = 20 #每次训练样本个数
train_sample = 500 #每轮训练总共训练的样本总数
char_dim = 20 #每个字的维度
sentence_length = 6 #样本文本长度
learning_rate = 0.005 #学习率
# 建立字表
vocab = build_vocab()
# 建立模型
model = build_model(vocab, char_dim, sentence_length)
# 选择优化器
optim = torch.optim.Adam(model.parameters(), lr=learning_rate)
log = []
# 训练过程
for epoch in range(epoch_num):
model.train()
watch_loss = []
for batch in range(int(train_sample / batch_size)):
x, y = build_dataset(batch_size, vocab, sentence_length) #构造一组训练样本
optim.zero_grad() #梯度归零
loss = model(x, y) #计算loss
loss.backward() #计算梯度
optim.step() #更新权重
watch_loss.append(loss.item())
print("=========\n第%d轮平均loss:%f" % (epoch + 1, np.mean(watch_loss)))
acc = evaluate(model, vocab, sentence_length) #测试本轮模型结果
log.append([acc, np.mean(watch_loss)])
#画图
plt.plot(range(len(log)), [l[0] for l in log], label="acc") #画acc曲线
plt.plot(range(len(log)), [l[1] for l in log], label="loss") #画loss曲线
plt.legend()
plt.show()
#保存模型
torch.save(model.state_dict(), "model.pth")
# 保存词表
writer = open("vocab.json", "w", encoding="utf8")
writer.write(json.dumps(vocab, ensure_ascii=False, indent=2))
writer.close()
return
#使用训练好的模型做预测
def predict(model_path, vocab_path, input_strings):
char_dim = 20 # 每个字的维度
sentence_length = 6 # 样本文本长度
vocab = json.load(open(vocab_path, "r", encoding="utf8")) #加载字符表
model = build_model(vocab, char_dim, sentence_length) #建立模型
model.load_state_dict(torch.load(model_path)) #加载训练好的权重
x = []
for input_string in input_strings:
x.append([vocab[char] for char in input_string]) #将输入序列化
model.eval() #测试模式
with torch.no_grad(): #不计算梯度
result = model.forward(torch.LongTensor(x)) #模型预测
for i, input_string in enumerate(input_strings):
print("输入:%s, 预测类别:%d, 概率值:%f" % (input_string, round(float(result[i])), result[i])) #打印结果
if __name__ == "__main__":
main()
test_strings = ["fnvfee", "wzsdfg", "rqwdeg", "nakwww"]
predict("model.pth", "vocab.json", test_strings)
四、网络结构
1.全连接层
2.RNN(Recurrent Neural Network,循环神经网络)
主要思想:将整个序列划分成多个时间步,将每一个时间步的信息依次输入模型,同时将模型输出的结果传给下一个时间步

公式:

python
class TorchRNN(nn.Module):
def __init__(self, input_size, hidden_size):
super(TorchRNN, self).__init__()
self.layer = nn.RNN(input_size, hidden_size, bias=False, batch_first=True)
def forward(self, x):
return self.layer(x)
3.CNN(Convolutional Neural Network,卷积神经网络)
以卷积操作为基础的网络结构,每个卷积核可以看成一个特征提取器

python
class TorchCNN(nn.Module):
def __init__(self, in_channel, out_channel, kernel):
super(TorchCNN, self).__init__()
self.layer = nn.Conv2d(in_channel, out_channel, kernel, bias=False)
def forward(self, x):
return self.layer(x)
4.归一化 Normalization
batch normalization
layer normalization

5.Dropout层
作用:减少过拟合
按照指定概率,随机丢弃一些神经元(将其化为零)
其余元素乘以 1 / (1 -- p)进行放大

如何理解其作用:
1)强迫一个神经单元,和随机挑选出来的其他神经单元共同工作,消除减弱了神经元节点间的联合适应性,增强了泛化能力
2)可以看做是一种模型平均,由于每次随机忽略的隐层节点都不同,这样就使每次训练的网络都是不一样的,每次训练都可以单做一个"新"的模型
启示:计算方式并不是越复杂就越好