[HTTP/2] Phần 1: Tìm hiểu hành trình đi lên HTTP/2

This entry is part 1 of 2 in the series Network

Điều gì xảy ra khi bạn duyệt web?

Internet đã trở thành một phần không thể thiếu trong cuộc sống hàng ngày. Mua sắm, ngân hàng, giao tiếp và giải trí đều phụ thuộc vào internet, và với sự phát triển của Internet vạn vật (IoT), ngày càng có nhiều thiết bị được kết nối trực tuyến, nơi chúng có thể được truy cập từ xa. Truy cập này được thực hiện nhờ một số công nghệ, bao gồm Giao thức Truyền Siêu văn bản (HTTP), đây là một phương pháp chính để yêu cầu truy cập vào các ứng dụng và tài nguyên web từ xa.

Mục đích sử dụng chính và đầu tiên của HTTP: để yêu cầu các trang web. Khi bạn mở một trang web trong trình duyệt của mình, dù trình duyệt đó trên pc/laptop, tablet, điện thoại di động hoặc bất kỳ thiết bị nào khác cho phép truy cập internet (tủ lạnh, tivi, …), rất nhiều thứ đang diễn ra.

Giả sử bạn khởi động trình duyệt và truy cập www.google.com. Trong vòng vài giây, những điều sau sẽ xảy ra

image - quochung.cyou PTIT

Bước 1: DNS, IPv4, IPv5, IPv6, IPv1-3

Khi bạn gõ www.google.com vào trình duyệt, nó không tự biết Google ở đâu. Trình duyệt sẽ nhờ đến một “cuốn danh bạ” đặc biệt gọi là DNS (Domain Name System). Công việc của DNS rất đơn giản: biến cái tên thân thiện với con người (www.google.com) thành một dãy số mà máy tính hiểu được, gọi là địa chỉ IP.

  • Tên miền giống như tên người trong danh bạ: “Google”.
  • Địa chỉ IP giống như số điện thoại: máy tính chỉ biết gọi bằng số, chứ không hiểu tên.

Hiện nay có hai “kiểu số điện thoại” chính:

  • IPv4: dạng cũ, ví dụ 216.58.192.4. Nhìn khá ngắn gọn, con người còn đọc được.
  • IPv6: dạng mới, ví dụ 2607:f8b0:4005:801:0:0:0:2004. Dài ngoằng, chỉ máy tính mới “thích” đọc. IPv6 sinh ra vì IPv4 gần như đã “hết số”, giống như khi một thành phố phải thêm mã vùng điện thoại mới vì dân cư quá đông.

Một fact: vì internet là toàn cầu, Google (và các công ty lớn khác) đặt máy chủ khắp nơi trên thế giới. DNS sẽ thông minh chọn cho bạn địa chỉ IP của máy chủ gần nhất, để tốc độ truy cập nhanh hơn. Thế nên một người ở Mỹ và một người ở châu Âu có thể nhận được IP hoàn toàn khác nhau khi cùng gõ www.google.com.

Điều gì đã xảy ra với IPv5?

Mỗi thiết bị kết nối Internet đều cần một địa chỉ IP duy nhất, giống như mỗi ngôi nhà cần một số nhà riêng.

  • Với IPv4, ta có khoảng 4,3 tỷ “số nhà” (2³² địa chỉ). Khi Internet mới ra đời, con số đó nghe như vô tận.
  • Nhưng rồi máy tính cá nhân, điện thoại thông minh, camera an ninh, đồng hồ thông minh, tủ lạnh IoT… tất cả đều cần một địa chỉ. Điều này khiến kho địa chỉ IPv4 gần như cạn kiệt.

Buộc phải mở ra một hệ thống đánh số mới rộng hơn. IPv6 chính là “hệ thống số nhà mở rộng” đó, với 128 bit, đủ để cấp địa chỉ cho hầu như mọi hạt cát trên trái đất. (2^128)

Bạn có thể thấy hơi lạ: chúng ta có IPv4, rồi nhảy thẳng sang IPv6. Thế IPv5 biến đi đâu? Và tại sao chẳng ai nhắc tới IPv1–IPv3?

Thực ra mọi gói tin Internet đều có một trường nhỏ ở đầu (4 bit) để ghi phiên bản. Về lý thuyết, chỉ có tối đa 15 phiên bản (0–15). (Có thể thấy ở ảnh dưới, phần Version đầu tiên)

image 1 - quochung.cyou PTIT
  • IPv6: giải quyết triệt để vấn đề “hết số nhà” trên Internet, đồng thời bổ sung nhiều tính năng mới. Đây mới là “người kế nhiệm” thực sự.
  • IPv0–IPv3: chỉ là các phiên bản thử nghiệm ban đầu. Không cái nào trở thành chuẩn chính thức.
  • IPv4: phiên bản đầu tiên thực sự phổ biến, đưa Internet bùng nổ như chúng ta thấy ngày nay.
  • IPv5: từng tồn tại dưới cái tên Internet Stream Protocol. Nó được thiết kế cho âm thanh và video theo thời gian thực (kiểu như những gì sau này VoIP làm). Nhưng nó sớm bị bỏ xó, vì vẫn mắc đúng nhược điểm của IPv4: không đủ không gian địa chỉ.

Vậy nên, IPv5 không “mất tích” mà chỉ dừng lại ở mức thử nghiệm. Thế giới chọn đi thẳng từ IPv4 sang IPv6 để giải quyết triệt để vấn đề cạn kiệt địa chỉ.

Bước 2: Tạo kết nối TCP/IP

Sau khi có địa chỉ IP, trình duyệt sẽ tìm cách kết nối đến máy chủ web. Kết nối này thường chạy qua:

  • Cổng 80 cho HTTP (truyền thống).
  • Cổng 443 cho HTTPS (phiên bản an toàn, có mã hóa).

Ngày nay hầu hết các trang web (kể cả Google) đều bắt buộc HTTPS nhờ công nghệ HSTS. Nghĩa là, ngay cả khi bạn gõ http://..., trình duyệt cũng sẽ tự động nâng cấp sang HTTPS.

TCP và IP phối hợp thế nào?

  • IP giống như địa chỉ nhà, đảm bảo gói tin tìm đúng nơi đến.
  • TCP giống như dịch vụ giao hàng uy tín: luôn hỏi “Bạn có nhận đủ chưa?”, nếu thiếu thì gửi lại.

Khi kết hợp, ta có TCP/IP, hai thứ làm nên xương sống Internet.

Một địa chỉ IP, nhiều dịch vụ

Một máy chủ có thể phục vụ nhiều “dịch vụ” khác nhau (web, email, FTP…). Để phân biệt, nó dùng cổng/port , giống như từ 1 địa chỉ nhà, chúng ta có thể chỉ tới số phòng, tầng, …

  • HTTP → cổng 80
  • HTTPS → cổng 443

HTTP

Khi kết nối TCP/IP đã ổn định, trình duyệt mới bắt đầu dùng HTTP để gửi yêu cầu: “Hãy cho tôi trang chủ Google”. Lúc này mới đến phần giao tiếp cấp ứng dụng mà ta thường nghe đến nhiều nhất.

Bước 3-10: Yêu cầu máy chủ phản hồi và trình duyệt bắt đầu “vẽ” trang

Sau khi trình duyệt gửi yêu cầu, máy chủ Google phản hồi. Nội dung phản hồi thường là HTML – ngôn ngữ mô tả cấu trúc của trang web.

HTML giống như bộ khung của một ngôi nhà: nó chia trang thành các phần (thẻ <div>, <p>, <h1>…), và kèm theo đường dẫn tới những vật liệu khác để hoàn thiện ngôi nhà:

  • CSS (màu sắc, kiểu dáng)
  • JavaScript (hành vi, tương tác)
  • Hình ảnh, font chữ, video…

Mã phản hồi và chuyển hướng

Không phải lúc nào máy chủ cũng trả về ngay một trang HTML:

  • Nếu bạn truy cập http://www.google.com, máy chủ sẽ gửi lại mã 301/302 để chuyển hướng bạn sang https://www.google.com.
  • Nếu bạn gõ nhầm, như www.google.com/nonexistent, bạn sẽ gặp mã 404 Not Found – giống như hỏi thủ thư một cuốn sách không có trong thư viện.

Trình duyệt dựng DOM

Khi nhận được HTML, trình duyệt sẽ phân tích và xây dựng DOM (Document Object Model) – bản đồ nội bộ của trang. Trong quá trình đọc HTML, nó phát hiện thêm tài nguyên cần tải (CSS, JS, hình ảnh…).

  • Google khá “nhẹ nhàng”: chỉ khoảng 16 tài nguyên bổ sung.
  • Trang trung bình: khoảng 75 tài nguyên (theo HTTP Archive).
  • Facebook? Có thể lên tới hàng trăm tài nguyên – mỗi ảnh, script, quảng cáo đều phải gọi thêm.

Mỗi tài nguyên lại đi qua vòng lặp yêu cầu–phản hồi như ban đầu, khiến duyệt web chậm dần. Đây cũng là lý do HTTP/2 ra đời: gom nhiều yêu cầu để tải nhanh hơn.


Khi nào thì “bắt đầu vẽ”

Trình duyệt không đợi tải hết tất cả mới hiển thị, mà sẽ render dần dần:

  • Nếu chờ đủ, web sẽ chậm chạp, người dùng nản.
  • Nếu render sớm quá, trang “nhảy loạn” khi CSS/ảnh/JS tải thêm – gây khó chịu.

Hãy tưởng tượng bạn xem họa sĩ vẽ tranh: ban đầu chỉ có khung, sau đó thêm màu, rồi chi tiết. Nếu vẽ dở dang mà đưa cho bạn xem, bức tranh cứ đổi hình dáng liên tục – đó chính là cảm giác khi trang web “nhảy”.


Tải nền

Ngay cả sau khi trang “trông như đã xong”, trình duyệt vẫn âm thầm tải thêm:

  • Ảnh, script theo dõi, quảng cáo…
  • Tính năng động: ví dụ Google gợi ý kết quả tìm kiếm ngay khi bạn gõ, hay Facebook tự động hiện bài mới.

Những thứ này chạy ngầm trong nền (AJAX, fetch API, WebSocket…), khiến web ngày nay giống ứng dụng thật sự chứ không chỉ là trang tĩnh.

HTTP là gì

Vào thập niên 1930, kỹ sư điện Vannevar Bush (MIT) đã lo ngại rằng khối lượng thông tin con người tạo ra vượt xa khả năng tiêu thụ. Trong bài tiểu luận As We May Think (1945), ông hình dung ra một thiết bị gọi là memex – nơi mọi tri thức được lưu trữ, có thể tra cứu nhanh chóng và liên kết với nhau giống như cách bộ não kết nối ý tưởng. Memex không bao giờ thành hiện thực, nhưng khái niệm “kết nối thông tin có ngữ cảnh” đã gieo mầm cho thế hệ sau.

image 3 - quochung.cyou PTIT

Đến những năm 1960, Ted Nelson đưa ra thuật ngữ hypertext – tập hợp văn bản/hình ảnh liên kết phức tạp mà giấy in không thể thể hiện. Nelson mơ về một “docuverse”, nơi thông tin được liên kết, không bao giờ bị xóa, và mọi người đều có thể truy cập. Dù dự án Xanadu của ông không thành công, nhưng nó đã tạo cảm hứng mạnh mẽ.

image 2 - quochung.cyou PTIT

Năm 1989, tại CERN, Tim Berners-Lee muốn một cách để quản lý thông tin từ các thí nghiệm khổng lồ. Ông kết hợp ý tưởng hypertext (liên kết tự do) và hypermedia (không giới hạn ở chữ viết) để đề xuất một hệ thống “phổ quát” , chính là nền móng của World Wide Web. Và từ đây, HTTP (Hypertext Transfer Protocol) ra đời.

HTTP (Hypertext Transfer Protocol) đúng như tên gọi, ban đầu được tạo ra chỉ để chuyển các tài liệu “siêu văn bản” – tức là những trang có thể chứa liên kết sang trang khác. Nhưng chẳng bao lâu, người ta nhận ra rằng nó cũng có thể dùng để gửi những thứ khác như hình ảnh, âm thanh, video…

Vì vậy, chữ Hypertext trong tên ngày nay không còn quá chính xác, nhưng cái tên HTTP đã ăn sâu nên không ai đổi nữa.

Điều quan trọng là HTTP hoạt động dựa trên kết nối mạng ổn định (thường qua TCP/IP). Nó không quan tâm đến cách bạn nối dây mạng, Wi-Fi hay Ethernet thế nào, tất cả việc đó do các tầng giao thức bên dưới lo. HTTP chỉ tập trung làm một việc: nhận yêu cầu từ trình duyệt và trả về phản hồi từ máy chủ.

Điểm mạnh của HTTP chính là sự đơn giản. Trình duyệt gửi yêu cầu, máy chủ trả lời – thế thôi. Chính sự giản đơn này giúp web phát triển thần tốc, dù sau này phải đánh đổi một chút để tối ưu hiệu năng (như trong HTTP/2, HTTP/3).

image 4 - quochung.cyou PTIT

Cú pháp HTTP cơ bản

Một yêu cầu HTTP, ở mức đơn giản nhất, chỉ là một dòng văn bản. Ví dụ:

GET /page.html↵

Ở đây:

  • GET là phương thức (cách bạn “xin” dữ liệu).
  • /page.html là tài nguyên bạn muốn.
  • Dấu xuống dòng () cho biết bạn đã gõ xong.

Thế là đủ. Khi đó, trình duyệt đã có kết nối TCP/IP với máy chủ rồi, nên nó chỉ cần nói: “Ê, cho tôi file này với!”

HTTP 0.9

Phiên bản đầu tiên, HTTP/0.9 (1991), chỉ biết làm đúng điều này: gửi GET, nhận file HTML, rồi đóng kết nối. Nhìn lại, bạn có thể thấy lạ: “Sao phải viết GET nữa? Chẳng phải mặc định nó chỉ có một lệnh duy nhất đó sao?” Nhưng chính nhờ tầm nhìn xa, sau này các nhà thiết kế bổ sung thêm nhiều phương thức khác (POST, HEAD…), nên cú pháp ban đầu vẫn giữ được tính mở rộng, chạy được với cả các phiên bản http cũ.

HTTP 1.0

HTTP/0.9 chỉ biết GET một file HTML rồi thôi, nhưng đến HTTP/1.0 (1996), web đã có thêm cơ bắp.

Ba phương thức mới quen mặt

  • GET (nâng cấp): Giờ đây không chỉ tải trang HTML, mà còn có thể yêu cầu ảnh, nhạc, video… Ngoài ra, GET còn “thông minh” hơn: có thể hỏi “file này có thay đổi từ lần cuối tôi tải không?” Nếu không, máy chủ chỉ trả lời “yên tâm, bản cũ còn dùng được” (giúp tiết kiệm băng thông).
  • HEAD: Giống như soi bìa sách thay vì đọc cả cuốn. Client chỉ lấy metadata (header HTTP) để kiểm tra thông tin: file còn tồn tại không, có đổi mới không…
  • POST: Đây là bước đột phá. Client có thể gửi dữ liệu lên server, ví dụ form đăng nhập hay đơn hàng mua sắm. Khác với GET, giao thức POST mang dữ liệu trong body request, kín đáo hơn (dù vẫn phải gửi qua HTTPS mới thật sự an toàn).

Header: lời nhắn gửi kèm yêu cầu

HTTP/1.0 bổ sung header – những dòng thông tin thêm để client “dặn dò” server. Ví dụ:

GET /page.html HTTP/1.0
Accept: text/html,application/xhtml+xml
Accept-Encoding: gzip, deflate
User-Agent: MyAwesomeWebBrowser 1.1

Đọc ngắn gọn thì header nói:

  • Tôi muốn nội dung HTML.
  • Tôi có thể nhận file nén (gzip, deflate).
  • Tôi đang dùng trình duyệt tên “MyAwesomeWebBrowser 1.1”

Đây chính là cơ chế giúp web mở rộng: chỉ cần thêm header mới là có thể dạy client/server hiểu thêm “ngôn ngữ” mà không phá vỡ giao thức.

Mã phản hồi: server biết cách trả lời

HTTP/0.9 không có khái niệm mã phản hồi. Nếu lỗi thì server chỉ nhét thông báo vào HTML. Sang HTTP/1.0, lần đầu tiên xuất hiện status code, ba chữ số huyền thoại:

Các mã 2xx, 3xx, 4xx, 5xx chia thành từng nhóm rõ ràng, biến HTTP thành một cuộc hội thoại có quy tắc: client hỏi, server đáp, không phải đoán mò nữa.

2xx – Thành công
  • 200 OK: Mọi thứ ngon lành, dữ liệu đây.
  • 201 Created: POST thành công, đã tạo tài nguyên mới.
  • 202 Accepted: Server nhận rồi, đang xử lý, bạn đợi chút.
  • 204 No Content: Yêu cầu thành công nhưng không có gì để gửi lại (ví dụ: xóa một record).
3xx – Chuyển hướng
  • 300 Multiple Choices: Có nhiều đường, bạn chọn.
  • 301 Moved Permanently: Tài nguyên đã dọn nhà vĩnh viễn (Location header chỉ đường).
  • 302 Moved Temporarily: Tạm thời dọn nhà.
  • 304 Not Modified: File chưa đổi từ lần cuối, cứ dùng bản cũ.
4xx – Lỗi từ phía client
  • 400 Bad Request: Server không hiểu bạn nói gì, sửa cú pháp đi.
  • 401 Unauthorized: Bạn chưa đăng nhập.
  • 403 Forbidden: Đăng nhập rồi nhưng không có quyền.
  • 404 Not Found: Nổi tiếng nhất – giống như bấm số điện thoại sai: “Thuê bao không tồn tại.”
5xx – Lỗi từ phía server
  • 500 Internal Server Error: Lỗi chung chỉ về việc server lỗi
  • 501 Not Implemented: Server không biết xử lý yêu cầu này.
  • 502 Bad Gateway: Proxy/gateway hỏi downstream server nhưng nhận lỗi.
  • 503 Service Unavailable: Server quá tải hoặc đang bảo trì, quay lại sau.

HTTP 1.1

Vì thế, HTTP/1.1 ra đời năm 1997, chỉ 9 tháng sau khi HTTP/1.0 được chuẩn hóa. Nó không thay đổi tận gốc như từ 0.9 → 1.0, nhưng đã tinh chỉnh, bổ sung và quan trọng nhất: đặt ra tiêu chuẩn chính thức cho toàn bộ thế giới web, vốn đang bùng nổ mạnh mẽ.

Đến nay, HTTP/1.1 đã trải qua nhiều lần sửa đổi:

  • RFC 2068 (1997) – bản đầu tiên
  • RFC 2616 (1999) – cập nhật và thay thế
  • RFCs 7230–7235 (2014) – chỉnh sửa, chi tiết hóa hơn (305 trang, gần 100.000 từ!)

Điều này cho thấy: giao thức tưởng chừng “đơn giản” lại phức tạp đến mức cần một bộ luật khổng lồ để mọi trình duyệt, máy chủ, proxy trên thế giới cùng “nói chung một ngôn ngữ”.

Những thay đổi quan trọng trong HTTP/1.1

1. Host header bắt buộc

Trong HTTP/1.0, client gửi request kiểu:

GET /index.html HTTP/1.0

Server hiểu là: à, nó đang xin file index.html từ chính website mà nó đang kết nối đến. Nhưng khi web phát triển, một server có thể chạy hàng trăm website khác nhau trên cùng một địa chỉ IP (virtual hosting). Làm sao server biết client muốn example.com/index.html hay blog.example.com/index.html?

Giải pháp: thêm Host header.

GET / HTTP/1.1
Host: www.google.com

Từ HTTP/1.1 trở đi, mọi request bắt buộc phải có Host. Nhờ đó:

  • Một server duy nhất có thể phục vụ nhiều website.
  • Tiết kiệm tài nguyên IP (IPv4 vốn khan hiếm).

Nếu không có bước này, có thể web sẽ “ngốn” sạch IPv4 từ rất sớm. Ngược lại, nếu hồi đó người ta không chọn giải pháp Host header, mà bắt client phải gửi URL đầy đủ (absolute-form), thì web có thể đã ép buộc sang IPv6 sớm hơn 20 năm.

2. Kết nối bền vững (Persistent connections / Keep-Alive)

HTTP ban đầu hoạt động theo kiểu: một request → một response → đóng kết nối.

Nhưng khi web nhiều hình ảnh, CSS, JS, việc liên tục đóng/mở kết nối TCP cực kỳ lãng phí. Mỗi lần bắt tay TCP tốn thêm vài trăm mili-giây, nhân lên hàng chục file thì người dùng sẽ “chờ dài cổ”.

HTTP/1.1 thay đổi mặc định:

  • Mọi kết nối được giữ mở trừ khi server nói rõ “Connection: close”.
  • Client có thể gửi tiếp nhiều request trên cùng một kết nối.
GET /style.css HTTP/1.1
Host: www.example.com

GET /script.js HTTP/1.1
Host: www.example.com

Trình duyệt có thể “nạp đơn hàng” liên tục thay vì phải xếp hàng lại từ đầu.

Tuy nhiên, điều này đòi hỏi phải có thêm cách xác định “đâu là hết dữ liệu” (vì không còn dấu hiệu đóng kết nối nữa). Do đó, HTTP/1.1 dùng 1 header Content-Length hoặc chunked transfer encoding để báo độ dài nội dung. Khi đó, các bên có thể từ content length và dữ liệu đã nhận được để biết bao giờ là kết thúc.

image 5 - quochung.cyou PTIT
3. Pipelining (nhưng thất bại)

HTTP/1.1 còn đưa ra ý tưởng pipelining: client gửi nhiều request “một lèo” mà không chờ phản hồi, server trả kết quả theo thứ tự.

Ví dụ:

  • Gửi cùng lúc request cho CSS, JS, ảnh.
  • Server trả về lần lượt CSS → JS → ảnh.

Nghe thì hay, nhưng thực tế bị head-of-line blocking: nếu request đầu tiên bị chậm (ví dụ truy vấn DB lâu), thì mọi request sau bị kẹt. Thêm nữa, hỗ trợ pipelining ở proxy và server thường kém (thậm chí lỗi). Vì vậy, pipelining gần như “chết yểu”, không được các trình duyệt phổ biến bật mặc định.

4. Bộ nhớ đệm (Caching) thông minh hơn

Trong HTTP/1.0, chỉ có header Expires: <date>. Nghĩa là:

  • Nếu quá ngày đó → tài nguyên hết hạn, phải tải lại từ server.
  • Nó cứng nhắc, ví dụ bạn muốn cho cache tồn tại 30 phút tính từ lúc tải thì không làm được (vì Expires cần một mốc thời gian cố định).

HTTP/1.1 thì mềm dẻo hơn nhờ Cache-Control:

  • max-age=600 → cache trong vòng 600 giây (10 phút).
  • no-cache → client vẫn lưu cache nhưng luôn xác thực lại với server trước khi dùng.
  • must-revalidate → nếu cache hết hạn thì bắt buộc hỏi server, không được tự ý dùng lại.
  • public / private → phân biệt cache dùng chung (CDN, proxy) hay chỉ cho trình duyệt người dùng.

Ví dụ thực tế:

  • File CSS/JS ít thay đổi → Cache-Control: max-age=604800 (1 tuần).
  • API JSON (luôn muốn dữ liệu mới) → Cache-Control: no-cache, must-revalidate.
  • Ảnh profile user (có thể đổi nhưng không thường xuyên) → Cache-Control: max-age=86400, must-revalidate.

Điểm mạnh: giúp tăng tốc độ web, giảm tải server, nhưng vẫn linh hoạt để dữ liệu quan trọng luôn mới.

5. Nhiều method và tính năng mới

HTTP/1.1 bổ sung khá nhiều method để phù hợp với web ngày càng phức tạp:

  • Method mới
    • PUT: ghi đè hoặc tạo tài nguyên ở URL cụ thể.
    • DELETE: xóa tài nguyên.
    • OPTIONS: hỏi server xem URL đó hỗ trợ những method nào (cần cho CORS).
    • TRACE: gửi request để server trả lại toàn bộ nội dung nó nhận → debug.
    • CONNECT: dùng cho tunneling (thường thấy khi HTTPS đi qua proxy).
  • Proxy support
    Cho phép request/response đi qua nhiều server trung gian (caching proxy, load balancer, CDN).
  • Authentication
    Có sẵn cơ chế như BasicDigest Authentication để bảo vệ tài nguyên.
  • Cookies
    HTTP vốn stateless (mỗi request độc lập, không nhớ “ai với ai”).
    Cookie giúp “nhớ” trạng thái (user đã login, giỏ hàng, session…).
  • Charset & Content-Language
    Giúp server khai báo tài liệu dùng encoding gì (UTF-8, ISO-8859-1…) và ngôn ngữ (en, vi…).
  • Status code mới
    Bổ sung thêm nhiều mã: 409 Conflict, 410 Gone, 100 Continue… để mô tả chính xác hơn tình huống.
  • Trailing headers & mở rộng headers vô hạn
    Cho phép thêm metadata bổ sung sau phần body (dùng trong chunked transfer).
    HTTP/1.1 cũng gỡ bỏ giới hạn số lượng header → dễ mở rộng (các API hiện nay tận dụng điều này rất nhiều, như Authorization, X-Request-ID, Content-Security-Policy).

Các vấn đề của HTTP/1.1

Sau khi HTTP/1.1 được chuẩn hóa, nó trở thành nền móng của web suốt 20 năm. Nhưng chính sự bùng nổ website đã làm lộ rõ giới hạn của giao thức này.

image 8 - quochung.cyou PTIT

Theo thống kê từ HTTP Archive:

  • Năm 2010, trang web trung bình ~ 700 KB, khoảng 50 request.
  • Đến 2018, tăng lên gần 1.8 MB, hơn 80–90 request.
  • Một số website lớn:
    • Wikipedia: chỉ 7 request, 0.06 MB (rất tối ưu).
    • Facebook: 172 request, 2.2 MB.
    • Amazon: 136 request, 4.46 MB.
    • Yahoo: 240 request, 3.8 MB.

Nguyên nhân: website ngày càng giàu media (ảnh, video), nhiều framework và script, và dùng AJAX để cập nhật động liên tục.

Các vấn đề performance của HTTP/1.1 và cách giải quyết tạm thời vào thời điểm này

Hãy tưởng tượng bạn mở một trang web rất đơn giản: chỉ có một ít văn bản và hai tấm hình.

  • Mỗi yêu cầu (request) từ trình duyệt đến server tốn 50ms để di chuyển qua mạng internet.
  • Máy chủ lấy dữ liệu từ file server, xử lý trong 10ms rồi trả về.
  • Trình duyệt khi nhận được ảnh, xử lý thêm 10ms trước khi gửi tiếp yêu cầu mới.
image 7 - quochung.cyou PTIT

Tổng cộng, để hiển thị xong trang này cần 360ms. Trong đó chỉ 60ms là thời gian xử lý thực sự ở máy khách và máy chủ; còn lại 300ms (hơn 80%) chỉ để… chờ dữ liệu qua lại trên đường truyền. Trong lúc chờ, cả server lẫn trình duyệt đều “ngồi chơi xơi nước”

Điều bất hợp lý lộ rõ ở mốc 120ms: trình duyệt vừa gửi yêu cầu lấy ảnh số 1 và biết chắc là sẽ cần ảnh số 2, nhưng không thể gửi ngay. Nó buộc phải chờ ảnh 1 trả về xong, kết nối mới “rảnh” để gửi tiếp yêu cầu ảnh 2 vào lúc 240ms. Cách vận hành này làm mọi thứ chậm hẳn đi.

Trình duyệt hiện đại thường mở 6 kết nối / domain. Nếu website có nhiều file, chúng được tải trên 6 “đường ống” cùng lúc.

Dùng nhiều kết nối HTTP

