总代码
#非原创,小白借鉴网上代码
import tensorflow as tf
class TRNNConfig(object):
"""RNN配置参数"""
#模型参数
embedding_dim = 64 # 词向量维度
seq_length = 600 # 序列长度
num_classes = 10 # 类别数
vocab_size = 5000 # 词汇表达小
num_layers = 2 # 隐藏层层数
hidden_dim = 128 # 隐藏层神经元
rnn = 'gru' # lstm 或 gru
dropout_keep_prob = 0.8 # dropout保留比例
learning_rate = 1e-3 # 学习率
batch_size = 128 # 每批训练大小
num_epochs = 10 # 总迭代轮次
print_per_batch = 100 # 每多少轮输出一次结果
save_per_batch = 10 # 每多少轮存入tensorboard
class TextRNN(object):
"""文本分类, RNN模型"""
def __init__(self, config):
self.config = config
#三个待输入的shuju
self.input_x = tf.placeholder(tf.int32, [None, self.config.seq_length], name='input_x')
self.input_y = tf.placeholder(tf.float32, [None, self.config.num_classes], name='input_y')
self.keep_prob = tf.placeholder(tf.float32, name='keep_prob')
self.rnn()
def rnn(self):
"""rnn 模型"""
#lstm核
def lstm_cell():
return tf.contrib.rnn.BasicLSTMCell(self.config.hidden_dim, state_is_tuple=True)
#gru核
def gru_cell():
return tf.contrib.rnn.GRUCell(self.congig.hidden_dim)
#为每一个rnn核后面加一个dropout层
def dropout():
if(self.config.cnn == 'lstm'):
cell = lstm_cell()
else:
cell = gru_cell()
return tf.contrib.rnn.DropoutWrapper(cell, output_keep_prob=self.keep_prob)
#词向量映射
with tf.device('/cpu:0'):
embedding = tf.get_variable('embedding', [self.config.vocab_size,
self.config.embedding_dim])
embedding_inputs = tf.nn.embedding_lookup(embedding, self.input_x)
with tf.name_scope("rnn"):
"""多层rnn网络"""
cells = [dropout() for _ in range(self.config.num_layers)]
rnn_cell = tf.contrib.rnn.MultiRNNCell(cells, state_is_tuple=True)
_outputs, _ = tf.nn.dynamic_rnn(cell=rnn_cell, inputs=embedding_inputs, dtype=tf.float32)
#取最后一个时序输出作为结果
last = _outputs[:, -1, :]
with tf.name_scope("score"):
"""全连接层,后面截dropout以及relu激活"""
fc = tf.layers.dense(last, self.config.hidden_dim, name='fc1')
fc = tf.contrib.layers.dorpout(fc, self.keep_prob)
fc = tf.nn.relu(fc)
"""分类器"""
self.logits = tf.layers.dense(fc, self.config.num_classes, name='fc2')
self.y_pred_cls = tf.argmax(tf.nn.softmax(self.logits))
with tf.name_scope("optimize"):
"""损失函数,交叉熵"""
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
logits=self.logits, labels=self.input_y
)
self.loss = tf.reduce_mean(cross_entropy)
"""优化器"""
self.optim = tf.train.AdamOptimizer(
learning_rate=self.config.learning_rate).minimize(self.loss)
with tf.name_scope("accuracy"):
"""准确率"""
correct_pred = tf.equal(tf.argmax(self.input_y, 1), self.y_pred_cls)
self.acc = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
self.input_x = tf.placeholder(tf.int32, [None, self.config.seq_length],name='input_x')
[None, self.config.seq_length],多维,行不定,列是seq_length
LSTM(Long Short-Term Memory)核
在RNN模型中,使用原因:
1. LSTM可以解决RNN的梯度消失和梯度爆炸的问题。传统的RNN在处理长序列时容易出现梯度消失或梯度爆炸的问题,导致难以捕捉到长期依赖关系。LSTM通过使用门控机制,能够有效地在信息传递和遗忘之间进行调节,从而更好地处理长期依赖关系。
2. LSTM可以记住和利用长期的上下文信息。LSTM中的记忆单元可以存储和更新长期的信息,并根据需要选择性地忘记和更新它。这使得LSTM能够捕捉到序列中重要的长期依赖关系,例如在自然语言处理中捕捉到句子中的语义信息。
3. LSTM具有较强的建模能力。LSTM的复杂性和表达能力很高,可以对复杂的序列数据进行建模,如自然语言处理中的语言模型、机器翻译和文本生成等任务。
对于给定的RNN模型,根据代码中的`self.config.rnn`的值选择使用LSTM核还是GRU核。如果`self.config.rnn`为'lstm',则使用 `tf.contrib.rnn.BasicLSTMCell` 函数创建一个LSTM核;否则,使用 `tf.contrib.rnn.GRUCell` 函数创建一个GRU核。
此外,为了防止过拟合,代码中在每一个RNN核后面添加了一个dropout层。dropout层将以 `self.keep_prob` 的概率保留输入数据的每个元素,以减少过拟合的风险。
GRU(Gated Recurrent Unit)核
是一种用于循环神经网络(RNN)的一种门控机制。它是对传统的RNN的改进,旨在更好地捕捉长期依赖关系,并且相对于LSTM来说更简化和高效。
GRU核的主要思想是通过引入重置门(reset gate)和更新门(update gate)来控制信息的流动。这两个门控机制在每个时间步骤都会对输入和隐状态进行操作,以决定要继续传递的信息和要更新的状态。通过这种方式,GRU可以选择性地忘记先前的状态和记住当前的输入,并根据需要更新和传递信息,有效地处理长期依赖关系。
与LSTM相比,GRU具有更简化的结构和更少的门控机制。它只有两个门控:重置门和更新门,而LSTM有三个门控:输入门、遗忘门和输出门。这使得GRU在计算上比LSTM更高效,并且需要更少的参数。
GRU在自然语言处理、语音识别、时间序列预测等任务中广泛应用,尤其在处理长序列和需要快速训练的场景中表现出色。
tf.contrib.rnn.BasicLSTMCell(self.config.hidden_dim, state_is_tuple=True)
创建一个具有指定隐藏单元数量(`self.config.hidden_dim`)的LSTM核
一个最简单的LSTM单元。先说一下用法。
__init__(
numunits, # int类型,LSTM内部的单元数,即每个time_step的输出大小
forget_bias=1.0, # float类型,遗忘的偏置
state_is_tuple=True, # 如果是True,接收和返回的是元组的形式,False马上就要遗弃
activation=None, # 激活函数,默认是tanh。
reuse=None, # (可选),重使用的变量。
name=None, # string类型,层的名称,名称相同的层将共享权重,但是需要reuse=True
dtype=None, # 数据类型,调用的时候必须定义。
**kwargs # 当从get_config()的配置构造单元格时,公共层属性的关键字命名属性,如trainable等。从cudnnlstm训练的检查点恢复时,必须使用CudnnCompatibleLSTMCell代替。
)
Dropout
`return tf.contrib.rnn.DropoutWrapper(cell, output_keep_prob=self.keep_prob)`这一行代码的作用是将通过`lstm_cell()`或`gru_cell()`函数创建的RNN核`cell`封装在一个dropout层中,并返回封装后的结果。
`tf.contrib.rnn.DropoutWrapper`是TensorFlow中的一个函数,用于将一个RNN核封装在一个dropout层中。它的参数包括:
- `cell`: 要封装的RNN核。
- `output_keep_prob`: 保留输出的概率。即在训练过程中,每个时间步的输出被保留的概率。通常用来防止过拟合。
最后,函数返回封装好的RNN核,即包含dropout层的RNN核。
代码实现
#lstm核
def lstm_cell():
return tf.contrib.rnn.BasicLSTMCell(self.config.hidden_dim,state_is_tuple=True)
#gru核
def gru_cell():
return tf.contrib.rnn.GRUCell(self.congig.hidden_dim)
#为每一个rnn核后面加一个dropout层
def dropout():
if(self.config.cnn == 'lstm'):
cell = lstm_cell()
else:
cell = gru_cell()
return tf.contrib.rnn.DropoutWrapper(cell, output_keep_prob=self.keep_prob)
词向量映射
embedding = tf.get_variable('embedding', [self.config.vocab_size,
self.config.embedding_dim])
embedding_inputs = tf.nn.embedding_lookup(embedding, self.input_x)
- `tf.get_variable()` 函数用于创建一个名为 `"embedding"` 的变量,它的形状为 `[self.config.vocab_size, self.config.embedding_dim]`。这个变量将作为嵌入层(embedding layer)的参数,用于将输入 `self.input_x` 中的索引值转化为其对应的向量表示。
- tf.nn.embedding_lookup()` 函数用于查找嵌入矩阵中与 `self.input_x` 中索引值对应的向量。嵌入矩阵 `embedding` 和索引矩阵 `self.input_x`。函数的返回值是一个新的张量 `embedding_inputs`,其形状与 `self.input_x` 相同,但其中的每个索引值都被对应的向量替代
ps
with tf.name_scope("rnn"):
创建一个 命名空间,命名空间对变量的使用没有任何影响,不会导致在命名空间内可以使用在其他地方就不可以使用的情况;
多层RNN空间
with tf.name_scope("rnn"):
"""多层rnn网络"""
cells = [dropout() for _ in range(self.config.num_layers)]
rnn_cell = tf.contrib.rnn.MultiRNNCell(cells, state_is_tuple=True)
_outputs, _ = tf.nn.dynamic_rnn(cell=rnn_cell, inputs=embedding_inputs,
dtype=tf.float32)
#取最后一个时序输出作为结果
last = _outputs[:, -1, :]
使用了一个列表推导式来创建一个包含 `self.config.num_layers` 个 `dropout()` 函数调用的列表 `cells`。每次迭代,都会调用 `dropout()` 函数并将其结果添加到列表 `cells` 中。
tf.contrib.rnn.MultiRNNCell
由多个简单的cells组成的RNN cell。用于构建多层循环神经网络。
参数
`cells`:一个包含 RNN 单元的列表。每个单元可以是 `tf.nn.rnn_cell.RNNCell` 类型的实例,用于定义每个 RNN 层的单元结构。
- `state_is_tuple`:一个布尔值,指示是否将 RNN 单元的状态作为元组返回。如果为 `True`,则状态将作为一个包含每个单元状态的元组返回;如果为 `False`,则状态将作为一个拼接的张量返回。
- `name`:可选参数,用于指定该多层 RNN 单元的名称。
`tf.nn.dynamic_rnn`
函数被调用来运行动态 RNN 模型。这个函数接受一个 RNN 单元 `rnn_cell`、输入数据 `embedding_inputs` 和数据类型 `dtype=tf.float32` 作为参数。该函数会自动根据输入数据和 RNN 单元构建一个动态计算图,并返回输出数据 `_outputs` 和最终状态 `_`。
全连接层,后面截dropout以及relu激活
fc = tf.layers.dense(last, self.config.hidden_dim, name='fc1')
fc = tf.contrib.layers.dorpout(fc, self.keep_prob)
fc = tf.nn.relu(fc)
首先,`tf.layers.dense` 函数被调用来创建一个全连接层。这个函数接受两个参数,第一个参数 `last` 是输入数据,就是上一段代码中得到的最后一个时间步的输出结果。第二个参数 `self.config.hidden_dim` 是神经网络的隐藏层维度,即输出的维度。通过指定 `name='fc1'` 参数,可以给这个全连接层起一个名字,方便后续的操作。
接着,`tf.contrib.layers.dropout` 函数被调用来对全连接层的输出 `fc` 进行 Dropout 操作。这个函数接受两个参数,第一个参数 `fc` 是输入数据,即全连接层的输出。第二个参数 `self.keep_prob` 是 Dropout 的保留率,即保留神经元的概率。通过在训练过程中随机地将一部分神经元的输出置为 0,Dropout 可以增加模型的泛化性能。
之后,`tf.nn.relu` 函数被调用来对经过 Dropout 操作后的全连接层的输出进行ReLU非线性激活。这个函数接受一个参数 `fc`,即输入数据。ReLU(Rectified Linear Unit)函数对所有负数输入置为0,对所有正数输入保持不变。这种非线性激活函数可以增加网络的拟合能力和非线性表达能力。
分类器
self.logits = tf.layers.dense(fc, self.config.num_classes, name='fc2')
self.y_pred_cls = tf.argmax(tf.nn.softmax(self.logits))
利用全连接神经网络将输入数据经过一系列的矩阵运算和非线性激活函数处理,得到最后的预测结果 `self.y_pred_cls`。
`tf.layers.dense` 函数被调用来创建另一个全连接层。这个函数的使用方式与前面的类似,它的第一个参数 `fc` 是输入数据,即上一层的输出结果。第二个参数 `self.config.num_classes` 是神经网络的类别数量,即输出的维度。通过指定 `name='fc2'` 参数,可以给这个全连接层起一个名字。
`tf.nn.softmax` 函数将全连接层的输出结果 `self.logits` 进行 softmax 激活。Softmax 函数将输出结果转化为概率分布,使得每个类别预测的概率值在 0 到 1 之间,并且所有类别的概率之和等于 1。
通过 `tf.argmax` 函数找到概率最大的类别的索引,作为模型的预测结果。这个函数的参数 `self.logits` 是输入数据,即需要进行预测的结果。`tf.argmax` 函数会返回一个具有最大值的元素的索引,它可以用于确定模型预测的类别。
损失函数,交叉熵,优化器
"""损失函数,交叉熵"""
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
logits=self.logits, labels=self.input_y
)
self.loss = tf.reduce_mean(cross_entropy)
"""优化器"""
self.optim = tf.train.AdamOptimizer(
learning_rate=self.config.learning_rate).minimize(self.loss)
- 使用 `tf.nn.softmax_cross_entropy_with_logits` 函数计算交叉熵损失。这个函数的参数 `logits` 是神经网络的输出结果 `self.logits`,`labels` 是真实标签数据 `self.input_y`。交叉熵损失函数用于衡量模型输出结果与真实标签的差异,即模型对每个类别的预测概率与真实标签的概率之间的差距。
- 使用 `tf.reduce_mean` 函数计算平均损失,将整个批次的损失值取平均得到最终的损失值。这个步骤是为了将批次中的所有样本损失值合并为一个标量。
- 使用 `tf.train.AdamOptimizer` 函数:是一个寻找全局最优点的优化算法,引入了二次方梯度校正,参数name:应用梯度时为了创建操作的可选名称。默认为“Adam”函数创建一个 Adam 优化器。Adam 是一种常用的优化算法,使用梯度下降来更新模型参数。这个函数的参数 `learning_rate` 是学习率,即更新参数的步长大小。在实例化优化器后,使用 `minimize` 方法传入损失值,即可生成一个优化操作,用于通过优化算法更新模型参数。
准确率
correct_pred = tf.equal(tf.argmax(self.input_y, 1), self.y_pred_cls)
self.acc = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
使用 `tf.argmax` 函数找到预测结果 `self.y_pred_cls` 中概率最高的类别的索引,并与真实标签 `self.input_y` 进行比较。`tf.equal`函数将返回一个布尔值的张量,表示预测结果是否与真实标签相等。这样就可以得到一个长度为批次大小的布尔值列表。
然后,使用 `tf.cast` 将布尔值转换为浮点型张量,其中 `correct_pred` 中的 `True` 转换为 1,`False` 转换为 0。这样就得到了一个浮点型的准确率列表。
最后,使用 `tf.reduce_mean` 函数计算准确率的平均值,将整个批次的准确率合并为一个标量。