Skip to main content

回归问题

线性回归

线性回归是利用连续性变量来估计实际数值(例如房价,呼叫次数和总销售额等)。

我们通过线性回归算法找出自变量和因变量间的最佳线性关系,图形上可以确定一条最佳直线。

这条最佳直线就是回归线。这个回归关系可以用Y=aX+bY=aX+b表示。

多个数据可以用Y=β0X0+β1X1+β2X2+βnXn+εY= β0X0 + β1X1 + β2X2+…… βnXn+ ε 表示。

损失函数

如何评估数据的离散程度呢?

平均值:数据相加除以数据个数

平均差:数据与平均值的差的绝对值相加除以数据个数

均方误差:数据与平均值的差的平方相加除以数据个数

数据1数据2平均值平均差均方误差
00000
-440416
714425

我们预期中,理想效果应该是 0、0 好于 -4、4 好于 7、1。只有均方误差正确的反应了这一点。

在预测出来的值和目标值之间差的部分,我们称之为损失,均方误差常见的用于评估数据离散程度的损失函数,以下是常见的损失函数及其特点

名称数学表达式值域导数表达式优点缺点
交叉熵损失(Cross Entropy)L=iyilog(y^i)L = -\sum_{i} y_i \log(\hat{y}_i)[0,+)[0,+\infty)Ly^i=yiy^i\frac{\partial L}{\partial \hat{y}_i} = -\frac{y_i}{\hat{y}_i}最常用,适合 one-hot 标签,梯度清晰,收敛快对异常值敏感,需要防止概率为0的情况
均方误差(MSE)L=1ni(yiy^i)2L = \frac{1}{n} \sum_{i} (y_i - \hat{y}_i)^2[0,+)[0,+\infty)Ly^i=2n(yiy^i)\frac{\partial L}{\partial \hat{y}_i} = -\frac{2}{n}(y_i - \hat{y}_i)简单直观,易于理解不适合分类任务,梯度在误差较大时过大
平均绝对误差(MAE)L=1niyiy^iL = \frac{1}{n} \sum_{i} \vert y_i - \hat{y}_i \vert[0,+)[0,+\infty)Ly^i=1nsgn(yiy^i)\frac{\partial L}{\partial \hat{y}_i} = -\frac{1}{n}sgn(y_i - \hat{y}_i)对异常值不敏感,稳定性好在零点不可导,优化困难

求导

通过误差的大小,我们可以慢慢修正我们的参数让线性拟合更好,导数可以反应数据变化的趋势,所以我们可以求导来修改参数。

这个过程也叫:求梯度、反向传播(狭义)

误差我们用的是均方误差:求了每个数据与平均值的差的平方,再加和,再求平均

每个数据的误差我们一般叫损失,写作lossloss,他的值等于数据集中的目标值,减去我们线性公式算出来的预测值。

即:loss=(y(wx+b))2loss = (y - (wx + b))^2

这个表达式中,loss是算出来的,y是目标值,源自数据集,x是特征值,源自数据集。

我们调整w和b的值就可以间接控制loss的值变大或变小(这里主要是期望变小)。

我想既更新w,也更新b。

所以分2次求导,第一次求:lossw\frac{\partial loss}{\partial w}

第二次求: lossb\frac{\partial loss}{\partial b}

手写线性回归

import numpy as np
from matplotlib import pyplot as plt


class Line:
def __init__(self, data):
self.w = 1
self.b = 0
self.learning_rate = 0.01
self.fig, (self.ax1, self.ax2) = plt.subplots(2, 1)
self.loss_list = []

def get_data(self, data):
self.X = np.array(data)[:, 0]
self.y = np.array(data)[:, 1]

def predict(self, x):
return self.w * x + self.b

def train(self, epoch_times):
for epoch in range(epoch_times):
total_loss = 0
for x, y in zip(self.X, self.y):
y_pred = self.predict(x)
# Calculate gradients
gradient_w = -2 * x * (y - y_pred)
gradient_b = -2 * (y - y_pred)
# Update weights
self.w -= self.learning_rate * gradient_w
self.b -= self.learning_rate * gradient_b
# Calculate loss
loss = (y - y_pred) ** 2
total_loss += loss
epoch_loss = total_loss / len(self.X)
self.loss_list.append(epoch_loss)
if epoch % 10 == 0:
print(f"loss: {epoch_loss}")
self.plot()
plt.ioff()
plt.show()

def plot(self):
plt.ion() # Enable interactive mode
self.ax2.clear()
self.ax1.clear()
x = np.linspace(0, 10, 100)
self.ax1.scatter(self.X, self.y, c="g")
self.ax1.plot(x, self.predict(x), c="b")
self.ax2.plot(list(range(len(self.loss_list))), self.loss_list)
plt.show()
plt.pause(0.1)

if __name__ == "__main__":
# Input data
data = [(1, 1), (1.8, 2), (2.5, 3), (4.2, 4), (5, 5), (6, 6), (7, 7)]
s = Line(data)
s.get_data(data)
s.train(100)

房价预测

from sklearn import datasets
from sklearn.linear_model import LinearRegression

# .fetch_california_housing() 加载加利福尼亚州住房数据集
loaded_data = datasets.fetch_california_housing()
# .data 数据集中的特征数据
data_X = loaded_data.data
# .target 数据集中的标签数据
data_y = loaded_data.target
# 创建线性回归模型
model = LinearRegression()
# 拟合模型
# .fit() 方法接受两个参数:特征数据和标签数据
model.fit(data_X, data_y)
# 打印回归系数和截距
print("回归系数 (斜率):", model.coef_)
print("截距:", model.intercept_)


# 预测前四所房屋价格
# .predict() 方法接受一个参数:特征数据
print(model.predict(data_X[:4, :]))
# 真实价格
print(data_y[:4])

# 效果评估
print(model.get_params())# 获取模型参数
# //{'copy_X': True, 'fit_intercept': True, 'n_jobs': None, 'positive': False}
print(model.score(data_X, data_y))
# // 0.606232685199805
# 这意味着数据集中因变量的 60% 的变异性已得到考虑,而其余 40% 的变异性仍未得到解释。

逻辑回归

有时候,数据并不是一种线性状态,例如:蝌蚪前期体型很小,变态之后体型忽然增大。

这过程更接近一个S

# 绘制逻辑回归的不同回归系数的sigmoid函数

import matplotlib.pyplot as plt
import numpy as np

def sigmoid(x,p=1):
# 直接返回sigmoid函数
return 1. / (1. + np.exp(-p*x))

def plot_sigmoid(p=1):
# param:起点,终点,间距
x = np.arange(-8, 8, 0.2)
y = sigmoid(x,p)
plt.plot(x, y)
plt.show()

if __name__ == '__main__':
plot_sigmoid()
plot_sigmoid(20)
plot_sigmoid(0.5)

逻辑回归是一种统计模型,它使用数学中的逻辑函数或 logit 函数作为 x 和 y 之间的方程式。Logit 函数将 y 映射为 x 的 sigmoid 函数。

f(x)=11+exf(x) = \frac{1}{1 + e^{-x}}

多个解释变量会影响因变量的值。要对此类输入数据集建模,逻辑回归公式假设不同自变量之间存在线性关系。您可以修改 sigmoid 函数并按如下公式计算最终输出变量

y=f(β0+β1x1+β2x2+βnxn)y = f(β0 + β1x1 + β2x2+… βnxn)

符号 β 表示回归系数。当您给它一个其中包含因变量和自变量的已知值的足够大的实验数据集时,logit 模型可以反向计算这些系数值。

除了 sigmoid 还有其他常见的激活函数,以下是 Sigmoid、ReLU、Softmax、Tanh 的多维对比表,包含定义、值域、优缺点、导数等内容:

名称数学表达式值域导数表达式优点缺点
Sigmoidσ(x)=11+ex\sigma(x) = \frac{1}{1 + e^{-x}}(0,1)(0, 1)σ(x)=σ(x)(1σ(x))\sigma'(x) = \sigma(x)(1 - \sigma(x))平滑,有概率解释梯度消失、输出非0均值
ReLUReLU(x)=max(0,x)\text{ReLU}(x) = \max(0, x)[0,+)[0, +\infty)ReLU(x)={1,x>00,x0\text{ReLU}'(x) = \begin{cases} 1, & x > 0 \\ 0, & x \le 0 \end{cases}计算简单,收敛快不可导于0,死神经元问题
Tanhtanh(x)=exexex+ex\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}(1,1)(-1, 1)tanh(x)=1tanh2(x)\tanh'(x) = 1 - \tanh^2(x)平滑,输出均值为0梯度消失
Softmaxsoftmax(xi)=exijexj\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}(0,1)(0,1)=1\sum=1yixj=yi(δijyj)\frac{\partial y_i}{\partial x_j} = y_i(\delta_{ij} - y_j)多分类概率输出,归一化对大值敏感,数值不稳定

对于不同的交叉熵、均方误差和这些激活函数的导数不同,你可以使用复合求导简化这个过程。

手写逻辑回归

import numpy as np
from matplotlib import pyplot as plt

class Sline:
def __init__(self, data):
self.w = 0
self.b = 0
self.learning_rate = 0.1
self.fig, (self.ax1, self.ax2) = plt.subplots(2, 1)
self.loss_list = []


def get_data(self, data):
self.X = np.array(data)[:, 0]
self.y = np.array(data)[:, 1]

def sigmoid(self, x):
return 1 / (1 + np.exp(-(self.w * x + self.b)))

def train(self, epoch_times):
for epoch in range(epoch_times):
total_loss = 0
for x, y in zip(self.X, self.y):
y_pred = self.sigmoid(x)
# w新 = w旧 - 学习率 * 梯度
grad = -2 * (y - y_pred) * (1 - y_pred) * y_pred * x
self.w = self.w - self.learning_rate * grad * x
# b新 = b旧 - 学习率 * 梯度
self.b = self.b - self.learning_rate * grad
loss = (y - y_pred) ** 2
total_loss += loss
epoch_loss = total_loss / len(self.X)
self.loss_list.append(epoch_loss)
if epoch % 10 == 0:
print(f"loss: {epoch_loss}")
self.plot()
plt.ioff()
plt.show()

def plot(self):
plt.ion() # 启用交互模式
self.ax2.clear()
self.ax1.clear()
x = np.linspace(0, 10, 100)
self.ax1.scatter(self.X, self.y, c="g")
self.ax1.plot(x, self.sigmoid(x), c="b")
self.ax2.plot(list(range(len(self.loss_list))), self.loss_list)
plt.show()
plt.pause(0.1)

if __name__ == "__main__":
# 散点输入
data = [(1, 0), (1.8, 0), (2.5, 0), (4.2, 1), (5, 1), (6, 1), (7, 1)]
s = Sline(data)
s.get_data(data)
s.train(1000)

使用sklearn框架

import numpy as np
from sklearn.linear_model import LogisticRegression

# 创建一些示例数据
X = np.array([[1], [2], [3], [4], [5]]) # 自变量
y = np.array([0, 0, 1, 1, 2]) # 因变量,0表示负类,1表示正类

# 创建逻辑回归模型
model = LogisticRegression()

# .fit()方法用于拟合模型,即训练模型x
model.fit(X, y)

# 预测新数据点
new_data_point = np.array([[6]]) # 要预测的新数据点
# .predict()方法预测新数据点的类别
predicted_class = model.predict(new_data_point)
# .predict_proba()方法预测新数据点的概率
predicted_probability = model.predict_proba(new_data_point)

print("预测类别:", predicted_class)
print("预测概率 (负类, 正类):", predicted_probability)
print(type(predicted_probability))
predicted_probability

手写数字分类实战

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

# 加载数据集
digits = datasets.load_digits()
# 获取特征和目标变量
X = digits.data
y = digits.target

# 数据预处理:随机分割训练集和测试集 , 如果不指定 random_state,每次运行结果都不一样。42为约定俗成的随机数种子
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 数据标准化
scaler = StandardScaler()
# .fit_transform()方法先拟合数据,再标准化。和降维算法的语法一致
X_train = scaler.fit_transform(X_train)
# .transform()方法直接使用在测试集上进行标准化操作
X_test = scaler.transform(X_test)

# 创建Logistic Regression模型 , 如果不指定 random_state,每次运行结果都不一样。42为约定俗成的随机数种子
model = LogisticRegression(random_state=42)

# .fit()方法用于拟合模型,即训练模型
model.fit(X_train, y_train)

# .predict()方法预测新数据点的类别
y_pred = model.predict(X_test)

# 效果评估
from sklearn.metrics import accuracy_score

# accuracy_score()方法计算准确率
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy}')

# 可视化数据-数据转图片
from matplotlib import pyplot as plt
# 选出预测错误的样本
index = []
# 遍历所有样本
for i in range(len(y_pred)):
# 判断是否相等
if y_pred[i] != y_test[i]:
# 如果不相等,添加到index中:预测值,真实值,图片(注意要变换形状为8*8)
index.append((
y_pred[i],
y_test[i],
X_test[i].reshape((8, 8))))

# 创建一个正方形画布
# nrows:子图的行数
# ncols:子图的列数
# print(len(index)) // 10
# 因为一共有10张图片,所以行数为2,列数为5,即2*5=10
fig, ax = plt.subplots(
nrows=3,
ncols=5,
)
# 实例化子画布
ax = ax.flatten()
for i in range(len(index)):
p = index[i][0] # 取出预测值
a = index[i][1] # 取出真实值
img = index[i][2] # 取出图片
# 在子画布上画出图片,格式为灰度图
ax[i].imshow(img, cmap='Greys')
ax[i].set_title(f'{p}-{a}')
plt.show()