Minh họa Transformer

Minh họa Transformer là bản dịch tiếng Việt của The Illustrated Transformer, Bản gốc được viết bởi Jay Alammar ( @JayAlammar ), hiện được sử dụng tại các lớp học tại MIT. Bản dịch được sửa đổi một phần không đáng kể để phù hợp hơn với bạn đọc.

Transformer được đề xuất trong paper Attention is All You Need. Mã cài đặt TensorFlow của nó được công bố dưới dạng một thành phần của gói Tensor2Tensor. Nhóm NLP của Havard cũng tạo ra một bản hướng dẫn chú giải bài báo với PyTorch. Trong bài này, chúng ta sẽ cố gắng đơn giản hóa hết mức mọi thứ và giới thiệu từng khái niệm. Hi vọng rằng cách tiếp cận này sẽ giúp mọi người hiểu một cách dễ dàng hơn mà không cần đến các kiến thức chuyên sâu liên quan.

Kiến trúc tổng quan

Hãy nhìn mô hình như một hộp đen. Trong một ứng dụng dịch máy, nó sẽ nhận vào một câu trong một ngôn ngữ và sinh ra bản dịch của nó trong một ngôn ngữ khác.

Minh họa transformer

Nếu mở hộp đen ra, ta sẽ thấy một thành phần mã hóa, và một thành phần giải mã, và một kết nối giữa chúng.

Minh họa transformer

Thành phần mã hóa là một ngăn xếp encoder (các encoder xếp chồng lên nhau) (bài báo gốc sử dụng 6 encoder – đây không phải là một con số đặc biệt, ta hoàn toàn có thể thử nghiệm với các cấu hình khác). Thành phần giải mã là một ngăn xếp decoder với cùng số lượng.

Minh họa transformer

Các encoder có kiến trúc giống nhau (nhưng không có cùng trọng số). Mỗi encoder lại được tạo nên bởi hai lớp con:

Minh họa Transformer

Đầu vào của encoder đầu tiên sẽ đi qua một lớp self-attention – một lớp giúp cho encoder nhìn vào các từ khác khi đang mã hóa một từ cụ thể. Chúng ta sẽ phân tích kỹ self-attention ở đoạn sau của bài viết.

Đầu ra của self-attention được truyền vào một mạng nơ ron truyền thẳng (feed-forward). Tất cả các vị trí khác nhau đều sử dụng chung một mạng truyền thẳng.

Decoder cũng có hai thành phần đó nhưng nằm giữa chúng là một lớp attention giúp decoder tập trung vào phần quan trọng của câu đầu vào.

Minh họa Transformer

Minh họa các Tensor

Chúng ta đã nắm được các thành phần chính của mô hình, tiếp theo, hãy cùng nhìn vào chi tiết các ví dụ về véc tơ và tensor để hiểu rõ luồng thông tin giữa các thành phần để dịch từ đầu vào thành câu đầu ra hoàn chỉnh.

Như cách tiếp cận thông thường của NLP, đầu tiên, ta chuyển đầu vào thành các véc tơ sử dụng thuật toán embedding.

Minh họa Transformer

Mỗi từ được biểu diễn bằng một véc tơ có kích thước 512. Để tiện minh họa, ta sẽ biểu diễn mỗi véc tơ như hình trên.

Embedding chỉ được thực hiện ở encoder dưới cùng trong mạng. Mọi encoder đều nhận vào một danh sách các véc tơ có kích thước 512. Tại encoder dưới cùng, đầu vào của nó là word embedding, nhưng các encoder khác, nó nhận vào đầu ra của encoder ngay phía dưới nó. Kích thước của danh sách véc tơ là một siêu tham số, ta có thể thiết đặt – thông thường, nó là độ dài của câu dài nhất trong dữ liệu huấn luyện.

Thông tin đi qua encoder bản chất là đi qua hai lớp self-attention và feed forward như đã mô tả phía trên.

Minh họa Transformer

Chúng ta bắt đầu quan sát được tính chất quan trọng của Transformer, đó là thông tin của các từ ở vị trí khác nhau sẽ có các luồng riêng độc lập trong encoder. Tồn tại sự liên hệ và ràng buộc giữa các luồng này tại lớp self-attention. Mặc dù vậy, lớp feed-forward không có thông tin về sự ràng buộc đó. Tính chất này cho phép các luồng khác nhau có thể được tính song song khi đi qua lớp feed-forward.

