问题描述
在上一次实战中介绍了深度学习找最大数字,这次将数字换成字符,实现list.index()的功能,同时也可以多理解embeding层和RNN层,这次举例的字符是“收藏点击加关注zeon3pang”,随机不重复(其实重复也可以做)的从这几个字符中抽取4个字符,“赞”字在第几个就将它归为第几类,如果没有抽到这个字就将他归为0类。步骤和上次实战类似,只不过对于自然语言的处理,相比上次多了一个词表的建立,所以第一步先建立一个词表。
步骤1:定义词表
步骤2:生成训练集
步骤3:定义神经网络
步骤4:代入数据训练参数
步骤5:模型评估预测
定义词表
import torch
import torch.nn as nn
import numpy as np
import random
import json
import matplotlib.pyplot as plt
def build_vocab():
chars = "点赞收藏加关注zeon3pang" #字符集
vocab = {"pad":0}
for index, char in enumerate(chars):
vocab[char] = index+1 #每个字对应一个序号
vocab['unk'] = len(vocab)
return vocab
对于词表,将字符存入字典里,每个字符对应有个数字,这样就将字符数字化了,而"pad"的存储分别对应着的是样本不一致时,将空缺的样本补充为0,这次实战用不到,因为我们每次都是抽取4个字符,所以样本都是相同长度的。"unk"的存储是对应着样本的字符不在词表里的一个问题,不过我们这次也用不到。这次只是先做一个了解。
生成训练集
def build_sample(vocab, sentence_length):
#随机从字表选取sentence_length个字
x = random.sample(list(vocab.keys()),sentence_length)
#指定样本分类
if '赞' in x:
y=x.index('赞')+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.LongTensor(dataset_y)
这里的样本生成与上次基本差不多,为一的区别是上次定义的 y y y值是一个列表,这次我们直接用一个数值来表示样本的类别。
定义神经网络
神经网络第一层肯定是就是embedding层了,虽说我们对每个字都制定了对应的数字,但是我们每个数字还需要对应一个向量,向量维数随便取个20维,然后通过反向传播来对每个字符的向量不断训练。对于第二层就是我们的RNN层了,因为我们的任务是一个序列任务,所以是需要经过RNN层的,而且RNN层有一个池化的作用,这里可以看我的这篇介绍深度学习之RNN。可以将前面的层作为一个特征工程,最后是一个线性层,然后他是一个多分类的问题,然后我们通过交叉熵损失函数进行一个反向传播。这样就完成了我们的神经网络的搭建。实例图如下:
这里因为我们需要判断五类,所以输出类别是5个类别,对应搭建的代码如下:
class TorchModel(nn.Module):
def __init__(self, vector_dim, sentence_length, vocab):
super(TorchModel, self).__init__()
self.embedding = nn.Embedding(len(vocab), vector_dim,padding_idx=0) #embedding层
self.classify = nn.Linear(5, 5)
self.rnn = nn.RNN(vector_dim,5,batch_first=True)
self.loss = nn.functional.cross_entropy
#当输入真实标签,返回loss值;无真实标签,返回预测值
def forward(self, x, y=None):
x = self.embedding(x)
x = self.rnn(x)[1]
x = x.squeeze()#经过RNN后会多一个维度,所以去掉一个维度
y_pred = self.classify(x)
if y is not None:
return self.loss(y_pred, y) #预测值和真实值计算损失
else:
return y_pred #输出预测结果
代入数据训练参数
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个用于测试的样本
correct, wrong = 0, 0
with torch.no_grad():
y_pred = model(x) #模型预测
for y_p, y_t in zip(y_pred, y): #与真实标签进行对比
if torch.argmax(y_p)==int(y_t) :
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 = 4 #样本文本长度
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
main()
训练部分代码比较简单,基本上和上次基本差不多,定义学习率维度一些参数,不同的是y值定义与上次不同,所以评估部分的代码比上次更加简洁一些,这里直接看结果:
这里有上次的调参经验,最终训练结果预测率准确率为百分百,说明他也是一个比较简单语言分类任务。
模型评估预测
def predict(model_path, vocab_path, input_strings):
char_dim = 20 # 每个字的维度
sentence_length = 4 # 样本文本长度
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(result):
print("输入:%s, 预测类别:%d" % (input_strings[i], int(input_string.argmax()))) #打印结果
test_strings = ["zeon", "点赞收藏", "收藏点赞", "关注收藏"]
predict("model.pth", "vocab.json", test_strings)
模型评估预测也是与上次代码相同,这里尝试的字符有"zeon", “点赞收藏”, “收藏点赞”, “关注收藏”,这几个,直接看结果:
预测结果都正确。
思考
做完这个小实战,突然想起上次一个建筑行业的数据分析面试题目,问:我们的数据因为都是人工输入,有些人因为输入的单位不同,所以他们输入的都是文本类型,怎么整合这个数据,让他变得统一?虽然好像可以通过一个简单的循环处理,但也可以通过这个模型处理。