Skip to main content

回归算法

函数与导数

初等数学的研究对象基本上是不变的量,而高等数学的研究对象是变化的量。高等数学对学习编程具有重要的意义,因为编程涉及到许多与数学密切相关的概念和技能。

本章节内容主要源自《高等数学(第七版)》,仅对语序、相似内容做调整。学习更多高等数学知识请移步其他平台。

本章提到的符号含义如下:

{}\{ \} 表集合、定义域或值域

  • A={1,2,3}A = \{1, 2, 3\} 表示集合 AA 包含元素 1,2,31, 2, 3
  • f(x)={yy=x2,xR}f(x) = \{y | y = x^2, x \in R\} 表示函数 f(x)f(x) 的值域是所有 x2x^2 的值,其中 xx 是实数。

\to 表逻辑关系,

  • aba \to b 表示 aa 推导出 bb.

  • f:XYf:X\to Y 表示 ff 是从 XXYY 的一个映射。

  • 不同场景还可表示:变换、趋向、收敛、极限等。

| 表“使得”或“满足”,Ry={yy0}R_y = \{y | y \geq 0\},表示一个实数集合 RyR_y,其中包含所有满足 y0y \geq 0 条件的实数 yy

\in 表属于,如果 aa 是集合 AA 的元素,记作 aAa \in A.

\subseteq 表子集,如果 A 是集合 B 的子集(A 与 B 可以相等), 记作 ABA \subseteq B.

\subset 表真子集, 如果 A 是集合 B 的真子集(A 与 B 不相等), 记作 ABA \subset B.

函数

映射的概念

定义:设 XXYY 是两个非空集合,

如果存在一个对应关系 ff,使得对于 XX 中的任意一个元素 xx,在 YY 中都有唯一确定的元素 yy 和它对应,

那么就称 ff 为从 XXYY 的一个映射,记作 f:XYf:X\to Y

其中,yy 称为 xx 在映射 ff 下的像,记作 y=f(x)y=f(x)。而 XX 中的元素 xx 称为 yy 的原像。

并称 XXff的定义域,记作 DfD_f

YYff的值域,记作 RfR_f

Rf=f(X)={f(x)xDf}R_f = f(X)=\{f(x) | x\in D_f\}

构成一个映射的条件是:

  1. 集合 XX ,即定义域 Df=XD_f = X
  2. 集合 YY ,即值域 RfYR_f \subseteq Y
  3. 对应法则ff,对于 XX 中的任意一个元素 xx,在 YY 中都有唯一确定的元素 yy 和它对应

注意

对每个 xXx \in Xf(x)f(x) 必须是确定唯一与之对应的

对于 yYy \in Yyy 可以有多个原像。

例如,f(x)=x2f(x)=x^2y=1y=1 的原像可以是 x=1x=1x=1x=-1

值域 RfR_fYY 的子集,即 RfYR_f \subseteq Y,而不一定是 Rf=YR_f = Y

导数

假设有一辆小电动车,装有一根透明的长管。管子的中点代表零,两端分别象征正无穷和负无穷,管内静置着一颗小球。当车辆运动小球也会跟着运动,小球的数值就是导数。

  • 平直道路:当车辆在平坦路面上行驶时,小球始终保持在零点位置。(常数的导数)
  • 爬坡下坡:上坡时,由于斜度影响,小球逐渐向正无穷移动;下坡时则向负无穷滑动。
  • 悬崖处:如果电动车遇到悬崖直接坠落,这代表函数在此处不连续,也就是不可导。
  • 起伏道路:当车辆行驶在起伏不断的路面上时,小球的运动轨迹类似波浪。这种变化与三角函数之间的关系异曲同工:例如,sin 的导数变为 cos。

幂函数求导

幂函数求导的通用法则:

ddxxn=nxn1\frac{d}{dx} x^n = nx^{n-1}

  1. “指数向前”: 将指数 nn 移到变量 xx 的前面作为系数。
  2. “并减一”: 将原指数 nn 减去1,得到新的指数 n1n-1
  • 基本形式:x2x^2 的导数。 将指数2移到前面,并将指数2减去1(2-1=1),所以导数是 2x1=2x2x^1 = 2x
    ddxx2=2x21=2x\frac{d}{dx} x^2 = 2x^{2-1} = 2x

  • 常数乘以幂函数:5x35x^3 的导数。 常数5保持不变,对 x3x^3 使用法则。将指数3移到前面,与常数5相乘,并将指数3减去1。
    ddx5x3=53x31=15x2\frac{d}{dx} 5x^3 = 5 \cdot 3x^{3-1} = 15x^2

  • 分式形式:1x4\frac{1}{x^4} 的导数。
    首先,将分式转化为指数形式:1x4=x4\frac{1}{x^4} = x^{-4}
    然后使用法则,将指数-4移到前面,并将指数-4减去1(41=5-4 - 1 = -5)。
    ddx1x4=ddxx4=4x41=4x5=4x5\frac{d}{dx} \frac{1}{x^4} = \frac{d}{dx} x^{-4} = -4x^{-4-1} = -4x^{-5} = -\frac{4}{x^5}

线性回归

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

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

这条最佳直线就是回归线。这个回归关系可以用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()