--------------- <> -----------------
--- KHOA HỌC - CÔNG NGHỆ - GIÁO DỤC - VIỆC LÀM ---
--- Học để đi cùng bà con trên thế giới ---

Tìm kiếm trong Blog

AI (3) - Dữ liệu và Học máy cơ bản

Bài trước: AI (2) - Cách tạo ra AI
-----

3. Dữ liệu và Học máy cơ bản

Ở bài học trước, bạn đã biết:

- Có nhiều hướng tiếp cận để tạo ra AI: dựa trên Luật và Tri thức, dựa trên Dữ liệu (Học máy). Kết hợp học máy với Luật và Tri thức, và một số hướng khác

- Hướng dựa trên Luật: con người phải lập trình sẵn mọi quy tắc; hướng Học máy: máy tự học quy luật từ dữ liệu mẫu

- Hiện nay, Học máy là hướng tiếp cận đang được nhiều người quan tâm

Bài học này, chúng ta sẽ tìm hiểu về: 

- Dữ liệu được dùng trong Học máy là gì?

- Máy “học” như thế nào?

- Làm sao để đánh giá là máy đã học tốt hay chưa?

3.1 Dữ liệu là nguyên liệu của AI

Trong Học máy, dữ liệu được tổ chức thành các mẫu (sample). Mỗi mẫu có hai phần:

- Đặc trưng (feature): những thông tin đầu vào dùng để mô tả mẫu

- Nhãn (label): kết quả đúng đã biết của mẫu đó (nếu có)

Ví dụ: trong bài toán “Dự đoán học sinh Đậu hay Rớt” ở bài trước, mỗi học sinh là một mẫu (bảng sau có 3 mẫu):

Số giờ tự học (đặc trưng 1)

Số giờ ngủ (đặc trưng 2)

Kết quả (nhãn)

1

5

Rớt (0)

4

7

Đậu (1)

5

8

Đậu (1)

Chất lượng dữ liệu quyết định chất lượng của mô hình. Có một nguyên tắc rất nổi tiếng: "Rác vào, rác ra" (Garbage in, garbage out - GIGO). Dữ liệu sai, thiếu, lệch hoặc quá ít thì dù thuật toán tốt đến đâu, kết quả cũng không đáng tin.

Một bộ dữ liệu tốt thường cần:

- Đủ lớn: có đủ ví dụ để máy tìm ra quy luật

- Đa dạng: bao phủ nhiều tình huống sẽ gặp ngoài thực tế

- Chính xác: nhãn được gắn đúng

- Không thiên lệch: không thiên về một nhóm nào đó một cách bất hợp lý

3.2 Mô hình và quy trình Học máy

Mô hình (model) là kết quả của quá trình học, nó có thể là một "công thức" hoặc cấu trúc mà máy rút ra từ dữ liệu, dùng để dự đoán cho dữ liệu mới.

Quy trình để tạo ra một hệ thống Học máy thường gồm các bước:

1. Thu thập dữ liệu: gom dữ liệu phù hợp với bài toán

2. Chuẩn bị dữ liệu: làm sạch, xử lý dữ liệu thiếu, hoặc sai, chọn đặc trưng

3. Huấn luyện (training): cho thuật toán học từ dữ liệu để tạo mô hình

4. Kiểm thử (testing): đo xem mô hình dự đoán tốt đến đâu trên dữ liệu chưa từng gặp

5. Triển khai và cải tiến: đưa vào sử dụng, theo dõi, bổ sung dữ liệu và huấn luyện lại khi cần 

Xem hình minh họa:



Trong bài toán “Dự đoán học sinh Đậu hay Rớt” (ở bài học trước), chúng ta mới làm bước 3 và dự đoán thử một học sinh.

3.3 Các loại Học máy

Như bạn đã biết, Học máy gồm nhiều loại khác nhau, cụ thể:


Loại học máy

Dữ liệu

Ý tưởng chính

Ví dụ ứng dụng

Học có giám sát (Supervised Learning)

Có nhãn

Học từ các cặp (đầu vào, đáp án đúng) để dự đoán đáp án cho đầu vào mới

Lọc thư rác, dự đoán đậu/rớt, dự đoán giá nhà

Học không giám sát (Unsupervised Learning)

Không có nhãn

Tự tìm cấu trúc, nhóm ẩn trong dữ liệu

Phân nhóm khách hàng, phát hiện bất thường

