import tensorflow as tf
import pandas as pd
import jieba
import numpy as np
当使用tensorflow的时候,要引用的包就很少了,就一个tensorflow就够用了,不像keras,需要从layers到activation都要手动引入。
def batch_iter(x, y, batch_size=64):
"""生成批次数据"""
data_len = len(x)
num_batch = int((data_len - 1) / batch_size) + 1
indices = np.random.permutation(np.arange(data_len))
x_shuffle = x[indices]
y_shuffle = y[indices]
for i in range(num_batch):
start_id = i * batch_size
end_id = min((i + 1) * batch_size, data_len)
yield x_shuffle[start_id:end_id], y_shuffle[start_id:end_id]
定义生成batch的函数,不同于keras,tf的batch需要自己生成,然后在每一个batch和每一个epoch都要手动feed_dict。
def feed_data(x_batch, y_batch):
feed_dict = {
input_x: x_batch,
input_y: y_batch,
}
return feed_dict
定义的feed_data是用于每次输入数据到模型中,可有可无。
embedding_dim = 64 # 词向量维度
seq_length = 100 # 序列长度
num_filters = 32 # 卷积核数目
filter_sizes = [3,4,5] # 卷积核尺寸
vocab_size = 80000 # 词汇表达小
dropout_keep_prob = 0.5 # dropout保留比例
learning_rate = 1e-3 # 学习率
batch_size = 64 # 每批训练大小
num_epochs = 10 # 总迭代轮次
print_per_batch = 100 # 每多少轮输出一次结果
save_per_batch = 10 # 每多少轮存入tensorboard
定义模型中常用的参数,其中需要注意的,是filter_sizes,根据textcnn的原作,这里的filter是用了3个,然后再将各自的结果merge起来,所以这里需要3个不同尺寸的filter,用来捕捉不同长度的词语(相当于ngram吧)组合的特征。
with tf.name_scope('input_layer'):
input_x = tf.placeholder(dtype=tf.int32,shape=[None,seq_length],name='input_x')
input_y = tf.placeholder(dtype=tf.float32,shape=[None,1],name='input_y')
with tf.device('/cpu:0'):
embedding = tf.get_variable('embedding5',[vocab_size,embedding_dim])
embeddign_inputs = tf.nn.embedding_lookup(embedding,input_x)
embedded_expaned = tf.expand_dims(embeddign_inputs,-1)
首先定义了一个name_scope,用于隔离不同空间之下的变量。
然后声明一个用cpu来处理的操作,而就这一句代码让我理解了整个embeedding的工作原理。因为keras并没有这个embedding_lookup的操作。所以我一直不明白vocab_size,和我的sequence_length的关系。现在有了这个lookup就容易理解很多了。
for i, filter_size in enumerate(filter_sizes):
with tf.name_scope("conv-maxpool-%s" % filter_size):
pooled_outputs = []
for i, filter_size in enumerate(filter_sizes):
filter_shape = [filter_size, embedding_dim, 1, num_filters]
W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="W")
b = tf.Variable(tf.constant(0.1, shape=[num_filters]), name="b")
conv1 = tf.nn.conv2d(input=embedded_expaned,
filter=W,
strides=[1,1,1,1],
padding='VALID',
name='conv')
h = tf.nn.relu(tf.nn.bias_add(conv1,b),name='relu')
max_pool = tf.nn.max_pool(
h,
ksize=[1,seq_length-filter_size+1,1,1],
strides= [1,1,1,1],
padding='VALID',
name='max_pool'
)
pooled_outputs.append(max_pool)
num_filters_total = num_filters * len(filter_sizes)
h_pool = tf.concat(pooled_outputs,3)
h_pool_flat =tf.reshape(h_pool,shape=[-1,num_filters_total])
可以说这个TextCNN的精髓所在,但其实在这种情况下还是很容易理解的(这种情况对应的是使用的预训练好的词向量)。
首先是对于不同size的filter,设定不同的name_scope。然后是定义filter_shape,这个是用来生成下面的权重。这里的权重,其实就是最后将要生成的embedding层,而如果是预训练好的词向量,这里的W就是全1的矩阵。接下来就进行conv的操作,这里和图像处理的无异。max_pool的处理也和图像的没差别。将几个size的filter处理好后,就将结果存起来。
然后将所有结果flat起来,用于连接下面的全连接层。
with tf.name_scope('dropout'):
h_drop = tf.nn.dropout(h_pool_flat,0.5)
with tf.name_scope('output'):
W = tf.get_variable(
"W4",
shape=[num_filters_total,1],
initializer= tf.contrib.layers.xavier_initializer(),
)
b = tf.Variable(tf.constant(0.1,shape=[1]),name="b")
l2_loss +=tf.nn.l2_loss(W)
l2_loss +=tf.nn.l2_loss(b)
scores = tf.nn.xw_plus_b(h_drop,W,b)
with tf.name_scope('loss'):
losses = mean_squared_error(scores,target=input_y,is_mean=True)
with tf.name_scope("optimize"):
optim = tf.train.GradientDescentOptimizer(learning_rate=learning_rate).minimize(losses)
全连接层接的之前先连接dropout,loss函数加上l2正则化的W和b,因为我这里做的是回归任务,所以目标函数是一个自定义的mse。接下来就优化目标函数。
for epoch in range(15):
print('Epoch:', epoch + 1)
batch_train = batch_iter(data[:train.shape[0]], train['score'].values, 32)
for x_batch, y_batch in batch_train:
y_batch = np.reshape(y_batch,(-1,1))
feed_dict = feed_data(x_batch, y_batch)
session.run(optim, feed_dict=feed_dict) # 运行优化
运行的代码如上,这里略去了数据预处理过程。
写这篇文章主要是为了记录自己的思路,有缘人看到有啥问题的话欢迎留言。
以后会尽量封装好,包括可以选择是否用预训练的词向量,以及训练的可视化等等。