Để tăng hơn nữa, nhiều trang web dùng domain sharding: chia file ra các subdomain như static.example.com, cdn.example.com… Như vậy mỗi domain được thêm 6 kết nối, tăng khả năng song song.

Ví dụ: StackOverflow tải:

  • jQuery từ Google,
  • Script/CSS từ cdn.static.net,
  • Ảnh từ i.stack.imgur.com.
image 9 - quochung.cyou PTIT

Nhưng: giải pháp này lại tạo ra gánh nặng:

  • TCP handshake: mỗi kết nối mới cần 3 bước SYN → SYN-ACK → ACK (tốn 1.5 round-trip).
  • TCP slow start: kết nối mới ban đầu chỉ gửi ít gói, tăng dần khi chứng minh mạng chịu tải. Nghĩa là request/response đầu tiên thường bị “thắt cổ chai”.
  • HTTPS handshake: thêm vài vòng trao đổi nữa để thiết lập mã hóa.
image 10 - quochung.cyou PTIT

Mozilla từng thống kê: 74% kết nối HTTP/1 chỉ dùng cho 1 transaction duy nhất → tức nhiều kết nối mở ra xong gần như bỏ phí.

Kết luận: mở nhiều kết nối giúp đỡ phần nào, nhưng chỉ là giải pháp tình thế. Nó tự tạo thêm chi phí (handshake, slow start, bộ nhớ) trong khi bản chất vấn đề vẫn là độ trễ.

Giảm số lượng request

Cách khác: thay vì tăng kết nối, hãy giảm số lần gọi.

  • Cache: dùng HTTP header Cache-Control, Expires để client lưu lại file, không tải lại nữa.
  • Sprite images: gộp nhiều icon nhỏ thành một ảnh lớn rồi dùng CSS để “cắt” ra (TinyPNG hay dùng cách này).
image 11 - quochung.cyou PTIT
  • Concatenate & Minify: gộp nhiều file CSS/JS → một file duy nhất, đồng thời xóa khoảng trắng, comment.
  • Inline: chèn CSS/JS/ảnh trực tiếp vào HTML (dùng <style>, <svg>, hoặc base64).

Nhược điểm:

  • Phức tạp: tạo sprite, gộp file cần phải triển khai từ trước.
  • Lãng phí: tải cả sprite 100 icon trong khi chỉ cần 2.
  • Cache khó quản lý: một thay đổi nhỏ buộc user tải lại cả file lớn (dù chỉ đổi một dòng CSS).

SPDY

Nguồn gốc: năm 2009, Google (Mike Belshe & Robert Peon) giới thiệu SPDY (đọc là “speedy”). Họ chạy thử nghiệm trên bản sao của 25 trang web lớn nhất và thấy cải thiện tốc độ tải trang rất rõ rệt, tới mức hai chữ số phần trăm trong nhiều trường hợp.

Ý tưởng chính của SPDY: không thay đổi “nghĩa” của HTTP (vẫn có GET/POST/headers/body), nhưng thay đổi cách đóng gói và gửi các yêu cầu/response sao cho nhanh hơn và hiệu quả hơn với mạng hiện có. SPDY làm 4 việc quan trọng:

  1. Multiplexing (đa luồng trên một kết nối):
    • Trước HTTP/1.1: trình duyệt mở nhiều kết nối TCP (mỗi tên miền thường ~6 kết nối đồng thời). Mỗi kết nối chỉ xử lý lần lượt một yêu cầu → xuất hiện “waterfall” (đọan thác): các nhóm tài nguyên phải chờ lượt.
    • SPDY: mọi request/response được chia thành các streams nhỏ, gửi xen kẽ qua một kết nối TCP duy nhất. Kết quả: tài nguyên có thể được gửi song song mà không cần mở thêm TCP connection.
  2. Request prioritization (ưu tiên yêu cầu):
    • Không phải mọi tài nguyên đều quan trọng như nhau. SPDY cho phép trình duyệt nói với server tài nguyên nào quan trọng hơn (ví dụ: CSS quan trọng hơn ảnh nền). Server có thể ưu tiên gửi trước các stream quan trọng.
  3. Header compression:
    • HTTP/1.1 gửi header dạng text cho từng yêu cầu, với nhiều request, header trùng lặp (cookie, user-agent, accept,…) gây lãng phí. SPDY nén header để giảm kích thước và số byte truyền.
    • Nếu mỗi request gửi một header Cookie: SESS=... dài 200 B, 360 request = 72 KB chỉ cho cookie. Header compression giảm con số này mạnh mẽ.
  4. Server push (đẩy từ server):
    • Server có thể “đẩy” các tài nguyên mà nó dự đoán client sẽ cần (ví dụ: CSS, JS) ngay khi client yêu cầu HTML, tránh vòng lặp client → server hỏi “tôi cần CSS không?” → server mới trả.

Vì các tính năng như multiplexing và framing yêu cầu cấu trúc nhỏ gọn, chính xác và dễ phân mảnh, điều khó/không thể làm tốt khi gửi toàn bộ theo dạng text line-based (như HTTP/1.x). Binary framing cho phép định nghĩa các frame nhỏ (HEADERS, DATA, SETTINGS, WINDOW_UPDATE, v.v.) để truyền và ghép lại chính xác.

Kết quả thực tế: triển khai SPDY ở Chrome và trên các dịch vụ Google cho thấy tốc độ tải trang cải thiện rõ rệt; nhiều browser/server khác cũng nhanh chóng bổ sung hỗ trợ. Tuy nhiên SPDY chỉ là bước trung gian: nó chứng minh các ý tưởng thực tế và trở thành nền tảng để chuẩn hóa HTTP/2.

image 6 - quochung.cyou PTIT

Ảnh: Sau khi HTTP/2 ra đời, SPDY bắt đầu giảm xuống

HTTP/2

SPDY chứng minh rằng các cải tiến có thể làm được, IETF (HTTP Working Group) dựa trên SPDY khi soạn HTTP/2 (bản draft đầu vào cuối 2012). HTTP/2 giữ hầu hết ý tưởng tốt của SPDY nhưng chuẩn hoá chúng, bổ sung một số cải tiến (ví dụ HPACK cho header compression) và xử lý các chi tiết tương thích/ bảo mật.

Tại sao dùng HTTP/2 thay vì HTTP/1.2?

HTTP/2 được tạo ra để giải quyết các vấn đề hiệu suất của HTTP/1, và phiên bản mới này thêm vào các khái niệm sau:

  • Giao thức nhị phân thay vì văn bản.
  • Ghép kênh (multiplexed) thay vì đồng bộ (synchronous).
  • Kiểm soát dòng chảy (flow control).
  • Ưu tiên luồng (stream prioritization).
  • Nén tiêu đề (header compression).
  • Đẩy từ máy chủ (server push).

Những khái niệm này (sẽ được mô tả chi tiết hơn trong chương) là những thay đổi cơ bản, phá vỡ tính tương thích ngược; nghĩa là, máy chủ web HTTP/1.0 có thể hiểu tin nhắn HTTP/1.1 và bỏ qua các chức năng thừa, nhưng điều này không đúng với tin nhắn HTTP/2, vì chúng có cấu trúc và định dạng khác. Vì lý do đó, HTTP/2 được coi là bản nâng cấp lớn..

Nhị phân thay vì văn bản

Một khác biệt chính giữa HTTP/1 và HTTP/2 là HTTP/2 là giao thức nhị phân dựa gói, trong khi HTTP/1 hoàn toàn dựa văn bản. Giao thức dựa văn bản dễ hiểu cho con người nhưng khó phân tích cho máy tính. Điều này chấp nhận được cho giao thức yêu cầu-phản hồi đơn giản mà HTTP bắt đầu, nhưng ngày càng hạn chế cho internet hiện đại.

Với giao thức văn bản, yêu cầu phải gửi và phản hồi nhận đầy đủ trước khi xử lý yêu cầu khác. HTTP hoạt động thế này 20 năm qua, dù có cải tiến nhỏ. HTTP/1.0 giới thiệu thân HTTP nhị phân, ví dụ, nơi hình ảnh và media khác có thể gửi trong phản hồi, và HTTP/1.1 giới thiệu pipelining và mã hóa chunked. Mã hóa chunked cho phép gửi phần thân tin nhắn trước, phần còn lại theo sau khi sẵn sàng. Thân HTTP chia thành chunk, và client nhận chunked response (hoặc server nhận chunked request) có thể bắt đầu xử lý trước khi nhận đầy đủ. Kỹ thuật này thường dùng khi độ dài dữ liệu tạo động không biết trước. Cả chunked encoding và pipelining có vấn đề chặn đầu hàng (HOL blocking), nơi tin nhắn đầu hàng ngăn phản hồi sau gửi, chưa kể pipelining không được hỗ trợ tốt trong thế giới thực.

HTTP/1.1
  • Định dạng: toàn bộ request/response là văn bản (ASCII/UTF-8).
    • Request line: GET /index.html HTTP/1.1
    • Headers: Host: example.com\r\n...
    • Body: theo sau, độ dài được báo bằng Content-Length hoặc chunked encoding.
  • Kết quả:
    • Parser phải đọc dấu hiệu kết thúc dòng (\r\n) để tách header.
    • Nếu header/body to, trình duyệt phải chờ nhiều (buffering).
    • Chỉ một request/response được xử lý trên một TCP connection tại một thời điểm → dễ nghẽn (head-of-line blocking).
    • Không có khái niệm “frame type”, tất cả đều là text nối tiếp nhau.
HTTP/2 – mọi thứ là frame
  • Cấu trúc rõ ràng, nhị phân: mỗi frame có header 9 byte cố định + payload.
    • Length (3 byte): kích thước payload.
    • Type (1 byte): phân loại (DATA, HEADERS, SETTINGS, PING, …).
    • Flags (1 byte): đánh dấu (END_STREAM, END_HEADERS, …).
    • Stream ID (4 byte, thực tế 31 bit): thuộc về stream nào.
    • Payload: dữ liệu thực (body, header block, …).
  • Ưu điểm:
    • Parser biết ngay độ dài frame → không cần scan ký tự xuống dòng.
    • Có thể ghép nhiều request/response song song trên một TCP connection (multiplexing, sẽ giải thích thêm ở dưới), nhờ phân biệt bằng stream ID.
    • Cho phép mở rộng: frame mới có thể được định nghĩa thêm mà không phá chuẩn.
    • Có nhiều loại frame khác nhau cho mục đích cụ thể: HEADERS, DATA, PRIORITY, WINDOW_UPDATE, PUSH_PROMISE… → kiểm soát chi tiết mà h1.1 không làm được.
image 16 - quochung.cyou PTIT

Cấu trúc frame header (9 bytes đầu):

  • Length (3 bytes): độ dài payload.
  • Type (1 byte): loại frame (HEADERS, DATA, SETTINGS, …).
  • Flags (1 byte): cờ tùy theo frame type.
  • R (1 bit): reserved, không dùng.
  • Stream Identifier (31 bits): xác định stream mà frame thuộc về.
  • Payload: dữ liệu thực, độ dài như Length.

Frame types (10 chuẩn + mở rộng):

  • DATA (0x0): payload chính.
  • HEADERS (0x1): HTTP headers + priority.
  • PRIORITY (0x2): thay đổi độ ưu tiên, dependency.
  • RST_STREAM (0x3): kết thúc stream (thường khi lỗi).
  • SETTINGS (0x4): config connection.
  • PUSH_PROMISE (0x5): server thông báo sẽ push object.
  • PING (0x6): test RTT.
  • GOAWAY (0x7): đóng connection, không nhận stream mới.
  • WINDOW_UPDATE (0x8): điều khiển flow (bytes còn có thể nhận).
  • CONTINUATION (0x9): nối thêm headers khi HEADERS quá dài.
  • Extension frames: cho phép mở rộng mà không phá protocol.
image 17 - quochung.cyou PTIT
t=   1646 [st=      1]    HTTP2_SESSION_RECV_SETTINGS
t=   1647 [st=      2]    HTTP2_SESSION_RECV_SETTING
                          --> id = "1 (SETTINGS_HEADER_TABLE_SIZE)"
                          --> value = 4096
t=   1647 [st=      2]    HTTP2_SESSION_RECV_SETTING
                          --> id = "5 (SETTINGS_MAX_FRAME_SIZE)"
                          --> value = 16384
t=   1647 [st=      2]    HTTP2_SESSION_RECV_SETTING
                          --> id = "6 (SETTINGS_MAX_HEADER_LIST_SIZE)"
                          --> value = 131072
t=   1647 [st=      2]    HTTP2_SESSION_RECV_SETTING
                          --> id = "3 (SETTINGS_MAX_CONCURRENT_STREAMS)"
                          --> value = 100
t=   1647 [st=      2]    HTTP2_SESSION_RECV_SETTING
                          --> id = "4 (SETTINGS_INITIAL_WINDOW_SIZE)"
                          --> value = 65536

Ghép kênh (multiplexing) trên một kết nối

Ý tưởng chính:
HTTP/2 biến mỗi yêu cầu thành một luồng (stream) riêng, và tất cả luồng đi chung một “đường ống TCP duy nhất”.

  • Yêu cầu được cắt thành các khung (frame) nhỏ.
  • Mỗi khung có ID luồng để biết nó thuộc yêu cầu nào.
  • Server và client chỉ việc ghép các khung lại → tái tạo thành thông điệp HTTP hoàn chỉnh.

Lợi ích:

  • Không bị nghẽn đầu hàng (head-of-line blocking ở mức HTTP): Nếu một yêu cầu chậm (ví dụ tải ảnh lớn), nó không làm tắc nghẽn các yêu cầu khác. Server có thể trả xen kẽ: một khung ảnh, rồi một khung CSS, rồi tiếp tục ảnh…
  • Chỉ cần một kết nối TCP duy nhất cho mọi tài nguyên → giảm overhead handshake, giảm tiêu tốn tài nguyên.
  • Server có thể quyết định ưu tiên: gửi CSS/JS quan trọng trước, hình ảnh phụ sau → tăng tốc độ hiển thị trang.

Ví dụ:
Cần tải 3 tài nguyên: styles.css, main.js, logo.png.

  • Với HTTP/1.1 pipelining:
    • Client gửi cả 3 yêu cầu nối tiếp.
    • Server phải trả đúng thứ tự: CSS → JS → PNG. Nếu file CSS to hoặc bị trễ, hai file sau cũng kẹt lại.
image 12 - quochung.cyou PTIT
  • Với HTTP/2 multiplexing:
    • Client gửi cả 3 yêu cầu trên cùng một kết nối.
    • Server có thể trả: một phần CSS → một phần JS → một phần PNG → tiếp tục CSS…
    • Trình duyệt nhận được khung CSS đầu tiên thì đã có thể render ngay, không cần chờ toàn bộ CSS tải xong.
image 13 - quochung.cyou PTIT
  • Bên trái (Client): trình duyệt gửi nhiều request (GET /styles.css, GET /script.js, GET /image.jpg…).
  • Giữa (HTTP/2 layer): tất cả request/response đều đi qua một kết nối TCP duy nhất nhưng được đóng gói thành nhiều stream. Mỗi stream lại chia nhỏ thành nhiều frame.
  • Bên phải (Server): server nhận request từ nhiều stream cùng lúc, xử lý rồi trả response xen kẽ trở lại trên cùng kết nối TCP.
Bước 1: Client gửi request
  • Client tạo 3 request:
    • Request 2 → GET /styles.css → Stream 5
    • Request 3 → GET /script.js → Stream 7
    • Request 4 → GET /image.jpg → Stream 9

Mỗi request sẽ được đánh số ID luồng (stream ID). Ở đây: 5, 7, 9 (client luôn tạo ID lẻ).

image 15 - quochung.cyou PTIT
Bước 2: HTTP/2 framing layer đóng gói
  • Request không gửi nguyên khối, mà bị chia nhỏ thành frames:
    • Frame chứa headers (ví dụ GET, URL, cookie, header HTTP).
    • Frame chứa body (nếu có).
  • Các frame từ nhiều stream có thể xếp nối tiếp nhau trên cùng kết nối TCP.

Ví dụ: thay vì phải gửi toàn bộ styles.css rồi mới đến script.js, rồi mới đến image.jpg, HTTP/2 sẽ gửi xen kẽ: một frame của Stream 5 (styles.css), sau đó một frame của Stream 7 (script.js), rồi một frame của Stream 9 (image.jpg)…

Bước 3: Server nhận và xử lý
  • Server đọc các frame, ghép lại theo stream ID để tái tạo thành request đầy đủ.
  • Sau đó server xử lý và tạo response.
Bước 4: Server trả response xen kẽ
  • Response cũng được cắt thành frames, rồi trả về client.
  • Các stream response không cần giữ đúng thứ tự yêu cầu ban đầu.
    • Server có thể ưu tiên gửi nhanh CSS (quan trọng để render trang) trước khi trả ảnh JPG.
    • Trong hình: Stream 5 và Stream 7 response đi xen kẽ nhau, trong khi Stream 9 (ảnh JPG) response nối tiếp.

Bạn có thể hình dung:

  • HTTP/1.1 giống như có 6 cái ống nước riêng, mỗi ống chỉ chảy được một loại nước. Nếu một ống nghẹt thì nước loại đó tắc hết.
  • HTTP/2 giống như một ống nước lớn duy nhất, trong đó dòng chảy có nhiều màu khác nhau (đỏ = ảnh, vàng = CSS, xanh = JS). Các giọt nước khác màu có thể luân phiên chạy trong cùng một ống, và khi đến đích thì được lọc ra đúng “xô” tương ứng.
image 14 - quochung.cyou PTIT

Stream prioritization (Ưu tiên dòng)

HTTP/1.x:

  • Trình duyệt phải “xếp hàng” tài nguyên vì giới hạn 6 kết nối/host.
  • Muốn ưu tiên CSS/JS quan trọng thì chỉ đơn giản gửi sớm hơn. Nhưng nếu kết nối đã đầy, thì phải đợi → cơ chế ưu tiên thô sơ, bị giới hạn bởi số kết nối.

HTTP/2:

  • Có thể gửi hàng trăm stream một lúc trên cùng một kết nối.
  • Nếu không có ưu tiên, dễ lãng phí băng thông: ví dụ hình ảnh (không critical) lại chiếm chỗ trước CSS (critical).
  • Cơ chế ưu tiên: Client gửi “hints” về độ ưu tiên (priority) → server chọn phân bổ tài nguyên.
    • Ví dụ: nếu cả CSS và ảnh đang chờ, server sẽ gửi nhiều frame CSS hơn để trình duyệt render trang sớm.
    • Ưu tiên mềm: không phải “gửi cái này trước, cái kia sau”, mà là chia băng thông nhiều–ít giữa các stream.

Flow control

Ta đã thấy ở Multiplexing nghĩa là nhiều stream chia sẻ cùng một TCP. Nhưng nếu client đọc chậm (ví dụ đang pause video), mà server vẫn dồn dữ liệu video về → nghẽn buffer, mất gói, phải retransmit → phí công.

TCP vốn đã có cơ chế window size để tránh sender “dội” dữ liệu quá nhiều so với khả năng nhận của receiver.

Nhưng: TCP coi cả kết nối như một dòng duy nhất. Nếu một luồng dữ liệu (ví dụ video) bị nghẽn, thì nó kéo theo tất cả dữ liệu khác trên cùng connection → “head-of-line blocking”.

  • HTTP/2 chia nhỏ kết nối thành nhiều stream (mỗi request/response).
  • Mỗi stream có cửa sổ riêng (window size), mặc định 65,535 byte.
  • Cơ chế:
    • Server chỉ được gửi tối đa số byte trong cửa sổ.
    • Khi client xử lý xong một phần dữ liệu, nó báo lại bằng WINDOW_UPDATE frame → tăng cửa sổ, cho phép server gửi tiếp.
  • Kết quả: stream nào “chậm” (ví dụ video đang pause) thì chỉ bị tạm ngừng riêng, không ảnh hưởng các stream khác (CSS, JS vẫn chạy bình thường).

Giả sử stream #11 là video, stream #13 là CSS:

  • Ban đầu: cả hai có window = 65,535.
  • Server gửi:
    • Video 10 KB → cửa sổ video còn 55,535.
    • CSS 20 KB → cửa sổ CSS còn 45,535.
  • Client pause video → không gửi WINDOW_UPDATE cho stream #11 → video tạm “đóng băng”.
  • Nhưng client vẫn gửi WINDOW_UPDATE cho stream #13 → CSS tiếp tục tải bình thường.

Lợi ích

  • Tinh chỉnh: quản lý luồng nào cần, luồng nào tạm dừng.
  • Hiệu quả: tránh lãng phí băng thông/memory vào dữ liệu không tiêu thụ.
  • Khả năng phối hợp: đặc biệt quan trọng khi đi qua proxy/CDN với tốc độ khác nhau.

Header compression

Vấn đề trong HTTP/1:

  • Mỗi request lặp lại hàng đống header giống nhau:
    • Cookie (to, nặng).
    • User-Agent, Host, Accept, Accept-Encoding
  • Với hàng trăm request nhỏ, phần header đôi khi to hơn cả body!
  • HTTP/1 chỉ nén body (gzip, deflate), không nén header.

Vấn đề: headers lớn, lặp nhiều (cookie, user-agent, accept, …). Một trang ~140 request, mỗi request ~460 bytes → ~63 KB headers, phần lớn lặp.

Cách làm:

  • Giữ bảng tĩnh (61 header phổ biến, ví dụ :method: GET).
  • Giữ bảng động (lưu các header đã thấy trong connection).
  • Ví dụ: :method: GET có thể là entry số 2 trong bảng. Thay vì gửi "GET" cả chữ, client chỉ gửi số 2.
  • Tương tự, host: example.com có thể được lưu ở entry #62 → request sau chỉ cần gửi 62 thay vì nguyên chuỗi.
  • Với các header mới/chưa có trong bảng, vẫn phải gửi chuỗi.
  • Nhưng để tiết kiệm, HTTP/2 dùng Huffman coding (biểu diễn ký tự bằng bit ngắn hơn cho ký tự thường gặp, dài hơn cho ký tự hiếm gặp).
  • Nhờ đó, chuỗi literal cũng nhỏ gọn hơn nhiều so với ASCII/UTF-8 thẳng.

Ví dụ:

  • Request 1: gửi :authority: www.akamai.com, :method: GET, ... → thêm vào bảng.
  • Request 2: gần giống, chỉ khác :path → chỉ cần gửi index + giá trị mới.
  • Tiết kiệm ~85% dung lượng.

Đẩy từ máy chủ

Cơ chế: server gửi PUSH_PROMISE frame gắn vào một stream hiện có.

  • Stream ID trong PUSH_PROMISE = stream gốc (ví dụ: /index.html).
  • Headers trong PUSH_PROMISE mô phỏng như client sẽ request file đó.
  • Sau đó server gửi DATA của object.

Mục tiêu: đặt tài nguyên vào cache trước khi client yêu cầu.

Rủi ro: push sai (tài nguyên đã cache rồi, hoặc client không cần) → lãng phí băng thông.

[SWE học A.I] Phần 6: Machine Learning Classification

This entry is part 5 of 8 in the series SWE Học A.I

Phân Loại (Classification)

Phân loại là một ứng dụng quan trọng của học máy (machine learning), trong đó chúng ta xem xét một tập hợp các đầu vào, so sánh mỗi đầu vào với một danh sách các lớp (hoặc danh mục) có thể có, và gán mỗi đầu vào vào lớp có khả năng cao nhất.

Quá trình này được gọi là phân loại hoặc phân danh mục, và được thực hiện bởi một bộ phân loại (classifier). Chúng ta có thể sử dụng các lớp để thực hiện các tác vụ đa dạng như nhận diện từ ngữ mà ai đó nói qua điện thoại, xác định loài động vật xuất hiện trong ảnh, hoặc đánh giá một trái cây đã chín hay chưa.

Một cách phổ biến để huấn luyện một bộ phân loại là sử dụng học có giám sát (supervised learning). bắt đầu bằng việc thu thập một tập hợp các mẫu dữ liệu mà chúng ta muốn phân loại, gọi là tập huấn luyện (training set). Đồng thời, chúng ta chuẩn bị một danh sách các lớp hoặc danh mục, chẳng hạn như các loài động vật có thể xuất hiện trong ảnh hoặc thể loại nhạc được gán cho một mẫu âm thanh. Sau đó, chúng ta xem xét từng mẫu trong tập huấn luyện và quyết định lớp nào nên được gán cho mẫu đó. Đây được gọi là nhãn (label) của mẫu.

IDẢnh (mô tả)Đặc trưng (Features)Nhãn (Label)
1Ảnh con vật có lông, 4 chân[lông: có, chân: 4, bay: không, sủa: có]Chó
2Ảnh con vật có cánh, biết bay[lông: không, chân: 2, bay: có, sủa: không]Chim
3Ảnh con vật có lông, 4 chân[lông: có, chân: 4, bay: không, sủa: không]Mèo
4Ảnh con vật có mai cứng[lông: không, chân: 4, bay: không, sủa: không]Rùa

Tiếp theo, chúng ta cung cấp từng mẫu cho máy tính, từng mẫu một, nhưng không cung cấp nhãn. Máy tính xử lý mẫu và đưa ra dự đoán về lớp mà mẫu đó nên được gán. Sau đó, chúng ta so sánh dự đoán của máy tính với nhãn của chúng ta. Nếu dự đoán của bộ phân loại không khớp với nhãn, chúng ta điều chỉnh bộ phân loại một chút để nó có khả năng dự đoán đúng lớp nếu gặp lại mẫu này.

image 2 - quochung.cyou PTIT

Quá trình này được gọi là huấn luyện (training), và chúng ta nói rằng hệ thống đang học (learning). Chúng ta lặp lại quá trình này nhiều lần, thường với hàng nghìn hoặc thậm chí hàng triệu mẫu, được tái sử dụng nhiều lần. Mục tiêu là cải thiện dần thuật toán cho đến khi các dự đoán của nó khớp với nhãn của chúng ta đủ thường xuyên để chúng ta cảm thấy nó sẵn sàng được áp dụng vào thực tế, nơi nó sẽ phân loại chính xác các mẫu mới mà nó chưa từng thấy. Khi đó, chúng ta kiểm tra bộ phân loại với dữ liệu mới để xem nó hoạt động tốt như thế nào và liệu nó đã sẵn sàng để sử dụng thực tế hay chưa.

Phân loại hai phần (Binary Classification)

Để bắt đầu, giả sử dữ liệu đầu vào chỉ thuộc về hai lớp khác nhau. Việc sử dụng hai lớp giúp đơn giản hóa việc thảo luận về phân loại mà không bỏ sót bất kỳ điểm cốt lõi nào. Vì chỉ có hai nhãn (hoặc lớp) khả thi cho mỗi đầu vào, chúng ta gọi đây là phân loại nhị phân (binary classification).

Nhóm kỹ thuật đầu tiên mà chúng ta sẽ xem xét được gọi chung là phương pháp ranh giới (boundary methods). Ý tưởng đằng sau các phương pháp này là chúng ta có thể quan sát các mẫu đầu vào được vẽ trên mặt phẳng và tìm một đường thẳng hoặc đường cong chia không gian sao cho tất cả các mẫu thuộc một nhãn nằm ở một bên của đường cong (hoặc ranh giới), và tất cả các mẫu thuộc nhãn kia nằm ở bên còn lại.

Giả sử chúng ta là nông dân trồng táo. Mỗi quả táo có thể là táo ngọt hoặc táo chua. Nếu chúng ta đo trọng lượng và đường kính của mỗi quả táo, ta có thể phân loại được loại của chúng. Giả sử rằng một chuyên gia nếm thử có thể phân biệt chính xác táo ngọt và táo chua – công việc của máy phân loại là mô phỏng khả năng đó. Với dữ liệu huấn luyện về trọng lượng và đường kính, chúng ta có thể vẽ điểm dữ liệu trên mặt phẳng và tìm một đường (thẳng hoặc cong) để phân chia hai loại táo. Mỗi điểm mới nằm về phía nào thì được gán nhãn tương ứng

