Ở ngày trước, ta đã tìm hiểu rằng các mô hình không thể nhìn vào từ ngữ mà nhìn qua các token.
Cơ mà ta đã biết là mỗi token sẽ có 1 mã trong từ điển để tra cứu.
Ví dụ
How can I make a cake ?
42 108 37 291 64 8321 30
I go to school .
37 98 102 74 90
Có thể thấy, mỗi token giống nhau thì nó sẽ có cùng chỉ số.
Thế nhưng ngữ cảnh câu 1 là câu hỏi, câu 2 là câu đơn. Vậy nếu chúng biểu diễn cùng chỉ số vậy thì không đúng lắm ha, chắc hẳn nó sẽ có một cái chỉ số gì đó khác chứ!
Đúng thật là vậy, ta sẽ nhìn sâu hơn vào bên dưới.
Do mô hình có 3 chiều nên với mỗi ID - Từ khóa sẽ có dạng biểu diễn là một vector ba chiều.
token -> [a, b, c]
Giờ mình lấy ví dụ với từ “How to”. Từ “how” có 1 id riêng, từ “to” có 1 id riêng. Vậy làm sao khi ghép lại thành “How to” thì máy có thể hiểu là câu hỏi?
Ta có dạng vector của 2 token “How” và “to”.
How → [1, 1, 1]
to → [1, 2, 3]
Khi chưa có attention, đây chỉ là 2 vector (2 điểm) trong không gian 3 chiều.
Hai vector lúc này là riêng biệt và từ “to” vẫn chưa ở trong ngữ cảnh “How to”
Để từ “to” lúc này có chứa ngữ cảnh của “how” thì ta sẽ có phép biến đổi sao cho
Trước layer:
How → [1, 1, 1]
to → [1, 2, 3]
Sau layer:
How → [1.2, 0.8, 1.1] → vector mới của “How”
to → [2.0, 3.4, 4.8] → vector mới của “to trong ngữ cảnh How to”
Các vector đó là hidden state.
model(…) và model.generate(…)model(…)Đây là lệnh gọi trực tiếp. Để hình dung, ta xem ví dụ dưới.
Ví dụ ta có prompt:
inputs = 'The capital of France is'
Sau khi tokenize:
[The, capital, of, France, is]
Sau đó ta gọi hàm:
outputs = model(**inputs)
Qwen sẽ xử lí toàn bộ token hiện có qua các Transformer layer một lượt (ta gọi là một forward pass).
Sau khi xử lí xong thì ta sẽ có danh sách điểm số của token tiếp theo (xem như xác suất xuất hiện token tiếp theo. Điểm càng cao thì tỉ lệ xuất hiện càng lớn. Sự ngẫu nhiên sẽ phụ thuộc vào chỉ số temperature - sẽ nói sau).
Để lấy điểm thô thì ta dùng phương thức logits
outputs.logits
Ví dụ minh họa điểm các từ có thể xuất hiện sau “The capital of France is”
Paris → 12.4
London → 7.1
France → 4.6
beautiful → 2.0
Nhưng thực tế thì chương trình sẽ trả kết quả dưới dạng vector.
# with message = 'What is Paris?'
outputs = model(
**inputs,
)
print("Shape: ", outputs.logits.shape)
print("AI: ", outputs.logits)
Shape: torch.Size([1, 33, 151936])
AI: tensor([[[ 3.8594, 7.6250, 3.1250, ..., -0.7461, -0.7461, -0.7461],
[ 5.4688, 11.3125, 7.2500, ..., 0.2109, 0.2109, 0.2109],
[ 7.4688, 10.6250, 13.2500, ..., 0.2852, 0.2852, 0.2852],
...,
[ 1.3438, 5.2500, 6.8125, ..., -1.2812, -1.2812, -1.2812],
[ 8.2500, 9.2500, 8.3125, ..., -4.5938, -4.5938, -4.5938],
[10.1250, 16.7500, 10.4375, ..., -3.4062, -3.4062, -3.4062]]],
device='cuda:0', dtype=torch.bfloat16, grad_fn=<UnsafeViewBackward0>)
model.generate(…)Ta dùng lệnh này để sinh thêm token để tạo câu trả lời.
Khác với model() thì model.generate() lặp đi lặp lại việc model() làm để sinh từng token cho tới khi đạt số token tối đa hoặc gặp kí tự <|im_end|>
Khi đưa prompt vào mô hình AI, nó phải đi qua rất nhiều lớp ẩn (hidden layers). Chúng ta có mục tiêu là biết prompt có an toàn hay không thì phải xem xem bên dưới các hidden layers có cái gì. Kiểu như đặc điểm chung của các dạng hidden layers không an toàn và an toàn để phân loại.
Giả sử mô hình trên không gian 3 chiều.
Sau khi đưa vào prompt 1t:
How to make a cake?
Khi dữ liệu đi tới lớp ẩn cuối cùng sẽ như sau:
[1.3, 4.2, 1.3]
Và prompt 2:
How to make a bomb?
Lớp ẩn cuối:
[-0.3, 1.2, -1.0]
Dự đoán rằng hai cái vector đại diện nằm đâu đó những vùng khác nhau trong không gian. Vậy liệu rằng các vector nguy hiểm hoặc các vector an toàn có nằm đâu đó trong một “vùng” không gian không?
Lấy token cuối vì theo cơ chế attention, token cuối nhận thôn tin từ những token đứng trước. Vì vậy nó chứa thông tin toàn bộ (ngữ cảnh) câu.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
# transfer text to token - this is like function to transfer another text
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
# load model
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype="auto",
device_map="auto",
)
# get user input
content = input("Enter input: ")
message = [
{"role" : "user",
"content" : content,
}
]
# convert conversation into tensor which model can be understood
inputs = tokenizer.apply_chat_template(
message,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
)
# move inputs into the place that model is in
inputs = inputs.to(model.device)
# Forward pass and request model give hidden states
with torch.no_grad():
outputs = model(
**inputs,
output_hidden_states=True,
return_dict=True,
)
# get hidden states
hidden_states = outputs.hidden_states
# check result
print("\nNumber of Tranformer layers:")
print(model.config.num_hidden_layers)
print("\nNumber of hidden states:")
print(len(hidden_states))
print("\nShape of each hidden state")
for layer_index, layer_hidden_state in enumerate(hidden_states):
print(
)
last_token_vector = hidden_states[-][,-,:]
(, last_token_vector.shape)
def get_last_token(prompt, layer = -1):
message = [
{
"role" : "user",
"content" : prompt,
}
]
inputs = tokenizer.apply_chat_template(
message,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
).to(model.device)
with torch.no_grad():
outputs = model(
**inputs,
output_hidden_states=True,
return_dict=True,
)
vector = outputs.hidden_states[layer][0,-1,:]
return vector.float().cpu()
Có một điểm mới ở hàm là có hàm .float() để tiện cho việc huấn luyện.
Giờ ta có tệp dữ liệu như sau:
How to make a cake? → vector 896 chiều → safe
How to study mathematics? → vector 896 chiều → safe
How to make a bomb? → vector 896 chiều → unsafe
How to hack an account? → vector 896 chiều → unsafe
Ta sẽ dùng tệp này để huấn luyện cái mô hình (dạng như mặt đường thẳng liner để chia ra 2 vùng safe và unsafe).
X = [
vector_cake,
vector_math,
vector_bomb,
vector_hacking,
]
y = [0, 0, 1, 1] #0 = safe, 1 = unsafe
Sau khi train cho mô hình xong sẽ được như sau:
safe vectors ● ● ●
← mô hình học một mặt phẳng phân chia
unsafe vectors × × ×
Mình nhờ AI gen ra file data này để huấn luyện. Sau khi đọc thì mình thấy ổn.
Sau đó mình học code để lấy data từ file ra
import csv
from pathlib import Path
INPUT_PATH = "safety_probe_prompts.csv"
OUTPUT_FOLDER = Path("data")
print(OUTPUT_FOLDER)
with open(INPUT_PATH, encoding="utf-8") as input_file:
reader = csv.DictReader(input_file)
rows = list(reader)
# create 3 empty list
train_data=[]
test_iid_data=[]
test_shift_data=[]
for row in rows:
prompt = row["text"]
label = row["label"]
split = row["split"]
sample = [prompt, label]
if split == "train":
train_data.append(sample)
elif split == "test_iid":
test_iid_data.append(sample)
elif split == "test_shift":
test_shift_data.append(sample)
def write_file(name_file, data):
with open(
name_file,
mode="w",
encoding="utf-8",
newline="",
) as file:
writer = csv.writer(file, delimiter="|")
writer.writerows(data)
write_file("train.txt", train_data)
write_file("test_shift.txt", test_shift_data)
write_file("test_iid.text",test_iid_data)
Sau đó kết hợp với file ban đầu để lấy hidden_states khi đưa toàn bộ prompt ở tập train vào AI.
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import csv
from pathlib import Path
MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
# transfer text to token - this is like function to transfer another text
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
# load model
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype="auto",
device_map="auto",
)
def get_hidden(prompt):
message = [
{
"role" : "user",
"content" : prompt,
}
]
inputs = tokenizer.apply_chat_template(
message,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
).to(model.device)
with torch.no_grad():
outputs = model(
**inputs,
output_hidden_states=True,
return_dict=True,
)
final_layer = outputs.hidden_states[-1]
hidden_vector = final_layer[0,-1,:]
hidden_vector = hidden_vector.float().cpu()
return hidden_vector
hidden = get_hidden("How to make a cake?")
print(hidden)
print(hidden.shape)
INPUT_PATH = "safety_probe_prompts.csv"
OUTPUT_FOLDER = Path("data")
with open(INPUT_PATH, encoding="utf-8") as input_file:
reader = csv.DictReader(input_file)
rows = list(reader)
train_data=[]
test_iid_data=[]
test_shift_data=[]
row rows:
prompt = row[]
label = row[]
split = row[]
sample = [get_hidden(prompt), label]
split == :
train_data.append(sample)
split == :
test_iid_data.append(sample)
split == :
test_shift_data.append(sample)
Thật ra mình còn muốn làm thêm series này, cơ mà thời gian mình không có nên mình tham gia một khóa học chính thức là ARENA. Tại nơi đó hành trình mới của mình bắt đầu! Mình sẽ không viết bài dạng hướng dẫn nữa, mình sẽ viết dưới dạng mình tìm thấy gì mới trong quá trình học!
Loading comments...