Học tăng cường (Reinforcement Learning)

Không có sẵn nhãn; có phần thưởng/phạt

Tác tử thử và sai, học cách hành động để nhận nhiều phần thưởng nhất

Chơi game, robot, điều khiển

Trong học có giám sát, có hai dạng bài toán chính:

- Phân loại (Classification): xếp dữ liệu vào các nhóm định sẵn, ví dụ Đậu/Rớt, thư rác/không phải thư rác

- Hồi quy (Regression): dự đoán một con số, ví dụ giá nhà, nhiệt độ ngày mai

Học sâu (Deep Learning) không phải một loại thứ tư trong nhánh Học máy, mà là một nhóm kỹ thuật dùng mạng nơ-ron nhiều tầng, có thể áp dụng cho cả ba loại Học máy ở trên.

3.4 Đánh giá mô hình

Tập huấn luyện và tập kiểm thử

Hãy hình dung một học sinh ôn thi bằng cách học thuộc đáp án của 10 đề cũ, rồi được kiểm tra bằng đúng 10 đề đó. Nếu học sinh đó đạt điểm cao, cũng chưa thể khẳng định là học sinh đó hiểu bài. Muốn biết học sinh có thật sự hiểu hay không, phải cho học sinh đó làm đề mới.

Trong Học máy cũng vậy. Chúng ta sẽ chia dữ liệu thành hai phần:

- Tập huấn luyện (training set): dùng để máy học, thường chiếm khoảng 70% đến 80% số lượng dữ liệu mẫu

- Tập kiểm thử (test set): giữ riêng, máy không được thấy khi học, chỉ dùng để đánh giá, thường chiếm khoảng 20% đến 30% số lượng dữ liệu mẫu

Độ chính xác

Độ chính xác (accuracy) là tỷ lệ dự đoán đúng trên tổng số mẫu được kiểm tra:

accuracy = số dự đoán đúng / tổng số mẫu

Ví dụ: mô hình dự đoán đúng 45 trên 50 mẫu kiểm thử thì độ chính xác là 45/50 = 0,9 (tức 90%).

Quá khớp và chưa khớp

- Quá khớp (overfitting): mô hình "học vẹt", đạt kết quả rất tốt trên tập huấn luyện nhưng kém trên dữ liệu mới. Thường xảy ra khi mô hình quá phức tạp hoặc dữ liệu quá ít

- Chưa khớp (underfitting): mô hình quá đơn giản, chưa nắm được quy luật, nên kém cả trên tập huấn luyện lẫn tập kiểm thử

Mục tiêu là mô hình có khả năng khái quát hóa tốt: làm tốt trên dữ liệu chưa từng thấy.

Cách nhận biết nhanh một mô hình là tốt hay không:

Tình huống
(độ chính xác)

Chẩn đoán

Train cao, test thấp

Quá khớp

Train thấp, test thấp

Chưa khớp

Train cao, test cao

Tốt

3.5 Cài đặt ví dụ

Chúng ta sẽ dùng lại thuật toán KNN, nhưng lần này làm đúng quy trình, gồm: chia dữ liệu, huấn luyện, kiểm thử và so sánh nhiều giá trị n_neighbors (số hàng xóm).

Dữ liệu là bộ Iris có sẵn trong scikit-learn: 150 bông hoa, mỗi bông có 4 đặc trưng (chiều dài, chiều rộng của đài hoa và cánh hoa), thuộc 3 loài. Đây là bài toán phân loại.

[phan_loai_hoa.py]

from sklearn.datasets import load_iris

from sklearn.model_selection import train_test_split

from sklearn.neighbors import KNeighborsClassifier

from sklearn.metrics import accuracy_score


# 1. Nạp dữ liệu: X là đặc trưng, y là nhãn (loài hoa)

du_lieu = load_iris()

X, y = du_lieu.data, du_lieu.target


# 2. Chia dữ liệu: 70% để huấn luyện, 30% để kiểm thử

X_train, X_test, y_train, y_test = train_test_split(

    X, y, test_size=0.3, random_state=42

)

print("Số mẫu huấn luyện:", len(X_train))

print("Số mẫu kiểm thử:", len(X_test))


# 3. Thử nhiều giá trị “số hàng xóm”

print("\nk | Độ chính xác (train) | Độ chính xác (test)")