image 4 - quochung.cyou PTIT

Khi có táo mới (chưa biết vị), chúng ta chỉ cần xem nó nằm ở bên nào của đường thẳng khi được vẽ trên biểu đồ. Những quả nằm ở phía “ngọt” sẽ được gán lớp “táo ngọt”, và những quả ở phía “chua” sẽ được gán lớp “táo chua”

Khi phân chia như vậy, chúng ta gọi các phần trên mặt phẳng là vùng quyết định (decision regions) hoặc miền, và đường phân chia là ranh giới quyết định (decision boundaries).

Phân loại Đa lớp (Multiclass Classification)

Phân loại nhị phân (binary classification) thường đơn giản và nhanh hơn so với phân loại đa lớp (multiclass classification). Tuy nhiên, trong thực tế, phần lớn dữ liệu có nhiều hơn hai lớp.

May mắn thay, thay vì xây dựng một mô hình phân loại đa lớp phức tạp, chúng ta có thể tạo ra một tập hợp các bộ phân loại nhị phân và kết hợp kết quả của chúng để đưa ra câu trả lời đa lớp.

Một-chọi-tất-cả (One-versus-Rest)

Phương pháp này còn được gọi là Một-chọi-tất-cả (OvR), Một-chọi-hết (OvA), Một-đối-kháng-tất-cả (OAA) hoặc phương pháp liên quan nhị phân (binary relevance method). Giả sử chúng ta có năm lớp dữ liệu, được đặt tên từ A đến E. Thay vì xây dựng một bộ phân loại đa lớp phức tạp để gán một trong năm nhãn này, chúng ta sẽ tạo ra năm bộ phân loại nhị phân đơn giản hơn, mỗi bộ tập trung vào một lớp cụ thể, được đặt tên từ A đến E.

  • Bộ phân loại A xác định liệu một mẫu dữ liệu có thuộc lớp A hay không. Vì đây là bộ phân loại nhị phân, nó tạo ra một ranh giới quyết định (decision boundary) chia không gian dữ liệu thành hai vùng: lớp A và tất cả các lớp còn lại (B, C, D, E). Tên gọi “Một-chọi-tất-cả” bắt nguồn từ đây: lớp A là “một”, còn các lớp B đến E là “tất cả”.
  • Tương tự, Bộ phân loại B xác định liệu mẫu thuộc lớp B hay không, Bộ phân loại C kiểm tra lớp C, và cứ thế cho đến lớp E.
image 5 - quochung.cyou PTIT

Các vùng quyết định của năm bộ phân loại nhị phân. Màu sắc từ tím đến hồng thể hiện xác suất tăng dần rằng một điểm thuộc vào lớp tương ứng.

Để phân loại một mẫu mới, chúng ta chạy mẫu đó qua cả năm bộ phân loại nhị phân, mỗi bộ trả về xác suất mẫu thuộc vào lớp tương ứng. Lớp có xác suất cao nhất sẽ được gán cho mẫu.

image 6 - quochung.cyou PTIT

Trong ví dụ này, bốn bộ phân loại đầu tiên trả về xác suất thấp, trong khi bộ phân loại cho lớp E gán xác suất cao hơn, do đó mẫu được dự đoán thuộc lớp E.

Giả sử bạn cần phân loại ảnh thành năm loài: chó, mèo, chim, cá và thỏ. Thay vì xây dựng một mô hình phân loại phức tạp, bạn tạo năm bộ phân loại nhị phân:

  • Bộ phân loại “chó” xác định liệu ảnh có phải là chó hay không (tức là chó so với mèo, chim, cá, thỏ).
  • Tương tự cho các loài còn lại.

Khi một ảnh mới được đưa vào, mỗi bộ phân loại sẽ trả về một xác suất. Giả sử xác suất là:

  • Chó: 0.1
  • Mèo: 0.3
  • Chim: 0.15
  • Cá: 0.05
  • Thỏ: 0.7

Ảnh sẽ được gán nhãn là “thỏ” vì xác suất cao nhất là 0.7.

chart - quochung.cyou PTIT

Ưu điểm và nhược điểm

Tuy nhiên, với phần cứng hỗ trợ xử lý song song, thời gian có thể được tối ưu tương đương với thời gian của một bộ phân loại.

Ưu điểm:

  • Khái niệm đơn giản, dễ hiểu.
  • Tốc độ xử lý nhanh, đặc biệt khi có thể chạy song song các bộ phân loại trên phần cứng phù hợp.

Nhược điểm:

  • Cần huấn luyện năm bộ phân loại thay vì một.
  • Mỗi mẫu phải được chạy qua năm bộ phân loại để xác định lớp, điều này có thể tốn thời gian khi số lớp lớn hoặc ranh giới phân loại phức tạp.

Một-chọi-một (One-versus-One)

Phương pháp thứ hai, Một-chọi-một (OvO), sử dụng nhiều bộ phân loại nhị phân so với ý tưởng OvR. Ý tưởng chính là xem xét từng cặp lớp trong dữ liệu và xây dựng một bộ phân loại nhị phân cho cặp lớp đó. Vì số lượng cặp lớp tăng nhanh khi số lớp tăng, số lượng bộ phân loại cũng tăng đáng kể. Để đơn giản, hãy xét ví dụ với bốn lớp (A, B, C, D)

image 7 - quochung.cyou PTIT

Quy trình như sau:

  1. Bộ phân loại A-B: Được huấn luyện chỉ với dữ liệu từ lớp A và B, bỏ qua các mẫu không thuộc A hoặc B. Bộ phân loại này tạo ranh giới phân tách giữa A và B, gán mọi điểm dữ liệu vào A hoặc B, ngay cả khi điểm đó thuộc lớp khác.
  2. Tiếp tục xây dựng các bộ phân loại cho các cặp khác: A-C, A-D, B-C, B-D, và C-D. Kết quả là sáu bộ phân loại nhị phân, mỗi bộ xác định mẫu thuộc vào một trong hai lớp cụ thể.
image 9 - quochung.cyou PTIT

Hàng trên: Bộ phân loại cho A-B, A-C, A-D. Hàng giữa: Bộ phân loại cho B-C, B-D. Hàng dưới: Bộ phân loại cho C-D.

Để phân loại một mẫu mới, chúng ta chạy mẫu qua cả sáu bộ phân loại. Mỗi bộ phân loại “bỏ phiếu” cho một trong hai lớp, và lớp nhận được nhiều phiếu nhất sẽ là nhãn dự đoán.

  • Hàng trên: Các phiếu là A, A, A.
  • Hàng giữa: Các phiếu là C, B.
  • Hàng dưới: Phiếu là C.

Kết quả: Lớp A nhận 3 phiếu, B nhận 1, C nhận 2, D nhận 0. Do đó, mẫu được dự đoán thuộc lớp A.

image 8 - quochung.cyou PTIT

Ưu điểm và nhược điểm

  • Ưu điểm:
    • Cung cấp cái nhìn rõ ràng hơn về cách mẫu được đánh giá qua từng cặp lớp, tăng tính minh bạch và khả năng giải thích (explainability).
    • Hữu ích khi các lớp có sự chồng lấn phức tạp, giúp con người dễ hiểu kết quả hơn.
  • Nhược điểm:
    • Số lượng bộ phân loại tăng nhanh theo số lớp. Ví dụ:
      • 4 lớp: 6 bộ phân loại.
      • 5 lớp: 10 bộ phân loại.
      • 20 lớp: 190 bộ phân loại.
      • 30 lớp: 435 bộ phân loại.
image 10 - quochung.cyou PTIT

Phân cụm (Clustering)

Phân cụm có nhãn

Phân cụm là một phương pháp giúp tổ chức dữ liệu thành các nhóm tương tự nhau. Chúng ta đã biết rằng một cách để phân loại các mẫu dữ liệu mới là chia không gian thành các vùng khác nhau, sau đó kiểm tra xem một điểm thuộc vùng nào. Tuy nhiên, một cách tiếp cận khác là nhóm chính dữ liệu huấn luyện thành các cụm (clusters), tức là các tập hợp có đặc điểm tương đồng. Nếu dữ liệu của chúng ta có nhãn (labels), làm thế nào để sử dụng nhãn này để tạo các cụm?

image 11 - quochung.cyou PTIT

Dữ liệu có năm nhãn khác nhau, được biểu thị bằng màu sắc. Với các nhóm tách biệt rõ ràng, chúng ta có thể tạo cụm bằng cách vẽ một đường bao quanh mỗi tập hợp điểm, như ở hình giữa. Nếu mở rộng các đường này cho đến khi chúng giao nhau, sao cho mỗi điểm trên lưới được gán màu theo cụm gần nhất, chúng ta có thể bao phủ toàn bộ không gian như ở hình bên phải.

Phân cụm không nhãn

Phương pháp trên yêu cầu dữ liệu có nhãn. Nhưng nếu dữ liệu không có nhãn thì sao? Trong trường hợp này, chúng ta có thể tự động nhóm dữ liệu không nhãn thành các cụm và áp dụng kỹ thuật vừa mô tả. Đây là một dạng học không giám sát (unsupervised learning), nơi dữ liệu không có nhãn sẵn.

Khi sử dụng thuật toán để tự động tạo cụm từ dữ liệu không nhãn, chúng ta cần chỉ định số lượng cụm mong muốn, thường được ký hiệu bằng chữ k (một ký hiệu tùy ý, không mang ý nghĩa cụ thể). Giá trị k là một siêu tham số (hyperparameter), tức là giá trị chúng ta chọn trước khi huấn luyện hệ thống.

Giá trị k quyết định thuật toán sẽ chia dữ liệu thành bao nhiêu vùng (hay bao nhiêu lớp). Vì thuật toán sử dụng trung bình hình học (geometric means) của các nhóm điểm để tạo cụm, nó được gọi là phân cụm k-means.

Ưu và nhược điểm của việc chọn k

Việc tự do chọn giá trị k vừa là lợi thế vừa là thách thức.

  • Lợi thế: Nếu chúng ta biết trước số lượng cụm cần thiết, chúng ta có thể chỉ định và thuật toán sẽ tạo ra kết quả phù hợp. Tuy nhiên, máy tính không biết ranh giới cụm nên dù chia thành k phần, kết quả có thể không đúng như kỳ vọng. Nếu dữ liệu tách biệt tốt, với các nhóm cách xa nhau, kết quả thường khớp với dự đoán. Nhưng nếu ranh giới cụm mờ hoặc chồng lấn, kết quả có thể bất ngờ.
  • Thách thức: Nếu không biết số lượng cụm phù hợp, việc chọn k quá thấp sẽ không tách biệt dữ liệu hiệu quả, còn chọn k quá cao sẽ khiến các điểm dữ liệu tương tự bị phân vào các lớp khác nhau.
image 12 - quochung.cyou PTIT

Với 200 điểm không nhãn, được sắp xếp rõ ràng thành năm nhóm.

Bây giờ, với các k khác nhau, ta có:

image 13 - quochung.cyou PTIT

Không ngạc nhiên khi k = 5 cho kết quả tốt nhất trong ví dụ này, vì ranh giới cụm dễ nhận thấy. Tuy nhiên, với dữ liệu phức tạp hơn, đặc biệt khi có hơn hai hoặc ba chiều, việc xác định số cụm tối ưu trước đó gần như bất khả thi.

Tối ưu hóa giá trị k

May mắn thay, chúng ta có thể huấn luyện mô hình phân cụm nhiều lần với các giá trị k khác nhau. Bằng cách đo lường chất lượng kết quả, quá trình điều chỉnh siêu tham số (hyperparameter tuning) giúp tự động tìm giá trị k tốt nhất. Nhược điểm là quá trình này tốn tài nguyên tính toán và thời gian. Do đó, việc trực quan hóa dữ liệu trước khi phân cụm rất hữu ích. Nếu chọn được giá trị k tối ưu hoặc một phạm vi giá trị khả thi ngay từ đầu, chúng ta có thể tiết kiệm thời gian và công sức.

Giả lập luồng hoạt động

Thuật toán k-means hoạt động theo các bước sau:

  1. Khởi tạo: Chọn ngẫu nhiên k điểm làm tâm cụm (centroids).
  2. Gán cụm: Gán mỗi điểm dữ liệu vào cụm có tâm gần nhất, dựa trên khoảng cách Euclidean: [latex]d(x, c) = \sqrt{\sum_{i=1}^{n} (x_i – c_i)^2}[/latex]
  3. Cập nhật tâm cụm: Tính lại tâm cụm bằng trung bình của các điểm trong mỗi cụm: [latex]c_j = \frac{1}{n_j} \sum_{i=1}^{n_j} x_i[/latex]
  4. Lặp lại: Lặp bước 2 và 3 cho đến khi tâm cụm không thay đổi hoặc đạt số lần lặp tối đa.

Hãy tưởng tượng bạn là chủ một quán cà phê và có dữ liệu về 10 khách hàng, được biểu thị bằng hai thông tin:

  • Số tiền chi tiêu (nghìn đồng) mỗi lần ghé quán.
  • Số lần ghé quán mỗi tháng.

Dữ liệu không có nhãn, nghĩa là bạn không biết trước khách hàng thuộc nhóm nào (như khách VIP, khách thường, v.v.). Mục tiêu là dùng k-means để chia 10 khách này thành 4 cụm dựa trên thói quen của họ.

Dữ liệu mẫu

Dưới đây là 10 điểm dữ liệu (mỗi điểm là một khách hàng):

Khách hàngChi tiêu (nghìn đồng)Số lần ghé quán
A502
B603
C551
D20010
E22012
F21011
G1005
H1106
I3003
J3104

Giả lập chạy thuật toán k-means với k = 4

Bước 1: Khởi tạo tâm cụm

Thuật toán chọn ngẫu nhiên 4 điểm làm tâm cụm ban đầu. Giả sử các tâm cụm được chọn là:

  • Tâm 1: (50, 2) – Khách A
  • Tâm 2: (200, 10) – Khách D
  • Tâm 3: (100, 5) – Khách G
  • Tâm 4: (300, 3) – Khách I
Bước 2: Gán cụm

Mỗi khách hàng được gán vào cụm có tâm gần nhất, dựa trên khoảng cách Euclidean. Công thức khoảng cách giữa hai điểm ((x_1, y_1)) và ((x_2, y_2)) là:

[ [latex]d = \sqrt{(x_2 – x_1)^2 + (y_2 – y_1)^2}[/latex] ]

Ví dụ, tính khoảng cách từ khách B (60, 3) đến Tâm 1 (50, 2):

[ [latex]d = \sqrt{(60 – 50)^2 + (3 – 2)^2} = \sqrt{10^2 + 1^2} = \sqrt{101} \approx 10.05[/latex] ]

Tương tự, tính khoảng cách từ B đến các tâm còn lại và gán B vào cụm có khoảng cách nhỏ nhất. Sau khi tính toán (giả sử kết quả), các khách hàng được gán như sau:

  • Cụm 1 (Tâm: 50, 2): Khách A, B, C
  • Cụm 2 (Tâm: 200, 10): Khách D, E, F
  • Cụm 3 (Tâm: 100, 5): Khách G, H
  • Cụm 4 (Tâm: 300, 3): Khách I, J
Bước 3: Cập nhật tâm cụm

Tính lại tâm cụm bằng trung bình tọa độ của các điểm trong cụm:

  • Cụm 1 (A: 50, 2; B: 60, 3; C: 55, 1):
    • Trung bình x: ((50 + 60 + 55) / 3 = 55)
    • Trung bình y: ((2 + 3 + 1) / 3 = 2)
    • Tâm mới: (55, 2)
  • Cụm 2 (D: 200, 10; E: 220, 12; F: 210, 11):
    • Trung bình x: ((200 + 220 + 210) / 3 = 210)
    • Trung bình y: ((10 + 12 + 11) / 3 = 11)
    • Tâm mới: (210, 11)
  • Cụm 3 (G: 100, 5; H: 110, 6):
    • Trung bình x: ((100 + 110) / 2 = 105)
    • Trung bình y: ((5 + 6) / 2 = 5.5)
    • Tâm mới: (105, 5.5)
  • Cụm 4 (I: 300, 3; J: 310, 4):
    • Trung bình x: ((300 + 310) / 2 = 305)
    • Trung bình y: ((3 + 4) / 2 = 3.5)
    • Tâm mới: (305, 3.5)
Bước 4: Lặp lại

Lặp lại bước 2 và 3 với các tâm cụm mới. Giả sử sau vài lần lặp, các tâm cụm không thay đổi nữa, ta được kết quả cuối cùng:

  • Cụm 1: Khách A, B, C (khách chi tiêu ít, ghé ít)
  • Cụm 2: Khách D, E, F (khách chi tiêu nhiều, ghé thường xuyên)
  • Cụm 3: Khách G, H (khách chi tiêu trung bình, ghé trung bình)
  • Cụm 4: Khách I, J (khách chi tiêu rất nhiều, ghé ít)
Kết quả:
  • Cụm 1: Khách hàng tiết kiệm, ít ghé quán – có thể là sinh viên hoặc khách vãng lai.
  • Cụm 2: Khách VIP, chi tiêu nhiều và ghé thường xuyên – nên ưu tiên chăm sóc.
  • Cụm 3: Khách trung thành nhưng chi tiêu vừa phải – có tiềm năng tăng chi tiêu.
  • Cụm 4: Khách chi tiêu lớn nhưng hiếm ghé – có thể là khách du lịch hoặc khách đặc biệt.
chart 1 - quochung.cyou PTIT

[SWE học A.I] Phần 3: Một số khái niệm suất thống kê

This entry is part 3 of 8 in the series SWE Học A.I

Xác suất

Xác suất thể hiện mức độ tin tưởng rằng một sự kiện sẽ xảy ra, thường được biểu thị dưới dạng phần trăm. Một số câu hỏi có thể cần câu trả lời bằng xác suất:

  • Khả năng tung được 7 lần mặt sấp trong 10 lần tung đồng xu công bằng là bao nhiêu?
  • Nếu rút một lá bài thì khả năng nhận được một lá Át (A) là bao nhiêu?

Cách phổ biến nhất để biểu thị xác suất là dưới dạng phần trăm, ví dụ: “Có 70% khả năng chuyến bay Vietjet sẽ bị trễ.”

Gọi xác suất này là [latex]P(X)[/latex], trong đó [latex]X[/latex] là sự kiện quan tâm. Tuy nhiên, trong thực tế, thường thấy nó được biểu thị dưới dạng số thập phân (trong trường hợp này là 0.7), và giá trị này phải nằm trong khoảng từ 0.0 đến 1.0.

Khả năng xảy ra (likelihood) tương tự như xác suất.

Có thể phân biệt bằng cách nhận xét xác suất là việc định lượng dự đoán về các sự kiện chưa xảy ra, trong khi khả năng xảy ra là đo lường tần suất của các sự kiện đã xảy ra. Trong thống kê và học máy, chúng ta thường sử dụng khả năng xảy ra – likelihood (dữ liệu từ quá khứ) để dự đoán xác suất (tương lai).

Xác suất của một sự kiện xảy ra phải nằm nghiêm ngặt trong khoảng từ 0% đến 100%, hoặc từ 0.0 đến 1.0. Về mặt logic, điều này có nghĩa là xác suất một sự kiện không xảy ra được tính bằng cách lấy 1.0 trừ đi xác suất của sự kiện đó:

[latex]P(\text{không } X) = 1 – P(X)[/latex]

  • Nếu xác suất trời mưa hôm nay là 0.3 (30%) → xác suất không mưa là 1 – 0.3 = 0.7 (70%).
  • Nếu xác suất bạn đến đúng giờ là 90% → xác suất đi trễ là 10%.

Đây là một điểm khác biệt nữa giữa xác suất và khả năng xảy ra. Tổng xác suất của tất cả các kết quả loại trừ lẫn nhau (mutually exclusive outcomes) của một sự kiện (nghĩa là chỉ một kết quả có thể xảy ra, không phải nhiều kết quả cùng lúc) phải bằng 1.0 hoặc 100%. Tuy nhiên, khả năng xảy ra không tuân theo quy tắc này.

Xác suất & Thống kê

Giả sử có một con xúc xắc 6 mặt, nhưng nó không công bằng, với mặt số 4 có xác suất xuất hiện cao hơn. Bạn tung xúc xắc 100 lần và ghi lại kết quả như sau:

  • Số 1: 15 lần
  • Số 2: 10 lần
  • Số 3: 10 lần
  • Số 4: 40 lần
  • Số 5: 15 lần
  • Số 6: 10 lần

Dựa trên dữ liệu này, xác suất thực nghiệm (empirical probability) của việc tung được số 4 là:

[latex]P(\text{số 4}) = \frac{40}{100} = 0.4[/latex]

Có thể thấy, xác suất là lý thuyết thuần túy về mức độ có khả năng một sự kiện sẽ xảy ra và không cần dữ liệu. Khi dự đoán kết quả của việc tung một con xúc xắc 6 mặt và được số 4. Với tư duy xác suất thuần túy, ta chỉ cần nói rằng xúc xắc có sáu mặt. Giả sử mỗi mặt có khả năng xảy ra như nhau, nên xác suất được số 4 là [latex]\frac{1}{6}[/latex], tức là 16.666%.

Nhưng khi nói về thống kê, có thể thấy thống kê không thể tồn tại nếu không có dữ liệu và sử dụng dữ liệu để khám phá xác suất cũng như cung cấp các công cụ để mô tả dữ liệu. Chúng ta cần tung xúc xắc để thu thập dữ liệu. Nếu chúng ta tung được 30 lần hoặc nhiều hơn, càng nhiều lần càng tốt, chỉ khi đó chúng ta mới có dữ liệu để xác định xác suất được số 4.” Cách tiếp cận này có vẻ ngớ ngẩn nếu giả định xúc xắc công bằng, nhưng nếu xúc xắc không công bằng thì sao? Trong trường hợp đó, thu thập dữ liệu là cách duy nhất để khám phá xác suất tung được số 4.

Xác Suất Kết Hợp (Joint Probabilities)

Giả sử ta có một đồng xu công bằng và một con xúc xắc sáu mặt công bằng. Ta muốn tính xác suất tung được mặt sấp (heads) trên đồng xu và tung được số 6 trên xúc xắc. Đây là hai xác suất riêng biệt của hai sự kiện độc lập, nhưng chúng ta muốn tìm xác suất cả hai sự kiện xảy ra cùng nhau. Đây được gọi là xác suất kết hợp (joint probability).

Với đồng xu có hai mặt, xác suất tung được mặt sấp là [latex]\frac{1}{2}[/latex]. Với xúc xắc sáu mặt, xác suất tung được số 6 là [latex]\frac{1}{6}[/latex]. Nếu hai sự kiện này độc lập (independent, nghĩa là sự kiện này không ảnh hưởng đến sự kiện kia), xác suất kết hợp được tính bằng cách nhân hai xác suất với nhau:

[latex]P(\text{mặt sấp và số 6}) = P(\text{mặt sấp}) \times P(\text{số 6}) = \frac{1}{2} \times \frac{1}{6} = \frac{1}{12}[/latex]

Để hiểu rõ hơn, chúng ta có thể liệt kê tất cả các kết quả có thể xảy ra khi tung đồng xu và xúc xắc.

  • Đồng xu: S (sấp, heads), N (ngửa, tails)
  • Xúc xắc: 1, 2, 3, 4, 5, 6

Kết hợp tất cả các khả năng, ta được 12 kết quả:

S1, S2, S3, S4, S5, S6(*), N1, N2, N3, N4, N5, N6

Kết quả mong muốn là S6 (mặt sấp và số 6), được đánh dấu bằng dấu sao (*). Vì chỉ có 1 kết quả mong muốn trong tổng số 12 kết quả có thể, xác suất là [latex]\frac{1}{12}[/latex]. Điều này phù hợp với phép nhân ở trên, được gọi là quy tắc nhân (product rule):

[latex]P(A \text{ AND } B) = P(A) \times P(B) \text{ (nếu } A \text{ và } B \text{ độc lập)}[/latex]

Xác Suất Hợp (Union Probabilities)

Xác suất kết hợp liên quan đến toán tử AND, nhưng nếu chúng ta muốn tính xác suất xảy ra sự kiện A hoặc sự kiện B? Đây được gọi là xác suất hợp (union probability), tương ứng với toán tử OR trong logic.

Trường hợp sự kiện loại trừ lẫn nhau (Mutually Exclusive Events)

Sự kiện loại trừ lẫn nhau là những sự kiện không thể xảy ra đồng thời. Ví dụ, khi tung một xúc xắc, ta không thể đồng thời được số 4 và số 6. Xác suất hợp trong trường hợp này đơn giản là cộng xác suất của từng sự kiện:

[latex]P(\text{số 4 OR số 6}) = P(\text{số 4}) + P(\text{số 6}) = \frac{1}{6} + \frac{1}{6} = \frac{2}{6} = \frac{1}{3}[/latex]

Trường hợp sự kiện không loại trừ lẫn nhau (Non-Mutually Exclusive Events)

Với các sự kiện có thể xảy ra đồng thời, việc tính xác suất hợp phức tạp hơn. Quay lại ví dụ đồng xu và xúc xắc, giả sử ta muốn tính xác suất tung được mặt sấp OR số 6. Liệt kê lại tất cả các kết quả:

S1, S2, S3, S4, S5, S6, N1, N2, N3, N4, N5, N6

Chúng ta quan tâm đến tất cả các kết quả có mặt sấp (S1, S2, S3, S4, S5, S6) hoặc số 6 (S6, N6). Đếm các kết quả thỏa mãn: S1, S2, S3, S4, S5, S6, N6, tổng cộng 7/12.

Nếu chỉ cộng xác suất, ta được:

[latex]P(\text{mặt sấp}) + P(\text{số 6}) = \frac{1}{2} + \frac{1}{6} = \frac{3}{6} + \frac{1}{6} = \frac{4}{6} = \frac{2}{3}[/latex]

Kết quả này sai vì S6 đã bị đếm hai lần (trong cả nhóm mặt sấp và nhóm số 6). Để khắc phục, chúng ta sử dụng quy tắc tổng (sum rule):

[latex]P(A \text{ OR } B) = P(A) + P(B) – P(A \text{ AND } B)[/latex]

Áp dụng vào ví dụ:

[latex]P(\text{mặt sấp OR số 6}) = P(\text{mặt sấp}) + P(\text{số 6}) – P(\text{mặt sấp AND số 6}) = \frac{1}{2} + \frac{1}{6} – \frac{1}{12} = \frac{6}{12} + \frac{2}{12} – \frac{1}{12} = \frac{7}{12}[/latex]

Xác Suất Có Điều Kiện và Định lý Bayes (Conditional Probability and Bayes’ Theorem)

Xác suất có điều kiện (conditional probability) là xác suất của sự kiện A xảy ra khi biết rằng sự kiện B đã xảy ra, ký hiệu là [latex]P(A|B)[/latex]. Ví dụ, một nghiên cứu tuyên bố rằng 85% bệnh nhân ung thư uống cà phê. Điều này nghe có vẻ khá đáng lo ngại

Định nghĩa xác suất này là [latex]P(\text{uống cà phê} | \text{ung thư}) = 0.85[/latex]. Nhưng điều chúng ta thực sự quan tâm là xác suất ngược lại: [latex]P(\text{ung thư} | \text{uống cà phê})[/latex]. (Bao nhiêu người uống cafe mắc bệnh ung thư)

Sử dụng Định lý Bayes

Định lý Bayes cho phép “lật ngược” xác suất có điều kiện:

[latex]P(A|B) = \frac{P(B|A) \times P(A)}{P(B)}[/latex]

Dựa trên dữ liệu:

  • [latex]P(\text{uống cà phê} | \text{ung thư}) = 0.85[/latex]
  • [latex]P(\text{ung thư}) = 0.005[/latex] (0.5% dân số mắc ung thư)
  • [latex]P(\text{uống cà phê}) = 0.65[/latex] (65% dân số uống cà phê)

