TensorFlow Data Flow Graph
一:层的理解
为了形象的理解神经网络,我们提出了层的概念,虽然这更加的形象了,但同时也给初学者带来了很多困扰和不太理解的地方,主要就是在涉及到代码的时候。
层的责任可以理解为三个,一是从某个地方拿到需要用于运算的数据;二是对这些数据进行运算;三是将运算的结果经过处理后输出到其他地方。那么取数据的地方和输出的地方是哪里呢?其实就是某些变量。
标准的神经网络分为三种层,分别为输入层(input layer)、隐藏层(hidden layer)和输出层(output layer)。
输入层:从我们自定义的变量中取得数据,将数据进行矩阵运算,再应用于激活函数,接着将结果存入到变量
隐藏层:从输入层输出的变量中获取数据,并进行运算和使用激活函数后,将结果输出到变量供下一个隐藏层使用,此过程根据你定义的隐藏层的层数会迭代执行多次。
输出层:从最后个隐藏层的输出变量中获取数据,经过运算和使用激活函数后,根据业务需要得到对应个数的输出结果。
二:词的向量化
对于神经网络,在处理字符等类型数据的时候,我们首先需要将字符数字化,然后再作为训练的数据输入网络进行训练。如何将字符数字化,根据自己的需要可以采用多种方案,比较常见的方案是采用one-hot编码,以及参照这种思想自定义的其他格式。在神经网络中,我们对字符数字化都是将字符或词语编码成向量,如果采用one-hot,词向量的大小一般由字符的个数来决定。
如果我们对“我们在这里很好”这句话进行one-hot编码,首先将这句话分为:“我们”、“在”、“这里”、“很好”四个词语,然后对每个词语进行one-hot编码,编码后的格式可能为:[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]。不难发现,one-hot编码的思想等同于每个词语依次分别放到表格的每列里面,表示哪个词语就将词语所在的列设置为1,其它列则设置为0。
在实际使用的场景下,我们可以参照这种思想来对输入的数据进行变换,我们可以根据词语的重要性使用不同的编号,例如上面如果“我们”比其他的都重要,那么可以将“我们”设置编号为3,则得到的向量就为:[[3,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
在此我们引出第一个变量input_vec_size,该变量定义每个词向量的大小,在这里就是4。
三:训练数据的批量化
训练的数据在进行训练前需要先将其批量化处理,批量化的方法下面举例说明。我们对下面的歌唱进行批量化:“西边的太阳快要落山了, 微山湖上静悄悄。 弹起我心爱的土琵琶, 唱起那动人的歌谣”
为了方便演示我们把里面的标点符号去掉,同时字符数字化的操作也省略掉,后面看到的这些词就当作是已经是数字化后的结果。
接下来我们先认识下几个变量:
- batch_size:批次的大小,就是batch里面有多少行,这里设置为2.
- num_steps:步长,就是每行里面最大的列数,这里设置为3.
首先,我们将数据分成batch_size行,得到如下格式:
西边的太阳快要落山了微山湖上静悄悄
弹起我心爱的土琵琶唱起那动人的歌谣
此时,在代码里面就等同于以下的二维数据:[ [西,边,的,太,阳,快,要,落,山,了,微,山,湖,上,静,悄,悄] [弹,起,我,心,爱,的,土,琵,琶,唱,起,那,动,人,的,歌,谣] ]
接着我们根据num_steps来分割每一行,得到如下格式:
西边的 | 太阳快 | 要落山 | 了微山 | 湖上静 | 悄悄 弹起我 | 心爱的 | 土琵琶 | 唱起那 | 动人的 | 歌谣
如此一来,每次输入到神经网络的数据就是一个小的二维数组的batch数据,如:
[
[西,边,的]
[弹,起,我]
]
我们再将每个字进行向量化,得到如下结果(input_vec_size=6)
[
[[1,0,0,0,0,0],[0,1,0,0,0,0],[0,0,1,0,0,0]]
[[0,0,0,1,0,0],[0,0,0,0,1,0],[0,0,0,0,0,1]]
]
这个三维的矩阵([batch_size,num_steps,input_vec_size])就是LSTM的训练输入。
数据的批量化示例图如下:
注:输入的矩阵可以是[batchsize,numsteps,inputvecsize]或[numsteps,batchsize,inputvecsize]。由于tf.nn.dynamicrnn函数的参数timemajor默认值为False,所以默认输入格式是[batchsize,numsteps,inputvecsize];如果使用[numsteps,batchsize,inputvecsize]效率会稍微高一点,因为内部的计算也会转换成这个结构。同时请注意,隐藏层输入的是什么结构,最终隐藏层输出的结构也一致。
四:构建模型
1,初始化对象
def __init__(self,batch_size,num_steps,input_vec_size,num_classes,lstm_size,num_layers,is_training=True,
learning_rate=0.001,grad_clip=5):
print("--tensorflow version:", tf.__version__)
print("--tensorflow path:", tf.__path__)
#batch的大小和截断长度
self.batch_size = batch_size
#等同其他地方的time_steps
self.num_steps = num_steps
#词向量大小(等同其他地方的input_size) embedding_size
self.input_vec_size = input_vec_size
#输出的类型数(词数)
self.num_classes = num_classes
#LSTM隐藏层神经元数:num_units,hidden_size
self.lstm_size = lstm_size
#LSTM隐藏层层数
self.num_layers = num_layers
#是否是训练状态
self.is_training = is_training
#学习率
self.learning_rate = learning_rate
#梯度裁剪
self.grad_clip = grad_clip
2,构建输入数据(实际数据占位符,变量)
def build_inputs(self,batch_size, num_steps, input_vec_size, num_classes):
# 输入定义数据占位符(TensorFlow默认使用GPU可能导致参数更新过慢,所以建议参考项目中的代码,尤其在定义Variables时注意要绑定CPU)
with tf.device("/cpu:0"):
# 输入的词矩阵,维度为batch_size * num_steps * input_vec_size
inputs = tf.placeholder(tf.float32, shape=(batch_size, num_steps, input_vec_size), name='inputs')
#预期输出 batch_size * num_classes
labels = tf.placeholder(tf.float32, shape=(batch_size, num_classes), name='labels')
#节点不被dropout的概率
keep_prob = tf.placeholder(tf.float32, name='keep_prob')
return inputs,labels,keep_prob
3,创建输入层
def build_input_layer(self,input_data, num_steps, input_vec_size, lstm_size):
with tf.variable_scope("input_wb"):
with tf.device("/cpu:0"):
input_wight = tf.Variable(tf.truncated_normal([input_vec_size, lstm_size]))
input_bias = tf.Variable(tf.zeros([lstm_size, ]))
tf.summary.histogram("input_weight",input_wight)
tf.summary.histogram("input_bias", input_bias)
#首先将向量转换为矩阵
inputs_data = tf.reshape(input_data, shape=[-1, input_vec_size])
#执行运算
rnn_inputs = tf.matmul(inputs_data, input_wight) + input_bias
#add 将输入运用sigmoid激活函数
#rnn_inputs = tf.nn.sigmoid(rnn_inputs)
#将数据再转换为隐藏层需要的格式 [batch_size,num_steps,lstm_size]
self.rnn_inputs = tf.reshape(rnn_inputs, shape=[-1, num_steps, lstm_size])
return self.rnn_inputs
4,构建隐藏层
def build_lstm_layer(self,lstm_size, num_layers, batch_size, keep_prob):
lstm_cell = tf.nn.rnn_cell.LSTMCell(lstm_size, state_is_tuple=True)
with tf.name_scope('dropout'):
if self.is_training:
# 添加dropout.为了防止过拟合,在它的隐层添加了 dropout 正则
lstm_cell = tf.nn.rnn_cell.DropoutWrapper(lstm_cell, output_keep_prob=keep_prob)
tf.summary.scalar('dropout_keep_probability', keep_prob)
#堆叠多个LSTM单元
stacked_lstm = tf.nn.rnn_cell.MultiRNNCell([lstm_cell for _ in range(num_layers)], state_is_tuple=True)
#初始化 LSTM 存储状态.batch_size,stacked_lstm.state_size
initial_state = stacked_lstm.zero_state(batch_size, tf.float32)
return stacked_lstm, initial_state
5,构建输出层
'''
构造输出层,与LSTM层 进行全连接
:param lstm_output lstm层的输出结果,[batch_size,num_steps,lstm_size]
:return:
'''
def build_output_layer(self,hidden_output, lstm_size, num_classes):
with tf.variable_scope("softwax"):
softmax_w = tf.Variable(tf.truncated_normal([lstm_size,num_classes]))
softmax_b = tf.Variable(tf.zeros(num_classes))
hidden_output = tf.transpose(hidden_output, [1, 0, 2])
hidden_output = tf.gather(hidden_output, int(hidden_output.get_shape()[0]) - 1)
#计算logits
logits = tf.matmul(hidden_output,softmax_w) + softmax_b
#输出层softmax返回概率分布
softmax_out = tf.nn.softmax(logits,name='predictions')
return softmax_out, logits
注意:下面的方式可以实现“多目标”的场景
def build_output_layer(self,hidden_output, lstm_size, num_classes):
with tf.variable_scope("output_wb"):
with tf.device("/cpu:0"):
output_w = tf.Variable(tf.truncated_normal([lstm_size, num_classes]))
output_b = tf.Variable(tf.zeros(num_classes))
tf.summary.histogram("output_weight", output_w)
tf.summary.histogram("output_bias", output_b)
# 将输出的维度进行转换(B,T,D) => (T,B,D)
hidden_output = tf.transpose(hidden_output, [1, 0, 2])
#这里取最后个num_steps得到的数据
hidden_output = tf.gather(hidden_output, int(hidden_output.get_shape()[0]) - 1)
#计算并得出结果
output_vec = tf.matmul(hidden_output, output_w) + output_b
#预测结果
product = tf.nn.sigmoid(output_vec)
return output_vec, product
6,构造损失(成本)函数
def build_loss(self,output_vec, labels):
#根据logits和labels计算损失。
#logits:[batch_size,num_classes]; labels:[batch_size,num_classes]
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(
logits=output_vec, labels=labels))
return loss
注意:下面的方式可以实现“多目标”的场景
def build_loss(self,output_vec, labels):
# 根据output_vec和labels计算损失。
#output_vec 未经过sigmod或softmax处理的输出
#logits:[batch_size,num_classes]; labels:[batch_size,num_classes]
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
logits=output_vec, labels=labels))
return loss
7,构造优化器
def build_optimizer(self,loss, learning_rate, grad_clip):
# 构造加速训练的优化方法
with tf.name_scope('train'):
optimizer = tf.train.AdamOptimizer(learning_rate)
'''
optimizer = tf.train.AdadeltaOptimizer(learning_rate)
optimizer = tf.train.AdagradOptimizer(learning_rate)
optimizer = tf.train.FtrlOptimizer(learning_rate)
optimizer = tf.train.RMSPropOptimizer(learning_rate)
'''
#optimizer.apply_gradients(zip(grads,tvar))
#该函数是简单的合并了compute_gradients()与apply_gradients()函数,返回为一个优化更新后的var_list
#如果global_step非None,该操作还会为global_step做自增操作
train_op = optimizer.minimize(loss)
return train_op
8,定义精准度评估函数
'''
定义计算模型预测结果准确度
'''
def accuracy_eval(self, product, y):
correct_pred = tf.equal(tf.argmax(product, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
return accuracy
9,构建模型
def build_model(self):
self.inputs, self.labels = self.build_inputs(self.batch_size, self.num_steps, self.input_vec_size, self.num_classes)
#输入层
rnn_inputs = self.build_input_layer(self.inputs, self.num_steps, self.input_vec_size, self.lstm_size)
#隐藏层
stacked_lstm, self.initial_state = self.build_lstm_layer(self.lstm_size, self.num_layers, self.batch_size, self.keep_prob)
hidden_output, self.final_state = tf.nn.dynamic_rnn(stacked_lstm, rnn_inputs, initial_state=self.initial_state)
#输出层
output_vec, self.predict = self.build_output_layer(hidden_output, self.lstm_size, self.num_classes)
if self.is_training :
# 使用损失函数
self.loss = self.build_loss(output_vec, self.labels)
#使用优化器
self.train_op = self.build_optimizer(self.loss, self.learning_rate, self.grad_clip)
return self.predict
10,将字符批量化
'''return 可迭代的元祖
x:[batch_size,num_steps,input_vec_size]的X训练数据
y:[batch_size,num_classes]
'''
def get_batches(batch_size,num_steps,input_vec_size):
n_batches = 12 #通过计算得出的批量数
for num in range(n_batches):
yield x, y
11,训练和保存模型
class Training:
def __init__(self, batch_size, num_steps, input_vec_size, num_classes, lstm_size, num_layers,epoch_size=1000,
learning_rate=0.001, keep_prob=0.75,grad_clip=5, checkpoint_dir='./checkpoints/v2',log_dir='./logs/v2'):
self.epoch_size = epoch_size
self.keep_prob = keep_prob
#检查点文件的存放目录
self.checkpoint_dir = checkpoint_dir
self.log_dir = log_dir
if not os.path.exists(checkpoint_dir):
os.makedirs(checkpoint_dir)
#训练轮数计数器变量(不需要被训练)
self.global_step = tf.Variable(0,name='global_step',trainable=False)
#创建模型
self.model = SitePrectModel(batch_size=batch_size, num_steps=num_steps, input_vec_size=input_vec_size,
num_classes=num_classes, lstm_size=lstm_size, num_layers=num_layers,
learning_rate=learning_rate,is_training=True,grad_clip=grad_clip)
self.model()
#初始化或重新加载会话
self.init_or_load_session()
#加载数据
self.data_util = DataUtil(batch_size)
def __call__(self, *args, **kwargs):
print('start training')
self.summary_writer = tf.summary.FileWriter(self.log_dir,tf.get_default_graph())
self.summary_log = tf.summary.merge_all()
self.current_epoch = 0
self.counter = 0
for epoch in range(self.epoch_size):
print('>>>>>>>训练轮数:{}'.format(epoch))
self.current_epoch = epoch
state = self.sess.run(self.model.initial_state)
batches = self.data_util.get_batches()
for x, y in batches:
self.counter += 1
state, product = self.optimization(x,y,state)
if self.counter % 20 == 0:
feed_dict = {self.model.inputs: x,
self.model.labels: y,
self.model.keep_prob:1.}
accuracy = self.sess.run(self.model.accuracy,feed_dict=feed_dict)
if (epoch+1)%100 == 0:
self.evaluation()
self.evaluation()
print('training end')
'''
初始化或加载Session
'''
def init_or_load_session(self):
self.sess = tf.Session()
self.saver = tf.train.Saver()
ckpt = tf.train.latest_checkpoint(checkpoint_dir=self.checkpoint_dir)
if ckpt:
print('restore session from ',ckpt)
self.saver.restore(self.sess, ckpt)
else:
print('initialize all variables')
self.sess.run(tf.initialize_all_variables())
def evaluation(self):
self.saver.save(self.sess,self.checkpoint_dir+'/model{}.ckpt'.format(self.current_epoch),
global_step=self.global_step)
def optimization(self,batch_x,batch_y,state):
feed_dict = {self.model.inputs: batch_x,
self.model.labels: batch_y,
self.model.keep_prob: self.keep_prob,
self.model.initial_state:state}
final_state, train_op, batch_loss, product,summary_log = self.sess.run([self.model.final_state,
self.model.train_op,
self.model.loss,
self.model.product,
self.summary_log],
feed_dict=feed_dict)
if self.counter % 100 == 0:
self.summary_writer.add_summary(summary_log, self.current_epoch)
print('训练误差: {:.4f}... '.format(batch_loss))
return final_state,product
12,开始训练
#训练模型
batch_size = 4 # 单个batch中序列的个数
num_steps = 1 # 单个序列中的字符数目
input_vec_size = 151 # 隐层节点个数,输入神经元数(单词向量的长度)
num_classes = 30 # 输出神经元数(最后输出的类别总数,例如这的基站数)
lstm_size = 160
num_layers = 6 # LSTM层个数
learning_rate = 0.0001 # 学习率
#feed in 1 when testing, 0.75 when training
keep_prob = 0.75 # 训练时dropout层中保留节点比例
epoch_size = 100 # 迭代次数
training = Training(batch_size=batch_size, num_steps=num_steps, input_vec_size=input_vec_size,
num_classes=num_classes, lstm_size=lstm_size, num_layers=num_layers,
learning_rate=learning_rate,epoch_size=epoch_size)
training()
tensorboard --logdir=./logs/v2
http://localhost:6006/