本系列的前两篇是:
自然语言处理实战(一),地址:https://www.jianshu.com/p/cf14318db049
自然语言处理实战(二),地址:https://www.jianshu.com/p/c7dc9346f97b
在前面的两篇中,针对我们自定的文本表示方法,分别用KNN和SVM进行训练和测试,研究表明,KNN的准确率达到0.864,SVM的准确率最高达到0.88以上。
回顾我们的文本表示模型:对于搜狗新闻语料,从每篇新闻的每个段落找那个抽取3个关键词,这三个关键词都用训练好的Word2Vec向量表示,每个词向量64维,每段可拼接为一个192维的向量,最好直接将所有的段落做向量相加运算,得到一个192维的文本向量,不足则补0。更多的细节,请访问自然语言处理实战(一)。
本文,我们将使用深度学习对已经处理好的数据进行研究。
随着计算机硬件和理论的发展,近年来,深度学习几乎在所有任务上都有超越传统机器学习算法的趋势。虽然深度学习在学术界备受鄙视,但不可否认的是,It works great ! 最初,人们对于深度学习的研究,大多集中在计算机视觉上,但近年来,大量的实践和研究表明,深度学习并不是计算机视觉的专利,对于自然语言处理,深度学习依然表现亮眼。
多层全连接神经网络
首先,我们使用深度学习最基本的多层全连接神经网络。网络结构如图所示:
设定一些超参数的初始值,初始值的设置一般是根据数据特征和经验来做,然后根据训练结果不断的调整。
# 定义一些全局参数
num_epoch = 100
batch_size = 100
num_class = 5
learning_rate = 0.005
# 样本维度
max_len = 192
# 网络结构
n_hidden_1 = 192
n_hidden_2 = 128
n_hidden_3 = 64
n_hidden_4 = 32
n_hidden_5 = 16
n_hidden_6 = 8
stddev = 0.1
weights = {
'w1': tf.Variable(tf.random_normal([max_len, n_hidden_1], stddev=stddev, seed=0)),
'w2': tf.Variable(tf.random_normal([n_hidden_1, n_hidden_2], stddev=stddev, seed=0)),
'w3': tf.Variable(tf.random_normal([n_hidden_2, n_hidden_3], stddev=stddev, seed=0)),
'w4': tf.Variable(tf.random_normal([n_hidden_3, n_hidden_4], stddev=stddev, seed=0)),
'w5': tf.Variable(tf.random_normal([n_hidden_4, n_hidden_5], stddev=stddev, seed=0)),
'w6': tf.Variable(tf.random_normal([n_hidden_5, n_hidden_6], stddev=stddev, seed=0)),
'out': tf.Variable(tf.random_normal([n_hidden_6, num_class], stddev=stddev, seed=0))
}
biases = {
'b1': tf.Variable(tf.random_normal([n_hidden_1], stddev=stddev, seed=0)),
'b2': tf.Variable(tf.random_normal([n_hidden_2], stddev=stddev, seed=0)),
'b3': tf.Variable(tf.random_normal([n_hidden_3], stddev=stddev, seed=0)),
'b4': tf.Variable(tf.random_normal([n_hidden_4], stddev=stddev, seed=0)),
'b5': tf.Variable(tf.random_normal([n_hidden_5], stddev=stddev, seed=0)),
'b6': tf.Variable(tf.random_normal([n_hidden_6], stddev=stddev, seed=0)),
'out': tf.Variable(tf.random_normal([num_class], stddev=stddev, seed=0))
}
X = tf.placeholder(tf.float32, [None, max_len])
Y = tf.placeholder(tf.float32, [None, num_class])
我们使用的数据总量是15000个样本,按照7:1:2划分训练集、验证集和测试集。因为为了和前面的实验中的测试集保持一致,在本实验中,验证集从原来的训练集中取出一部分,这样,我们的训练集还有10500个样本,足够。另外,为了更好的表达各个类别,我们将类别标记转为one-hot编码,比如原来两个样本标记为[1, 2],转换为onr-hot编码后的标记为[[1, 0, 0, 0, 0], [0, 1, 0, 0, 0]],这是一种常用的处理手段。
# 划分训练集、验证集和测试集
def load_data():
# 读取数据
data = pd.read_pickle('D:\python\demo\paper\material\\train4\\train3000random.pkl')
# x:特征,y:标签
x = data.iloc[1, :].tolist()
y = data.iloc[0, :].tolist()
# 首先划分出测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, train_size=0.8, random_state=1)
# 然后划分出训练集和验证集,验证集占总体数据集的10%
x_train_train, x_valid, y_train_train, y_valid = train_test_split(x_train, y_train, train_size=0.875,
random_state=1)
# 将标签转化为one-hot编码
enc = preprocessing.OneHotEncoder()
# 首先将每个类别标记都单独表示为一个list
y_train_train_trans = [[float(i)] for i in y_train_train]
y_valid_trans = [[float(i)] for i in y_valid]
y_test_trans = [[float(i)] for i in y_test]
enc.fit(y_train_train_trans)
y_train_train_onehot = enc.transform(y_train_train_trans).toarray()
y_valid_onehot = enc.transform(y_valid_trans).toarray()
y_test_onehot = enc.transform(y_test_trans).toarray()
return x_train_train, x_valid, x_test, y_train_train_onehot, y_valid_onehot, y_test_onehot
和前面的机器学习不同,在深度学习中,我们是批量的取数据,所以有必要做一个迭代的批量获得数据的函数,满足神经网络批处理的要求,代码如下:
# 按批获得数据迭代器
def batch_iter(x_train, y_train, batch_size):
# get dataset and label
x, y = x_train, y_train
x = np.array(x)
y = np.array(y)
data_size = len(x)
#print("data_size",data_size)
num_batches_per_epoch = int(data_size/batch_size)
for batch_index in range(num_batches_per_epoch):
start_index = batch_index*batch_size
end_index = min((batch_index+1)*batch_size, data_size)
return_x = x[start_index:end_index]
return_y = y[start_index:end_index]
if len(return_x) < batch_size:
print(len(return_x))
print(return_x)
print(return_y)
#import sys
#sys.exit(0)
yield return_x, return_y
准备工作完毕,下面是使用tensorflow训练神经网络的核心代码:
def multilayer_perceptron(X, weights, biases):
layer_1 = tf.nn.relu(tf.add(tf.matmul(X, weights['w1']), biases['b1']))
layer_2 = tf.nn.relu(tf.add(tf.matmul(layer_1, weights['w2']), biases['b2']))
layer_3 = tf.nn.relu(tf.add(tf.matmul(layer_2, weights['w3']), biases['b3']))
layer_4 = tf.nn.relu(tf.add(tf.matmul(layer_3, weights['w4']), biases['b4']))
layer_5 = tf.nn.relu(tf.add(tf.matmul(layer_4, weights['w5']), biases['b5']))
layer_6 = tf.nn.relu(tf.add(tf.matmul(layer_5, weights['w6']), biases['b6']))
return tf.add(tf.matmul(layer_6, weights['out']), biases['out'])
# 定义损失和优化操作
pred = multilayer_perceptron(X, weights, biases)
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=Y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
# 定义准确率计算
corr = tf.equal(tf.arg_max(pred, 1), tf.arg_max(Y, 1))
accr = tf.reduce_mean(tf.cast(corr, 'float'))
# 保存训练过程中的损失和准确率
cost_dict = {}
train_acc_dict = {}
valid_acc_dict = {}
# 初始化所有参数
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
# 获取数据
x_train, x_valid, x_test, y_train, y_valid, y_test = load_data()
# 循环训练数据集
for epoch in range(num_epoch):
avg_cost = 0
iterations = batch_iter(x_train, y_train, batch_size)
total_batch = int(len(y_train) / batch_size)
#print("total_batch", total_batch)
while True:
try:
batch_x, batch_y = next(iterations)
# print('batch_x',batch_x)
# print('batch_y',batch_y)
feeds = {X: batch_x, Y: batch_y}
c = sess.run(optimizer, feed_dict=feeds)
avg_cost += sess.run(cost, feed_dict=feeds)
except StopIteration as e:
#print('Generator return value:', e.value)
break
# Display logs per epoch step
avg_cost = avg_cost / total_batch
if (epoch + 1) % 1 == 0:
print("Epoch: %03d/%03d cost: %.9f" %(epoch, num_epoch, avg_cost))
train_acc = sess.run(accr, feed_dict={X: x_train, Y: y_train})
print("训练准确率:%.3f" % train_acc)
valid_acc = sess.run(accr, feed_dict={X: x_valid, Y: y_valid})
print("验证集准确率:%.3f" % valid_acc)
cost_dict[epoch] = avg_cost
train_acc_dict[epoch] = train_acc
valid_acc_dict[epoch] = valid_acc
# 将当前的TensorFlow计算图写入日志
writer = tf.summary.FileWriter(r"D:\python\demo\paper\model\DNN",
tf.get_default_graph())
writer.close()
# 画图
# 中文和符号的正常显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
x_axis = list(cost_dict.keys())
y_cost = list(cost_dict.values())
y_train_acc = list(train_acc_dict.values())
y_valid_acc = list(valid_acc_dict.values())
fig = plt.figure()
#plt.plot(x_axis, y_cost, linestyle='-', color='green', label='平均损失')
plt.plot(x_axis, y_train_acc, linestyle='-', color='fuchsia', label='训练准确率')
plt.plot(x_axis, y_valid_acc, linestyle='-', color='darkorange', label='验证准确率')
# 添加标题和坐标轴标签
plt.title('训练过程准确率变化')
plt.xlabel('epoch')
plt.ylabel('准确率')
# 显示图例
plt.legend()
# 显示图形
plt.show()
print("Optimization Finished!")
不出意外,这个网络很快就发生了过拟合,训练过程可以看下图
可以发现,在迭代20次之前,训练准确率直接飙到了1,验证集上的准确率也难有上升趋势,更准确点,可以看下图:
通过和前面机器学习算法的比较,我们发现在过拟合之前,验证集上的准确率和KNN以及SVM是差不多的,那么,为了进一步提升准确率,我们需要先解决过拟合的问题。
过拟合的常用解决方案
- 数据少,特征多导致的过拟合,增加数据,删选特征。
- 模型过于复杂,参数过多导致的过拟合,奥卡姆剃刀原则,使用更简单的模型。
- Dropout,随机丢弃一些神经元,可以让网络结构更贱简单,减少参数量,增加随机性,降低过拟合风险。
- 正则化,对权重参数做正则化惩罚。
- 控制梯度大小,防止梯度爆炸和梯度消失的问题。
- 增加数据噪声。
- 利用验证集提前终止训练。
- 调整超参数。
第一步:减小网络复杂度
在这一步,我直接去掉第一层全连接层,也就是时候网络变成一个五个隐层的网络,从第一层到第五层的神经元个数分别为:128/64/32/16/8。
第二步:正则化
对权重参数进行惩罚,理论上可以降低过拟合。
经过这两步操作之后的准确率变化如图所示:
过拟合现象似乎并没有有效改善,此时,我怀疑我的一些超参数设置肯能不是很合理,其中最重要的就是学习率的设置,当我把学习率改为0.001的时候,训练过程如下图所示:
学习率:0.0001
更细粒度的:
对每一层之后加入Dropout。keep_prob=0.5
keep_prob=0.2
因此,我们可以得知,快速过拟合的原因还是我们的网络过于复杂了。
是否可以通过减小keep_prob来间接降低模型复杂度?keep_prob=0.2,训练1000次:
在进一步降低各层神经元个数,并设keep_prob=0.8,之后
准确率基本在缓慢上升,训练损失也在缓慢下降,但验证集损失仍然有较大的波动,当把训练集和验证集的比例调为1:1的时候:
虽然验证集损失在上升,但波动明显减小了很多。另外,当直接将预备的测试集做验证集的时候,损失同样波动很大,这说明除了网络设计不好之外,我们使用的数据或太少或噪声比较大或代表性不强,第二个问题后面再讨论,现在还是进一步优化网络。
Batch Normalization
批标准化(Batch Normalization )简称BN算法,是为了克服神经网络层数加深导致难以训练而诞生的一个算法。批标准化一般用在非线性映射(激活函数)之前,对y= Wx + b进行规范化,也就是说使得激活函数的输入稳定在一个比较好(比较小)的范围(均值为0,方差为1),这样可以避免激活函数的某些区间上表现不佳的问题,让每一层的输入有一个稳定的分布会有利于网络的训练
举个例子:
在神经网络中, 数据分布对训练会产生影响. 比如某个神经元 x 的值为1, 某个 Weights 的初始值为 0.1, 这样后一层神经元计算结果就是 Wx = 0.1; 又或者 x = 20, 这样 Wx 的结果就为 2. 现在还不能看出什么问题, 但是, 当我们加上一层激励函数, 激活这个 Wx 值的时候, 问题就来了. 如果使用 像 tanh 的激励函数, Wx 的激活值就变成了 ~0.1 和 ~1, 接近于 1 的部已经处在了 激励函数的饱和阶段, 也就是如果 x 无论再怎么扩大, tanh 激励函数输出值也还是 接近1. 换句话说, 神经网络在初始阶段已经不对那些比较大的 x 特征范围 敏感了。normalization 预处理, 使得输入的 x 变化范围不会太大, 让输入值经过激励函数的敏感部分. 但刚刚这个不敏感问题不仅仅发生在神经网络的输入层, 而且在隐藏层中也经常会发生.
优点
- 减少了参数的人为选择,可以取消dropout和L2正则项参数,或者采取更小的L2正则项约束参数。
- 减少了对学习率的要求,可以适当调大学习率。
- 可以不再使用局部响应归一化了,BN本身就是归一化网络(局部响应归一化-AlexNet)。
- 更破坏原来的数据分布,一定程度上缓解过拟合。
atch Normalization 不足
- 如果 Batch Size 太小,则 BN 效果明显下降。因为在小的 BatchSize 意味着数据样本少,因而得不到有效统计量,也就是说噪音太大。
- 对于有些像素级图片生成任务来说,BN 效果不佳;对于图片分类等任务,只要能够找出关键特征,就能正确分类,这算是一种粗粒度的任务,因为在 Mini-Batch 内多张无关的图片之间计算统计量,弱化了单张图片- 本身特有的一些细节信息。
- RNN 等动态网络使用 BN 效果不佳且使用起来不方便。
- 训练时和推理时统计量不一致。
对我们的网络每层加入批标准化后:
learning_rate = 0.001
keep_prob = 0.8
训练:验证:测试 = 7:1:2
经过重新调整参数:
batch_size = 500
learning_rate = 0.0001
最终得到:
保存模型
通过上面的曲线,感觉模型的表现已经差不多了,接下来把模型保存下来就好了。
通过画图可以发现,在epoch=850之前并没有明显的过拟合,但之后虽然验证集准确率仍然保持相对平衡,但验证集损失有上升的趋势,因此选择850作为截断点。
保存模型得到四个文件:
.data 文件是训练好的参数值,
meta 文件是定义的神经网络图,即所有变量、操作、集合等
checkpoint 文件是所有模型的保存路径
训练模型的完整代码
import tensorflow as tf
import pandas as pd
from sklearn.model_selection import train_test_split
import numpy as np
from sklearn import preprocessing
import matplotlib.pyplot as plt
# 定义一些全局参数
regularizer_rate = 0.0001
num_epoch = 1000
batch_size = 500
num_class = 5
learning_rate = 0.0001
keep_prob = 0.8
# 样本维度
max_len = 192
# 网络结构
#n_hidden_1 = 192
n_hidden_2 = 64
n_hidden_3 = 32
n_hidden_4 = 16
n_hidden_5 = 8
n_hidden_6 = 5
# 计算图保存路径
g_dir = r'D:\python\demo\paper\model\DNN\graph'
# 模型保存路径
m_dir = r'D:\python\demo\paper\model\DNN\temp\sogou_DNN_v3'
stddev = 0.1
weights = {
#'w1': tf.Variable(tf.random_normal([max_len, n_hidden_1], stddev=stddev, seed=0)),
'w2': tf.Variable(tf.random_normal([max_len, n_hidden_2], stddev=stddev, seed=0)),
'w3': tf.Variable(tf.random_normal([n_hidden_2, n_hidden_3], stddev=stddev, seed=0)),
'w4': tf.Variable(tf.random_normal([n_hidden_3, n_hidden_4], stddev=stddev, seed=0)),
'w5': tf.Variable(tf.random_normal([n_hidden_4, n_hidden_5], stddev=stddev, seed=0)),
'w6': tf.Variable(tf.random_normal([n_hidden_5, n_hidden_6], stddev=stddev, seed=0)),
'out': tf.Variable(tf.random_normal([n_hidden_6, num_class], stddev=stddev, seed=0))
}
biases = {
#'b1': tf.Variable(tf.random_normal([n_hidden_1], stddev=stddev, seed=0)),
'b2': tf.Variable(tf.random_normal([n_hidden_2], stddev=stddev, seed=0)),
'b3': tf.Variable(tf.random_normal([n_hidden_3], stddev=stddev, seed=0)),
'b4': tf.Variable(tf.random_normal([n_hidden_4], stddev=stddev, seed=0)),
'b5': tf.Variable(tf.random_normal([n_hidden_5], stddev=stddev, seed=0)),
'b6': tf.Variable(tf.random_normal([n_hidden_6], stddev=stddev, seed=0)),
'out': tf.Variable(tf.random_normal([num_class], stddev=stddev, seed=0))
}
X = tf.placeholder(tf.float32, [None, max_len], name='X')
Y = tf.placeholder(tf.float32, [None, num_class], name='Y')
is_training = tf.placeholder(tf.bool, name="is_training")
# 划分训练集、验证集和测试集
def load_data():
# 读取数据
data = pd.read_pickle('D:\python\demo\paper\material\\train4\\train3000random.pkl')
# x:特征,y:标签
x = data.iloc[1, :].tolist()
y = data.iloc[0, :].tolist()
# 首先划分出测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, train_size=0.8, random_state=1)
# 然后划分出训练集和验证集,验证集占总体数据集的10%
x_train_train, x_valid, y_train_train, y_valid = train_test_split(x_train, y_train, train_size=0.875, random_state=1)
# 将标签转化为one-hot编码
enc = preprocessing.OneHotEncoder()
# 首先将每个类别标记都单独表示为一个list
y_train_train_trans = [[float(i)] for i in y_train_train]
y_valid_trans = [[float(i)] for i in y_valid]
y_test_trans = [[float(i)] for i in y_test]
enc.fit(y_train_train_trans)
y_train_train_onehot = enc.transform(y_train_train_trans).toarray()
y_valid_onehot = enc.transform(y_valid_trans).toarray()
y_test_onehot = enc.transform(y_test_trans).toarray()
return x_train_train, x_valid, x_test, y_train_train_onehot, y_valid_onehot, y_test_onehot
def multilayer_perceptron(X, weights, biases, is_training):
#layer_1 = tf.nn.relu(tf.add(tf.matmul(X, weights['w1']), biases['b1']))
layer_2 = tf.nn.relu(tf.add(tf.matmul(X, weights['w2']), biases['b2']))
batch_normal_2 = tf.layers.batch_normalization(layer_2, training=is_training)
Dropout2 = tf.nn.dropout(batch_normal_2, keep_prob=keep_prob)
layer_3 = tf.nn.relu(tf.add(tf.matmul(Dropout2, weights['w3']), biases['b3']))
batch_normal_3 = tf.layers.batch_normalization(layer_3, training=is_training)
Dropout3 = tf.nn.dropout(batch_normal_3, keep_prob=keep_prob)
layer_4 = tf.nn.relu(tf.add(tf.matmul(Dropout3, weights['w4']), biases['b4']))
batch_normal_4 = tf.layers.batch_normalization(layer_4, training=is_training)
Dropout4 = tf.nn.dropout(batch_normal_4, keep_prob=keep_prob)
layer_5 = tf.nn.relu(tf.add(tf.matmul(Dropout4, weights['w5']), biases['b5']))
batch_normal_5 = tf.layers.batch_normalization(layer_5, training=is_training)
Dropout5 = tf.nn.dropout(batch_normal_5, keep_prob=keep_prob)
layer_6 = tf.nn.relu(tf.add(tf.matmul(Dropout5, weights['w6']), biases['b6']))
batch_normal_6 = tf.layers.batch_normalization(layer_6, training=is_training)
Dropout6 = tf.nn.dropout(batch_normal_6, keep_prob=keep_prob)
return tf.add(tf.matmul(Dropout6, weights['out']), biases['out'])
# 按批获得数据迭代器
def batch_iter(x_train, y_train, batch_size):
# get dataset and label
x, y = x_train, y_train
x = np.array(x)
y = np.array(y)
data_size = len(x)
#print("data_size",data_size)
num_batches_per_epoch = int(data_size/batch_size)
for batch_index in range(num_batches_per_epoch):
start_index = batch_index*batch_size
end_index = min((batch_index+1)*batch_size, data_size)
return_x = x[start_index:end_index]
return_y = y[start_index:end_index]
if len(return_x) < batch_size:
print(len(return_x))
print(return_x)
print(return_y)
#import sys
#sys.exit(0)
yield return_x, return_y
# 定义损失和优化操作
pred = multilayer_perceptron(X, weights, biases, is_training=is_training)
# 正则化
regularizer = tf.contrib.layers.l2_regularizer(regularizer_rate)
regularization = regularizer(weights['w2']) + regularizer(weights['w3']) + regularizer(weights['w4']) +\
regularizer(weights['w5']) + regularizer(weights['w6']) + regularizer(weights['out'])
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=Y)) + regularization
cost_n = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=Y))
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
# Batch Normalization 中需要计算移动平均值,所以 BN 中有一些 update_ops,在训练中需要通过 tf.control_dependencies() 来添加对
# update_ops 的调用,实现训练过程中对BN层的参数进行更新,否则验证和测试结果会很差
with tf.control_dependencies(update_ops):
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
# 定义准确率计算
label_index = tf.arg_max(pred, 1, name='label_index')
corr = tf.equal(tf.arg_max(pred, 1), tf.arg_max(Y, 1))
accr = tf.reduce_mean(tf.cast(corr, 'float'), name='accr')
# 保存训练过程中的损失和准确率
cost_dict = {}
valid_cost_dict = {}
train_acc_dict = {}
valid_acc_dict = {}
# 初始化所有参数
init = tf.global_variables_initializer()
saver = tf.train.Saver(max_to_keep=1)
with tf.Session() as sess:
sess.run(init)
# 获取数据
x_train, x_valid, x_test, y_train, y_valid, y_test = load_data()
# 循环训练数据集
for epoch in range(num_epoch):
avg_cost = 0
iterations = batch_iter(x_train, y_train, batch_size)
total_batch = int(len(y_train) / batch_size)
#print("total_batch", total_batch)
while True:
try:
batch_x, batch_y = next(iterations)
# print('batch_x',batch_x)
# print('batch_y',batch_y)
feeds = {X: batch_x, Y: batch_y, is_training: True}
c = sess.run(optimizer, feed_dict=feeds)
avg_cost += sess.run(cost, feed_dict=feeds)
except StopIteration as e:
#print('Generator return value:', e.value)
break
# Display logs per epoch step
avg_cost = avg_cost / total_batch
# 验证集损失
valid_cost = sess.run(cost, feed_dict={X: x_valid, Y: y_valid, is_training: False})
print("验证损失:", valid_cost)
if (epoch + 1) % 1 == 0:
print("Epoch: %03d/%03d cost: %.9f" %(epoch, num_epoch, avg_cost))
train_acc = sess.run(accr, feed_dict={X: x_train, Y: y_train, is_training: True})
print("训练准确率:%.3f" % train_acc)
valid_acc = sess.run(accr, feed_dict={X: x_valid, Y: y_valid, is_training: False})
print("验证集准确率:%.3f" % valid_acc)
cost_dict[epoch] = avg_cost
train_acc_dict[epoch] = train_acc
valid_acc_dict[epoch] = valid_acc
valid_cost_dict[epoch] = valid_cost
# 提前停止,保存模型
if epoch == 849:
saver.save(sess, m_dir, global_step=epoch)
print("模型保存,终止程序!")
break
# 将当前的TensorFlow计算图写入日志
writer = tf.summary.FileWriter(g_dir, tf.get_default_graph())
writer.close()
# 画图
# 中文和符号的正常显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
x_axis = list(cost_dict.keys())
y_cost = list(cost_dict.values())
y_valid_cost = list(valid_cost_dict.values())
y_train_acc = list(train_acc_dict.values())
y_valid_acc = list(valid_acc_dict.values())
fig = plt.figure()
# 将第一个画板划分为2行1列组成的区块,并获取到第一块区域
ax1 = plt.subplot(211)
plt.plot(x_axis, y_train_acc, linestyle='-', color='fuchsia', label='训练准确率')
plt.plot(x_axis, y_valid_acc, linestyle='-', color='darkorange', label='验证准确率')
# 添加标题和坐标轴标签
ax1.set_title('训练过程准确率变化')
plt.xticks(np.arange(0, num_epoch, 5))
plt.yticks(np.arange(0.3, 1, 0.1))
# 显示图例
plt.legend()
# 选中第二个子区域,并绘图
ax2 = plt.subplot(212)
plt.plot(x_axis, y_cost, linestyle='-', color='green', label='训练平均损失')
plt.plot(x_axis, y_valid_cost, linestyle='-', color='peru', label='验证集损失')
ax2.set_title('训练过程平均损失变化')
plt.xticks(np.arange(0, num_epoch, 5))
plt.yticks(np.arange(0.0, 1.8, 0.2))
# 显示图例
plt.legend()
plt.xlabel('epoch')
# 显示图形
plt.show()
print("Optimization Finished!")
建议:在写训练模型的代码的时候,对引入的每一步参数和操作尽量都明确指定一个名字,方便恢复模型时获得每个值。batch_size设的比较小时,模型收敛快,但随机性大,模型震荡较大,设的较大时,模型收敛稍慢,但模型震荡小。当然,上述代码还有很多优化的空间。
从上图可以看到,收敛后的模型在验证集上的准确率在0.86左右,可想而知,咱测试集应该也是差不多的,下面使用训练好的模型测试一下:
m_dir = r'D:/python/demo/paper/model/DNN/temp/'
# 划分训练集、验证集和测试集
# 注意这个函数不能直接从sogou_DNN_v3文件引入,因为from 一个文件 import * 的时候回运行一遍引入的文件
def load_data():
# 读取数据
data = pd.read_pickle('D:\python\demo\paper\material\\train4\\train3000random.pkl')
# x:特征,y:标签
x = data.iloc[1, :].tolist()
y = data.iloc[0, :].tolist()
# 首先划分出测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, train_size=0.8, random_state=1)
# 然后划分出训练集和验证集,验证集占总体数据集的10%
x_train_train, x_valid, y_train_train, y_valid = train_test_split(x_train, y_train, train_size=0.875, random_state=1)
# 将标签转化为one-hot编码
enc = preprocessing.OneHotEncoder()
# 首先将每个类别标记都单独表示为一个list
y_train_train_trans = [[float(i)] for i in y_train_train]
y_valid_trans = [[float(i)] for i in y_valid]
y_test_trans = [[float(i)] for i in y_test]
enc.fit(y_train_train_trans)
y_train_train_onehot = enc.transform(y_train_train_trans).toarray()
y_valid_onehot = enc.transform(y_valid_trans).toarray()
y_test_onehot = enc.transform(y_test_trans).toarray()
return x_train_train, x_valid, x_test, y_train_train_onehot, y_valid_onehot, y_test_onehot
graph = tf.Graph()
with graph.as_default():
with tf.Session() as sess:
saver = tf.train.import_meta_graph(m_dir + 'sogou_DNN_v3-849.meta')
saver.restore(sess, tf.train.latest_checkpoint(m_dir))
X = graph.get_operation_by_name('X').outputs[0]
Y = graph.get_operation_by_name('Y').outputs[0]
pred = graph.get_operation_by_name('label_index').outputs[0]
is_training = graph.get_operation_by_name("is_training").outputs[0]
accr = graph.get_operation_by_name('accr').outputs[0]
# 获取数据
x_train, x_valid, x_test, y_train, y_valid, y_test = load_data()
feeds = {X: x_test, Y: y_test, is_training: False}
pred_label_index, test_accr = sess.run([pred, accr], feed_dict=feeds)
print('测试准确率:', test_accr)
print(pred_label_index)
测试准确率: 0.8553333。
纠正一个前面的问题,在 train_test_split 函数中,random_state设为0或者不设,每个采样都是不一样的,所以应该设为非0数字,保证每次采样都一样。
上面的代码中,处理准确率,我们还拿到了一个很关键的数据,pred_label_index 表示预测样本的预测输出中最大的值得索引,比如一个样本的预测输出(不要忘了是one-hot编码)是[0 ,1, 0, 0, 0],那么在pred_label_index 中就是 1 ,而根据one-hot编码规则,[0 ,1, 0, 0, 0]实际对应的是2,所以只要将pred_label_index 里面的值都加1 ,其实就是预测的标签列表,比如[ 1+1, 0+1, 1+1, 2+1......3+1, 2+1] = [2, 1, 2, 3......4, 3]。通过这个列表,我们可以很方便的计算预测数据的召回率,F1值等。
总结
经过KNN、SVM和深度学习对我们的文本表示模型进行测试,发现准确率基本在0.86左右,导致准确率不能进一步提升的原因有二:一是我们所使用的算法本身的局限性以及没有调到最优,二是这种文本表示模型本身的局限性。本人是使用TextRank4Keyword对每篇文章的每一个段落抽取三个关键字,但这个算法本身并不一定能抽取准确的关键字,经过本人测试,也证明了这点。另外,我们将每一个段落的关键字简单相加,对数据的压缩和改变的影响是存在的,这导致了特征构建并不完美。最后,我们使用的word2vec模型是由维基百科训练得到的64维的词向量模型,和我们要使用的搜狗新闻分类场景不一致,并且词向量维数较小,也可能是一个缺憾。但是,我们在如此不利的条件下,仅仅使用10000余例数据,仍然达到了0.86左右的分类准确率,充分说明了我们的研究具有极大的前景和意义。
参考资料:
https://blog.csdn.net/fontthrone/article/details/76652772
https://blog.csdn.net/hebi123s/article/details/82769141