Hướng dẫn tạo chatGPT: xây dựng AI Chatbot với Transformer
1. Giới thiệu
1. 1 Transformer
Transformer là một kiến trúc mô hình mang tính đột phá trong lĩnh vực trí tuệ nhân tạo (AI), đặc biệt trong xử lý ngôn ngữ tự nhiên (NLP). Được giới thiệu bởi Vaswani và các cộng sự trong bài báo “Attention is All You Need” năm 2017, Transformer đã nhanh chóng trở thành nền tảng cho nhiều tiến bộ trong AI hiện đại.
Trước khi Transformer xuất hiện, các mô hình như RNN (Recurrent Neural Networks) và LSTM (Long Short-Term Memory) là trụ cột trong NLP. Tuy nhiên, những mô hình này gắp khó khăn trong việc xử lý các dữ liệu dài hơn do tính tuần tực (sequential processing). Transformer khác biệt vì nó khai thác cơ chế attention, cho phép mô hình tính toán song song, nhanh hơn và hiệu quả hơn.
1.2. Các yếu tố cốt lõi của Transformer
Self-Attention Mechanism:
- Các token trong dữ liệu đầu vào có khả năng “chú ý” (đánh trọng số) lẫn nhau, cho phép mô hình hiểu được mối quan hệ ngược giữa các từ hoặc câu trong bài viết.
- Khác với RNN có tính tuần tực, Transformer xử lý toàn bộ dữ liệu đầu vào cùng một lúc, giúp tiết kiệm thời gian.
Kiến trúc Encoder-Decoder:
- Encoder: Mã hóa dữ liệu đầu vào thành các biểu diễn vector.
- Decoder: Sinh dữ liệu đầu ra dựa trên biểu diễn đó.
Position Embedding:
- Transformer không xử lý dữ liệu theo tự nhiên như RNN, vì vậy nó sử dụng position embedding để giữ ngữ cảnh.
1.3. Ứng dụng nổi bật của Transformer
ChatGPT được xây dựng dựa trên mô hình ngôn ngữ GPT (Generative Pre-trained Transformer), một loại mô hình học sâu sử dụng kiến trúc Transformer. Các mô hình GPT được huấn luyện theo cách tiền huấn luyện (pre-training) và học tập qua sự điều chỉnh (fine-tuning). Quá trình tiền huấn luyện liên quan đến việc học từ một lượng lớn văn bản để dự đoán từ tiếp theo trong câu, giúp mô hình học được các cấu trúc ngữ pháp, ngữ nghĩa và kiến thức thế giới. Nhờ khả năng xử lý ngôn ngữ mạnh mẽ, ChatGPT có thể trò chuyện tự nhiên và hiểu được ngữ cảnh trong các cuộc hội thoại.
2. Mục tiêu của bài viết
Bạn đã từng tự hỏi liệu mình có thể tạo ra một chatbot AI giống như ChatGPT không? Bài viết này sẽ hướng dẫn bạn từng bước để xây dựng một chương trình chatbot riêng, tạm gọi là YourGPT. Mặc dù chưa thể đạt được độ chính xác và tự nhiên như ChatGPT, YourGPT vẫn có thể trả lời các câu hỏi dựa trên những kiến thức mà nó được học.
Với YourGPT, khi bạn đưa vào một số từ khóa, chương trình sẽ đưa ra một số từ tiếp theo phù hợp với từ đầu vào.
3. Dữ liệu
Tại sao chuẩn bị dữ liệu lại quan trọng?
Hình dung việc chuẩn bị dữ liệu giống như xây dựng nền móng cho một tòa nhà. Một nền móng yếu kém sẽ dẫn đến toàn bộ công trình bị lung lay. Tương tự, dữ liệu kém chất lượng sẽ làm giảm đáng kể hiệu suất của mô hình AI của bạn.
Việc thu thập dữ liệu không phải chỉ đơn giản là tải về hàng loạt. Bạn cần những nguồn dữ liệu:
- Chính xác và đáng tin cậy
- Phù hợp với mục tiêu cụ thể của dự án
- Đại diện cho các tình huống thực tế bạn muốn mô hình giải quyết
Các nguồn tốt nhất thường đến từ:
- Tài liệu học thuật từ các trường đại học.
- Báo cáo nghiên cứu khoa học
- Các nguồn dữ liệu được gán nhãn bởi chuyên gia
Làm sạch dữ liệu: loại bỏ “rác” thông tin
Không phải tất cả dữ liệu đều có giá trị. Quá trình làm sạch là một nghệ thuật:
- Loại bỏ các bản ghi trùng lặp
- Wipe out dữ liệu nhiễu
- Chuẩn hóa định dạng văn bản
- Kiểm tra và sửa lỗi chính tả
Hãy nghĩ về việc này như việc chọn lọc những viên ngọc quý từ một đống đá vụn.
Trong bài thực hành này, tôi sử dụng bộ dữ liệu aicandy_llm_dataset_qmvqmhro.txt bao gồm nhiều bộ câu văn hoàn chỉnh và có ý nghĩa, được sử dụng thực tế trong cuộc sống, ví dụ như phía dưới:
Sự kiên nhẫn là chìa khóa mở ra cánh cửa của thành công.
Hạnh phúc không phải là đích đến, mà là hành trình ta đang đi.
Khi bạn ngừng so sánh, bạn bắt đầu tỏa sáng.
Mỗi ngày là một cơ hội mới để bắt đầu lại từ đầu.
Học hỏi từ thất bại là cách nhanh nhất để trưởng thành.
Người mạnh mẽ không phải là người không bao giờ ngã, mà là người luôn đứng dậy.
Tương lai của bạn phụ thuộc vào những gì bạn làm hôm nay.
Đôi khi, im lặng là câu trả lời mạnh mẽ nhất.
Bộ dataset này có thể download miễn phí tại đây
4. Mô hình
Để phù hợp với bài hướng dẫn và với tài nguyên GPU, chúng ta sẽ xây dựng mạng MicroLLM cơ bản và vẫn theo cấu trúc của transformer.
4.1. PositionalEncoding
Lớp PositionalEncoding sử dụng tính toán Sin cho chiều chẵn, Cos cho chiều lẻ, đảm bảo vị trí được mã hóa độc nhất theo cách dễ phân biệt với công thức:
\[ \text{pos}[i, 2j] = \sin(\text{position} \times \text{div}\text{_}\text{term}[j]) \]
\[ \text{pos}[i, 2j+1] = \cos(\text{position} \times \text{div}\text{_}\text{term}[j]) \]
\[ \text{div}\text{_}\text{term} = e^{-\frac{\log(10000)}{d\text{_}\text{model}} \times j} \]
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.positional_encoding = PositionalEncoding(embedding_dim, dropout=dropout)
Lớp Embedding chuyển đổi các token (dạng số nguyên) thành vector có số chiều là embedding_dim. Mỗi token sẽ được biểu diễn bằng một vector có độ dài embedding_dim
self.positional_encoding: Thêm thông tin về vị trí cho từng token trong câu. Sử dụng lớp PositionalEncoding để giúp Transformer hiểu thứ tự vị trí của các từ trong chuỗi, thêm thông tin vị trí vào mỗi token embedding. Điều này rất quan trọng vì Transformer không có khả năng xử lý vị trí tự nhiên như các mô hình tuần tự khác.
4.2. Encoder_layer và Decoder_layer
Trong mô hình Transformer, encoder layer và decoder layer là hai thành phần chính cấu thành nên các lớp mã hóa và giải mã của Transformer.
Với encode_layer:
encoder_layer = nn.TransformerEncoderLayer(
d_model=embedding_dim,
nhead=num_heads,
dim_feedforward=embedding_dim * 4,
dropout=dropout,
batch_first=True,
activation='gelu'
)
d_model=embedding_dim: Định nghĩa kích thước của vector đầu vào/đầu ra của mô hình, bằng với kích thước embedding đã định nghĩa trước đó. Đây là số chiều mà mô hình sẽ sử dụng xuyên suốt quá trình xử lý.
nhead=num_heads: Xác định số lượng heads trong cơ chế Multi-Head Attention. Mỗi head sẽ học các mối quan hệ khác nhau giữa các token, Vector embedding sẽ được chia đều cho các heads.
dim_feedforward=embedding_dim * 4: Kích thước của hidden layer trong feed-forward network. Thường được set bằng 4 lần embedding_dim theo kiến trúc transformer gốc.
dropout=dropout: Tỉ lệ dropout áp dụng cho các lớp trong encoder, giúp tránh overfitting trong quá trình huấn luyện. Được áp dụng sau attention và feed-forward networks.
batch_first=True: Chỉ định format của tensor đầu vào.
activation=’gelu’: Hàm kích hoạt sử dụng trong feed-forward network, GELU (Gaussian Error Linear Unit) thường được dùng trong các mô hình transformer hiện đại.
Với decoder_layer:
decoder_layer = nn.TransformerDecoderLayer(
d_model=embedding_dim,
nhead=num_heads,
dim_feedforward=embedding_dim * 4,
dropout=dropout,
batch_first=True,
activation='gelu'
)
d_model=embedding_dim: Xác định kích thước vector của mô hình phải giống với kích thước embedding của encoder và được sử dụng xuyên suốt trong tất cả các sublayers.
nhead=num_heads: Số lượng heads trong cả self-attention và cross-attention, mỗi head học một khía cạnh khác nhau của mối quan hệ giữa các tokens.
dim_feedforward=embedding_dim * 4: Kích thước của hidden layer trong feed-forward network, thường gấp 4 lần d_model theo thiết kế gốc của transformer.
dropout=dropout: Tỉ lệ dropout áp dụng cho các lớp trong decoder, giúp giảm overfitting khi huấn luyện.
batch_first=True: Format của tensor đầu vào là (batch, sequence_length, feature_dim), thuận tiện khi làm việc với PyTorch và các framework hiện đại.
activation=’gelu’: Sử dụng hàm kích hoạt GELU trong feed-forward network. GELU là lựa chọn phổ biến trong các mô hình transformer hiện đại.
4.3. Mask
Mask là một cơ chế quan trọng trong transformer để đảm bảo tính tuần tự trong quá trình sinh văn bản và giúp mô hình học được các dependency một chiều phù hợp với ngôn ngữ tự nhiên. Mask giúp ngăn decoder nhìn thấy các token trong tương lai khi sinh ra token hiện tại.
Trong quá trình encode, không cần mask vì nó có thể nhìn thấy toàn bộ chuỗi đầu vào.
Trong quá trình decoder, sử dụng mask để giúp mô hình học cách dự đoán token tiếp theo dựa trên các token trước đó.
mask = (torch.triu(torch.ones(sz, sz)) == 1).transpose(0, 1)
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
torch.triu: Tạo ma trận tam giác trên (upper triangular matrix)
float(‘-inf’): Các vị trí bị mask sẽ có giá trị âm vô cùng.
float(0.0): Các vị trí được phép attention có giá trị 0.
5. Chương trình train
Bước 1: Tải dataset
Tạo hàm getSentencesFromFile để tải dữ liệu trong tệp và trả về list các câu (sentence).
with open(filePath, "r", encoding='utf-8') as file:
sentences = [line.strip() for line in file.readlines()]
return sentences
Tạo hàm getVocabFromSentences để tạo ra một từ điển chuẩn hóa từ dữ liệu văn bản thô.
for sentence in sentences:
words = sentence.split()
vocab_set.update(word.lower() for word in words)
vocab_list = sorted(list(vocab_set))
Quá trình thực hiện gồm duyệt qua từng câu trong danh sách câu, Tách câu thành các từ dựa trên khoảng trắng, dùng word.lower() để chuyển mỗi từ về dạng chữ thường và cuối cùng là sắp xếp các từ theo thứ tự alphabet.
word_to_id = {word: idx for idx, word in enumerate(vocab)}: Để chuyển từ thành số
id_to_word = {idx: word for idx, word in enumerate(vocab)}: để chuyển số thành từ
Với các câu ngắn, để đảm bảo tất cả các câu có cùng độ dài (đây là yêu cầu trong quá trình tính toán khi train), ta cần thêm padding (số 0) vào cuối các câu ngắn.
input_seq = input_seq + [0] * (max_len - len(input_seq))
target_seq = target_seq + [0] * (max_len - len(target_seq))
input_tensor = torch.tensor(input_seq, dtype=torch.long).unsqueeze(0).to(device)
target_tensor = torch.tensor(target_seq, dtype=torch.long).unsqueeze(0).to(device)
Và sau đó, chuyển các cặp câu đầu vào và đầu ra (target) về dạng tensor để phục vụ tính toán training.
Bước 2: Tải model
Quá trình train model LLM cần rất nhiều thời gian, trong quá trình train, có thể bị dừng đột ngột, vì thế chương trình cần hỗ trợ tiếp tục train từ trạng thái đã lưu trước đó. Lựa chọn lưu model theo dạng checkpoint để lưu giữ các thông tin, trạng thái của model phục vụ tiếp tục train.
Bước vào train, kiểm tra xem đã có checkpoint chưa, nếu chưa có thì sẽ tạo mới model, nếu đã có thì tải thông tin model từ checkpoint để tiếp tục train.
checkpoint = torch.load(filename, map_location=device, weights_only=True)
# Check that required keys exist in the checkpoint
required_keys = ['word_to_id', 'id_to_word', 'vocab', 'vocab_size',
'model_state_dict', 'optimizer_state_dict', 'epoch', 'loss']
for key in required_keys:
if key not in checkpoint:
raise KeyError(f"Key '{key}' not found in the checkpoint")
word_to_id = checkpoint['word_to_id']
id_to_word = checkpoint['id_to_word']
vocab = checkpoint['vocab']
vocab_size = checkpoint['vocab_size']
model.load_state_dict(checkpoint['model_state_dict'])
model.to(device)
Chương trình sẽ tìm và tải các key quan trọng trong model như word_to_id, id_to_word, vocab, vocab_size, model_state_dict, optimizer_state_dict, epoch, loss.
Bước 3: Train
Trong quá trình train cần đưa model vào chế độ train() để kích hoạt cơ chế dropout và Batch Normalization. (Dropout: Một phần của các node trong mạng sẽ ngẫu nhiên bị “bỏ qua”, Batch Normalization: Tính toán thống kê (mean, variance) dựa trên từng batch thay vì toàn bộ tập dữ liệu).
for src, tgt in training_data: Tạo Vòng lặp để duyệt qua từng batch trong tập dữ liệu huấn luyện (training_data). Trong mỗi vòng này thực hiện:
optimizer.zero_grad(): Reset gradient về 0 trước mỗi batch
model(src, tgt): Forward pass qua mô hình
loss = criterion(output.view(–1, output.size(–1)), tgt.view(–1)): Tính toán loss
if torch.isnan(loss) or torch.isinf(loss): Kiểm tra các loss bất thường, nếu có bất thường thì bỏ qua, không cập nhật weights
loss.backward(): Tính gradient
clip_grad_norm_: Giới hạn gradient để tránh gradient explosion
optimizer.step(): Cập nhật weights dựa trên gradient
scheduler.step(avg_loss): Điều chỉnh learning rate dựa trên loss, thường dùng để giảm learning rate khi loss không giảm.
Dưới dây là log train thực tế trong bài này:
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe# python AIcandy_LLM_Transformer_hkosbvui.py --mode train
Training on device: cuda
Epoch [1/15], Loss: 6.0512, Learning Rate: 0.0003
Epoch: 1, saved checkpoint at checkpoints/checkpoint.pt, loss: 6.051231508255005
Epoch [2/15], Loss: 5.7744, Learning Rate: 0.0003
Epoch: 2, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.774354704856872
Epoch [3/15], Loss: 5.6661, Learning Rate: 0.0003
Epoch: 3, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.6660626544952395
Epoch [4/15], Loss: 5.6510, Learning Rate: 0.0003
Epoch: 4, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.651025490760803
Epoch [5/15], Loss: 5.6516, Learning Rate: 0.0003
Epoch [6/15], Loss: 5.5861, Learning Rate: 0.0003
Epoch: 6, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.586063185691834
Epoch [7/15], Loss: 5.5738, Learning Rate: 0.0003
Epoch: 7, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.573755222320557
Epoch [8/15], Loss: 5.5501, Learning Rate: 0.0003
Epoch: 8, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.550085548400879
Epoch [9/15], Loss: 5.5398, Learning Rate: 0.0003
Epoch: 9, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.53977979850769
Epoch [10/15], Loss: 5.5083, Learning Rate: 0.0003
Epoch: 10, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.508275399208069
Epoch [11/15], Loss: 5.4954, Learning Rate: 0.0003
Epoch: 11, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.495386622428894
Epoch [12/15], Loss: 5.4803, Learning Rate: 0.0003
Epoch: 12, saved checkpoint at checkpoints/checkpoint.pt, loss: 5.480328410148621
Epoch [13/15], Loss: 5.4821, Learning Rate: 0.0003
Epoch [14/15], Loss: 5.7278, Learning Rate: 0.0003
Epoch [15/15], Loss: 5.8890, Learning Rate: 0.0003
Model training completed!
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe#
Trong log trên, chúng ta thấy loss còn cao, nguyên nhân là model chúng ta xây dựng là microLLM, một mô hình thu nhỏ trong transformer, hơn nữa dữ liệu chúng ta sử dụng rất nhỏ so với dữ liệu thực tế cần cho các model LLM.
Sau khi train thành công sẽ có tệp checkpoint tại:
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe# ls checkpoints/
checkpoint.pt
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe#
6. Chương trình test
Xây dựng chương trình test với tham số đầu vào là tệp checkpoint, các từ đầu tiên trong câu và số lượng từ giới hạn trong câu, chương trình sẽ tạo ra các từ tiếp theo dựa theo từ đầu vào.
Bước 1: Text input
Bước này sẽ nhận danh sách các input key
parser.add_argument('--input',
type=str,
default=None,
help='Input sentence to test (only applicable when in test mode))')
args = parser.parse_args()
test_sentences = [args.input]
Bước 2: Tải model
Bước này sẽ tải model cùng khởi tạo các tham số lớp như khi chúng ta training. Sau đó đưa model vào chế độ đánh giá.
model.load_state_dict(checkpoint['model_state_dict'])
model.eval()
Bước 3: generate_sentence
Chú ý tắt việc tính gradient. Điều này giúp tiết kiệm bộ nhớ và tăng tốc độ vì quá trình sinh văn bản không cần backpropagation (lan truyền ngược). Đây là chế độ phù hợp khi sử dụng mô hình chỉ để dự đoán hoặc sinh dữ liệu (inference).
Chuyển đổi văn bản đầu vào thành các token, sau đó chuyển sang dạng tensor:
input_tokens = [word_to_id.get(word.lower(), 0) for word in input_text.split()]
tgt_tensor = torch.tensor(output_tokens, dtype=torch.long).unsqueeze(0).to(device)
Dự đoán token tiếp theo và tính xác suất để chọn token tiếp theo phù hợp
output = model(input_tensor, tgt_tensor)
next_token_logits = output[0, -1, :]
probs = torch.softmax(next_token_logits / temperature, dim=0)
next_token = torch.multinomial(probs, 1).item()
Dừng sinh từ tiếp theo nếu đã đạt số lượng từ tối đa hoặc token vừa sinh là token kết thúc câu.
if next_token == word_to_id.get('.', -1) or len(output_tokens) > max_length:
break
Và cuối cùng là chuyển đổi token thành từ và trả ra văn bản.
Okey, giờ hãy thử nghiệm với model YourGPT nào!
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe# python AIcandy_LLM_Transformer_hkosbvui.py --mode test --input "Cuộc sống"
Training on device: cuda
Starting sentence generation testing:
--------------------------------------------------
Input: 'Cuộc sống'
Generated sentence: 'cuộc sống chân công là không ngừng có đến cơ để'
--------------------------------------------------
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe# python AIcandy_LLM_Transformer_hkosbvui.py --mode test --input "Thành công"
Training on device: cuda
Starting sentence generation testing:
--------------------------------------------------
Input: 'Thành công'
Generated sentence: 'thành công nghĩa tử bạn có một lấy của người bạn'
--------------------------------------------------
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe# python AIcandy_LLM_Transformer_hkosbvui.py --mode test --input "Cuộc sống là một bài hát"
Training on device: cuda
Starting sentence generation testing:
--------------------------------------------------
Input: 'Cuộc sống là một bài hát'
Generated sentence: 'cuộc sống là một bài "không" đến việc bạn những điều'
--------------------------------------------------
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe# python AIcandy_LLM_Transformer_hkosbvui.py --mode test --input "Sự kiên trì"
Training on device: cuda
Starting sentence generation testing:
--------------------------------------------------
Input: 'Sự kiên trì'
Generated sentence: 'sự kiên trì biết là thành. của bạn cười hãy những'
--------------------------------------------------
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe# python AIcandy_LLM_Transformer_hkosbvui.py --mode test --input "Không có giới hạn nào ngoại trừ"
Training on device: cuda
Starting sentence generation testing:
--------------------------------------------------
Input: 'Không có giới hạn nào ngoại trừ'
Generated sentence: 'không có giới hạn nào ngoại trừ mạnh trái hôm bằng'
--------------------------------------------------
root@aicandy:/aicandy/projects/LLM/AIcandy_LLM_MicroLLM_ihdvcmqe#
Vâng, dạy nó kiểu “mần non” nên nó cũng trả lời kiểu “mần non” 😀
Bạn yên tâm, với phương pháp hiện tại, hoàn toàn có thể dạy nó theo kiểu “đại học” để nó trả lời theo kiểu “đại học”.
7. Kết luận
Qua bài viết này, tôi đã hướng dẫn bạn cách tạo và thử nghiệm một AI Chatbot dựa trên mô hình Transformer với cách tiếp cận đơn giản, sử dụng mô hình microLLM nhằm phù hợp với tài nguyên GPU hạn chế và một tập dữ liệu nhỏ để minh họa. Kết quả cho thấy chatbot hoạt động được nhưng độ chính xác chưa cao và câu trả lời còn thiếu tự nhiên.
Tuy nhiên, với phương pháp này, bạn hoàn toàn có thể xây dựng một mô hình chatbot của riêng mình và cải thiện độ chính xác, độ mượt mà của văn bản bằng cách:
- Sử dụng các mô hình lớn hơn và mạnh mẽ hơn, phù hợp với tài nguyên phần cứng bạn có.
- Tăng quy mô tập dữ liệu để mô hình học tốt hơn các ngữ cảnh phức tạp.
Hãy nhớ rằng các mô hình thương mại như ChatGPT mà bạn đã biết được xây dựng bằng cách sử dụng hàng nghìn GPU hiệu năng cao, bộ dữ liệu khổng lồ và thời gian huấn luyện kéo dài để đạt được chất lượng vượt trội. Trong khi đó, phương pháp của bài viết này giúp bạn làm quen với cách tạo chatbot AI, đặt nền tảng cho những dự án AI chuyên sâu hơn trong tương lai.
Hãy tiếp tục khám phá, nâng cấp công nghệ và sáng tạo thêm để tạo ra những chatbot có thể đáp ứng nhu cầu thực tế của bạn!
8. Source code
Source code miễn phí tại đây