「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。
逻辑回归
相信很多读者都听说过线性回归,线性回归是一个很常用的机器学习算法。我们通常使用线性回归来解决预测问题。像房价预测、股票预测等。这些例子的输出都是一个连续值,我们也把这类问题称作回归问题。
而机器学习另一个研究的问题就是分类问题了,这类问题的输出结果是离散的。像我们动物分类的问题,我们输出的结果是一种确定的动物,猫或者狗,而不会是一个介于猫狗之间的动物。
那逻辑回归又是什么呢?逻辑回归就是在线性回归的基础上,使用一个激活函数,对线性回归的结果进行二次处理,从而解决分类问题的一种算法。
我们可以看下面这个例子。
假如我们有一个模型用于计算学生的成绩:
其中、、 为 0.3、0.4、0.3,而、、 分别为输入的语文、数学、英语成绩。b 则是另外的加分。
假设额外加分为 0,我们就可以把式子写成:
我们可以尝试用在这个模型求一组数据:
[100, 100, 100]
[90, 50, 80]
输出结果分别是:
100
52
现在我们可以使用一个激活函数,对上面的结果进行处理。我们假设激活函数为:
在实际应用中我们都会选择使用 ReLU 和 Tanh 函数作为激活函数,这里只是为了举例子。
上面的函数可以将大于 60 的数字变成 1,将小于 60 的数字变成 -1,这样我们就把连续的结果映射成了离散的结果。
注意:上面的激活函数在 x=60 处没有定义,这是只作为学习例子,在实际使用中不可取。
因为激活函数的输入是回归方程的输出,因此我们可以把函数写成:
这样我们就用回归的方式解决了分类问题。
神经网络
神经网络也是一种机器学习的算法,它的思想就是将多个逻辑回归函数排列在一层,给每一个节点初始化一组参数。
上面是单个节点,其中 X 为输入的数据,W 为我们的参数(这里省去了 b)然后经过激活函数,输出 -1。其实上面就是一个简单的逻辑回归,我们再添加几个节点:
我们还可以继续添加节点数,构造一个更加复杂的函数。这就是我们所说的神经网络。而如果我们再宽度(添加节点)的基础上,再添加其它层的节点,就是我们深度学习的由来了。下图就是一个全连接前馈神经网络的图。
图片摘自李宏毅《机器学习》课程 PPT
神经网络在解释性上比较弱,我们很难知道每个节点起了什么作用,但是在效果上神经网络非常出色。
特征提取
下面我们来使用神经网络来实现一个手写体数字识别的问题。首先我们需要有手写体数字的数据集,数据集可以在下面下载:
提取码:wqas
需要先读取所有的图片,然后提取图片的特征,即像素信息。我们需要安装几个模块:
pip install opencv-python
pip install numpy
pip install scikit-learn
然后我们使用 OpenCV 读取图片数据:
import os
import re
import cv2
import random
import numpy as np
# 1、特征提取
def get_feature(img_path, img=None):
"""
提取特征
"""
if img_path:
img = cv2.imread(img_path, 0)
# 对图片进行阈值处理,将小于 200 的像素点处理为黑色
# 将图片转换成一维
img = img.reshape((img.size, ))
return img
images = []
def get_features(imgs_path):
global images
# 遍历数据集
for root, dirs, files in os.walk(r'C:\Users\Administrator\Desktop\dataset'):
for file in files:
current_image_path = os.path.join(root, file)
# 获取所有图片路径
images.append(current_image_path)
# 创建一个可以装所有数据的多维数组
feature_size = get_feature(images[0]).size
dataset = np.ones((len(images), feature_size+1), dtype=np.uint8)
for index in range(len(images)):
# 提取目标值
target = re.findall(r"dataset\\(.*?)\\", images[index])
feature = get_feature(images[index])
# 将特征值和目标值合并
data = np.hstack((feature, target))
# 合并所有数据集
dataset[index, :] = data
return dataset
dataset = get_features(None)
其中 get_feature 函数的作用是获取单张图片的特征,并将特征转换成一维的形式。这样我们才能将它作为参数放入神经网络中。但是此时我们还少了一个东西,就是目标值。
我们监督学习的数据通常需要特征和目标两个部分,而在数据集中,我们为每个数字的图片设置了一个目录。0 目录中全是数字 0,1 目录中全是数字 1。因此我们可以为每个数据添加标签。
在 get_features 函数中读取了数据集中所有图片,并为每个数字添加标签。最后我们得到了一个新的 ndarray 数组,这个数组我们就可以放入神经网络中进行训练。现在特征提取了,我们可以开始训练模型了。
训练模型
我们这里使用的是机器学习模块 scikit-learn,里面集成了神经网络的实现。我们先来创建一个模型:
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
# 1、数据集拆分
X_train, X_test, y_trian, y_test = train_test_split(dataset[:, :-1], dataset[:, -1:], test_size=0.25, random_state=21)
# 2、训练神经网络
mlp = MLPClassifier(solver='lbfgs', random_state=1, hidden_layer_sizes=[700, 700, 700, 700])
mlp.fit(X_train, y_trian)
我们使用 train_test_split 将数据集拆分成训练集和测试集,这样我们就能验证模型的好坏。
然后使用 MLPClassifier 类创建一个神经网络,我们只需要关注 hidden_layer_sizes 参数即可,这个参数就是我们各层网络的节点个数。这里我使用了一个非常大的网络,然后调用 fit 方法填充数据并训练,这样我们就训练好了一个模型。
我们可以调用 score 方法对模型进行评估:
# 3、模型评估
score = mlp.score(X_test, y_test)
print(0.9902985074626866)
输出结果如下:
0.9902985074626866
可以看到模型的准确率达到了 99%。但是实际应用中,会发现并没有这么理想。我们来测试一下。
手写体识别
我们使用 OpenCV 写一个写字板,然后再来进行测试。
import random
img = np.ones((100, 100), dtype=np.uint8)*255
drawing = False
def draw(event, x, y, flags, param):
global drawing
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
if drawing and event == cv2.EVENT_MOUSEMOVE:
img[y][x] = 0 + random.randint(0, 20)
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw)
while True:
cv2.imshow('image', img)
key = cv2.waitKey(1)
if key == ord('q'):
kernel = np.ones((5, 5), np.uint8)
img = cv2.erode(img, kernel)
img = cv2.resize(img, (28, 28))
cv2.imwrite('1.jpg', img)
break
cv2.destroyAllWindows()
feature = get_feature('1.jpg')
print(feature.shape)
y_pre = mlp.predict(np.array([feature]))
plt.imshow(img)
plt.show()
print("预测结果为", y_pre)
上面我们用 OpenCV 创建了一个写字板,我们只需要在上面写一个数字,然后按 q 键即可获得图片的特征数据。
我们调用 mlp.predict 来对数字进行预测,在实际测试中发现准确率要远远低于 99%。大家可以在自己的电脑上尝试一下。