引言:最进在搞机器学习,写了几篇东西,这准备用NLP技术,写一个对文章类型分类的模型。
一、搜集训练数据、验证数据和测试数据:
上面图中,分别是 :
essay.test(测试数据):
essay.train(训练数据):
essay.val(验证数据):
essay.vocab(词表):
二、训练数据特征提取:
1、定义文件路径
# 主目录
root_path = 'data/essayData/data'
# 训练数据路径
train_path = os.path.join(root_path, 'essay.train.txt')
# 验证数据路径
val_path = os.path.join(root_path, 'essay.val.txt')
# 测试数据路径path
test_path = os.path.join(root_path, 'essay.test.txt')
# 总的词表数据路径
vocab_path = os.path.join(root_path, 'essay.vocab.txt')
2、此次训练的文本一共分为10类(‘体育’, ‘财经’, ‘房产’, ‘家居’, ‘教育’, ‘科技’, ‘时尚’, ‘时政’, ‘游戏’, ‘娱乐’):
def read_labels():
labels = ['体育', '财经', '房产', '家居', '教育', '科技', '时尚', '时政', '游戏', '娱乐']
label_to_id = dict(zip(labels, range(len(labels))))
return label_to_id
label_to_id = read_labels()
print(label_to_id)
输出:
{'体育': 0, '财经': 1, '房产': 2, '家居': 3, '教育': 4, '科技': 5, '时尚': 6, '时政': 7, '游戏': 8, '娱乐': 9}
3、把词表(vocab_path = os.path.join(root_path, ‘essay.vocab.txt’))里面每个词都用不同的数字标记,生成序列:
# 总的词表数据路径
vocab_path = os.path.join(root_path, 'essay.vocab.txt')
def read_vocab(vocab_path):
with open_file(vocab_path) as fp:
words = [word.strip() for word in fp.readlines()] # 因为这里用的是 python3,因此不需要对中文进行其他编码
word_to_id = dict(zip(words, range(len(words))))
return words, word_to_id
words,word_to_id = read_vocab(vocab_path)
print(words)
print(word_to_id)
输出:
词数量太多了这里指 放前面一部分输出数据
['<PAD>', ',', '的', '。', '一', '是', '在', '0', '有', '不', '了', '中', '1', '人',...]
{'<PAD>': 0, ',': 1, '的': 2, '。': 3, '一': 4, '是': 5, '在': 6, '0': 7, '有': 8,...}
这样就有了 word——>id 的映射。
4、对训练样本操作(后面的 验证样本和测试样本也是同样的操作):
#读取文件,并把文本 类型 和 内容分开
def read_file(filename):
contexts, labels = [], []
with open_file(filename) as f:
for line in f:
label, content = line.strip().split('\t')
contexts.append(list(content))
labels.append(label)
return contexts, labels
# 训练数据路径
train_path = os.path.join(root_path, 'essay.train.txt')
contexts, labels = read_file(train_path)
输出(由于一些数量太多 ,这边输出一些关键信息):
contexts:
[['马','晓','旭'...]
[...]
.
.
.
]
contexts一共有5000行,每一行是一篇文章的所有词;
labels:
['体育', '体育', '体育', '体育', '体育', '体育', '体育', '体育', '体育', '体育'...]
labels一共有5000行,每一行是一篇文章的类别;
contexts和labels是互相对应的。
# 每个文章提取max_length个词,并且把没个词转成word_to_id里的数值,把每个类别也转成label_to_id里对应的数值
def data_file(word_to_id, label_to_id, max_length,contexts,labels):
context_id, label_id = [], []
for i in range(len(contexts)):
context_id.append([word_to_id[x] for x in contexts[i] if x in word_to_id])
label_id.append(label_to_id[labels[i]])
#将文本设为固定长度
x_txt= kr.preprocessing.sequence.pad_sequences(data_id, max_length) # 将句子都变成max_length大小的句子,超过max_length的从后边开始数,去除前边的
y_lbl= kr.utils.to_categorical(label_id, num_classes=len(label_to_id)) # 将标签转换为one-hot表示
return x_txt, y_lbl
x_txt, y_lbl = data_file(word_to_id, label_to_id, max_length,contexts,labels)
x_txt:一共是5000行,每行都是数字,每个数字对应word_to_id里的词;
y_lbl:一共是5000行,每行有10个数,其中只有一个数字为1,其他都为0,1所在的那列,代表x_txt对应行文章的类别;
现在,数据特征以及提取完毕。
三、模型的搭建:
# 文本分类,CNN模型
class ContextClassifyByCNN():
def __init__(self):
# 三个待输入的数据
self.seqNum = 600 # self.seqNum 每个文章提取多少个词
self.classNum = 10 # self.seqNum 总的类别数
self.embedding_dim = 64 # 每个词的词向量大小
self.vocab_size = 5000 # 词表里词总个数
self.num_filters = 256 # 卷积核个数
self.kernel_size = 5 # 卷积核大小
self.hidden_dim = 128 # 全连接层神经元数量
self.input_x = tf.placeholder(tf.int32, [None, self.seqNum], name='input_x')
self.input_y = tf.placeholder(tf.float32, [None,self.classNum], name='input_y')
self.k_prob = tf.placeholder(tf.float32, name='k_prob ')#drop比例
self.cnnFun()
def cnnFun(self):
# 词向量映射
with tf.device('/cpu:0'):
self.embedding = tf.get_variable('embedding', [self.vocab_size, self.embedding_dim])
embedding_inputs = tf.nn.embedding_lookup(self.embedding, self.input_x)
with tf.name_scope("cnn"):
conv = tf.layers.conv1d(embedding_inputs, self.num_filters, self.kernel_size, name='conv')
# 池化层
gmp = tf.reduce_max(conv, reduction_indices=[1], name='gmp')
with tf.name_scope("score"):
# 全连接层,后面接dropout以及relu激活
fc = tf.layers.dense(gmp, self.hidden_dim, name='fc1')
fc = tf.contrib.layers.dropout(fc, self.k_prob )
fc = tf.nn.relu(fc)
# 分类器
self.logits = tf.layers.dense(fc, self.classNum, name='fc2')
self.y_pred_cls = tf.argmax(tf.nn.softmax(self.logits), 1) # 预测类别
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=0.001).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)) # tf.cast()将correct_pred 转化成tf.float32类型
四、建立模型和训练模型
# 载入训练集
x_txt, y_lbl = data_file(word_to_id, label_to_id, max_length,contexts,labels)
# 初始化model
model= ContextClassifyByCNN()
# 创建session
session = tf.Session()
session.run(tf.global_variables_initializer())
# 配置 Saver
saver = tf.train.Saver()
total_batch = 0 # 总批次
best_acc_val = 0.0 # 最佳验证集准确率
last_improved = 0 # 记录上一次提升批次
require_improvement = 1000 # 如果超过1000轮未提升,提前结束训练
flag = False
for epoch in range(10000):
print('Epoch:', epoch + 1)
batch_train = batch_iter(x_txt, y_lbl, 64)
for x_batch, y_batch in batch_train:
feed_dict=feed_dict = {
model.input_x: x_batch,
model.input_y: y_batch,
model.k_prob : 0.5
}
if total_batch % 100 == 0:
# 每多少轮次输出在训练集和验证集上的性能
feed_dict[model.k_prob ] = 1.0
loss_train, acc_train = session.run([model.loss, model.acc], feed_dict=feed_dict)
loss_val, acc_val = evaluate(session, x_val, y_val) # todo
if acc_val > best_acc_val:
# 保存最好结果
best_acc_val = acc_val
last_improved = total_batch
saver.save(sess=session, save_path=save_path)
session.run(model.optim, feed_dict=feed_dict) # 运行优化 真正开始运行,因为是相互依赖,倒着找的
total_batch += 1
if total_batch - last_improved > require_improvement or acc_val > 0.98:
# 验证集正确率长期不提升,提前结束训练
print("No optimization for a long time, auto-stopping...")
flag = True
break # 跳出循环
五、总结
整个文本分类一共分为2个部分:特征数据提取、模型建立和模型训练。还有一点:最终模型ContextClassifyByCNN里面的self.embedding,是训练出来的词表里词的词向量。