Tổng số lượt xem trang

Thứ Hai, 7 tháng 8, 2017

BÀI 7: BINARY LOGISTIC REGRESSION (HỒI QUY LOGISTIC)

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: 


Thứ hai: Vì giá trị , nên chúng ta giới hạn y trong đoạn [0, 1]. Do vậy, chúng ta chọn , trong đó
, với  

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  

 được tính ra như sau:
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:
cho mọi giá trị của j = 0, ...,n.
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 computeCostgradient 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]): 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)
(giá trị thực hay rời rạc)
(Binary) Logistic Regression
1 (nhị phân)
 (giá trị thực hay rời rạc)


Tài liệu tham khảo:

0 nhận xét: