Một. Áp dụng TextCNN cho phân loại văn bản nhằm nhận diện ý định trong hệ thống hỏi-đáp.
Ba. Mã nguồn (Toàn bộ mã nguồn: https://github.com/jiangnanboy/movie_knowledge_graph_app/tree/master/intent_classification/pytorch/textcnn)
import os
import torch
from torchtext import data,datasets
from torchtext.data import Iterator, BucketIterator
from torchtext.vocab import Vectors
from torch import nn,optim
import torch.nn.functional as F
import pandas as pd
import pickle
THIET_BI = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
duong_dan_phan_loai_y_dinh = os.path.abspath(os.path.join(os.getcwd(), '../..'))
# Đường dẫn dữ liệu huấn luyện
du_lieu_huan_luyen = os.path.join(duong_dan_phan_loai_y_dinh,'classification_data/classification_data.csv')
# Đọc dữ liệu
du_lieu_huan_luyen = pd.read_csv(du_lieu_huan_luyen)
# Tách theo ký tự
ham_tach_tu =lambda x: x.split(' ')
VAN_BEN = data.Field(
sequential=True,
tokenize=ham_tach_tu,
lower=True,
use_vocab=True,
pad_token='<pad>',
unk_token='<unk>',
batch_first=True,
fix_length=20)
NHAN = data.Field(
sequential=False,
use_vocab=False)
# Lấy tập dữ liệu huấn luyện hoặc kiểm tra
def lay_tap_dieu(csv_data, text_field, label_field, kiem_tra=False):
truong_du_lieu = [('id', None), ('text', text_field), ('label', label_field)]
mau_vd = []
if kiem_tra: # Tập kiểm tra, không tải nhãn
for text in csv_data['text']:
mau_vd.append(data.Example.fromlist([None, text, None], truong_du_lieu))
else: # Tập huấn luyện
for text, label in zip(csv_data['text'], csv_data['label']):
mau_vd.append(data.Example.fromlist([None, text, label], truong_du_lieu))
return mau_vd, truong_du_lieu
mau_huan_luyen,truong_huan_luyen = lay_tap_dieu(du_lieu_huan_luyen, VAN_BEN, NHAN)
tap_huan_luyen = data.Dataset(mau_huan_luyen, truong_huan_luyen)
# Word embedding đã được huấn luyện trước
duong_dan_embedding = os.path.join(os.getcwd(), 'sgns.sogou.char')
vector = Vectors(name=duong_dan_embedding)
# Xây dựng từ điển
VAN_BEN.build_vocab(tap_huan_luyen, min_freq=1, vectors = vector)
duong_dan_tu = os.path.join(os.getcwd(), 'words.pkl')
with open(duong_dan_tu, 'wb') as f_tu:
pickle.dump(VAN_BEN.vocab, f_tu)
KICH_THUOC_LOP = 163
# Xây dựng iterator
lap_huan_luyen = BucketIterator(
dataset=tap_huan_luyen,
batch_size=KICH_THUOC_LOP,
shuffle=True,
sort_within_batch=False)
# Xây dựng mô hình phân loại
class MauTextCNN(nn.Module):
def __init__(self, kich_thuoc_tu_van_ben, chieu_embedding, so_luong_ra, so_luong_bo_loc=100, kich_thuoc_bo_loc=(3,4,5), ty_le_thoat=0.5):
'''
kich_thuoc_tu_van_ben: Kích thước từ điển
chieu_embedding: Kích thước embedding từ
so_luong_ra: Số lượng lớp đầu ra
so_luong_bo_loc: Số lượng kernel tích chập
kich_thuoc_bo_loc(3,4,5): Ba kích thước kernel tích chập, mỗi loại có so_luong_bo_loc kernel, chiều rộng kernel đều là chieu_embedding
'''
super(MauTextCNN, self).__init__()
self.embedding = nn.Embedding(kich_thuoc_tu_van_ben, chieu_embedding)
# conv2d(in_channel,out_channel,kernel_size,stride,padding),stride mặc định là 1, padding mặc định là 0
self.tich_chap = nn.ModuleList([nn.Conv2d(1, so_luong_bo_loc,(k, chieu_embedding)) for k in kich_thuoc_bo_loc])
self.thoat = nn.Dropout(ty_le_thoat)
self.tuyen_tinh = nn.Linear(so_luong_bo_loc * len(kich_thuoc_bo_loc), so_luong_ra)
'''
Phương thức forward với cách tính tích chập và pooling:
1. Tích chập
Công thức tính shape sau tích chập: np.floor((n + 2p - f)/s + 1)
Input shape:(batch, in_channel, hin, win) = (163, 1, 20, 300), 20 là độ dài câu, 300 là kích thước embedding
Output shape:
hout=(20 + 2 * 0 - 1 * (3 - 1) - 1)/1 + 1 = 18
wout=(300 + 2 * 0 - 1 * (300 - 1) -1)/1 + 1 = 1
=>
output:(batch, out_channel, hout, wout) = (163, 100, 18, 1)
2. MaxPool1d
Công thức đơn giản: np.floor((l + 2p - f)/s + 1)
Input shape:(N,C,L):(163, 100, 18, 1) -> squeeze(3) -> (163, 100, 18)
Output shape:
lout = (18 + 2*0 - 18)/18 +1 = 1 -> (163, 100, 1)
'''
def xuly_tien(self, x):
# x :(batch, seq_len) = (163, 20)
x = self.embedding(x) # [batch,word_num,embedding_dim] = [N,H,W] -> (163, 20, 300)
x = x.unsqueeze(1) # [batch, channel, word_num, embedding_dim] = [N,C,H,W] -> (163, 1, 20, 300)
x = [F.relu(conv(x)).squeeze(3) for conv in self.tich_chap] # len(filter_size) * (N, filter_num, H) -> 3 * (163, 100, 18)
# MaxPool1d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False),stride mặc định là kernal_size
x = [F.max_pool1d(output,output.shape[2]).squeeze(2) for output in x] # len(filter_size) * (N, filter_num) -> 3 * (163, 100)
x = torch.cat(x, 1) # (N, filter_num * len(filter_size)) -> (163, 100 * 3)
x = self.thoat(x)
x = self.tuyen_tinh(x)
return x
from torch.utils.tensorboard import SummaryWriter
ghi_chu = SummaryWriter(os.getcwd()+'/log', comment='textcnn')
# Huấn luyện
# Xây dựng model
model = MauTextCNN(len(VAN_BEN.vocab),VAN_BEN.vocab.vectors.shape[1],16).to(THIET_BI)
# Sử dụng embedding đã huấn luyện trước để khởi tạo, requires_grad=True để fine-tune
model.embedding.weight.data.copy_(VAN_BEN.vocab.vectors)
# Chế độ huấn luyện
model.train()
# Tối ưu và hàm mất mát
#optimizer = torch.optim.Adam(model.parameters(),lr=0.1, weight_decay=0.01)
optimizer = torch.optim.SGD(model.parameters(),lr=0.1, momentum=0.9, nesterov=True)
ham_mat_mat = nn.CrossEntropyLoss()
for vong_lap in range(300):
for i, batch in enumerate(lap_huan_luyen):
van_ben_mau = batch.text
nhan_mau = batch.label
van_ben_mau = van_ben_mau.to(THIET_BI)
nhan_mau = nhan_mau.to(THIET_BI)
ket_qua = model.xuly_tien(van_ben_mau)
mat_mat = ham_mat_mat(ket_qua, nhan_mau)
optimizer.zero_grad()
mat_mat.backward()
optimizer.step()
if (vong_lap+1) % 10 == 0:
print ('vong_lap [{}/{}], Loss: {:.4f}'.format(vong_lap+1, 300, mat_mat.item()))
ghi_chu.add_scalar('loss',mat_mat.item(),global_step=vong_lap+1)
ghi_chu.flush()
ghi_chu.close()
duong_dan_model = os.path.join(os.getcwd(), "model.h5")
torch.save(model.state_dict(), duong_dan_model)