for k in [1, 3, 5, 11, 25, 60]:

    model = KNeighborsClassifier(n_neighbors=k)

    model.fit(X_train, y_train)  # học trên tập huấn luyện


    acc_train = accuracy_score(y_train, model.predict(X_train))

    acc_test = accuracy_score(y_test, model.predict(X_test))

    print(f"{k} | {acc_train:.2f} | {acc_test:.2f}")

Bạn chạy chương trình trên và quan sát bảng kết quả. Ví dụ:

So mau huan luyen: 105

So mau kiem thu:  45


k | Độ chính xác (train) | Độ chính xác (test)

1 1.00 | 1.00

3 0.94 | 1.00

5 0.95 | 1.00

11 0.95 | 1.00

25 0.91 | 1.00

60 0.88 | 0.96

Một vài nhận xét:

- train_test_split giữ lại một phần dữ liệu để máy không được thấy khi học. Tham số random_state=42 giúp kết quả lặp lại được mỗi lần chạy

- Với k = 1, mô hình chỉ nhìn đúng 1 hàng xóm. Khi dự đoán lại chính tập huấn luyện, hàng xóm gần nhất của một điểm thường là chính nó, nên độ chính xác trên tập huấn luyện thường rất cao (gần hoặc bằng 1,00). Con số này chưa nói lên khả năng với dữ liệu mới, nên chúng ta phải xem cột test

- Khi k rất lớn (ví dụ 60), mô hình phải "hỏi ý kiến" quá nhiều hàng xóm, kể cả những điểm khá xa, nên mô hình trở nên quá đơn giản và có thể kém đi

- Nhờ so sánh cột train và cột test, chúng ta chọn được giá trị k cho kết quả tốt nhất trên dữ liệu chưa từng thấy. Kết quả cụ thể phụ thuộc vào cách chia dữ liệu, bạn hãy tự chạy và nhận xét

3.6 Xem và đọc thêm

Sử dụng các từ khóa sau, tìm kiếm trên Google, Youtube, Chatbot để xem và đọc thêm: học máy cơ bản, supervised learning, train test split, overfitting là gì; tham số random_state=42

3.7 Bài tập và câu hỏi

Bài tập

Bài tập 3.1: Cài đặt và chạy đoạn mã trong phần lý thuyết. Ghi lại bảng kết quả của bạn.

Bài tập 3.2: Trong ví dụ phần 3.5, đổi test_size=0.3 thành 0.2 rồi 0.5, và đổi random_state sang vài giá trị khác. Kết quả độ chính xác trên tập kiểm thử có thay đổi không? Vì sao?

Bài tập 3.3: Quay lại Ví dụ 2, Phần 2.2, của bài 2 (dự đoán đậu hay rớt). Hãy tự bổ sung thêm khoảng 10 học sinh vào dữ liệu, rồi chia thành tập huấn luyện và kiểm thử, và tính độ chính xác. Dữ liệu càng nhiều thì kết quả có ổn định hơn không?

Câu hỏi

Câu 3a: Trong học máy, "nhãn" (label) của một mẫu dữ liệu là gì?

A. Tên của thuật toán được dùng để huấn luyện mô hình

B. Số thứ tự của mẫu trong bảng dữ liệu

C. Kết quả đúng đã biết gắn với mẫu dữ liệu đó

D. Dung lượng bộ nhớ mà mẫu dữ liệu chiếm

Câu 3b: Loại học máy nào sử dụng dữ liệu đã được gắn nhãn để học cách dự đoán đáp án cho dữ liệu mới?

A. Học có giám sát

B. Học không giám sát

C. Học tăng cường

D. Không loại nào ở trên

Câu 3c: Vì sao khi huấn luyện mô hình, ta phải giữ lại một tập kiểm thử riêng mà máy không được thấy khi học?

A. Để tiết kiệm bộ nhớ cho máy tính

B. Để máy tính học nhanh hơn vì có ít dữ liệu hơn

C. Để mô hình ghi nhớ chính xác từng mẫu trong tập huấn luyện

D. Để đánh giá khả năng của mô hình trên dữ liệu chưa từng thấy, tránh việc chỉ "học vẹt"

Câu 3d: Hiện tượng quá khớp (overfitting) là gì?

A. Mô hình quá đơn giản nên kém ở cả dữ liệu huấn luyện lẫn dữ liệu mới

B. Mô hình đạt kết quả rất tốt trên dữ liệu huấn luyện nhưng kém trên dữ liệu mới

C. Mô hình có kết quả tốt đều trên cả dữ liệu huấn luyện và dữ liệu mới

D. Mô hình không thể chạy được vì dữ liệu bị thiếu

Câu 3e: Một nhóm xây dựng mô hình học máy để lọc thư rác. Kết quả: độ chính xác trên tập huấn luyện là 99%, nhưng trên tập kiểm thử chỉ còn 62%. Nhận định và hướng xử lý đúng đắn nhất là gì?

A. Mô hình rất tốt vì đạt 99%, có thể triển khai ngay

B. Chỉ cần cố làm cho độ chính xác trên tập huấn luyện đạt 100% là xong

C. Mô hình có dấu hiệu quá khớp; nên bổ sung dữ liệu đa dạng hơn hoặc dùng mô hình đơn giản hơn, rồi kiểm thử lại

D. Đưa luôn tập kiểm thử vào huấn luyện để con số 62% tăng lên

-----

Nhập môn ngành CNTT (5) - Phương pháp học

Bài trước: Nhập môn ngành CNTT (4) - Lộ trình học chương trình Kỹ sư CNTT
-----
5. Phương pháp học
-----
Bài sau:

CTDL & Thuật toán nâng cao (1) - Tập dữ liệu rời nhau (DSU)

1. Tập dữ liệu rời nhau

Tập dữ liệu rời nhau (thường được gọi là Disjoint Set Union - DSU hoặc Union-Find) là một cấu trúc dữ liệu (CTDL) lưu trữ một tập hợp các phần tử được phân hoạch thành nhiều tập con rời nhau (không giao nhau).

CTDL này cho phép thực hiện 2 thao tác rất hiệu quả là xác định xem 2 phần tử có thuộc cùng một tập hợp hay không và gộp 2 tập hợp lại với nhau.

Để dễ hiểu, chúng ta sẽ xét một ví dụ cụ thể: Xây dựng mạng lưới bạn bè

1.1 Bối cảnh bài toán: “Xây dựng mạng lưới bạn bè”

Giả sử mạng xã hội facebook (FB) bắt đầu với N người dùng hoàn toàn xa lạ (chưa ai quen ai). Khi một người muốn kết bạn với một người khác, server của FB liên tục phải xử lý 2 việc sau:

1. Kiểm tra kết nối (find): Xem hai người A và B có đang cùng một mạng lưới bạn bè với nhau không?

2. Kết bạn (Union): Khi A và B trực tiếp kết bạn với nhau, hai mạng lưới bạn bè của họ sẽ nhập lại thành một mạng lưới lớn hơn

1.2 Ánh xạ các thao tác của DSU vào mạng xã hội

Thao tác của DSU

Ý nghĩa trên mạng xã hội

Mô tả logic

MakeSet(user_id)

Đăng ký tài khoản mới

Khi một người (mới) tham gia mạng xã hội, hệ thống sẽ tạo cho họ một "mạng lưới bạn bè" riêng biệt mà họ là người đại diện duy nhất

Find(user_id)

Tìm người đại diện mạng lưới

Truy ngược qua các mối quan hệ để tìm ra đại diện cho mạng lưới bạn bè mà người đó đang thuộc về

Union(user_A, user_B)

Kết bạn trực tiếp

Khi A và B kết bạn, hệ thống gộp toàn bộ thành viên trong mạng lưới của A và mạng lưới của B lại làm một

1.3. Ví dụ minh họa từng bước

Giả sử mạng xã hội FB có 5 người dùng: các người dùng có ID (mã định danh) từ 1 đến 5. Ban đầu, chưa ai kết bạn với ai:

Sử dụng mảng có tên là parent để lưu các “đại diện mạng lưới”, mỗi người tự làm đại diện cho mạng lưới của họ:

parent = [1, 2, 3, 4, 5]

Bước 1: Người dùng 1 kết bạn với người dùng 2 

Union(1, 2)

- Hệ thống tìm đại diện của 1 là 1, đại diện của 2 là 2. Do hai người thuộc hai nhóm khác nhau, nên sẽ thực hiện gộp lại

- Cập nhật: Cho người 2 trỏ về và nhận người 1 làm đại diện chung

Bước 2: Người dùng 3 kết bạn với người dùng 4

Union(3, 4)

- Hệ thống tìm đại diện của 3 là 3, đại diện của 4 là 4. Do hai người thuộc hai nhóm khác nhau, nên sẽ thực hiện gộp lại

