引言
在机器学习和统计建模中,逻辑回归是一种广泛使用的分类算法,特别是在二分类问题中。尽管它的名字中有“回归”二字,但它实际上是一种分类算法,因为它的输出是离散的类别标签。
什么是逻辑回归?
逻辑回归是一种预测分析方法,用于估计一个事件的发生概率。它通过使用逻辑函数将线性回归的输出映射到0或1(或更普遍的,到0和1之间的概率)来实现这一点。
逻辑回归的原理
逻辑回归的核心原理是将线性回归模型与逻辑函数结合起来,以预测一个事件发生的概率。具体步骤如下:
-
线性模型:首先,逻辑回归构建一个线性模型,该模型的输出是一个线性组合,即
-
逻辑函数:然后,将这个线性组合𝑧 通过逻辑函数(Sigmoid函数)转换为概率 𝑝,即
-
概率解释:逻辑回归的输出是一个介于0和1之间的概率值,这使得它非常适合于表示事件发生的可能性。
-
决策边界:逻辑回归通过设定一个阈值(通常是0.5)来确定分类结果。如果 𝑝(𝑦=1∣𝑥) 大于阈值,则预测 𝑦=1,否则预测 𝑦=0。
逻辑回归模型
逻辑回归模型的数学表达式可以表示为:
其中:
- 𝑝(𝑦=1∣𝑥)是给定输入 𝑥 时,输出为1的概率。
- 𝛽0,𝛽1,...,𝛽𝑛是模型参数。
- 𝑒 是自然对数的底数。
这个函数被称为Sigmoid函数或逻辑函数,它将任何实数映射到(0,1)区间,非常适合表示概率。
优化算法
为了找到最佳参数 𝛽 ,我们通常使用梯度下降或其变体(如随机梯度下降)来最小化损失函数。
逻辑回归的优缺点
优点
-
易于理解和实现:逻辑回归模型相对简单,容易理解和实现,对于初学者来说是一个很好的起点。
-
快速计算:逻辑回归的计算相对快速,特别是当使用现代优化算法时。
-
概率解释:输出可以解释为概率,这在许多应用场景中非常有用,如风险评估和决策制定。
-
适用于大规模数据集:逻辑回归可以很好地扩展到大规模数据集。
-
特征工程友好:逻辑回归对特征工程友好,可以通过添加交互项、多项式项等来增强模型。
缺点
-
假设线性关系:逻辑回归假设特征与输出的对数几率是线性关系,这可能不适用于所有数据。
-
容易过拟合:如果模型中有太多的特征,逻辑回归可能会过拟合。
-
对异常值敏感:逻辑回归对异常值比较敏感,这可能会影响模型的性能。
-
不适合多分类问题:标准逻辑回归模型是为二分类问题设计的,虽然可以通过一对多(One-vs-All)的方法扩展到多分类问题,但这种方法可能会增加计算复杂性。
-
对特征尺度敏感:逻辑回归对特征的尺度比较敏感,因此在应用之前通常需要进行特征标准化或归一化。
逻辑回归的应用
逻辑回归在多个领域都有广泛的应用,包括:
- 信用评分:预测个人违约的概率。
- 医疗诊断:根据症状预测疾病的存在。
- 垃圾邮件过滤:识别电子邮件是否为垃圾邮件。
- 推荐系统:预测用户对产品或服务的偏好。
代码实践
逻辑回归相关部分:
class LogisticRegression:
def __init__(self, lr=0.05, num_step=1000):
self.lr = lr
self.num_step = num_step
self.weights = None
self.bias = None
def sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def fit(self, X, y):
num_samples, num_features = X.shape
# 初始化权重和偏置 w全部置为1 b置为0
self.weights = np.ones(num_features)
self.bias = 0
# 梯度下降优化
for _ in range(self.num_step):
# 得到线性模型预测值,结果用sigmod函数映射到(0, 1)上作为预测的概率
linear_model = np.dot(X, self.weights) + self.bias
y_predicted = self.sigmoid(linear_model)
# 计算梯度
dw = (1 / num_samples) * np.dot(X.T, (y_predicted - y))
db = (1 / num_samples) * np.sum(y_predicted - y)
# 更新权重和偏置
self.weights -= self.lr * dw
self.bias -= self.lr * db
def predict(self, X):
linear_model = np.dot(X, self.weights) + self.bias
y_predicted = self.sigmoid(linear_model)
# 预测概率大于0.5,则预测结果为1
pred = [1 if i > 0.5 else 0 for i in y_predicted]
return np.array(pred)
数据集部分:
def generate_data(num_samples=100):
X = np.random.rand(num_samples, 2) * 10
y = np.zeros(num_samples)
x1, x2 = 0, 0
for i in range(num_samples):
x1 += X[i, 0]
x2 += X[i, 1]
x1 /= num_samples
x2 /= num_samples
for i in range(num_samples):
if X[i, 0] * 10 + X[i, 1] * 5 > 100:
y[i]=1
return X, y
def split_data(X, y, rate):
data_len = y.size
y.resize((data_len, 1))
data = numpy.concatenate((X, y), axis=1)
numpy.random.shuffle(data)
test_len = data_len - int(rate*data_len)
return data[:test_len, :2], data[:test_len, 2], data[test_len:, :2], data[test_len:, 2]
import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl
mpl.rcParams["font.sans-serif"] = ["SimHei"]
class LogisticRegression:
def __init__(self, lr=0.05, num_step=1000):
self.lr = lr
self.num_step = num_step
self.weights = None
self.bias = None
def sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def fit(self, X, y):
num_samples, num_features = X.shape
# 初始化权重和偏置 w全部置为1 b置为0
self.weights = np.ones(num_features)
self.bias = 0
# 梯度下降优化
for _ in range(self.num_step):
# 得到线性模型预测值,结果用sigmod函数映射到(0, 1)上作为预测的概率
linear_model = np.dot(X, self.weights) + self.bias
y_predicted = self.sigmoid(linear_model)
# 计算梯度
dw = (1 / num_samples) * np.dot(X.T, (y_predicted - y))
db = (1 / num_samples) * np.sum(y_predicted - y)
# 更新权重和偏置
self.weights -= self.lr * dw
self.bias -= self.lr * db
def predict(self, X):
linear_model = np.dot(X, self.weights) + self.bias
y_predicted = self.sigmoid(linear_model)
# 预测概率大于0.5,则预测结果为1
pred = [1 if i > 0.5 else 0 for i in y_predicted]
return np.array(pred)
def generate_data(num_samples=100):
# 生成随机特征
X = np.random.rand(num_samples, 2) * 10
# 根据特定规则生成标签,在中心点的右上角全部为1
y = np.zeros(num_samples)
x1, x2 = 0, 0
for i in range(num_samples):
x1 += X[i, 0]
x2 += X[i, 1]
x1 /= num_samples
x2 /= num_samples
for i in range(num_samples):
if X[i, 0] * 10 + X[i, 1] * 5 > 100:
y[i] = 1
return X, y
def split_data(X, y, rate):
data_len = y.size
y.resize((data_len, 1))
data = np.concatenate((X, y), axis=1)
np.random.shuffle(data)
test_len = data_len - int(rate * data_len)
return data[:test_len, :2], data[:test_len, 2], data[test_len:, :2], data[test_len:, 2]
def plot_data(X, y, title='data'):
plt.scatter(X[y == 0][:, 0], X[y == 0][:, 1], color='blue', label='类别 0')
plt.scatter(X[y == 1][:, 0], X[y == 1][:, 1], color='red', label='类别 1')
plt.xlabel('X1')
plt.ylabel('X2')
plt.title(title)
plt.legend()
plt.show()
def plot_data_test(X, y, pred, title='data'):
right_X, right_y = X[y == pred], pred[y == pred]
wrong_X, wrong_y = X[y != pred], pred[y != pred]
plt.scatter(right_X[right_y == 0][:, 0], right_X[right_y == 0][:, 1], color='blue', label='类别 0')
plt.scatter(right_X[right_y == 1][:, 0], right_X[right_y == 1][:, 1], color='red', label='类别 1')
plt.scatter(wrong_X[wrong_y == 0][:, 0], wrong_X[wrong_y == 0][:, 1], color='blue', marker='x',
label='类别 0(真实为类别 1)')
plt.scatter(wrong_X[wrong_y == 1][:, 0], wrong_X[wrong_y == 1][:, 1], color='red', marker='x',
label='类别 1(真实为类别 0)')
plt.xlabel('X1')
plt.ylabel('X2')
plt.title(title)
plt.legend()
plt.show()
def plot_decision_boundary(X, y, pred, model):
# 确定坐标轴范围
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# 生成网格点坐标
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))
# 使用模型进行预测
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 绘制决策边界
plt.contourf(xx, yy, Z, alpha=0.4)
# plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolors='k')
right_X, right_y = X[y == pred], pred[y == pred]
wrong_X, wrong_y = X[y != pred], pred[y != pred]
plt.scatter(right_X[right_y == 0][:, 0], right_X[right_y == 0][:, 1], color='blue', label='类别 0')
plt.scatter(right_X[right_y == 1][:, 0], right_X[right_y == 1][:, 1], color='red', label='类别 1')
plt.scatter(wrong_X[wrong_y == 0][:, 0], wrong_X[wrong_y == 0][:, 1], color='blue', marker='x',
label='类别 0(真实为类别 1)')
plt.scatter(wrong_X[wrong_y == 1][:, 0], wrong_X[wrong_y == 1][:, 1], color='red', marker='x',
label='类别 1(真实为类别 0)')
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('决策边界')
plt.show()
num_samples, rate = 1000, 0.1
# 构造数据集
X, y = generate_data(num_samples)
train_X, train_y, test_X, test_y = split_data(X, y, rate)
y.resize(y.size)
plot_data(X, y, "生成的数据分布情况")
plot_data(train_X, train_y, "训练数据分布情况")
plot_data(test_X, test_y, "测试数据分布情况")
classifier = LogisticRegression()
classifier.fit(train_X, train_y)
pred = classifier.predict(test_X)
result = pred == test_y
plot_data_test(test_X, test_y, pred, f"精确率:{result.sum() * 100 / result.size}%")
plot_decision_boundary(test_X, test_y, pred, classifier)
分类结果:
结论
逻辑回归是一种强大的分类算法,适用于各种二分类问题。它简单、易于理解和实现,并且对于初学者来说是一个很好的起点。然而,它也有一些局限性,比如对特征的线性关系假设较强,对于复杂的非线性问题可能需要更高级的模型。