Tính:

[latex]P(\text{ung thư} | \text{uống cà phê}) = \frac{P(\text{uống cà phê} | \text{ung thư}) \times P(\text{ung thư})}{P(\text{uống cà phê})} = \frac{0.85 \times 0.005}{0.65} \approx 0.006538[/latex]

Kết quả cho thấy xác suất một người uống cà phê bị ung thư chỉ là 0.65%, rất thấp so với 85% ban đầu. Điều này minh họa rằng hướng của điều kiện rất quan trọng.

Naive Bayes

Định lý Bayes là nền tảng của thuật toán Naive Bayes, một phương pháp học máy phổ biến để phân loại. Thuật toán này giả định các đặc trưng (features) độc lập với nhau, giúp đơn giản hóa tính toán. Ví dụ, trong phân loại văn bản, Naive Bayes có thể dự đoán một bài đánh giá là tích cực hay tiêu cực dựa trên các từ xuất hiện.

Minh họa đơn giản

Giả sử ta xây dựng một mô hình để phân loại email. Ta có dữ liệu:

  • 60% email là spam.
  • Từ “miễn phí” xuất hiện trong 50% email spam và 10% email không spam.

Tính xác suất một email chứa từ “miễn phí” là spam:

Biểu thứcÝ nghĩaGiá trị
[latex]P(\text{spam})[/latex]Xác suất email bất kỳ là spam[latex]0.6[/latex]
[latex]P(\text{“miễn phí”} \mid \text{spam})[/latex]Xác suất email spam chứa từ “miễn phí”[latex]0.5[/latex]
[latex]P(\text{“miễn phí”} \mid \text{không spam})[/latex]Xác suất email không spam chứa từ “miễn phí”[latex]0.1[/latex]
[latex]P(\text{không spam}) = 1 – P(\text{spam}) = 0.4[/latex]Xác suất email không phải spam[latex]0.4[/latex]

[latex]P(\text{spam} | \text{miễn phí}) = \frac{P(\text{miễn phí} | \text{spam}) \times P(\text{spam})}{P(\text{miễn phí})}[/latex]

Với:

  • [latex]P(\text{miễn phí} | \text{spam}) = 0.5[/latex]
  • [latex]P(\text{spam}) = 0.6[/latex]

Xét trường hợp email là spam:

  • [latex]P(\text{spam})[/latex]: Xác suất một email là spam (ví dụ: 30% email là spam).
  • [latex]P(\text{miễn phí} \mid \text{spam})[/latex]: Xác suất email có từ “miễn phí” nếu nó là spam (ví dụ: 80% email spam có từ “miễn phí”).
  • Đóng góp: [latex]P(\text{miễn phí} \mid \text{spam}) \times P(\text{spam})[/latex].

Xét trường hợp email là không spam:

  • [latex]P(\text{không spam})[/latex]: Xác suất một email không phải spam (ví dụ: 70% email không spam).
  • [latex]P(\text{miễn phí} \mid \text{không spam})[/latex]: Xác suất email có từ “miễn phí” nếu nó không spam (ví dụ: 10% email không spam có từ “miễn phí”).
  • Đóng góp: [latex]P(\text{miễn phí} \mid \text{không spam}) \times P(\text{không spam})[/latex].

=> (Hay công thức xác suất toàn phần)

Công thức xác suất toàn phần:

[latex]P(A) = P(A \mid B_1) \cdot P(B_1) + P(A \mid B_2) \cdot P(B_2) + \dots + P(A \mid B_n) \cdot P(B_n)[/latex]

Hoặc viết ngắn gọn bằng tổng:

[latex]P(A) = \sum_{i=1}^{n} P(A \mid B_i) \cdot P(B_i)[/latex]

Chú thích:

  • [latex]A[/latex]: Biến cố cần tính xác suất.
  • [latex]B_1, B_2, \dots, B_n[/latex]: Một phân hoạch của không gian mẫu (các biến cố rời nhau và phủ toàn bộ không gian mẫu).
  • [latex]P(A \mid B_i)[/latex]: Xác suất của [latex]A[/latex] khi biết rằng [latex]B_i[/latex] xảy ra.
  • [latex]P(B_i)[/latex]: Xác suất của biến cố [latex]B_i[/latex].
  • [latex]P(\text{miễn phí}) = P(\text{miễn phí} | \text{spam}) \times P(\text{spam}) + P(\text{miễn phí} | \text{không spam}) \times P(\text{không spam}) [/latex]
  • [latex]= 0.5 \times 0.6 + 0.1 \times 0.4 = 0.34[/latex]

Kết quả:

[latex]P(\text{spam} | \text{miễn phí}) = \frac{0.5 \times 0.6}{0.34} \approx 0.882[/latex]

Email chứa từ “miễn phí” có 88.2% khả năng là spam.

Phân phối nhị thức (Binomial Distribution)

Giả sử ta đang phát triển một động cơ mới và đã thực hiện 1 bài kiểm tra với 10 lần thử. Kết quả là 8 lần thành công và 2 lần thất bại:

✓ ✓ ✓ ✓ ✓ ✘ ✓ ✘ ✓ ✓

Ta kỳ vọng động cơ mới có thể đạt tỷ lệ thành công 90%, nhưng dữ liệu cho thấy chỉ đạt 80% (8 lần thành công trong 10 lần thử trong bài kiểm tra). Mỗi bài kiểm tra tốn thời gian và chi phí, nên ta quyết định quay lại bàn thiết kế để cải tiến.

Tuy nhiên, một kỹ sư trong nhóm cho rằng cần thêm bài kiểm tra và lập luận: “Chúng ta chỉ biết chắc khi thử nhiều hơn. Điều gì xảy ra nếu các bài kiểm tra tiếp theo đạt 9 lần thử hoặc 10 lần thử thành công? Ví dụ, nếu tung đồng xu 10 lần và được 8 lần mặt ngửa, điều đó không có nghĩa đồng xu bị ‘cố định’ ở 80% ngửa trong tất cả các lần.”

Ngay cả với đồng xu công bằng, 10 lần tung không phải lúc nào cũng chia đều kết quả. Xác suất có 5 lần mặt ngửa thường xuyên xảy ra, nhưng ta cũng có thể được 3, 4, 6, hoặc 7 lần. Thậm chí 10 lần mặt ngửa cũng có thể xảy ra, dù rất hiếm. Vậy làm thế nào để xác định?

Và ta có phân phối nhị thức, dùng để đo lường khả năng đạt được [latex]k[/latex] lần thành công trong [latex]n[/latex] lần thử với xác suất thành công là [latex]p[/latex].

image 27 - quochung.cyou PTIT

Ta có hình ảnh [latex]n=10[/latex] lần kiểm tra. Phân phối nhị thức được cài đặt [latex]p=0.9[/latex], hay với mỗi lần kiểm tra, ta có tỉ lệ 90% máy sẽ chạy thành công, và 10% thất bại. (Như kỳ vọng về máy). Và theo biểu đồ, có xác suất đạt 8 lần thành công trong 10 lần là 0,1937 (19,37%).

Phân phối nhị thức là một mô hình xác suất dùng để tính khả năng đạt được đúng [latex]k[/latex] lần thành công trong [latex]n[/latex] lần thử, khi:

  • Mỗi lần thử độc lập với nhau.
  • Mỗi lần thử chỉ có hai kết quả: thành công (success) hoặc thất bại (failure) theo [latex]p[/latex] cố định.

Công thức xác suất nhị thức là:

[latex]P(k) = {n \choose k} p^k (1-p)^{n-k}[/latex]

Trong đó:

  • [latex]n[/latex]: Số lần thử (trials). Trong ví dụ, [latex]n = 10[/latex] (10 bài kiểm tra).
  • [latex]k[/latex]: Số lần thành công mong muốn. Ví dụ, [latex]k = 8[/latex] (8 lần thành công).
  • [latex]p[/latex]: Xác suất thành công mỗi lần thử. Ví dụ, [latex]p = 0.9[/latex] (90%).
  • [latex]1-p[/latex]: Xác suất thất bại mỗi lần thử, ở đây là [latex]0.1[/latex].
  • [latex]{n \choose k}[/latex]: Tổ hợp chập [latex]k[/latex] của [latex]n[/latex], tức số cách chọn [latex]k[/latex] lần thành công trong [latex]n[/latex] lần thử, được tính bằng:

[latex]{n \choose k} = \frac{n!}{k!(n-k)!}[/latex]

Ý nghĩa của các tham số

  • [latex]n[/latex] (số lần thử): Quy mô của thí nghiệm. Số lần thử càng lớn, kết quả càng ổn định và gần với [latex]p[/latex].
  • [latex]k[/latex] (số lần thành công): Số lần mong muốn đạt được kết quả “thành công”. Ví dụ, mong muốn biết xác suất đúng 8 lần thành công.
  • [latex]p[/latex] (xác suất thành công): Tỷ lệ kỳ vọng của sự kiện thành công. Trong ví dụ, [latex]p=0,9[/latex] là tỷ lệ thành công mong muốn của động cơ.
  • [latex]1-p[/latex] (xác suất thất bại): Tỷ lệ thất bại, bổ sung cho [latex]p[/latex].

Ta muốn kiểm tra xem việc đạt 8 lần thành công trên 10 lần thử có hợp lý không, nếu giả định rằng động cơ thực sự có xác suất thành công là 90%. Sử dụng phân phối nhị thức, ta tính xác suất đúng 8 lần thành công như sau:

[latex]P(k = 8) = {10 \choose 8} \times 0.9^8 \times 0.1^2[/latex]

Tính từng thành phần:

  • [latex]{10 \choose 8} = \frac{10!}{8! \cdot 2!} = 45[/latex]
  • [latex]0.9^8 \approx 0.43046721[/latex]
  • [latex]0.1^2 = 0.01[/latex]

Vậy:

[latex]P(k = 8) = 45 \times 0.43046721 \times 0.01 \approx 0.1937 \text{ (19,37%)}[/latex]

Xác suất đạt đúng 8 lần thành công là 19,37%, không phải quá thấp. Tuy nhiên, để đánh giá rủi ro, ta cần tính xác suất đạt 8 hoặc ít hơn lần thành công:

[latex]P(k \leq 8) = \sum_{k=0}^{8} {10 \choose k} \times 0.9^k \times 0.1^{10 – k}[/latex]

Tổng này cho kết quả:

[latex]P(k \leq 8) \approx 0.2639 \text{ (26,39%)}[/latex]

Điều đó có nghĩa là ngay cả khi tỷ lệ thành công thực sự là 90%, thì vẫn có 26,39% khả năng động cơ chỉ đạt 8 hoặc ít hơn lần thành công trong 10 lần thử. Vì vậy việc thử nghiệm nhiều hơn theo ý ban đầu là hợp lý, chưa thể khẳng định cần cải tiến động cơ ngay chỉ với 10 lần thử.

Phương sai, độ lệch chuẩn và phân phối Chuẩn (Normal Distribution)

Phương sai và Độ lệch chuẩn

Trong thống kê, khi mô tả dữ liệu, chúng ta thường quan tâm đến việc đo lường độ chênh lệch giữa giá trị trung bình (mean) và từng điểm dữ liệu. Điều này giúp chúng ta hiểu dữ liệu phân bố “rải rác” như thế nào.

Ví dụ, giả sử ta muốn nghiên cứu số lượng thú cưng mà nhân viên trong văn phòng của ta sở hữu (đây là tổng thể, không phải mẫu). Văn phòng có 7 nhân viên, và số lượng thú cưng họ sở hữu lần lượt là: [0, 1, 5, 7, 9, 10, 14]. Trung bình số thú cưng là:

[latex] \overline{x} = \frac{0 + 1 + 5 + 7 + 9 + 10 + 14}{7} = 6.571 [/latex]

Hãy trừ giá trị trung bình này khỏi mỗi điểm dữ liệu để xem mỗi giá trị cách trung bình bao xa:

Thông tin này hữu ích vì nó cho chúng ta cảm nhận về mức độ phân tán của dữ liệu. Tuy nhiên, làm thế nào để tổng hợp các độ chênh lệch này thành một con số duy nhất để mô tả mức độ phân tán?

Một ý tưởng là lấy trung bình của các độ chênh lệch, nhưng các giá trị âm và dương sẽ triệt tiêu lẫn nhau khi cộng lại. Chúng ta có thể lấy giá trị tuyệt đối (loại bỏ dấu âm), nhưng một cách tiếp cận tốt hơn là bình phương các độ chênh lệch trước khi cộng. Bình phương không chỉ loại bỏ giá trị âm (vì bình phương số âm cho kết quả dương), mà còn làm nổi bật các độ chênh lệch lớn hơn và dễ xử lý hơn về mặt toán học (đạo hàm của giá trị tuyệt đối không đơn giản). Sau đó, lấy trung bình của các giá trị bình phương này để được phương sai (variance), một thước đo mức độ phân tán của dữ liệu.

Công thức phương sai của tổng thể là:

[latex] \sigma^2 = \frac{\sum_{i=1}^{n} (x_i – \mu)^2}{n} [/latex]

Trong đó:

  • [latex]\mu[/latex] là trung bình của tổng thể.
  • [latex]x_i[/latex] là từng giá trị trong tập dữ liệu.
  • [latex]n[/latex] là số lượng phần tử.

Phương sai của số thú cưng là khoảng 21.39. Nhưng con số này có ý nghĩa gì? Phương sai cao cho thấy dữ liệu phân tán nhiều, nhưng vì chúng ta đã bình phương các độ chênh lệch, giá trị này không còn cùng đơn vị với dữ liệu gốc (số thú cưng). Để đưa nó về thang đo ban đầu, chúng ta lấy căn bậc hai của phương sai, được gọi là độ lệch chuẩn (standard deviation):

[latex] \sigma = \sqrt{\sigma^2} [/latex]

Độ lệch chuẩn là khoảng 4.62 thú cưng. Điều này có nghĩa là trung bình, số lượng thú cưng của nhân viên dao động khoảng ±4.62 quanh giá trị trung bình 6.571. Độ lệch chuẩn dễ diễn giải hơn vì nó cùng đơn vị với dữ liệu gốc.

Phân phối liên tục

Hầu hết các ví dụ xác suất từ đầu bài đều là biến rời rạc. Ví dụ:

  • Tung xúc xắc: có đúng 6 kết quả có thể (1, 2, 3, 4, 5, 6)
  • Tung đồng xu: có đúng 2 kết quả có thể (ngửa, sấp)
  • Rút bài: có đúng 52 lá bài khác nhau

NNhưng trong thực tế, nhiều thứ không thể đếm được như vậy:

Bạn đến trạm xe bus. Xe sẽ đến sau bao lâu?

  • 2 phút? 2.5 phút? 2.37 phút? 2.371846 phút?
  • Thời gian có thể là bất kỳ số nào từ 0 đến 15 phút (chu kỳ xe bus)
  • Không có “danh sách” cụ thể các thời gian có thể như với xúc xắc

Nhiệt độ ở Sài Gòn hôm nay là bao nhiêu?

  • 32°C? 32.1°C? 32.15°C? 32.1543°C?
  • Nhiệt độ có thể là bất kỳ số nào trong khoảng (ví dụ 25°C đến 38°C)
  • Bạn có thể đo chính xác đến hàng triệu chữ số thập phân nếu muốn

Tốc độ mạng wifi của bạn bây giờ là bao nhiêu Mbps?

Tốc độ luôn thay đổi và có thể là bất kỳ số nào trong một khoảng

50.2 Mbps? 50.28374 Mbps? 50.283746291 Mbps?

Đặc điểm của Biến Liên tục:
  • Vô số giá trị có thể: Ngay cả trong khoảng nhỏ như [1, 2], vẫn có vô số số thực
  • Không thể liệt kê: Không thể viết danh sách tất cả giá trị có thể như với xúc xắc
  • Độ chính xác tùy ý: Bạn luôn có thể đo chính xác hơn bằng cách thêm chữ số thập phân

Điều quan trọng: Với biến liên tục, xác suất để có chính xác một giá trị cụ thể (như đúng 32.0000000°C) thực tế là 0. Thay vào đó, ta chỉ có thể tính xác suất trong các khoảng (như từ 31.5°C đến 32.5°C).

Thay vì tìm xác suất của một giá trị chính xác, chúng ta chỉ tính xác suất trong các khoảng:

  • Không tìm xác suất cân nặng chính xác 72.8 kg
  • Mà tìm xác suất cân nặng từ 72.7 đến 72.9 kg

Hàm mật độ xác suất (PDF)

Hàm mật độ xác suất (PDF) cho chúng ta biết “mật độ” của xác suất tại từng điểm trên đường cong phân phối. Nói cách khác, PDF cho biết một giá trị cụ thể có khả năng xuất hiện “dày đặc” như thế nào.

image 44 - quochung.cyou PTIT

Hình dung PDF như bản đồ mật độ dân số của một thành phố:

  • Những khu vực có PDF cao = khu vực đông dân
  • Những khu vực có PDF thấp = khu vực ít dân
  • Nhưng để biết có bao nhiêu người sống trong một khu vực cụ thể, ta phải tính diện tích khu vực đó

Tính Xác suất thực tế = Diện tích dưới Đường cong

Để tìm xác suất thực sự, chúng ta cần tính diện tích dưới đường cong PDF:

Xác suất (từ a đến b) = Diện tích dưới đường cong PDF từ a đến b

Ví dụ về cân nặng khi xem xét nhiều cân nặng của chú chó Golden Retriever:

  • Để tìm xác suất cân nặng từ 62 đến 66 pound
  • Chúng ta tính diện tích dưới đường cong PDF trong khoảng [62, 66]

Hàm Phân bố Tích lũy (CDF)

Do trục dọc của PDF không phải là xác suất mà là mật độ xác suất. Để tính xác suất, chúng ta cần tích phân một khoảng giá trị [latex]x[/latex] để tìm diện tích dưới đường cong.

image 37 - quochung.cyou PTIT

Phân phối Chuẩn (Normal Distribution)

Phân phối chuẩn, hay còn gọi là phân phối Gaussian, là một phân phối xác suất hình chuông đối xứng, với phần lớn giá trị tập trung quanh trung bình và độ phân tán được xác định bởi độ lệch chuẩn. Các “đuôi” của phân phối mỏng dần khi xa trung bình.

Phân phối chuẩn là phân phối hình chuông kinh điển, được xác định hoàn toàn bởi hai tham số: trung bình [latex]\mu[/latex] (mean)độ lệch chuẩn [latex]\sigma[/latex] (standard deviation). Trung bình cho biết tâm của đường cong chuông nằm ở đâu, còn độ lệch chuẩn biểu thị độ “rộng” của nó.

image 29 - quochung.cyou PTIT

Ví dụ, hãy xem xét cân nặng của giống chó Golden Retriever. Giả sử trung bình cân nặng là 64.43 pound và độ lệch chuẩn là 2.99 pound

image 38 - quochung.cyou PTIT

Giả sử chúng ta lấy mẫu cân nặng của 50 con Golden Retriever trưởng thành và vẽ chúng trên trục số

image 30 - quochung.cyou PTIT

Chúng ta nhận thấy các giá trị tập trung gần trung bình, và càng xa trung bình thì càng ít giá trị. Để hình dung rõ hơn, chúng ta có thể tạo một biểu đồ tần suất (histogram), chia các giá trị vào các “thùng” (bin) có độ dài bằng nhau. chúng ta thử chia thùng với độ dài 0.5 pound. (Ví dụ 57.4 – 57.9 gộp thành chung 1 bin)

image 32 - quochung.cyou PTIT

Biểu đồ này không cho thấy hình dạng rõ ràng vì các thùng quá nhỏ và mẫu không đủ lớn. Hãy thử tăng kích thước thùng lên 3 pound

image 33 - quochung.cyou PTIT

Bây giờ, chúng ta thấy một hình chuông rõ ràng. Cho thấy mẫu có khả năng tuân theo phân phối chuẩn. Nếu điều chỉnh kích thước thùng phù hợp và chuẩn hóa để tổng diện tích bằng 1 (yêu cầu của phân phối xác suất), chúng ta sẽ có một đường cong chuông gần đúng, như trong hình dưới:

image 34 - quochung.cyou PTIT

Đặc điểm của Phân phối Chuẩn

Phân phối chuẩn có các đặc điểm quan trọng sau:

  • Tính đối xứng: Hai bên đối xứng quanh trung bình.
  • Tập trung quanh trung bình: Phần lớn giá trị nằm gần trung bình.
  • Độ phân tán: Được xác định bởi độ lệch chuẩn.
  • Đuôi mỏng dần: Xác suất giảm dần khi xa trung bình, nhưng không bao giờ chạm 0.

Hàm mật độ xác suất (PDF) của phân phối chuẩn

Hàm mật độ xác suất cho phân phối chuẩn trông như sau:

[latex] f(x) = \frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{(x-\mu)^2}{2\sigma^2}} [/latex]

Trong đó:

  • μ (mu) là trung bình
  • σ (sigma) là độ lệch chuẩn
  • x là giá trị chúng ta muốn tính
image 35 - quochung.cyou PTIT

Hàm Phân bố tích luỹ (CDF) của phân phối chuẩn

Ví dụ, để tìm xác suất một con Golden Retriever có cân nặng từ 62 đến 66 pound, chúng ta cần tính diện tích dưới đường cong trong khoảng này

image 39 - quochung.cyou PTIT

Hàm CDF, với hình dạng cong chữ S (gọi là đường cong sigmoid), biểu thị diện tích dưới đường cong PDF cho đến một giá trị x nhất định

image 40 - quochung.cyou PTIT

Khi tính diện tích từ âm vô cực đến giá trị trung bình 64.43, hàm CDF cho kết quả chính xác là 0.5, tương ứng với 50%! – Tương ứng xác suất 50% một chú chó Golden Retriever có cân nặng rơi vào một nửa bên trái [- âm vô cực, 64.43]

image 41 - quochung.cyou PTIT

Tính hàm CDF cho phân bố chuẩn trong Python

Python
from scipy.stats import norm

mean = 64.43  # giá trị trung bình
std_dev = 2.99  # độ lệch chuẩn

x = norm.cdf(64.43, mean, std_dev)

print(x)  # in ra 0.5

Ta có thể tính diện tích của một khoảng giữa bằng cách trừ hai giá trị diện tích. Nếu muốn tìm xác suất quan sát được một chú chó Golden Retriever có cân nặng từ 62 đến 66 pound, ta tính diện tích đến 66 và trừ đi diện tích đến 62.

image 42 - quochung.cyou PTIT
Python
from scipy.stats import norm

mean = 64.43
std_dev = 2.99

x = norm.cdf(66, mean, std_dev) - norm.cdf(62, mean, std_dev)

print(x)  # in ra 0.4920450147062894

Hàm CDF nghịch đảo (Inverse CDF)

image 43 - quochung.cyou PTIT

Từ hàm CDF, ta có thể tính xác suất một chú chó Golden Retriever có cân nặng từ khoảng [a,b] là 40% hay x. Ví dụ, đôi lúc ta muốn trả lời các câu hỏi như:

  • Ta muốn tìm cân nặng mà 95% chó Golden Retriever nằm dưới mức đó.
Python
from scipy.stats import norm

x = norm.ppf(.95, loc=64.43, scale=2.99)
print(x)  # 69.3481123445849

Điểm Z (Z-Scores)

Việc chuẩn hóa một phân bố chuẩn sao cho giá trị trung bình bằng 0 và độ lệch chuẩn bằng 1, gọi là phân bố chuẩn tắc (standard normal distribution), là rất phổ biến. Điều này giúp dễ dàng so sánh độ phân tán của một phân bố chuẩn với phân bố chuẩn khác, ngay cả khi chúng có giá trị trung bình và phương sai khác nhau.

Điều đặc biệt quan trọng với phân bố chuẩn tắc là nó biểu diễn tất cả giá trị x dưới dạng số độ lệch chuẩn, gọi là điểm Z (Z-scores). Việc chuyển một giá trị x thành điểm Z sử dụng công thức chuẩn hóa cơ bản:

[latex]z = \frac{x – \mu}{\sigma} [/latex]

Ví dụ: Ta có hai ngôi nhà từ hai khu vực khác nhau. Khu A có giá trị trung bình của nhà là 140,000 USD và độ lệch chuẩn 3,000 USD. Khu B có giá trị trung bình 800,000 USD và độ lệch chuẩn 10,000 USD.

Bây giờ, ta có hai ngôi nhà: Nhà A từ khu A trị giá 150,000 USD và nhà B từ khu B trị giá 815,000 USD. Ngôi nhà nào đắt hơn so với giá trung bình của khu vực của nó?

Nếu biểu diễn hai giá trị này dưới dạng số độ lệch chuẩn, ta có thể so sánh chúng so với giá trị trung bình của khu vực. Sử dụng công thức điểm Z:

[latex] z_A = \frac{150000 – 140000}{3000} = 3.333 [/latex]

[latex] z_B = \frac{815000 – 800000}{10000} = 1.5 [/latex]

Như vậy, nhà ở khu A thực sự đắt hơn nhiều so với khu vực của nó so với nhà ở khu B, vì chúng có điểm Z lần lượt là 3.333 và 1.5.

Dưới đây là cách chuyển đổi giá trị x từ một phân bố với trung bình và độ lệch chuẩn cho trước thành điểm Z và ngược lại.

Python
def z_score(x, mean, std):
    return (x - mean) / std

def z_to_x(z, mean, std):
    return (z * std) + mean

mean = 140000
std_dev = 3000
x = 150000

# Chuyển thành điểm Z và ngược lại
z = z_score(x, mean, std_dev)
back_to_x = z_to_x(z, mean, std_dev)

print("Z-Score: {}".format(z))  # Z-Score: 3.3333333333333335
print("Back to X: {}".format(back_to_x))  # Back to X: 150000.0

Hệ số biến thiên (Coefficient of Variation)

Một công cụ hữu ích để đo độ phân tán là hệ số biến thiên (coefficient of variation). Nó so sánh hai phân bố và định lượng mức độ phân tán của mỗi phân bố. Công thức đơn giản: chia độ lệch chuẩn cho giá trị trung bình.

[latex]CV = \frac{\sigma}{\mu} [/latex]

Ví dụ, áp dụng cho hai khu vực:

[latex] CV_A = \frac{3000}{140000} \approx 0.0214 [/latex]

[latex] CV_B = \frac{10000}{800000} = 0.0125 [/latex]

Như vậy, khu A, dù rẻ hơn khu B, có độ phân tán cao hơn, nghĩa là giá nhà ở khu A đa dạng hơn khu B.

Định Lý Giới Hạn Trung Tâm (Central Limit Theorem)

Phân phối chuẩn (normal distribution) hữu ích vì nó xuất hiện rất nhiều trong tự nhiên, chẳng hạn như cân nặng của giống chó Golden Retriever trưởng thành. Tuy nhiên, điều thú vị hơn là phân phối chuẩn còn xuất hiện trong các bối cảnh không liên quan đến tổng thể tự nhiên. Khi chúng ta lấy mẫu đủ lớn từ một tổng thể, ngay cả khi tổng thể đó không tuân theo phân phối chuẩn, phân phối chuẩn vẫn sẽ xuất hiện.

Hãy tưởng tượng ta đang đo lường một tổng thể hoàn toàn ngẫu nhiên và đồng nhất (uniformly random), nơi mọi giá trị từ 0.0 đến 1.0 đều có khả năng xảy ra như nhau, không có giá trị nào được ưu tiên. Điều kỳ diệu xảy ra khi chúng ta lấy các mẫu lớn dần từ tổng thể này, tính trung bình của mỗi mẫu, và vẽ chúng thành biểu đồ histogram:

Python
# Các mẫu từ phân phối đồng nhất sẽ có trung bình tạo thành phân phối chuẩn.
import random
import plotly.express as px

sample_size = 31
sample_count = 1000