- Cập nhật: Cho người 4 trỏ về và nhận người 3 làm đại diện chung

Bước 3: Người dùng 2 kết bạn với người dùng 3

Union(2, 3)

- Hệ thống tìm đại diện của 2 là người 1 và đại diện của 3 là người 3. Vì hai đại diện khác nhau (1 và 3), nên hệ thống sẽ tiến hành sáp nhập hai mạng lưới này lại thành một. Mạng lưới của 3 nhận 1 làm đại diện chung

- Kết quả: Lúc này nhóm {1, 2} và nhóm {3, 4} đã gộp lại thành mạng lưới lớn gồm 4 người {1, 2, 3, 4}

Bước 4: Kiểm tra “là bạn” (Find)

Khi hệ thống cần kiểm tra xem người dùng 4 có thuộc cùng mạng lưới bạn bè với người dùng 1 hay không, thực hiện các bước sau:

- Find(4) đi ngược lên tìm thấy đại diện là 1

- Find(1) thấy đại diện là 1

- Vì Find(4) == Find(1), nên hệ thống kết luận hai người này cùng thuộc một mạng lưới bạn bè mà không cần phải duyệt qua toàn bộ danh sách bạn bè phức tạp

1.4 Cài đặt trên Python

class FriendNetworkDSU:

    def __init__(self, n):

        """

        Khởi tạo mạng lưới với n người dùng (ID từ 1 đến n).

        Mảng parent lưu đại diện của từng người, ban đầu mỗi người tự làm đại diện cho chính mình.

        (Lưu ý: Index 0 không dùng để dễ khớp với ID từ 1 đến n).

        """

        self.parent = list(range(n + 1))


    def find(self, user_id):

        """

        Tìm đại diện (gốc) của mạng lưới chứa user_id.

        Đi ngược lên cha của mình cho đến khi gặp người tự làm đại diện.

        """

        if self.parent[user_id] == user_id:

            return user_id

        # Đệ quy tìm đại diện cấp cao hơn

        return self.find(self.parent[user_id])


    def union(self, user_a, user_b):

        """

        Gộp hai mạng lưới bạn bè chứa user_a và user_b lại với nhau.

        """

        root_a = self.find(user_a)

        root_b = self.find(user_b)


        if root_a != root_b:

            # Hai người ở hai mạng lưới khác nhau -> gộp lại

            # Cho đại diện của mạng lưới b trỏ về và nhận đại diện của mạng lưới a làm gốc chung

            self.parent[root_b] = root_a

            print(f"-> Kết bạn thành công: Người {user_b} và người {user_a} đã chung một mạng lưới (Gốc chung: {root_a})")

            return True

        else:

            print(f"-> Người {user_a} và người {user_b} đang ở cùng một mạng lưới rồi!")

            return False


    def are_connected(self, user_a, user_b):

        """

        Kiểm tra xem hai người có cùng 1 mạng lưới bạn bè hay không (Find check).

        """

        return self.find(user_a) == self.find(user_b)



# ==========================================

# CHẠY THỬ NGHIỆM

# ==========================================

if __name__ == "__main__":

    # Giả sử mạng xã hội có 5 người dùng (ID từ 1 đến 5)

    n = 5

    fb_network = FriendNetworkDSU(n)

    

    print(f"Đại diện ban đầu (parent): {fb_network.parent[1:]}\n")


    # Bước 1: Người dùng 1 kết bạn với người dùng 2

    print("--- Bước 1: Union(1, 2) ---")

    fb_network.union(1, 2)

    print(f"Trạng thái parent hiện tại: {fb_network.parent[1:]}\n")


    # Bước 2: Người dùng 3 kết bạn với người dùng 4

    print("--- Bước 2: Union(3, 4) ---")

    fb_network.union(3, 4)

    print(f"Trạng thái parent hiện tại: {fb_network.parent[1:]}\n")


    # Bước 3: Người dùng 2 kết bạn với người dùng 3

    print("--- Bước 3: Union(2, 3) ---")

    fb_network.union(2, 3)

    print(f"Trạng thái parent hiện tại: {fb_network.parent[1:]}\n")


    # Bước 4: Kiểm tra "là bạn" (Find) giữa người 4 và người 1

    print("--- Bước 4: Kiểm tra kết nối Find(4) và Find(1) ---")

    root_1 = fb_network.find(1)

    root_4 = fb_network.find(4)

    print(f"Đại diện của người 1 là: {root_1}")

    print(f"Đại diện của người 4 là: {root_4}")


    if fb_network.are_connected(1, 4):

        print("=> Kết luận: Người dùng 4 và người dùng 1 cùng thuộc một mạng lưới bạn bè!")

    else:

        print("=> Kết luận: Hai người này KHÔNG cùng 1 mạng lưới.")

