Giới Thiệu
Trong thế giới công nghệ hiện đại, việc phát hiện các câu hỏi trùng lặp là một thách thức lớn, đặc biệt trong các hệ thống quản lý thi cử. Hãy tưởng tượng rằng hai giáo viên khác nhau đã nhập các câu hỏi tương tự nhau vào cơ sở dữ liệu nhưng với cách diễn đạt khác nhau. Điều này có thể gây ra sự nhầm lẫn và khó khăn cho cả giáo viên và sinh viên. Bài viết này sẽ hướng dẫn bạn xây dựng một API kiểm tra tính tương tự văn bản sử dụng Sentence Transformers (SBERT) và Flask.
Sentence-Transformers (SBERT) Là Gì?
Sentence Transformers (SBERT) là một công cụ mạnh mẽ cho phép chuyển đổi câu thành các nhúng ngữ nghĩa. Điều này giúp chúng ta đo lường mức độ tương tự của hai câu không chỉ dựa trên từ ngữ mà còn dựa trên ý nghĩa. Chẳng hạn, hai câu sau đây:
“Thủ đô của Pháp là thành phố nào?”
“Thành phố nào là thủ đô của Pháp?”
Mặc dù chúng có cấu trúc ngữ pháp khác nhau, nhưng ý nghĩa của chúng là giống nhau. SBERT giúp nhận diện các câu hỏi trùng lặp một cách tự động, dễ dàng hơn so với việc sử dụng các phương pháp tìm kiếm từ khóa thông thường.
Thiết Lập Dự Án
Trước khi bắt đầu lập trình, hãy thiết lập một môi trường sạch sẽ cho dự án của bạn. Tôi khuyên bạn nên sử dụng môi trường ảo (venv) để giữ cho các phụ thuộc tách biệt và tránh xung đột với các dự án Python khác.
Cài Đặt Thư Viện Cần Thiết
Trong môi trường ảo của bạn, cài đặt các thư viện cần thiết:
pip install flask sentence-transformers
Các thư viện này sẽ giúp bạn:
- Flask → tạo API REST của chúng ta.
- Sentence Transformers → tải SBERT và tính toán các nhúng câu.
Xây Dựng Ứng Dụng Flask Cơ Bản
Trước khi đưa vào SBERT, hãy bắt đầu với một ứng dụng Flask tối giản nhất có thể để đảm bảo mọi thứ hoạt động.
Tạo một tệp có tên app.py trong thư mục dự án của bạn và thêm mã sau:
python
from flask import Flask
# Tạo một instance của ứng dụng Flask
app = Flask(__name__)
# Định nghĩa một route cơ bản
@app.route('/')
def home():
return "Xin chào, Flask đang chạy!"
if __name__ == '__main__':
app.run(debug=True)
Khởi động server với:
python app.py
Hoặc
flask --app app run
Nếu mọi thứ được thiết lập đúng cách, bạn sẽ thấy thông báo:
* Đang chạy trên http://127.0.0.1:5000/ (Nhấn CTRL+C để thoát)
Mở trình duyệt và truy cập vào http://127.0.0.1:5000, bạn sẽ thấy:
Xin chào, Flask đang chạy!
Thêm Trợ Giúp Cho SBERT
Ứng dụng Flask của chúng ta đã chạy, giờ hãy tích hợp SBERT. Để giữ cho mã của chúng ta sạch sẽ, hãy tạo một tệp mới có tên embedding_service.py để xử lý ba điều:
- Tải mô hình một lần (lazy loading).
- Chuyển đổi văn bản thành các nhúng (vector).
- So sánh hai nhúng để lấy độ tương tự.
Bước 1: Tải Mô Hình
python
from transformers import AutoTokenizer, AutoModel
# Biến toàn cục được tải chậm
_tokenizer = None
_model = None
def _load_model():
"""Tải tokenizer và mô hình SBERT một lần (lazy loading)."""
global _tokenizer, _model
if _tokenizer is None or _model is None:
_tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
_model = AutoModel.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
return _tokenizer, _model
Bước 2: Chuyển Câu Thành Vector
python
import torch
import torch.nn.functional as F
def mean_pooling(model_output, attention_mask):
"""Tính trung bình các nhúng token với attention mask (SBERT pooling tiêu chuẩn)."""
token_embeddings = model_output[0]
mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
return torch.sum(token_embeddings * mask_expanded, 1) / torch.clamp(mask_expanded.sum(1), min=1e-9)
def to_vector(text):
"""Chuyển đổi một chuỗi (hoặc danh sách các chuỗi) thành nhúng SBERT."""
if isinstance(text, str):
text = [text]
tokenizer, model = _load_model()
encoded_input = tokenizer(text, padding=True, truncation=True, return_tensors="pt")
with torch.no_grad():
model_output = model(**encoded_input)
sentence_embeddings = mean_pooling(model_output, encoded_input["attention_mask"])
return F.normalize(sentence_embeddings, p=2, dim=1)
Bước 3: So Sánh Hai Câu
python
def compare(text1, text2):
"""So sánh hai văn bản và trả về độ tương tự cosine."""
v1 = to_vector(text1)
v2 = to_vector(text2)
similarity = torch.nn.functional.cosine_similarity(v1, v2)
return similarity.item()
Xây Dựng REST API
Bây giờ chúng ta sẽ mở rộng tệp app.py để:
- Chấp nhận hai văn bản đầu vào qua yêu cầu POST.
- Sử dụng
embedding_serviceđể tính toán độ tương tự. - Trả về điểm tương tự dưới dạng JSON.
Bước 1: Nhập Trợ Giúp
Trong app.py, nhập trợ giúp mà chúng ta đã tạo:
python
from flask import Flask, request, jsonify
from embedding_service import compare
Bước 2: Thêm Một Route Mới
Chúng ta sẽ định nghĩa một endpoint mới /similarity chấp nhận yêu cầu POST với hai văn bản.
python
@app.route('/similarity', methods=['POST'])
def similarity():
try:
data = request.get_json()
text1 = data.get("text1")
text2 = data.get("text2")
if not text1 or not text2:
return jsonify({"error": "Cả text1 và text2 đều cần thiết"}), 400
score = compare(text1, text2)
return jsonify({
"text1": text1,
"text2": text2,
"similarity": score
})
except Exception as e:
return jsonify({"error": str(e)}), 500
Bước 3: Kiểm Tra API
Chạy lại ứng dụng Flask:
python app.py
Giờ đây, bạn có thể gửi yêu cầu POST với hai câu (sử dụng curl, Postman, hoặc httpie):
curl -X POST http://127.0.0.1:5000/similarity \
-H "Content-Type: application/json" \
-d '{"text1": "Thủ đô của Pháp là thành phố nào?", "text2": "Thành phố nào là thủ đô của Pháp?"}'
Nếu mọi thứ hoạt động tốt, bạn sẽ nhận được phản hồi JSON như sau:
{
"similarity": 0.9416358470916748,
"text1": "Thủ đô của Pháp là thành phố nào?",
"text2": "Thành phố nào là thủ đô của Pháp?"
}
Kết Luận Và Các Bước Tiếp Theo
Trong bài viết này, chúng ta đã xây dựng một API kiểm tra tính tương tự văn bản từ đầu đến cuối. Để cải thiện dự án này, bạn có thể:
- Đăng ký các nhúng vào một cơ sở dữ liệu vector (chẳng hạn như Pinecone, Weaviate, Qdrant hoặc FAISS).
- Mở rộng API với FastAPI + Uvicorn cho môi trường sản xuất.
- Thêm xác thực và ghi log nếu người khác sẽ sử dụng dịch vụ của bạn.
Với những cải tiến này, dịch vụ nhúng của bạn có thể phát triển thành một công cụ tìm kiếm ngữ nghĩa thực thụ.