# Định lý giới hạn trung tâm, 1000 mẫu, mỗi mẫu chứa 31 số ngẫu nhiên từ 0.0 đến 1.0
x_values = [(sum([random.uniform(0.0, 1.0) for i in range(sample_size)]) / sample_size)
            for _ in range(sample_count)]

y_values = [1 for _ in range(sample_count)]

px.histogram(x=x_values, y=y_values, nbins=20).show()
image 45 - quochung.cyou PTIT

Tại sao các số ngẫu nhiên đồng nhất, khi được lấy mẫu theo nhóm 31 và tính trung bình, lại tạo ra một phân phối gần giống phân phối chuẩn? Chẳng phải mọi số đều có khả năng xảy ra như nhau, và phân phối phải phẳng (flat) thay vì hình chuông (bell-curved) sao?

Đây chính là sự khác biệt giữa biến ngẫu nhiên đơn lẻthống kê của tập hợp biến ngẫu nhiên.

Giải thích hiện tượng: Các số riêng lẻ trong mẫu không tạo ra phân phối chuẩn; chúng tạo ra phân phối đồng nhất (uniform distribution), nơi mọi giá trị đều có xác suất như nhau. Tuy nhiên, khi chúng ta nhóm các số này thành mẫu và tính trung bình, trung bình của các mẫu này sẽ tạo ra một phân phối chuẩn.

A) Nguyên lý tổ hợp (Combinatorial Principle)
  • Khi lấy mẫu n số từ phân phối đồng nhất [0,1], có vô số cách để tạo ra trung bình ≈ 0.5
  • Chỉ có rất ít cách để tạo ra trung bình cực trị (gần 0 hoặc gần 1)
  • Ví dụ: Để có trung bình = 0.1 với 31 số, hầu hết các số phải rất nhỏ (xác suất thấp)
  • Để có trung bình = 0.5, các số có thể phân bố đa dạng (xác suất cao)
B) Quy luật số lớn (Law of Large Numbers)
  • Khi kích thước mẫu tăng, trung bình mẫu hội tụ về kỳ vọng của tổng thể
  • Với phân phối đồng nhất [0,1]: E(X) = 0.5
  • Độ biến thiên của trung bình giảm theo tỷ lệ 1/√n

Đây chính là định lý giới hạn trung tâm, khẳng định rằng những điều thú vị xảy ra khi chúng ta lấy mẫu đủ lớn từ một tổng thể, tính trung bình của mỗi mẫu, và vẽ chúng thành phân phối:

  • Trung bình của các trung bình mẫu bằng trung bình tổng thể.
  • Nếu tổng thể tuân theo phân phối chuẩn, thì trung bình mẫu cũng sẽ tuân theo phân phối chuẩn.
  • Nếu tổng thể không tuân theo phân phối chuẩn, nhưng kích thước mẫu lớn hơn 30, trung bình mẫu vẫn sẽ gần giống phân phối chuẩn.
  • Độ lệch chuẩn của trung bình mẫu bằng độ lệch chuẩn tổng thể chia cho căn bậc hai của kích thước mẫu ([latex]\frac{\sigma}{\sqrt{n}}[/latex])

Những đặc tính này cho phép chúng ta suy ra các thông tin hữu ích về tổng thể dựa trên mẫu, ngay cả khi tổng thể không tuân theo phân phối chuẩn. Nếu bạn chỉnh sửa mã trên và thử với kích thước mẫu nhỏ như 1 hoặc 2, bạn sẽ không thấy phân phối chuẩn xuất hiện. Nhưng khi kích thước mẫu đạt 31 trở lên, phân phối sẽ dần hội tụ về phân phối chuẩn:

image 46 - quochung.cyou PTIT

Số 31 là con số tiêu chuẩn trong thống kê vì đây thường là điểm mà phân phối mẫu hội tụ về phân phối tổng thể, đặc biệt khi đo lường trung bình mẫu hoặc các tham số khác. Khi mẫu có ít hơn 31 phần tử, ta có thể sử dụng phân phối T (T-distribution) thay vì phân phối chuẩn, vì phân phối T có đuôi dày hơn (fatter tails) khi kích thước mẫu nhỏ, phản ánh sự bất định lớn hơn.

image 47 - quochung.cyou PTIT

Khoảng Tin Cậy (Confidence Intervals)

Khoảng tin cậy là một phép tính phạm vi cho thấy chúng ta tin tưởng bao nhiêu phần trăm rằng trung bình mẫu (hoặc tham số khác) nằm trong một phạm vi của trung bình tổng thể.

Ví dụ: Dựa trên mẫu gồm 31 con chó Golden Retriever với trung bình mẫu là 64.408 và độ lệch chuẩn mẫu là 2.05, ta tin tưởng 95% rằng trung bình tổng thể nằm trong khoảng từ 63.686 đến 65.1296. Làm thế nào ta biết được điều này?

Bước 1: Chọn mức tin cậy (Level of Confidence – LOC)

Ta muốn tin tưởng 95% rằng trung bình mẫu nằm trong phạm vi trung bình tổng thể mà ta sẽ tính toán. Đây là mức tin cậy. Chúng ta có thể tận dụng định lý giới hạn trung tâm để suy ra phạm vi này. Trước tiên, ta cần giá trị z tới hạn (critical z-value), tức là phạm vi đối xứng trong phân phối chuẩn đơn vị (standard normal distribution) chứa 95% xác suất ở trung tâm

image 48 - quochung.cyou PTIT
image 49 - quochung.cyou PTIT

Chúng ta sử dụng hàm CDF ngược (inverse CDF). Để có 95% diện tích đối xứng ở trung tâm, chúng ta loại bỏ 5% diện tích ở hai đuôi. Chia đôi 5% này, mỗi đuôi sẽ chứa 2.5% diện tích. Do đó, các giá trị diện tích cần tra cứu là 0.025 và 0.975

Python
from scipy.stats import norm

def critical_z_value(p):
    norm_dist = norm(loc=0.0, scale=1.0)
    left_tail_area = (1.0 - p) / 2.0
    upper_area = 1.0 - ((1.0 - p) / 2.0)
    return norm_dist.ppf(left_tail_area), norm_dist.ppf(upper_area)

print(critical_z_value(p=.95))
# (-1.959963984540054, 1.959963984540054)

Chúng ta tra cứu giá trị x cho diện tích 0.025 và 0.975, từ đó xác định phạm vi trung tâm chứa 95% diện tích. Giá trị z dưới và trên sẽ đối xứng nhau

Kết quả là ±1.95996, tức giá trị z tới hạn bao quát 95% xác suất ở trung tâm của phân phối chuẩn đơn vị. Tiếp theo, ta sử dụng định lý giới hạn trung tâm để tính sai số biên (margin of error – E), tức là phạm vi quanh trung bình mẫu chứa trung bình tổng thể ở mức tin cậy đó. Với mẫu 31 con Golden Retriever có trung bình 64.408 và độ lệch chuẩn 2.05, công thức tính sai số biên là:

[latex]E = z \cdot \frac{s}{\sqrt{n}}[/latex]

Áp dụng sai số biên này vào trung bình mẫu, chúng ta sẽ có khoảng tin cậy.

Python
from math import sqrt
from scipy.stats import norm

def critical_z_value(p):
    norm_dist = norm(loc=0.0, scale=1.0)
    left_tail_area = (1.0 - p) / 2.0
    upper_area = 1.0 - ((1.0 - p) / 2.0)
    return norm_dist.ppf(left_tail_area), norm_dist.ppf(upper_area)

def confidence_interval(p, sample_mean, sample_std, n):
    # Kích thước mẫu phải lớn hơn 30
    lower, upper = critical_z_value(p)
    lower_ci = lower * (sample_std / sqrt(n))
    upper_ci = upper * (sample_std / sqrt(n))
    return sample_mean - lower_ci, sample_mean + upper_ci

print(confidence_interval(p=.95, sample_mean=64.408, sample_std=2.05, n=31))
# (63.68635915701992, 65.12964084298008)

Cách diễn giải: Dựa trên mẫu 31 con Golden Retriever với trung bình mẫu 64.408 và độ lệch chuẩn mẫu 2.05, ta tin tưởng 95% rằng trung bình tổng thể nằm trong khoảng từ 63.686 đến 65.1296. Đây là khoảng tin cậy của chúng ta.

Reference:

  • Data Science from Scratch – Joel Grus
  • Essential Math for Data Science – Thomas Nield
  • Deep Learning – Andrew Glassner

[SWE học A.I] Phần 2: Một số khái niệm toán học

This entry is part 2 of 8 in the series SWE Học A.I

Tổng hợp (Summations)

Tổng hợp (summation) được biểu diễn bằng ký hiệu sigma [latex]\sum[/latex] và được dùng để cộng các phần tử lại với nhau.

Ví dụ, nếu ta muốn lặp qua các số từ 1 đến 5, nhân mỗi số với 2, rồi cộng tổng lại, cách biểu diễn bằng tổng hợp sẽ như sau:

[latex]\sum_{i=1}^{5} 2i[/latex]

Tổng hợp [latex]\sum[/latex] có nghĩa là “cộng một loạt các phần tử lại với nhau”, sử dụng chỉ số i và giá trị tối đa n để biểu thị mỗi lần lặp.

Lũy thừa (Exponents)

Lũy thừa (exponentiation) là phép nhân một số với chính nó một số lần nhất định. Ví dụ, khi ta nâng 2 lên lũy thừa 3 (biểu diễn là [latex]2^3[/latex]), điều đó có nghĩa là nhân ba số 2 với nhau:

[latex]2^3 = 2 \cdot 2 \cdot 2 = 8[/latex]

  • Cơ số (base): Là giá trị được nâng lên lũy thừa (trong ví dụ trên là 2).
  • Số mũ (exponent): Là số lần cơ số được nhân với chính nó (trong ví dụ trên là 3).

Các thuộc tính của lũy thừa

1. Quy tắc nhân (Product Rule)

Khi nhân hai lũy thừa có cùng cơ số, ta cộng các số mũ lại. Ví dụ:

[latex]2^2 \cdot 2^3 = 2^{2+3} = 2^5 = 32[/latex]

Giải thích:

  • [latex]2^2 = 4[/latex], [latex]2^3 = 8[/latex].
  • [latex]4 \cdot 8 = 32[/latex], hoặc trực tiếp: [latex]2^5 = 32[/latex].
  • Quy tắc này chỉ áp dụng khi các lũy thừa có cùng cơ số.

2. Quy tắc chia (Quotient Rule)

Khi chia hai lũy thừa có cùng cơ số, ta trừ số mũ. Ví dụ:

[latex]\frac{x^5}{x^2} = x^{5-2} = x^3[/latex]

Giải thích:

  • Khi chia, các thừa số giống nhau ở tử số và mẫu số triệt tiêu, để lại [latex]x^3[/latex].

3. Lũy thừa âm (Negative Exponents)

Lũy thừa âm biểu thị nghịch đảo của lũy thừa dương. Ví dụ:

[latex]x^{-3} = \frac{1}{x^3}[/latex]

Ví dụ:

  • [latex]\frac{x^5}{x^7} = x^{5-7} = x^{-2} = \frac{1}{x^2}[/latex].
  • Quy tắc nhân vẫn áp dụng cho lũy thừa âm: [latex]x^5 \cdot x^{-3} = x^{5+(-3)} = x^2[/latex].

4. Lũy thừa bằng 0

Bất kỳ số nào (khác 0) nâng lên lũy thừa 0 đều bằng 1:

[latex]x^0 = 1[/latex]

Giải thích:

  • Xét [latex]\frac{x^3}{x^3} = x^{3-3} = x^0[/latex]. Vì một số chia cho chính nó bằng 1, nên [latex]x^0 = 1[/latex].
  • Thuộc tính này đảm bảo tính nhất quán trong các quy tắc lũy thừa.

5. Lũy thừa của lũy thừa (Power Rule)

Khi một lũy thừa được nâng lên một lũy thừa khác, ta nhân các số mũ:

[latex]x^3 = x^{2 \cdot 3} = x^6[/latex]

Giải thích:

  • [latex]x^3 = x^2 \cdot x^2 \cdot x^2 = x^6[/latex].

6. Lũy thừa phân số (Fractional Exponents)

Lũy thừa phân số biểu thị căn (root). Ví dụ:

  • Căn bậc hai: [latex]\sqrt{4} = 4^{\frac{1}{2}} = 2[/latex], vì [latex]2 \cdot 2 = 4[/latex].
  • Căn bậc ba: [latex]\sqrt[3]{8} = 8^{\frac{1}{3}} = 2[/latex], vì [latex]2 \cdot 2 \cdot 2 = 8[/latex].

Ví dụ:

  • [latex]8^{\frac{2}{3}}[/latex] nghĩa là lấy căn bậc ba của 8, rồi bình phương:
    • [latex]\sqrt[3]{8} = 2[/latex].
    • [latex]2^2 = 4[/latex].
    • Do đó, [latex]8^{\frac{2}{3}} = 4[/latex].

Lũy thừa vô tỷ: Lũy thừa với số mũ vô tỷ, như [latex]2^\pi[/latex], có thể được tính xấp xỉ bằng cách sử dụng số hữu tỷ gần đúng. Ví dụ:

[latex]2^\pi \approx 2^{3.1415926535} \approx 8.824977827[/latex]2

Logarit (Logarithms)

Logarit (logarithm) là một hàm toán học tìm số mũ của một cơ số cụ thể để đạt được một số nhất định. Ví dụ, câu hỏi “2 mũ bao nhiêu bằng 8?” được biểu diễn là:

[latex]2^x = 8[/latex]

Câu trả lời là [latex]x = 3[/latex], vì [latex]2^3 = 8[/latex]. Cách biểu diễn bằng logarit là:

[latex]\log_2(8) = 3[/latex]

  • Trong Python, nếu không chỉ định cơ số, hàm log() sử dụng cơ số là số Euler [latex]e \approx 2.718[/latex], phổ biến trong khoa học dữ liệu (data science).
  • Trong các lĩnh vực như đo lường động đất, cơ số mặc định thường là 10.

Giải thích:

  • Cơ số (base) là 2.
  • Logarit tìm số mũ (3) để [latex]2^3 = 8[/latex].
Toán tửThuộc tính lũy thừaThuộc tính logarit
Nhân[latex]x^a \cdot x^b = x^{a+b}[/latex][latex]\log_b (xy) = \log_b x + \log_b y[/latex]
Chia[latex]\frac{x^a}{x^b} = x^{a-b}[/latex][latex]\log_b \left(\frac{x}{y}\right) = \log_b x – \log_b y[/latex]
Lũy thừa[latex]x^a \cdot x^b = x^{a \cdot b}[/latex][latex]\log_b (x^a) = a \log_b x[/latex]
Lũy thừa 0[latex]x^0 = 1[/latex]Không xác định
Nghịch đảo[latex]x^{-a} = \frac{1}{x^a}[/latex][latex]\log_b \left(\frac{1}{x}\right) = -\log_b x[/latex]


Số Euler và Logarit Tự nhiên

Số Euler, ký hiệu [latex]e[/latex], là một hằng số toán học với giá trị xấp xỉ 2.71828, có vai trò quan trọng trong giải tích và các lĩnh vực liên quan.

Tính chất đặc biệt của số Euler nằm ở chỗ đạo hàm của hàm mũ [latex]e^x[/latex] chính là chính nó, giúp đơn giản hóa các phép tính trong các bài toán liên quan đến hàm mũ và logarit.

Xác suất thống kê

Bằng cách nghiên cứu và phân tích dữ liệu đầu vào, chúng ta có thể lựa chọn các thuật toán học máy phù hợp nhất. Những ý tưởng và công cụ hỗ trợ phân tích này thường được tập hợp dưới khái niệm xác suất thống kê. Các khái niệm và ngôn ngữ xác suất thống kê thống kê xuất hiện khắp nơi trong lĩnh vực học máy, từ các bài báo học thuật, chú thích trong mã nguồn, đến tài liệu của các thư viện phần mềm.

Sự ngẫu nhiên (Randomness)

Số ngẫu nhiên đóng vai trò quan trọng trong nhiều thuật toán học máy. Chúng được sử dụng để khởi tạo hệ thống, điều khiển các bước trong quá trình học, và đôi khi còn ảnh hưởng đến kết quả đầu ra.

Việc chọn số ngẫu nhiên đúng cách là yếu tố quyết định: nó có thể tạo ra sự khác biệt giữa một hệ thống học được từ dữ liệu và cho ra kết quả hữu ích, với một hệ thống không học được gì.

Thay vì chọn các số một cách tùy tiện, chúng ta sử dụng nhiều công cụ để kiểm soát loại số cần dùng và cách chọn chúng.

Thông thường, chúng ta chọn một số ngẫu nhiên trong khoảng giới hạn bởi giá trị tối thiểu và tối đa, như khi ai đó yêu cầu bạn “chọn một số từ 1 đến 10”. Trong ví dụ này, lựa chọn của chúng ta bị giới hạn trong một tập hợp hữu hạn các số nguyên từ 1 đến 10. Tuy nhiên, trong thực tế, chúng ta thường làm việc với số thực, có thể nằm giữa các số nguyên. Trong khoảng từ 1 đến 10, có 10 số nguyên, nhưng số lượng số thực là vô hạn.

Khi nói về các tập hợp số, dù ngẫu nhiên hay không, chúng ta thường đề cập đến giá trị trung bình của chúng. Đây là cách đơn giản để mô tả đặc trưng của tập hợp. Có ba cách phổ biến để tính giá trị trung bình, và chúng thường được sử dụng, nên chúng ta sẽ làm rõ tại đây. Hãy lấy ví dụ một danh sách gồm năm số: 1, 3, 4, 4, 13.

  • Trung bình cộng (mean) là giá trị mà chúng ta thường hiểu là “trung bình” trong ngôn ngữ hàng ngày. Nó được tính bằng cách cộng tất cả các phần tử trong danh sách và chia cho số lượng phần tử. Trong ví dụ, tổng các phần tử là 1 + 3 + 4 + 4 + 13 = 25. Có năm phần tử, nên trung bình cộng là 25 / 5 = 5.
  • Mốt (mode) là giá trị xuất hiện nhiều nhất trong danh sách. Trong ví dụ, số 4 xuất hiện hai lần, trong khi các số khác chỉ xuất hiện một lần, nên 4 là mốt. Nếu không có giá trị nào xuất hiện nhiều hơn các giá trị khác, danh sách được coi là không có mốt.
  • Trung vị (median) là giá trị nằm ở giữa khi danh sách được sắp xếp từ nhỏ đến lớn. Trong danh sách đã sắp xếp của chúng ta (1, 3, 4, 4, 13), số 4 nằm ở giữa, nên 4 là trung vị. Nếu danh sách có số phần tử chẵn, trung vị là trung bình cộng của hai phần tử ở giữa. Ví dụ, với danh sách 1, 3, 4, 8, trung vị là (3 + 4) / 2 = 3,5.

Biến ngẫu nhiên và phân phối xác suất (Random Variables and Probability Distributions)

Giả sử bạn là một nhiếp ảnh gia được giao nhiệm vụ chụp ảnh cho một bài báo về bãi xe phế liệu, tập trung vào các xe tải và ô tô hỏng.

Bạn đến một bãi xe chứa nhiều phương tiện hỏng hóc. Sau khi trò chuyện với chủ bãi, bạn đồng ý trả tiền để cô ấy mang từng chiếc xe đến cho bạn chụp ảnh. Để thêm phần thú vị, cô ấy sử dụng một bánh xe quay cũ trong văn phòng, với mỗi khe trên bánh xe đại diện cho một chiếc xe trong bãi, được đánh số từ 1.

image 19 - quochung.cyou PTIT

Mỗi lần bạn trả tiền, cô ấy quay bánh xe. Khi bánh xe dừng, cô ấy ghi lại số ở vị trí trên cùng, lái xe kéo đến và mang chiếc xe tương ứng đến cho bạn. Bạn chụp ảnh, sau đó cô ấy đưa xe trở lại bãi. Nếu muốn chụp chiếc xe khác, bạn trả tiền, cô ấy quay bánh xe, và quy trình lặp lại.

Giả sử bài báo yêu cầu bạn chụp ảnh năm loại xe khác nhau: sedan, bán tải (pickup), minivan, SUV, và wagon. Với mỗi loại xe, bạn muốn biết xác suất nhận được loại xe đó khi bánh xe quay. Để tính toán, bạn đi kiểm tra từng chiếc xe trong bãi và phân loại chúng vào năm nhóm này. Kết quả được thể hiện như sau:

image 20 - quochung.cyou PTIT

Trong gần 950 chiếc xe, minivan chiếm số lượng lớn nhất, tiếp theo là xe bán tải, wagon, sedan, và SUV. Vì mỗi chiếc xe có cơ hội được chọn như nhau, khi quay bánh xe, khả năng cao nhất bạn sẽ nhận được một chiếc minivan.

Nhưng cụ thể, khả năng nhận được minivan cao hơn bao nhiêu?

Để xác định xác suất nhận được từng loại xe, chúng ta chia số lượng xe của mỗi loại cho tổng số xe.

Xác định số lượng từng loại xe
Ví dụ:

  • Minivan: 320 chiếc
  • Pickup: 210 chiếc
  • Wagon: 180 chiếc
  • Sedan: 130 chiếc
  • SUV: 110 chiếc
  • Tổng cộng: 950 chiếc

Tính xác suất bằng cách chia số lượng mỗi loại xe cho tổng số xe

  • Minivan: 320 / 950 ≈ 0.337
  • Pickup: 210 / 950 ≈ 0.221
  • Wagon: 180 / 950 ≈ 0.189
  • Sedan: 130 / 950 ≈ 0.137
  • SUV: 110 / 950 ≈ 0.116

Chuyển sang phần trăm bằng cách nhân với 100

  • Minivan: 0.337 × 100 = 33.7%
  • Pickup: 22.1%
  • Wagon: 18.9%
  • Sedan: 13.7%

Kết quả này cho biết xác suất nhận được từng loại xe, như được minh họa dưới đây:

image 21 - quochung.cyou PTIT

Tổng các xác suất của năm loại xe bằng 1,0, thể hiện quy tắc cơ bản của xác suất: các giá trị phải nằm trong khoảng từ 0 đến 1 và tổng bằng 1.

Dựa trên phân phối xác suất này, chúng ta có thể hình dung một bánh xe quay đơn giản hơn, như sau:

image 22 - quochung.cyou PTIT

Xác suất để con trỏ dừng ở một vùng nhất định tỷ lệ với phần chu vi của vùng đó, được vẽ theo tỷ lệ giống như trong phân phối xác suất.

Thông thường, khi tạo số ngẫu nhiên trên máy tính, chúng ta không sử dụng bánh xe quay mà dựa vào phần mềm để mô phỏng quá trình này. Chẳng hạn, chúng ta có thể cung cấp cho một hàm thư viện danh sách các giá trị, như độ cao của các cột trong phân phối xác suất, và yêu cầu trả về một giá trị. Chúng ta kỳ vọng sẽ nhận được minivan khoảng 34% thời gian, xe bán tải khoảng 26% thời gian, v.v.

Biến ngẫu nhiên

Việc chọn một giá trị ngẫu nhiên từ danh sách các lựa chọn, mỗi lựa chọn có xác suất riêng, đòi hỏi một quy trình cụ thể. Để tiện lợi, chúng ta gói gọn quá trình này vào một khái niệm gọi là biến ngẫu nhiên. (random variable)

Quá trình chọn một giá trị từ phân phối được gọi là rút một giá trị từ biến ngẫu nhiên.

Hàm khối xác suất

Chúng ta đã gọi biểu đồ xác suất là phân phối xác suất, nhưng nó cũng có thể được xem như một hàm. Khi gọi hàm này, nó trả về một loại xe với xác suất tương ứng. Khi chỉ có một số lượng hữu hạn giá trị trả về, như năm loại xe trong ví dụ, chúng ta gọi đó là hàm khối xác suất (probability mass function – pmf) hoặc phân phối xác suất rời rạc. Các thuật ngữ này nhấn mạnh rằng chỉ có một số lượng cố định các kết quả có thể xảy ra.

Chúng ta cũng có thể tạo ra các phân phối xác suất liên tục, thường được sử dụng khi khởi tạo các giá trị trong mạng nơ-ron.

Hãy lấy ví dụ: giả sử bạn muốn biết lượng dầu còn lại trong mỗi chiếc xe mà chủ bãi mang đến. Lượng dầu là một biến liên tục, vì nó có thể là bất kỳ số thực nào.

Biểu đồ phân phối xác suất liên tục cho phép chúng ta tính xác suất nhận được một giá trị trong một khoảng nhất định bằng cách tính diện tích dưới đường cong trong khoảng đó. Chẳng hạn, để tìm xác suất nhận được một chiếc xe có 0,45 đơn vị dầu, chúng ta không chỉ nhìn vào giá trị tại 0,45 mà xem xét một khoảng nhỏ, ví dụ từ 0,44 đến 0,46, và tính diện tích dưới đường cong trong khoảng này. Điều này có nghĩa là đường cong có thể có giá trị lớn hơn 1, miễn là tổng diện tích dưới toàn bộ đường cong bằng 1.

image 23 - quochung.cyou PTIT

Phân phối như thế này được gọi là phân phối xác suất liên tục (continuous probability distribution – cpd) hoặc hàm mật độ xác suất (probability density function – pdf). Đôi khi, thuật ngữ “hàm mật độ xác suất” được sử dụng một cách không chính thức cho cả phân phối rời rạc, nhưng ngữ cảnh thường giúp làm rõ ý nghĩa.

Hiệp phương sai và Tương quan (Covariance and Correlation)

Đôi khi các biến số có thể liên quan với nhau theo những cách thú vị. Hãy tưởng tượng một biến cho chúng ta biết nhiệt độ bên ngoài, và biến kia cho biết khả năng có tuyết rơi. Khi nhiệt độ rất cao, khả năng có tuyết gần như bằng không – việc biết được giá trị của một biến sẽ tiết lộ thông tin quý giá về biến kia. Trong trường hợp này, mối quan hệ mang tính âm tính: khi nhiệt độ tăng cao, khả năng có tuyết giảm xuống, và ngược lại.

Mặt khác, biến thứ hai có thể cho chúng ta biết số lượng người bơi dự kiến tại hồ địa phương. Mối liên hệ giữa nhiệt độ và số người bơi mang tính dương tính, bởi vì trong những ngày ấm áp hơn, chúng ta sẽ thấy nhiều người bơi hơn, và ngược lại.

Khả năng phát hiện những mối quan hệ này và đo lường sức mạnh của chúng có giá trị thực tiễn to lớn. Giả sử chúng ta đang lên kế hoạch huấn luyện một thuật toán để trích xuất thông tin từ tập dữ liệu. Nếu phát hiện ra hai giá trị trong dữ liệu có mối liên hệ chặt chẽ (như nhiệt độ và khả năng có tuyết), chúng ta có thể loại bỏ một trong hai biến đó khỏi dữ liệu vì nó thừa thãi. Điều này không chỉ cải thiện tốc độ huấn luyện mà còn có thể nâng cao chất lượng kết quả.

Trong phần này, chúng ta sẽ khám phá một phép đo gọi là hiệp phương sai (covariance), được các nhà toán học phát triển để xác định sức mạnh của những mối quan hệ này. Chúng ta cũng sẽ tìm hiểu một biến thể gọi là tương quan (correlation), thường hữu ích hơn vì nó không phụ thuộc vào quy mô của các con số liên quan.

Hiệp phương sai

Hãy tưởng tượng chúng ta có hai biến và nhận thấy một mô hình số học cụ thể liên quan đến chúng. Khi giá trị của một biến tăng, biến kia tăng theo một bội số cố định của lượng đó, và điều tương tự xảy ra khi một trong hai biến giảm.