1.5 Xem và đọc thêm

Sử dụng các từ khóa sau, tìm kiếm trên Google, Youtube, Chatbot để xem và đọc thêm: Tập dữ liệu rời nhau, Cấu trúc dữ liệu DSU.

1.6 Bài tập và câu hỏi

Bài tập

Bài tập 1.1: Tự tay viết lại từng dòng mã trong phần lý thuyết, tìm hiểu ý nghĩa từng dòng mã.

Bài tập 1.2: Dựa trên ví dụ trong phần lý thuyết, thực hiện các yêu cầu sau:

a. Khởi tạo một mạng xã hội gồm 6 người dùng. Khi đó:

parent = [1, 2, 3, 4, 5, 6]

b. Lập trình các lệnh kết bạn để parent có trạng thái là:

parent = [1, 1, 1, 1, 5, 1]

Câu hỏi

Câu 1a: Trong ngữ cảnh bài toán xây dựng mạng lưới bạn bè sử dụng cấu trúc dữ liệu DSU, thao tác Union(user_A, user_B) có ý nghĩa tương đương với hoạt động nào sau đây trên mạng xã hội?

A. Đăng ký tài khoản mới cho một người dùng

B. Tìm người đại diện cho mạng lưới bạn bè của một người dùng

C. Kết bạn trực tiếp, gộp toàn bộ thành viên trong mạng lưới của hai người lại thành một

D. Kiểm tra xem hai người có đang cùng một mạng lưới bạn bè hay không

Câu 1b: Trong đoạn mã cài đặt DSU bằng Python trong tài liệu, mảng parent ban đầu với n người dùng được khởi tạo như thế nào?

A. Tất cả các phần tử đều được gán giá trị bằng 0 để dễ đồng bộ index

B. Mỗi người tự làm đại diện cho chính mình (parent[user_id] == user_id)

C. Tất cả các phần tử đều trỏ chung về một người dùng duy nhất có ID là 1

D. Mảng parent để trống và chỉ thêm phần tử khi có người kết bạn

Câu 1c: Dựa vào ví dụ minh họa từng bước trong tài liệu (với 5 người dùng, ban đầu parent = [1, 2, 3, 4, 5]), sau khi thực hiện lần lượt các lệnh Union(1, 2), Union(3, 4), và Union(2, 3), kết quả cấu trúc các nhóm lúc đó sẽ như thế nào?

A. Tạo thành 3 nhóm rời nhau gồm: nhóm {1, 2}, nhóm {3, 4}, và nhóm {5} đứng riêng lẻ

B. Nhóm {1, 2} và nhóm {3, 4} được sáp nhập lại thành một mạng lưới lớn gồm 4 người {1, 2, 3, 4}, còn người dùng 5 vẫn đứng riêng

C. Tất cả 5 người dùng đều được gộp chung vào một mạng lưới duy nhất có gốc đại diện là 5

D. Cả 5 người dùng đều trở thành các tập hợp độc lập hoàn toàn và không có liên kết nào

Câu 1d: Cho một mạng xã hội gồm 6 người dùng với mảng parent ban đầu là [1, 2, 3, 4, 5, 6]. Sau một chuỗi các thao tác kết bạn, mảng parent (từ index 1 đến 6) cập nhật thành trạng thái: [1, 1, 1, 1, 5, 1]. Dựa vào thuật toán find trong tài liệu, khi hệ thống gọi hàm kiểm tra are_connected(2, 4), kết quả trả về sẽ là gì và vì sao?

A. False, vì người dùng 2 và người dùng 4 có các giá trị cha trực tiếp trong mảng khác nhau

B. True, vì khi thực hiện truy ngược tìm gốc, cả người 2 và người 4 đều có chung một đại diện cấp cao nhất là 1

C. False, vì người dùng 4 thuộc về mạng lưới của người dùng 5, trong khi người dùng 2 thuộc về mạng lưới khác

D. True, vì người dùng 2 và người dùng 4 trỏ trực tiếp vào nhau trong cấu trúc cây

-----
Bài sau: