MLP-1:skLearn库简介
MLP-1:skLearn库简介
摘要
本文通过一个实战案例,系统展示了使用scikit-learn库构建和优化机器学习模型的完整流程。从基础概念入手,逐步深入到模型训练、特征工程、结构优化等核心环节。文章以“判断数字奇偶性”这一直观任务为切入点,揭示了许多初学者常犯的典型错误——直接使用原始数值作为特征,导致模型无法学习到有效规律。通过对比不同MLP网络结构的性能表现,本文生动诠释了“网络结构不是越深越好,关键在于特征表示和结构匹配”这一核心原理。文章最后将所学知识应用于经典的鸢尾花分类任务,演示了如何从数据探索、模型选择到最终部署的完整工作流,为读者提供了可复现的学习路径和实践指南。
1. skLearn库简介
回顾上一篇文章,可以发现手搓模型还是太麻烦了,其实我们完全不需要自己造轮子的,可以使用现成的库——
skLearn(scikit-learn)。
skLearn是一个基于Python的机器学习库,提供了丰富的工具和算法,用于数据预处理、模型选择、训练和评估。它支持各种监督学习和无监督学习算法,适用于分类、回归、聚类等任务。参考:https://scikit-learn.org/stable/getting_started.html
安装sklearn
pip install scikit-learn
2. sklearn基本方法
奇偶数判别
LogisticRegression
我们来完成一个简单的任务:判断一个正整数是奇数还是偶数。我们先使用skLearn中的sklearn.linear_model里的LogisticRegression模型来完成这个任务。首先导入必要的库:
import numpy as np
from sklearn.linear_model import LogisticRegression
我们首先准备一份训练集,在scikit-learn中,训练集通常以特征矩阵
X
X
X 和标签向量
y
y
y 的形式表示。对于奇偶数判别,标签为
0
0
0 (偶数)或
1
1
1 (奇数)。至于特征矩阵,我们先简单以一些正整数作为特征矩阵。比如
0
0
0 ~
1000
1000
1000 的所有整数,然后予以标注。
X = np.array([[i] for i in range(1000)]) # 特征矩阵,包含0到9999的整数
y = np.array([0 if i % 2 == 0 else 1 for i in X]) # 标签向量,偶数为0,奇数为1
接着我们调用LogisticRegression模型进行训练,这是一种概率型分类模型(奇特的是虽然它叫做逻辑回归,但它不是回归),适用于二分类问题:
其基本语法为(see: github->LogisticRegression):
sklearn.linear_model.LogisticRegression(
penalty='deprecated',
*,
C=1.0,
l1_ratio=0.0,
dual=False,
tol=0.0001,
fit_intercept=True,
intercept_scaling=1,
class_weight=None,
random_state=None,
solver='lbfgs',
max_iter=100,
verbose=0,
warm_start=False,
n_jobs=None)
model = LogisticRegression()
model.fit(X, y)
.fit()方法用于训练模型,接受特征矩阵
X
X
X 和标签向量
y
y
y 作为输入。其基本语法为:
model.fit(X, y, sample_weight=None)
参数解释:
X: 训练数据的特征矩阵,形状为(n_samples, n_features)。y: 训练数据的标签向量,形状为(n_samples,)。sample_weight: 可选参数,表示每个样本的权重,形状为(n_samples,)。如果提供了这个参数,模型在训练过程中会考虑样本的权重。
训练完成后,模型就可以用来进行预测了。我们可以来试试训练效果了,使用.predict()方法就能让模型输出预测结果了,完整代码如下:
# 完整代码
import numpy as np
from sklearn.linear_model import LogisticRegression
X = np.array([[i] for i in range(1000)])
y = np.array([0 if i % 2 == 0 else 1 for i in X])
model = LogisticRegression()
model.fit(X, y)
def predict(x):
return model.predict([[x]])[0]
while True:
x = int(input("请输入一个整数:"))
result = predict(x)
if result == 0:
print(f"{x} 是偶数")
else:
print(f"{x} 是奇数")
请输入一个整数:1
1 是偶数
请输入一个整数:10
10 是偶数
请输入一个整数:99
99 是偶数
请输入一个整数:50
50 是偶数
可以看到,模型输出的预测结果全部都是偶数,这是怎么回事?让我们先看看模型输出的概率是什么,.predict_proba()方法可以输出每个类别的概率:
test = np.array([[1], [10], [99], [50]])
print(model.predict_proba(test))
输出如下:
[[0.50149582 0.49850418]
[0.50146881 0.49853119]
[0.50120177 0.49879823]
[0.50134879 0.49865121]]
可以看到,模型对于每个输入的预测概率都非常接近 0.5 0.5 0.5 ,这说明模型根本没有学会区分奇数和偶数。这主要有两个原因:
- 特征选择不当:我们使用的特征矩阵 X X X 只是整数本身,没有提供任何有助于区分奇数和偶数的信息。模型无法从这些特征中学习到有用的模式。
- 模型复杂度不足:
LogisticRegression本质上是单层感知机的一种变体,使用sigmoid激活函数和对数损失函数。它是一个线性模型,无法捕捉到奇数和偶数之间的非线性关系。对于这个任务,我们需要一个能够捕捉非线性关系的模型,比如一个多层感知机。正如我的上一篇文章所说,线性模型只适用于线性可分问题,而奇偶数判别是一个非线性问题。
我们不妨看看多层感知机能做到什么程度:
MLPClassifier
加大剂量
在sklearn中,我们可以使用sklearn.neural_network中的MLPClassifier()来构建一个多层感知机模型。这个模型可以捕捉到数据中的非线性关系。其基本语法为:
from sklearn.neural_network import MLPClassifier
mlp = MLPClassifier(
# 隐藏层结构
hidden_layer_sizes=(100, 50, 25), # 3个隐藏层:100 → 50 → 25个神经元
# 输入层 → 100神经元 → 50神经元 → 25神经元 → 输出层
# 激活函数
activation='relu', # 默认
# 其他激活函数:
# activation='tanh', # 输出范围(-1, 1),某些任务效果好
# activation='logistic', # sigmoid,输出(0, 1),用于概率
# 优化算法
solver='adam', # 默认,自适应学习率,推荐
# solver='sgd', # 随机梯度下降,可精细控制学习率
# solver='lbfgs', # 适用于小数据集,收敛快但内存消耗大
# 学习率
learning_rate='constant', # 固定学习率
# learning_rate='adaptive', # 自适应调整
learning_rate_init=0.001, # 初始学习率(重要!)
# 正则化:防止过拟合
alpha=0.0001, # L2正则化强度,越大惩罚越重
early_stopping=True, # 早停:验证集不再提升时停止
validation_fraction=0.1, # 验证集比例(当early_stopping=True时)
# 训练控制
max_iter=500, # 最大迭代次数
batch_size='auto', # 自动设置批次大小,通常为min(200, n_samples)
shuffle=True, # 每个epoch打乱数据
# 随机性
random_state=42, # 固定随机种子,确保可重复
# btw,这里使用的是随机梯度下降。因为对每个参数都进行梯度下降的话对算力要求很高。所以就随机抽一些幸运参数进行梯度下降,而随机数就需要设置种子
# 输出信息
verbose=True, # 显示训练过程
tol=0.0001, # 损失函数变化小于此值时停止
# 其他
n_iter_no_change=10, # 连续n次验证集不提升则早停
momentum=0.9, # 动量(仅sgd有效)
nesterovs_momentum=True # 使用Nesterov动量(仅sgd有效)
)
我们先不管那些乱七八糟的参数,先随便建立一个模型来看看效果:
from sklearn.neural_network import MLPClassifier
mlp = MLPClassifier(hidden_layer_sizes=(100, 50, 25), max_iter=500, random_state=42)
# 参数设置:隐藏层:100 → 50 → 25个神经元,最大迭代500次,固定随机种子为42
mlp.fit(X, y) # 训练模型
完整代码:
from sklearn.neural_network import MLPClassifier
import numpy as np
# 生成训练集
X = np.array([[i] for i in range(1000)]) # 特征矩阵,包含0到999的整数
y = np.array([0 if i % 2 == 0 else 1 for i in X]) # 标签向量,偶数为0,奇数为1
mlp = MLPClassifier(hidden_layer_sizes=(100, 50, 25), max_iter=500, random_state=42)
# 参数设置:隐藏层:100 → 50 → 25个神经元,最大迭代500次,固定随机种子为42
mlp.fit(X, y) # 训练模型
while True:
x = int(input("请输入一个整数:"))
if x == -1:
break
print(f"{x}: {mlp.predict_proba([[x]])[0]}") # 输出预测概率
测试结果:
1: [0.47502009 0.52497991]
2: [0.51611346 0.48388654]
10: [0.51356327 0.48643673]
99: [0.53063444 0.46936556]
13: [0.51028369 0.48971631]
15: [0.50825465 0.49174535]
20: [0.51131413 0.48868587]
0: [0.7154205 0.2845795]
11: [0.51240552 0.48759448]
98: [0.53040494 0.46959506]
91: [0.52879813 0.47120187]
这个结果虽然好了一点,但是还是跟瞎猜差不多。加大剂量!首先,我们增加训练集的规模,改成 0 0 0 到 10000 10000 10000 之间的整数;其次,我们将最大迭代次数增大到 100000 100000 100000 次;最后我们使用更加复杂的神经网络结构: 100 → 200 → 100 → 100 → 50 100→200→100→100→50 100→200→100→100→50 。完整代码如下:
from sklearn.neural_network import MLPClassifier
import numpy as np
# 生成训练集
X = np.array([[i] for i in range(10000)]) # 特征矩阵,包含0到9999的整数
y = np.array([0 if i % 2 == 0 else 1 for i in X]) # 标签向量,偶数为0,奇数为1
mlp = MLPClassifier(hidden_layer_sizes=(100, 200, 100, 100, 50), verbose=True, max_iter=100000, random_state=42)
# 参数设置:隐藏层:100 → 200 → 100 → 100 → 50个神经元,最大迭代10万次,固定随机种子为42,显示训练过程
mlp.fit(X, y) # 训练模型
# 测试:
test = np.array([[1], [10], [99], [13], [15], [20], [0], [11], [98], [91]])
print(mlp.predict_proba(test))
训练用时 8.2 s 8.2s 8.2s ,结果如下:
Iteration 1, loss = 11.50616156
Iteration 2, loss = 3.69303093
Iteration 3, loss = 1.42989335
Iteration 4, loss = 0.82352310
Iteration 5, loss = 1.10181871
Iteration 6, loss = 1.82684127
Iteration 7, loss = 1.28478512
Iteration 8, loss = 3.74259920
Iteration 9, loss = 1.06326634
Iteration 10, loss = 0.93331854
Iteration 11, loss = 0.80288589
Iteration 12, loss = 0.75585343
Iteration 13, loss = 0.88438287
Iteration 14, loss = 0.72747858
Iteration 15, loss = 0.89641201
Iteration 16, loss = 0.75876511
Iteration 17, loss = 1.05313596
Iteration 18, loss = 0.99882522
Iteration 19, loss = 0.72421928
Iteration 20, loss = 0.70514009
Iteration 21, loss = 0.80062524
Iteration 22, loss = 0.72309526
Iteration 23, loss = 0.71626797
Iteration 24, loss = 0.74568386
Iteration 25, loss = 0.72303105
Iteration 26, loss = 0.71345158
Iteration 27, loss = 0.75542292
Iteration 28, loss = 0.71268419
Iteration 29, loss = 0.70309636
Iteration 30, loss = 0.71470971
Iteration 31, loss = 0.72773114
Iteration 32, loss = 0.73624637
Iteration 33, loss = 0.75126838
Iteration 34, loss = 0.70775173
Iteration 35, loss = 0.70185253
Iteration 36, loss = 0.71088527
Iteration 37, loss = 0.70514569
Iteration 38, loss = 0.69975014
Iteration 39, loss = 0.69986709
Iteration 40, loss = 0.71601158
Iteration 41, loss = 0.71146375
Iteration 42, loss = 0.70939624
Iteration 43, loss = 0.70115722
Iteration 44, loss = 0.71245884
Iteration 45, loss = 0.70269710
Iteration 46, loss = 0.70437215
Iteration 47, loss = 0.71114841
Iteration 48, loss = 0.70559597
Iteration 49, loss = 0.71240957
Training loss did not improve more than tol=0.000100 for 10 consecutive epochs. Stopping.
[[4.34308545e-02 9.56569145e-01]
[5.03043848e-01 4.96956152e-01]
[5.05390262e-01 4.94609738e-01]
[5.05269882e-01 4.94730118e-01]
[5.06239764e-01 4.93760236e-01]
[5.06673335e-01 4.93326665e-01]
[9.99870077e-01 1.29923121e-04]
[5.04377238e-01 4.95622762e-01]
[5.05314423e-01 4.94685577e-01]
[5.04783539e-01 4.95216461e-01]]
可以看到模型很快就收敛了,增大训练轮数并没有很好地改善训练效果。模型依然是在瞎猜,训练过程可视化如下:


