使用机器学习实现奇偶分类
由于后续主要使用机器学习进行流量识别、指纹识别,因此这里主要聚焦于分类问题。
假定背景:对任意给定的数根据奇偶性进行分类。
步骤
大部分情况下,使用机器学习进行分类工作,主要包含以下步骤:
- 收集数据
- 读入数据
- 预处理数据(清除无效数据、数据类型调整、选取特征值)
- 将数据分为训练集和测试集
- 使用特定参数的模型对训练集进行训练
- 使用测试集对训练好的模型进行测试
- 调整特征、参数,重复上述工作
实验过程
第一次实验
由于这里只是对整数进行奇偶性判断,因此数据使用生成的随机数即可
import pandas as pd import random def g(): i = random.randint(0,999999) return [i, i%2] data = pd.DataFrame([g() for i in range(100000)])
上述代码将会生成长度为 100000 的列表,并且每个元素都包含随机数与其奇偶性(0 为偶数,1 为奇数)
列表将会被转换成 pandas 的DataFrame
类型,以便于后续处理
在 Jupyter Notebook 中,可以使用 data.head()
对数据进行预览(如果不填写参数,默认只显示前 5 个)
0 | 1 | |
---|---|---|
0 | 319255 | 1 |
1 | 430655 | 1 |
2 | 286442 | 0 |
3 | 709373 | 1 |
4 | 589398 | 0 |
这里不存在缺失值,因此暂时不做其他额外处理,只将特征和标签分离即可。
对于pandas.DataFrame
,只需要使用中括号即可直接取一列数据。在机器学习中,特征使用X
(大写)表示,每一行是一组数据,可能包含多列特征值;标签使用y
表示,行数和特征行数相同,表示该数据对应的类型。
在这里,特征和标签都只有一列,分别使用x
和y
表示。由于前面提到,特征往往存在多列,因此后续模型遍历也会认为 x 是一个二维数组。对于一列的数据,在后续操作中无法直接对其训练。在这里使用 numpy 的reshape()
来将一维数组转换为二维数组,以便于后续操作。
x = data[0].to_numpy().reshape(-1, 1) y = data[1]
如果使用 Jupyter Notebook,可以使用单独的两个块输出下x
和y
的值进行再次确认。
接下来则是将数据分为训练集和测试集,使用 sklearn 的train_test_split
可以按照要求分割训练集和测试集。通过设置test_size=0.3
来将 30% 的数据用于测试,70% 的数据用于训练。同时这里的分割将会乱序分割,以避免数据顺序导致模型训练出现偏差。
from sklearn.model_selection import train_test_split x_train, x_test, y_train, y_test = train_test_split(x,y, test_size=0.3)
模型拟选用 K-近邻 与 随机森林 两种。这里仅使用相应算法,不需要考虑具体实现,当作黑盒使用即可。
首先是 K-近邻 分类器(KNeighborsClassifier)
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier() knn.fit(x_train, y_train) knn.score(x_test, y_test)
接下来是 随机森林 分类器(RandomForestClassifier)
from sklearn.ensemble import RandomForestClassifier rfc = RandomForestClassifier() rfc.fit(x_train,y_train) rfc.score(x_test,y_test)
可以发现两个算法只有大概 50% 准确率,约等于瞎猜。
以 K-近邻 为例,其算法实际上基于特征之间的距离。将多维度特征映射至高维空间,将距离较近的数据分类到一起。而显然对于一维数轴,奇偶性和距离没有任何关系。因此对于这些训练器几乎无法得到一个较好的结果。
那么如何对其进行分类呢?
第二次实验
如果采用人脑思维进行奇偶性判断,我们实际上只需要注重于最后一位的奇偶性,或者说二进制下的最后一位就是奇偶性。那么在特征提取中,应该尝试将每位的数提取出来作为特征。而非整体作为特征。
def g(): i = random.randint(0,999999) return list(map(int, list("%010d"%i))) + [i%2]
对上面的g()
进行修改,先使用格式化输出将高位用 0 补全到 10 位(必须保证所有数特征个数相同),然后将其分割成单个数字并整合为整数特征。
如下,是新的数据集(最后一列为标签)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 8 | 5 | 1 | 1 | 9 | 1 | 1 |
1 | 0 | 0 | 0 | 0 | 8 | 5 | 3 | 9 | 1 | 1 | 1 |
2 | 0 | 0 | 0 | 0 | 6 | 0 | 9 | 2 | 6 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 4 | 7 | 8 | 7 | 2 | 8 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 | 8 | 1 | 2 | 5 | 1 |
由于这次特征不是单维,因此只需要将前 10 列提取即可
x = data.iloc[:,:10] y = data[10]
后续代码无需更改,再次执行,即可发现 K-近邻 准确率达到 75% 左右,随机森林 准确率达到 99%。
那么这就完成了么?从原理上来说,K-近邻 在解决该问题上确实存在缺陷,但是准确率达到 99% 的随机森林应该是可以做到 100% 的。
尝试修改模型参数
rfc = RandomForestClassifier( n_estimators=81, max_features=6, oob_score=True, random_state=10, )
再次执行即可得到 100% 的准确率。
简单总结
对于更为复杂的数据和场景,将会需要更为精妙的方式来提取特征,甚至需要对特征的不同项进行加减操作。同时对于某些模型,要求对数据进行归一化处理(数据除以该列最大值,得到一个 0~1 的浮点数)
看似机器学习可以是由机器进行分类,但实际上除去打标签部分需要手动操作,如何提取特征也需要人进行参与。对特征进行提取需要需要对数据具有深刻理解,甚至在某些情况下与自己手动分类所花费的工作量差别并不太大。
完整代码
# To add a new cell, type '# %%' # To add a new markdown cell, type '# %% [markdown]' # %% [markdown] # # 奇偶性判断 # # 使用随机数生成数据,并判断数据奇偶性 # %% [markdown] # ## 读入数据 # # %% import pandas as pd import random def g(): i = random.randint(0,999999) # return [i, i%2] return list(map(int, list("%010d"%i))) + [i%2] data = pd.DataFrame([g() for i in range(100000)]) # %% data.head() # %% [markdown] # ## 预处理数据 # # 将特征和标签分离 # %% # x = data[0].to_numpy().reshape(-1, 1) # y = data[1] x = data.iloc[:,:10] y = data[10] # %% x # %% [markdown] # ## 分离训练集和测试集 # # 按照 7:3 的比例分割 # %% from sklearn.model_selection import train_test_split x_train, x_test, y_train, y_test = train_test_split(x,y, test_size=0.3) print(x_train) # %% [markdown] # ## 训练并测试模型 # # 分别使用 K-近邻 和 随机森林 算法 # %% from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier() knn.fit(x_train, y_train) knn.score(x_test, y_test) # %% from sklearn.ensemble import RandomForestClassifier rfc = RandomForestClassifier(n_estimators=81,max_features=6, oob_score=True,random_state=10) rfc.fit(x_train,y_train) rfc.score(x_test,y_test)