一. 算法介绍
1.1 回归与分类
回归与分类是机器学习中的两大基本任务,下面先简要介绍一下二者的基本概念与区别
1.1.1 回归(Regression)
回归的目标是对连续值的预测,即在给定一组特征 X的情况下,预测一个实数值 y,在回归问题中,变量是连续的,可以取任意的值。比如给定若干特征值进行如房价的预测或者气温和降水量的预测。
总而言之,回归的输入和输出都没有固定的值,其预测图像往往是连续的
1.1.2 分类(Classify)
分类的目标是离散类别的预测,即在给定一组特征 X的情况下,预测一个离散的类别标签 y,在分类问题中,目标的预测结果是离散的,可以取有限个类别。该问题的经典应用场景有垃圾邮件分类、好瓜坏瓜分类等。
1.1.3 回归与分类的区别
特性 | 回归 | 分类 |
---|---|---|
目标变量类型 | 连续值 | 离散类别 |
任务目标 | 预测一个实数值 | 预测一个类别标签 |
评价指标 | 均方误差(MSE)、R^2等 | 准确率、精确率、召回率、F1分数等 |
应用场景 | 房价预测、销售预测、气温预测 | 图像分类、文本分类、医疗诊断 |
1.2 算法简介
首先对该算法进行一个定性,别看逻辑回归叫做回归,但这其实是一种分类算法,尤其适用于二分类问题。
它通过学习数据特征与类别之间的关系,来预测样本属于某一类别的概率。
该算法的核心是线性模型和Sigmoid函数的结合。
1.2.1 线性模型
z = ω ⋅ χ + b z=\omega\cdot\chi+b z=ω⋅χ+b
公式如上,该公式与初中学的线性函数一致,不同的是该函数的 ω \omega ω和 χ \chi χ代表的不是单独的数字,而是特征向量及其权重的集合,即输入特征 χ = ( x 1 , x 2 , … , x n ) \mathbf{\chi} = (x_1, x_2, \ldots, x_n) χ=(x1,x2,…,xn),权重向量 ω = ( w 1 , w 2 , … , w n ) \mathbf{\omega} = (w_1, w_2, \ldots, w_n) ω=(w1,w2,…,wn)。
在该算法中, ω \omega ω和b的确定就是训练的核心重点,为了将计算所得的值映射到概率空间中,引入了Sigmoid函数。
1.2.2 Sigmoid函数
σ ( z ) = 1 1 + e − z = 1 1 + e ω ⋅ χ + b \sigma(z)=\dfrac{1}{1+e^{-z}}=\dfrac{1}{1+e^{\omega\cdot\chi+b}} σ(z)=1+e−z1=1+eω⋅χ+b1
该函数的图像如下:
当输入值越趋近于负无穷时,输出结果越接近于0,反之越接近于1,当输入值等于0时,输出值为0.5。由此,我们可以将线性模型得到的数值映射到(0, 1)之间,当结果为(0, 0.5)时我们可以判断其为A,反之则为B。
先前提到了该算法的训练重点就是找到合适的 ω \omega ω和b,由此我们引入两个概念,损失函数和梯度下降法
1.2.3 损失函数
L ( y , y ^ ) = − [ y ⋅ l n ( y ^ ) + ( 1 − y ) ⋅ l n ( 1 − y ^ ) ] L(y,\hat{y})=-[y\cdot ln(\hat{y})+(1-y)\cdot ln(1-\hat{y})] L(y,y^)=−[y⋅ln(y^)+(1−y)⋅ln(1−y^)]
如上即为单个样本的损失函数,其中
- y为样本的真实标签(0或1)
- y ^ \hat{y} y^是样本的预测概率
该函数衡量的是模型预测值与真实值之间的差异,整个训练集的损失函数为所有样本损失的平均:
J
(
w
,
b
)
=
−
1
m
∑
i
=
1
m
[
y
i
⋅
l
n
(
y
i
^
)
+
(
1
−
y
i
)
⋅
l
n
(
1
−
y
i
^
)
]
J(w,b)= -\dfrac{1}{m}\sum\limits_{i=1}^m[y_i\cdot ln(\hat{y_i})+(1-y_i)\cdot ln(1-\hat{y_i})]
J(w,b)=−m1i=1∑m[yi⋅ln(yi^)+(1−yi)⋅ln(1−yi^)]
- 其中m为样本的数量
体感上,我们就是希望找到一对合适的w和b,使得 J ( w , b ) J(w,b) J(w,b)的值最小,由此我们引入梯度下降法
1.2.4 梯度下降
梯度下降法是一种优化算法用于通过迭代更新参数来最小化损失函数。在逻辑回归中,参数包括权重 w和偏置 b。
梯度下降的目标是找到使损失函数最小化的参数值。通过计算损失函数对参数的偏导数(梯度),我们可以确定如何调整参数以减少损失。
梯度下降的更新公式如下:
w
=
w
−
α
⋅
∂
l
o
s
s
∂
w
b
=
b
−
α
⋅
∂
l
o
s
s
∂
b
w=w-\alpha\cdot \dfrac{\partial loss}{\partial w}\\ b=b-\alpha\cdot \dfrac{\partial loss}{\partial b}
w=w−α⋅∂w∂lossb=b−α⋅∂b∂loss
其中
∂
l
o
s
s
∂
w
=
1
m
∑
i
=
1
m
(
y
−
y
^
)
x
i
j
∂
l
o
s
s
∂
b
=
1
m
∑
i
=
1
m
(
y
−
y
^
)
\dfrac{\partial loss}{\partial w}=\dfrac{1}{m}\sum\limits_{i=1}^m(y-\hat{y})x_{ij}\\ \dfrac{\partial loss}{\partial b}=\dfrac{1}{m}\sum\limits_{i=1}^m(y-\hat{y})
∂w∂loss=m1i=1∑m(y−y^)xij∂b∂loss=m1i=1∑m(y−y^)
-
∂ l o s s ∂ w \dfrac{\partial loss}{\partial w} ∂w∂loss:这是损失函数对权重 w 的偏导数,也就是权重 w的梯度。它告诉我们,如何调整权重w以减少损失。
-
m m m:训练样本的数量。
-
y i ^ \hat{y_i} yi^:第 i个样本的预测值,由模型的 sigmoid 函数计算得到。
-
y y y:第 iii 个样本的实际标签。
-
x i j x_{ij} xij:第 i 个样本的第j个特征值。
梯度下降公式的主要目的是计算每个w和b对损失函数的影响,即在当前参数下,每个特征对损失的贡献。通过计算这个梯度,我们可以知道在下一次迭代中如何调整w和b以减少整体损失。
二.算法实现
2.1 数据集收集与处理
此次实验使用titanic数据集,该数据集包含了泰坦尼克号幸存者与死亡者的信息数据
def load_data():
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
data = pd.read_csv(url)
return data
然后,我们从其中选取部分标签并对标签进行处理,比如年龄需要将部分缺失的数据用平均值填充避免数据缺失,年龄需要将其转换成更容易处理的0/1,之后再将数据和标签分开,最后对其进行标准化。
def preprocess_data(data):
# 选择五个特征:Pclass, Sex, Age, SibSp, Parch 和目标变量 Survived
data = data.loc[:, ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Survived']]
data.loc[:, 'Age'] = data['Age'].fillna(data['Age'].mean())
data.loc[:, 'Sex'] = data['Sex'].map({'male': 0, 'female': 1})
X = data[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch']].values
y = data['Survived'].values
scaler = StandardScaler()
X = scaler.fit_transform(X)
return X, y
2.2 函数定义
根据上面提到的公式定义需要的函数
def sigmoid(z):
# 确保z是一个numpy数组,需要对输入的数据进行处理
z = np.array(z, dtype=np.float64)
return 1 / (1 + np.exp(-z))
def compute_loss(y, y_pred):
#损失函数计算
m = len(y)
return -1 / m * np.sum(y * np.log(y_pred) + (1 - y) * np.log(1 - y_pred))
def compute_gradients(X, y, y_pred):
# 计算并返回权重和偏置的梯度
m = X.shape[0]
dw = 1 / m * np.dot(X.T, (y_pred - y))
db = 1 / m * np.sum(y_pred - y)
dw = dw.astype(np.float64)
db = db.astype(np.float64)
return dw, db
def gradient_descent(X, y, w, b, learning_rate, num_iterations):
# 通过多次迭代更新权重和偏置,降低损失
for i in range(num_iterations):
z = np.dot(X, w) + b
y_pred = sigmoid(z)
loss = compute_loss(y, y_pred)
dw, db = compute_gradients(X, y, y_pred)
#更新w和b
w -= learning_rate * dw
b -= learning_rate * db
# 每100次迭代打印一次损失
if i % 100 == 0:
print(f'Iteration {i}, Loss: {loss:.4f}')
return w, b
2.3 主函数部分
在这个部分,我们需要对现有的数据集进行处理划分,再初始化w和b并使用上面定义的迭代函数进行迭代以获取理想的值。
def main():
# 读取并预处理数据
data = load_data()
X, y = preprocess_data(data)
# 切分数据为训练集和测试集,测试集占20%
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 初始化参数:权重为0,偏置为0
w = np.zeros(X_train.shape[1], dtype=np.float64)
b = 0
learning_rate = 0.01 # 学习率
num_iterations = 1000 # 迭代次数
# 训练模型,通过梯度下降优化参数
w, b = gradient_descent(X_train, y_train, w, b, learning_rate, num_iterations)
# 测试模型,在测试集上评估准确性
z_test = np.dot(X_test, w) + b
y_test_pred = sigmoid(z_test) > 0.5 # 阈值为0.5,判断是否为正类
# 计算并打印测试集上的准确率
accuracy = np.mean(y_test_pred == y_test)
print(f'Accuracy on test set: {accuracy:.4f}')
if __name__ == "__main__":
main()
2.4 运行结果
结果如下,我们可以看到随着迭代次数增加,损失值逐渐下降,在测试集中的正确率可以达到0.7877。
Iteration 0, Loss: 0.6931
Iteration 100, Loss: 0.6070
Iteration 200, Loss: 0.5562
Iteration 300, Loss: 0.5249
Iteration 400, Loss: 0.5047
Iteration 500, Loss: 0.4911
Iteration 600, Loss: 0.4816
Iteration 700, Loss: 0.4748
Iteration 800, Loss: 0.4698
Iteration 900, Loss: 0.4660
Accuracy on test set: 0.7877
2.5 模型使用
我们可以添加一个函数以判断新的数据是否生存
def predict_survival(single_data, w, b, scaler):
# 处理单条数据:标准化并使用训练好的模型进行预测
single_data = np.array(single_data).reshape(1, -1)
single_data = scaler.transform(single_data)
z = np.dot(single_data, w) + b
y_pred = sigmoid(z)
return '生存' if y_pred >= 0.5 else '死亡'
这个函数会将输入的数据处理好后,使用指定的w和b进行预测,然后再主函数中添加如下代码
single_data = [3, 0, 25, 1, 0] # 示例数据
prediction = predict_survival(single_data, w, b, scaler)
print(f'单条数据 {single_data} 的预测结果: {prediction}')
即可打印出判断结果。