揠苗助长
那到底该怎么办呢?加大剂量也没有解决问题,我们需要换个思路了。
事实上模型是很蠢的,它只知道数值,不能像人类一样观察数字的特征。所以我们需要帮它一把:把整数主动切割成一个个数位(btw,这叫特征工程)。比如对于
1234
1234
1234 ,我们可以把它切割成
1
、
2
、
3
、
4
1、2、3、4
1、2、3、4 四个特征,这样模型就能学会区分奇数和偶数了。另外,训练集和测试集应该分开来,不然有作弊的嫌疑了,可以使用sklearn.model_selection中的train_test_split()方法来划分数据集。完整代码如下:
import numpy as np
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
# 生成训练集
X = np.array([[i // 1000, (i // 100) % 10, (i // 10) % 10, i % 10] for i in range(10000)]) # 特征矩阵,包含0到9999的整数的数位
y = np.array([0 if i % 2 == 0 else 1 for i in range(10000)]) # 标签向量,偶数为0,奇数为1
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 划分训练集和测试集
mlp = MLPClassifier(hidden_layer_sizes=(100, 200, 100, 100, 50), verbose=True, max_iter=10000, random_state=42)
mlp.fit(X_train, y_train) # 训练模型
print(mlp.predict_proba(X_test)) # 输出测试集的预测概率
训练用时 6.9 s 6.9s 6.9s ,结果如下:
Iteration 1, loss = 0.68777020
Iteration 2, loss = 0.66622085
Iteration 3, loss = 0.62001161
Iteration 4, loss = 0.57593778
Iteration 5, loss = 0.53675469
Iteration 6, loss = 0.50286099
Iteration 7, loss = 0.48240945
Iteration 8, loss = 0.46329647
Iteration 9, loss = 0.43487611
Iteration 10, loss = 0.40404124
Iteration 11, loss = 0.38117801
Iteration 12, loss = 0.35889008
Iteration 13, loss = 0.34462273
Iteration 14, loss = 0.33366370
Iteration 15, loss = 0.31632548
Iteration 16, loss = 0.28798638
Iteration 17, loss = 0.25855991
Iteration 18, loss = 0.23320373
Iteration 19, loss = 0.20166432
Iteration 20, loss = 0.17472542
Iteration 21, loss = 0.13089081
Iteration 22, loss = 0.07698803
Iteration 23, loss = 0.03052304
Iteration 24, loss = 0.84869468
Iteration 25, loss = 0.22360412
Iteration 26, loss = 0.17875562
Iteration 27, loss = 0.13078567
Iteration 28, loss = 0.07141429
Iteration 29, loss = 0.03831337
Iteration 30, loss = 0.02012316
Iteration 31, loss = 0.01143507
Iteration 32, loss = 0.00733975
Iteration 33, loss = 0.00505409
Iteration 34, loss = 0.00366184
Iteration 35, loss = 0.00274886
Iteration 36, loss = 0.00215806
Iteration 37, loss = 0.00175018
Iteration 38, loss = 0.00144417
Iteration 39, loss = 0.00123180
Iteration 40, loss = 0.00106842
Iteration 41, loss = 0.00093812
Iteration 42, loss = 0.00084342
Iteration 43, loss = 0.00075605
Iteration 44, loss = 0.00067514
Iteration 45, loss = 0.00061370
Iteration 46, loss = 0.00054883
Iteration 47, loss = 0.00049072
Iteration 48, loss = 0.00044409
Iteration 49, loss = 0.00040732
Iteration 50, loss = 0.00037553
Iteration 51, loss = 0.00034995
Iteration 52, loss = 0.00032839
Training loss did not improve more than tol=0.000100 for 10 consecutive epochs. Stopping.
[[9.99989772e-01 1.02280422e-05]
[9.99976168e-01 2.38315613e-05]
[4.55322916e-06 9.99995447e-01]
...
[6.42331457e-07 9.99999358e-01]
[1.63215699e-04 9.99836784e-01]
[7.29811034e-06 9.99992702e-01]]
可视化:


我们还可以自己写一个测试集来测试:
for i in range(20):
print(f"{i}: {mlp.predict_proba([[i // 1000, (i // 100) % 10, (i // 10) % 10, i % 10]])[0]}")
0: [0.98256681 0.01743319]
1: [0.00297643 0.99702357]
2: [9.99425414e-01 5.74585901e-04]
3: [5.59901256e-04 9.99440099e-01]
4: [9.99794431e-01 2.05569351e-04]
5: [7.34892334e-04 9.99265108e-01]
6: [0.99801244 0.00198756]
7: [0.00110751 0.99889249]
8: [9.99516616e-01 4.83384098e-04]
9: [1.15132026e-05 9.99988487e-01]
10: [9.99691378e-01 3.08621581e-04]
11: [6.45956565e-04 9.99354043e-01]
12: [9.99761776e-01 2.38223976e-04]
13: [1.91402975e-04 9.99808597e-01]
14: [9.99716855e-01 2.83145054e-04]
15: [5.61237259e-04 9.99438763e-01]
16: [0.99780962 0.00219038]
17: [0.00137235 0.99862765]
18: [9.99651294e-01 3.48706030e-04]
19: [8.83581373e-06 9.99991164e-01]
正确率:


这次就好多了嘛!帮它划分一下数位,效果立竿见影。从数据可以看出,模型在所有 20 20 20 个测试样本上都做出了正确的预测,准确率达到 100 % 100\% 100% 。
上压力
现在是时候给模型上上压力了,我们使用几个规模不同的模型,同时,我们使用.loss_属性来查看模型的最终损失值,看看它们的训练效果如何。完整代码如下:
import numpy as np
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
# 生成训练集
X = np.array([[i // 1000, (i // 100) % 10, (i // 10) % 10, i % 10] for i in range(10000)]) # 特征矩阵,包含0到9999的整数的数位
y = np.array([0 if i % 2 == 0 else 1 for i in range(10000)]) # 标签向量,偶数为0,奇数为1
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 划分训练集和测试集
hls = [
(1,), # 1层,1个神经元
(10,), # 1层,10个神经元
(50, 25), # 2层,50 → 25个神经元
(100, 50, 25), # 3层,100 → 50 → 25个神经元
(100, 200, 100, 100, 50) # 5层,100 → 200 → 100 → 100 → 50个神经元
]
for _ in hls:
mlp = MLPClassifier(hidden_layer_sizes = _, max_iter = 10000, random_state = 42)
mlp.fit(X_train, y_train) # 训练模型
print(f"=====Hidden layer sizes: {_}=====")
for i in range(10):
print(f"{i}: {mlp.predict_proba([[i // 1000, (i // 100) % 10, (i // 10) % 10, i % 10]])[0]}")
print(f"Final loss: {mlp.loss_:.6f}") # 输出最终的损失值
训练用时 16.4 s 16.4s 16.4s ,训练结果:
=====Hidden layer sizes: (1,)=====
0: [0.50166031 0.49833969]
1: [0.50166031 0.49833969]
2: [0.50166031 0.49833969]
3: [0.50166031 0.49833969]
4: [0.50166031 0.49833969]
5: [0.50166031 0.49833969]
6: [0.50166031 0.49833969]
7: [0.50166031 0.49833969]
8: [0.50166031 0.49833969]
9: [0.50166031 0.49833969]
Final loss: 0.692530
=====Hidden layer sizes: (10,)=====
0: [9.99996395e-01 3.60475669e-06]
1: [0.12452799 0.87547201]
2: [0.60506477 0.39493523]
3: [0.67293176 0.32706824]
4: [0.67173593 0.32826407]
5: [0.63121121 0.36878879]
6: [0.58874651 0.41125349]
7: [0.54491744 0.45508256]
8: [0.50038123 0.49961877]
9: [0.01779306 0.98220694]
Final loss: 0.496439
=====Hidden layer sizes: (50, 25)=====
0: [9.99864665e-01 1.35334713e-04]
1: [1.92549503e-06 9.99998075e-01]
2: [9.99325782e-01 6.74218015e-04]
3: [5.15287998e-04 9.99484712e-01]
4: [0.99550681 0.00449319]
5: [0.2754147 0.7245853]
6: [0.47394217 0.52605783]
7: [0.68106488 0.31893512]
8: [0.82258472 0.17741528]
9: [0.01901988 0.98098012]
Final loss: 0.240345
=====Hidden layer sizes: (100, 50, 25)=====
0: [9.99999998e-01 2.26154482e-09]
1: [6.48791339e-04 9.99351209e-01]
2: [9.99980589e-01 1.94110058e-05]
3: [1.85694344e-05 9.99981431e-01]
4: [9.99693248e-01 3.06751724e-04]
5: [9.61142475e-04 9.99038858e-01]
6: [0.99803304 0.00196696]
7: [0.00924812 0.99075188]
8: [0.9843704 0.0156296]
9: [0.01242735 0.98757265]
Final loss: 0.001051
=====Hidden layer sizes: (100, 200, 100, 100, 50)=====
0: [0.98256681 0.01743319]
1: [0.00297643 0.99702357]
2: [9.99425414e-01 5.74585901e-04]
3: [5.59901256e-04 9.99440099e-01]
4: [9.99794431e-01 2.05569351e-04]
5: [7.34892334e-04 9.99265108e-01]
6: [0.99801244 0.00198756]
7: [0.00110751 0.99889249]
8: [9.99516616e-01 4.83384098e-04]
9: [1.15132026e-05 9.99988487e-01]
Final loss: 0.000328
可视化:


分析:
-
网络复杂度与性能:
-
( 1 , ) (1,) (1,) 层网络:所有预测接近 0.5 0.5 0.5,相当于随机猜测(损失最高 0.692 0.692 0.692 )
-
( 10 , ) (10,) (10,) 层网络:开始学习但性能有限(损失 0.496 0.496 0.496 )
-
( 50 , 25 ) (50,25) (50,25) 层网络:性能显著提升(损失 0.240 0.240 0.240 )
-
( 100 , 50 , 25 ) (100,50,25) (100,50,25) 层网络:接近完美(损失 0.001 0.001 0.001 )
-
( 100 , 200 , 100 , 100 , 50 ) (100,200,100,100,50) (100,200,100,100,50) 层网络:最佳性能(损失 0.000328 0.000328 0.000328 )
-
-
预测模式:
-
随着网络加深,预测概率趋近 0 0 0 或 1 1 1 ,置信度提高
-
简单网络对所有数字预测相似,复杂网络能区分奇偶模式
-
-
网络深度影响:
-
深层网络显著降低损失值
-
网络复杂度与性能呈负相关(复杂度越高,损失越低)
-
-
置信度分析:
-
深层网络预测置信度更高(接近 0 0 0 或 1 1 1 )
-
网络越深,预测分布越两极分化
-
旧事重提
回顾上一篇文章提到的“异或问题”,这下我们可以把它秒了,不用手搓几十行代码了。我们接着看看不同神经网络结构对这个简单训练的影响
import numpy as np
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
for _ in [(1,), (2,), (2, 2), (2, 3), (3, 3), (5, 5), (1, 1, 1), (1, 1, 1, 1, 1), (1, 3, 5), (100, 100, 100)]:
mlp = MLPClassifier(hidden_layer_sizes = _, max_iter = 10000, random_state = 42)
mlp.fit(X, y)
print(f"{'=' * 10}Hidden layer sizes: {_}{'=' * 10}")
print(mlp.predict_proba(X))
print(f"训练准确率:{accuracy_score(y, mlp.predict(X))}")
==========Hidden layer sizes: (1,)==========
[[0.64316442 0.35683558]
[0.47099079 0.52900921]
[0.66153121 0.33846879]
[0.49120908 0.50879092]]
训练准确率:0.5
==========Hidden layer sizes: (2,)==========
[[0.46791702 0.53208298]
[0.46791702 0.53208298]
[0.46791702 0.53208298]
[0.46791702 0.53208298]]
训练准确率:0.5
==========Hidden layer sizes: (2, 2)==========
[[0.40752216 0.59247784]
[0.40752216 0.59247784]
[0.40752216 0.59247784]
[0.94168446 0.05831554]]
训练准确率:0.75
==========Hidden layer sizes: (2, 3)==========
[[0.37871879 0.62128121]
[0.37871879 0.62128121]
[0.37871879 0.62128121]
[0.92407838 0.07592162]]
训练准确率:0.75
==========Hidden layer sizes: (3, 3)==========
[[0.95535636 0.04464364]
[0.12329944 0.87670056]
[0.12329944 0.87670056]
[0.97055401 0.02944599]]
训练准确率:1.0
==========Hidden layer sizes: (5, 5)==========
[[0.94410733 0.05589267]
[0.2205106 0.7794894 ]
[0.00852119 0.99147881]
[0.98988103 0.01011897]]
训练准确率:1.0
==========Hidden layer sizes: (1, 1, 1)==========
[[0.41622532 0.58377468]
[0.41622532 0.58377468]
[0.41622532 0.58377468]
[0.41622532 0.58377468]]
训练准确率:0.5
==========Hidden layer sizes: (1, 1, 1, 1, 1)==========
[[0.3633386 0.6366614]
[0.3633386 0.6366614]
[0.3633386 0.6366614]
[0.3633386 0.6366614]]
训练准确率:0.5
==========Hidden layer sizes: (1, 3, 5)==========
[[0.9607789 0.0392211 ]
[0.02993141 0.97006859]
[0.21253402 0.78746598]
[0.96075698 0.03924302]]
训练准确率:1.0
==========Hidden layer sizes: (100, 100, 100)==========
[[0.99685733 0.00314267]
[0.0025775 0.9974225 ]
[0.00270515 0.99729485]
[0.99784047 0.00215953]]
训练准确率:1.0

网络不是越深越好,而是"结构对路"才好。
核心发现:
1. 完全失败的案例
- ( 1 , ) (1,) (1,): 全错-单层感知机,理论上就无法解决XOR
- ( 2 , ) (2,) (2,): 全一样-陷入局部最优,梯度消失
- ( 1 , 1 , 1 ) (1,1,1) (1,1,1): 全一样-太窄,再多层也白搭
- ( 1 , 1 , 1 , 1 , 1 ) (1,1,1,1,1) (1,1,1,1,1): 全一样-深度≠能力,宽度是关键
原因:这些网络要么理论无能(单层),要么实践卡壳(训练失败)。
2. 部分成功的案例
- ( 2 , 2 ) (2,2) (2,2) : 3 3 3 个一样, 1 1 1 个不同 - 学到了一点,但没学全
- ( 2 , 3 ) (2,3) (2,3) : 同上 - 加宽也没用对地方
原因:网络有能力,但优化算法被困在局部最优。模型找到了一个"偷懒"的解:把三个点归为一类,另一个点单独处理。
3. 成功的案例
- ( 3 , 3 ) (3,3) (3,3) : 基本正确 - 找到有效解
- ( 5 , 5 ) (5,5) (5,5) : 更好 - 置信度更高
- ( 1 , 3 , 5 ) (1,3,5) (1,3,5) : 不错 - 非对称结构也能工作
- ( 100 , 100 , 100 ) (100,100,100) (100,100,100) : 完美 - 置信度接近 100 % 100\% 100%
原因:足够的容量 + 合适的优化,找到了真正的XOR边界。
深入分析:
为什么 ( 2 , ) (2,) (2,) 层理论上能解决XOR,实际却失败?
- 理论: 2 2 2 个神经元的隐藏层足够表达XOR
- 实际:ReLU激活函数 + 随机初始化可能导致:
- 神经元"死亡"(梯度为 0 0 0 )
- 对称权重更新(两个神经元学一样的东西)
- 学习率不合适
为什么窄而深的网络 ( 1 , 1 , 1 ) (1,1,1) (1,1,1) 失败?
- 每一层只有 1 1 1 个神经元 → 信息瓶颈
- 相当于:输入 → 线性变换 → 激活 → 线性变换 → …
- 多个线性变换的复合还是近似线性 → 无法解决非线性问题
为什么宽网络 ( 5 , 5 ) (5,5) (5,5) 成功?
- 冗余神经元提供了"多个尝试机会"
- 即使某些神经元初始化不好,其他神经元能补上
- 更宽的决策边界容错性更强
训练过程的坑:
- 初始化陷阱:坏的初始化让网络"开局就废"
- 梯度消失:深层网络信号传不过去
- 对称更新:神经元学重复的特征
- 局部最优:找到简单但不正确的解
结构选择启示:
- 宁可宽一点,不要窄到底 - ( 5 , 5 ) (5,5) (5,5) 比 ( 2 , ) (2,) (2,) 可靠
- 深度需要宽度支撑 - ( 1 , 1 , 1 ) (1,1,1) (1,1,1) 不如 ( 3 , 3 ) (3,3) (3,3)
- 不对称结构可能有效 - ( 1 , 3 , 5 ) (1,3,5) (1,3,5) 表现不错
- 过参数化有时必要 - ( 100 , 100 , 100 ) (100,100,100) (100,100,100) 最稳定
控制变量
为了使实验更严谨,我们需要使用控制变量的方法:固定神经元的数量,什么样的架构是最优的呢?
- 我们首先考虑一个简单的情况:每层神经元的数量都相同。进而我们固定神经元总数为 12 12 12 ,因为 12 12 12 的因数比较多。
import numpy as np
from sklearn.neural_network import MLPClassifier
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
for _ in range(1, 13):
if 12 % _ == 0:
print(f"{'='*10}Hidden layer sizes: {(_,) * (12 // _)}{'='*10}")
mlp = MLPClassifier(hidden_layer_sizes=(_,) * (12 // _), max_iter=100000, random_state=42)
mlp.fit(X, y)
print(f"Loss: {mlp.loss_}")
==========Hidden layer sizes: (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)==========
Loss: 0.7296399904253026
==========Hidden layer sizes: (2, 2, 2, 2, 2, 2)==========
Loss: 0.6961985491718647
==========Hidden layer sizes: (3, 3, 3, 3)==========
Loss: 0.694427912695449
==========Hidden layer sizes: (4, 4, 4)==========
Loss: 0.029485135787019764
==========Hidden layer sizes: (6, 6)==========
Loss: 0.3689697563410117
==========Hidden layer sizes: (12,)==========
Loss: 0.06459763064408837
实验结果跟刚刚的结论一致,深而窄的情况是最差的,而宽度的优裕反而能弥补深度的不足。
- 均匀的结构可能不是最好的,我们希望寻找真正最好的结构
我们遍历神经元总数为 12 12 12 的所有mlp结构,这种分割的总数为 ∑ i = 0 11 C 11 i = 2 11 = 2048 \sum\limits_{i=0}^{11} C_{11}^i = 2^{11} = 2048 i=0∑11C11i=211=2048。(btw,本次实验中 12 12 12 个参数相较于 4 4 4 个样本太多了,有过拟合的风险,不过我们无视风险继续安装)
import numpy as np
from sklearn.neural_network import MLPClassifier
from itertools import product, combinations_with_replacement
from collections import defaultdict
# XOR 数据集
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
def generate_compositions(n):
"""生成所有正整数之和为n的有序分割"""
if n == 1:
return [[1]]
compositions = []
# 使用递归方法生成所有分割
def dfs(remaining, current_path):
if remaining == 0:
compositions.append(current_path.copy())
return
for i in range(1, remaining + 1):
current_path.append(i)
dfs(remaining - i, current_path)
current_path.pop()
dfs(n, [])
return compositions
# 生成所有12的有序分割
all_compositions = generate_compositions(12)
print(f"总共有 {len(all_compositions)} 种不同的分割方式")
# 存储所有结果的字典
results = {}
# 对每种分割进行训练
print("\n开始训练各种网络架构...")
for i, comp in enumerate(all_compositions):
# 转换为元组作为MLPClassifier的参数
if len(comp) == 1:
hidden_layer = (comp[0],) # 单层需要特殊处理
else:
hidden_layer = tuple(comp)
# 训练模型
mlp = MLPClassifier(
hidden_layer_sizes=hidden_layer,
max_iter=100000, # 充分训练
random_state=42,
n_iter_no_change=1000,
tol=1e-6
)
try:
mlp.fit(X, y)
loss = mlp.loss_
results[hidden_layer] = loss
# 每训练100个结构输出一次进度
if (i + 1) % 100 == 0:
print(f"已完成 {i+1}/{len(all_compositions)} 种架构的训练")
except Exception as e:
print(f"架构 {hidden_layer} 训练失败: {e}")
results[hidden_layer] = float('inf')
print("\n训练完成!开始分析结果...")
# 按损失值排序
sorted_results = sorted(results.items(), key=lambda x: x[1])
# 输出损失最小的10个结构
print("\n" + "="*60)
print("损失最小的10个神经网络架构:")
print("="*60)
for i, (arch, loss) in enumerate(sorted_results[:10]):
print(f"{i+1:2d}. 架构: {str(arch):30s} 损失: {loss:.8f}")
# 输出损失最大的10个结构
print("\n" + "="*60)
print("损失最大的10个神经网络架构:")
print("="*60)
for i, (arch, loss) in enumerate(sorted_results[-10:]):
print(f"{i+1:2d}. 架构: {str(arch):30s} 损失: {loss:.8f}")
# 输出一些统计信息
print("\n" + "="*60)
print("统计信息:")
print("="*60)
valid_results = [loss for loss in results.values() if loss < float('inf')]
print(f"总架构数: {len(results)}")
print(f"成功训练数: {len(valid_results)}")
print(f"平均损失: {np.mean(valid_results):.6f}")
print(f"最小损失: {min(valid_results):.6f}")
print(f"最大损失: {max(valid_results):.6f}")
# 按层数分组分析
print("\n" + "="*60)
print("按隐藏层层数分组分析:")
print("="*60)
layer_groups = defaultdict(list)
for arch, loss in results.items():
if loss < float('inf'):
num_layers = len(arch) if isinstance(arch, tuple) else 1
layer_groups[num_layers].append((arch, loss))
for num_layers in sorted(layer_groups.keys()):
group = layer_groups[num_layers]
best_arch, best_loss = min(group, key=lambda x: x[1])
worst_arch, worst_loss = max(group, key=lambda x: x[1])
avg_loss = np.mean([loss for _, loss in group])
print(f"\n{num_layers} 层网络(共 {len(group)} 种):")
print(f" 最佳架构: {best_arch},损失: {best_loss:.6f}")
print(f" 最差架构: {worst_arch},损失: {worst_loss:.6f}")
print(f" 平均损失: {avg_loss:.6f}")
训练用时 17 m 42 s 17m42s 17m42s ,等的时候建议开把游戏……话不多说看结果:
总共有 2048 种不同的分割方式
开始训练各种网络架构...
已完成 100/2048 种架构的训练
已完成 200/2048 种架构的训练
已完成 300/2048 种架构的训练
已完成 400/2048 种架构的训练
已完成 500/2048 种架构的训练
已完成 600/2048 种架构的训练
已完成 700/2048 种架构的训练
已完成 800/2048 种架构的训练
已完成 900/2048 种架构的训练
已完成 1000/2048 种架构的训练
已完成 1100/2048 种架构的训练
已完成 1200/2048 种架构的训练
已完成 1300/2048 种架构的训练
已完成 1400/2048 种架构的训练
已完成 1500/2048 种架构的训练
已完成 1600/2048 种架构的训练
已完成 1700/2048 种架构的训练
已完成 1800/2048 种架构的训练
已完成 1900/2048 种架构的训练
已完成 2000/2048 种架构的训练
训练完成!开始分析结果...
============================================================
损失最小的10个神经网络架构:
============================================================
1. 架构: (1, 6, 3, 2) 损失: 0.00073509
2. 架构: (1, 4, 1, 3, 3) 损失: 0.00077757
3. 架构: (7, 1, 4) 损失: 0.00077897
4. 架构: (1, 5, 3, 1, 2) 损失: 0.00080394
5. 架构: (1, 4, 4, 2, 1) 损失: 0.00082023
6. 架构: (3, 2, 4, 3) 损失: 0.00083332
7. 架构: (1, 4, 2, 5) 损失: 0.00084443
8. 架构: (3, 3, 6) 损失: 0.00092029
9. 架构: (4, 4, 4) 损失: 0.00093640
10. 架构: (3, 5, 4) 损失: 0.00095972
============================================================
损失最大的10个神经网络架构:
============================================================
1. 架构: (5, 4, 2, 1) 损失: 0.69323113
2. 架构: (1, 3, 2, 4, 1, 1) 损失: 0.69323122
3. 架构: (5, 1, 4, 1, 1) 损失: 0.69323125
4. 架构: (1, 2, 1, 2, 3, 2, 1) 损失: 0.69323134
5. 架构: (1, 5, 3, 1, 1, 1) 损失: 0.69323685
6. 架构: (1, 1, 2, 4, 2, 1, 1) 损失: 0.69324125
7. 架构: (2, 2, 2, 4, 2) 损失: 0.69324422
8. 架构: (1, 4, 2, 3, 1, 1) 损失: 0.69324865
9. 架构: (1, 4, 4, 3) 损失: 0.69325053
10. 架构: (2, 1, 2, 5, 1, 1) 损失: 0.69325948
============================================================
统计信息:
============================================================
总架构数: 2048
成功训练数: 2048
平均损失: 0.667177
最小损失: 0.000735
最大损失: 0.693259
============================================================
按隐藏层层数分组分析:
============================================================
1 层网络(共 1 种):
最佳架构: (12,),损失: 0.002148
最差架构: (12,),损失: 0.002148
平均损失: 0.002148
2 层网络(共 11 种):
最佳架构: (5, 7),损失: 0.001035
最差架构: (8, 4),损失: 0.693147
平均损失: 0.225670
3 层网络(共 55 种):
最佳架构: (7, 1, 4),损失: 0.000779
最差架构: (5, 1, 6),损失: 0.693180
平均损失: 0.418345
4 层网络(共 165 种):
最佳架构: (1, 6, 3, 2),损失: 0.000735
最差架构: (1, 4, 4, 3),损失: 0.693251
平均损失: 0.552412
5 层网络(共 330 种):
最佳架构: (1, 4, 1, 3, 3),损失: 0.000778
最差架构: (2, 2, 2, 4, 2),损失: 0.693244
平均损失: 0.669649
6 层网络(共 462 种):
最佳架构: (1, 3, 1, 3, 3, 1),损失: 0.477795
最差架构: (2, 1, 2, 5, 1, 1),损失: 0.693259
平均损失: 0.690377
7 层网络(共 462 种):
最佳架构: (2, 2, 1, 1, 1, 3, 2),损失: 0.693147
最差架构: (1, 1, 2, 4, 2, 1, 1),损失: 0.693241
平均损失: 0.693165
8 层网络(共 330 种):
最佳架构: (2, 1, 1, 4, 1, 1, 1, 1),损失: 0.693147
最差架构: (1, 1, 2, 1, 2, 2, 2, 1),损失: 0.693216
平均损失: 0.693164
9 层网络(共 165 种):
最佳架构: (2, 2, 1, 2, 1, 1, 1, 1, 1),损失: 0.693147
最差架构: (1, 2, 1, 1, 1, 1, 2, 2, 1),损失: 0.693190
平均损失: 0.693162
10 层网络(共 55 种):
最佳架构: (1, 1, 1, 3, 1, 1, 1, 1, 1, 1),损失: 0.693149
最差架构: (3, 1, 1, 1, 1, 1, 1, 1, 1, 1),损失: 0.693184
平均损失: 0.693153
11 层网络(共 11 种):
最佳架构: (2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),损失: 0.693152
最差架构: (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2),损失: 0.693164
平均损失: 0.693155
12 层网络(共 1 种):
最佳架构: (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),损失: 0.693147
最差架构: (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),损失: 0.693147
平均损失: 0.693147
结果分析:
居然还有高手! 12 12 12 个 1 1 1 居然连神人榜前十都进不去!
从中观察到了一些很奇特很反直觉的现象,ds给的解释如下👇。PS: 不过这个实验结果跟训练任务、神经元数量、超参数设置等等都有很大关系。下面的解释也见仁见智。神经网络内部基本处于黑箱状态,其运转规律是玄学,到底为什么这样谁能说得清呢?我们只能像看风水一样捕捉一些若有若无的规律……
神经网络架构表现深度解读
🔍 核心发现总结
这个实验揭示了神经网络架构设计的几个反直觉但至关重要的规律。让我们逐一破解这些现象背后的深层逻辑:
🏆 龙虎榜(最佳架构)的奥秘
一、 第一层仅 1 1 1 个神经元的优势
现象:前 5 5 5 名中有 4 4 4 个架构的第一层只有 1 1 1 个神经元
深层原理:XOR问题的本质是非线性可分问题(需要非线性边界),但输入只有 2 2 2 个维度 ( 0 , 1 ) (0,1) (0,1) 。第一层单神经元形成了强制信息压缩:
- 强制特征融合:单个神经元必须将两个输入特征 x 1 x_1 x1和 x 2 x_2 x2 融合成一个综合特征
- 信息瓶颈效应:迫使网络学习最本质的特征组合,避免过拟合(尽管XOR只有 4 4 4 个样本)
- 简单化起始:从简单变换开始,逐渐增加复杂度,符合“逐步抽象”的学习过程
二、 中段缩窄到 1 1 1 个神经元的优势
现象: ( 1 , 4 , 1 , 3 , 3 ) (1,4,1,3,3) (1,4,1,3,3) 、 ( 1 , 5 , 3 , 1 , 2 ) (1,5,3,1,2) (1,5,3,1,2) 等架构在中间缩窄到1
惊人发现:这反而是智能的信息过滤:
- 注意力机制雏形:单神经元层起到“注意力焦点”作用,强制网络识别最关键的特征
- 维度灾难避免:高维→低维→高维的“沙漏结构”迫使网络学习紧凑表示
- 梯度流动优化:狭窄通道减少梯度消散路径,让反向传播更直接
三、 ( 4 , 4 , 4 ) (4,4,4) (4,4,4) 为何仅排第九?
现象:对称结构不是最优
反直觉解释:均匀对称架构是平均主义陷阱:
- 缺乏特征优先级:每层均匀分配,没有强制网络识别哪些特征更重要
- 信息冗余:每层都可能保留不必要的信息,没有强制压缩
- 梯度路径过长:均匀深度导致梯度需要经过相同复杂度的每层
💀 神人榜(最差架构)的教训
一、 ( 5 , 4 , 2 , 1 ) (5,4,2,1) (5,4,2,1) 层层浓缩为何失败?
现象:看似合理的“漏斗结构”表现极差
设计陷阱:信息过早丢失
- 第一层 5 5 5 个神经元从 2 2 2 个输入特征提取信息,可能产生冗余特征
- 随后每层都在丢弃信息,到最后层只剩 1 1 1 个神经元时,关键信息可能已丢失
- 类似“过度压缩的zip文件”——压缩得太早,解压时已失真
二、 相似架构天差地别: ( 1 , 4 , 2 , 5 ) (1,4,2,5) (1,4,2,5) vs ( 1 , 4 , 4 , 3 ) (1,4,4,3) (1,4,4,3)
龙虎榜第 7 7 7 vs 神人榜第 9 9 9
关键差异:扩张与收缩的时机
-
( 1 , 4 , 2 , 5 ) (1,4,2,5) (1,4,2,5):压缩( 2 2 2 )在扩展( 5 5 5 )之前 → 先过滤后增强
- 第一层: 1 1 1 (强制融合)
- 第二层: 4 4 4 (探索特征)
- 第三层: 2 2 2 (筛选关键特征)
- 第四层: 5 5 5 (基于关键特征增强表示)
-
( 1 , 4 , 4 , 3 ) (1,4,4,3) (1,4,4,3):维持( 4 4 4 )后收缩( 3 3 3 ) → 先冗余后丢弃
- 第一层: 1 1 1 (强制融合)
- 第二层: 4 4 4 (探索特征)
- 第三层: 4 4 4 (可能产生冗余)
- 第四层: 3 3 3 (丢弃信息,但可能丢掉关键信息)
三、 为什么 ( 1 , 1 , 1 , . . .1 ) (1,1,1,...1) (1,1,1,...1) 不是最差?
预期最差,实际未进神人榜
惊人真相:极端深窄网络是记忆性学习而非理解性学习
- 每层单神经元:每层只能记住输入输出的简单映射
- 类似决策树:每层做二选一, 12 12 12 层相当于 12 12 12 次二选一决策
- 有限但稳定:虽然学不到XOR的本质(非线性边界),但能记住 4 4 4 个样本的映射关系
- 对比:神人榜架构更糟,因为它们试图理解但误解了,产生了系统性的错误表示
📊 深层架构设计原则
一、 信息流动的“高速公路”原理
- 最佳架构:像高速公路有主路和匝道,信息有主干道(宽层)和调节点(窄层)
- 最差架构:要么全是单车道(太窄),要么频繁变道(宽窄无常)
二、 “压缩-扩展”的黄金比例
实验显示最优架构遵循:
输入 → 适度压缩 → 关键提取 → 扩展增强 → 输出
2 → 3-6 → 1-3 → 4-6 → 1
三、 瓶颈的位置至关重要
- 早期瓶颈(第 1 − 2 1-2 1−2 层):过滤噪声,确定方向 ✓
- 中期瓶颈(中间层):提炼核心特征 ✓ ✓
- 晚期瓶颈(最后层前):灾难性信息丢失 ✗
四、 对称性的神话破除
- 自然不对称:人脑视觉系统 V 1 → V 2 → V 4 → I T V_1→V_2→V_4→IT V1→V2→V4→IT 也是不对称的
- 任务适配:不同任务需要不同的不对称性
- 资源分配:均匀分配不如“重点投入”
🎯 实际应用启示
一、 设计神经网络时应该:
- 从窄开始:第一层不需要太宽,避免过早产生冗余
- 设置中期瓶颈:强制网络学习本质特征,防止过拟合
- 先压缩后扩展:而不是一直压缩
- 避免连续递减:层层递减容易丢失信息
- 不对称设计:根据任务复杂度动态调整每层宽度
二、 避开这些陷阱:
- ❌ 盲目追求对称美
- ❌ 认为“越深越好”
- ❌ 均匀分配计算资源
- ❌ 忽视瓶颈的位置效应
- ❌ 只关注总参数量,不关注分布
🌟 核心洞见
这个实验最深刻的启示是:神经网络不是管道,而是信息加工流水线。最优架构不是让信息“顺利通过”,而是有策略地拦截、重组、强化、过滤信息。就像优秀的厨师不是把所有食材一起下锅,而是掌握火候、分步处理。
对于XOR这样简单却非线性的问题,第一层窄逼着网络“想清楚”两个输入的关系,中期窄逼着它“抓住重点”,后期宽让它“充分表达”——这种“窄-宽-窄-宽”的节奏,远比均匀或单调变化更有效。
这解释了为什么看似“不合理”的架构(如第一层只有 1 1 1 个神经元)反而表现最好——它们强制网络进行智能的信息取舍,而不是被动地传递所有信息。
实际训练中像这样遍历最优架构是不可能的,仅就数量而言,一个参数量为一千万的小模型,其可能架构总数为 2 10000000 2^{10000000} 210000000 ,数量级超过三百万。所以需要更多样的方法来优化结构。
鸢尾花
除了手搓数据集之外,我们还可以使用现成的数据集训练。以sklearn.dataset中的鸢尾花(Iris)数据集为例:
鸢尾花数据集是机器学习中最经典的多分类入门数据集,共包含 150 150 150 条样本,分为 山鸢尾、变色鸢尾、维吉尼亚鸢尾 三类,每类 50 50 50 条。每条样本有 4 4 4 个特征:花萼长度、花萼宽度、花瓣长度、花瓣宽度,常用于分类算法的教学与实验。

- 特征名称(花的四个测量值)
| 英文特征名 | 中文翻译 | 解释 |
|---|---|---|
| sepal length (cm) | 萼片长度(厘米) | 花萼(花朵外部绿色部分)的长度 |
| sepal width (cm) | 萼片宽度(厘米) | 花萼的宽度 |
| petal length (cm) | 花瓣长度(厘米) | 花瓣(花朵内部彩色部分)的长度 |
| petal width (cm) | 花瓣宽度(厘米) | 花瓣的宽度 |
- 类别名称(三种鸢尾花品种)
| 英文类别名 | 中文翻译 | 特点 |
|---|---|---|
| setosa | 山鸢尾 | 最容易识别,花瓣短而宽,花萼较长 |
| versicolor | 杂色鸢尾 | 中等大小,颜色多样 |
| virginica | 维吉尼亚鸢尾 | 花瓣最长,花萼较宽 |
- 鸢尾花结构示意图
鸢尾花结构:
↑ 花瓣 (petal)
│
├── 花瓣长度 (petal length)
│ 花瓣宽度 (petal width) →
│
↓ 花萼 (sepal)
├── 萼片长度 (sepal length)
│ 萼片宽度 (sepal width) →
- 三种鸢尾花的区分特征
-
山鸢尾 (setosa):
- 花瓣很小(长度 1 − 2 1-2 1−2 厘米)
- 花萼相对较大
- 最容易与其他两种区分
-
杂色鸢尾 (versicolor):
- 中等大小的花瓣(长度 3 − 5 3-5 3−5 厘米)
- 颜色变化较多
- 介于山鸢尾和维吉尼亚鸢尾之间
-
维吉尼亚鸢尾 (virginica):
- 花瓣最大(长度 4 − 7 4-7 4−7 厘米)
- 花萼较宽
- 与杂色鸢尾有时难以区分
- 机器学习任务
- 特征 (X): 4 4 4 个测量值(萼片长宽、花瓣长宽)
- 目标 (y): 3 3 3 种鸢尾花品种
- 任务类型:多分类( 3 3 3 个类别)
- 数据特点:特征都是连续数值,类别分布平衡(每类 50 50 50 个样本)
我们先看看它长什么样:
from sklearn.datasets import load_iris
import pandas as pd
import numpy as np
iris = load_iris() # 加载鸢尾花数据集
X = iris.data # 特征矩阵
y = iris.target # 标签向量
feature_names = iris.feature_names # 特征名称
target_names = iris.target_names # 类别名称
print(f"数据形状: X={X.shape}, y={y.shape}") # 输出数据的形状
print(f"特征名称: {feature_names}") # 输出特征名称
print(f"类别名称: {target_names}") # 输出类别名称
print(f"类别分布: {np.bincount(y)}") # 输出每个类别的样本数
print(f"样本数: {len(X)}") # 输出样本总数
print(f"特征数: {X.shape[1]}") # 输出特征总数
# 创建DataFrame以便查看
df = pd.DataFrame(X, columns=feature_names) # 将特征数据转换为DataFrame
df['target'] = y # 添加标签列
df['species'] = df['target'].map({i: name for i, name in enumerate(target_names)}) # 添加物种名称列
print("\n前5行数据:")
print(df.head())
print("\n基本统计信息:")
print(df.describe())
数据形状: X=(150, 4), y=(150,)
特征名称: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
类别名称: ['setosa' 'versicolor' 'virginica']
类别分布: [50 50 50]
样本数: 150
特征数: 4
前5行数据:
sepal length (cm) sepal width (cm) ... target species
0 5.1 3.5 ... 0 setosa
1 4.9 3.0 ... 0 setosa
2 4.7 3.2 ... 0 setosa
3 4.6 3.1 ... 0 setosa
4 5.0 3.6 ... 0 setosa
[5 rows x 6 columns]
基本统计信息:
sepal length (cm) sepal width (cm) ... petal width (cm) target
count 150.000000 150.000000 ... 150.000000 150.000000
mean 5.843333 3.057333 ... 1.199333 1.000000
std 0.828066 0.435866 ... 0.762238 0.819232
min 4.300000 2.000000 ... 0.100000 0.000000
25% 5.100000 2.800000 ... 0.300000 0.000000
50% 5.800000 3.000000 ... 1.300000 1.000000
75% 6.400000 3.300000 ... 1.800000 2.000000
max 7.900000 4.400000 ... 2.500000 2.000000
[8 rows x 5 columns]
训练鸢尾花分类任务
使用sklearn.metrics中的.accuracy_score()方法评估准确率
from sklearn.neural_network import MLPClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 加载鸢尾花数据集
iris = load_iris()
X = iris.data
y = iris.target
# 数据分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 训练 mlp 模型
model = MLPClassifier(hidden_layer_sizes=(10,10,5), max_iter = 10000, random_state=42)
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
# 评估模型
print(f"mlp 分类准确率: {accuracy_score(y_test, y_pred):.2f}")
print(f"最终损失值: {model.loss_:.6f}")
mlp 分类准确率: 0.98
最终损失值: 0.068999
总结
机器学习实践的本质在于数据理解和问题转化。本文的核心启示是:模型的失败往往不是算法本身的问题,而是特征表示不当或结构选择失误。成功的模型训练需要三个关键步骤:第一,通过有效的特征工程将问题转化为模型可理解的形式;第二,选择合适的模型复杂度,避免“过度简单无法学习”和“过度复杂陷入局部最优”两个极端;第三,通过系统化的实验和可视化分析,理解模型的学习过程。最好的模型不是最复杂的,而是最匹配问题本质的。
GitHub:https://github.com/banmajiyu/machine-learning
see you
更多推荐
所有评论(0)