Tiếp theo, hãy cùng theo dõi một ví dụ ngắn hơn để hiểu được điều gì xảy ra tại các lớp con của encoder.

Bắt đầu mã hóa

Như đã đề cập trước đó, một encoder nhận vào một danh sách các véc tơ và xử lý chúng bằng cách truyền các véc tơ này qua lớp self-attention và lớp feed-forward. Đầu ra của nó được gửi tới encoder tiếp theo.

Minh họa Transformer
Từ ở mỗi vị trí được truyền qua lớp self-attention. Sau đó chúng được truyền qua cùng một mạng feed-forward một cách riêng biệt.

Self-Attention một cách trực quan

Giả sử câu sau là câu đầu vào mà chúng ta cần dịch:

The animal didn't cross the street because it was too tired

Từ “it” trong câu trên đại diện cho cái gì? “Con vật” (animal) hay “đường phố” (street)? Câu hỏi này đơn giản với con người nhưng không đơn giản với các thuật toán.

Khi mô hình xử lý từ “it”, self-attention cho phép nó liên kết “it” với “animal”.

Khi mô hình xử lý từng từ (từng vị trí trong cầu đầu vào), self-attention cho phép nó quan sát các vị trí khác trong câu để tìm ra ý tưởng cho việc mã hóa từ hiện tại tốt hơn.

Nếu bạn quen với các mạng RNN, hãy nghĩ về cách RNN duy trì một trạng thái ẩn cho phép nó kết hợp biểu diễn của từ/véc tơ trước đó với từ mà nó đang xử lý. Self-attention là cách mà Transformer sử dụng để duy trì hiểu biết về các từ khác có liên quan đến từ hiện tại.

Minh họa Transformer
Khi mã hóa từ “it” tại encoder thứ 5 (encoder trên cùng của ngăn xếp), một phần attention tập trung vào “The Animal”, và sử dụng một phần biểu diễn của nó để mã hóa từ “it”.

Hãy ghé thăm Tensor2Tensor notebook, bạn có thể nạp một mô hình Transformer, thử nghiệm với nó một cách trực quan.

Chi tiết về Self-Attention

Đầu tiên, hãy cùng xem cách tính self-attention sử dụng các véc tơ, sau đó ta sẽ xem cách cài đặt thực tế sử dụng các ma trận.

Bước đầu tiên để tính self-attention là tạo ra ba véc tơ từ mỗi véc tơ đầu vào của encoder (trong trường hợp này là embedding của mỗi từ). Với mỗi từ, ta sẽ tạo một véc tơ truy vấn (Query), một vếc tơ khóa (Key), và một véc tơ giá trị (Value). Các véc tơ này được tạo ra bằng cách nhân embedding với ba ma trận được cập nhật trong quá trình huấn luyện.

Chú ý rằng, các véc tơ mới này có chiều nhỏ hơn véc tơ embedding. Chiều của chúng là 64, trong khi véc tơ embedding cũng như đầu vào và đầu ra của encoder có chiều 512. Mặc dù chiều của chúng không nhất thiết phải nhỏ hơn, đây là một lựa chọn trong kiến trúc để việc tính toán multiheaded attention (gần như) cố định.

Minh họa Transformer
Nhân  x1 với ma trận trọng số WQ ta có q1, véc tơ “truy vấn” đi cùng với từ đó. Ta sẽ tạo các véc tơ “truy vấn”, “khóa” và “giá trị” là các phép chiếu ứng với mỗi từ trong cầu đầu vào.


Các véc tơ “truy vấn”, “khóa”, và “giá trị” là gì?

Đó là các khái niệm trừu tượng hữu ích cho việc tính toán và nghĩ về attention. Khi bạn đã đọc cách mà attention được tính, bạn sẽ hiểu vai trò của từng véc tơ trong bộ ba này.

Bước thứ hai để tính self-attention là tính điểm. Giả sử chúng ta tính self-attention cho từ đầu tiên trong ví dụ, “Thinking”. Ta cần tính điểm cho mỗi từ trong câu đầu vào so với từ này. Điểm sẽ quyết định cần chú ý bao nhiêu vào các phần khác của câu đầu vào khi ta đang mã hóa một từ cụ thể.

