Trong Bài 5 và Bài 6, chúng ta đã dùng mô hình Linear
Regression với giả định rằng biến phụ thuộc (đầu ra - dự đoán) phụ thuộc tuyến
tính với một hay nhiều biến độc lập (đầu vào). Tuy nhiên có rất nhiều bài toán
thực tế sẽ không đảm bảo việc giả định như vậy. Những bài toán mà mối quan hệ
giữa biến phụ thuộc và các biến độc lập là phi tuyến tính (non-linear). Đây là lúc chúng ta cần nghĩ tới mô hình hồi
quy Logistic.
Mô hình hồi quy Logistic sẽ dùng hàm biến đổi logarit cho biến
đầu ra để biến mô hình từ quan hệ phi tuyến tính sang tuyến tính. Nói một cách
khác, nó biểu diễn quan hệ hồi quy tuyến tính dưới dạng hàm logarit, nên đôi
khi nó cũng được gọi là Logit Regression.
Mô hình Logistic có một giả định rằng biến phụ thuộc (dự
đoán) có giá trị rời rạc. Nếu biến dự đoán chỉ lấy hai giá trị rời rạc, đó là
mô hình Binary Logistic Regression. Nếu biến dự đoán lấy nhiều hơn hai giá trị,
đó là mô hình Multinomial Logistic Regression.
Trong bài này, chúng ta chỉ tập trung vào Binary Logistic
Regression (nếu không có chú thích, chúng ta dùng thuật ngữ Logistic Regression
) vì nó rất hay được sử dụng trong thực tế, ví dụ khi bài toán liên quan tới dự
báo: có/không, thành công/thất bại, sống/chết, qua/trượt, bị bệnh/không bị, chấp
nhận/từ chối, nam/nữ, v.v...
Binary Logistic Regression là một trong những thuật toán quan
trọng bậc nhất trong Máy học. Do vậy, đây là bài học quan trọng mà chúng ta cần
đầu tư thời gian.
Sau bài học này, người học có khả năng:
·
Hiểu và
mô tả được hàm Logistic
·
Tìm hiểu
bài toán và dữ liệu tuyển sinh
·
Hàm chi
phí cho Logistic Regression
·
Cài đặt
thuật toán Logistic Regression.
7.1 Hàm Logistic
Trong bài
này, chúng ta hãy làm về bài toán phân lớp (Classification). Bài toán phân lớp
là một trường hợp đặc biệt của bài toán hồi qui (Regression), với điều kiện biến
dự đoán y chỉ nhận hai giá trị, 0 và
1 (chúng ta quan tâm tới bài toán binary logistic regression) (đọc thêm [5]). Có
2 nhận xét để tiếp cận bài toán này.
Thứ nhất: Dùng các hàm hồi qui tuyến tính
(Linear Regression) để dự đoán biến y,
khi biết x. Ở đây chúng ta bỏ qua điều
kiện giá trị của y là rời rạc (0 hoặc
1). Ở Linear Regression chúng ta chọn hàm dự đoán:
Tóm lại, chúng ta có hàm dự đoán với đầu vào x như sau:
Hàm Logistic cũng được gọi là là hàm sigmoid.
Chúng ta dựng đồ thị cho hàm này bằng Python:
01. import numpy as np 02. from matplotlib import pyplot 03. def sigmoid(z): 04. return 1 / (1 + np.exp(-z)) 05. nums= np.linspace(-10, 10, 100) 06. fig, ax = pyplot.subplots() 07. ax.plot(nums, sigmoid(nums), 'r') 08. ax.set_title('Sigmoid') 09. pyplot.show() |
Chương trình 7.1: Dựng đồ thị hàm sigmoid.
Chương trình 7.1 sẽ cho đồ thị:
Hình 7.1: Đồ thị hàm Sigmoid.
Chúng ta nhận thấy rằng giá trị hàm tiến dần tới 1 khi giá trị
của biến tiến tới giá trị lớn dương; ngược lại, giá trị của hàm tiến dần tới 0
khi giá trị của biến tiến tới giá trị nhỏ âm. Hàm sigmoid rất hữu dụng trong
nhiều bài toán vì nó có thể cho đầu vào là một giá trị thực bất kì, và đầu ra
là một giá trị năm trọng đoạn [0,1]:
Một tính chất thú vị của hàm là:
Chính vì vậy, nó cũng được dùng như một hàm xác suất. Logistic
Regression là một thuật toán dùng để ước lượng xác suất p với sự kiện phân lớp
, với dữ
liệu đã có x, được viết:
7.2 Dữ liệu và Bài toán
Dữ liệu
trong bài này, chúng ta sẽ lấy từ khóa học [1], với tệp ex2data1.txt. Dữ liệu được thu thập có 2 biến độc lập là: điểm của
kì thi 1, cùng điểm của kì thi 2; và một biến phụ thuộc đó là quyết định của hội
đồng trường (1 là đỗ, 0 là trượt). Người ta muốn đoán kết quả của hội đồng trường
thông qua điểm của 2 kì thi. Chúng ta hãy xem thông tin tổng quan về dữ liệu,
thông qua chương trình:
01. import pandas as pd 02. import numpy as np 03. import os 04. from matplotlib import pyplot 05. duongDan = os.getcwd() + '\data\ex2data1.txt' 06. tenCot = ['Exam 1', 'Exam 2', 'Admitted'] 07. duLieu = pd.read_csv(duongDan, names= tenCot) 08. print (duLieu.shape) 09. print (duLieu.head()) 10. print duLieu.describe() 11. duLieu.plot(kind = 'density', subplots = True, sharex = False) 12. pyplot.show() |
Chương trình 7.2: Kết nối cơ sở dữ liệu và đồ thị hóa dữ liệu.
Dữ liệu sẽ xuất hiện từ Chương trình 7.2:
(100, 3)
Exam 1 Exam 2
Admitted
0 34.623660 78.024693 0
1 30.286711 43.894998 0
2 35.847409 72.902198 0
3 60.182599 86.308552 1
4 79.032736 75.344376 1
Exam 1 Exam 2
Admitted
count 100.000000 100.000000
100.000000
mean 65.644274 66.221998
0.600000
std 19.458222 18.582783
0.492366
min 30.058822 30.603263
0.000000
25% 50.919511 48.179205
0.000000
50% 67.032988 67.682381
1.000000
75% 80.212529 79.360605
1.000000
max 99.827858 98.869436
1.000000
Hình 7.2: Đồ thị dữ liệu cho 2 kì thi và kết quả
của hội đồng (1-đỗ, 0-trượt).
Như vậy, dữ liệu có 100 hàng và 3 cột. Dữ liệu
trong 2 cột đầu không có sự khác nhau về đơn vị và tỉ lệ. Do vậy mà chúng ta
không nhất thiết phải chuẩn hóa dữ liệu. Hơn nữa, nhìn vào Hình 7.1 chúng ta có
phỏng đoán rằng để một thí sinh đỗ (1) kết quả của 2 kì thi phải đều lớn hơn
60.
Trong
bài này, chúng ta có thể hình ảnh hóa dữ liệu rõ hơn nữa. Chúng ta sẽ dùng màu
xanh để chỉ những dữ liệu đỗ (1), và đỏ để chỉ dữ liệu trượt (0) để có thể cảm
nhận rõ hơn về bài toán:
01. def doThi():02. do = duLieu[duLieu['Admitted'].isin([1])]03. truot = duLieu[duLieu['Admitted'].isin([0])]04. fig, ax = pyplot.subplots()05. ax.scatter(do['Exam 1'], do['Exam 2'], c='b', marker='v',label='Admitted')06. ax.scatter(truot['Exam 1'],truot['Exam 2’],c='r',marker='x',label='Not Admitted')07. ax.legend()08. ax.set_xlabel('Exam 1 Score')09. ax.set_ylabel('Exam 2 Score')10. pyplot.show()11. doThi() |
Chương trình 7.3: Dựng đồ thị cho dữ liệu.
Để hiểu rõ Chương trình 7.3, người học nên xem thêm [2, 3, 4]. Một số chú thích thêm cho Chương trình:
-
Dòng 02: Lấy ra dữ liệu đỗ ( có cột Admitted =
1)
-
Dòng 03: Lấy ra dữ liệu trượt (có cột Admitted =
0)
-
Dòng 04: là một hàm trả gia một cặp, fig - hình ảnh và ax- trục của hình ảnh. Nếu chúng ta muốn thay đổi các đặc tính của ảnh,
hay lựu lại ảnh thành một tệp, thì fig
sẽ giúp chúng ta thực hiện điều đó.
-
Dòng 05: Vẽ biểu đồ cho những dữ liệu đỗ (hình
chữ ‘v’, màu xanh – ‘b’)
-
Dòng 06: Vẽ biểu đồ cho những dữ liệu trượt
(hình chữ ‘v’, màu xanh – ‘r’)
Hình 7.3 Đồ thị biểu diễn tập dữ liệu quan sát với điểm 2 kì
thi và
kết quả (Admitted – đỗ, Not Admitted – trượt)
7.3 Hàm chi phí và Gradient descent:
Hàm chi phí cho hàm logistic là:
Các bước cài đặt hàm chi phí giống như trong thuật toán
Linear Regression:
01. maTran = duLieu.values 02. m,n = maTran.shape 03. X_cacCot = maTran[:,0:n-1] 04. X = np.insert(X_cacCot, 0, values = 1, axis = 1) 05. y = maTran[:,n-1:n] 06. print X[:5] # in ra 5 dòng đầu của X, sau khi có cột 1s 07. print y[:5] # in ra 5 dòng đầu của X, 08. theta = np.zeros((1, X.shape[1])) 09. print theta # in ra theta khởi tạo 10. print X.shape, y.shape, theta.shape # in ra kích cỡ của X, y, theta 11. def computeCost(theta, X, y): 12. theta = np.matrix(theta) 13. h_theta = sigmoid(np.dot(X,theta.T)) 14. first = np.multiply(-y, np.log(h_theta)) 15. second = np.multiply((1 - y), np.log(1 - h_theta)) 16. return np.sum(first - second) / (len(X)) 17. print computeCost(theta, X, y) # in ra chi phí ban dầu |
Chương trình 7.4
Chương trình 7.4 có phần đầu từ dòng 01 tới
dòng 10 chỉ để lấy ra dữ liệu, thiết lập theta
khởi tạo và in ra để kiểm tra. Phần hai để xây dựng tính hàm chi phí . Cuối
dùng, dòng 17 để kiểm tra giá trị tính xem có đúng
không.
[[ 1. 34.62365962 78.02469282]
[ 1.
30.28671077 43.89499752]
[ 1.
35.84740877 72.90219803]
[ 1.
60.18259939 86.3085521 ]
[ 1.
79.03273605 75.34437644]]
[[ 0.]
[ 0.]
[ 0.]
[ 1.]
[ 1.]]
[[ 0. 0. 0.]]
(100, 3) (100, 1) (1, 3)
0.69314718056
Thuật toán Gradient descent
Bây giờ là lúc chúng ta tính đạo hàm của hàm chi phí ứng với
mỗi thành phần tham số của
Chú ý rằng, mặc dù công thức đạo hàm này giống
như công thức đạo hàm trong mô hình Linear Regression, tuy nhiên nó lại khác
nhau ở định nghĩa hàm
. Trong thuật toán Linear Regression, chúng ta sẽ cập nhật
theo công thức:
trong bài toán của chúng ta đang xét thì m = 100, n = 3.
Tuy nhiên, việc sử dụng
công thức trực tiếp như vậy trong logistic regression sẽ không cho kết quả tốt
bằng việc sử dụng các hàm đã được tối ưu sẵn trong thư viện scipy.
Cụ thể chúng ta sẽ sử dụng hàm fmin_tnc,
hàm đã được cài đặt để tối ưu quá trình hội tụ cho
.
01. def gradient(theta, X, y): 02. theta = np.matrix(theta) 03. X = np.matrix(X) 04. y = np.matrix(y) 05. parameters = theta.shape[1] 06. grad = np.zeros(parameters) 07. error = sigmoid(np.dot(X,theta.T)) -y 08. for i in range(parameters): 09. term = np.multiply(error, X[:,i]) 10. grad[i] = np.sum(term) / len(X) 11. return grad 12. print gradient(theta, X, y) # in ra kết quả đạo hàm 13. import scipy.optimize as opt 14. result = opt.fmin_tnc(func=computeCost, x0=theta, fprime=gradient, args=(X, y)) 15. print result |
Chương trình 7.5
Một số chú thích thêm cho chương trình 7.5:
-
Vì theo yêu cầu tham số truyên vào của hàm opt.fmin_tnc mà trật tự (theta,
X, y) trong các hàm computeCost và gradient phải đúng như vậy.
Chương trình 7.5 sẽ cho kết quả (dòng đầu là giá trị của đạo
hàm của theta tại X và y):
[ -0.1
-12.00921659 -11.26284221]
NIT NF
F GTG
0 1 6.931471805599453E-001 2.71082898E+002
1 3 6.318123602631309E-001 7.89087138E-001
...
18 49 2.035713457338774E-001 1.79431409E-006
tnc: fscale = 746.536
tnc: |fn-fn-1] = 2.13682e-009 -> convergence
19 51
2.035713435970531E-001 1.83538449E-007
tnc: Converged (|f_n-f_(n-1)| ~= 0)
(array([-25.87355367,
0.2119368 , 0.20722583]), 51, 1)
Giải thích thêm cho hàm scipy.optimize.fmin_tnc(func, x0, fprime=None, args=()) (thao khảo [6]):
-
func: hàm muốn cực tiểu, có dạng func(x, *args), trong đó x
là giá trị cần tính. Và hàm này phải trả ra giá trị, đồng thời có thêm sự hỗ trợ
của một hàm khác để tính giá trị đạo hàm của nó (trong tham số fprime).
-
x0: kiểu mảng, được truyền cho giá trị khởi tạo ban đầu. Trong
bài này là theta.
-
fprime:đạo hàm của hàm ở tham số đầu (func), có dạng fprime(x, *args) trong đó x là giá
trị cần tính.
-
args: là một tuple, các tham số dùng để truyền cho hàm. Trong bài
này là (X,y).
-
(array([-25.87355367, 0.2119368
, 0.20722583]), 51, 1) kết quả trả ra của hàm:
o array([-25.87355367, 0.2119368 ,
0.20722583]): là theta cuối cùng
o 51: số lần tính giá trị hàm
o 1: hội tụ
Chúng ta sẽ lấy kết quả theta sau cùng để tính toán:
theta_sauCung = result[0] print computeCost(theta_sauCung, X, y) |
Chúng
ta lấy kết theta và tính chi phí cuối cùng:
[-25.87355367
0.2119368 0.20722583]
0.203571343597
Chúng
ta xây dựng hàm dự đoán. Nếu giá trị của hàm logistic mà nhỏ hơn 0.5 thì hàm dự
đoán sẽ nhận kết quả 0, và ngược lại nhận kết quả 1:
def predict(theta, X): probability = sigmoid(np.dot(X,theta.T)) return [1 if x >= 0.5 else 0 for x in probability] |
Từ
đó chúng ta kiểm tra độ chính xác của hàm dự đoán:
predictions = predict(theta_sauCung, X) soLanDoanDung = [1 if ((a == 1 and b == 1) or (a == 0 and b == 0)) else 0 for (a, b) in zip(predictions, y)] doChinhXac = (sum(map(int, soLanDoanDung)) / len(soLanDoanDung)*100)print ('Do chinh xac = {0:.2f}%'.format(doChinhXac)) |
Chương
trình sẽ cho kết quả:
Do chinh xac = 89%
Chúng
ta xem Hình 7.4 để xem mô hình của chúng ta xây dựng được:
Hình 7.4: Đồ thị biểu diễn đường biên phân tách tập dữ liệu
kết quả (Admitted – đỗ, Not Admitted – trượt)
7.4 Kết luận:
·
Mention tới
cuốn sách Master Algorithms để nói tầm quan trọng của Logistic Regression
·
Có nhiều
hàm liên tục thỏa mãn điều kiện bị chặn trong đoạn [0, 1], nhưng hàm logistic vẫn
được coi là phù hợp với các bài toán thực tế hơn cả.
·
Nhiều người
nhầm nhiệm vụ mô hình Logistic Regression với mô hình Linear Regression. Mặc dù
tên có từ Regression, nhưng thuật toán Logistic Regression là thuật toán phân lớp
(Classification) chứ không thuộc lớp bài toán Hồi qui (Regression). Đúng ra,
thuật toán nên là Logistic Classification :-). Nhưng vì tính lịch sử, thuật
toán vẫn có tên gọi như vậy: Logistic Regression.
Một vài ý có thể tổng kết cho
Bài 5, 6, và 7:
Algorithms
|
Số biến phụ thuộc
|
Số biến đọc lập
|
Simple
Linear Regression
|
1
(giá trị thực)
|
1
(giá trị thực hay rời rạc)
|
Multiple
Linear Regression
|
1
(giá trị thực)
|
|
(Binary)
Logistic Regression
|
1
(nhị phân)
|
|
Tài
liệu tham khảo:
0 nhận xét:
Đăng nhận xét