Ví dụ cụ thể: giả sử biến A tăng 3 đơn vị, và biến B tăng 6 đơn vị. Sau đó, B tăng 4 đơn vị, và A tăng 2 đơn vị. Tiếp theo, A giảm 4 đơn vị, và B giảm 8 đơn vị. Trong mọi trường hợp, B tăng hoặc giảm gấp đôi lượng mà A tăng hoặc giảm, vì vậy bội số cố định của chúng ta là 2.

Khi chúng ta quan sát thấy mối quan hệ như vậy (với bất kỳ bội số nào, không chỉ riêng số 2), chúng ta nói rằng hai biến này đồng biến (covary). Chúng ta đo lường sức mạnh của mối liên hệ giữa hai biến, hay tính nhất quán mà chúng đồng biến, bằng một con số gọi là hiệp phương sai. Nếu phát hiện ra rằng khi một giá trị tăng hoặc giảm, giá trị kia cũng làm điều tương tự theo một lượng có thể dự đoán được, thì hiệp phương sai là một số dương, và chúng ta nói rằng hai biến đang thể hiện hiệp phương sai dương.

Cách cổ điển để thảo luận về hiệp phương sai là vẽ các điểm trong không gian 2D

image 25 - quochung.cyou PTIT

Ở đây chúng ta thấy hai tập hợp điểm đồng biến khác nhau. Mỗi điểm có tọa độ x và y, nhưng chúng chỉ là đại diện cho bất kỳ hai biến nào chúng ta muốn so sánh. Sự thay đổi của y theo dõi sự thay đổi của x càng nhất quán, hiệp phương sai càng mạnh.

Nếu một giá trị giảm bất cứ khi nào giá trị kia tăng, chúng ta nói các biến có hiệp phương sai âm.

image 26 - quochung.cyou PTIT

Nếu hai biến không có chuyển động khớp nhau một cách nhất quán như vậy, thì hiệp phương sai bằng không.

image 27 - quochung.cyou PTIT

Khái niệm hiệp phương sai của chúng ta chỉ nắm bắt được mối quan hệ giữa các biến khi sự thay đổi của chúng là bội số của nhau. Hình trên cho thấy rằng có thể có một mô hình rõ ràng trong dữ liệu (ở đây các chấm tạo thành một phần của hình tròn), nhưng hiệp phương sai vẫn bằng không vì các mối quan hệ rất không nhất quán.

Tương quan

Hiệp phương sai là một khái niệm hữu ích, nhưng nó có một vấn đề. Do cách nó được định nghĩa về mặt toán học, nó không tính đến mối quan hệ giữa các đơn vị của hai biến, điều này khiến chúng ta khó so sánh sức mạnh của các hiệp phương sai khác nhau.

Ví dụ, giả sử chúng ta đo một tá biến mô tả một cây đàn guitar: độ dày của gỗ, chiều dài của cần đàn, thời gian một nốt nhạc cộng hưởng, lực căng trên dây đàn, v.v. Chúng ta có thể tìm thấy hiệp phương sai giữa các cặp phép đo khác nhau, nhưng không thể so sánh một cách có ý nghĩa lượng hiệp phương sai để tìm ra cặp nào có mối quan hệ mạnh nhất và yếu nhất. Ngay cả thang đo cũng quan trọng: nếu chúng ta tìm hiệp phương sai cho một cặp phép đo bằng centimet và hiệp phương sai cho một cặp phép đo khác bằng inch, chúng ta không thể so sánh những giá trị đó để nói cặp nào đồng biến mạnh hơn.

Dấu của hiệp phương sai là tất cả những gì chúng ta học được: giá trị dương có nghĩa là mối quan hệ dương, giá trị âm có nghĩa là mối quan hệ âm, và số không có nghĩa là không có mối quan hệ. Chỉ có dấu là một vấn đề, bởi vì chúng ta thực sự muốn so sánh các tập hợp biến khác nhau.

Sau đó, chúng ta có thể tìm ra thông tin hữu ích như biến nào có tương quan dương và âm mạnh nhất và yếu nhất. Chúng ta có thể sử dụng thông tin đó để cắt giảm kích thước tập dữ liệu của mình, ví dụ, bằng cách loại bỏ một trong các phép đo trong một hoặc nhiều cặp có liên quan chặt chẽ.

Để có được một phép đo cho phép chúng ta thực hiện những so sánh này, chúng ta có thể tính toán một con số hơi khác gọi là hệ số tương quan (correlation coefficient), hay đơn giản là tương quan. Giá trị này bắt đầu với hiệp phương sai nhưng bao gồm một bước tính toán bổ sung. Kết quả là một con số không phụ thuộc vào các đơn vị được chọn cho các biến. Chúng ta có thể nghĩ về tương quan như một phiên bản được chia tỷ lệ của hiệp phương sai, luôn cho chúng ta một giá trị giữa −1 và 1. Giá trị +1 cho chúng ta biết chúng ta có tương quan dương hoàn hảo, trong khi giá trị −1 cho chúng ta biết chúng ta có tương quan âm hoàn hảo.

Tương quan dương hoàn hảo dễ nhận biết: tất cả các chấm nằm dọc theo một đường thẳng di chuyển theo hướng đông bắc-tây nam.

image 28 - quochung.cyou PTIT

Loại mối quan hệ nào giữa các điểm mang lại cho chúng ta tương quan dương, nhưng ở đâu đó trong khoảng từ 0 đến 1? Đó là khi giá trị y tiếp tục tăng với x, nhưng tỷ lệ sẽ không cố định. Chúng ta có thể không dự đoán được nó thay đổi bao nhiều, nhưng chúng ta biết rằng sự tăng của x gây ra sự tăng của y, và sự giảm của x gây ra sự giảm của y.

image 29 - quochung.cyou PTIT

Hình trên cho thấy biểu đồ chấm cho một số giá trị dương của tương quan giữa 0 và 1. Các chấm càng gần với việc rơi trên một đường thẳng, giá trị tương quan càng gần với 1. Chúng ta nói rằng nếu giá trị gần số không thì tương quan yếu (hoặc thấp), nếu nó khoảng 0.5 thì trung bình, và nếu nó gần 1 thì mạnh (hoặc cao).

Bây giờ hãy xem xét giá trị tương quan bằng không. Tương quan bằng không có nghĩa là không có mối quan hệ giữa sự thay đổi của một biến và sự thay đổi của biến kia. Chúng ta không thể dự đoán điều gì sẽ xảy ra. Nhớ lại rằng tương quan chỉ là một phiên bản được chia tỷ lệ của hiệp phương sai, vì vậy khi hiệp phương sai bằng không, tương quan cũng vậy.

image 30 - quochung.cyou PTIT

Tương quan âm giống như tương quan dương, chỉ khác là các biến di chuyển theo hướng ngược nhau: khi x tăng, y giảm. Giống như với tương quan dương, nếu giá trị gần số không thì tương quan yếu (hoặc thấp), nếu nó khoảng −0.5 thì trung bình, và nếu nó gần −1 thì mạnh (hoặc cao).

image 31 - quochung.cyou PTIT

Khi hai biến có tương quan dương hoặc âm hoàn hảo (tức là giá trị +1 và −1), chúng ta nói rằng các biến được tương quan tuyến tính, bởi vì (như chúng ta đã thấy) các điểm nằm trên một đường thẳng. Các biến được mô tả bởi bất kỳ giá trị nào khác của tương quan được cho là tương quan phi tuyến tính.

Đạo hàm (Derivative)

Một trong những khía cạnh quan trọng nhất của một đường cong là đạo hàm của nó. Đạo hàm cung cấp thông tin về hình dạng của đường cong tại bất kỳ điểm nào trên đó. Trong phần này, chúng ta sẽ xem xét các ý tưởng cốt lõi dẫn đến khái niệm đạo hàm.

Cực đại và cực tiểu (Maximums and Minimums)

Trong học máy, một nhiệm vụ quan trọng là giảm thiểu sai số của hệ thống. Nói rộng ra, ta muốn tìm cực đại (điểm cao nhất) hoặc cực tiểu (điểm thấp nhất) của đường cong trên toàn bộ chiều dài của nó. Nếu đây là điểm cao nhất hoặc thấp nhất của cả đường cong, ta gọi chúng là cực đại toàn cục (global maximum) và cực tiểu toàn cục (global minimum).

image 33 - quochung.cyou PTIT

Để đơn giản hóa, ta tập trung vào vùng lân cận của một điểm. Hãy tưởng tượng ta đứng tại một điểm trên đường cong và bước sang trái. Nếu con đường dốc lên, ta đi tiếp cho đến khi nó dốc xuống, rồi dừng lại. Nếu con đường dốc xuống, ta dừng khi nó bắt đầu dốc lên. Ta làm tương tự khi bước sang phải. Kết quả, ta có ba điểm: điểm xuất phát, điểm dừng bên trái, và điểm dừng bên phải.

image 34 - quochung.cyou PTIT

Để hiểu sâu hơn về đạo hàm, ta cần làm quen với đường tiếp tuyến. Hãy hình dung đường cong như một con đường uốn lượn. Tại mỗi điểm, ta có thể vẽ một đường thẳng. Đường tiếp tuyến này cho biết con đường đang dốc lên, dốc xuống, hay nằm ngang tại điểm đó.

image 35 - quochung.cyou PTIT

Điều kỳ diệu là tại các điểm cực đại hoặc cực tiểu cục bộ, đường tiếp tuyến luôn nằm ngang, với độ dốc bằng 0. Vì vậy, để tìm cực đại và cực tiểu, ta chỉ cần tìm những điểm mà đường tiếp tuyến nằm ngang.

image 39 - quochung.cyou PTIT

Để vẽ đường tiếp tuyến, ta chọn một điểm, bước một khoảng nhỏ sang trái và phải trên đường cong, đánh dấu hai điểm, rồi nối chúng bằng một đường thẳng. Khi kéo hai điểm này lại gần điểm ban đầu, đường thẳng sẽ trở thành đường tiếp tuyến. Độ dốc của đường này chính là đạo hàm.

image 36 - quochung.cyou PTIT

Đạo hàm không chỉ là một khái niệm toán học trừu tượng, mà còn là công cụ cốt lõi trong học máy. Khi ta muốn giảm sai số, ta sử dụng đạo hàm để xác định hướng di chuyển trên đường cong.

Nếu đạo hàm dương, ta di chuyển ngược hướng để tìm cực tiểu.

image 38 - quochung.cyou PTIT

Nếu đạo hàm âm, ta di chuyển theo hướng dương.

image 37 - quochung.cyou PTIT

Bằng cách lặp lại quá trình này, ta tiến gần đến điểm có đạo hàm bằng 0 – nơi cực đại hoặc cực tiểu cục bộ nằm

Đạo hàm riêng (Partial Derivatives)

Thay vì tính độ dốc trên một hàm một chiều, đạo hàm riêng tính độ dốc theo từng biến, giả sử các biến còn lại được giữ không đổi.

Hãy xem xét hàm [latex]f(x, y) = 2x^3 + 3y^3[/latex]. Đạo hàm riêng theo [latex]x[/latex] và [latex]y[/latex] lần lượt là:

[latex]\frac{\partial f}{\partial x} = 6x^2[/latex]

[latex]\frac{\partial f}{\partial y} = 9y^2[/latex]

Khi làm việc với các hàm đa biến, khái niệm gradient (gradient vector) trở nên quan trọng. Gradient là một vectơ bao gồm tất cả các đạo hàm riêng theo từng biến, chỉ ra hướng và độ lớn của sự thay đổi nhanh nhất của hàm. Với hàm [latex]f(x, y)[/latex], gradient được biểu diễn là:

[latex]\nabla f = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} \right)[/latex]

Trong trường hợp [latex]f(x, y) = 2x^3 + 3y^3[/latex], gradient tại một điểm [latex](x, y)[/latex] là:

[latex]\nabla f = (6x^2, 9y^2)[/latex]

Ví dụ, tại điểm [latex](1, 2)[/latex]:

  • [latex]\frac{\partial f}{\partial x} = 6 \cdot 1^2 = 6[/latex]
  • [latex]\frac{\partial f}{\partial y} = 9 \cdot 2^2 = 36[/latex]
  • Gradient: [latex]\nabla f = (6, 36)[/latex]

Gradient không chỉ cho biết độ dốc theo từng hướng mà còn chỉ ra hướng tăng trưởng nhanh nhất của hàm. Điều này rất quan trọng trong các thuật toán tối ưu hóa như gradient descent, nơi gradient được sử dụng để cập nhật các tham số nhằm giảm thiểu hàm mất mát.

image 1 - quochung.cyou PTIT

Đây là một bề mặt cong với các giá trị [latex]z = f(x, y)[/latex] thay đổi theo [latex]x[/latex] và [latex]y[/latex]. Một số điểm đáng chú ý:

  • Trục [latex]x[/latex] và [latex]y[/latex] đại diện cho các biến đầu vào, trong khi trục [latex]z[/latex] thể hiện giá trị của hàm.
  • Màu sắc thay đổi (từ xanh đậm đến vàng) cho thấy sự thay đổi của giá trị [latex]z[/latex], với các vùng màu vàng thường là đỉnh hoặc đáy của bề mặt.
  • Độ dốc của bề mặt tại một điểm (ví dụ tại [latex](x, y) = (1, 2)[/latex]) có thể được biểu diễn bằng gradient, với thành phần [latex]6[/latex] theo [latex]x[/latex] và [latex]36[/latex] theo [latex]y[/latex].

Quy tắc Chuỗi (Chain Rule).

Giả sử bạn có hai hàm số sau:

  • Hàm thứ nhất: [latex]y = x^2 + 1[/latex]
  • Hàm thứ hai: [latex]z = y^3 – 2[/latex]

Lưu ý rằng hai hàm này có mối liên kết, vì biến [latex]y[/latex] là đầu ra của hàm thứ nhất nhưng lại là đầu vào của hàm thứ hai. Do đó, chúng ta có thể thay hàm [latex]y[/latex] vào hàm [latex]z[/latex] như sau:

[latex]z = (x^2 + 1)^3 – 2[/latex]

Vậy đạo hàm của [latex]z[/latex] theo [latex]x[/latex] là gì? Chúng ta đã có biểu thức thay thế biểu diễn [latex]z[/latex] theo [latex]x[/latex]

Kết quả: [latex]6x(x^2 + 1)^2[/latex]

Vậy đạo hàm của [latex]z[/latex] theo [latex]x[/latex] là:

[latex]\frac{dz}{dx} = 6x(x^2 + 1)^2[/latex]

Tuy nhiên, hãy thử một cách tiếp cận khác. Nếu chúng ta tính riêng đạo hàm của hàm [latex]y[/latex] và hàm [latex]z[/latex], sau đó nhân chúng với nhau, điều này cũng tạo ra đạo hàm của [latex]z[/latex] theo [latex]x[/latex]! Hãy thử làm:

  • Đạo hàm của [latex]y = x^2 + 1[/latex] theo [latex]x[/latex]: [latex]\frac{dy}{dx} = 2x[/latex]
  • Đạo hàm của [latex]z = y^3 – 2[/latex] theo [latex]y[/latex]: [latex]\frac{dz}{dy} = 3y^2[/latex]
  • Theo quy tắc chuỗi: [latex]\frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{dx} = 3y^2 \cdot 2x = 6xy^2[/latex]

Biểu thức [latex]6xy^2[/latex] trông có vẻ khác với [latex]6x(x^2 + 1)^2[/latex], nhưng đó chỉ vì chúng ta chưa thay hàm [latex]y = x^2 + 1[/latex] vào. Hãy thay [latex]y[/latex] để biểu diễn toàn bộ đạo hàm theo [latex]x[/latex]:

[latex]6xy^2 = 6x(x^2 + 1)^2[/latex]

Kết quả này trùng khớp với đạo hàm đã tính trước đó!

Đây chính là quy tắc chuỗi, quy tắc này phát biểu rằng: với một hàm [latex]y[/latex] (có biến đầu vào là [latex]x[/latex]) được đưa vào một hàm khác [latex]z[/latex] (có biến đầu vào là [latex]y[/latex]), chúng ta có thể tìm đạo hàm của [latex]z[/latex] theo [latex]x[/latex] bằng cách nhân hai đạo hàm tương ứng:

[latex]\frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{dx}[/latex]

Reference:

  • Essential Math for Data Science (Thomas Nield)
  • Deep Learning (Andrew Glassner)

Setting Up n8n SSO with Self-Hosted Keycloak:

Step 1: Deploy Keycloak with Docker and PostgreSQL

1.1 Set Up PostgreSQL

Create a PostgreSQL instance for Keycloak’s data persistence.

mkdir -p ~/keycloak/postgres

Create a docker-compose.yml file for Keycloak and PostgreSQL:

version: '3.8'
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: secure_password
    volumes:
      - ./postgres:/var/lib/postgresql/data
    restart: unless-stopped
  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    command: start
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: secure_password
      KC_HOSTNAME: auth.example.com
      KC_PROXY: edge
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin_secure_password
    ports:
      - "8080:8080"
    depends_on:
      - postgres
    restart: unless-stopped

Save this as keycloak/docker-compose.yml. Replace secure_password and admin_secure_password with strong, unique values, and update auth.example.com to your Keycloak subdomain.

1.2 Configure Nginx for SSL

Set up Nginx as a reverse proxy to secure Keycloak with HTTPS.

Install Nginx:

sudo apt update
sudo apt install nginx

Create an Nginx configuration file:

sudo nano /etc/nginx/sites-available/keycloak

Add the following, replacing auth.example.com with your subdomain:

server {
    listen 80;
    server_name auth.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name auth.example.com;

    ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;

    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable the configuration:

sudo ln -s /etc/nginx/sites-available/keycloak /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Obtain an SSL certificate using Certbot:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d auth.example.com

1.3 Start Keycloak

Launch the Keycloak stack:

cd ~/keycloak
docker-compose up -d

Access the Keycloak admin console at https://auth.example.com/admin, logging in with admin and admin_secure_password. Change the admin password immediately for security.

Step 2: Configure Keycloak as a SAML Identity Provider

Keycloak will act as the Identity Provider (IdP) for n8n. We’ll configure a SAML client, and set up mappers for user attributes.

Get required information from N8N

image - quochung.cyou PTIT
  1. Navigate to Settings -> SSO
  2. Save the Entity ID and Redirect URL

Create a SAML Client for n8n

  1. Navigate to Clients > Create Client.
  2. Set:
    • Client Type: SAML
    • Client ID: (n8n’s Entity ID from previous step, matching its base URL)
    • Name: n8n (Your choice)
  3. Configure:
    • Valid Redirect URIs: https://n8n.example.com/saml/acs (Redirect URI from previous step)
  4. Settings: (Image below)
    • Name ID format: Email
    • Force name ID format: ON
    • Force POST binding: ON
    • Include AuthnStatement: ON
    • Sign Document: ON
    • Sign Asseration: ON
    • Client Signature Required: Off
  5. Save the client.
image 1 - quochung.cyou PTIT
image 2 - quochung.cyou PTIT
image 3 - quochung.cyou PTIT

Configure Mappers

Mappers define how user attributes are sent to n8n.

  1. In the n8n client, go to Clients Scope > (The entity id, ex: https://yourdomain.com/rest/sso/saml/metadata-dedicated)
  2. Add mapper -> By configuration -> User property

2. Create the mapper like image, each one config look like this

image 5 - quochung.cyou PTIT
image 6 - quochung.cyou PTIT
image 7 - quochung.cyou PTIT
image 8 - quochung.cyou PTIT
image 4 - quochung.cyou PTIT

Export SAML Metadata

  1. Go to Realm Settings > SAML 2.0 Identity Provider Metadata.
  2. Download the XML file or copy the metadata URL (e.g., https://auth.example.com/realms/n8n-realm/protocol/saml/descriptor).
image 9 - quochung.cyou PTIT
image 10 - quochung.cyou PTIT

Setup the N8N use SAML metadata

image 11 - quochung.cyou PTIT

Edit metadata

Locate <md:IDPSSODescriptor WantAuthnRequestsSigned=”true” in the XML, and change true to false

Final gonna look like <md:IDPSSODescriptor WantAuthnRequestsSigned=”false”

Test Setting

  1. Open an incognito browser window and navigate to https://n8n.example.com.
  2. You should be redirected to Keycloak’s login page.
  3. Log in with a Keycloak user (create one in Users > Add User if needed, ensuring the email matches an n8n user).
  4. Upon successful authentication, you should be redirected to n8n’s dashboard.

Tìm hiểu lí do cần database, datalake, data warehouse?

Mở đầu

Trong thời đại số ngày nay, dữ liệu hiện hữu ở khắp mọi nơi và đóng vai trò cực kỳ quan trọng trong cuộc sống cũng như công việc của chúng ta. Từ việc chúng ta lướt Facebook, Tiktok hàng ngày, đặt vé xem phim online, cho đến cách các công ty lớn vận hành chuỗi cung ứng phức tạp, mọi thứ đều không ngừng tạo ra và tiêu thụ dữ liệu.

Ví dụ:

  • Facebook biết chúng ta khi lướt trên bảng tin, sẽ dừng lại tại bài viết nào lâu, bài viết nào thu hút sự chú ý, chủ đề nào chúng ta thường xem, bạn bè nào chúng ta thường hay tương tác
  • Các app đặt vé xem phim có dữ liệu về phim nào nhiều người xem, bộ phim thể loại nào thì thường được giới tính, độ tuổi nào quan tâm, ….
image - quochung.cyou PTIT

Nhưng bạn có bao giờ dừng lại và tự hỏi, với một khối lượng dữ liệu khổng lồ và đa dạng như vậy – từ những con số khô khan trong bảng tính Excel, các giao dịch ngân hàng, đến hình ảnh, video, bài đăng mạng xã hội, hay những dòng log hệ thống dày đặc – làm thế nào để chúng ta có thể lưu trữ, quản lý và quan trọng nhất là khai thác chúng một cách hiệu quả?

Trong mọi tổ chức, thông thường sẽ xuất hiện hai nhu cầu cốt lõi:

  • Xử lý các hoạt động diễn ra hàng ngày
  • Phân tích chiến lược dựa trên dữ liệu đã tích luỹ

OLTP và OLAP

OLTP (Online Transaction Processing) – Xử lý giao dịch trực tuyến

Hãy tưởng tượng bạn đang thực hiện một giao dịch trực tuyến: đặt một chiếc vé máy bay trên website hãng hàng không, chuyển tiền qua ứng dụng ngân hàng, hay đơn giản là mua một món hàng trên sàn thương mại điện tử.

Tất cả những hành động này đòi hỏi hệ thống phải xử lý ngay lập tức, đảm bảo tính chính xác tuyệt đối (tiền phải được chuyển đúng tài khoản, vé phải được xác nhận và không bị bán trùng) và diễn ra nhanh chóng, không làm bạn phải chờ đợi. Đó chính là bản chất của OLTP – Xử lý giao dịch trực tuyến.

OLTP tập trung vào việc xử lý một khối lượng lớn các giao dịch nhỏ, riêng lẻ diễn ra liên tục trong thời gian thực. Các ví dụ đời thường của hệ thống OLTP có ở khắp nơi quanh ta: máy tính tiền ở siêu thị (quét mã sản phẩm, trừ số lượng tồn kho, tính tiền), máy ATM rút tiền, hệ thống đặt phòng khách sạn, hay việc bạn cập nhật thông tin cá nhân trên một trang web

image 1 - quochung.cyou PTIT

Đặc điểm quan trọng nhất của các hệ thống OLTP là ưu tiên tốc độ xử lý cực nhanh (thường tính bằng mili giây) và tính nhất quán của dữ liệu. Chắc chắn khách hàng sẽ không muốn tiền của mình bị “bốc hơi” hay thông tin đơn hàng bị sai lệch.

Hệ thống OLTP được thiết kế để đảm bảo điều đó. Chúng thường làm việc với dữ liệu hiện tại (trạng thái mới nhất của thông tin) và thực hiện các truy vấn tương đối đơn giản và lặp đi lặp lại, như tìm kiếm thông tin một bản ghi cụ thể (point query), thêm mới, cập nhật hoặc xóa dữ liệu dựa trên hành động của người dùng.

OLAP (Online Analytical Processing) – Xử lý phân tích trực tuyến

Tuy nhiên, từ góc nhìn của một nhà quản lý doanh nghiệp. Thay vì xử lý từng giao dịch nhỏ lẻ, bạn lại có những câu hỏi lớn hơn, mang tính chiến lược:

  • “Trong quý vừa qua, dòng sản phẩm nào mang lại lợi nhuận cao nhất?”
  • “Xu hướng mua sắm của nhóm khách hàng ở độ tuổi 25-35 là gì?”
  • “Chiến dịch marketing giảm giá 10% vừa rồi có thực sự hiệu quả trong việc thu hút khách hàng mới không?”
image 2 - quochung.cyou PTIT

Để trả lời những câu hỏi này, không thể chỉ nhìn vào một vài giao dịch đơn lẻ. Chúng ta cần phải xem xét một khối lượng dữ liệu lịch sử khổng lồ, tổng hợp thông tin từ nhiều nguồn, và phân tích chúng từ nhiều góc độ khác nhau (ví dụ: theo thời gian, theo khu vực địa lý, theo dòng sản phẩm, theo kênh bán hàng…). Đây chính là lúc OLAP – Xử lý phân tích trực tuyến phát huy vai trò

Các hệ thống OLAP được thiết kế để hỗ trợ việc phân tích dữ liệu phức tạp, giúp khám phá các xu hướng, mẫu hình ẩn giấu và cung cấp những hiểu biết sâu sắc (insights) để hỗ trợ việc ra quyết định kinh doanh (một phần quan trọng của Business Intelligence – BI). Ví dụ về các tác vụ OLAP bao gồm: phân tích doanh thu theo vùng miền qua các năm , dự báo nhu cầu thị trường cho sản phẩm mới, phân khúc khách hàng dựa trên hành vi mua sắm, hay đánh giá hiệu quả hoạt động của các phòng ban.

image 3 - quochung.cyou PTIT

Khác với OLTP, các truy vấn OLAP thường rất phức tạp, đòi hỏi quét qua hàng triệu, thậm chí hàng tỷ bản ghi để tính toán các chỉ số tổng hợp (như tổng, trung bình, đếm, tỷ lệ phần trăm). Do đó, thời gian phản hồi của các truy vấn OLAP có thể lâu hơn đáng kể, từ vài giây, vài phút đến hàng giờ, tùy thuộc vào độ phức tạp và khối lượng dữ liệu. Chúng làm việc chủ yếu với dữ liệu lịch sử được tích lũy qua thời gian.

Tại sao cần phân biệt OLTP và OLAP? Mâu thuẫn thúc đẩy sự tách biệt

Bạn có thể thắc mắc: tại sao không dùng chung một hệ thống cho cả hai việc? Câu trả lời nằm ở sự mâu thuẫn cơ bản trong nhu cầu và đặc tính của OLTP và OLAP.

Hãy hình dung thế này: Hệ thống OLTP giống như một quầy giao dịch ngân hàng luôn bận rộn, cần xử lý nhanh chóng yêu cầu nạp/rút tiền của từng khách hàng. Trong khi đó, hệ thống OLAP giống như một phòng họp lớn nơi các nhà phân tích đang xem xét sổ sách kế toán của cả năm, thực hiện những phép tính toán phức tạp để lập kế hoạch chiến lược.

Nếu cố gắng thực hiện cả hai việc trong cùng một “căn phòng” (tức là trên cùng một cơ sở dữ liệu), sẽ dẫn dàng xảy ra việc các truy vấn phân tích OLAP nặng nề, đòi hỏi nhiều tài nguyên (CPU, bộ nhớ, đọc/ghi đĩa) sẽ làm chậm hoặc thậm chí “tắc nghẽn” các giao dịch OLTP đang diễn ra.

Thử tưởng tượng giao dịch mua hàng online của bạn bị treo chỉ vì bộ phận phân tích đang chạy một báo cáo doanh thu khổng lồ trên cùng hệ thống. Điều này rõ ràng là không thể chấp nhận được đối với các hoạt động kinh doanh cốt lõi, vốn đòi hỏi sự ổn định và tốc độ

image 4 - quochung.cyou PTIT

Database, Data Warehouse, Data Lake

Database (Cơ sở dữ liệu) truyền thống

Database truyền thống, đặc biệt là cơ sở dữ liệu quan hệ (Relational Database), đóng vai trò như cuốn sổ cái điện tử, là nền tảng lưu trữ dữ liệu cho hầu hết các ứng dụng mà chúng ta tương tác hàng ngày, từ mạng xã hội, ứng dụng ngân hàng đến các website thương mại điện tử. Chúng được thiết kế và tối ưu chủ yếu cho các tác vụ OLTP – xử lý giao dịch trực tuyến.

image 5 - quochung.cyou PTIT

Đặc điểm chính của Database truyền thống:

  • Loại dữ liệu: Chúng chủ yếu làm việc với dữ liệu có cấu trúc (structured data). Đây là loại dữ liệu được tổ chức một cách gọn gàng, ngăn nắp trong các bảng (tables), với các hàng (rows) và cột (columns) được định nghĩa rõ ràng, tương tự như cách bạn tổ chức dữ liệu trong một file Excel. Ví dụ: bảng Users có các cột UserID, UserName, Email, Password; bảng OrdersOrderID, UserID, OrderDate, TotalAmount.  
  • Schema (Lược đồ/Cấu trúc): Database truyền thống sử dụng cơ chế Schema-on-Write. Điều này có nghĩa là bạn phải định nghĩa chi tiết cấu trúc của các bảng (tên cột, kiểu dữ liệu của cột là gì – số, chữ, ngày tháng…) trước khi bạn có thể lưu trữ bất kỳ dữ liệu nào vào đó. Giống như bạn phải kẻ sẵn các cột trong sổ kế toán trước khi ghi chép các khoản thu chi vậy. Lược đồ này thường khá cứng nhắc (rigid) và việc thay đổi nó có thể phức tạp, nhưng chính sự cứng nhắc này lại giúp đảm bảo tính nhất quán và toàn vẹn của dữ liệu.  
  • Mục đích chính: Mục tiêu hàng đầu là phục vụ các hoạt động đọc, ghi, cập nhật, xóa (CRUD – Create, Read, Update, Delete) dữ liệu một cách nhanh chóng và đáng tin cậy, hỗ trợ trực tiếp cho các giao dịch OLTP của ứng dụng. Chúng cần đảm bảo mỗi giao dịch diễn ra thành công hoặc thất bại hoàn toàn (tính nguyên tử – Atomicity) và dữ liệu luôn ở trạng thái hợp lệ.  
  • Người dùng: Người dùng trực tiếp chính của các database này thường là các Kỹ sư Backend (Backend Engineers). Họ là những người xây dựng và bảo trì các ứng dụng, viết code để tương tác (đọc/ghi) với database. Người dùng cuối như chúng ta chỉ tương tác gián tiếp với database thông qua giao diện của ứng dụng web hoặc mobile.  
  • Ví dụ: Cơ sở dữ liệu lưu trữ thông tin tài khoản người dùng và bài viết của một diễn đàn trực tuyến, cơ sở dữ liệu quản lý thông tin sản phẩm, đơn hàng và khách hàng của một cửa hàng online, hay cơ sở dữ liệu chứa thông tin chuyến bay, lịch trình và đặt chỗ của một hãng hàng không.

Data Warehouse (Kho dữ liệu)

Khi một doanh nghiệp phát triển, họ thường có rất nhiều hệ thống hoạt động (OLTP) khác nhau: hệ thống quản lý bán hàng (POS), hệ thống quản lý quan hệ khách hàng (CRM), hệ thống quản lý kho (Inventory), hệ thống quản lý nhân sự (HR)…

Mỗi hệ thống lại có cơ sở dữ liệu riêng, dẫn đến tình trạng dữ liệu bị phân mảnh và cô lập (data silos). Việc tổng hợp dữ liệu từ tất cả các nguồn này để có một cái nhìn toàn cảnh về tình hình kinh doanh trở nên vô cùng khó khăn. Thêm vào đó, như chúng ta đã thảo luận, việc chạy các truy vấn phân tích phức tạp (OLAP) trực tiếp trên các hệ thống OLTP đang hoạt động sẽ gây ảnh hưởng nghiêm trọng đến hiệu năng.

Để giải quyết những thách thức này, khái niệm Data Warehouse (Kho dữ liệu) đã ra đời vào cuối những năm 1980, đầu 1990. Bạn có thể hình dung Data Warehouse như một thư viện trung tâm khổng lồ, được xây dựng riêng biệt với các hệ thống hoạt động hàng ngày. Nó không trực tiếp phục vụ các giao dịch tức thời, mà nhiệm vụ chính là thu thập, tích hợp và lưu trữ dữ liệu từ nhiều nguồn OLTP khác nhau trong toàn doanh nghiệp, sau đó tối ưu hóa dữ liệu đó cho mục đích phân tích (OLAP)Business Intelligence (BI)

image 7 - quochung.cyou PTIT

Quá trình “nhập sách” vào thư viện – ETL/ELT:

Làm thế nào để dữ liệu từ các hệ thống nguồn khác nhau có thể được chuyển vào Data Warehouse một cách thống nhất? Đó là nhờ một quy trình quan trọng gọi là ETL (Extract – Transform – Load).  

  1. Extract (Trích xuất): Dữ liệu được lấy ra (trích xuất) từ các hệ thống nguồn khác nhau (database OLTP, file log, thậm chí cả dữ liệu từ các dịch vụ bên ngoài như Google Analytics hay hệ thống CRM của đối tác).  
  2. Transform (Biến đổi): Đây là bước quan trọng nhất. Dữ liệu thô được trích xuất thường không đồng nhất (ví dụ: ngày tháng có định dạng khác nhau, tên khách hàng viết hoa/thường lẫn lộn, đơn vị tiền tệ khác nhau…). Ở bước này, dữ liệu sẽ được làm sạch (loại bỏ lỗi, dữ liệu trùng lặp), chuẩn hóa (đưa về cùng một định dạng, đơn vị đo lường), tích hợp (kết hợp dữ liệu từ nhiều nguồn, ví dụ: liên kết thông tin khách hàng từ CRM với lịch sử mua hàng từ hệ thống bán lẻ), và biến đổi thành một cấu trúc (schema) phù hợp cho việc phân tích.  
  3. Load (Tải): Sau khi đã được “tút tát” sạch đẹp và đúng chuẩn, dữ liệu sẽ được tải vào Data Warehouse
image 8 - quochung.cyou PTIT

Đôi khi, thứ tự có thể thay đổi một chút thành ELT (Extract – Load – Transform), tức là dữ liệu được tải vào Data Warehouse trước rồi mới thực hiện biến đổi tại đó. Cách tiếp cận này thường phổ biến hơn với các nền tảng dữ liệu hiện đại có khả năng xử lý mạnh mẽ.

Đặc điểm chính của Data Warehouse:

  • Loại dữ liệu: Chủ yếu lưu trữ dữ liệu có cấu trúc hoặc bán cấu trúc đã được xử lý, làm sạch và tích hợp từ nhiều nguồn khác nhau. Dữ liệu ở đây không còn ở dạng thô như lúc ban đầu.  
  • Schema (Lược đồ/Cấu trúc): Vẫn sử dụng Schema-on-Write hoặc schema được định nghĩa trước, tương tự database truyền thống. Tuy nhiên, schema trong Data Warehouse thường được thiết kế theo mô hình hướng chủ đề (subject-oriented), tập trung vào các lĩnh vực kinh doanh cốt lõi như Bán hàng (Sales), Tiếp thị (Marketing), Tài chính (Finance), Nhân sự (HR)… thay vì theo cấu trúc của từng ứng dụng nguồn. Các mô hình phổ biến là Star Schema và Snowflake Schema, giúp tối ưu cho các truy vấn phân tích.  
  • Mục đích chính: Phục vụ các hoạt động phân tích dữ liệu lịch sử, tạo báo cáo quản trị (BI reports), khám phá insight kinh doanh và hỗ trợ ra quyết định chiến lược (OLAP). Nó giúp trả lời các câu hỏi “Tại sao?” và “Điều gì sẽ xảy ra?”.  
  • Người dùng: Người dùng chính là các Chuyên viên phân tích nghiệp vụ/kinh doanh (Business Analysts) , các nhà quản lý, lãnh đạo doanh nghiệp – những người cần các báo cáo tổng hợp, dashboard trực quan và những phân tích sâu sắc về tình hình hoạt động của công ty.  
  • Tính chất dữ liệu:
    • Tích hợp (Integrated): Dữ liệu từ nhiều nguồn được hợp nhất và chuẩn hóa, tạo ra một bức tranh toàn cảnh.  
    • Bất biến (Non-volatile): Dữ liệu trong Data Warehouse một khi đã được ghi vào thì rất hiếm khi bị cập nhật hay xóa đi. Thay vào đó, các dữ liệu mới (ví dụ: doanh số của ngày hôm qua) sẽ được nạp thêm vào. Điều này giúp lưu giữ lịch sử thay đổi của dữ liệu theo thời gian.  
    • Gắn nhãn thời gian (Time-variant): Dữ liệu luôn được gắn với một yếu tố thời gian (ngày, tuần, tháng, quý, năm), cho phép các nhà phân tích xem xét và so sánh dữ liệu qua các khoảng thời gian khác nhau, từ đó nhận diện xu hướng và mẫu hình phát triển.  
    • Thường là Read-only: Các nhà phân tích chủ yếu thực hiện các truy vấn đọc dữ liệu chứ không sửa đổi dữ liệu gốc trong kho.

Việc xây dựng Data Warehouse được xem là nỗ lực quan trọng đầu tiên của các doanh nghiệp nhằm phá vỡ các “ốc đảo dữ liệu” (data silos) và tạo ra một “nguồn sự thật duy nhất” (single source of truth) cho việc báo cáo và phân tích. Trước khi có Data Warehouse, việc tạo ra một báo cáo tổng hợp đáng tin cậy, ví dụ như kết hợp dữ liệu bán hàng từ hệ thống POS với dữ liệu chi phí marketing từ phòng Marketing và dữ liệu tồn kho từ bộ phận Logistics, là một công việc cực kỳ thủ công, tốn thời gian và dễ sai sót. Quy trình ETL tự động hóa việc tích hợp và làm sạch này, cung cấp cho các nhà phân tích một bộ dữ liệu tập trung, nhất quán và đáng tin cậy để họ có thể đặt ra những câu hỏi phức tạp hơn về hoạt động kinh doanh.  

Data Lake (Hồ dữ liệu)

image 9 - quochung.cyou PTIT

Data Warehouse đã giải quyết rất tốt bài toán tích hợp dữ liệu cho mục đích BI và báo cáo. Tuy nhiên, cùng với sự bùng nổ của Big Data và sự trỗi dậy của Khoa học dữ liệu (Data Science) và Học máy (Machine Learning – ML), Data Warehouse bắt đầu bộc lộ những hạn chế:

  1. Kém linh hoạt: Cấu trúc dữ liệu (schema) phải được định nghĩa trước (Schema-on-Write) khiến việc thêm nguồn dữ liệu mới hoặc thay đổi cấu trúc trở nên tốn thời gian và công sức.  
  2. Khó xử lý dữ liệu phi cấu trúc: Data Warehouse được thiết kế chủ yếu cho dữ liệu có cấu trúc hoặc bán cấu trúc. Việc lưu trữ và phân tích các loại dữ liệu phi cấu trúc như văn bản (email, bài đăng mạng xã hội, đánh giá sản phẩm), hình ảnh, video, âm thanh, dữ liệu cảm biến (IoT)… là rất khó khăn hoặc không hiệu quả.  
  3. Chi phí lưu trữ cao: Việc lưu trữ một khối lượng dữ liệu khổng lồ, đặc biệt là dữ liệu lịch sử trong nhiều năm, trên các hệ thống Data Warehouse truyền thống có thể rất tốn kém.  
  4. Mất mát thông tin tiềm năng: Quá trình Transform trong ETL có thể làm mất đi một số chi tiết hoặc sắc thái trong dữ liệu gốc, mà những chi tiết này lại có thể hữu ích cho các mô hình Machine Learning hoặc các phân tích khám phá sâu hơn.

Để khắc phục những hạn chế này và đáp ứng nhu cầu mới của thời đại Big Data, khái niệm Data Lake (Hồ dữ liệu) đã xuất hiện.

Tương tự, Data Lake là một kho lưu trữ tập trung, có khả năng chứa mọi loại dữ liệu – từ dữ liệu có cấu trúc trong các bảng, dữ liệu bán cấu trúc như file JSON, XML, đến dữ liệu phi cấu trúc như text, ảnh, video – ở định dạng gốc (raw format) của chúng. Nó được thiết kế để xử lý khối lượng dữ liệu cực lớn (terabytes đến petabytes và hơn thế nữa) với chi phí lưu trữ thấp (thường tận dụng các dịch vụ lưu trữ đối tượng trên đám mây như Amazon S3, Google Cloud Storage).

Đặc điểm chính của Data Lake:

  • Loại dữ liệu: Chấp nhận tất cả các loại dữ liệu: structured, semi-structured, và unstructured. Đây là điểm khác biệt lớn nhất so với Database và Data Warehouse.  
  • Schema (Lược đồ/Cấu trúc): Sử dụng cơ chế Schema-on-Read. Nghĩa là dữ liệu được đổ vào hồ mà không cần định nghĩa cấu trúc trước. Cấu trúc (schema) chỉ được áp dụng hoặc suy ra khi dữ liệu được đọc ra để phục vụ một mục đích phân tích cụ thể. Điều này mang lại sự linh hoạt tối đa, cho phép lưu trữ dữ liệu mới một cách nhanh chóng mà không cần lo lắng về việc phải thiết kế schema trước.  
  • Mục đích chính:
    • Lưu trữ tập trung mọi loại dữ liệu của tổ chức với chi phí thấpkhả năng mở rộng cao.  
    • Cung cấp “nguyên liệu thô” cho các hoạt động Khoa học dữ liệu (Data Science)Học máy (Machine Learning), đặc biệt là huấn luyện các mô hình phức tạp đòi hỏi dữ liệu gốc, chưa qua xử lý nhiều.  
    • Phục vụ các tác vụ phân tích khám phá (exploratory analysis), nơi các nhà khoa học dữ liệu muốn tự do tìm tòi, khám phá dữ liệu mà không bị giới hạn bởi một cấu trúc định sẵn.
    • Xử lý Big Data và các luồng dữ liệu thời gian thực (streaming data).  
  • Người dùng: Người dùng chính của Data Lake là các Nhà khoa học dữ liệu (Data Scientists)Kỹ sư dữ liệu (Data Engineers). Họ là những người cần truy cập vào dữ liệu gốc, đa dạng và có các công cụ để xử lý, phân tích dữ liệu này (ví dụ: Python với thư viện Pandas, Scikit-learn; R; Apache Spark…). Các Business Analysts cũng có thể sử dụng dữ liệu từ Data Lake, nhưng thường là sau khi dữ liệu đã được xử lý và đưa vào một lớp có cấu trúc hơn (ví dụ: thông qua Data Lakehouse hoặc các công cụ truy vấn SQL-on-Lake).  
  • Lợi ích:
    • Linh hoạt (Flexibility): Có thể lưu trữ mọi loại dữ liệu mà không cần định nghĩa schema trước.
    • Chi phí thấp (Low Cost): Tận dụng các giải pháp lưu trữ rẻ tiền, đặc biệt là trên cloud.  
    • Khả năng mở rộng (Scalability): Dễ dàng mở rộng dung lượng lưu trữ khi dữ liệu tăng lên.  
    • Hỗ trợ đa dạng công cụ: Có thể sử dụng nhiều loại công cụ và framework phân tích khác nhau trên cùng một dữ liệu.  
  • Thách thức – Nguy cơ “Đầm lầy dữ liệu” (Data Swamp): Chính sự linh hoạt của Data Lake cũng là con dao hai lưỡi. Nếu không có quy trình quản lý, kiểm soát chất lượng và tài liệu hóa (metadata management) tốt, Data Lake rất dễ biến thành một “đầm lầy dữ liệu” – một nơi chứa đầy dữ liệu không rõ nguồn gốc, chất lượng kém, trùng lặp, khó hiểu và không thể sử dụng được. Việc quản trị (governance) trong Data Lake là một thách thức lớn.  

Sự ra đời của Data Lake đã thực sự trở thành yếu tố then chốt thúc đẩy sự phát triển mạnh mẽ của Khoa học dữ liệu và Học máy trong môi trường doanh nghiệp. Các mô hình ML phức tạp thường “thích” dữ liệu thô, nơi chúng có thể tự mình khám phá các đặc trưng (features) và mẫu hình mà con người có thể bỏ qua hoặc loại bỏ trong quá trình Transform của ETL vào Data Warehouse. Data Lake cung cấp chính xác nguồn “nguyên liệu” đa dạng, khổng lồ và linh hoạt đó, cho phép các Data Scientist tự do thử nghiệm, xây dựng và huấn luyện các mô hình dự đoán, phân loại, gợi ý… mà không bị giới hạn bởi cấu trúc cứng nhắc của Data Warehouse truyền thống.

So sánh nhanh: Data Lake, Database, Data Warehouse

Đặc điểmDatabase (Cơ sở dữ liệu)Data Warehouse (Kho dữ liệu)Data Lake (Hồ dữ liệu)
Mục đích chínhXử lý giao dịch trực tuyến (OLTP), hoạt động hàng ngàyPhân tích kinh doanh (BI), báo cáo quản trị, hỗ trợ ra quyết định (OLAP) Lưu trữ mọi loại dữ liệu thô, Khoa học dữ liệu, Học máy, phân tích khám phá, Big Data
Loại dữ liệuChủ yếu có cấu trúc (Structured) Có cấu trúc, bán cấu trúc (Đã được xử lý, tích hợp) Mọi loại: Có cấu trúc, bán cấu trúc, phi cấu trúc (Structured, Semi-structured, Unstructured)
Cấu trúc dữ liệu (Schema)Schema-on-Write (Định nghĩa trước, cứng nhắc) Schema-on-Write (Định nghĩa trước, hướng chủ đề) Schema-on-Read (Linh hoạt, áp dụng khi đọc)
Cách xử lý dữ liệuTối ưu cho đọc/ghi/cập nhật/xóa nhanh các bản ghi nhỏTối ưu cho các truy vấn phức tạp, quét lượng lớn dữ liệu lịch sử (ETL/ELT trước khi tải) Lưu trữ dữ liệu thô, xử lý linh hoạt khi cần (Thường là ELT)
Người dùng chínhKỹ sư Backend, Ứng dụng Chuyên viên phân tích (Business Analysts), Nhà quản lý Nhà khoa học dữ liệu (Data Scientists), Kỹ sư dữ liệu (Data Engineers)
Tốc độ truy vấnRất nhanh (cho giao dịch nhỏ) Nhanh (cho truy vấn phân tích đã tối ưu) Có thể chậm hơn (tùy công cụ và tối ưu hóa, ưu tiên lưu trữ rẻ)
Tính linh hoạtThấp Trung bình (Khó thay đổi schema) Cao
Chi phí lưu trữTùy thuộc vào quy mô, có thể cao Thường cao Thấp (Thiết kế cho lưu trữ rẻ)
Ví dụ điển hìnhQuản lý tài khoản người dùng, đơn hàng online Phân tích doanh thu theo quý, báo cáo hiệu quả marketing Huấn luyện mô hình gợi ý sản phẩm, phân tích sentiment từ mạng xã hội, xử lý dữ liệu IoT

Ai/Vai trò nào làm việc với các hệ thống thông tin này?

Để những dữ liệu phức tạp này có thể được xây dựng, vận hành trơn tru và thực sự mang lại giá trị cho tổ chức, không thể thiếu vai trò của những con người với các kỹ năng chuyên môn khác nhau. Sự phát triển của các kiến trúc dữ liệu cũng kéo theo sự chuyên môn hóa ngày càng cao của các vị trí công việc trong lĩnh vực này.

  • Backend Engineer (Kỹ sư Backend / Lập trình viên Backend): Đây là những người thường làm việc trực tiếp nhất với các Database truyền thống (OLTP). Nhiệm vụ chính của họ là xây dựng và duy trì phần “hậu trường” (backend) của các ứng dụng web hoặc mobile – chính là những ứng dụng tạo ra và tiêu thụ dữ liệu hàng ngày. Họ viết code để ứng dụng có thể đọc, ghi, cập nhật và xóa dữ liệu trong database một cách chính xác, hiệu quả và an toàn, đảm bảo các giao dịch OLTP diễn ra suôn sẻ.  
  • Business Analyst (Chuyên viên phân tích nghiệp vụ / kinh doanh – BA): BA là những người dùng của Data Warehouse. Họ là cầu nối giữa bộ phận kinh doanh và kỹ thuật. Sử dụng dữ liệu đã được làm sạch và tích hợp trong Data Warehouse, BA thực hiện các phân tích, tạo ra các báo cáo trực quan (dashboards), khám phá các xu hướng kinh doanh và cung cấp những insight giá trị giúp ban lãnh đạo đưa ra các quyết định sáng suốt hơn (hoạt động Business Intelligence – BI). Họ cần hiểu rõ về nghiệp vụ kinh doanh và có kỹ năng sử dụng các công cụ BI như Tableau, Power BI cũng như SQL để truy vấn dữ liệu.  
  • Data Scientist (Nhà khoa học dữ liệu): Đây là những “nhà thám hiểm” của thế giới dữ liệu, thường làm việc nhiều nhất với Data Lake (nhưng cũng có thể khai thác cả Data Warehouse). Công việc của họ không chỉ dừng lại ở việc phân tích dữ liệu quá khứ mà còn đi sâu vào việc khám phá dữ liệu thô, áp dụng các thuật toán thống kê và Machine Learning (Học máy) để xây dựng các mô hình dự đoán, phân loại, phát hiện bất thường, hoặc tìm ra những insight hoàn toàn mới lạ mà con người khó nhận biết. Họ cũng có thể tạo ra các tính năng sản phẩm dựa trên dữ liệu, ví dụ như hệ thống gợi ý sản phẩm “người mua X cũng mua Y” trên các trang thương mại điện tử. Họ cần nền tảng vững chắc về toán, thống kê, lập trình (thường là Python hoặc R) và kiến thức về các thuật toán ML.  
  • Data Engineer (Kỹ sư dữ liệu): Có thể nói Data Engineer là những người “thợ xây” chính của toàn bộ hạ tầng dữ liệu. Vai trò của họ cực kỳ quan trọng, đảm bảo “dòng chảy” dữ liệu được thông suốt và hiệu quả. Họ chịu trách nhiệm thiết kế, xây dựng, kiểm thử và bảo trì các “đường ống” ( dữ liệu (data pipelines), bao gồm cả việc triển khai quy trình ETL/ELT để di chuyển và biến đổi dữ liệu từ các hệ thống nguồn (OLTP) vào Data Warehouse hoặc Data Lake. Họ đảm bảo dữ liệu luôn sẵn sàng, đáng tin cậy và có thể truy cập được cho các Data Analyst và Data Scientist sử dụng. Kỹ năng cần thiết bao gồm lập trình, hiểu biết về các hệ thống database, data warehouse, data lake, các công cụ ETL và các nền tảng Big Data.  
  • Analytics Engineer (Kỹ sư phân tích): Đây là một vai trò tương đối mới nhưng đang ngày càng trở nên quan trọng, đặc biệt trong bối cảnh của Modern Data Stack (Ngăn xếp dữ liệu hiện đại). Analytics Engineer hoạt động ở lớp trung gian, nối liền khoảng cách giữa Data Engineer và Data Analyst/Scientist. Trong khi Data Engineer tập trung vào việc xây dựng hạ tầng và đưa dữ liệu thô vào Lake/Warehouse, Analytics Engineer lại tập trung vào việc biến đổi (transform) dữ liệu thô đó thành những bộ dữ liệu (data models) sạch sẽ, có cấu trúc tốt, dễ hiểu, đáng tin cậy và được tối ưu hóa cho mục đích phân tích và báo cáo BI. Họ thường là những người rất giỏi SQL, thành thạo các công cụ mô hình hóa dữ liệu như dbt (data build tool), và có hiểu biết tốt về nhu cầu phân tích của nghiệp vụ.  

Một số xu hướng mới

  • Data Lakehouse: Như mình đã nhắc đến ở phần so sánh, Data Lakehouse là một kiến trúc lai đầy hứa hẹn, đang thu hút nhiều sự chú ý. Mục tiêu của nó là kết hợp những ưu điểm tốt nhất của cả Data Warehouse và Data Lake vào một nền tảng duy nhất. Cụ thể, nó cố gắng mang lại sự linh hoạt và chi phí thấp của Data Lake (lưu trữ mọi loại dữ liệu trên bộ nhớ rẻ tiền như object storage) cùng với các tính năng quản lý dữ liệu mạnh mẽ của Data Warehouse như cấu trúc dữ liệu (schema enforcement), đảm bảo tính toàn vẹn giao dịch (ACID transactions), quản trị dữ liệu (data governance) và hiệu năng truy vấn SQL cao. Bằng cách này, Data Lakehouse hướng tới việc đơn giản hóa kiến trúc dữ liệu tổng thể, giảm thiểu việc sao chép dữ liệu giữa Lake và Warehouse, và cho phép nhiều loại workload khác nhau (từ BI, SQL analytics đến Data Science, ML) cùng hoạt động hiệu quả trên một bản sao dữ liệu duy nhất.  
  • DataOps: Lấy cảm hứng từ thành công của DevOps trong lĩnh vực phát triển phần mềm, DataOps ra đời với mục tiêu áp dụng các nguyên tắc tương tự vào toàn bộ vòng đời của dữ liệu, từ khâu thu thập, xử lý, đến phân tích và cung cấp insight. DataOps nhấn mạnh vào Tự động hóa (Automation) các quy trình dữ liệu (như kiểm thử, triển khai pipeline), Giám sát (Monitoring) liên tục chất lượng và hiệu năng dữ liệu, và thúc đẩy Hợp tác (Collaboration) chặt chẽ giữa các nhóm liên quan (Data Engineers, Analysts, Scientists, nghiệp vụ). Mục tiêu cuối cùng của DataOps là tăng tốc độ đưa dữ liệu và insight đến người dùng, cải thiện chất lượng và độ tin cậy của dữ liệu, đồng thời giảm thiểu lỗi và các công việc thủ công lặp đi lặp lại.  
  • Reverse ETL: Nếu như ETL/ELT truyền thống tập trung vào việc đưa dữ liệu VÀO Data Warehouse hoặc Data Lake để phân tích, thì Reverse ETL lại làm điều ngược lại. Nó lấy những dữ liệu đã được xử lý, làm giàu, hoặc những insight giá trị (ví dụ: điểm số khách hàng tiềm năng, phân khúc khách hàng, dự đoán churn rate) từ chính Data Warehouse/Lakehouse và đẩy chúng TRỞ LẠI các hệ thống hoạt động hàng ngày mà các bộ phận nghiệp vụ thường xuyên sử dụng, như hệ thống CRM (Quản lý quan hệ khách hàng), công cụ Marketing Automation, nền tảng quảng cáo, hay công cụ hỗ trợ khách hàng. Việc này giúp “kích hoạt” dữ liệu phân tích, biến insight thành hành động cụ thể một cách nhanh chóng và tự động, giúp các nhóm Sales, Marketing, Customer Success… có thể cá nhân hóa tương tác, tối ưu chiến dịch và cải thiện trải nghiệm khách hàng dựa trên dữ liệu cập nhật nhất.  
  • Data Products & Data Mesh: Đây là một xu hướng mang tính chiến lược và tổ chức hơn là chỉ về công nghệ. Nó đề xuất một cách tiếp cận mới trong việc quản lý và chia sẻ dữ liệu trong các tổ chức lớn, đặc biệt là những nơi có nhiều bộ phận nghiệp vụ (domains) khác nhau. Thay vì tập trung tất cả dữ liệu và đội ngũ data vào một nhóm trung tâm (thường dẫn đến tắc nghẽn), kiến trúc Data Mesh đề xuất phân tán quyền sở hữu và trách nhiệm quản lý dữ liệu về cho chính các nhóm nghiệp vụ (domain teams) – những người hiểu rõ nhất về dữ liệu của mình. Mỗi domain team sẽ chịu trách nhiệm biến dữ liệu của họ thành các “Sản phẩm dữ liệu” (Data Products). Một Data Product không chỉ là dữ liệu thô, mà là một đơn vị logic hoàn chỉnh, bao gồm dữ liệu đã được làm sạch, mô hình hóa, có tài liệu rõ ràng, đáng tin cậy, dễ khám phá, dễ truy cập (thông qua các cổng ra – output ports được định nghĩa tốt) và có chủ sở hữu chịu trách nhiệm về chất lượng. Cách tiếp cận này nhằm mục đích tăng cường khả năng mở rộng, tính linh hoạt, chất lượng dữ liệu và giúp dữ liệu thực sự trở thành tài sản được quản lý tốt như các sản phẩm phần mềm khác trong tổ chức.  

Để tổng kết lại một cách ngắn gọn nhất, bạn có thể nhớ:

  • Database (Cơ sở dữ liệu): Lựa chọn hàng đầu cho các hoạt động hàng ngày, cần xử lý giao dịch nhanh chóng, đáng tin cậy với dữ liệu có cấu trúc. Nó là trái tim của các ứng dụng OLTP.
  • Data Warehouse (Kho dữ liệu): Ngôi nhà lý tưởng cho việc phân tích kinh doanh (BI), tạo báo cáo quản trị, khám phá insight từ dữ liệu lịch sử đã được tích hợp và làm sạch, chủ yếu là dữ liệu có cấu trúc. Nó là trung tâm của các hệ thống OLAP.
  • Data Lake (Hồ dữ liệu): Giải pháp tối ưu khi bạn cần lưu trữ mọi loại dữ liệu (bao gồm cả phi cấu trúc) ở dạng thô, với chi phí thấp và quy mô lớn, phục vụ cho các nhu cầu phân tích nâng cao, Khoa học dữ liệu và Học máy.

Register a custom function in Camunda 7 with Spring Boot

Introduction to Camunda 7

image 2 - quochung.cyou PTIT

Camunda 7 is a powerful, open-source platform designed for workflow and process automation. It offers a comprehensive suite of tools for managing business processes, including capabilities for modeling, automating, and optimizing workflows.

Before Camunda BPM 7.18

In both approaches, you need to register your method via the function mapper. If you want to call a method from a bean using an expression like ${myBean.method()}, the process happens automatically without any additional code. However, if you prefer to use the method in the format ${runner:method()} or ${method()}, here’s how you can do it.

In the ProcessEngineConfigurationImpl class provided by Camunda, you’ll find the ExpressionManager. This class exposes all the necessary methods for you to use.

image 11 - quochung.cyou PTIT
image 12 - quochung.cyou PTIT

To register a new function mapper, you need to create a class that implements the FunctionMapper interface. Here’s a sample implementation to get you started.

image 13 - quochung.cyou PTIT

Now, you can create a process engine plugin to initialize the function mapper when Camunda’s process engine starts configuring.

image 14 - quochung.cyou PTIT

Now you can use ${jsonPath(a, b) in workflow

After Camunda BPM 7.18

After Camunda 7.18, the addFunctionMapper function was removed. You might need an alternative workaround, and this link might be helpful for you.

https://forum.camunda.io/t/how-do-i-add-functionmappers-to-expressionmanager-in-7-18/40466/2

Triển khai Cursor Pagination trong Java Spring JPA/Hibernate

Từ bài viết trước

Cursor Pagination

Kĩ thuật: keyset pagination and seek method

Ở một số trang web, bạn sẽ thấy, bạn không thể đi đến thẳng trang cuối, hoặc nhảy đến 1 trang bất kì, mà thông thường sẽ có nút để sang trang kế và trang phía trước. Như vậy ta có thể assume rằng:

Người dùng sẽ chỉ mở trang 10 sau khi mở trang 9.Vậy, ta chỉ cần nhớ vị trí cuối cùng của bài viết ở trang 9 là ở id bao nhiêu, rồi dùng WHERE để truy vấn từ điểm đó, chứ không cần bỏ dần để đi đến điểm đó nữa

SELECT * FROM my_table WHERE id > 21 ORDER BY id LIMIT 5
image 17 - quochung.cyou PTIT
Tối ưu truy vấn Pagination (phân trang) sử dụng Spring Boot (Java) 38

Ví dụ: Sort theo thời gian

SELECT *
FROM my_table
WHERE (update_date = '2017-12-21' AND id > 21) 
    OR update_date > '2017-12-21'
ORDER BY update_date,id LIMIT 5

Cơ bản là ta sẽ chỉ lấy các bài viết có cùng thời gian đăng như bài viết cuối và id > , hoặc thời gian lớn hơn bài viết cuối

image 18 - quochung.cyou PTIT

Để hình dung rõ hơn, hãy thử nhìn vào bảng sau:

IDName
1An
2Nam
3Quan
4Tien
5Hoang
6Nguyen
7Duc
8Thanh
9Hai
10Minh

Ta mong muốn nhận được dữ liệu ở Page 1 như sau

IDName
1An
2Nam
3Quan
4Tien
5Hoang

Ta sẽ có câu query để lấy page 2 dạng như sau

Lấy page 1: SELECT * FROM users ORDER BY ID LIMIT 5
SELECT * FROM users WHERE ID > '5' ORDER BY ID LIMIT 5

Ta có thể thêm HAL bằng Spring HateOAS để response trả về ở dạng như sau, giúp ta lấy dữ liệu dễ dàng hơn

{
    “cursor”: {
        “previous_page”: null,
        “next_page”: "next___5"
    }
}
{
    “cursor”: {
        “previous_page”: "prev___5" ,
        “next_page”: "next___10"
    }
}
  • Cursor sẽ như một con trỏ chỉ tới một record nào đó trong dữ liệu, và khi ta truyền cursor đó vào, backend cần biết ta đang muốn lấy trang tiếp theo hay trang trước từ vị trí con trỏ đó
  • Có nhiều cách để làm điều này, ví dụ thêm prefix dạng như prev___, hay next___ như ví dụ trên
  • Hoặc đơn giản hơn, ta sẽ truyền thêm một biến chỉ ra là đó là next hay prev

Null Cursor

  • Lúc này, từ phía backend, ta có thể sử dụng count hoặc tương tự để kiểm tra xem có trang ở trước đó hay ở sau đó không, rồi trả response ra là null để thể hiện rằng không còn trang nào khác để đi tới ở hướng đó.

Next Cursor

  • Có thể bạn đã nắm được về cách để đi tới trang tiếp theo với cursor pagination.
  • Ví dụ, ta có một danh sách id tăng dần, thì từ trang có id [1,2,3,4,5], ta sẽ where id > 5 để tới trang tiếp theo
SELECT * FROM entries WHERE (myCol > 5) ORDER BY myCol ASC LIMIT 2; -> 6,7
SELECT * FROM entries WHERE (myCol > 7) ORDER BY myCol ASC LIMIT 2; -> 8,9
image 9 - quochung.cyou PTIT
image 10 - quochung.cyou PTIT
  • Với một danh sách id giảm dần, ví dụ trang là [10,9,8,7,6] thì ta sẽ where id < 6 để tới trang tiếp theo.
SELECT * FROM entries WHERE (myCol < 5) ORDER BY myCol DESC LIMIT 2; -> 4,3
SELECT * FROM entries WHERE (myCol < 3) ORDER BY myCol DESC LIMIT 2; -> 2,1
image 11 - quochung.cyou PTIT
image 12 - quochung.cyou PTIT

Previous Cursor

  • Tuy nhiên, việc đi tới trang trước đó trở lên khó khăn hơn. Bởi vì dữ liệu sẽ được trả ra từ phía trái đầu
  • Ví dụ, ta đang ở trang id [8,9] , và ta thực hiện truy vấn lấy trang trước đó, 2 phần tử
SELECT * FROM entries WHERE (myCol < 8) ORDER BY myCol ASC LIMIT 2;
image 13 - quochung.cyou PTIT
  • Kết quả sẽ trả về 0,1. Bởi vì nó đang đọc từ trái sang, và khi limit sẽ lấy từ đầu trái chứ không lấy 6,7

Cách giải quyết lấy previous cursor

  • Cách để giải quyết vấn đề này, ta sẽ đảo ngược chiều order by, và sau đó khi limit để lấy đúng vùng dữ liệu xong, ta sẽ đảo một lần nữa về chiều đúng
SELECT pagination.* FROM(SELECT * FROM entries WHERE (myCol < 8) ORDER BY myCol DESC LIMIT 2) AS pagination ORDER BY myCol ASC;
image 14 - quochung.cyou PTIT
  • Các bước vừa xảy ra
  • 1. Đầu tiên, select các row có id < 8 và order by desc. ta có 7,6,…,1,0
  • Sau đó limit 2 để lấy 7,6
  • Rồi order by asc lần nữa để lấy thành 6,7 . Là thứ tự ASC như ban đầu ta muốn

Time Complexity

  • Next Page Travesal: O(log(N) + L) , ta mấy log(N) để tìm tới điểm bất kì vì nó thực hiện như binary search và ta đã index database. Và L là Limit là số ta phải lặp đến để lấy vùng đầu tiên đã chọn
  • Previous Page Traversal: O(log(N) + 2L), sở dĩ là 2L vì ta đã phải đảo chiều 1 lần.
  • Thông thường limit được đặt ở 5-20 cho 1 trang, vì vậy có thể coi L là constant và không đáng kể

Triển khai trong code

image 15 - quochung.cyou PTIT
  • Ta có một class thực hiện việc lấy cursor trước hoặc sau. Sử dụng để decode một giá trị cursor base64 hoặc encode 1 giá trị
  • Ví dụ ta đang cursor pagination theo column 35, ta sẽ có hàm getEncodeCursor để tạo ra giá trị mã hoá cursor
  • Hàm getDecodedCursor sẽ chuyển giá trị mã hoá đó thành số để dùng paging
image 16 - quochung.cyou PTIT
  • Đây là class thực hiện việc paging và nhảy trang. Có thể thấy logic sẽ kiểm tra xem ta đang lấy trang trước hay trang sau, sau đó sort cho phù hợp.
image 17 - quochung.cyou PTIT
image 18 - quochung.cyou PTIT
  • Class xử lý việc trả về. Tại đây ta sẽ lấy ra element tại đầu list và cuối list của trang hiện tại, rồi tìm thử xem phía trước đó còn trang nào không, hay sau đó còn trang nào hay không

Tham khảo:

[BlockChain] P2: Chainlink, một giải pháp khắc phục “oracle problem”, ví dụ thực tế

This entry is part 2 of 2 in the series Blockchain

Chainlink là gì?

image 4 - quochung.cyou PTIT
Chainlink là một dự án mã nguồn mở, được phát triển bởi SmartContract, nhằm giải quyết vấn đề "oracle problem" trong blockchain. Chainlink cung cấp một cách để kết nối các hợp đồng thông minh với các nguồn dữ liệu bên ngoài mà không cần tin tưởng vào bất kỳ bên thứ ba nào.

Ví dụ thực tế

Có thể xem ví dụ thực tế về blockchain qua bảng tính ở bài viết trước trong series. Tại bài này, ta sẽ nói về vấn đề mà Chainlink tập trung giải quyết

  • Chainlink giải quyết một hạn chế cơ bản của blockchain: mặc dù blockchain giúp ta tạo ra một bảng tính an toàn, bảo mật, stable vì phi tập trung, nhưng nó không thể tự mình truy cập hoặc xác minh dữ liệu thế giới thực.

Ví dụ:

  • Bạn có thể sử dụng bảng tính để lưu các lần chấm công của nhân viên, nhưng chính bảng tính không thể xác minh xem những dữ liệu nhập vào đó có đúng hay không; bạn cần một hệ thống bảng chấm công bên ngoài để thực sự theo dõi những giờ này. Vì vậy, để bảng tính có ích, nó cần dữ liệu chỉ nhập chính xác từ một nguồn hệ thống bên ngoài nà, và ta sẽ cần một cây cầu trung gian giữa dữ liệu ngoài và hệ thống blockchain bảo mật bên trong.

Đó chính là những gì Chainlink cung cấp cho blockchain. Nó hoạt động như một mạng lưới oracle phi tập trung. Oracle giống như những sứ giả lấy thông tin từ các nguồn bên ngoài và đưa nó vào blockchain một cách an toàn, và có thể tin tưởng được.

image 8 - quochung.cyou PTIT

Chainlink đảm bảo rằng dữ liệu từ thế giới bên ngoài đến được với hợp đồng thông minh của blockchain một cách an toàn và đáng tin cậy. Vì vậy, nếu hợp đồng thông minh của bạn phụ thuộc vào thông tin thế giới thực,ví dụ như dữ liệu thời tiết, giá cả chứng khoán hiện tại, …, các oracle của Chainlink cung cấp một cách đáng tin cậy để lấy thông tin đó lên blockchain.

Hợp đồng thông minh sau đó có thể thực thi dựa trên dữ liệu thời gian thực được cung cấp, cho dù là chuyển tiền khi đáp ứng được những điều kiện nhất định, hay xác nhận xảy ra một sự kiện. Chainlink đảm bảo thông tin này chính xác và quy trình là phi tập trung, nên không có thực thể đơn lẻ nào có thể thao túng dữ liệu—cũng giống như không có một người đơn lẻ nào có thể bí mật thay đổi giờ làm việc đã theo dõi của mọi người trong bảng tính chia sẻ của chúng ta mà không bị người khác phát hiện.

Ngoài ra còn một số vấn đề khác sẽ thảo luận ở dưới.


Vấn đề “oracle problem”

image 5 - quochung.cyou PTIT
- Trong blockchain, hợp đồng thông minh không thể truy cập trực tiếp vào dữ liệu từ bên ngoài mà cần thông qua một bên thứ ba, gọi là "oracle". Tuy nhiên, việc tin tưởng vào một bên thứ ba có thể gây ra rủi ro cho hợp đồng thông minh. Đó chính là vấn đề "oracle problem".
- Sự bảo mật của blockchain được bảo vệ nhờ nhiều các "xác thực" trên nhiều máy khác nhau, phi tập trung. Tuy nhiên, khi dữ liệu từ bên ngoài được đưa vào, nó có thể bị "tấn công" từ bên ngoài, gây ra rủi ro cho hợp đồng thông minh.

Centralized Oracle Problem

image 6 - quochung.cyou PTIT

Cơ chế oracle blockchain sử dụng một node tập trung để cung cấp dữ liệu cho hợp đồng thông minh điều này cũng tạo ra 1 SPOF (Single Point of Failure), tức là, nếu điểm này bị quá tải, sập, hay có vấn đề thì toàn hệ thống bị chết. Điều này vô tình làm mất đi mục đích của ứng dụng blockchain phi tập trung, khi mà ta không tập trung dữ liệu, hay xử lý vào 1 chỗ. Nếu oracle duy nhất không hoạt động, thì hợp đồng thông minh sẽ không thể tiếp cận với dữ liệu cần thiết để thực thi hoặc sẽ thực hiện không chính xác dựa trên dữ liệu cũ.

Thậm chí tồi tệ hơn, nếu oracle duy nhất bị lỗi, thì dữ liệu được gửi lên chuỗi có thể hoàn toàn sai lệch và dẫn đến hợp đồng thông minh thực hiện những kết quả hoàn toàn sai. Điều này thường được gọi là vấn đề “garbage in, garbage out” nơi mà đầu vào xấu dẫn đến đầu ra xấu. Bên cạnh đó, vì giao dịch blockchain được tự động hóa và không thể thay đổi, một kết quả hợp đồng thông minh dựa trên dữ liệu sai lạc không thể được đảo ngược, có nghĩa là tiền của người dùng có thể bị mất vĩnh viễn.

image 7 - quochung.cyou PTIT

Việc thực sự vượt qua vấn đề oracle đòi hỏi phải có oracle phi tập trung để ngăn chặn việc lỗi, hỏng dữ liệu, hay node hoạt động không chính xác hay ngừng hoạt động. Mạng Lưới Oracle Phi Tập Trung, hoặc gọi tắt là DON (Decentralized Oracle Network), kết hợp nhiều nút oracle độc lập và nhiều nguồn dữ liệu đáng tin cậy để thiết lập sự phi tập trung từ đầu đến cuối

[Java Core] B5: Tính chất trừu tượng, Interface và Abstract Class trong Java

This entry is part 5 of 8 in the series Java Core

Buổi 5: Interface và trừu tượng

  • Interface là gì?
  • Interface và Abstract class
  • Tính trừu tượng
  • Khi nào dùng interface, khi nào dùng abstract class?
  • Bài tập về interface và trừu tượng

Interface là gì?

  • Interface là một khái niệm trừu tượng, nó chỉ ra những gì mà một đối tượng có thể làm, nhưng không nói ra làm thế nào để đối tượng đó làm được điều đó.

Ví dụ cú pháp khai báo interface:

image 48 - quochung.cyou PTIT
  • Trong ví dụ trên, interface Animal chỉ ra rằng một Animal có thể làm được 2 việc là ăn và di chuyển, nhưng không nói ra làm thế nào để ăn và di chuyển.
  • Lúc này, ví dụ ta có 1 class Dog và Cat thì ta có thể implement interface Animal vào 2 class này như sau:
image 50 - quochung.cyou PTIT
  • Lúc này, ta có thể khởi tạo 2 đối tượng Dog và Cat và gọi các phương thức eat() và travel() mà không cần quan tâm đến cách thức của 2 phương thức này.
image 51 - quochung.cyou PTIT
  • Kết quả khi chạy chương trình:

Dog is eating

Dog is travelling

Cat is eating

Cat is travelling

Take away | Tổng hợp

  • Như vậy, interface có thể nhìn nhận như 1 bản hợp đồng, tôi quy định trước, tôi chỉ định nghĩa là, các class implement interface này phải làm được những gì tôi quy định.
  • Như vậy, khi bên khác implement interface này, bên đó phải làm theo những gì mà interface quy định, nếu không làm theo, bên đó sẽ bị lỗi compile.
  • Còn nếu bên nào sử dụng interface, ta có thể khởi tạo đối tượng của class implement interface đó, và gọi các phương thức, và biết chắc phương thức tồn tại và có thể gọi được ở class đó, vì class đó đã implement interface đó.

Abstract class

  • Abstract Class – Lớp trừu tượng, là 1 lớp trong Java, nhưng có thể chứa cả các phương thức trừu tượng (abstract method) và các phương thức thường (non-abstract method).
  • Abstract Class không thể khởi tạo đối tượng, nhưng có thể khai báo biến kiểu Abstract Class, và khởi tạo đối tượng của class con của Abstract Class.

Ví dụ:

image 52 - quochung.cyou PTIT
  • Kết quả khi chạy chương trình:

Dog is eating

Animal is travelling

Cat is eating

Animal is travelling

Take away | Tổng hợp

  • Như vậy, Abstract Class nhìn chung giống Class, nhưng mà có thể tạo thêm phương thức trừu tượng, các class kế thừa từ Abstract Class này phải implement các phương thức trừu tượng này, nếu không sẽ bị lỗi compile.
  • Các phương thức trừu tượng này, có thể hiểu là các phương thức mà Abstract Class này quy định, các class kế thừa từ Abstract Class này phải có, và phải implement các phương thức này.
  • Các phương thức thường (non-abstract method) thì không cần implement, vì các class kế thừa từ Abstract Class này đã có sẵn các phương thức này.
  • Abstract Class thì không thể khởi tạo đối tượng, nhưng có thể khai báo biến kiểu Abstract Class, và khởi tạo đối tượng của class con của Abstract Class.

Ví dụ

image 53 - quochung.cyou PTIT
  • Lí do, vì abstract class hay interface chứa những hàm trừu tượng, những hàm chỉ định nghĩa, chưa có logic và giao việc triển khai logic cho các nơi ghi đè nó, nên không thể khởi tạo đối tượng từ abstract class hay interface được. (Vì nó chưa có logic, chưa có gì để chạy cả)

So sánh Interface và Abstract Class

InterfaceAbstract Class
Interface chỉ có thể chứa các phương thức trừu tượngAbstract Class có thể chứa cả các phương thức trừu tượng và các phương thức thường
Interface không thể khởi tạo đối tượngAbstract Class không thể khởi tạo đối tượng
Biến trong interface mặc định là public static finalBiến trong abstract class không có mặc định gì cả
Interface không có constructorAbstract Class có constructor
Interface không có gì để ghi đèAbstract Class có thể có các phương thức để ghi đè
Interface có thể kế thừa nhiều interface khácAbstract Class chỉ có thể kế thừa 1 abstract class khác
Các class implement interface có thể implement nhiều interface khácCác class kế thừa từ abstract class chỉ có thể kế thừa 1 abstract class khác
  • Note: Sau này interface có thể có các phương thức thường, nhưng mà vẫn không thể khởi tạo đối tượng từ interface được.
  • Về biến trong interface, mặc định là public static final, nên khi khai báo biến trong interface, không cần khai báo public static final nữa. Tức là các biến trong interface là tồn tại duy nhất, không đổi được, ở dạng static.
  • Về biến trong abstract class, không có mặc định gì cả, nên có thể tạo các “biến thường”, tức là mỗi object tạo ra bởi 1 class chứa giá trị khác nhau.

Special Question: Diamond Problem

  • Diamond Problem là gì?
  • Diamond Problem là một vấn đề xảy ra khi đa kế thừa, tức là nếu 1 class kế thừa từ 2 nơi có trùng thứ gì đó, thì nó làm sao biết nên dùng cái nào ???
  • Mặc định thì, Java không cho phép kế thừa từ 2 class, nhưng nếu kế thừa từ 2 interface thì được. (Vì interface chỉ chứa các phương thức trừu tượng, không có gì để ghi đè)
  • Ví dụ:
image 55 - quochung.cyou PTIT

Khi nào dùng interface, khi nào dùng abstract class?

  • Khi nào dùng interface, khi nào dùng abstract class?
  • Câu trả lời đơn giản: Khi nào cần thì dùng, tuỳ vào mục đích của mình
  • Nhưng mà, để hiểu rõ hơn, ta cùng xem ví dụ sau:
image 56 - quochung.cyou PTIT
  • Như vậy, ta có thể thấy, khi dùng interface, ta phải implement lại cả phương thức travel(), trong khi khi dùng abstract class, ta không cần implement lại phương thức travel().
  • Tuy nhiên, interface lại cho phép ta kế thừa nhiều interface khác, trong khi abstract class thì không.

Tổng kết

  • Có thể thấy, abstract class cho phép ta có các khả năng trừu tượng, và có thể chứa các logic có sẵn ta muốn dùng cho nhiều class con. Lúc này ta có thể cân nhắc sử dụng abstract class
  • Thay vì điều đó, interface thường được dùng để chỉ behavior - tính cách, cách xử sự của 1 đối tượng. Tức là 1 cái ô tô thì có thể xử lý các interface là ChayXe, ChayDongCo, ChayDien, ChayDau… Như vậy, interface thường được dùng để chỉ ra tính chất của 1 đối tượng, còn logic thì để class implement interface đó tự triển khai.
image 47 - quochung.cyou PTIT
  • Với abstract class, ta dùng cho những case ta có logic tập trung, có thể reuse ở class con. Vừa có khả năng trừu tượng mà các class con sẽ có đặc tính riêng. Tuy nhiên nếu class đó quá lớn, ví dụ class PhuongTien có đủ thứ hàm như ChayXe, ChayDongCo, ChayDien, ChayDau… , thì ví dụ XeDap kế thừa lại có các hàm chạy động cơ, chạy điện => Như thế bị thừa, không cần thiết, làm confuse cho người đọc code => Ta nên tách ra thành interface.
  • Với interface, ta dùng cho những case ta chỉ cần chỉ ra tính chất của 1 đối tượng, còn logic thì để class implement interface đó tự triển khai.

Tính trừu tượng

  • Tính trừu tượng là gì?
  • Tính trừu tượng là một khái niệm trong lập trình hướng đối tượng, nó cho phép ta chỉ ra những gì mà một đối tượng có thể làm, nhưng không nói ra làm thế nào để đối tượng đó làm được điều đó.
  • Tức là, khi ta dùng xe máy, ta chỉ biết là nó có thể chạy, có thể dừng, có thể bật đèn, có thể bật còi, nhưng ta không cần biết làm thế nào để nó chạy, dừng, bật đèn, bật còi.
  • Tính trừu tượng là một khái niệm quan trọng trong lập trình hướng đối tượng, nó cho phép ta tách biệt được phần logic của đối tượng và phần giao tiếp với đối tượng. Ta chỉ quan tâm đến phần giao tiếp với đối tượng, còn phần logic của đối tượng thì ta không cần quan tâm. Điều này giúp ta có thể dễ dàng thay đổi phần logic của đối tượng mà không ảnh hưởng đến phần giao tiếp với đối tượng.

Tại sao cần sử dụng interface

  • Giả sử, ta có 1 interface là “Athlete” – Vận động viên với 2 hàm là play() – là chơi thể thao nào đó
image 45 - quochung.cyou PTIT
image 46 - quochung.cyou PTIT
  • Giả sử, ta có 1 class Team – 1 đội chơi, sẽ có thể thêm vận động viên vào, ta nhận thấy, nếu có thể, ta luôn lên sử dụng interface thay vì khai báo thẳng 1 class cụ thể, vì khi đó, ta có thể thêm bất cứ vận động viên nào vào đội chơi, miễn là vận động viên đó implement interface Athlete.
image 57 - quochung.cyou PTIT
  • Như code trên, bất kì vận động viên nào implement interface Athlete đều có thể thêm vào đội chơi, và có thể chơi được. Điều này giúp code dễ mở rộng, flexible hơn, sau này ta có thể thêm bất cứ vận động viên nào vào đội chơi, miễn là vận động viên đó implement interface Athlete.

Đây cũng được biết là quy tắc prefer interface over class hay prefer composition over inheritance

Interface làm đúng sự phụ thuộc của module level cao

  • Giả sử, ta có class DienThoai, khi này khi ta muốn sạc điện thoại, ta sẽ gọi 1 class bên trong DienThoai là sạc điện bằng củ sạc Samsung
image 58 - quochung.cyou PTIT
  • Ta thấy, với thiết kế hiện tại, ta sẽ truyền tham số là cường độ vào củ sạc Samsung để class đó thực hiện sạc.
  • Tuy nhiên, bất kì sự thay đổi nào ở class CuSac vào phương thức sac có thể yêu cầu class điện thoại truyền thêm tham số, hay phải dùng tên hàm khác, …
  • Điều này là không đúng, điện thoại rõ ràng là module cấp cao hơn, và nên có quyền quyết định dùng củ sạc nào, chứ không phải củ sạc quyết định điện thoại phải làm gì để dùng.
  • Vì vậy, ta nên tạo ra 1 interface là CuSac để điện thoại có thể sạc, và class CuSacSamSung implement interface CuSac này.
image 59 - quochung.cyou PTIT
  • Lúc này, sự phụ thuộc đã thay đổi. Giờ DienThoai chỉ sử dụng interface củ sạc, ví dụ như Iphone quy định là tôi dùng cổng lightning, máy Samsung bảo tôi dùng cổng sạc USB-C vậy. Các nhà sản xuất củ sạc phải đi theo họ.
  • Thì ở đây, DienThoai dùng interface để định nghĩa là tôi sẽ muốn sạc như thế nào. Còn củ sạc thì implement interface đó, và tự triển khai logic riêng của mình, nhưng phải tuân thủ theo hợp đồng interface mà DienThoai sử dụng