Điểm được tính bằng phép nhân vô hướng giữa véc tơ truy vấn với véc tơ khóa của từ mà ta đang tính điểm. Nếu ta tiến hành self-attention cho từ ở vị trí thứ nhất, điểm đầu tiên sẽ là tích vô hướng của q1 và k1. Điểm thứ hai là tích vô hướng của q1 và k2.

Minh họa Transformer

Bước thứ ba và bước thứ tư là chia điểm cho 8 (căn bậc hai của số chiều của véc tơ khóa trong bài báo gốc – 64. Điều này giúp cho độ dốc ổn định hơn. Có thể có các giá trị khả dĩ khác, nhưng đây là giá trị mặc định), và truyền kết quả qua một phép softmax. Softmax chuẩn hóa các điểm để chúng là các số dương có tổng bằng 1.

Minh họa Transformer

Điểm softmax sẽ quyết định mỗi từ sẽ được thể hiện nhiều hay ít tại vị trí hiện tại. Rõ ràng là từ tại vị trí này sẽ có điểm softmax cao nhất, nhưng đôi khi, chú ý đến các từ khác là cần thiết để hiểu từ hiện tại.

Bước thứ năm là nhân mỗi véc tơ giá trị với điểm softmax (trước khi cộng chúng lại). Một cách trực giác, việc này bảo toàn giá trị của các từ mà ta muốn chú ý và bỏ qua các từ không liên quan (nhân chúng với một số rất nhỏ, ví dụ 0.001).

Bước thứ sáu là cộng các véc tơ giá trị đã được nhân trọng số. Kết quả chính là đầu ra của lớp self-attention tại vị trí hiện tại (từ đầu tiên trong ví dụ của ta).

Minh họa Transformer

Đến đây là kết thúc việc tính toán self-attention. Véc tơ kết quả có thể được gửi tới mạng truyền thẳng. Trong cài đặt thực tế, việc tính toán này được thực hiện với ma trận để đảm bảo tốc độ. Ta sẽ tiếp tục theo dõi cách thực hiện việc này sau khi đã hiểu một cách trực quan cách tính trên mức từ.

Tính self-attention bằng ma trận

Bước đầu tiên là tính các ma trận Truy vấn, Khóa, và Giá trị. Ta thực hiện điều này bằng cách gộp các embedding vào ma trận X, và nhân chúng với ma trận trọng số sẽ được huấn luyện (WQ, WK, WV).

Minh họa Transformer
Mỗi hàng trong X ứng với một từ trong câu đầu vào. Ta lại thấy sự khác biệt trong kích thước của các véc tơ embedding (512, hay 4 ô trong hình), và các véc tơ q/k/v (64, hay 3 ô trong hình)

Cuối cùng, do ta đang thực hiện trên ma trận, ta có thể gộp bước từ 2 đến 6 trong một công thức duy nhất để tính đầu ra của lớp self-attention.

Minh họa Transformer
Tính toán self-attention dưới dạng ma trận

Quái vật nhiều đầu

Bài báo làm mịn lớp self-attention bằng cơ chế mang tên “multi-headed” (nhiều đầu) attention. Cơ chế này cải thiện hiệu năng của lớp attention theo hai khía cạnh:

  1. Nó mở rộng khả năng của mô hình trong việc tập trung vào các vị trí khác nhau. Trong ví dụ phía trên, z1 chứa một số thông tin mã hóa từ các vị trí khác, nhưng bị lấn át bởi từ ở chính vị trí đó. Khi ta dịch câu “The animal didn’t cross the street because it was too tired”, ta muốn biết chính xác “it” dùng để chỉ cái gì.
  2. Nó mang lại cho lớp attention nhiều không gian con để biểu diễn. Như chúng ta sắp theo dõi, với multi-headed attention chúng ta không chỉ có một mà nhiều bộ ma trận trọng số Query/Key/Value (Transformer sử dụng tám đầu attention, do đó ta sẽ có 8 bộ cho mỗi encoder/decoder). Mỗi bộ được khởi tạo ngẫu nhiên. Sau đó, kết thúc huấn luyện, mỗi bộ được dùng để phản ánh embedding đầu vào (hoặc véc tơ từ các encoder/decoder phía dưới) trong một không gian con riêng biệt.
