正则化是一种广义的术语,指的是通过归一化或添加惩罚项来减少过拟合几率的方法。
1. 岭回归(L2 正则化)
最常见的正则化形式之一是岭回归,或称为L2正则化,有时也称为Tikhonov正则化。其通过惩罚模型系数的平方和(即2-范数)来实现。在这种情况下,模型的惩罚项如下:
其中,λ 是一个控制惩罚强度的自由参数。
2. 套索回归(L1 正则化)
另一种常见的正则化形式是套索回归,涉及惩罚回归系数绝对值的和(即1-范数):
虽然这在概念上与岭回归非常相似,但结果可能会有很大的不同:套索回归可以将某些特征的系数缩减为零,而岭回归则着重于缩小所有参数,以避免过度依赖某一特征。
3. 弹性网
当你缺乏领域知识时,可以使用弹性网。
选择岭回归、套索回归还是弹性网?
正则化应该几乎总是使用,因为这些技术可以减少过拟合。
如何选择有点困难,但可以理解其背后的假设:
- 岭回归假设系数是正态分布的。因此,如果你不希望任何特征过度占主导地位,可以使用岭回归。
- 套索回归假设系数是拉普拉斯分布的(简单来说,这意味着某些预测变量非常有用,而有些则完全无关紧要)。套索回归具有将系数缩减为零的能力,从而消除对输出无用的预测变量,因此可以自动进行特征选择。简单来说,如果你有很少的预测变量且其影响力中等或较大,可以使用套索回归。
- 弹性网实际上是两者之间的折中,因此需要花费大量的计算时间来达到这种折中。如果你有足够的资源,可以使用弹性网。
代码实现
# 实验跟踪
import mlflow
import os
# 这是Docker化的方法。
# 我们构建了两个Docker容器,一个用于Python/Jupyter,另一个用于mlflow。
# URL `mlflow` 解析为同一Composer中的另一个容器。
mlflow.set_tracking_uri("http://localhost:5000")
# 在Docker化的方式中,运行此代码的用户将是`root`。
# MLflow还将记录运行的用户ID为`root`。
# 若要更改这一点,我们需要将此environ["LOGNAME"]设置为你的名字。
os.environ["LOGNAME"] = "chaky"
# mlflow.create_experiment(name="chaky-diabetes-example") #如尚未创建,则创建
mlflow.set_experiment(experiment_name="chaky-regularization-example")
# 导入所需的库
from sklearn.datasets import load_diabetes
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import numpy as np
from time import time
# 加载数据集
diabetes = load_diabetes()
print("Features: ", diabetes.feature_names)
X = diabetes.data
y = diabetes.target
m = X.shape[0] # 样本数量
n = X.shape[1] # 特征数量
# 拆分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# 标准化特征
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# 添加截距
intercept = np.ones((X_train.shape[0], 1))
X_train = np.concatenate((intercept, X_train), axis=1)
intercept = np.ones((X_test.shape[0], 1))
X_test = np.concatenate((intercept, X_test), axis=1)
# 定义线性回归类并进行正则化
from sklearn.model_selection import KFold
class LinearRegression(object):
kfold = KFold(n_splits=3)
def __init__(self, regularization, lr=0.001, method='batch', num_epochs=500, batch_size=50, cv=kfold):
self.lr = lr
self.num_epochs = num_epochs
self.batch_size = batch_size
self.method = method
self.cv = cv
self.regularization = regularization
def mse(self, ytrue, ypred):
return ((ypred - ytrue) ** 2).sum() / ytrue.shape[0]
def fit(self, X_train, y_train):
# 创建kfold评分列表
self.kfold_scores = list()
# 重置验证损失
self.val_loss_old = np.infty
for fold, (train_idx, val_idx) in enumerate(self.cv.split(X_train)):
X_cross_train = X_train[train_idx]
y_cross_train = y_train[train_idx]
X_cross_val = X_train[val_idx]
y_cross_val = y_train[val_idx]
self.theta = np.zeros(X_cross_train.shape[1])
with mlflow.start_run(run_name=f"Fold-{fold}", nested=True):
params = {"method": self.method, "lr": self.lr, "reg": type(self).__name__}
mlflow.log_params(params=params)
for epoch in range(self.num_epochs):
perm = np.random.permutation(X_cross_train.shape[0])
X_cross_train = X_cross_train[perm]
y_cross_train = y_cross_train[perm]
if self.method == 'sto':
for batch_idx in range(X_cross_train.shape[0]):
X_method_train = X_cross_train[batch_idx].reshape(1, -1)
y_method_train = y_cross_train[batch_idx]
train_loss = self._train(X_method_train, y_method_train)
elif self.method == 'mini':
for batch_idx in range(0, X_cross_train.shape[0], self.batch_size):
X_method_train = X_cross_train[batch_idx:batch_idx+self.batch_size, :]
y_method_train = y_cross_train[batch_idx:batch_idx+self.batch_size]
train_loss = self._train(X_method_train, y_method_train)
else:
X_method_train = X_cross_train
y_method_train = y_cross_train
train_loss = self._train(X_method_train, y_method_train)
mlflow.log_metric(key="train_loss", value=train_loss, step=epoch)
yhat_val = self.predict(X_cross_val)
val_loss_new = self.mse(y_cross_val, yhat_val)
mlflow.log_metric(key="val_loss", value=val_loss_new, step=epoch)
if np.allclose(val_loss_new, self.val_loss_old):
break
self.val_loss_old = val_loss_new
self.kfold_scores.append(val_loss_new)
print(f"Fold {fold}: {val_loss_new}")
def _train(self, X, y):
yhat = self.predict(X)
m = X.shape[0]
grad = (1/m) * X.T @ (yhat - y) + self.regularization.derivation(self.theta)
self.theta = self.theta - self.lr * grad
return self.mse(y, yhat)
def predict(self, X):
return X @ self.theta
def _coef(self):
return self.theta[1:]
def _bias(self):
return self.theta[0]
# 创建`Ridge`、`Lasso`和`ElasticNet`类
class LassoPenalty:
def __init__(self, l):
self.l = l
def __call__(self, theta):
return self.l * np.sum(np.abs(theta))
def derivation(self, theta):
return self.l * np.sign(theta)
class RidgePenalty:
def __init__(self, l):
self.l = l
def __call__(self, theta):
return self.l * np.sum(np.square(theta))
def derivation(self, theta):
return self.l * 2 * theta
class ElasticPenalty:
def __init__(self, l = 0.1, l_ratio = 0.5):
self.l = l
self.l_ratio = l_ratio
def __call__(self, theta):
l1_contribution = self.l_ratio * self.l * np.sum(np.abs(theta))
l2_contribution = (1 - self.l_ratio) * self.l * 0.5 * np.sum(np.square(theta))
return (l1_contribution + l2_contribution)
def derivation(self, theta):
l1_derivation = self.l * self.l_ratio * np.sign(theta)
l2_derivation = self.l * (1 - self.l_ratio) * theta
return (l1_derivation + l2_derivation)
class Lasso(LinearRegression):
def __init__(self, method, lr, l):
self.regularization = LassoPenalty(l)
super().__init__(self.regularization, lr, method)
class Ridge(LinearRegression):
def __init__(self, method, lr, l):
self.regularization = RidgePenalty(l)
super().__init__(self.regularization, lr, method)
class ElasticNet(LinearRegression):
def __init__(self, method, lr, l, l_ratio=0.5):
self.regularization = ElasticPenalty(l, l_ratio)
super().__init__(self.regularization, lr, method)
# 进行实验
regs = ["Ridge", "Lasso", "ElasticNet"]
for reg in regs:
params = {"method": "batch", "lr": 0.1, "l": 0.1}
mlflow.start_run(run_name=f"method-{params['method']}-lr-{params['lr']}-reg-{reg}", nested=True)
print("="*5, reg, "="*5)
type_of_regression = str_to_class(reg)
model = type_of_regression(**params)
model.fit(X_train, y_train)
yhat = model.predict(X_test)
mse = model.mse(yhat, y_test)
print("Test MSE: ", mse)
mlflow.log_metric(key="test_mse", value=mse)
signature = mlflow.models.infer_signature(X_train, model.predict(X_train))
mlflow.sklearn.log_model(model, artifact_path='model', signature=signature)
mlflow.end_run()
这部分代码覆盖了正则化在Python中的完整实现,包括了Lasso、Ridge和ElasticNet的具体实现与实验。
结语
正则化是机器学习中必不可少的技术之一,通过在模型中引入适当的约束,可以有效地减少过拟合并提高模型的泛化能力。在实际应用中,根据数据的特点选择合适的正则化方法,如L2正则化(岭回归)、L1正则化(套索回归)或两者结合的弹性网,是至关重要的。理解和掌握这些方法将为构建更加稳健和高效的模型打下坚实的基础。
敬请期待下一篇博文:基于Python的机器学习系列(5):Closed Form。
如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!
欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。
谢谢大家的支持!