12、基于逻辑回归的广告点击率预测

基于逻辑回归的广告点击率预测

1. 引言

在线广告点击率预测是一个典型的机器学习问题,在处理这个问题时,会面临包括类别特征在内的诸多挑战。之前我们采用了基于树的算法,它能同时处理数值和类别特征。现在,我们将重点学习一种预处理技术——独热编码(One-hot Encoding),以及逻辑回归算法、逻辑回归的正则化方法,还有适用于超大型数据集的变体。此外,我们还会探讨逻辑回归在特征选择方面的应用。

2. 独热编码:将类别特征转换为数值特征

在处理机器学习问题时,许多算法只能处理数值特征,而实际数据中常常包含类别特征。独热编码就是一种将类别特征转换为数值特征的有效方法。

2.1 简单映射的问题

如果将具有 k 个可能值的类别特征简单地映射为从 1 到 k 的数值特征,会引入序数特征和距离属性。例如,将 [Tech, Fashion, Fashion, Sports, Tech, Tech, Sports] 映射为 [1, 2, 2, 3, 1, 1, 3] ,这会暗示 Sports 大于 Tech ,并且 Sports Tech 更接近 Fashion ,但这种关系在实际数据中可能并不存在。

2.2 独热编码的原理

独热编码将类别特征转换为 k 个二进制特征,每个二进制特征表示对应可能值的存在与否。例如,上述示例经过独热编码后会变成相应的二进制向量。

2.3 使用 DictVectorizer 进行独热编码

scikit-learn 中的 DictVectorizer 提供了一种高效的解决方案,它可以将字典对象(类别特征: 值)转换为独热编码向量。以下是一个示例代码:

from sklearn.feature_extraction import DictVectorizer
X_dict = [{'interest': 'tech', 'occupation': 'professional'},
          {'interest': 'fashion', 'occupation': 'student'},
          {'interest': 'fashion', 'occupation': 'professional'},
          {'interest': 'sports', 'occupation': 'student'},
          {'interest': 'tech', 'occupation': 'student'},
          {'interest': 'tech', 'occupation': 'retired'},
          {'interest': 'sports', 'occupation': 'professional'}]
dict_one_hot_encoder = DictVectorizer(sparse=False)
X_encoded = dict_one_hot_encoder.fit_transform(X_dict)
print(X_encoded)

运行上述代码,输出结果如下:

[[ 0.  0.  1.  1.  0.  0.]
 [ 1.  0.  0.  0.  0.  1.]
 [ 1.  0.  0.  1.  0.  0.]
 [ 0.  1.  0.  0.  0.  1.]
 [ 0.  0.  1.  0.  0.  1.]
 [ 0.  0.  1.  0.  1.  0.]
 [ 0.  1.  0.  1.  0.  0.]]

我们还可以查看映射关系:

print(dict_one_hot_encoder.vocabulary_)

输出结果为:

{'interest=fashion': 0, 'interest=sports': 1,
'occupation=professional': 3, 'interest=tech': 2,
'occupation=retired': 4, 'occupation=student': 5}

对于新数据,我们可以使用以下代码进行转换:

new_dict = [{'interest': 'sports', 'occupation': 'retired'}]
new_encoded = dict_one_hot_encoder.transform(new_dict)
print(new_encoded)

输出结果为:

[[ 0.  1.  0.  0.  1.  0.]]

并且可以将编码后的特征逆转换回原始特征:

print(dict_one_hot_encoder.inverse_transform(new_encoded))

输出结果为:

[{'interest=sports': 1.0, 'occupation=retired': 1.0}]
2.4 使用 LabelEncoder OneHotEncoder 处理字符串特征

对于字符串对象格式的特征,我们可以先使用 LabelEncoder 将类别特征转换为从 1 到 k 的整数特征,然后再使用 OneHotEncoder 将整数特征转换为 k 个二进制编码特征。以下是示例代码:

import numpy as np
X_str = np.array([['tech', 'professional'],
                  ['fashion', 'student'],
                  ['fashion', 'professional'],
                  ['sports', 'student'],
                  ['tech', 'student'],
                  ['tech', 'retired'],
                  ['sports', 'professional']])
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
label_encoder = LabelEncoder()
X_int = label_encoder.fit_transform(X_str.ravel()).reshape(*X_str.shape)
print(X_int)
one_hot_encoder = OneHotEncoder()
X_encoded = one_hot_encoder.fit_transform(X_int).toarray()
print(X_encoded)

输出结果如下:

[[5 1]
 [0 4]
 [0 1]
 [3 4]
 [5 4]
 [5 2]
 [3 1]]
[[ 0.  0.  1.  1.  0.  0.]
 [ 1.  0.  0.  0.  0.  1.]
 [ 1.  0.  0.  1.  0.  0.]
 [ 0.  1.  0.  0.  0.  1.]
 [ 0.  0.  1.  0.  0.  1.]
 [ 0.  0.  1.  0.  1.  0.]
 [ 0.  1.  0.  1.  0.  0.]]
2.5 处理新类别

如果在新数据中遇到训练数据中未出现的新类别,应该忽略它。 DictVectorizer 会自动处理这种情况,而 LabelEncoder 不会隐式处理。为了避免这个问题,可以将字符串数据转换为字典对象,然后使用 DictVectorizer 。以下是转换函数的定义和使用示例:

def string_to_dict(columns, data_str):
    columns = ['interest', 'occupation']
    data_dict = []
    for sample_str in data_str:
        data_dict.append({column: value for column, value in zip(columns, sample_str)})
    return data_dict

new_str = np.array([['unknown_interest', 'retired'],
                    ['tech', 'unseen_occupation'],
                    ['unknown_interest', 'unseen_occupation']])
columns = ['interest', 'occupation']
new_encoded = dict_one_hot_encoder.transform(string_to_dict(columns, new_str))
print(new_encoded)

输出结果如下:

[[ 0.  0.  0.  0.  1.  0.]
 [ 0.  0.  1.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.]]
3. 逻辑回归分类器

之前我们仅基于 4000 万个样本中的前 10 万个样本训练基于树的模型,因为在大型数据集上训练树模型计算成本极高且耗时。由于独热编码的应用,我们不再局限于直接处理类别特征的算法,现在可以转向对大型数据集具有高扩展性的新算法,逻辑回归就是其中一种最具扩展性的分类算法。

3.1 逻辑函数

在深入了解逻辑回归算法之前,我们先介绍作为算法核心的逻辑函数(也称为 Sigmoid 函数)。逻辑函数将输入映射到 0 到 1 之间的输出,其定义如下:

import numpy as np
def sigmoid(input):
    return 1.0 / (1 + np.exp(-input))

我们可以通过以下代码可视化逻辑函数:

z = np.linspace(-8, 8, 1000)
y = sigmoid(z)
import matplotlib.pyplot as plt
plt.plot(z, y)
plt.axhline(y=0, ls='dotted', color='k')
plt.axhline(y=0.5, ls='dotted', color='k')
plt.axhline(y=1, ls='dotted', color='k')
plt.yticks([0.0, 0.25, 0.5, 0.75, 1.0])
plt.xlabel('z')
plt.ylabel('y(z)')
plt.show()

在 S 形曲线中,所有输入都被转换到 0 到 1 的范围内。对于正输入,值越大输出越接近 1;对于负输入,值越小输出越接近 0;当输入为 0 时,输出为中点 0.5。

3.2 逻辑回归的机制

在逻辑回归中,逻辑函数的输入 z 成为特征的加权和。给定一个具有 n 个特征 x1, x2, ..., xn 的数据样本 x(x 表示特征向量 (x1, x2, ..., xn) ),以及模型的权重(也称为系数)w(w 表示向量 (w1, w2, ..., wn) ),z 可以表示为:
[z = w_1x_1 + w_2x_2 + … + w_nx_n]
有时,模型还会带有一个截距(也称为偏差)w0,上述线性关系变为:
[z = w_0 + w_1x_1 + w_2x_2 + … + w_nx_n]
逻辑函数的输出 y(z) 在 0 到 1 的范围内,在算法中,它表示目标为 “1” 或正类别的概率。因此,逻辑回归是一种概率分类器,类似于朴素贝叶斯分类器。

逻辑回归模型的权重向量 w 是从训练数据中学习得到的,目标是尽可能将正样本预测为接近 1,将负样本预测为接近 0。在数学上,权重的训练是为了最小化定义为均方误差(MSE)的成本,MSE 衡量了真实值和预测值之间差异平方的平均值。给定 m 个训练样本 ((x^{(i)}, y^{(i)})),其中 (y^{(i)}) 要么是 1(正类别)要么是 0(负类别),关于待优化权重的成本函数 J(w) 可以表示为:
[J(w) = \frac{1}{2m} \sum_{i=1}^{m} (y^{(i)} - \hat{y}^{(i)})^2]
然而,上述成本函数是非凸的,这意味着在搜索最优 w 时,会找到许多局部(次优)最优解,函数不会收敛到全局最优解。

为了克服这个问题,实际中使用的成本函数定义如下:
[J(w) = -\frac{1}{m} \sum_{i=1}^{m} [y^{(i)} \log(\hat{y}^{(i)}) + (1 - y^{(i)}) \log(1 - \hat{y}^{(i)})]]
我们可以更详细地查看单个训练样本的成本:
- 当 (y^{(i)} = 1) 时,如果预测正确(正类别概率为 100%),样本成本 j 为 0;随着正类别概率降低,成本不断增加;当错误地预测为没有正类别的可能性时,成本为无穷大。

y_hat = np.linspace(0, 1, 1000)
cost = -np.log(y_hat)
plt.plot(y_hat, cost)
plt.xlabel('Prediction')
plt.ylabel('Cost')
plt.xlim(0, 1)
plt.ylim(0, 7)
plt.show()
  • 当 (y^{(i)} = 0) 时,如果预测正确(正类别概率为 0,即负类别概率为 100%),样本成本 j 为 0;随着正类别概率增加,成本不断增加;当错误地预测为没有负类别的可能性时,成本为无穷大。
y_hat = np.linspace(0, 1, 1000)
cost = -np.log(1 - y_hat)
plt.plot(y_hat, cost)
plt.xlabel('Prediction')
plt.ylabel('Cost')
plt.xlim(0, 1)
plt.ylim(0, 7)
plt.show()

最小化这个替代成本函数实际上等同于最小化基于 MSE 的成本函数,而且选择它的优点包括:
- 明显是凸函数,因此可以找到最优的模型权重。
- 预测值 (\hat{y}^{(i)}) 或 (1 - \hat{y}^{(i)}) 的对数求和简化了其关于权重的导数计算。
由于使用了对数函数,这个成本函数也被称为对数损失,或简称为对数损失。

3.3 通过梯度下降训练逻辑回归模型

现在的问题是如何获得最优的 w,使得成本函数 (J(w)) 最小化。我们可以通过梯度下降来实现。

梯度下降(也称为最速下降)是一种通过一阶迭代优化来最小化目标函数的过程。在每次迭代中,它沿着目标函数在当前点的负导数方向移动一步,这意味着待优化点会迭代地向目标函数的最小值下降。这个比例被称为学习率,或步长。可以用以下数学公式总结:
[w_{new} = w_{old} - \alpha \nabla J(w_{old})]
其中,左边的 w 是学习步骤后的权重向量,右边的 w 是移动前的权重向量,(\alpha) 是学习率,(\nabla J(w_{old})) 是一阶导数,即梯度。

在我们的例子中,我们从成本函数 (J(w)) 关于 w 的导数开始。这可能需要一些微积分知识,但我们会逐步讲解。

首先,我们计算 (\hat{y}^{(i)}) 关于 w 的导数。这里以第 j 个权重 (w_j) 为例(注意 (z^{(i)} = w_0 + w_1x_1^{(i)} + … + w_nx_n^{(i)}),为了简化,我们省略 ((i))):
[\frac{\partial \hat{y}}{\partial w_j} = \hat{y}(1 - \hat{y})x_j]
然后,样本成本 J(w) 的导数为:
[\frac{\partial J(w)}{\partial w_j} = \frac{1}{m} \sum_{i=1}^{m} (\hat{y}^{(i)} - y^{(i)})x_j^{(i)}]
最后,m 个样本的总成本的导数为:
[\nabla J(w) = \frac{1}{m} X^T (\hat{y} - y)]
结合上述推导,权重可以更新如下:
[w = w - \alpha \frac{1}{m} X^T (\hat{y} - y)]
w 在每次迭代中都会更新。经过大量迭代后,学习到的 w 和 b 可以用于对新样本进行分类:
[\hat{y} = \sigma(w^T x + b)]
默认的决策阈值是 0.5,但也可以是其他值。在需要尽可能避免假阴性的情况下,例如预测火灾发生(正类别)以进行警报,决策阈值可以低于 0.5,如 0.3,具体取决于我们的谨慎程度和预防正事件发生的积极性。另一方面,当需要避免假阳性类别时,例如预测产品成功率以进行质量保证,决策阈值可以高于 0.5,如 0.7,具体取决于我们设定的标准。

3.4 从零实现逻辑回归算法

有了对基于梯度下降的训练和预测过程的深入理解,我们现在可以从零实现逻辑回归算法。

首先,定义计算当前权重下预测值 (\hat{y}) 的函数:

def compute_prediction(X, weights):
    """ Compute the prediction y_hat based on current weights
    Args:
        X (numpy.ndarray)
        weights (numpy.ndarray)
    Returns:
        numpy.ndarray, y_hat of X under weights
    """
    z = np.dot(X, weights)
    predictions = sigmoid(z)
    return predictions

接着,定义以梯度下降方式更新权重的函数:

def update_weights_gd(X_train, y_train, weights, learning_rate):
    """ Update weights by one step
    Args:
        X_train, y_train (numpy.ndarray, training data set)
        weights (numpy.ndarray)
        learning_rate (float)
    Returns:
        numpy.ndarray, updated weights
    """
    predictions = compute_prediction(X_train, weights)
    weights_delta = np.dot(X_train.T, y_train - predictions)
    m = y_train.shape[0]
    weights += learning_rate / float(m) * weights_delta
    return weights

然后,定义计算成本 J(w) 的函数:

def compute_cost(X, y, weights):
    """ Compute the cost J(w)
    Args:
        X, y (numpy.ndarray, data set)
        weights (numpy.ndarray)
    Returns:
        float
    """
    predictions = compute_prediction(X, weights)
    cost = np.mean(-y * np.log(predictions) - (1 - y) * np.log(1 - predictions))
    return cost

现在,将这些函数组合起来,定义模型训练函数:

def train_logistic_regression(X_train, y_train, max_iter, learning_rate, fit_intercept=False):
    """ Train a logistic regression model
    Args:
        X_train, y_train (numpy.ndarray, training data set)
        max_iter (int, number of iterations)
        learning_rate (float)
        fit_intercept (bool, with an intercept w0 or not)
    Returns:
        numpy.ndarray, learned weights
    """
    if fit_intercept:
        intercept = np.ones((X_train.shape[0], 1))
        X_train = np.hstack((intercept, X_train))
    weights = np.zeros(X_train.shape[1])
    for iteration in range(max_iter):
        weights = update_weights_gd(X_train, y_train, weights, learning_rate)
        # Check the cost for every 100 (for example) iterations
        if iteration % 100 == 0:
            print(compute_cost(X_train, y_train, weights))
    return weights

最后,使用训练好的模型预测新输入的结果:

def predict(X, weights):
    if X.shape[1] == weights.shape[0] - 1:
        intercept = np.ones((X.shape[0], 1))
        X = np.hstack((intercept, X))
    return compute_prediction(X, weights)

下面是一个小例子来验证实现的逻辑回归算法:

X_train = np.array([[6, 7],
                    [2, 4],
                    [3, 6],
                    [4, 7],
                    [1, 6],
                    [5, 2],
                    [2, 0],
                    [6, 3],
                    [4, 1],
                    [7, 2]])
y_train = np.array([0,
                    0,
                    0,
                    0,
                    0,
                    1,
                    1,
                    1,
                    1,
                    1])
weights = train_logistic_regression(X_train, y_train, max_iter=1000, learning_rate=0.1, fit_intercept=True)

输出的成本值逐渐减小,这意味着模型正在被优化。我们可以检查模型在新样本上的性能:

X_test = np.array([[6, 1],
                   [1, 3],
                   [3, 1],
                   [4, 5]])
predictions = predict(X_test, weights)
print(predictions)

为了可视化结果,可以使用以下代码:

plt.scatter(X_train[:,0], X_train[:,1], c=['b']*5+['k']*5, marker='o')
colours = ['k' if prediction >= 0.5 else 'b' for prediction in predictions]
plt.scatter(X_test[:,0], X_test[:,1], marker='*', c=colours)
plt.xlabel('x1')
plt.ylabel('x2')
plt.show()

训练好的模型能够正确预测新样本。

4. 通过梯度下降进行逻辑回归的点击率预测

在小例子验证之后,我们将部署刚刚开发的算法,并在点击率预测项目中进行测试。

同样,前 10000 个样本用于训练,接下来的 10000 个样本用于测试:

n = 10000
X_dict_train, y_train = read_ad_click_data(n)
dict_one_hot_encoder = DictVectorizer(sparse=False)
X_train = dict_one_hot_encoder.fit_transform(X_dict_train)
X_dict_test, y_test = read_ad_click_data(n, n)
X_test = dict_one_hot_encoder.transform(X_dict_test)
X_train_10k = X_train
y_train_10k = np.array(y_train)

训练一个逻辑回归模型,迭代 10000 次,学习率为 0.01,包含截距,并每 1000 次迭代打印当前成本:

import timeit
start_time = timeit.default_timer()
weights = train_logistic_regression(X_train, y_train, max_iter=10000, learning_rate=0.01, fit_intercept=True)
print("--- %0.3fs seconds ---" % (timeit.default_timer() - start_time))

训练花费了 209 秒,并且成本逐渐减小。训练好的模型在测试集上的表现如下:

X_test_10k = X_test
predictions = predict(X_test_10k, weights)
from sklearn.metrics import roc_auc_score
print('The ROC AUC on testing set is: {0:.3f}'.format(roc_auc_score(y_test, predictions)))

测试集上的 ROC AUC 为 0.711,这个结果与之前使用随机森林得到的结果相当。

如开头所述,逻辑回归分类器在大型数据集上的训练效果较好,而基于树的模型通常不是这样。我们通过基于前 10 万个样本(仅比之前的样本量多 10 倍)训练模型来验证这一点。重复上述过程,只是这次 n = 100000

start_time = timeit.default_timer()
weights = train_logistic_regression(X_train_100k, y_train_100k, max_iter=10000, learning_rate=0.01, fit_intercept=True)
print("--- %0.3fs seconds ---" % (timeit.default_timer() - start_time))

基于 10 万个样本训练模型花费了超过一个小时!那么,我们如何有效地处理大型训练数据集,不仅仅是 10 万个样本,而是数百万个样本(例如训练文件中的 4000 万个样本)呢?这将是我们后续需要进一步探讨的问题。

5. 随机梯度下降优化逻辑回归训练

为了更高效地处理大型数据集,我们可以采用随机梯度下降(Stochastic Gradient Descent, SGD)来替代传统的梯度下降。随机梯度下降在每次迭代中只使用一个训练样本或一小批训练样本(mini-batch)来计算梯度并更新权重,而不是使用整个训练集。这样可以大大减少计算量,加快训练速度。

5.1 随机梯度下降原理

随机梯度下降的核心思想是在每次迭代中随机选择一个样本或一小批样本,计算这些样本的梯度并更新权重。与梯度下降相比,随机梯度下降的更新更加频繁,能够更快地收敛到最优解附近。

其更新公式与梯度下降类似,但只使用一个样本或一小批样本的梯度:
[w_{new} = w_{old} - \alpha \nabla J_{sample}(w_{old})]
其中,(\nabla J_{sample}(w_{old})) 是单个样本或一小批样本的梯度。

5.2 实现随机梯度下降的逻辑回归

我们可以修改之前的代码,实现基于随机梯度下降的逻辑回归。以下是修改后的代码:

import numpy as np

def sigmoid(input):
    return 1.0 / (1 + np.exp(-input))

def compute_prediction(X, weights):
    z = np.dot(X, weights)
    predictions = sigmoid(z)
    return predictions

def update_weights_sgd(X_train, y_train, weights, learning_rate):
    # 随机选择一个样本
    random_index = np.random.randint(0, X_train.shape[0])
    X_sample = X_train[random_index:random_index+1]
    y_sample = y_train[random_index:random_index+1]
    predictions = compute_prediction(X_sample, weights)
    weights_delta = np.dot(X_sample.T, y_sample - predictions)
    weights += learning_rate * weights_delta
    return weights

def compute_cost(X, y, weights):
    predictions = compute_prediction(X, weights)
    cost = np.mean(-y * np.log(predictions) - (1 - y) * np.log(1 - predictions))
    return cost

def train_logistic_regression_sgd(X_train, y_train, max_iter, learning_rate, fit_intercept=False):
    if fit_intercept:
        intercept = np.ones((X_train.shape[0], 1))
        X_train = np.hstack((intercept, X_train))
    weights = np.zeros(X_train.shape[1])
    for iteration in range(max_iter):
        weights = update_weights_sgd(X_train, y_train, weights, learning_rate)
        if iteration % 100 == 0:
            print(compute_cost(X_train, y_train, weights))
    return weights

def predict(X, weights):
    if X.shape[1] == weights.shape[0] - 1:
        intercept = np.ones((X.shape[0], 1))
        X = np.hstack((intercept, X))
    return compute_prediction(X, weights)

我们可以使用之前的小例子来测试基于随机梯度下降的逻辑回归:

X_train = np.array([[6, 7],
                    [2, 4],
                    [3, 6],
                    [4, 7],
                    [1, 6],
                    [5, 2],
                    [2, 0],
                    [6, 3],
                    [4, 1],
                    [7, 2]])
y_train = np.array([0,
                    0,
                    0,
                    0,
                    0,
                    1,
                    1,
                    1,
                    1,
                    1])
weights = train_logistic_regression_sgd(X_train, y_train, max_iter=1000, learning_rate=0.01, fit_intercept=True)

通过观察成本的变化,我们可以看到模型是否在不断优化。

6. 逻辑回归的正则化

在实际应用中,逻辑回归模型可能会出现过拟合的问题,即模型在训练集上表现很好,但在测试集上表现不佳。为了避免过拟合,我们可以使用正则化方法。常见的正则化方法有 L1 正则化和 L2 正则化。

6.1 L1 正则化

L1 正则化也称为 Lasso 正则化,它在成本函数中添加了权重的绝对值之和作为惩罚项。L1 正则化的成本函数可以表示为:
[J(w) = -\frac{1}{m} \sum_{i=1}^{m} [y^{(i)} \log(\hat{y}^{(i)}) + (1 - y^{(i)}) \log(1 - \hat{y}^{(i)})] + \lambda \sum_{j=1}^{n} |w_j|]
其中,(\lambda) 是正则化参数,控制惩罚项的强度。L1 正则化的一个重要特点是它可以使一些权重变为 0,从而实现特征选择的功能。

6.2 L2 正则化

L2 正则化也称为 Ridge 正则化,它在成本函数中添加了权重的平方和作为惩罚项。L2 正则化的成本函数可以表示为:
[J(w) = -\frac{1}{m} \sum_{i=1}^{m} [y^{(i)} \log(\hat{y}^{(i)}) + (1 - y^{(i)}) \log(1 - \hat{y}^{(i)})] + \frac{\lambda}{2} \sum_{j=1}^{n} w_j^2]
L2 正则化可以使权重值变小,但不会使权重变为 0。

6.3 实现带正则化的逻辑回归

我们可以修改之前的代码,实现带正则化的逻辑回归。以下是修改后的代码:

import numpy as np

def sigmoid(input):
    return 1.0 / (1 + np.exp(-input))

def compute_prediction(X, weights):
    z = np.dot(X, weights)
    predictions = sigmoid(z)
    return predictions

def update_weights_gd_regularized(X_train, y_train, weights, learning_rate, lambda_reg, reg_type='L2'):
    predictions = compute_prediction(X_train, weights)
    weights_delta = np.dot(X_train.T, y_train - predictions)
    m = y_train.shape[0]
    if reg_type == 'L1':
        reg_term = lambda_reg * np.sign(weights)
    elif reg_type == 'L2':
        reg_term = lambda_reg * weights
    weights += learning_rate / float(m) * (weights_delta - reg_term)
    return weights

def compute_cost_regularized(X, y, weights, lambda_reg, reg_type='L2'):
    predictions = compute_prediction(X, weights)
    cost = np.mean(-y * np.log(predictions) - (1 - y) * np.log(1 - predictions))
    if reg_type == 'L1':
        reg_term = lambda_reg * np.sum(np.abs(weights))
    elif reg_type == 'L2':
        reg_term = (lambda_reg / 2) * np.sum(weights**2)
    cost += reg_term
    return cost

def train_logistic_regression_regularized(X_train, y_train, max_iter, learning_rate, lambda_reg, reg_type='L2', fit_intercept=False):
    if fit_intercept:
        intercept = np.ones((X_train.shape[0], 1))
        X_train = np.hstack((intercept, X_train))
    weights = np.zeros(X_train.shape[1])
    for iteration in range(max_iter):
        weights = update_weights_gd_regularized(X_train, y_train, weights, learning_rate, lambda_reg, reg_type)
        if iteration % 100 == 0:
            print(compute_cost_regularized(X_train, y_train, weights, lambda_reg, reg_type))
    return weights

def predict(X, weights):
    if X.shape[1] == weights.shape[0] - 1:
        intercept = np.ones((X.shape[0], 1))
        X = np.hstack((intercept, X))
    return compute_prediction(X, weights)

我们可以使用之前的小例子来测试带正则化的逻辑回归:

X_train = np.array([[6, 7],
                    [2, 4],
                    [3, 6],
                    [4, 7],
                    [1, 6],
                    [5, 2],
                    [2, 0],
                    [6, 3],
                    [4, 1],
                    [7, 2]])
y_train = np.array([0,
                    0,
                    0,
                    0,
                    0,
                    1,
                    1,
                    1,
                    1,
                    1])
weights = train_logistic_regression_regularized(X_train, y_train, max_iter=1000, learning_rate=0.01, lambda_reg=0.1, reg_type='L2', fit_intercept=True)

通过调整正则化参数 (\lambda),我们可以控制正则化的强度,从而避免过拟合。

7. 逻辑回归的特征选择

逻辑回归不仅可以用于分类,还可以用于特征选择。通过观察权重的大小,我们可以判断哪些特征对模型的预测结果影响较大,从而选择重要的特征。

7.1 基于权重大小的特征选择

在训练好逻辑回归模型后,我们可以查看每个特征对应的权重。权重的绝对值越大,说明该特征对模型的影响越大。我们可以设置一个阈值,选择权重绝对值大于该阈值的特征作为重要特征。

以下是一个简单的示例代码:

# 假设我们已经训练好逻辑回归模型,得到了权重 weights
threshold = 0.1
important_features = []
for i, weight in enumerate(weights):
    if np.abs(weight) > threshold:
        important_features.append(i)
print("重要特征的索引:", important_features)
7.2 随机森林辅助特征选择

除了基于逻辑回归的权重进行特征选择外,我们还可以使用随机森林来辅助特征选择。随机森林可以计算每个特征的重要性得分,我们可以选择得分较高的特征。

以下是一个使用随机森林进行特征选择的示例代码:

from sklearn.ensemble import RandomForestClassifier

# 假设我们有训练数据 X_train 和标签 y_train
rf = RandomForestClassifier()
rf.fit(X_train, y_train)
feature_importances = rf.feature_importances_
threshold = 0.05
important_features_rf = []
for i, importance in enumerate(feature_importances):
    if importance > threshold:
        important_features_rf.append(i)
print("随机森林选择的重要特征索引:", important_features_rf)
8. 总结与展望

本文详细介绍了基于逻辑回归的广告点击率预测方法。我们首先学习了独热编码,将类别特征转换为数值特征,以便使用逻辑回归算法。接着,深入探讨了逻辑回归的原理,包括逻辑函数、成本函数和梯度下降算法,并从零实现了逻辑回归模型。为了处理大型数据集,我们介绍了随机梯度下降和正则化方法,以提高模型的训练效率和泛化能力。最后,我们讨论了逻辑回归在特征选择方面的应用。

在实际应用中,我们可以根据具体情况选择合适的方法和参数。例如,对于大型数据集,可以优先考虑随机梯度下降;对于过拟合问题,可以使用正则化方法。同时,特征选择可以帮助我们减少特征维度,提高模型的性能和可解释性。

未来,我们可以进一步探索更高效的优化算法,如 Adagrad、Adadelta 和 Adam 等,以提高逻辑回归的训练速度和性能。此外,还可以尝试将逻辑回归与其他机器学习算法相结合,如神经网络、支持向量机等,以获得更好的预测效果。

以下是一个简单的流程图,展示了基于逻辑回归的广告点击率预测的主要步骤:

graph LR
    A[数据预处理] --> B[独热编码]
    B --> C[划分训练集和测试集]
    C --> D[训练逻辑回归模型]
    D --> E[模型评估]
    E --> F[特征选择]
    F --> G[优化模型]
    G --> H[预测新数据]

通过以上步骤,我们可以构建一个完整的广告点击率预测系统,为在线广告投放提供有力的支持。

【无人机】基于改进粒子群算法的无人机路径规划研究[和遗传算法、粒子群算法进行比较](Matlab代码实现)内容概要:本文围绕基于改进粒子群算法的无人机路径规划展开研究,重点探讨了在复杂环境中利用改进粒子群算法(PSO)实现无人机三维路径规划的方法,并将其与遗传算法(GA)、标准粒子群算法等传统优化算法进行对比分析。研究内容涵盖路径规划的多目标优化、避障策略、航路点约束以及算法收敛性和寻优能力的评估,所有实验均通过Matlab代码实现,提供了完整的仿真验证流程。文章还提到了多种智能优化算法在无人机路径规划中的应用比较,突出了改进PSO在收敛速度和全局寻优方面的优势。; 适合人群:具备一定Matlab编程基础和优化算法知识的研究生、科研人员及从事无人机路径规划、智能优化算法研究的相关技术人员。; 使用场景及目标:①用于无人机在复杂地形或动态环境下的三维路径规划仿真研究;②比较不同智能优化算法(如PSO、GA、蚁群算法、RRT等)在路径规划中的性能差异;③为多目标优化问题提供算法选型和改进思路。; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注算法的参数设置、适应度函数设计及路径约束处理方式,同时可参考文中提到的多种算法对比思路,拓展到其他智能优化算法的研究与改进中。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值