Minh họa Transformer
Với multi-headed attention, ta duy trì các ma trận trọng số Q/K/V riêng biệt cho mỗi đầu. Như đã giới thiệu, ta nhân X với ma trận WQ/WK/WV để tạo ra các ma trận Q/K/V.


Nếu ta thực hiện self-attention như đã vạch ra bên trên, với 8 lần tính với các ma trận khác nhau, ta có 8 ma trận Z khác nhau.

Minh họa Transformer

Điều này mang lại một khó khăn nhỏ. Mạng truyền thẳng không phù hợp để nhận vào 8 ma trận, thay vào đó nó cần 1 ma trận duy nhất (mỗi từ một véc tơ). Do đó, ta cần biến đổi 8 ma trận về 1 ma trận duy nhất.

Vậy làm thế nào? Ta nối các ma trận lại và nhân chúng với một ma trận trọng số được bổ sung WO.

Minh họa Transformer

Như vậy là khá đầy đủ cho multi-headed self-attention. Có khá nhiều ma trận. Ta sẽ gộp chúng vào cùng một chỗ.

Minh họa Transformer

Giờ ta đã chạm tới các đầu attention, hãy cùng xem lại ví dụ trước đây để xem các đầu attention tập trung vào vị trí nào khi mã hóa từ “it” trong câu ví dụ:

Minh họa Transformer
Khi mã hóa từ “it”, một đầu tập trung vào “the animal”, trong khi đầu khác tập trung vào “tired” — nghĩa là, mô hình biểu diễn từ “it” sử dụng một phần của cả “animal” và “tired”.

Nếu ta gộp hết các đầu attention vào cùng một hình ảnh, mọi chuyện có thể trở nên khó giải thích hơn:

Biểu diễn thứ tự trong chuỗi với Positional Encoding

Một điều chưa được đề cập đến trong mô hình của chúng ta là cách xử lý thứ tự của các từ trong chuỗi đầu vào.

Để giải quyết vấn đề này, transformer thêm một véc tơ vào mỗi embedding đầu vào. Các véc tơ này tuân theo một mẫu cố định mà mô hình học được, giúp nó xác định vị trí của từng từ hoặc khoảng cách của các từ khác nhau trong chuỗi. Ý tưởng ở đây là việc thêm các giá trị đó sẽ cung cấp thông tin về khoảng cách giữa các véc tơ embedding khi chúng được phản ánh thông qua các véc tơ Q/K/V và thông qua phép lấy tích vô hướng.

Để mô hình biết được thứ tự từ, ta thêm vào véc tơ mã hóa vị trí (positional embedding) – giá trị của nó tuân theo một mẫu cố định.


Nếu giả định embedding có chiều bằng 4, các véc tơ mã hóa vị trí sẽ trông như sau:

Minh họa Transformer
Một ví dụ thực tế của positional encoding với embedding ví dụ có kích thước bằng 4.

Mẫu cố định này trông như thế nào?

Ở hình phía dưới, mỗi hàng ứng với mã hóa vị trí của một véc tơ. Hàng đầu tiên là véc tơ được cộng với véc tơ embedding của từ đầu tiên của chuỗi đầu vào. Mỗi hàng chứa 512 giá trị – mỗi giá trị trong khoảng 1 và -1. Chúng ta đưa chúng lên giải màu để có thể quan sát được mẫu này.

Một ví dụ mã hóa vị trí cho 20 từ (hàng) với kích thước embedding 512 (cột). Bạn có thể thấy mầu này chia đôi xuống ở giữa. Điều này bởi vì giá trị của nửa bên trái sử dụng một hàm (hàm sin) và nửa bên phải sử dụng một hàm khác (hàm cos). Sau đó chúng được nối lại để hình thành véc tơ mã hóa vị trí.

Công thức của positional encoding được mô tả trong bài báo (phần 3.5). Bạn có thể xem code sinh positional encoding tại get_timing_signal_1d(). Đây không phải phương pháp duy nhất để mã hóa vị trí. Mặc dù vậy, nó tạo ra lợi thế trong việc mở rộng cho các độ dài chưa biết (ví dụ trong trường hợp mô hình cần phải dịch một câu dài hơn so với các ví dụ trong tập huấn luyện).

Cập nhật Tháng 7 2020: Positional encoding phía trên là từ cài đặt Tranformer2Transformer của Transformer. Phương pháp trong bài báo có một chút khác biệt. Hai tín hiệu không được nối với nhau mà được đan xen. Hình dưới đây minh họa cách mã hóa vị trí này. Đây là code để sinh ra nó:

Minh họa Transformer

Kết nối residual

Một chi tiết nữa trong kiến trúc của encoder cần phải nhắc đến là trong mỗi lớp con (self-attention, mạng truyền thẳng) của encoderkết nối residual xung quanh chúng, và sau đó là bước chuẩn hóa lớp.

Minh họa Transformer

Nếu trực quan hóa véc tơ và phép chuẩn hóa lớp liên quan đến self-attention, nó sẽ trông như sau:

Minh họa Transformer

Điều này cũng được thực hiện ở decoder. Nếu ta coi Transformer là 2 găn xếp encoderdecoder, nó sẽ trông như sau:

Phía Decoder

Ta đã đi qua đa số các khái niệm của phía encoder, và cũng nằm được một cách cơ bản các thành phần của decoder. Giờ hãy xem chúng hoạt động như thế nào.

Encoder bắt đầu bằng việc xử lý câu đầu vào. Kế quả của encoder trên cùng được chuyển thành một bộ các véc tơ attention K và V. Chúng được sử dụng bởi mỗi decoder trong lớp “encoder-decoder attention” để giúp decoder tập trung vào phần quan trọng trong chuỗi đầu vào.

Minh họa Transformer
Sau bước mã hóa, ta bắt đầu giải mã. Mỗi bước trong khâu giải mã sẽ đưa ra một phần tử trong chuỗi đầu ra (bản dịch tiếng Anh trong trường hợp này).


Các bước dưới đây được lặp lại cho đến khi gặp một ký tự đặc biệt đánh dấu việc decoder đã kết thúc việc giải mã. Đầu ra của mỗi bước được đưa vào decoder dưới cùng của bước tiếp theo và decoder trả về kết quả tương tự như những gì encoder thực hiện. Và cũng như những gì ta làm với đầu vào encoder, ta thực hiện nhúng từ và thêm positional encoding cho đầu vào của decoder để xác định vị trí của từng từ.

Các lớp self attention của decoder hoạt động hơi khác so với tại encoder:

Trong decoder, lớp self-attention chỉ cho phép chú ý lên các vị trí phía trước của chuỗi đầu ra. Điều này được thực hiện bằng cách che đi các vị trí phía sau thông qua masking (đặt chúng về -inf) trước bước softmax khi tính self-attention.

Lớp “Encoder-Decoder Attention” hoạt động như self-attention nhiều đầu, ngoại trừ việc nó tạo các ma trận Q từ lớp phía dưới và lấy các ma trận K và V từ đầu ra của ngăn xếp encoder.

Lớp Tuyến tính cuối cùng và Softmax

Ngăn xếp decoder đưa ra véc tơ của các số thực. Làm sao ta chuyển chúng thành các từ? Đó là việc của lớp tuyến tính cuối cùng và lớp Softmax.

Lớp tuyến tính là một mạng kết nối đầy đủ đơn giản, ánh xạ véc tơ được tạo bởi ngăn xếp decoder thành một véc tơ lớn hơn rất nhiều, được gọi là véc tơ logit.

Giả sử mô hình biết 10K từ tiếng Anh (tập từ vựng đầu ra của mô hình) được học từ bộ huấn luyện. Nó sẽ tạo ra véc tơ logit có độ rộng 10K ô – mỗi ô tương ứng với một điểm của một từ. Đó là cách chúng ta phiên dịch đầu ra của mô hình sau khi đi qua lớp tuyến tính.

Lớp softmax sau đó chuyển các điểm này thành xác suất (các số dương có tổng bằng 1). Ô với xác suất cao nhất được chọn, và từ ứng với nó là đầu ra của bước hiện tại.


Hình này bắt đầu từ phía dưới với véc tơ được sinh ra từ ngăn xếp decoder và được chuyển thành từ đầu ra.

Tóm tắt quá trình huấn luyện

Chúng ta đã nắm được toàn bộ quá trình truyền thẳng thông qua một Transformer đã được huấn luyện, ta cũng nên xem xét cách huấn luyện mô hình.

Khi huấn luyện, mô hình ban đầu cũng có luồng thông tin truyền thẳng y như đã nói phía trên. Nhưng vì huấn luyện trên một bộ dataset có nhãn, ta có thể so sánh đầu ra của nó với nhãn đúng của dữ liệu.

Để minh họa, hãy giả sử tập từ vựng đầu ra chỉ bao gồm 6 từ (“a”, “am”, “i”, “thanks”, “student”, và “<eos>” (viết tắt của ‘end of sentence’ – ‘kết thúc câu’)).

Tập từ vựng đầu ra được tạo trước cả khi ta bắt đầu huấn luyện.


Khi đã định nghĩa tập từ vựng đầu ra, ta có thể sử dụng một véc tơ có độ rộng cố định để biểu diễn các từ trong đó, gọi là one-hot encoding. Ví dụ, ta có thể biểu diễn từ “am” với véc tơ như sau:

Ví dụ: one-hot encoding của tập từ vựng.

Sau phần tóm tắt này, hãy cùng thảo luận về hàm loss – độ đo để ta tối ưu mô hình trong quá trình huấn luyện.

Hàm Loss

Huấn luyện mô hình, giả sử như trong bước đầu tiên, ta huấn luyện nó dịch một ví dụ đơn giản, từ “merci” sang “thanks”.

Điều này nghĩa là gì? Nó có nghĩa là ta muốn phân bố xác suất chỉ ra từ “thanks”. Nhưng vì mô hình chưa được huấn luyện, điều này khó có thể xảy ra.

Vì tham số của mô hình (trọng số) được khởi tạo ngẫu nhiên, mô hình (chưa huấn luyện) sẽ đưa ra một giá trị bất kỳ cho mỗi ô/từ. Ta so sánh chúng với giá trị thực tế, thay đổi các trọng số của mô hình thông qua lan truyền ngược để có được kết quả gần hơn với giá trị đúng.

Làm sao để so sánh hai phân bố xác suất? Ta đơn giản trừ chúng cho nhau. Chi tiết hơn, hãy xem về cross-entropy và Kullback–Leibler divergence.

Chú ý rằng đây là một ví dụ đã được đơn giản hóa tối đa. Thực tế, ta sẽ sử dụng các câu có nhiều hơn một từ. Ví dụ đầu vào: “je suis étudiant” và đầu ra mong muốn: “i am a student”. Ta muốn mô hình có thể đưa ra được phân bố xác suất mà:

  • Mỗi phân bố xác suất biểu diễn bởi một véc tơ có chiều cố định là vocab_site (6 trong ví dụ, và 30K hay 50K trong thực tế)
  • Phân bố đầu tiên có xác suất cao nhất tại ô ứng với từ “i”
  • Phân bố thứ hai có xác suất cao nhất tại ô ứng với từ “am”
  • Cứ như vậy, cho đến khi phân bố thứ 5 chỉ ra ký tự ‘<end of sentence>’, nó cũng có một ô ứng với nó từ bộ từ vựng 10K phần tử.
Ví dụ về phân bố xác suất mục tiêu mà ta sử dụng để huấn luyện mô hình trong một câu cụ thể.

Sau khi huấn luyện mô hình với một bộ dữ liệu đủ to, ta hi vọng sẽ có được phân bố xác suất như sau:

Hy vọng rằng sau khi huấn luyện, mô hình sẽ cho ra bản dịch đúng như chúng ta mong đợi. Lưu ý rằng mọi vị trí đều ít nhiều có một giá trị xác suất nào đó ngay cả khi nó không phải là đầu ra của bước hiện tại – một tính chất rất hữu ích của softmax trong quá trình huấn luyện.

Với các từ đầu ra của decoder, ta có thể sử dụng giải thuật tham lam hoặc beam search để có được câu đầu ra cuối cùng.

Hi vọng thông qua bài viết Minh họa Transformer các bạn đã hiểu kỹ và hiểu rõ hơn về Transformer và cách thức hoạt động của nó. Bài viết không thể hoàn thành nếu không có tư liệu quý giá của Jay Alammar (@JayAlammar). Hãy theo dõi tác giả để thể hiện sự trân trọng đối với công sức của bạn ấy.

Thank you very much Jay Alammar!

trituenhantao.io

Nếu bạn thấy bài viết hữu ích, hãy chia sẻ với những người quan tâm, và hãy thường xuyên truy cập website để có được những kiến thức mới, chuyên sâu về lĩnh vực!