Đây là một trong những câu hỏi phỏng vấn khoa học dữ liệu phổ biến yêu cầu người ta tạo ROC và các đường cong tương tự từ đầu, tức là không có dữ liệu trong tay. Vì mục đích của câu chuyện này, tôi giả định rằng người đọc nhận thức được ý nghĩa và cách tính toán đằng sau các số liệu này cũng như ý nghĩa của chúng và cách chúng được diễn giải. Vì vậy, tôi sẽ tập trung vào khía cạnh thực hiện tương tự. Chúng tôi bắt đầu bằng việc nhập các thư viện cần thiết (chúng tôi cũng nhập toán học vì mô-đun đó được sử dụng trong tính toán)
import pandas as pd import numpy as np import matplotlib.pyplot as plt import math
Bước đầu tiên là tạo dữ liệu 'thực tế' gồm 1 (xấu) và 0 (hàng hóa), vì dữ liệu này sẽ được sử dụng để tính toán và so sánh độ chính xác của mô hình thông qua các số liệu nói trên. Đối với bài viết này, chúng tôi sẽ tạo “vectơ thực tế” từ Phân phối đồng đều. Đối với bài viết tiếp theo và có liên quan, chúng tôi sẽ sử dụng Phân phối nhị thức.
actual = np.random.randint(0, 2, 10000)
Đoạn mã trên tạo ra 10.000 số nguyên ngẫu nhiên thuộc về [0,1], đây là vectơ của lớp nhị phân thực tế của chúng ta. Tất nhiên bây giờ chúng ta cần một vectơ xác suất khác cho các lớp thực tế này. Thông thường, những xác suất này là đầu ra của mô hình Machine learning. Tuy nhiên, ở đây chúng tôi sẽ tạo chúng một cách ngẫu nhiên và đưa ra một số giả định hữu ích. Giả sử mô hình cơ bản là 'mô hình hồi quy logistic', do đó, hàm liên kết là logistic hoặc logit.
Hình dưới đây mô tả hàm logistic tiêu chuẩn. Đối với mô hình hồi quy logistic, biểu thức -k(x-x_0) được thay thế bằng 'điểm'. 'Điểm' là tổng có trọng số của các tính năng mô hình và các tham số mô hình.
Do đó, khi 'điểm' = 0, hàm logistic phải vượt qua 0,5 trên trục Y. Điều này là do logit(p) = log-odds(p) = log(p/(1-p)) = 0 => p = 1-p => p =0,5. Cũng lưu ý rằng khi 'điểm' đạt được giá trị dương cao hoặc âm cao, hàm sẽ tiệm cận về phía 1 (xấu) hoặc 0 (tốt). Do đó, giá trị tuyệt đối của 'điểm' càng cao thì xác suất dự đoán cũng càng cao. Nhưng chúng ta đang ghi điểm gì? Chúng tôi đang chấm điểm từng dữ liệu đầu vào có trong 'vectơ thực tế' của chúng tôi. Sau đó, nếu chúng ta muốn giả định rằng mô hình hồi quy logistic cơ bản của chúng ta là có kỹ năng, tức là có tính dự đoán; mô hình nên cho điểm tương đối cao hơn giữa điểm xấu và điểm tốt. Như vậy, hàng xấu nên có nhiều điểm dương hơn (để đảm bảo xác suất dự đoán gần bằng 1) và hàng hóa nên có nhiều điểm âm hơn (để đảm bảo xác suất dự đoán gần bằng 0). Điều này được gọi là thứ tự xếp hạng của mô hình. Nói cách khác, cần có sự phân biệt hoặc tách biệt giữa các điểm số và từ đó xác suất dự đoán giữa hàng xấu và hàng tốt. Vì chúng ta đã thấy rằng điểm 0 hàm ý xác suất tốt = xác suất xấu = 0,5; điều này có nghĩa là mô hình không thể phân biệt giữa tốt và xấu. Nhưng vì chúng ta biết rằng điểm dữ liệu thực sự sẽ tốt hoặc xấu, do đó, điểm 0,5 là điểm kém nhất có thể có từ mô hình. Điều này cho chúng ta một số trực giác để chuyển sang bước tiếp theo.
Điểm số có thể được tạo ngẫu nhiên bằng cách sử dụng phân phối Chuẩn Chuẩn với giá trị trung bình là 0 và độ lệch chuẩn là 1. Tuy nhiên, chúng tôi muốn các điểm dự đoán khác nhau cho hàng xấu và hàng hóa. Chúng ta cũng muốn điểm xấu phải cao hơn điểm tốt. Vì vậy, chúng ta sử dụng phân phối chuẩn chuẩn và dịch chuyển giá trị trung bình của nó để tạo ra sự tách biệt giữa hàng hóa và hàng xấu.
# scores for bads bads = np.random.normal(0, 1, actual.sum()) + 1 # scores for goods goods = np.random.normal(0, 1, len(actual) - actual.sum()) - 1 plt.hist(bads) plt.hist(goods)
Trong đoạn mã nói trên, chúng tôi đã lấy mẫu điểm xấu và điểm tốt từ hai phân phối chuẩn chuẩn khác nhau nhưng chúng tôi đã dịch chuyển chúng để tạo ra sự tách biệt giữa hai phân phối này. Chúng tôi dịch chuyển điểm xấu (được biểu thị bằng màu xanh lam trong hình ảnh) 1 về bên phải và ngược lại 1 về bên trái. Điều này đảm bảo những điều sau:
Tất nhiên, chúng ta có thể tối đa hóa sự phân tách này bằng cách tăng tham số 'shift' và gán giá trị cho nó cao hơn 1. Tuy nhiên, trong câu chuyện này, chúng ta sẽ không làm điều đó. Chúng ta sẽ khám phá điều đó trong những câu chuyện liên quan tiếp theo. Bây giờ, hãy xem xét xác suất được tạo ra bởi những điểm số này.
# prob for bads bads_prob = list((map(lambda x: 1/(1 + math.exp(-x)), bads))) # prob for goods goods_prob = list((map(lambda x: 1/(1 + math.exp(-x)), goods))) plt.hist(bads_prob) plt.hist(goods_prob)
Như đã thảo luận trước đó, khi 'điểm' được đẩy qua hàm logistic, chúng ta sẽ có được xác suất. Rõ ràng là xác suất xấu (màu xanh) cao hơn (và nghiêng về 1) so với xác suất tốt (màu cam) (và lệch về 0).
Bước tiếp theo là kết hợp các vectơ thực tế và dự đoán vào một khung dữ liệu duy nhất để phân tích. Chúng tôi chỉ định các xác suất xấu trong đó trường hợp dữ liệu thực sự xấu và ngược lại
# create predicted array bads = 0 goods = 0 predicted = np.zeros((10000)) for idx in range(0, len(actual)): if actual[idx] == 1: predicted[idx] = bads_prob[bads] bads += 1 else: predicted[idx] = goods_prob[goods] goods += 1 actual_df = pd.DataFrame(actual, columns=['actual']) predicted_df = pd.DataFrame(predicted, columns=['predicted']) predicted_df = pd.concat([actual_df, predicted_df], axis = 1) predicted_df = predicted_df.sort_values(['predicted'], ascending = False).reset_index() predicted_df = predicted_df.drop(columns = 'predicted')
Bước tiếp theo là tạo thùng. Điều này là do các đường cong mà chúng ta muốn tạo ra có bản chất rời rạc. Đối với mỗi thùng, chúng tôi tính toán tích lũy số liệu mong muốn của mình. Nói cách khác, chúng tôi tạo ra các hàm phân phối tích lũy cho các biến ngẫu nhiên rời rạc - hàng hóa và hàng hóa xấu.
n_bins = 50 bin_size = math.floor(len(predicted_df) / n_bins) curve_metrics = []
Chúng ta nên tranh thủ số lượng hàng hóa xấu và kích thước thùng để tham khảo.
print("number of bads:", bads) print("number of goods:", goods) print("number of total data points:", len(actual)) print("bin size:", len(predicted_df) / n_bins)
số điểm xấu: 4915
số lượng hàng: 5085
tổng số điểm dữ liệu: 10000
kích thước thùng: 200,0
Tiếp theo là đoạn mã chính nơi chúng tôi thực hiện các phép tính thực tế của các số liệu cơ bản
for k in range(1, n_bins + 1): if k < n_bins: TP = predicted_df.loc[ : k*bin_size-1, "actual"].sum() FP = k*bin_size - TP FN = predicted_df.loc[(k*bin_size) : , "actual"].sum() TN = len(actual) - k*bin_size - FN cum_bads = predicted_df.loc[ : k*bin_size-1, "actual"].sum() cum_goods = k*bin_size - cum_bads else: TP = predicted_df.loc[ : , "actual"].sum() FP = len(actual) - TP FN = 0 TN = 0 cum_bads = bads cum_goods = goods curve_metrics.append([k, TP, FP, TN, FN, cum_bads, cum_goods])
Lưu ý rằng vòng lặp for sẽ chạy từ 1 đến n_bins, tức là nó bỏ đi một cái ở cuối. Do đó, chúng tôi có n_bins + 1 làm 'giá trị dừng'.
Đối với k = 1 đến k = n_bins-1, chúng tôi thực hiện các phép tính tích lũy của Giá trị dương tính Đúng, Giá trị dương tính giả, Giá trị âm tính giả, Giá trị âm tính thực, bds tích lũy và hàng hóa tích lũy bằng cách sử dụng bin_size.
Lưu ý rằng đoạn mã “predicted_df.loc[ : k* bin_size-1, "actual"].sum()” sẽ chạy từ chỉ mục = 0 đến chỉ mục = kbin_size-1. Do đó, nó lấy ra đoạn bằng k *bin_size. Do đó, chúng tôi trừ 1 từ k *bin_size
Tương tự, đoạn mã “predicted_df.loc[(k*bin_size) : , "actual"].sum()” sẽ chạy từ chỉ mục = k*bin_size đến chỉ mục cuối cùng. Do đó, nếu bin từ 0 đến 49 (cỡ 50) thì snipper chạy từ chỉ số = 50 (bằng bin_size) trở đi
Đối với k = n_bins, chúng tôi chỉ mở rộng nó đến chỉ mục cuối cùng của tập dữ liệu. Trong đó, đoạn mã “predicted_df.loc[ : , "actual"].sum()” tổng hợp tất cả các điểm xấu khi quá trình lập chỉ mục chạy từ chỉ mục = 0 đến chỉ mục cuối cùng của khung dữ liệu. Chúng ta cũng có thể thay thế bằng “TP = bads”. FN và TN đều = 0 vì tại điểm giới hạn cuối cùng chúng ta gán mọi thứ là 'xấu'. Do đó, không còn Âm tính giả (xấu thực tế) hoặc Âm tính thực sự (tốt thực tế). Bởi vì, lớp phủ định không tồn tại khi k = n_bins.
Sẽ rất hữu ích khi kiểm tra xem ma trận tích lũy trông như thế nào.
curve_metrics
Lưu ý rằng với k = n_bins = 50, chúng ta đã tích lũy tất cả hàng hóa (5085) và tất cả hàng hóa xấu (4915).
Bây giờ chúng ta đã sẵn sàng thực hiện các phép tính thực tế cần thiết cho các đường cong mong muốn
curve_metrics_df = pd.DataFrame(curve_metrics, columns=["cut_off_index", "TP", "FP", "TN", "FN", "cum_bads", "cum_goods"]) curve_metrics_df["cum%bads"] = curve_metrics_df["cum_bads"] / (actual.sum()) curve_metrics_df["cum%goods"] = curve_metrics_df["cum_goods"] / (len(actual) - actual.sum()) curve_metrics_df["precision"] = curve_metrics_df["TP"] / (curve_metrics_df["TP"] + curve_metrics_df["FP"]) curve_metrics_df["recall"] = curve_metrics_df["TP"] / (curve_metrics_df["TP"] + curve_metrics_df["FN"]) curve_metrics_df["sensitivity"] = curve_metrics_df["TP"] / (curve_metrics_df["TP"] + curve_metrics_df["FN"]) # specificity is the recall on the negative class curve_metrics_df["specificity"] = curve_metrics_df["TN"] / (curve_metrics_df["TN"] + curve_metrics_df["FP"])
Đó là nó. Bây giờ chúng ta có mọi thứ cần thiết để vẽ đường cong của mình.
plt.plot(curve_metrics_df["cum%goods"], curve_metrics_df["cum%bads"], label ="roc curve") plt.xlabel("cum%goods") plt.ylabel("cum%bads") plt.title("ROC Curve") plt.legend() plt.show() plt.plot(1 - curve_metrics_df["specificity"], curve_metrics_df["sensitivity"], label ="sensitivity specificity curve") plt.xlabel("1 - Specificity") plt.ylabel("Sensitivity") plt.title("Sensitivity vs 1-Specificity Curve") plt.legend() plt.show() plt.plot(curve_metrics_df["recall"], curve_metrics_df["precision"], label ="precision recall curve") plt.xlabel("Precision") plt.ylabel("Recall") plt.title("Precision Recall Curve") plt.legend() plt.show()
Tất cả các đường cong đều xác nhận việc sử dụng một mô hình có tay nghề cao (như chúng tôi đã hình thành ngay từ đầu). Việc hoàn thành này là nhiệm vụ tạo ra những đường cong này từ đầu.
Điều cần suy nghĩ - “điều gì xảy ra với những đường cong này khi các lớp học bị mất cân bằng nghiêm trọng?” Đây sẽ là chủ đề của câu chuyện tiếp theo.
Nếu bạn thích truyện này thì hãy xem những truyện khác của mình nhé.