Skip to content

Commit 20fe3cd

Browse files
committed
Albert model
1 parent f37c1a1 commit 20fe3cd

6 files changed

+539
-0
lines changed

AlbertRun.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from sklearn.model_selection import StratifiedKFold
2+
import torch.optim as optim
3+
from utils.loss import loss
4+
import torch
5+
import pandas as pd
6+
from AlbertTweetModel import AlbertTweetModel
7+
from AlbertTrainModel import albert_train_model
8+
from utils.albert_get_train_val_loaders import albert_get_train_val_loaders
9+
10+
num_epochs = 3
11+
batch_size = 1
12+
seed_value = 28091997
13+
14+
torch.cuda.manual_seed(seed_value)
15+
torch.cuda.manual_seed_all(seed_value)
16+
torch.backends.cudnn.deterministic = True
17+
torch.backends.cudnn.benchmark = True
18+
skl = StratifiedKFold(n_splits=5, shuffle=True, random_state=seed_value)
19+
20+
train_df = pd.read_csv('./data/train.csv')
21+
train_df['text'] = train_df['text'].astype(str)
22+
train_df['selected_text'] = train_df['selected_text'].astype(str)
23+
24+
for fold, (train_idx, val_idx) in enumerate(skl.split(train_df, train_df.sentiment), start=1):
25+
print("========== Fold {} ========== ".format(fold))
26+
model = AlbertTweetModel()
27+
optimizer = optim.AdamW(model.parameters(), lr=3e-5, betas=(0.9, 0.999))
28+
loss = loss
29+
dataloader_dict = albert_get_train_val_loaders(
30+
train_df, train_idx, val_idx, batch_size)
31+
albert_train_model(
32+
model,
33+
dataloader_dict,
34+
loss,
35+
optimizer,
36+
num_epochs,
37+
f'./weights/albert/albert_fold_{fold}.bin'
38+
)

AlbertTrainModel.py

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import torch
2+
from utils.compute_jaccard_score import compute_jaccard_score
3+
import tqdm
4+
5+
6+
def albert_train_model(model, dataloaders_dict, loss, optimizer, num_epochs, filename):
7+
model.cuda()
8+
9+
for epoch in range(num_epochs):
10+
# Mỗi epoch sẽ thực hiện 2 phase
11+
for phase in ['train', 'val']:
12+
# Nếu phase train thì huấn luyện, phase val thì tính loss và jaccard
13+
if phase == 'train':
14+
model.train()
15+
else:
16+
model.eval()
17+
18+
# Khởi tạo loss và jaccard
19+
epoch_loss = 0.0
20+
epoch_jaccard = 0.0
21+
22+
for data in tqdm.tqdm((dataloaders_dict[phase])):
23+
# Lấy thông tin dữ liệu
24+
ids = data['ids'].cuda()
25+
masks = data['masks'].cuda()
26+
tweet = data['tweet']
27+
offsets = data['offsets'].numpy()
28+
token_type_id = data['token_type_ids'].cuda()
29+
start_idx = data['start_idx'].cuda()
30+
end_idx = data['end_idx'].cuda()
31+
32+
# Reset tích lũy đạo hàm
33+
optimizer.zero_grad()
34+
35+
with torch.set_grad_enabled(phase == 'train'):
36+
start_logits, end_logits = model(ids, masks, token_type_id)
37+
loss_value = loss(
38+
start_logits, end_logits, start_idx, end_idx)
39+
40+
# nếu là phase train thì thực hiện lan truyền ngược
41+
# và cập nhật tham số
42+
if phase == 'train':
43+
loss_value.backward()
44+
optimizer.step()
45+
46+
epoch_loss += loss_value.item() * len(ids)
47+
48+
start_idx = start_idx.cpu().detach().numpy()
49+
end_idx = end_idx.cpu().detach().numpy()
50+
51+
start_logits = torch.softmax(
52+
start_logits, dim=1).cpu().detach().numpy()
53+
end_logits = torch.softmax(
54+
end_logits, dim=1).cpu().detach().numpy()
55+
56+
# Tính toán jaccard cho tất cả các câu
57+
for i in range(len(ids)):
58+
jaccard_score = compute_jaccard_score(
59+
tweet[i],
60+
start_idx[i],
61+
end_idx[i],
62+
start_logits[i],
63+
end_logits[i],
64+
offsets[i]
65+
)
66+
epoch_jaccard += jaccard_score
67+
68+
# Trung bình loss và jaccard
69+
epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
70+
epoch_jaccard = epoch_jaccard / \
71+
len(dataloaders_dict[phase].dataset)
72+
73+
print("Epoch {}/{} | {:^5} | Loss: {:.4f} | Jaccard: {:.4f}".format(epoch +
74+
1, num_epochs, phase, epoch_loss, epoch_jaccard))
75+
torch.save(model.state_dict(), filename)

AlbertTweetDataset.py

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import torch
2+
import pandas as pd
3+
import transformers
4+
import sentencepiece as spm
5+
from utils import sentencepiece_pb2
6+
7+
8+
class OffsetTokenizer():
9+
def __init__(self, path_model):
10+
self.spt = sentencepiece_pb2.SentencePieceText()
11+
self.sp = spm.SentencePieceProcessor(model_file=path_model)
12+
13+
def encode(self, text, lower=True):
14+
if lower:
15+
text = text.lower()
16+
offset = []
17+
ids = []
18+
self.spt.ParseFromString(self.sp.encode_as_serialized_proto(text))
19+
20+
for piece in self.spt.pieces:
21+
offset.append((piece.begin, piece.end))
22+
ids.append(piece.id)
23+
24+
return {"token": self.sp.encode_as_pieces(text), "ids": ids, "offsets": offset}
25+
26+
27+
class AlbertTweetDataset(torch.utils.data.Dataset):
28+
def __init__(self, df, max_len=128):
29+
# Dataframe dữ liệu
30+
self.df = df
31+
# Độ dài tối đa của câu
32+
self.max_len = max_len
33+
# Nhãn
34+
self.labeled = 'selected_text' in df
35+
# Khởi tạo mã hóa SentencePiece cho Albert
36+
self.tokenizer = OffsetTokenizer(
37+
path_model='./albert.torch/albert-large-v2/spiece.model')
38+
39+
# self.sp = spm.SentencePieceProcessor(
40+
# model_file='./albert.torch/albert-large-v2/spiece.model')
41+
# self.spt = sentencepiece_pb2.SentencePieceText()
42+
43+
# ========= TEST CODE =======
44+
# self.tokenizer = transformers.AlbertTokenizer.from_pretrained(
45+
# './albert.torch/albert-large-v2/spiece.model',
46+
# do_lower_case=True)
47+
# print(self.tokenizer.tokenize(" Nguyen Duc Thang"))
48+
# print(self.tokenizer.encode("Nguyen Duc Thang"))
49+
# print(self.tokenizer.decode([2, 20449, 13, 8484, 119, 263, 3, 0]))
50+
# print(self.spt.ParseFromString(
51+
# self.sp.encode_as_serialized_proto("Nguyen Duc Thang".lower())))
52+
# offset = []
53+
# ids = []
54+
55+
# for piece in self.spt.pieces:
56+
# offset.append((piece.begin, piece.end))
57+
# ids.append(piece.id)
58+
# print(ids)
59+
# print(offset)
60+
# ======= END TEST =======
61+
62+
def __len__(self):
63+
""" Trả về độ dài của DataFrame """
64+
return len(self.df)
65+
66+
def get_input_data(self, row):
67+
"""
68+
Tạo sample input cho 1 dòng dữ liệu
69+
- Input: [CLS] <sentiment>[SEP]token11 token12 ... [SEP][pad][pad]
70+
"""
71+
# Thêm khoảng trắng vào đầu câu đầu vào
72+
tweet = " " + " ".join(row.text.lower().split())
73+
# Mã hóa câu đầu vào
74+
encoding = self.tokenizer.encode(tweet)
75+
# Mã hóa sentiment
76+
sentiment_id = self.tokenizer.encode(row.sentiment)["ids"]
77+
# 2 là CLS, 3 là SEP, 0 là <pad>
78+
ids = [2] + sentiment_id + [3] + encoding["ids"] + [3]
79+
# token type ids
80+
token_type_ids = [0] * 3 + [1] * (len(encoding["ids"]) + 1)
81+
# offset là vị trí các token của câu ban đầu
82+
offsets = [(0, 0)] * 3 + encoding["offsets"] + [(0, 0)]
83+
84+
# Thêm các token pad cho viền
85+
pad_len = self.max_len - len(ids)
86+
if pad_len > 0:
87+
ids += [0] * pad_len
88+
offsets += [(0, 0)] * pad_len
89+
token_type_ids += [0] * pad_len
90+
ids = torch.tensor(ids)
91+
# Tạo mặt nạ attention, đánh dấu 1 cho toàn bộ câu đầu vào
92+
# Trừ các phần là <pad>
93+
masks = torch.where(ids != 1, torch.tensor(1), torch.tensor(0))
94+
offsets = torch.tensor(offsets)
95+
token_type_ids = torch.tensor(token_type_ids)
96+
97+
return ids, masks, tweet, offsets, token_type_ids
98+
99+
def get_target_idx(self, row, tweet, offsets):
100+
selected_text = " " + " ".join(row.selected_text.lower().split())
101+
102+
len_st = len(selected_text) - 1
103+
# Vị trí bắt đầu và kết thúc của selectec_text trong tweet
104+
idx0, idx1 = None, None
105+
106+
for ind in (i for i, e in enumerate(tweet) if e == selected_text[1]):
107+
if " " + tweet[ind:ind+len_st] == selected_text:
108+
idx0 = ind
109+
idx1 = ind + len_st - 1
110+
111+
# Đánh dấu những vị trí mà có ký tự của selected_text là 1
112+
char_targets = [0] * len(tweet)
113+
if idx0 != None and idx1 != None:
114+
for ct in range(idx0, idx1 + 1):
115+
char_targets[ct] = 1
116+
117+
# Đánh dấu những token chứa selected_text
118+
target_idx = []
119+
for j, (offset1, offset2) in enumerate(offsets):
120+
if sum(char_targets[offset1:offset2]) > 0:
121+
target_idx.append(j)
122+
123+
# Token bắt đầu và token kết thúc của selected_text
124+
start_idx = target_idx[0]
125+
end_idx = target_idx[-1]
126+
127+
return start_idx, end_idx
128+
129+
def __getitem__(self, index):
130+
"""
131+
Chuyển đổi hàng dữ liệu thứ index trong dataFrame
132+
sang dữ liệu đầu vào của mô hình
133+
Các thuộc tính cho dữ liệu đầu vafo:
134+
- ids
135+
- masks
136+
- tweet
137+
- offsets
138+
- token_type_ids
139+
- start_idx
140+
- end_idx
141+
"""
142+
data = {}
143+
row = self.df.iloc[index]
144+
145+
ids, masks, tweet, offsets, token_type_ids = self.get_input_data(row)
146+
data['ids'] = ids
147+
data['masks'] = masks
148+
data['tweet'] = tweet
149+
data['offsets'] = offsets
150+
data['token_type_ids'] = token_type_ids
151+
152+
if self.labeled:
153+
start_idx, end_idx = self.get_target_idx(row, tweet, offsets)
154+
data['start_idx'] = start_idx
155+
data['end_idx'] = end_idx
156+
157+
return data

AlbertTweetModel.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from torch import nn
2+
import torch
3+
from transformers import AlbertConfig, AlbertModel
4+
5+
class AlbertTweetModel(nn.Module):
6+
def __init__(self):
7+
super(AlbertTweetModel, self).__init__()
8+
config = AlbertConfig.from_pretrained(
9+
'./albert.torch/albert-large-v2/config.json',
10+
output_hidden_states=True
11+
)
12+
self.bert = AlbertModel.from_pretrained(
13+
'./albert.torch/albert-large-v2/pytorch_model.bin',
14+
config=config
15+
)
16+
17+
self.dropout = nn.Dropout(0.5)
18+
self.fc = nn.Linear(config.hidden_size, 2)
19+
nn.init.normal_(self.fc.weight, std=0.2)
20+
nn.init.normal_(self.fc.bias, 0)
21+
22+
def forward(self, input_ids, attention_mask, token_type_ids):
23+
# Đầu vào Roberta cần chỉ số các token (input_ids)
24+
# Và attention_mask (Mặt nạ biểu diễn câu 0 = pad, 1 = otherwise)
25+
# Và token_type_ids
26+
_, _, hs = self.bert(input_ids, attention_mask, token_type_ids)
27+
28+
# len(hs) = 13 tensor, mỗi tensor shape là (1, 128, 768)
29+
x = torch.stack([hs[-1], hs[-2], hs[-3], hs[-4]])
30+
# x shape (4,1,128,768)
31+
x = torch.mean(x, 0)
32+
# x shape (1,128,768)
33+
x = self.dropout(x)
34+
x = self.fc(x)
35+
# x shape (1,128,2)
36+
start_logits, end_logits = x.split(1, dim=-1)
37+
38+
# Nếu số chiều cuối là 1 thì bỏ đi (1,128,1) -> (1,128)
39+
# Ví dụ (AxBxCX1) --> size (AxBxC)
40+
start_logits = start_logits.squeeze(-1)
41+
end_logits = end_logits.squeeze(-1)
42+
43+
return start_logits, end_logits

utils/albert_get_train_val_loaders.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import torch
2+
from AlbertTweetDataset import AlbertTweetDataset
3+
4+
5+
def albert_get_train_val_loaders(df, train_idx, val_idx, batch_size=8):
6+
"""Chia dữ liệu train và val
7+
8+
Args:
9+
df (DataFrame): Dataframe dữ liệu
10+
train_idx (list): Danh sách chỉ số cho tập train
11+
val_idx (list): Danh sách chỉ số cho tập val
12+
batch_size (int, optional): Batchsize cho mô hình. Defaults to 8.
13+
"""
14+
train_df = df.iloc[train_idx]
15+
val_df = df.iloc[val_idx]
16+
17+
train_loader = torch.utils.data.DataLoader(
18+
AlbertTweetDataset(train_df),
19+
batch_size=batch_size,
20+
shuffle=True,
21+
num_workers=2
22+
)
23+
24+
val_loader = torch.utils.data.DataLoader(
25+
AlbertTweetDataset(val_df),
26+
batch_size=batch_size,
27+
shuffle=True,
28+
num_workers=2
29+
)
30+
31+
dataloader_dict = {"train": train_loader, "val": val_loader}
32+
33+
return dataloader_dict

0 commit comments

Comments
 (0)