-
Notifications
You must be signed in to change notification settings - Fork 0
/
logistic_regression.py
112 lines (90 loc) · 4.06 KB
/
logistic_regression.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import numpy as np
class LogisticRegressionClassifier:
"""
An implementation of a logistic regression classifier
"""
def __init__(self):
self.theta = None # weights vector. Numpy array of shape (1, number of features)
self.J = None # cost (float)
@staticmethod
def sigmoid(X, theta):
"""
Sigmoid function for logistic regression
:param X: input features. Numpy array of shape (m, n+1) (m=number of input sequences,
n=number of features + 1 for bias)
:param theta: weights vector. Numpy array of shape (n+1, 1)
:return: logistic regression over X. Numpy array of shape (m, 1)
"""
return 1.0 / (1.0 + np.exp(-np.dot(X, theta)))
@staticmethod
def cost(Y_prob, Y):
"""
Compute the log loss between predicted labels and gold labels
:param Y_prob: predicted probabilities. Numpy array of shape (m, 1) (m= number of labels)
:param Y: true labels. Numpy array of shape (m, 1)
:return: cost value (float)
"""
#Y = Y + np.expm1(1e-10)
return float(-(np.dot(Y.T, np.log(Y_prob)) + np.dot((1-Y).T, np.log(1 - Y_prob)))/Y.shape[0])
@staticmethod
def gradient_descent(X, Y, Y_prob, theta, alpha):
"""
Update the weights vector
:param X: training features. Numpy array of shape (m, n+1) (m=number of training sequences,
n=number of features + 1 for bias)
:param Y: training labels. Numpy array of shape (m, 1)
:param Y_prob: predicted probabilities. Numpy array of shape (m, 1)
:param theta: weights vector. Numpy array of shape (n+1, 1)
:param alpha: learning rate. Float
:return: updated weight vector
"""
return theta - alpha / X.shape[0] * np.dot(X.T, Y_prob-Y)
def train(self, X, Y, alpha, num_iters, theta=None, verbose=False):
"""
Train the logistic regression classifier on the provided X sequences
:param X: training features. Numpy array of shape (m, n+1) (m=number of training sequences,
n=number of features + 1 for bias)
:param Y: training labels. Numpy array of shape (m, 1)
:param alpha: learning rate (float)
:param num_iters: number of training iterations (int)
:param theta: weights vector. Numpy array of shape (n+1, 1)
:param verbose: print beginning and end of the process
:return: reference to the instance object
"""
if not theta:
theta = np.zeros((X.shape[1], 1))
if verbose:
print("Training LR classifier...")
J = None
for i in range(num_iters):
Y_prob = self.sigmoid(X, theta) #Prob for list of sequences
J = self.cost(Y_prob, Y)
theta = self.gradient_descent(X, Y, Y_prob, theta, alpha)
self.theta = theta
self.J = J
if verbose:
print("Training finished")
print(f"The cost after training is {self.get_cost()}")
print(f"The resulting weights vector is {self.get_weights()}")
print()
return self
def predict(self, X):
"""
Predict polarity labels for the input sequences X (0=Negative, 1=Positive)
:param X: input sequences. Numpy array of features with shape (m, n+1) (m=number of input sequences,
n=number of features + 1 for bias)
:return: predicted labels. Numpy array of size (m, 1)
"""
return np.array([1 if self.sigmoid(x, self.theta) >= 0.5 else 0 for x in X])
def get_cost(self):
"""
Get the computed cost
:return: cost value (float)
"""
return self.J
def get_weights(self):
"""
Get the weights vector
:return: weights vector
"""
return [round(t, 8) for t in np.squeeze(self.theta)]