2026.09.22 09:49
学习路线
Python -> Python数据分析 -> LangChain -> LangGraph -> FastAPI + SQLAIchemy -> 机器学习 -> 深度学习 -> NLP -> Coze -> Dify -> 大模型应用基础 -> 大模型微调 -> 多模态 -> vibeCoding -> Hermes -> Docker
复习巩固总结
1. 编码六步走
- 加载数据
- 数据的预处理,切分测试集和训练集
- 特征工程:特征提取,特征预处理(标准化/归一化)
- 模型训练(保存模型)
- 模型预测(使用模型)
- 模型评估
2. 有监督学习
2.1 回归
2.1.1 KNN
不常用
from sklearn.neighbors import KNeighborsRegressor # KNN算法的 回归模型
x_train = [[0, 0, 1], [1, 1, 0], [3, 10, 10], [4, 11, 12]] # 训练集的特征数据,因为特征可以有多个特征,所以是一个二维数组
y_train = [0.1, 0.2, 0.3, 0.4] # 训练集的标签数据,因为标签是连续的,所以是一个一维数组
x_test = [[3, 11, 10]]
estimator = KNeighborsRegressor(n_neighbors=3)
estimator.fit(x_train, y_train)
y_pre = estimator.predict(x_test)
print(f"预测值为:{y_pre}")
2.1.2 一元线性回归
2.1.2.1 正规方程法
波士顿房价预测
from sklearn.preprocessing import StandardScaler # 特征处理
from sklearn.model_selection import train_test_split # 数据集划分
from sklearn.linear_model import LinearRegression # 正规方程的回归模型
from sklearn.linear_model import SGDRegressor # 梯度下降的回归模型
from sklearn.metrics import mean_absolute_error, mean_squared_error, root_mean_squared_error # 均方误差评估
from sklearn.linear_model import Ridge, RidgeCV # 岭回归
import pandas as pd # 数据处理
import numpy as np # 数组处理
data_url = "https://raw.githubusercontent.com/Csengupta1101/Datasets/main/BostonHousing.csv"
raw_df = pd.read_csv(data_url, storage_options={"User-Agent": "Mozilla/5.0"})
data = raw_df.drop(columns=["medv"]).values # 前 13 列是特征
target = raw_df["medv"].values # 最后一列房价是标签
x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=23)
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_test = transfer.transform(x_test)
estimator = LinearRegression(fit_intercept=True) # fit_intercept=True:是否需要截距(Bias,偏置),默认是True
estimator.fit(x_train, y_train)
print(f'权重:{estimator.coef_}')
print(f'偏置:{estimator.intercept_}')
y_pre = estimator.predict(x_test)
print(f'均方误差:{mean_squared_error(y_test, y_pre)}') # MSE:均方误差,公式:每个样本的误差平方和 / 样本总数
print(f'均方根误差:{root_mean_squared_error(y_test, y_pre)}') # RMSE:均方根误差,公式:每个样本的误差平方和 / 样本总数,开平方根
print(f'平均绝对误差:{mean_absolute_error(y_test, y_pre)}') # MAE:平均绝对误差,公式:每个样本的误差绝对值和 / 样本总数
2.1.2.2 梯度下降法
波士顿房价预测
from sklearn.preprocessing import StandardScaler # 特征处理
from sklearn.model_selection import train_test_split # 数据集划分
from sklearn.linear_model import LinearRegression # 正规方程的回归模型
from sklearn.linear_model import SGDRegressor # 梯度下降的回归模型
from sklearn.metrics import mean_absolute_error, mean_squared_error, root_mean_squared_error # 均方误差评估
from sklearn.linear_model import Ridge, RidgeCV # 岭回归
import pandas as pd # 数据处理
import numpy as np # 数组处理
data_url = "https://raw.githubusercontent.com/Csengupta1101/Datasets/main/BostonHousing.csv"
raw_df = pd.read_csv(data_url, storage_options={"User-Agent": "Mozilla/5.0"})
data = raw_df.drop(columns=["medv"]).values # 前 13 列是特征
target = raw_df["medv"].values # 最后一列房价是标签
x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=23)
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_test = transfer.transform(x_test)
estimator = SGDRegressor(fit_intercept=True, learning_rate="constant", eta0=0.01)
estimator.fit(x_train, y_train)
print(f'权重:{estimator.coef_}')
print(f'偏置:{estimator.intercept_}')
y_pre = estimator.predict(x_test)
print(f'均方误差:{mean_squared_error(y_test, y_pre)}') # 参1:测试集的真实标签,参2:测试集的预测标签
print(f'均方根误差:{root_mean_squared_error(y_test, y_pre)}') # 参1:测试集的真实标签,参2:测试集的预测标签
print(f'平均绝对误差:{mean_absolute_error(y_test, y_pre)}') # 参1:测试集的真实标签,参2:测试集的预测标签
2.1.3 多元线性回归
波士顿房价预测,使用的是梯度下降法
from sklearn.preprocessing import StandardScaler # 特征处理
from sklearn.model_selection import train_test_split # 数据集划分
from sklearn.linear_model import LinearRegression # 正规方程的回归模型
from sklearn.linear_model import SGDRegressor # 梯度下降的回归模型
from sklearn.metrics import mean_absolute_error, mean_squared_error, root_mean_squared_error # 均方误差评估
from sklearn.linear_model import Ridge, RidgeCV # 岭回归
import pandas as pd # 数据处理
import numpy as np # 数组处理
data_url = "https://raw.githubusercontent.com/Csengupta1101/Datasets/main/BostonHousing.csv"
raw_df = pd.read_csv(data_url, storage_options={"User-Agent": "Mozilla/5.0"})
data = raw_df.drop(columns=["medv"]).values # 前 13 列是特征
target = raw_df["medv"].values # 最后一列房价是标签
x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=23)
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_test = transfer.transform(x_test)
estimator = SGDRegressor(fit_intercept=True, learning_rate="constant", eta0=0.01)
estimator.fit(x_train, y_train)
print(f'权重:{estimator.coef_}')
print(f'偏置:{estimator.intercept_}')
y_pre = estimator.predict(x_test)
print(f'均方误差:{mean_squared_error(y_test, y_pre)}') # 参1:测试集的真实标签,参2:测试集的预测标签
print(f'均方根误差:{root_mean_squared_error(y_test, y_pre)}') # 参1:测试集的真实标签,参2:测试集的预测标签
print(f'平均绝对误差:{mean_absolute_error(y_test, y_pre)}') # 参1:测试集的真实标签,参2:测试集的预测标签
2.2 分类
2.2.1 KNN
不常用
from sklearn.neighbors import KNeighborsClassifier
x_train = [[0], [1], [2], [3]] # 训练集特征数据,因为特征可以有多个特征,所以是一个二维数组
y_train = [0, 0, 1, 1] # 训练集标签数据,因为标签是离散的,所以是一个一维数组
x_test = [[5]] # 测试集特征数据
estimator = KNeighborsClassifier(n_neighbors=2)
estimator.fit(x_train, y_train)
y_pre = estimator.predict(x_test)
print(f"预测值为:{y_pre}")
2.2.2 逻辑回归
逻辑回归 = 有监督学习,有特征、有标签,且标签是离散的(分类)= 适用于二分类。
电信流失用户预测
def dm03_logistic_regression():
churn_df = pd.read_csv(Path(__file__).resolve().parent / 'data' / 'churn.csv')
churn_df = pd.get_dummies(churn_df, columns=['Churn', 'gender'])
churn_df.drop(['Churn_No', 'gender_Male'], axis=1, inplace=True)
churn_df.rename(columns={'Churn_Yes': 'flag'}, inplace=True)
x = churn_df[['Contract_Month', 'internet_other', 'PaymentElectronic']]
y = churn_df['flag'] # False -> 不流失,True -> 流失
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=23)
estimator = LogisticRegression()
estimator.fit(x_train, y_train)
y_pre = estimator.predict(x_test)
print(f"预测值:{y_pre}")
print(f"准确率:{estimator.score(x_test, y_test)}") # 预测前 0.7679205110007097
print(f"准确率:{accuracy_score(y_test, y_pre)}") # 预测后 0.7679205110007097
print(f"精确率:{precision_score(y_test, y_pre)}") # 0.580769230769230
print(f"召回率:{recall_score(y_test, y_pre)}") # 0.4092140921409214
print(f"F1值:{f1_score(y_test, y_pre)}") # 0.48012718600953896
print(f"分类评估报告:\n{classification_report(y_test, y_pre)}")
2.2.3 决策树
2.2.3.1 C4.5树
不常用,没有实现案例,但是可以将 CART 树的示例 改成使用 C4.5 树:
estimator = DecisionTreeClassifier(criterion="entropy", max_depth=10)
但这样改也只是分裂指标换成熵,树还是二叉 CART:没有信息增益率,也不会按 C4.5 那样一次多叉、用特征熵去压「取值多的特征」
2.2.3.2 CART树
泰坦尼克号案例
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
from pathlib import Path
path = Path(__file__).resolve().parent / 'data' / 'train.csv'
data = pd.read_csv(path)
x = data[['Pclass', 'Sex', 'Age']]
y = data['Survived']
x = x.copy() # 拷贝数据,不写也行
x['Age'] = x['Age'].fillna(x['Age'].mean())
x = pd.get_dummies(x, columns=['Sex'])
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=23)
estimator = DecisionTreeClassifier(max_depth=10)
estimator.fit(x_train, y_train)
y_pre = estimator.predict(x_test)
print(f'预测值:{y_pre}')
print(f'分类评估报告:\n{classification_report(y_test, y_pre)}')
plt.figure(figsize=(30, 20)) # 设置图片大小,30 * 100(dpi) * 20 * 100(dpi) = 3000 * 2000像素
plot_tree(estimator, filled=True, max_depth=10)
plt.savefig(Path(__file__).resolve().parent / 'data' / 'my_titanic.png')
plt.show()
2.2.4 朴素贝叶斯
商品评论感情分析
import numpy as np # 数学计算包
import pandas as pd # 数据处理包
import matplotlib.pyplot as plt # 画图包
import jieba # 分词包
from sklearn.feature_extraction.text import CountVectorizer # 词频统计包,把评论内容 转成 词频矩阵.
from sklearn.metrics import accuracy_score
from sklearn.naive_bayes import MultinomialNB # 朴素贝叶斯对象
from pathlib import Path
df = pd.read_csv(Path(__file__).parent / "data" / "书籍评价.csv", encoding="gbk")
df["labels"] = np.where(df["评价"] == "好评", 1, 0)
y = df["labels"]
comment_list = [','.join(jieba.lcut(line)) for line in df['内容'].values]
print(comment_list)
with open(Path(__file__).parent / "data" / "stopwords.txt", "r", encoding="utf-8") as src_f:
stopwords_list = src_f.readlines()
stopwords_list = [line.strip() for line in stopwords_list]
stopwords_list = list(set(stopwords_list))
print(stopwords_list)
transfer = CountVectorizer(stop_words=stopwords_list) # 参数:停用词列表
x = transfer.fit_transform(comment_list).toarray()
x_train = x[:10]
y_train = y[:10]
x_test = x[10:]
y_test = y[10:]
estimator = MultinomialNB() # 创建朴素贝叶斯模型对象
estimator.fit(x_train, y_train) # 模型训练
y_pre = estimator.predict(x_test)
print(f'模型预测结果:{y_pre}')
print(f'模型准确率为:{accuracy_score(y_test, y_pre)}')
3. 无监督学习 - 聚类
根据样本之间的相似性,把样本划到不同类别里。相似度怎么算,团就怎么变。常用的是欧氏距离(不是唯一的,曼哈顿、余弦等也能当尺子)。
目的:在没有先验知识(没有现成标签)的情况下,自动发现数据里的内在结构和模式。所以是无监督学习。
实现方案:KMeans算法
3.1 KMeans
3.1.1 底层实现
举例:15 个点,
| P1 | 7 | 7 | P8 | 9 | 10 | |
| P2 | 2 | 3 | P9 | 10 | 7 | |
| P3 | 6 | 8 | P10 | 5 | 5 | |
| P4 | 1 | 4 | P11 | 7 | 6 | |
| P5 | 1 | 2 | P12 | 9 | 3 | |
| P6 | 3 | 1 | P13 | 2 | 8 | |
| P7 | 8 | 8 | P14 | 5 | 11 | |
| P15 | 5 | 2 |
第一步:事先确定常数 :最终要几簇,几个质心。API 里就是 n_clusters
对于此例,
第二步:随机选 个样本点当初始聚类中心
对于此例,初始中心取 P1、P2。
第三步:每个样本算到 个中心的距离,跟最近的那个中心,算这个簇
例如 P3 到 P1:
| P1 | P2 | 跟谁 | P1 | P2 | 跟谁 | ||
|---|---|---|---|---|---|---|---|
| P3 | 1.41 | 6.40 | P1 | P9 | 3 | 8.94 | P1 |
| P4 | 6.71 | 1.41 | P2 | P10 | 2.83 | 3.61 | P1 |
| P5 | 7.81 | 1.41 | P2 | P11 | 1 | 5.83 | P1 |
| P6 | 7.21 | 2.24 | P2 | P12 | 4.47 | 7.00 | P1 |
| P7 | 1.41 | 7.81 | P1 | P13 | 5.10 | 5.00 | P2 |
| P8 | 3.61 | 9.90 | P1 | P14 | 4.47 | 8.54 | P1 |
| P15 | 5.39 | 3.16 | P2 |
- P1 簇:P1、P3、P7、P8、P9、P10、P11、P12、P14
- P2 簇:P2、P4、P5、P6、P13、P15
第四步:每个簇用样本坐标的均值当新中心:新中心和旧中心一样 → 停;否则回到第 3 步,直到中心不再变
P1 簇 9 个点:,
P2 簇 6 个点:,,均值 。课件写成 ,后面距离表按课件这两个数走,谁跟谁簇不变。
旧中心 、 和新的不一样,还没停。所以用新中心再算一遍距离、再贴标签。
| 跟谁 | 跟谁 | ||||||
|---|---|---|---|---|---|---|---|
| P1 | 0.36 | 5.73 | P8 | 3.24 | 9.00 | ||
| P2 | 6.75 | 1.61 | P9 | 2.82 | 8.54 | ||
| P3 | 1.39 | 5.40 | P10 | 3.18 | 3.22 | ||
| P4 | 7.02 | 1.00 | P11 | 1.32 | 5.39 | ||
| P5 | 8.16 | 2.72 | P12 | 4.66 | 7.38 | ||
| P6 | 7.57 | 3.79 | P13 | 5.25 | 3.41 | ||
| P7 | 1.06 | 7.07 | P14 | 4.30 | 7.16 | ||
| P15 | 5.25 | 3.41 |
簇成员和上一轮一样。中心再按均值更新;若再算还是这两拨人,中心不再挪,收敛。平面上左边一坨(P2 那簇)、右边一坨(P1 那簇)。
3.1.2 代码实现
"""
案例:
演示Kmeans聚类算法入门案例
Kmeans简介:
它属于无监督学习,即:有特征,无标签,根据样本间的相似性进行划分
所谓的相似性可以理解为就是距离,例如:欧氏距离,曼哈顿(城市街区)举例,切比雪夫距离,闵式距离...
一般大厂,项目初期在没有先备知识(标签)的情况下,可能会用
"""
import os
os.environ['OMP_NUM_THREADS'] = '4' # OpenMP多任务程序,这里设置为4个线程,防止出现线程冲突等
# 导包
from sklearn.cluster import KMeans # 聚类的API,采用指定质心来分簇
import matplotlib.pyplot as plt # 绘图的
from sklearn.datasets import make_blobs # 默认会按照高斯分布(正态分布)生成数据集,只需要指定均值,标准差
from sklearn.metrics import calinski_harabasz_score # 评价指标,值越大,聚类效果越好
# 1. 准备数据集
# 参1:样本数量 参2:样本特征数量(2列) 参3:样本标签数量 参4:标准差 参5:随机种子
x, y = make_blobs(n_samples=1000, n_features=2, centers=[[-1, -1], [0, 0], [1, 1], [2, 2]], cluster_std=[0.4, 0.2, 0.3, 0.4], random_state=23)
print(x)
print(y)
# 2. 绘制上述的图形
# 参1:横坐标 参2:纵坐标 参3:颜色
plt.scatter(x[:, 0], x[:, 1])
plt.show()
# 3. 创建Kmeans对象
# 参1:聚类数量 参2:随机种子
estimator = KMeans(n_clusters=4, random_state=23)
# 4. 模型训练和预测
y_pre = estimator.fit_predict(x) # 预测值
# 5. 绘图预测结果
plt.scatter(x[:, 0], x[:, 1], c=y_pre)
plt.show()
# 6. 评价指标
print(f'评价指标:{calinski_harabasz_score(x, y_pre)}') # 越大越好
| 干什么 | |
|---|---|
n_clusters | 开始时的聚类中心个数,也就是准备划几簇、几个质心(centroids)。课件默认 8 |
fit(x) | 只根据 算质心、把训练样本归簇。没有 y |
predict(x) | 已经有质心之后,看每个样本离哪个质心近,吐簇编号 |
fit_predict(x) | 先 fit 再 predict,算中心并给出每个样本属于哪一类 |
3.2 评估
3.2.1 SSE + 肘方法
3.2.1.1 SSE
| 符号 | 是什么 |
|---|---|
| 第 个簇 | |
聚类中心个数,就是 n_clusters | |
| 某个簇里的一个样本 | |
| 第 簇的质心。课件写 ,下标跟簇走 |
三层套娃,从里往外:
- 一个样本的误差平方:。课件口诀:真实值 − 质心 = 误差,再平方。图上就是点到红质心那根线的长度平方
- 一个簇:把该簇所有 的误差平方加起来(公式里层 )
- 整份数据:再对 个簇求和(外层 )= SSE
SSE 越小,点越靠近自己的中心,内聚越高,聚类效果越好。 不是「越小 越好」——下面肘方法会看到, 加大 SSE 几乎总会掉。
3.2.1.2 肘方法:用 SSE 定
对 个点的数据集, 从 1 扫到 ,每次聚完算一次 SSE:
- :全员一个中心,点离中心远,SSE 最大
- 往上加:多几个中心,点能跟更近的那个,SSE 单调往下掉
- :每个点自己当中心,,SSE = 0。
横轴 、纵轴代价(SSE / Cost ),曲线先陡后平。下降率突然变缓的拐点当成最佳 n_clusters——像胳膊肘,所以叫肘。
3.2.1.3 代码实现
"""
案例:
演示聚类算法的评估指标,即:SSE + 肘部法,SC轮廓系数法,CH轮廓系数法
聚类算法的评估指标:
思路1:SSE + 肘部法
SSE:
概述:
所有簇的所有样本到该质心的误差的平方和
特点:
随着K值的增加,SSE值会逐渐减少
目标:
SSE值越小,代表簇内样本越聚集,内聚程度越高
肘部法:
K值增大,SSE值会随之减小,下降梯度陡然变缓的时候,那个K值,就是我们要的最佳值
"""
import os
os.environ['OMP_NUM_THREADS'] = '4' # 设置OMP程序运行时使用的线程数
# 导包
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.metrics import calinski_harabasz_score
# 1. 定义函数,演示:SSE + 肘部法
def dm01_sse():
# 1. 定义sse列表,记录:每个K值的 SSE值
sse_list = []
# 2. 生成数据集
# 参1:样本数量 参2:特征数量 参3:4个簇 参4:4个簇的std标准差 参5:固定随机种子
x, y = make_blobs(
n_samples=1000,
n_features=2,
centers=[[-1, -1], [0, 0], [1, 1], [2, 2]],
cluster_std=[0.4, 0.2, 0.2, 0.2],
random_state=23
)
# 3. for循环遍历,获取到每个K值,计算其对应的SSE值,并添加到sse_list列表中
for k in range(1, 100):
# 3.1 创建KMeans对象,参1:聚的数量,参2:最大迭代次数,参3:固定随机种子
estimator = KMeans(n_clusters=k, max_iter=100, random_state=23)
# 3.2 模型训练
estimator.fit(x)
# 3.3 模型预测
# 3.4 获取到每个簇的SSE值
sse_value = estimator.inertia_
# 3.5 将每个K值对应的sse值,添加到sse_list列表中
sse_list.append(sse_value)
# 4. 绘制sse曲线 -> 数据的可视化
# print(f'sse_list列表:{sse_list}')
# 4.1 创建画布,指定:画布的尺寸
plt.figure(figsize=(20, 10))
# 4.2 设置标题
plt.title('SSE value')
# 4.3 设置x轴的刻度
plt.xticks(range(1, 100, 3))
# 4.4 添加x轴,y轴的标签
plt.xlabel('K')
plt.ylabel('SSE')
# 4.5 绘制网格
plt.grid()
# 4.6 绘制折线图
# 参1:K值 参2:该K值对应的SSE值
plt.plot(range(1, 100), sse_list)
plt.show()
# 2. 定义函数,演示:SC轮廓系数法
# 3. 定义函数,演示:CH轮廓系数法
# 4. 测试
if __name__ == '__main__':
dm01_sse()
3.2 SC
3.2.1 底层实现
对每一个样本 算一个 ,再对所有样本取平均。
| 名字 | 算什么 | 好坏 | |
|---|---|---|---|
| 内聚 Cohesion | 到同簇其他点的平均距离 | 越小越好:簇内越像 | |
| 分离 Separation | 到最近另一簇里所有点的平均距离(各簇先各自平均,再取最小那个) | 越大越好:越不像别的簇、耦合越低 |
分母取 是为了把结果压进 :
- (离别的簇更远):,贴 1
- (其实更该跟邻居簇):,往 −1 掉
- :,卡在边界上
| 靠近 | 含义 |
|---|---|
| ,自己这簇挤、离别人远,分对了 | |
| ,两簇边上,可去可留 | |
| ,跟错簇了 |
整份数据的 SC = 所有样本 的平均。越大越好。 和 SSE「越小越好」方向相反;和肘方法搭配时,常扫一圈 ,看哪一个 的平均轮廓系数最高。
3.2.2 代码实现
# 2. 定义函数,演示:SC轮廓系数法
def dm02_sc():
# 1. 定义sse列表,记录:每个K值的 SSE值
sc_list = []
# 2. 生成数据集
# 参1:样本数量 参2:特征数量 参3:4个簇 参4:4个簇的std标准差 参5:固定随机种子
x, y = make_blobs(
n_samples=1000,
n_features=2,
centers=[[-1, -1], [0, 0], [1, 1], [2, 2]],
cluster_std=[0.4, 0.2, 0.2, 0.2],
random_state=23
)
# 3. for循环遍历,获取到每个K值,计算其对应的SSE值,并添加到sse_list列表中
# for k in range(1, 100):
for k in range(2, 100): # 考虑簇外,至少2个簇
# 3.1 创建KMeans对象,参1:聚的数量,参2:最大迭代次数,参3:固定随机种子
estimator = KMeans(n_clusters=k, max_iter=100, random_state=23)
# 3.2 模型训练
estimator.fit(x)
# 3.3 模型预测
y_pre = estimator.predict(x)
# 3.4 获取到每个簇的SSE值
# sc_value = estimator.inertia_
sc_value = silhouette_score(x, y_pre)
# 3.5 将每个K值对应的sse值,添加到sse_list列表中
sc_list.append(sc_value)
# 4. 绘制sse曲线 -> 数据的可视化
# print(f'sc_list列表:{sc_list}')
# 4.1 创建画布,指定:画布的尺寸
plt.figure(figsize=(20, 10))
# 4.2 设置标题
plt.title('SC value')
# 4.3 设置x轴的刻度
plt.xticks(range(1, 100, 3))
# 4.4 添加x轴,y轴的标签
plt.xlabel('K')
plt.ylabel('SC')
# 4.5 绘制网格
plt.grid()
# 4.6 绘制折线图
# 参1:K值 参2:该K值对应的SSE值
# plt.plot(range(1, 100), sc_list)
plt.plot(range(2, 100), sc_list)
plt.show()
3.3 CH
3.3.1 底层实现
口诀:类内距离平方和越小越好,类间距离平方和越大越好,种类数少点更好。
| 符号 | 是什么 |
|---|---|
| 第 个样本 | |
| 所在簇的质心 | |
| 第 簇的质心 | |
| 全体样本均值(按簇大小加权后,等于各质心的加权平均) | |
| 第 簇的样本数 | |
| 总样本数 | |
质心个数,n_clusters |
SSW(簇内):每个样本到自己质心的距离平方再加总,就是 99.1 的 SSE。越小,内聚越高。
SSB(簇间):每个质心到全局均值的距离平方,再按该簇人数 加权后加总。越大,簇和簇离得越开、耦合越低。
前半截 :分子大、分母小,比值越大越好。后半截 是在罚 :簇越多这项越小。不然 一直加,SSW 跟着 SSE 往下掉,光看比值会偏向切得很碎。种类少也能把 CH 抬上去,所以「种类数少点更好」写进公式里了。
CH 越大越好。 时分母 ,算不了,至少两簇。
3.3.2 代码实现
# 3. 定义函数,演示:CH轮廓系数法
def dm03_ch():
# 1. 定义sse列表,记录:每个K值的 SSE值
ch_list = []
# 2. 生成数据集
# 参1:样本数量 参2:特征数量 参3:4个簇 参4:4个簇的std标准差 参5:固定随机种子
x, y = make_blobs(
n_samples=1000,
n_features=2,
centers=[[-1, -1], [0, 0], [1, 1], [2, 2]],
cluster_std=[0.4, 0.2, 0.2, 0.2],
random_state=23
)
# 3. for循环遍历,获取到每个K值,计算其对应的SSE值,并添加到sse_list列表中
for k in range(2, 100): # 考虑簇外,至少2个簇
# 3.1 创建KMeans对象,参1:聚的数量,参2:最大迭代次数,参3:固定随机种子
estimator = KMeans(n_clusters=k, max_iter=100, random_state=23)
# 3.2 模型训练
estimator.fit(x)
# 3.3 模型预测
y_pre = estimator.predict(x)
# 3.4 获取到每个簇的SSE值
# sc_value = silhouette_score(x, y_pre)
ch_value = calinski_harabasz_score(x, y_pre)
# 3.5 将每个K值对应的sse值,添加到sse_list列表中
ch_list.append(ch_value)
# 4. 绘制sse曲线 -> 数据的可视化
# print(f'ch_list列表:{ch_list}')
# 4.1 创建画布,指定:画布的尺寸
plt.figure(figsize=(20, 10))
# 4.2 设置标题
plt.title('CH value')
# 4.3 设置x轴的刻度
plt.xticks(range(1, 100, 3))
# 4.4 添加x轴,y轴的标签
plt.xlabel('K')
plt.ylabel('CH')
# 4.5 绘制网格
plt.grid()
# 4.6 绘制折线图
# 参1:K值 参2:该K值对应的SSE值
plt.plot(range(2, 100), ch_list)
plt.show()
3.4 三种评估方法对比
SSE 只盯点到质心;SC 的 是点到同簇其他人, 把簇间也算进去;CH 三件事一起看(类内平方和小、类间平方和大、种类少点更好),距离走质心 / 全局均值,扫 比 SC 快。
| SSE | SC | CH | |
|---|---|---|---|
| 看什么 | 只簇内(内聚) | 簇内 + 簇间 | 簇内 SSW + 簇间 SSB + 罚 |
| 距离相对谁 | 样本 → 自己的质心 | 样本 → 其他样本(同簇平均 / 最近邻簇平均) | 样本 → 质心(SSW=SSE);质心 → 全局均值(SSB) |
| 好坏 | 越小越好 | 越大越好, | 越大越好 |
| 能算,通常最大 | 不能算(没有「其他簇」) | 不能算(分母 ) | |
| (一人一簇) | SSE,看起来最「好」 | 没定义,不会漂亮地掉到 0 | 罚项 把碎切压下去 |
| 怎么定 | 不能看绝对值最小,看肘拐点 | 扫一圈 ,取平均轮廓系数峰值 | 扫一圈 ,取 CH 峰值;算得快 |
| sklearn | inertia_ | silhouette_score | calinski_harabasz_score |
SSE 必须配肘方法,否则 越大越好是假象。要同时量化「挤不挤、离不离」,用 SC 或 CH;样本多、只为找 ,CH 更省。
3.3 用户分群实现案例
"""
案例:
基于用户的年收入和消费指数,根据用户的相似性进行聚类
"""
import os
os.environ['OMP_NUM_THREADS'] = '4'
# 导包
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.metrics import silhouette_score, calinski_harabasz_score
import pandas as pd
import numpy as np
from pathlib import Path
# 1. 定义函数,找聚类的质心数(K值)
def dm01_find_k():
# 1. 加载数据集
data_path = Path(__file__).parent / 'data' / 'customers.csv'
df = pd.read_csv(data_path)
# df.info()
# print(df.head())
# 2. 定义ssc_list,sc_list,记录:不同K值的 评估效果
sse_list = [] # sse:只考虑簇内,越小越好
sc_list = [] # sc:考虑簇内和簇外,越大越好
# 抽取特征
x = df.iloc[:, 3:5]
print(x)
# 3. 定义for训练,测试不同K值的评估效果
for k in range(2, 20):
# 3.1. 创建(KMeans模型)对象
estimator = KMeans(n_clusters=k, max_iter=100, random_state=23)
# 3.2. 模型训练
estimator.fit(x)
# 3.3. 模型预测
y_pre = estimator.predict(x)
# 3.4. 分别把评分添加到对应的列表中
sse_list.append(estimator.inertia_)
sc_list.append(silhouette_score(x, y_pre))
# 4. 绘制折线图,看看K值 哪个最好
plt.figure(figsize=(20, 10))
# print(sse_list)
# print(sc_list)
plt.plot(range(2, 20), sse_list, label='SSE')
plt.show()
plt.figure(figsize=(20, 10))
plt.plot(range(2, 20), sc_list, label='SC')
plt.show()
# 结论:K=5的时候,效果最好
# 2. 定义函数,实现:模型训练,模型预测,模型评估
def dm02_train_predict_evaluate():
# 1. 加载数据集
df = pd.read_csv(Path(__file__).parent / 'data' / 'customers.csv')
# 2. 提取特征
x = df.iloc[:, 3:5]
# print(x.head())
# 3. 模型训练 k=5 是刚才通过 SSE + 肘部法,SC轮廓系数 获取出来的
estimator = KMeans(n_clusters=5, max_iter=100, random_state=23)
estimator.fit(x)
# 4. 模型预测
y_pre = estimator.predict(x)
# print(y_pre) # [2, 3, 2, 3, 2, 0, 0, 1, 4, 4, 0, ...]
# 5. 绘制5个簇的样本点 -> 散点图
# print(x.values) # [[15 39], [15 81], [16 6], [16 77], [17 40], [17 76], ...]
plt.scatter(x.values[y_pre == 0, 0], x.values[y_pre == 0, 1]) # 0号簇
plt.scatter(x.values[y_pre == 1, 0], x.values[y_pre == 1, 1]) # 1号簇
plt.scatter(x.values[y_pre == 2, 0], x.values[y_pre == 2, 1]) # 2号簇
plt.scatter(x.values[y_pre == 3, 0], x.values[y_pre == 3, 1]) # 3号簇
plt.scatter(x.values[y_pre == 4, 0], x.values[y_pre == 4, 1]) # 4号簇
# 6. 绘制5个簇的质心 -> 散点图
# print(estimator.cluster_centers_)
plt.scatter(estimator.cluster_centers_[:, 0], estimator.cluster_centers_[:, 1])
# 7. 设置标题,x轴,y轴标签
plt.title('Clusters of customers')
plt.xlabel('Annual Income (k$)')
plt.ylabel('Spending Score (1-100)')
plt.legend()
plt.show()
# 3. 测试
if __name__ == '__main__':
# dm01_find_k()
dm02_train_predict_evaluate()
# 代码 x.values[y_pre == 0, 0] 解释
x = [[15, 39], [15, 81], [16, 6], [16, 77], [17, 40], [17, 76], [18, 94], [18, 35], [19, 79], [19, 33]]
# print(x[True, False, True, True, False, False])
x2 = np.array(x) # 模拟:x.values
# print(type(x2)) # <class 'numpy.ndarray'>
# 模拟:x.values[y_pre == 0]
# 细节:随便写,个数要一致,即:True是要,False是不要
result = x2[[True, False, True, True, False, False, True, False, True, True]]
# print(result) # [[15 39], [16 6], [16 77], [18 94], [19 79]]
# 模拟:x.values[y_pre == 0, 0]
result2 = x2[[True, False, True, True, False, False, True, False, True, True], 0]
# print(result2) # [15 16 16 18 19]
# 模拟:x.values[y_pre == 0, 1]
result3 = x2[[True, False, True, True, False, False, True, False, True, True], 1]
# print(result3) # [39 6 77 94 79]
4. 集成学习
4.1 Bagging - 随机森林
import pandas as pd
from sklearn.model_selection import train_test_split # 切分训练集和测试集
from sklearn.tree import DecisionTreeClassifier # 决策树
from sklearn.ensemble import RandomForestClassifier # 随机森林算法(分类器)
from sklearn.model_selection import GridSearchCV # 网格搜索
from pathlib import Path
path = Path(__file__).resolve().parent / 'data' / 'train.csv'
df = pd.read_csv(path) # 加载数据
x = df[['Pclass', 'Sex', 'Age']].copy() # 船舱等级,性别,年龄
y = df['Survived']
x['Age'] = x['Age'].fillna(x['Age'].mean())
x = pd.get_dummies(x, columns=['Sex'])
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=23)
estimator3 = RandomForestClassifier()
params = {'n_estimators': [60, 90, 100, 130, 150], 'max_depth': [3, 5, 7, 9]}
gs_estimator = GridSearchCV(estimator3, param_grid=params, cv=3)
gs_estimator.fit(x_train, y_train)
y_pre3 = gs_estimator.predict(x_test)
print(f'网格搜索模型的准确率为:{gs_estimator.score(x_test, y_test)}') # 0.7988826815642458
print(f'最佳参数:{gs_estimator.best_params_}') # {'max_depth': 5, 'n_estimators': 100}
4.2 Boosting
4.2.1 AdaBoost自适应提升树
红酒品质分类案例
import pandas as pd
from sklearn.preprocessing import LabelEncoder # 标签编码器
from sklearn.model_selection import train_test_split # 训练集、测试集分割
from sklearn.tree import DecisionTreeClassifier # 决策树模型
from sklearn.ensemble import AdaBoostClassifier # AdaBoost分类器 -> 集成学习Boosting思想
from sklearn.metrics import accuracy_score # 模型评估 -> 正确率
from pathlib import Path
path = Path(__file__).resolve().parent / 'data' / 'wine0501.csv'
df_wine = pd.read_csv(path)
df_wine = df_wine[df_wine['Class label'] != 1]
x = df_wine[['Alcohol', 'Hue']] # 酒精 和 色泽
y = df_wine['Class label'] # 标签类
le = LabelEncoder()
y = le.fit_transform(y)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=23, stratify=y)
estimator2 = AdaBoostClassifier(estimator=estimator1, n_estimators=200, learning_rate=0.1)
estimator2.fit(x_train, y_train)
y_pred2 = estimator2.predict(x_test)
print(f'AdaBoost模型预测结果:{y_pred2}')
print(f'AdaBoost模型准确率:{accuracy_score(y_test, y_pred2)}') # 0.9583333333333334
4.2.2 GBDT
泰坦尼克号案例
import pandas as pd
from sklearn.model_selection import train_test_split # 切割训练集和测试集
from sklearn.tree import DecisionTreeClassifier # 决策树分类器
from sklearn.ensemble import GradientBoostingClassifier # 梯度提升树分类器
from sklearn.metrics import accuracy_score, classification_report # 模型评估
from sklearn.model_selection import GridSearchCV # 网格搜索
from pathlib import Path
path = Path(__file__).resolve().parent / 'data' / 'train.csv'
df = pd.read_csv(path)
x = df[['Pclass', 'Sex', 'Age']].copy()
y = df['Survived']
x['Age'] = x['Age'].fillna(x['Age'].mean())
x = pd.get_dummies(x)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=23)
param_dict = {
'n_estimators': [50, 60, 70, 80, 90, 100, 110], # 弱学习器数量
'learning_rate': [0.3, 0.5, 0.6, 0.7], # 学习率
'max_depth': [3, 5, 6, 7, 8, 9] # 树最大深度
}
estimator3 = GradientBoostingClassifier()
estimator4 = GridSearchCV(estimator3, param_dict, cv=5)
estimator4.fit(x_train, y_train)
print(f'网格搜索后的模型准确率为:{estimator4.best_score_}') # 0.8005220131980696
print(f'网格搜索后的模型最佳参数为:{estimator4.best_params_}') # {'learning_rate': 0.3, 'max_depth': 3, 'n_estimators': 50}
4.2.3 XGBoost
红酒品质分类案例
import joblib # 保存和加载模型
import numpy as np # 用于数值计算
import pandas as pd # 用于数据处理
from sklearn.utils import class_weight # 计算样本权重
import xgboost as xgb # 极限提升树对象
from collections import Counter # 统计数据
from sklearn.model_selection import GridSearchCV, train_test_split # 训练集和测试集的划分
from sklearn.metrics import accuracy_score, classification_report # 模型(分类)评估报告
from sklearn.model_selection import StratifiedKFold # 分层K折交叉验证,类似于 网格搜索时 cv=折数
from pathlib import Path
def dm01_data_split():
data_path = Path(__file__).parent / 'data' / '红酒品质分类.csv'
df = pd.read_csv(data_path)
x = df.iloc[:, :-1]
y = df.iloc[:, -1] - 3 # 最后1列是标签,默认范围是:[3, 8] -> [0, 5]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=23, stratify=y)
pd.concat([x_train, y_train], axis=1).to_csv(Path(__file__).parent / 'data' / '红酒品质分类_train.csv', index=False) # 忽略索引
pd.concat([x_test, y_test], axis=1).to_csv(Path(__file__).parent / 'data' / '红酒品质分类_test.csv', index=False)
def dm02_model_train():
train_path = Path(__file__).parent / 'data' / '红酒品质分类_train.csv'
test_path = Path(__file__).parent / 'data' / '红酒品质分类_test.csv'
train_data = pd.read_csv(train_path)
test_data = pd.read_csv(test_path)
x_train = train_data.iloc[:, :-1] # 除了最后1列,都是特征
y_train = train_data.iloc[:, -1] # 最后1列是标签
x_test = test_data.iloc[:, :-1]
y_test = test_data.iloc[:, -1]
estimator = xgb.XGBClassifier(
max_depth=5, # 树的最大深度
n_estimators=100, # 树的数量
learning_rate=0.1, # 学习率
random_state=23, # 随机种子
objective='multi:softmax', # 多分类问题,使用多分类模型
)
cw = class_weight.compute_sample_weight('balanced', y_train)
estimator.fit(x_train, y_train, sample_weight=cw)
print(f'准确率:{estimator.score(x_test, y_test)}')
joblib.dump(estimator, Path(__file__).parent / 'model' / '红酒品质分类.pkl') # 后缀名也可以写 .pth,都是pickle文件格式
print('模型保存成功!')
def dm03_use_model():
train_path = Path(__file__).parent / 'data' / '红酒品质分类_train.csv'
test_path = Path(__file__).parent / 'data' / '红酒品质分类_test.csv'
train_data = pd.read_csv(train_path)
test_data = pd.read_csv(test_path)
x_train = train_data.iloc[:, :-1] # 除了最后1列,都是特征
y_train = train_data.iloc[:, -1] # 最后1列是标签
x_test = test_data.iloc[:, :-1]
y_test = test_data.iloc[:, -1]
estimator = joblib.load(Path(__file__).parent / 'model' / '红酒品质分类.pkl')
param_dict = {
'max_depth': [2, 3, 5, 6, 7],
'n_estimators': [30, 50, 100, 150],
'learning_rate': [0.2, 0.3, 1],
}
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=23)
gs_estimator = GridSearchCV(estimator, param_dict, cv=skf)
gs_estimator.fit(x_train, y_train)
y_pre = gs_estimator.predict(x_test)
print(f'预测结果:{y_pre}')
print(f'最优参数组合:{gs_estimator.best_params_}') # {'learning_rate': 0.3, 'max_depth': 3, 'n_estimators': 150}
print(f'最优估计器对象:{gs_estimator.best_estimator_}')
print(f'最优得分:{gs_estimator.best_score_}') # 0.6747579656862746
print(f'准确率:{accuracy_score(y_test, y_pre)}')
if __name__ == '__main__':
# dm01_data_split()
# dm02_model_train()
dm03_use_model()
5. 解决过拟合问题
5.1 L1正则化 - Lasso
def dm04_l1_regularization():
np.random.seed(23)
x = np.random.uniform(-3, 3, size=100)
y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
X = x.reshape(-1, 1)
X3 = np.hstack([X, X ** 2, X ** 3, X ** 4, X ** 5, X ** 6, X ** 7, X ** 8, X ** 9])
estimator = Lasso(alpha=0.1) # alpha:正则化系数(惩罚系数),默认是1
estimator.fit(X3, y)
y_pre = estimator.predict(X3)
print(f'均方误差:{mean_squared_error(y, y_pre)}') # 参1:真实值,参2:预测值
plt.scatter(x, y) # 以散点图的形式绘制 真实值
plt.plot(np.sort(x), y_pre[np.argsort(x)], color='red')
plt.show()
5.2 L2正则化 - Ridge
def dm05_l2_regularization():
np.random.seed(23)
x = np.random.uniform(-3, 3, size=100)
y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
X = x.reshape(-1, 1)
X3 = np.hstack([X, X ** 2, X ** 3, X ** 4, X ** 5, X ** 6, X ** 7, X ** 8, X ** 9])
estimator = Ridge(alpha=10)
estimator.fit(X3, y)
y_pre = estimator.predict(X3)
print(f'均方误差:{mean_squared_error(y, y_pre)}') # 参1:真实值,参2:预测值
plt.scatter(x, y) # 以散点图的形式绘制 真实值
plt.plot(np.sort(x), y_pre[np.argsort(x)], color='red')
plt.show()
5.3 剪枝
L1 / L2 管线性模型的 (w);树过拟合是问得太细、把噪声也学进去。2.2.3 长完再砍,或长的时候就卡住。剪枝 = 把某棵子树收成一片叶子(分类用多数类,回归用均值),让树简单一点、泛化更好。
| 预剪枝 | 后剪枝 | |
|---|---|---|
| 何时 | 还在长,切之前先问要不要这一刀 | 先用训练集长成完整树,再自底向上收 |
| 判据 | 这一刀验证集精度不升就停,当前节点当叶子 | 这个非叶收成叶子后精度升了就剪 |
| 代价 | 快,很多枝根本不长 | 慢,整棵都要先长出来 |
| 风险 | 可能切太早,欠拟合 | 欠拟合风险小,通常留更多枝、泛化更好 |
sklearn 不是「每个节点拿验证集比精度」,后剪枝走 CART 的代价复杂度剪枝(CCP):
(\alpha) 越大砍得越狠。交叉验证选 (\alpha),就接近「验证集决定剪不剪」。XGBoost 的 (\gamma T) 是同一句话,不过税在选切点时就交,不只事后砍。
| sklearn | |
|---|---|
| 预剪枝 | max_depth、min_samples_split、min_samples_leaf、min_impurity_decrease |
| 后剪枝 | ccp_alpha;候选用 cost_complexity_pruning_path,再 GridSearchCV |
泰坦尼克同一份数据(2.2.3.2 那套):
| 深度 | 叶子 | 训练 | 测试 | |
|---|---|---|---|---|
| 不剪 | 21 | 125 | 0.883 | 0.804 |
预剪枝 max_depth=3 | 3 | 8 | 0.816 | 0.793 |
后剪枝 ccp_alpha≈0.0024 | 9 | 16 | 0.834 | 0.799 |
6. 时序预测问题
据历史时间序列,去估后面还没发生的值。任务听着简单(过去 → 未来),做起来难:趋势会拐、噪声大、人还会改主意。
金融、交通、能源。都是「过去这条曲线,后面往哪拐」。
| 领域 | 例子 |
|---|---|
| 金融 | 股价、汇率、利率;企业资金流、机构流动性;GDP、CPI |
| 交通 | 流量、行驶速度、交通运行指数 |
| 能源 | 见下表 |
能源里再拆发电侧和用电侧。火电出力相对稳,时序更常拿来对付会跟着天气抖的那部分:
| 预测什么 | |
|---|---|
| 发电 | 风电功率、光伏功率(风光随风、随日照变) |
| 用电 / 市场 | 工业和居民能源消费;电力负荷;电力市场价格、能源商品交易量 |
负荷预测问的是:这一小时、这一天,全社会要多少电。发少了停电,发多了浪费。后面案例会拿负荷这类序列上手。
6.1 时序数据分类
| 单步:只估未来 1 个时刻 | 多步:一次估未来 多个 时刻 | |
|---|---|---|
| 单变量:历史上就这一条序列 | 用这一条的过去,估它自己的下一步 | 用这一条的过去,估它后面一串 |
| 多变量:历史上有多条序列 | 用这几条的过去,估目标的下一步 | 用这几条的过去,估目标后面一串 |
| 任务 | 变量 | 步数 | 做法 |
|---|---|---|---|
| 单变量单步 | 1 | 1 | 直接建一个模型,一个特征 |
| 单变量多步 | 1 | 多个 | 方案 1:建一个模型,滚动——把刚测出来的值再塞回特征,去估再下一步 方案 2:不同时间步各建一个模型(专门估 、专门估 …) |
| 多变量单步 | 多个 | 1 | 直接建一个模型,多个特征 |
| 多变量多步 | 多个 | 多个 | 方案 1:多特征模型 + 滚动(预测值再当特征) 方案 2:不同时间步分别建模(每个模型都吃多个特征) 方案 3:直接建模,一次吐出整段未来(深度学习里的序列到序列) |
6.2 算法介绍
| 路 | 例子 | 思路 |
|---|---|---|
| 统计 | 简单平均、指数平滑、AR、MA、ARIMA | 简单平均最朴素:历史一平均,当未来的预测 |
| 深度学习 | RNN 一族、iTransformer | 自己吃序列;多步还可以序列到序列,对应 105.1 方案 3 |
| 传统机器学习 | 线性回归、决策树、随机森林、XGBoost、LightGBM、SVM……凡是回归模型都行 | 不直接吞一条线,要先把时序拆成二维表 |
传统机器学习和深度学习,落地时都常做同一件事:时序 → 一张二维结构化表,再拿回归模型去拟合。每一行是一个预测时刻,每一列是特征,最后一列是那天/那小时的负荷。XGBoost 吃的就是这种表,不是原始的 (time, value) 长条。
原始往往两张长表:时间和负荷;时间和天气(温度、湿度、风速)。拼成一行样本,时间本身要拆开,负荷的历史要做成特征。
时间能拆成什么(日历特征):
- 早上 / 中午 / 晚上
- 白天 / 夜里
- 季节、第几度(春夏秋冬、第几季度)
- 工作日 / 周末
- 年月日、时分秒里还能再挖节假日等
负荷能做成什么(滞后 / 窗口):
| 含义 | 例子 | |
|---|---|---|
| 近期 | 设一个窗口,用刚过去的一段估下一段 | 过去 1 小时 → 未来 1 小时用电量 |
| 远期 / 周期 | 同一个钟点在更早周期的值 | 昨天、上一周、上个月、上年同期的负荷 |
6.3 电力负荷预测案例
电力负荷预测/
├── data/ 原始负荷 csv、做成的训练表
├── log/ 跑起来写下的日志文件
├── model/ 训好的 XGBoost(joblib / json 那种)
├── src/ 正经业务:读数、特征、训练、预测
└── utils/ 自己写的工具
- utils/log.py
# -*- coding: utf-8 -*-
import logging
from pathlib import Path
# from datetime import datetime
import datetime
import pandas as pd
class Logger(object):
# 日志级别关系映射
level_relations = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'crit': logging.CRITICAL
}
def __init__(self, root_path, log_name, level='info', fmt='%(asctime)s - %(levelname)s: %(message)s'):
# 相对路径按本文件所在目录解析,不跟进程 cwd 走
root = Path(root_path)
if not root.is_absolute():
root = (Path(__file__).resolve().parent / root).resolve()
self.root_path = root
# 初始logger名称和格式
self.log_name = log_name
# 初始格式
self.fmt = fmt
# 先声明一个 Logger 对象
self.logger = logging.getLogger(log_name)
# 设置日志级别
self.logger.setLevel(self.level_relations.get(level))
def get_logger(self):
# 指定对应的 Handler 为 FileHandler 对象, 这个可适用于多线程情况
path = self.root_path / 'log'
path.mkdir(parents=True, exist_ok=True)
file_name = path / f'{self.log_name}.log'
rotate_handler = logging.FileHandler(file_name, encoding="utf-8", mode="a")
# Handler 对象 rotate_handler 的输出格式
formatter = logging.Formatter(self.fmt)
rotate_handler.setFormatter(formatter)
# 将rotate_handler添加到Logger
self.logger.addHandler(rotate_handler)
return self.logger
if __name__ == '__main__':
# 1. 创建日志对象
# 参1:日志文件的父目录 参2:日志文件名
logger = Logger('../', 'hg_test').get_logger()
# 2. 往日志文件中写数据
logger.info('我是普通的日志信息') # 这个会被写到 hg.test 日志文件中
logger.error('我是错误的日志信息') # 日志默认是追加,不是覆盖
# 3. 演示下错误日志,写入日志
try:
logger.info('开始计算了...')
print(10 / 0)
except Exception as e:
logger.error(f'计算出错了,原因是: {e}')
else:
logger.info('计算成功!')
finally:
logger.info('计算结束!')
# 4. 生成 train_年月日.log 日志文件
print(datetime.datetime.now()) # 获取当前时间
print(pd.to_datetime(datetime.datetime.now()).strftime("%Y%m%d%H%M%S"))
new_name = 'train_' + pd.to_datetime(datetime.datetime.now()).strftime("%Y%m%d%H%M%S") + '.log'
print(new_name)
- utils/common.py
# -*- coding: utf-8 -*-
# 导包
import pandas as pd
import numpy as np
from pathlib import Path
# 该工具类的目的是:对数据做预处理 -> 时间格式化,按照时间升序排列,且对数据去重
# 数据集在 data 目录下的 train.csv 文件中 -> 拆分训练集 和 测试集
# 测试集在 data 目录下的 test.csv 文件中 -> 模拟项目上线后,真实的测试集
# 定义函数 data_preprocessing(),对数据做预处理操作
def data_preprocessing(path):
"""
1.获取数据源
2.时间格式化,转为2024-12-20 09:00:00这种格式
3.按时间升序排序
4.去重
:param path:
:return:
"""
# 1. 加载数据集
# data = pd.read_csv(Path(__file__).resolve().parent / 'data' / 'train.csv')
# data.info()
data = pd.read_csv(path)
# 2. 时间格式化,转为: '%Y-%m-%d %H:%M:%S'
# data['time'] = pd.to_datetime(data['time'], format='%Y-%m-%d %H:%M:%S')
data['time'] = pd.to_datetime(data['time']).dt.strftime('%Y-%m-%d %H:%M:%S')
# 3. 按时间升序排序
data.sort_values('time', ascending=True, inplace=True)
# 4. 去重
data.drop_duplicates(inplace=True)
# 5. 打印和返回
# print(data)
return data
- src/train.py
# -*- coding: utf-8 -*-
# 导包
import sys
from pathlib import Path
import pandas as pd
import matplotlib.pyplot as plt
import datetime
# 无论从哪一层目录运行,都能找到项目根下的 utils
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from utils.log import Logger
from utils.common import data_preprocessing
from xgboost import XGBRegressor
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import mean_squared_error, mean_absolute_error
import joblib
plt.rcParams['font.family'] = 'SimHei'
plt.rcParams['font.size'] = 15
# 1. 定义电力负荷模型类,配置日志,获取数据源
class PowerLoadModel(object):
# 1.1 初始化属性信息
def __init__(self, filename):
# 1.1.1 拼接日志文件名
logfile_name = "train_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S')
# 1.1.2 创建日志对象
self.logfile = Logger('../', logfile_name).get_logger()
# 测试写一条日志
self.logfile.info('开始创建 电力负荷模型类的 对象了')
# 1.1.3 获取数据源
self.data_source = data_preprocessing(filename)
# 4. 测试
if __name__ == '__main__':
# 4.1 创建电力负荷模型类的对象
input_file = PROJECT_ROOT / 'data' / 'train.csv'
model = PowerLoadModel(input_file)
# 4.2 打印数据源
print(model.data_source)
机器学习
112. 电力负荷预测案例_查看数据整体和各小时负荷分布
# 2. 查看数据源的整体分布情况
def ana_data(data): # analysis: 分析
"""
1.查看数据整体情况
2.负荷整体的分布情况
3.各个小时的平均负荷趋势,看一下负荷在一天中的变化情况
4.各个月份的平均负荷趋势,看一下负荷在一年中的变化情况
5.工作日与周末的平均负荷情况,看一下工作日的负荷与周末的负荷是否有区别
:param data: 数据源
:return:
"""
# 2.1 为了防止会修改源数据,我们做一次拷贝
data = data.copy(deep=True)
# 2.2 查看数据整体情况
data.info()
print(data.head())
# 2.3 负荷整体的分布情况,直方图
# 2.3.1 创建画布
fig = plt.figure(figsize=(20, 40))
# fig = plt.figure(figsize=(40, 80))
# 2.3.2 添加子图
ax1 = fig.add_subplot(411)
ax1.hist(data['power_load'], bins=100) # 负荷,直方图,100个区间
ax1.set_title('负荷整体分布情况直方图')
ax1.set_xlabel('负荷')
# plt.show()
# 2.4 各个小时的平均负荷趋势,看一下负荷在一天中的变化情况
# 2.4.1 新增1列,充当小时
data['hour'] = data['time'].str[11:13]
# print(data.head())
# 2.4.2 根据小时分组计算平均值
data_hour_avg = data.groupby(by='hour', as_index=False)['power_load'].mean()
# print(data_hour_avg) # [列1 hour, 列2 power_load 当前小时的平均负荷]
# 2.4.3 画出折线图
ax2 = fig.add_subplot(412)
ax2.plot(data_hour_avg['hour'], data_hour_avg['power_load'], color='b', linewidth=2)
ax2.set_title('各小时的平均负荷趋势图')
ax2.set_xlabel('小时')
ax2.set_ylabel('负荷')
fig_dir = PROJECT_ROOT / 'data' / 'fig'
fig_dir.mkdir(parents=True, exist_ok=True)
plt.savefig(fig_dir / '负荷整体的分布情况分析图.png')
plt.show()
# 5. 测试
if __name__ == '__main__':
# ...
# 4.3 查看数据分布
ana_data(model.data_source)
113. 电力负荷预测案例_按照月份_假日可视化数据
# 2. 查看数据源的整体分布情况
def ana_data(data): # analysis: 分析
# ...
# 2.5 各个月份的平均负荷趋势,看一下负荷在一年中的变化情况
data['month'] = data['time'].str[5:7]
# print(data.head())
data_month_avg = data.groupby('month', as_index=False)['power_load'].mean()
ax3 = fig.add_subplot(413)
ax3.plot(data_month_avg['month'], data_month_avg['power_load'], color='r', linewidth=2) # x轴:月份,y轴:平均负荷
ax3.set_title('各个月份的平均负荷趋势')
ax3.set_xlabel('月份')
ax3.set_ylabel('平均负荷')
# 2.6 工作日与周末的平均负荷情况,看一下工作日的负荷与周末的负荷是否有区别
data['week_day'] = data['time'].apply(lambda x: pd.to_datetime(x).weekday())
# print(data.head())
data['is_holiday'] = data['week_day'].apply(lambda x: 1 if x in [5, 6] else 0) # 0:周一至周五,1:周末
# print(data.head())
power_load_holiday_avg = data[data['is_holiday'] == 1]['power_load'].mean()
power_load_workday_avg = data[data['is_holiday'] == 0]['power_load'].mean()
ax4 = fig.add_subplot(414)
ax4.bar(x=['工作日平均负荷', '周末平均负荷'], height=[power_load_workday_avg, power_load_holiday_avg])
ax4.set_title('工作日与周末的平均负荷对比')
ax4.set_ylabel('平均负荷')
# ...
114. 电力负荷预测案例_添加小时和月份字段
# 3. 特征工程(重点)
def feature_engineering(data, logger):
"""
对给定的数据源,进行特征工程处理,提取出关键的特征
1.提取出时间特征:小时、月份
2.提取出相近时间窗口中的负荷特征:step大小窗口的负荷
3.提取昨日同时刻负荷特征
4.剔除出现空值的样本
5.整理时间特征,并返回
:param data: 数据源
:param logger: 日志
:return:
"""
logger.info("===============开始进行特征工程处理===============")
# 先拷贝数据
result = data.copy(deep=True)
logger.info("===============开始提取时间特征===================")
# 3.1 提取出时间特征:小时、月份
# 3.1.1 提取出对应的小时,用以表示短期的时间特征
result['hour'] = result['time'].str[11:13]
# 3.1.2提取出对应的月份,用以表示长期的时间特征
result['month'] = result['time'].str[5:7]
# 3.1.3 对时间特征hour和month字段进行one-hot编码处理
hour_month_encoding = pd.get_dummies(result[['hour', 'month']])
# print(hour_month_encoding.head())
# print(hour_month_encoding.info())
# 3.1.4 对one-hot编码后的结果进行拼接
result = pd.concat([result, hour_month_encoding], axis=1)
# print(result.head())
print(result.info())
# 5. 测试
if __name__ == '__main__':
# 4.1 创建电力负荷模型类的对象
input_file = PROJECT_ROOT / 'data' / 'train.csv'
model = PowerLoadModel(input_file)
# 4.2 打印数据源
# print(model.data_source)
# 4.3 查看数据分布
# ana_data(model.data_source)
# 4.4 特征工程
feature_engineering(model.data_source, model.logfile)
115. 电力负荷预测案例_特征工程_添加上n小时(窗口字段)
# 3. 特征工程(重点)
def feature_engineering(data, logger):
# ...
logger.info("==============开始提取相近时间窗口中的负荷特征====================")
# 3.2 提取出相近时间窗口中的负荷特征:step大小窗口的负荷
# print(result['step_load'])
# tmp_df = result['step_load'].shift(1)
# tmp_df = result['step_load'].shift(2)
# print(tmp_df)
# 指定window_size下的相近时间窗口负荷
window_size = 3
shift_list = [result['power_load'].shift(i) for i in range(1, window_size + 1)]
shift_data = pd.concat(shift_list, axis=1)
shift_data.columns = ['前' + str(i) + '小时' for i in range(1, window_size + 1)]
result = pd.concat([result, shift_data], axis=1)
116. 电力负荷预测案例_特征工程_昨日同时刻负荷
# 3. 特征工程(重点)
def feature_engineering(data, logger):
# ...
logger.info("============开始提取昨日同时刻负荷特征===========================")
# 3.3 提取昨日同时刻负荷特征
# 3.3.1 给特征新增1列 yesterday_time
result['yesterday_time'] = result['time'].apply(
lambda x: (pd.to_datetime(x) - pd.to_timedelta('1d')).strftime('%Y-%m-%d %H:%M:%S'))
# print(result.head())
# 3.3.2 我们把所有的 日期 和 负荷拼接成字典,方便查找
# 格式:{'2025-04-18 10:00:00': 100, '2025-04-18 11:00:00': 101, '2025-04-18 12:00:00': 102}
time_load_dict = result.set_index('time')['power_load'].to_dict()
# print(time_load_dict)
# 3.3.3 新增1列 yesterday_load,表示:昨天的相同时刻的负荷
result['yesterday_load'] = result['yesterday_time'].apply(lambda x: time_load_dict.get(x))
# print(result.head())
# result.info()
# 3.4 剔除出现空值的样本
result.dropna(inplace=True)
# 3.5 整理时间特征列,并返回
# 3.5.1 整理时间特征列
time_feature_names = list(hour_month_encoding.columns) + list(shift_data.columns) + ['yesterday_load']
logger.info(f"特征列名是:{time_feature_names}") # ['hour_0', 'hour_1', 'hour_2', 'hour_3', 'hour_4', 'hour_5', 'hour_6', 'hour_7', 'hour_8', 'hour_9', 'hour_10', 'hour_11', 'hour_12', 'hour_13', 'hour_14', 'hour_15', 'hour_16', 'hour_17', 'hour_18', 'hour_19', 'hour_20', 'hour_21', 'hour_22', 'hour_23', 'month_0', 'month_1', 'month_2', 'month_3', 'month_4', 'month_5', 'month_6', 'month_7', 'month_8', 'month_9', 'month_10', 'month_11', '前1小时', '前2小时', '前3小时', 'yesterday_load']
# 3.5.2 返回结果
return result, time_feature_names
# 5. 测试
if __name__ == '__main__':
# ...
# 4.4 特征工程
feature_data, feature_columns = feature_engineering(model.data_source, model.logfile)
# 4.5 模型训练
# print(pd.to_datetime('2025-04-18 10:00:00') - pd.to_timedelta('1d'))
# print(pd.to_datetime('2025-04-18 10:00:00') - pd.to_timedelta('3d'))
117. 电力负荷预测案例_模型训练_评估_保存
# 4. 模型训练,评估,保存
def model_train(data, features, logger):
"""
1.数据集切分
2.网格化搜索与交叉验证
3.模型实例化
4.模型训练
5.模型评价
6.模型保存
:param data: 特征工程处理后的输入数据
:param features: 特征名称
:param logger: 日志对象
:return:
"""
logger.info("=========开始模型训练===================")
# 4.1 数据集切分
x = data[features]
y = data['power_load']
# print(x.shape, y.shape) # (14160, 40) (14160,)
# print(x.head())
# print(y.head())
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=23)
# # 4.2 网格化搜索与交叉验证
# logger.info('==========网格搜索 + 交叉验证 寻找最优超参==========')
# logger.info(f'开始时间:{datetime.datetime.now()}')
# # 4.2.1 定义参数字典
# param_dict = {
# 'n_estimators': [50, 100, 150, 200],
# 'max_depth': [3, 5, 6, 7, 9],
# 'learning_rate': [0.1, 0.01]
# }
# # 4.2.2 创建XGBoost模型对象(Extreme Gradient Boosting Tree,极端梯度提升树)
# estimator = XGBRegressor()
# grid_cv = GridSearchCV(estimator=estimator, param_grid=param_dict, cv=5)
# # 4.2.3 模型训练
# grid_cv.fit(x_train, y_train)
# # 4.2.5 打印最优参数组合
# logger.info(f'最优参数组合:{grid_cv.best_params_}') # {'learning_rate': 0.1, 'max_depth': 5, 'n_estimators': 200}
# logger.info(f'结束时间:{datetime.datetime.now()}')
# 4.3 模型实例化
estimator = XGBRegressor(n_estimators=200, max_depth=5, learning_rate=0.1)
# 4.4 模型训练
estimator.fit(x_train, y_train)
y_pred = estimator.predict(x_test)
# 4.5 模型评价
print(f"均方误差:{mean_squared_error(y_test, y_pred)}") # 5739.624491534932
print(f"均方根误差:{root_mean_squared_error(y_test, y_pred)}") # 75.76030947359529
print(f"平均绝对误差:{mean_absolute_error(y_test, y_pred)}") # 44.57857092100919
print(f"平均绝对百分比误差:{mean_absolute_percentage_error(y_test, y_pred)}") # 0.07295813995418061
logger.info("=========================模型训练完成=============================")
# 4.6 模型保存
model_dir = PROJECT_ROOT / 'model'
model_dir.mkdir(parents=True, exist_ok=True)
joblib.dump(estimator, model_dir / 'xgb_20260922.pkl') # pickle文件 -> 后缀名一般是.pkl,.pth,.pickle
logger.info("模型保存完成,保存路径:{}".format(model_dir / 'xgb_20260922.pkl'))
# 5. 测试
if __name__ == '__main__':
# ...
# 5.5 模型训练
# 参1:处理后的全部数据集 参2:特征名称列表 参3:日志对象
model_train(feature_data, feature_columns, model.logfile)
# ...
118. 电力负荷预测案例_预测类代码实现
# -*- coding: utf-8 -*-
import os
import sys
from pathlib import Path
import pandas as pd
import datetime
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from utils.log import Logger
from utils.common import data_preprocessing
from sklearn.metrics import mean_absolute_error
import matplotlib.ticker as mick
import joblib
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'SimHei'
plt.rcParams['font.size'] = 15
# 1. 配置电力负荷预测类
class PowerLoadPredict(object):
def __init__(self, file_path):
# 配置日志记录
logfile_name = "predict_" + datetime.datetime.now().strftime('%Y%m%d%H%M%S')
self.logfile = Logger('../', logfile_name).get_logger()
# 获取数据源
self.data_source = data_preprocessing(file_path)
# 历史数据转为字典,key:时间,value:负荷,目的是为了避免频繁操作dataframe,提高效率。实际开发场景中可以使用redis进行缓存
# 格式为:{'2015-01-01 00:00:00': 100, '2015-01-01 01:00:00': 101, ...}
self.data_dict = self.data_source.set_index('time')['power_load'].to_dict()
# 4. 测试
if __name__ == '__main__':
"""
模型预测
1.导包、配置绘图字体
2.定义电力负荷预测类,配置日志,获取数据源、历史数据转为字典(避免频繁操作dataframe,提高效率)
3.加载模型
4.模型预测
4.1 确定要预测的时间段(2015-08-01 00:00:00及以后的时间)
4.2 为了模拟实际场景的预测,把要预测的时间以及以后的负荷都掩盖掉,因此新建一个数据字典,只保存预测时间以前的数据字典
4.3 预测负荷
4.3.1 解析特征(定义解析特征方法)
4.3.2 利用加载的模型预测
4.4 保存预测时间对应的真实负荷
4.5 结果保存到evaluate_list,三个元素分别是预测时间、真实负荷、预测负荷,方便后续进行预测结果评价
4.6 循环结束后,evaluate_list转为DataFrame
5.预测结果评价
5.1 计算预测结果与真实结果的MAE
5.2 绘制折线图(预测时间-真实负荷折线图,预测时间-预测负荷折线图),查看预测效果
"""
# 4.1 创建电力负荷预测类对象
input_file = PROJECT_ROOT / 'data' / 'test.csv'
pp = PowerLoadPredict(input_file)
print(pp.data_source)
print(pp.data_dict)
# 4.2 加载模型对象
estimator = joblib.load(PROJECT_ROOT / 'model' / 'xgb_20260922.pkl')
119. 电力负荷预测案例_解析预测特征
# 2. 预测数据解析特征,保持与模型训练时的特征列名一致
def pred_feature_extract(data_dict, time, logger):
"""
预测数据解析特征,保持与模型训练时的特征列名一致
1.解析时间特征
2.解析时间窗口特征
3.解析昨日同时刻特征
:param data_dict:历史数据,字典格式,key:时间,value:负荷
:param time:预测时间,字符串类型,格式为2024-12-20 09:00:00
:param logger:日志对象
:return:
"""
logger.info(f'=========解析预测时间为:{time}所对应的特征==============')
# 特征列清单
feature_names = ['hour_00', 'hour_01', 'hour_02', 'hour_03', 'hour_04', 'hour_05',
'hour_06', 'hour_07', 'hour_08', 'hour_09', 'hour_10', 'hour_11',
'hour_12', 'hour_13', 'hour_14', 'hour_15', 'hour_16', 'hour_17',
'hour_18', 'hour_19', 'hour_20', 'hour_21', 'hour_22', 'hour_23',
'month_01', 'month_02', 'month_03', 'month_04', 'month_05', 'month_06',
'month_07', 'month_08', 'month_09', 'month_10', 'month_11', 'month_12',
'前1小时', '前2小时', '前3小时', 'yesterday_load']
# 2.1 解析时间特征,即:time字段(预测时间)对应的 那条数据样本
# 2.1.1 截取要预测的time字段的小时信息
pred_hour = time[11:13] # 例如:'2015-08-01 00:00:00' -> '00'
hour_part = []
for i in range(24):
if pred_hour == feature_names[i][5:7]:
hour_part.append(1)
else:
hour_part.append(0)
# print(hour_part)
# 2.1.2 截取要预测的time字段的月份信息
pred_month = time[5:7] # 例如:'2015-08-01 00:00:00' -> '08'
month_part = []
for i in range(24, 36):
if pred_month == feature_names[i][6:8]:
month_part.append(1)
else:
month_part.append(0)
# print(month_part)
# 2.2 解析时间窗口特征
his_part = []
# 2.2.1 前1小时的负荷
last_1h_time = (pd.to_datetime(time) - pd.to_timedelta('1h')).strftime('%Y-%m-%d %H:%M:%S')
# print(last_1h_time)
last_1h_load = data_dict.get(last_1h_time, 500) # 获取前一个小时对应的负荷,若没有则默认为500
# print(last_1h_load)
# 2.2.2 前2小时负荷
last_2h_time = (pd.to_datetime(time) - pd.to_timedelta('2h')).strftime('%Y-%m-%d %H:%M:%S')
last_2h_load = data_dict.get(last_2h_time, 500)
# 2.2.3 前3小时负荷
last_3h_time = (pd.to_datetime(time) - pd.to_timedelta('3h')).strftime('%Y-%m-%d %H:%M:%S')
last_3h_load = data_dict.get(last_3h_time, 500)
# 2.3 昨日同一时刻的负荷
yesterday_time = (pd.to_datetime(time) - pd.to_timedelta('1d')).strftime('%Y-%m-%d %H:%M:%S')
yesterday_load = data_dict.get(yesterday_time, 500)
# 2.4 拼接特征数据
feature_data = hour_part + month_part + [last_1h_load, last_2h_load, last_3h_load, yesterday_load]
# print(feature_data)
# print(type(feature_data))
# 2.5 转成df对象,返回
feature_df = pd.DataFrame(feature_data, columns=feature_names)
# print(feature_df)
return feature_df
# 4. 测试
if __name__ == '__main__':
# ...
# 4.3 确定要预测的时间段:2015-08-01 00:00:00及以后的时间
# print(pp.data_source[pp.data_source['time'] >= '2015-08-01 00:00:00'])
pred_times = pp.data_source['time'][pp.data_source['time'] >= '2015-08-01 00:00:00']
# 4.4 为了模拟实际场景的预测,把要预测的时间以及以后的负荷都掩盖掉,因此新建一个数据字典,只保存预测时间以前的数据字典
for pred_time in pred_times: # pred_time: 2015-08-01 00:00:00, 2015-08-01 01:00:00, ...
print(f"正在预测{pred_time}的负荷")
data_dict_masked = {k: v for k, v in pp.data_dict.items() if k < pred_time}
# print(data_dict_masked)
# 4.5 预测负荷
# 4.5.1 解析特征(定义解析特征方法)
feature_df = pred_feature_extract(data_dict_masked, pred_time, pp.logfile)
# 4.5.2 利用加载的模型预测
y_pred = estimator.predict(feature_df)
print(f"预测结果为:{y_pred}")
120. 电力负荷预测案例_结果展示
# 3. 绘制时间与预测负荷折线图,时间与真实负荷折线图
def prediction_plot(data):
"""
绘制时间与预测负荷折线图,时间与真实负荷折线图,展示预测效果
:param data: 数据一共有三列:时间、真实值、预测值
:return:
"""
# 3.1 创建画布
fig = plt.figure(figsize=(40, 26))
# 3.2 创建子图
ax = fig.add_subplot()
# 3.3 绘制时间与真实负荷的折线图
ax.plot(data['预测时间'], data['真实负荷'], color='blue', label='真实值')
ax.plot(data['预测时间'], data['预测负荷'], color='red', label='预测值')
# 3.4 添加标题
ax.set_title('真实负荷和预测负荷关系图')
ax.set_xlabel('时间')
ax.set_ylabel('负荷')
# 3.5 添加网格
ax.grid(True, linestyle='--', alpha=0.5)
# 3.6 添加图例
ax.legend(loc='best', fontsize=20)
# 3.7 设置刻度间隔,以及x轴标签值 旋转角度
# 横坐标时间若不处理太过密集,这里调大时间展示的间隔
ax.xaxis.set_major_locator(mick.MultipleLocator(50))
# 时间展示时旋转45度
plt.xticks(rotation=45) # x轴标签值 旋转角度
# 3.8 保存图片
save_path = PROJECT_ROOT / 'data' / 'fig'
plt.savefig(save_path / '真实负荷和预测负荷关系图.png')
# 3.9 展示图片
plt.show()
# 4. 测试
if __name__ == '__main__':
# ...
# 4.8 循环结束后,evaluate_list转为DataFrame
evaluate_df = pd.DataFrame(evaluate_list, columns=['预测时间', '真实负荷', '预测负荷'])
# print(evaluate_df)
# 4.9.预测结果评价
# 4.9.1 计算预测结果与真实结果的MAE
print(f'平均绝对误差:{mean_absolute_error(evaluate_df['真实负荷'], evaluate_df['预测负荷'])}')
# 4.9.2 绘制折线图(预测时间-真实负荷折线图,预测时间-预测负荷折线图),查看预测效果
prediction_plot(evaluate_df)
121. 电力负荷预测案例_扩展思路
本案例已经能跑通:csv → 拆二维表 → XGBoost → 估下一小时。还可以从四头再拧。
121.1 特征
现在主要是小时 / 月份独热、前 3 小时、昨天同期。还能:
| 做什么 | |
|---|---|
| 时间窗口 | window_size=3 不一定最优,加长、缩短、或再加「上周同一小时」 |
| 历史负荷 | 滚动均值、峰值、工作日/周末分开的滞后,比光塞三个点更有结构 |
| 外部特征 | 温度、湿度、风速;工业用电、居民用电分开的历史。这才是 6.1 严格意义上的多变量 |
负荷跟天气绑得很死,夏天空调一开曲线就翘。数据源里现在只有时间和负荷,精度往上抬,外部特征往往比再调两个 XGBoost 参数更值钱。
121.2 算法
| 先分群再建模 | 按地区,或用 3.1 把负荷曲线相近的点先抱团,每团单独一棵树。全国一个模型容易两头不讨好 |
| 换模型 | 6.2 里的 LightGBM、RNN、iTransformer。表格式继续 Boosting;真要序列到序列再上深度 |
| 组合 | 4.1 Bagging 多套子模型投票;Stacking 用另一层模型去揉几路预测 |
不是必须上 Transformer。窗口和天气没备齐之前,换算法经常涨不动。
121.3 预测速度
预测时也要拼特征:前 1/2/3 小时、昨天同期。现在 data_dict 整表塞内存,predict.py 里已经写了可以换成 Redis:历史负荷按时间当 key,查一次就有,不用每次扫 DataFrame。线上每小时一批点时,缓存比反复读 csv 重要。
121.4 工程扩展
| 数据源 | 现在只读 csv。工具层接到 MySQL、Postgres、Oracle、ES、Redis、Hive,换库不改训练逻辑 |
| 部署 | 项目打成容器,换台机器不用重配 Anaconda 路径 |
| 服务化 | 预测从脚本里拆出来,对外 HTTP:Web 传来时间(和地区),返回下一小时负荷。FastAPI 那套正好接 |
前两头管准不准,后两头管能不能上线。课上停在 csv + 本地 pkl,这四条是同一条项目往外长的方向。
深度学习
机器学习刚收住:sklearn 拆表、fit、评估。大模型开发要的底座换成 多层神经网络,框架换成 PyTorch。这条课卡在学习路线里「机器学习 → 深度学习 → NLP → …」的第二段。
1. 深度学习与神经网络课程导学
1.1 这门课在整条路线上干什么
就业「王炸」:金融、医疗、客服、教育、新闻、娱乐、自动驾驶、制造、电商、物流都在上大模型。岗位名一长串——Agent / RAG / Dify / Coze / 微调 / 多模态 / NLP / 推理优化
课上把大模型能力拆成四段,本课只打中间「技术基建」里的深度学习这块:
| 阶段 | 内容 | 你现在 |
|---|---|---|
| 基础能力 | Python、数据分析、计基、大模型概念 | 已经走过 |
| 技术基建 | 机器学习、PyTorch 与深度学习、NLP、Transformer | 机器学习刚完,从这里进 |
| 核心工程 | 微调、RAG、知识图谱、Agent | 后面 |
| 前沿 | 多模态、RLHF、压缩蒸馏、部署 | 更后面 |
所以不是上来微调 GPT,而是先把网络怎么堆、误差怎么回传、CNN / RNN 各吃什么数据搞清楚。
1.2 神经网络怎么长到今天
| 年 | 节点 |
|---|---|
| 1943 | MP 神经元模型 |
| 1949 | Hebb 学习规则 |
| 1958 | Perceptron,第一次兴起 |
| 1969 | 第一次 AI 寒冬 |
| 1982 | Hopfield |
| 1986 | BP 反向传播,第二次兴起 |
| 1995 | SVM 把风头抢走一阵 |
| 2006 | DBN,深度网络重新被认真对待 |
| 2012 | CNN 第三次兴起 |
| 2020 前后 | 图上标了 Winter?——大模型起来之后叙事又换了 |
课上要走的是这条线里还能用手推的部分:神经元 → 一层 → BP 多层 → CNN / RNN。
1.3 从生物神经元到一个公式
生物神经元:胞体收信号,树突接收,轴突传出,突触接到下一个细胞,神经递质过缝。脉冲有方向。
抽象成一层人工神经元,和线性回归几乎同一句话:
| 生物 | 人工 |
|---|---|
| 树突上的输入 | |
| 突触强度 | 权重 |
| 胞体整合 | 加权和 |
| 是否发放 | 激活函数 |
| 轴突输出 |
机器学习里 没有 ,或者逻辑回归把 换成 sigmoid。深度学习把很多个这样的单元叠起来, 负责弯,叠深了才能自动抠特征。
1.4 一层、BP、多层
正向:输入层 → 隐含层 → 输出层,信息往右走。
BP(反向传播):输出和真值比出误差,再从右往左把误差分给每个 (图上 、)。和线性回归梯度下降同一类事,只是层多了,要用链式法则一层层传。1986 年 BP 能算了,多层才训得动。
再深就是多个 Hidden Layer:输入若干维,中间几层全连接,最后输出。层数上去,叫深度学习。
深度学习是机器学习的一个分支,核心是用多层神经网络做自动特征提取和模式识别。sklearn 那套要你自己做标准化、独热、窗口;深度网络希望从原始像素、原始序列里自己学表示。
1.5 CNN 和 RNN:这门课两条主干
| 吃什么 | 结构直觉 | |
|---|---|---|
| CNN 卷积 | 图像 | 卷积抽局部、降采样缩小、再全连接分类。例:32×32×3 图 → 若干卷积/池化 → 展平 → 10 类 |
| RNN 循环 | 序列(文本、语音、时间序列) | 隐藏状态 带着上一拍 ,按时间展开: 共用一套 |
负荷预测那次用 XGBoost 拆表,是传统机器学习吃时序。121.2 说的 RNN / iTransformer,就是序列不拆死、让网络自己记前后。图像课上还没碰过,CNN 是新的一条。
1.6 还是那三块:数据、算法、算力
和机器学习导学同一张图:
| 角色 | |
|---|---|
| 数据 | 基础。文字、图像、视频。没有量,多层网喂不饱 |
| 算法 | 引擎。机器学习是落地引擎;深度学习主攻 NLP / CV 这类高维 |
| 算力 | 平台。CPU 也能跑小网;深度、大模型靠 GPU |
层一深,矩阵乘爆炸,所以后面会碰到 PyTorch 把计算丢到 GPU。
1.7 课程学什么、学完能干什么
顺序:
PyTorch 框架 → 深度学习基础(BP 等)→ CNN → RNN → 案例实战
课上说的四块亮点,对应到作业形态:
| 块 | 落到什么 |
|---|---|
| PyTorch 从会用到懂原理 | 张量、点乘、自动求导;手拆反向,报错能对上哪一层 |
| 人工神经网络基础 | 损失(MSE、交叉熵)、梯度下降、L1/L2;电商价格分类那种小案例 |
| CNN + RNN | CIFAR10 图像分类;词嵌入 + RNN 做文本(歌词生成那种) |
| 项目贯穿 | 入门:PyTorch 线性回归 / 逻辑回归;进阶:图像分类、文本生成 |
目标四条:神经网络基础、用 PyTorch 搭结构、会 CNN、会 RNN。这是大模型应用的底座,不是大模型本身。
2. 知识框架介绍
1.7 那条顺序,换成自下而上四层。机器学习在图像和文本上偏弱,所以从人工神经网络再分出 CNN(CV)和 RNN(NLP);两条线最后都接到全连接层。
机器学习吃图 / 文本偏弱
/ \
4 CNN(CV) 4 RNN(NLP)
卷积 → 池化 → 全连接 词嵌入 → 循环 → 全连接
案例:CIFAR10 图像分类 案例:歌词生成
\ /
3 全连接层(两条共用)
|
2 ANN 人工神经网络
输入 1 层 → 隐藏 n 层 → 输出 1 层
|
1 张量 Tensor(标量、向量都是它的特例)
PyTorch / TensorFlow
| 层 | 学什么 | 对应后面 |
|---|---|---|
| 1 张量 | 标量、向量、矩阵 → 统一叫 Tensor。框架先会 PyTorch(课上也点名 TensorFlow,主线用前者) | 建网之前的数据形态 |
| 2 ANN | 1.4 那套:一层输入、若干隐藏、一层输出;BP 回传 | 线性回归 / 逻辑回归用 PyTorch 重写 |
| 3 全连接 | 每个输出都连上一层所有输入。CNN、RNN 抽出特征之后,分类 / 生成都靠它收口 | 两条主干的共同尾巴 |
| 4 CNN | 卷积抽局部、池化缩小,再全连接。专吃图像 | CIFAR10,走向 CV |
| 4 RNN | 词先嵌入成向量,循环层记前后,再全连接。专吃序列 | 歌词生成,走向 NLP |
先把 1、2 垒实:张量算错、层没叠对,后面卷积核、循环状态都会漂。CNN / RNN 不是另起炉灶,是 ANN 换了中间几层,尾巴仍是全连接。
3. 深度学习_简介
课件两张图把位置先钉死,再对比「人抽特征」和「网自己抽」。1.4 已经说过它是机器学习的分支;这里按课上原话把三层关系和黑盒说清楚。
3.1 它在哪一层
人工智能 AI 造能干活的机器(仿智)
└── 机器学习 ML 不必把规则一条条写死;特征工程主要是人做
└── 深度学习 DL 特征工程交给算法;底层是深度神经网络
- 机器学习是实现人工智能的一种途径,不是唯一途径
- 深度学习是机器学习的一个子集,也就是实现机器学习的一种方法
- 更具体:深度学习是机器学习里一种基于对数据做特征学习的算法
时间轴从 1950s 画到 2010s,和 1.2 那张年表是同一条故事:感知机 → 寒冬 → BP → 2012 CNN 再热。
网络本身:1.3 一个神经元 = 加权求和 + 激活函数。叠起来就是输入层 → 若干隐藏层 → 输出层。「深度」指的是中间用了多层;每一层做一次非线性变换,逐渐抽出更复杂、更抽象的特征。没有非线性,多层等价于一层,深不起来。
3.2 和传统机器学习差在哪
同一件事:图里一辆车,输出「是车 / 不是车」。
| 传统机器学习 | 深度学习 | |
|---|---|---|
| 输入 | 图 / 原始数据 | 一样,张量里只是数值 |
| 中间 | 人做特征提取,再交给分类器 | 特征提取 + 分类都在网里,合成一块黑盒 |
| 谁设计特征 | 依赖人工 | 算法从大量数据里自动抽 |
传统路线:输入 → 人工特征(边、轮子、颜色……)→ 分类 → 输出。深度路线:输入直接进多层网,中间不拆给你看。这就是叫黑盒、可解释性差的原因——不是不能用,是你很难指着某一层说「这就是轮子」。
它尤其擅长高维数据:图像、语音(频谱图)、文本。课上对应两条:1.5 的 CNN(图)和 RNN(序列 / 频谱 / 文本)。2 里两条线最后都接到全连接,只是中间换了抽特征的层。
4. 深度学习_特点
3 把位置和黑盒说完。课上把特点收成四条,前两条是它强在哪,后两条是代价。
| 特点 | 一句话 | |
|---|---|---|
| ① | 多层非线性变换 | 一层一层弯,浅层抓边和颜色,深层才认出物体、脸 |
| ② | 自动特征提取 | 不用人做特征工程,从原始数据里自己学有用的表示 |
| ③ | 要大数据和算力 | 标注要多,训练常靠 GPU;图、NLP 的突破是数据和算力堆出来的 |
| ④ | 可解释性差 | 内部不透明,叫黑箱;为什么判成这一类,不好指给人看 |
4.1 多层非线性变换
模型由多个层次组成,每一层都用非线性激活把输入拧一下。3.1 说了:没有非线性,多层叠起来还是一层线性。浅层通常抓住简单特征(边缘、颜色),更高层再组合成复杂模式(物体、人脸)。「深」不是层数好看,是一层层抽象往上走。
4.2 自动特征提取
和 3.2 同一件事。传统机器学习:人设计特征再分类。深度学习:从原始像素、波形、字里自己学有用的特征,少做手工特征工程。图像、语音、文本这些高维领域,它才显得出色。
4.3 大数据和计算能力
多层参数极多,小数据容易过拟合,还算不动。通常要大量标注和强计算(GPU)。1.6 那三块里,数据和算力在这里被点名:图像识别、NLP 近几年的突破,是大数据 + 高性能计算托起来的,不只是换了个算法名字。
4.4 可解释性差
内部运作相对不透明,叫黑箱。知道输入输出,很难说明「为什么判成车」。3.2 的黑盒就是这条。医疗、金融这类要给说法的场景,这是挑战,不是小毛病。
5. 常用模型介绍
2 课上只深挖 CNN、RNN;这页把常见模型摊开,先认名字和各自吃什么。
输入层 → 隐藏层(卷积 / 池化 / 全连接等)→ 输出层
| 模型 | 英文 | 课上怎么用 |
|---|---|---|
| 卷积神经网络 | CNN | 图像,CV。隐藏层常见:卷积、池化、全连接 |
| 循环神经网络 | RNN | 文本,NLP |
| 自编码器 | Autoencoder | 无监督:降维、特征、异常检测 |
| 生成对抗网络 | GAN | 生成:图像、视频 |
| Transformer | Transformer | NLP:翻译、生成;丢掉递推,改注意力 |
| 深度强化学习 | DRL | 点名,这门课不展开 |
| 图神经网络 | GNN | 点名,这门课不展开 |
5.1 CNN
专吃图像:分类、检测、分割。1.5 那条。卷积层自动抽局部特征,池化把图缩小、参数变少,算得动。抽完再全连接收口。
5.2 RNN
专吃序列:NLP、语音。有记忆,能看输入在时间上的依赖(上一拍的状态带着走)。标准 RNN 记不了太远,长期依赖很难;后面 LSTM / GRU、再往后 Transformer,都是在补这一刀。
5.3 自编码器
无监督。编码器把输入压成低维表示,解码器再从这点低维往回重建输入。常用于降维、学特征、异常检测(重建差得大,多半是异常)。
5.4 GAN
两个子网对着练:生成器造假样本,尽量像真的;判别器分辨真假。用在图像生成、视频合成。
5.5 Transformer
主战场是 NLP:机器翻译、文本生成、摘要。丢掉 RNN 那种一步步递推,改用自注意力(self-attention),整句可以并行看。这就是现在大模型底座;课上先把 CNN / RNN 垒实,名字先挂上。
6. 应用场景介绍
深度学习是底座,上面叠计算机视觉、文本处理、语音交互、人机交互、知识图谱这些能力,再落到智慧医疗、智能安防、智能零售、智慧教育、智能家居、智能制造、智能汽车、智能机器人等产业。
常见场景:计算机视觉、自然语言处理、自动驾驶、推荐系统、医疗健康、工业与制造、语音与音频。下面把 CV、NLP、推荐 写细,最后补 多模态大模型:图、文、语音不再各管各的。
6.1 计算机视觉(CV)
对应 5.1。把像素变成「这张图里有什么、在哪」。
| 任务 | 干什么 | 例子 |
|---|---|---|
| 图像分类 | 整图归到某一类 | 人脸识别、物体检测的前置;社交媒体自动打标签、医学影像里找病变 |
| 目标检测 | 图像或视频里定位并分类多个对象 | 自动驾驶行人检测、监控里的入侵检测 |
| 面部识别 | 用脸做身份验证或分类 | 手机解锁、安防监控 |
| 图像生成 | 按输入造新图 | 风格转换、超分辨率;艺术风格迁移、老照片修复(5.4) |
6.2 自然语言处理(NLP)
| 任务 | 干什么 | 例子 |
|---|---|---|
| 机器翻译 | 一种语言自动翻成另一种 | Google 翻译、实时语音翻译 |
| 情感分析 | 正面 / 负面 / 中性 | 社交媒体监控、商品评论 |
| 文本生成 | 生成通顺文本 | 写作助手、新闻生成 |
| 语音识别 | 语音 → 文字 | Siri、Alexa、自动字幕 |
| 聊天机器人 | 理解输入并回话 | 客服机器人、GPT 类虚拟助手 |
6.3 推荐系统
按历史行为猜下一步爱看什么、爱买什么。
| 场景 | 依据 | 例子 |
|---|---|---|
| 影音 | 评分、播放记录 | Netflix、Spotify |
| 电商 | 购买、浏览 | 亚马逊、淘宝 |
| 社交 | 互动、关注 | Facebook、Instagram 内容 / 好友推荐 |
自动驾驶、医疗、工业、智能家居,是把 CV、语音嵌进具体行业,不是另起一套算法。
6.4 多模态大模型
6.1~6.3 默认一种输入对一种输出:图进图出、字进字出。多模态是把几种信号放进同一个模型:一张图 + 一句话一起理解,或听语音再对着画面回答。
底座多半是 5.5,再接上图像 / 语音编码器。常见用法:
| 任务 | 干什么 | 例子 |
|---|---|---|
| 图文理解 | 看图答题、看图说话 | 视觉问答、图片描述、文档里的表和字一起读 |
| 文生图 / 图生文 | 一种模态生成另一种 | 文生图、给图写标题 |
| 语音 + 文本 / 画面 | 听、说、看打通 | 实时翻译时对着画面指物体、带视觉的语音助手 |
前面三条(CV、NLP、推荐)是专才;多模态大模型是把专才拼成一个能同时看、读、听的通用接口。后面动手仍先落在单模态的 CV(CIFAR10)和 NLP(文本生成),这里先知道它把谁拼在一起。
7. 发展史介绍
1.2 按年份点过节点。这里拆成两层:人工智能四段浪潮,再收成深度学习自己的四段。反向传播单独画清楚——多层能训,靠的就是它。
7.1 人工智能四段
| 阶段 | 时间 | 靠什么 | 节点 |
|---|---|---|---|
| 符号主义 | 1950s–1970s | 专家系统,规则由人写 | 1950 图灵国际象棋程序;1962 Samuel 跳棋程序赢过人类高手(第一次浪潮) |
| 统计主义 | 1980s–2000s | 统计模型 | 1993 Vapnik 提出 SVM;1997 IBM 深蓝赢卡斯帕罗夫(第二次浪潮) |
| 神经网络 / 深度学习 | 21 世纪初 | 深度网络 | 2012 AlexNet;2016 AlphaGo 赢李世石(第三次浪潮) |
| 大规模预训练 | 2017–至今 | 大模型、生成 | 2017 Transformer;2018 BERT、GPT;2022 ChatGPT,进入 AIGC |
前两段几乎不靠「深」网络:符号是 if-then,统计是 SVM 这类浅模型。第三次浪潮才把 5.1 推上台面;2017 之后叙事换成预训练大模型,接 5.5、6.4。
7.2 深度学习四段
| 阶段 | 时间 | 发生了什么 |
|---|---|---|
| 早期探索 | 1940s–1980s | McCulloch–Pitts 神经元(仿生物);1957 Rosenblatt 感知机,能做简单二分类;1960s 多层感知机(MLP)出现,算力和数据不够,用不开 |
| 挑战与瓶颈 | 1980s–1990s | 1986 Rumelhart 等人发表 反向传播(BP),多层可以用梯度下降改参数。算力弱、图像和语音又高维,深度网还是没铺开 |
| 复兴与突破 | 2000s–2010s | 2006 Hinton 深度信念网络(DBN),深度学习重新被当回事;2012 Krizhevsky 的 AlexNet 在 ImageNet 上大赢,CNN 成主流 |
| 爆发期 | 2016–至今 | 2016 AlphaGo;2017 Transformer;2018 BERT / GPT;2022 ChatGPT → 大模型 AIGC |
和 7.1 对上:BP 解决了「多层怎么训」,但还要等数据和 GPU;AlexNet 是计算机视觉爆点,Transformer 是 NLP / 大模型爆点。
7.3 反向传播(BP)
网络从左到右:
输入层 → 隐藏层(若干层全连接)→ 输出层
每个神经元仍是 1.3 那句:
训练走两趟:
正向:输入往右算到输出,和真值比,得到损失
反向:损失从输出层往左传,逐层更新权重 w、偏置 b
| 方向 | 干什么 |
|---|---|
| 正向(→) | 输入层进数,隐藏层一层层 再激活,输出层吐预测 |
| 损失 | 预测和标签差多少 |
| 反向(←) | 按损失对每个 求梯度,从右往左改参数 |
和线性回归的梯度下降是一类事,只是层多了,要用链式法则一层层拆。1986 年 BP 能算之后,7.2 里「多层」才不是纸上结构。没有这一步,深网只是一堆固定的 ,学不成特征。
8. PyTorch框架简介
2 最底层是张量,动手用的框架是 PyTorch(同一层还有 TensorFlow,后面代码走前者)。
8.1 它是什么
PyTorch 是基于 Python 的深度学习框架,把数据封成**张量(Tensor)**再算。提供构建、训练、部署机器学习和深度学习模型的工具,学术和工业都在用,尤其是计算机视觉、自然语言处理、强化学习。
安装(清华源):
pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple
8.2 特点
| 特点 | 含义 |
|---|---|
| 类似 NumPy 的张量计算 | Tensor 像 ndarray,多了梯度、能上 GPU |
| 自动微分 | 正向算出损失,反向自动给每个参数梯度,不用手推 7.3 |
| 深度学习库 | nn.Linear、卷积、RNN 这类层直接调 |
| 动态计算图 | 边跑边建图,Python 控制流(if / for)就是网络结构 |
| GPU 加速 | CUDA,矩阵乘丢显卡,对上 4.3 |
| 多场景、跨平台 | CV / NLP / 强化学习;Windows、Linux、云端都能跑 |
自动微分怎么接到参数更新。例如 ,,对 求导:
权重按梯度下降改一刀(学习率 ,当前 ):
PyTorch 的 autograd 干的就是「求这个 40」;你只写正向的 ,反向不用自己推 。
8.3 发展简史
| 年 | 节点 |
|---|---|
| 2016 | Facebook 发布第一版 |
| 2018 | 1.0,进入生产可用 |
再往前,深度学习框架走过几段:早期 C / C++ 库(OpenNN 一类)API 重、没 GPU;2012 前后 Caffe、Theano、Chainer 起来,有 GPU、能搭复杂网;2015–2018 TensorFlow、Keras、MXNet、Caffe2 和 PyTorch 挤在稳定期,自动微分和分布式成标配;2019 之后 TensorFlow 2.x 和 PyTorch 并列为常用两套,国产还有飞桨等。
现在写模型常见就是 nn.Linear(3, 5) 这种一层:3 个输入、5 个输出的全连接。后面张量运算、自动求导、把网丢到 GPU,都在这个框架里完成。