山东大学计算机学院机器学习实验3(a Python version)
实验目的:
- 正则化线性回归和正则化逻辑回归的实现。
- 使用正则化的线性回归和正则化的逻辑回归来拟合数据。
- 使用正则化参数来调整模型的复杂度。
- 使用正则化的正规方程来求解线性回归的最佳参数。
- 使用牛顿法来求解逻辑回归的最佳参数。
实验步骤与内容:
① 正则化线性回归:
1) 首先,它加载了两个数据文件,ex3Linx.dat 和 ex3Liny.dat,其中 x 和 y 分别是特征和对应的标签数据。
2) 然后,通过散点图可视化了原始数据。接下来,代码将特征 x 进行多项式扩展,添加了 2 到 5 次方的项,通过 np.c_ 函数将这些项拼接成新的特征矩阵。
3) 正则化项被引入到线性回归中,通过在计算参数 theta 的时候,在原始的最小二乘法目标函数中添加了正则化项。这是通过在正规方程求解中,矩阵 (x.T @ x + lamda * lambda_matrix) 的逆矩阵中引入正则化的。
4) 最后,使用得到的 theta 对新的数据 x_range 进行预测,并通过散点图和拟合的函数图像展示了回归结果。
Lambda = 0,1,5
② 正则化逻辑回归:
1)加载数据:
从 ex3Logx.dat 和 ex3Logy.dat 文件中加载特征 x 和标签 y。
2)特征映射:
使用 map_feature 函数将原始特征映射到高维空间,生成多项式特征,以便在高维空间中拟合更复杂的决策边界。
3)可视化数据:
绘制正例和负例的数据点,用蓝色表示正例,红色表示负例。
4)实现正则化 logistic 回归:
定义 sigmoid 函数,将线性预测转换为概率值。
定义损失函数 loss_function,包括正则化项。
使用梯度下降法和牛顿法进行参数优化,迭代更新参数 theta 直至收敛。
对于不同的正则化参数 lambda,计算决策边界并绘制等高线。
1. k = 0
2. lamdas = [0,1,5,10,20]
3. max_err = 1e-10
4. max_iteration = 5005
5. L = np.zeros(max_iteration)
6. color = ['r', 'g', 'b', 'y', 'c']
7. legend_lines = []
8. for lamda in lamdas:
9. theta = np.zeros(n)
10. for t in tqdm(range(max_iteration)):
11. z = X @ theta
12. L[t] = loss_function(lamda, theta)
13. if t > 1 and abs(L[t] - L[t - 1]) < max_err:
14. break
15. grad = X.T @ (sigmoid(z) - y) / m
16. H = X.T.dot(np.diag(1 - sigmoid(z)).dot(np.diag(sigmoid(z))).dot(X)) / m
17. tmp = np.copy(theta)
tmp[0] = 0
18. grad += tmp * (lamda / m)
19.
20. tmp = np.eye(n)
21. tmp[0, 0] = 0
22. H += tmp * (lamda / m)
23.
24. theta = theta - np.linalg.inv(H).dot(grad)
25.
26. z = np.zeros([len(u), len(v)])
27. for i in range(len(u)):
28. for j in range(len(v)):
29. z[i, j] = map_feature(np.array([u[i]]), np.array([v[j]])).dot(theta)
30.
31. z = z.T
32. plt.contour(u, v, z, [0], colors=color[k])
33. legend_lines.append(Line2D([0], [0], linestyle='-', color=color[k]))
34.
35. k = k + 1
5)可视化决策边界:
绘制不同正则化参数下的决策边界,使用等高线表示。
设置图例,包括不同正则化参数的标签和正例/负例的标签。
结论分析与体会:
一、线性回归
- 数据可视化:
散点图展示了原始数据,其中 x 是特征,y 是对应的标签。蓝色散点表示原始数据。 - 多项式特征扩展:
特征 x 被扩展为包含 1、x、x2、x3、x4、x5 的矩阵。这样的多项式扩展可以更好地拟合复杂的数据模式。 - 正则化处理:
通过引入正则化,使用正则化参数 lambda 控制模型复杂度。
正则化项通过在正规方程中添加 lambda * ||theta||^2 的形式来防止过拟合。 - 模型参数优化:
通过求解正规方程,使用带有正则化的逆矩阵来优化模型参数 theta。 - 拟合结果可视化:
利用得到的模型参数进行预测,并在图中显示拟合曲线。红色曲线表示拟合的函数。 - 实验结论与分析:
正则化对模型的影响主要通过调整 lambda 参数实现。
当 lambda 较小时,模型可能过于复杂,容易过拟合。
当 lambda 较大时,模型更趋向于简单,有助于防止过拟合。 - 可能的改进方向:
通过尝试不同的 lambda 值,进一步了解正则化对模型的影响。
考虑交叉验证等技术,以选择最优的正则化参数。
在拟合曲线中添加置信区间,以更全面地评估模型的性能。 - 结论:
此实验通过正则化的多项式线性回归展示了在处理数据时如何平衡模型的复杂性。
结果表明,在选择合适的正则化参数时,可以更好地适应数据并防止过拟合。
二、逻辑回归 - 正则化 Logistic 回归:
代码实现了带有正则化的 logistic 回归,这是一种通过对大系数进行惩罚来防止过拟合的技术。
正则化由超参数 lambda (λ) 控制,对 lambda 的不同值进行尝试,观察其对决策边界的影响。 - 特征映射:
使用特征映射函数将原始特征转换为更高维度的空间,使算法能够捕捉特征之间的复杂关系。 - 数据可视化:
在特征空间中可视化正例(蓝色)和负例(红色),以了解训练数据的分布。 - 收敛和迭代:
通过跟踪损失函数在迭代过程中的变化来监控优化算法(牛顿法)的收敛性。
迭代循环在损失函数变化低于一定阈值时中断,表示收敛。 - 正则化的影响(lambda):
使用不同的 lambda 值(0、1、5、10、20)演示正则化对决策边界的影响。
更高的 lambda 对大系数施加更强的惩罚,可能导致具有减少过拟合的简化模型。 - 等高线图:
使用等高线图可视化不同 lambda 值下特征空间中的决策边界。
每个等高线图表示 logistic 回归假设等于零的点,分隔正例和负例。 - 反思:
正则化在防止过拟合方面至关重要,尤其在处理高维数据或存在许多特征的情况下。
正则化参数(lambda)的选择涉及在拟合训练数据和保持模型简单之间的权衡。
决策边界的可视化揭示了正则化如何影响模型的灵活性。 - 模型评估:
还应在单独的验证或测试数据集上评估模型性能,以确保其在新的、未见过的数据上有良好的泛化能力。 - 未来工作的考虑:
对正则化参数的微调和探索其他超参数调整技术可以进一步优化模型。
交叉验证可用于在数据的不同子集上鲁棒地评估模型的性能。
总的来说,提供的代码演示了带有特征映射的正则化 logistic 回归的应用,并可视化了正则化对决策边界的影响。理解这些概念对构建健壮的机器学习模型至关重要,特别是在过拟合是一个问题的情况下。
源代码:
线性回归正则化:
import numpy as np
from tqdm import tqdm
import matplotlib.pyplot as plt
# 指定.dat文件的路径
file_path1 = 'data3/ex3Linx.dat'
# 使用numpy的loadtxt函数读取数据
x = np.loadtxt(file_path1)
# 指定.dat文件的路径
file_path = 'data3/ex3Liny.dat'
# 使用numpy的loadtxt函数读取数据
y = np.loadtxt(file_path)
plt.scatter(x, y, marker='o', c='b', label='Original Data')
# plt.xlabel('Feature 1')
# plt.ylabel('Feature 2')
# plt.title('Scatter plot of Feature 1 and Feature 2')
# plt.legend()
# plt.show()
x = np.c_[np.ones(x.shape[0]), x, x ** 2, x ** 3, x ** 4, x ** 5]
n = len(x[0])
# 生成对角矩阵
lambda_matrix = np.eye(n)
# 将左上角元素设为零
lambda_matrix[0, 0] = 0
lamda = 5
theta = np.linalg.inv(x.T @ x + lamda * lambda_matrix) @ x.T @ y
# print(theta.shape)
print((x @ theta).shape)
print(x.shape)
# 假设你有一个要预测的 x 的范围
x_range = np.linspace(min(x[:, 1]), max(x[:, 1]), 100)
x_range_poly = np.c_[np.ones(100), x_range, x_range ** 2, x_range ** 3, x_range ** 4, x_range ** 5]
# 使用得到的 theta 进行预测
y_pred = x_range_poly @ theta
# 绘制原始数据散点图
plt.scatter(x[:, 1], y, marker='o', c='b', label='Original Data')
# 绘制拟合的函数图像
plt.plot(x_range, y_pred, c='r', label='Fitted Function')
plt.xlabel('Feature 1')
plt.ylabel('Output (y)')
plt.title('Regularized Linear Regression')
plt.legend()
plt.show()
逻辑回归正则化:
import numpy as np
from tqdm import tqdm
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
# 指定.dat文件的路径
file_path1 = 'data3/ex3Logx.dat'
# 使用numpy的loadtxt函数读取数据
x = np.loadtxt(file_path1, delimiter=',')
# 指定.dat文件的路径
file_path = 'data3/ex3Logy.dat'
# 使用numpy的loadtxt函数读取数据
y = np.loadtxt(file_path)
x = np.c_[np.ones(x.shape[0]), x]
u = x[:, 1]
v = x[:, 2]
def map_feature(feat1, feat2):
degree = 6
out = np.ones(len(feat1))
for i in range(1, degree + 1):
for j in range(i + 1):
out = np.column_stack((out, (feat1 ** (i - j)) * (feat2 ** j)))
return out
X = map_feature(u, v)
m, n = X.shape
sigmoid = lambda z: 1 / (1 + np.exp(-z))
# Assuming y is a NumPy array and x is a 2D NumPy array
pos = np.where(y == 1)[0]
neg = np.where(y == 0)[0]
plt.plot(x[pos, 1], x[pos, 2], 'o', markerfacecolor='none', markeredgecolor='blue', markersize=4)
plt.plot(x[neg, 1], x[neg, 2], 'o', markerfacecolor='none', markeredgecolor='red', markersize=4)
u = np.linspace(-1, 1.5, 200)
v = np.linspace(-1, 1.5, 200)
z = np.zeros((len(u), len(v)))
plt.title('Regularized Logistic Regression')
plt.xlabel('u')
plt.ylabel('v')
def loss_function(lamda, thetas):
my_sum = 0.0
for theta in thetas:
my_sum = my_sum + lamda / m / 2 * theta * theta
my_sum = my_sum - np.sum(y @ np.log(sigmoid(X @ thetas)) + (1 - y) @ np.log(1 - sigmoid(X @ thetas))) / m
return my_sum
k = 0
lamdas = [0,1,5,10,20]
max_err = 1e-10
max_iteration = 5005
L = np.zeros(max_iteration)
color = ['r', 'g', 'b', 'y', 'c']
legend_lines = []
for lamda in lamdas:
theta = np.zeros(n)
for t in tqdm(range(max_iteration)):
z = X @ theta
L[t] = loss_function(lamda, theta)
if t > 1 and abs(L[t] - L[t - 1]) < max_err:
break
grad = X.T @ (sigmoid(z) - y) / m
H = X.T.dot(np.diag(1 - sigmoid(z)).dot(np.diag(sigmoid(z))).dot(X)) / m
tmp = np.copy(theta)
tmp[0] = 0
grad += tmp * (lamda / m)
tmp = np.eye(n)
tmp[0, 0] = 0
H += tmp * (lamda / m)
theta = theta - np.linalg.inv(H).dot(grad)
z = np.zeros([len(u), len(v)])
for i in range(len(u)):
for j in range(len(v)):
z[i, j] = map_feature(np.array([u[i]]), np.array([v[j]])).dot(theta)
z = z.T
plt.contour(u, v, z, [0], colors=color[k])
legend_lines.append(Line2D([0], [0], linestyle='-', color=color[k]))
k = k + 1
# 添加第一个图例
legend1 = plt.legend(legend_lines, [f'lambda={l}' for l in lamdas])
# 添加第二个图例
legend2 = plt.legend(['pos', 'neg'], loc='upper left')
# 将第一个图例添加到轴上
plt.gca().add_artist(legend1)
plt.show()