Python 机器学习秘籍第二版(五)
原文:
annas-archive.org/md5/1a114450f966ee5154c07d3ee2c9ce43译者:飞龙
第十一章:生物识别人脸识别
在本章中,我们将涵盖以下食谱:
-
从网络摄像头捕获和处理视频
-
使用 Haar 级联构建人脸检测器
-
构建眼鼻检测器
-
执行主成分分析
-
执行核主成分分析
-
执行盲源分离
-
使用局部二值模式直方图构建人脸识别器
-
使用基于 HOG 的模型识别人脸
-
面部特征点识别
-
通过人脸识别进行用户身份验证
技术要求
为了处理本章中的食谱,你需要以下文件(可在 GitHub 上找到):
-
video_capture.py -
face_detector.py -
eye_nose_detector.py -
pca.py -
kpca.py -
blind_source_separation.py -
mixture_of_signals.txt -
face_recognizer.py -
FaceRecognition.py -
FaceLandmarks.py -
UserAuthentification.py
简介
人脸识别指的是在给定图像中识别一个人的任务。这与人脸检测不同,人脸检测是在给定图像中定位人脸。在人脸检测过程中,我们不在乎这个人是谁;我们只是识别包含人脸的图像区域。因此,在典型的生物识别人脸识别系统中,在识别之前,我们需要确定人脸的位置。
人脸识别对人类来说非常容易。我们似乎毫不费力地就能做到,而且我们一直在做!我们如何让机器做到同样的事情呢?我们需要了解我们可以用哪些面部特征来唯一地识别一个人。我们的大脑似乎有一种内部结构,能够对特定的特征做出反应,例如边缘、角点、运动等。人类视觉皮层将这些特征组合成一个单一的连贯推理。如果我们想让我们的机器以准确率识别人脸,我们需要以类似的方式制定问题。我们需要从输入图像中提取特征,并将它们转换为有意义的表示。
从网络摄像头捕获和处理视频
网络摄像头当然不是一项创新技术;它们的出现可以追溯到 20 世纪 90 年代初,从那时起,由于视频聊天程序、街景摄像头和宽带互联网连接的普及,它们越来越受欢迎。目前,网络摄像头是每个人的对象,并且几乎总是集成在显示器框架中,如台式机、上网本和笔记本电脑。网络摄像头最常用的用途是传输视频流和录制。
在第一种情况下,网络摄像头用于视频聊天程序、电视广播和街景摄像头——这些摄像头拍摄特定位置的固定点。在第二种情况下,网络摄像头用于创建可以上传到互联网的照片和视频,例如 YouTube 或社交网站。这些类型网络摄像头的优点是,它们可以取代更传统的相机使用方式,即使它们的视频质量较差。
准备工作
在这个菜谱中,我们将使用网络摄像头来捕获视频数据。让我们看看如何使用 OpenCV-Python 从网络摄像头捕获视频片段。
如何做到这一点...
让我们看看如何按照以下步骤捕获和处理网络摄像头的视频:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
video_capture.py文件中给出):
import cv2
- OpenCV 提供了一个可以用来从网络摄像头捕获图像的视频捕获对象。
0输入参数指定了网络摄像头的 ID。如果你连接了一个 USB 摄像头,那么它将有一个不同的 ID:
# Initialize video capture object
cap = cv2.VideoCapture(0)
- 定义使用网络摄像头捕获的帧的缩放因子。
scaling_factor = 0.5
- 启动一个无限循环并持续捕获帧,直到你按下Esc键。从网络摄像头读取帧:
# Loop until you hit the Esc key
while True:
# Capture the current frame
ret, frame = cap.read()
- 调整帧大小是可选的,但在你的代码中仍然是一个有用的功能:
frame = cv2.resize(frame, None, fx=scaling_factor, fy=scaling_factor,
interpolation=cv2.INTER_AREA)
- 显示帧:
cv2.imshow('Webcam', frame)
- 在捕获下一帧之前等待 1 毫秒:
c = cv2.waitKey(1)
if c == 27:
break
- 释放视频捕获对象:
cap.release()
- 在退出代码之前关闭所有活动窗口:
cv2.destroyAllWindows()
如果你运行此代码,你将看到网络摄像头的视频。
它是如何工作的...
在这个菜谱中,我们使用了网络摄像头通过 OpenCV-Python 捕获视频数据。为此,执行了以下操作:
-
初始化视频捕获对象。
-
定义图像大小缩放因子。
-
循环直到你按下Esc键:
-
捕获当前帧。
-
调整帧大小。
-
显示图像。
-
检测是否按下了Esc键。
-
-
释放视频捕获对象。
-
关闭所有活动窗口。
更多内容...
OpenCV 提供了一个非常简单的接口来捕获网络摄像头的实时流。要捕获视频,你需要创建一个VideoCapture对象。它的参数可以是设备索引或视频文件的名称。然后我们可以逐帧获取它们。然而,我们不要忘记释放捕获。
参见
使用 Haar 级联构建人脸检测器
正如我们之前讨论的,人脸检测是确定输入图像中人脸位置的过程。在这个菜谱中,我们将使用Haar 级联进行人脸检测。这是通过在多个尺度上从图像中提取许多简单特征来实现的。这些简单特征是边缘、线和矩形特征,它们很容易计算。然后通过创建简单分类器的级联来训练它们。
准备工作
在这个菜谱中,我们将学习如何确定由我们的网络摄像头捕获的视频帧中人脸的位置。这个过程使用了自适应增强技术来提高其鲁棒性。
如何做到这一点...
让我们看看如何使用 Haar 级联构建人脸检测器:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
face_detector.py文件中给出):
import cv2
import numpy as np
- 加载人脸检测器级联文件。这是一个我们可以用作检测器的训练模型:
face_cascade = cv2.CascadeClassifier('cascade_files/haarcascade_frontalface_alt.xml')
- 检查级联文件是否正确加载:
if face_cascade.empty():
raise IOError('Unable to load the face cascade classifier xml file')
- 创建视频捕获对象:
cap = cv2.VideoCapture(0)
- 定义图像下采样的缩放因子:
scaling_factor = 0.5
- 继续循环,直到按下Esc键:
# Loop until you hit the Esc key
while True:
# Capture the current frame and resize it
ret, frame = cap.read()
- 调整帧大小:
frame = cv2.resize(frame, None, fx=scaling_factor, fy=scaling_factor,
interpolation=cv2.INTER_AREA)
- 将图像转换为灰度图。我们需要灰度图像来运行人脸检测器:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
- 在灰度图像上运行人脸检测器。
1.3参数指的是每个阶段的缩放乘数。5参数指的是每个候选矩形应该具有的最小邻居数,以便我们保留它。这个候选矩形基本上是一个可能检测到人脸的潜在区域:
face_rects = face_cascade.detectMultiScale(gray, 1.3, 5)
- 在每个检测到的人脸区域周围绘制矩形:
for (x,y,w,h) in face_rects:
cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 3)
- 显示输出图像:
cv2.imshow('Face Detector', frame)
- 在进行下一次迭代之前等待 1 毫秒。如果用户按下 Esc 键,则跳出循环:
c = cv2.waitKey(1)
if c == 27:
break
- 在退出代码之前释放和销毁对象:
cap.release()
cv2.destroyAllWindows()
如果你运行此代码,你将在摄像头视频中看到被检测到的人脸。
它是如何工作的...
在这个配方中,我们学习了如何确定由摄像头捕获的视频帧中的人脸位置。为此,执行了以下操作:
-
加载人脸级联文件。
-
检查人脸级联文件是否已加载。
-
初始化视频捕获对象。
-
定义缩放因子。
-
循环直到按下Esc键:
-
捕获当前帧并调整其大小。
-
转换为灰度图。
-
在灰度图像上运行人脸检测器。
-
在图像上绘制矩形。
-
显示图像。
-
检查是否按下了Esc键。
-
-
释放视频捕获对象并关闭所有窗口。
还有更多...
Haar 级联是一种基于机器学习的方法,其中通过许多正负图像训练级联函数。然后,它被用来检测其他图像中的对象。
参见
-
使用 Haar 级联进行人脸检测:
docs.opencv.org/3.1.0/d7/d8b/tutorial_py_face_detection.html#gsc.tab=0 -
使用简单特征的增强级联快速对象检测:
www.cs.cmu.edu/~efros/courses/LBMV07/Papers/viola-cvpr-01.pdf
构建眼睛和鼻子检测器
在之前的配方中,使用 Haar 级联构建人脸检测器,我们使用了 Haar 级联方法来检测由摄像头捕获的视频帧中的人脸位置。这种方法可以扩展到检测所有类型的对象。这正是我们将在这里讨论的内容。
准备工作
在这个配方中,我们将看到如何使用 Haar 级联方法来检测输入视频中的人脸的眼睛和鼻子。
如何做到这一点...
让我们看看我们如何构建眼睛和鼻子检测器:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
eye_nose_detector.py文件中给出):
import cv2
import numpy as np
- 加载人脸、眼睛和鼻子级联文件:
# Load face, eye, and nose cascade files
face_cascade = cv2.CascadeClassifier('cascade_files/haarcascade_frontalface_alt.xml')
eye_cascade = cv2.CascadeClassifier('cascade_files/haarcascade_eye.xml')
nose_cascade = cv2.CascadeClassifier('cascade_files/haarcascade_mcs_nose.xml')
- 检查文件是否正确加载:
# Check if face cascade file has been loaded
if face_cascade.empty():
raise IOError('Unable to load the face cascade classifier xml file')
# Check if eye cascade file has been loaded
if eye_cascade.empty():
raise IOError('Unable to load the eye cascade classifier xml file')
# Check if nose cascade file has been loaded
if nose_cascade.empty():
raise IOError('Unable to load the nose cascade classifier xml file')
- 初始化视频捕获对象:
# Initialize video capture object and define scaling factor
cap = cv2.VideoCapture(0)
- 定义缩放因子:
scaling_factor = 0.5
- 保持循环,直到用户按下Esc键:
while True:
# Read current frame, resize it, and convert it to grayscale
ret, frame = cap.read()
- 调整帧大小:
frame = cv2.resize(frame, None, fx=scaling_factor, fy=scaling_factor,
interpolation=cv2.INTER_AREA)
- 将图像转换为灰度:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
- 在灰度图像上运行人脸检测器:
# Run face detector on the grayscale image
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
- 由于我们知道面孔总是有眼睛和鼻子,我们只能在面部区域运行这些检测器:
# Run eye and nose detectors within each face rectangle
for (x,y,w,h) in faces:
- 提取面部 ROI:
# Grab the current ROI in both color and grayscale images
roi_gray = gray[y:y+h, x:x+w]
roi_color = frame[y:y+h, x:x+w]
- 运行眼睛检测器:
# Run eye detector in the grayscale ROI
eye_rects = eye_cascade.detectMultiScale(roi_gray)
- 运行鼻子检测器:
# Run nose detector in the grayscale ROI
nose_rects = nose_cascade.detectMultiScale(roi_gray, 1.3, 5)
- 在眼睛周围画圆圈:
# Draw green circles around the eyes
for (x_eye, y_eye, w_eye, h_eye) in eye_rects:
center = (int(x_eye + 0.5*w_eye), int(y_eye + 0.5*h_eye))
radius = int(0.3 * (w_eye + h_eye))
color = (0, 255, 0)
thickness = 3
cv2.circle(roi_color, center, radius, color, thickness)
- 在鼻子上画一个矩形:
for (x_nose, y_nose, w_nose, h_nose) in nose_rects:
cv2.rectangle(roi_color, (x_nose, y_nose), (x_nose+w_nose,
y_nose+h_nose), (0,255,0), 3)
break
- 显示图像:
# Display the image
cv2.imshow('Eye and nose detector', frame)
- 在进行下一次迭代之前等待 1 毫秒。如果用户按下Esc键,则中断循环:
# Check if Esc key has been pressed
c = cv2.waitKey(1)
if c == 27:
break
- 在退出代码之前释放和销毁对象:
# Release video capture object and close all windows
cap.release()
cv2.destroyAllWindows()
如果你运行此代码,你将看到网络摄像头视频中检测到的人的眼睛和鼻子。
工作原理…
在这个菜谱中,我们学习了如何在输入视频中检测人的眼睛和鼻子。为此,已经执行了以下操作:
-
加载面部、眼睛和鼻子级联文件。
-
检查面部、眼睛和鼻子级联文件是否已加载。
-
初始化视频捕获对象并定义缩放因子。
-
在帧上循环:
-
读取当前帧,调整大小,并将其转换为灰度。
-
在灰度图像上运行人脸检测器。
-
在每个面部矩形内运行眼睛和鼻子检测器。
-
显示图像。
-
检查是否按下了Esc键。
-
-
释放视频捕获对象并关闭所有窗口。
更多内容…
在网络摄像头中识别面部元素对于识别主题可能很有用。全局视觉信息和局部特征(眼睛和鼻子的形态)在面部感知和识别中是基本的。事实上,关于面部识别的研究表明,男性更容易识别具有突出特征的面孔,如鹰钩鼻、眯眼等。
相关内容
- 分类案例研究 - Viola-Jones 人脸检测器:
www.cse.psu.edu/~rtc12/CSE586/lectures/violaJonesDetector.pdf
执行主成分分析
主成分分析(PCA)是一种常用于计算机视觉和机器学习的降维技术。当我们处理具有高维度的特征时,训练机器学习系统变得过于昂贵。因此,在训练系统之前,我们需要降低数据的维度。然而,当我们降低维度时,我们不想丢失数据中存在的信息。这就是 PCA 发挥作用的地方!PCA 识别数据的重要成分,并按重要性顺序排列。
准备工作
在这个菜谱中,我们将看到如何对输入数据进行主成分分析(PCA)。
如何操作…
让我们看看如何对一些输入数据进行 PCA:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
pca.py文件中给出):
import numpy as np
from sklearn import decomposition
- 让我们为我们的输入数据定义五个维度。前两个维度将是独立的,但接下来的三个维度将依赖于前两个维度。这基本上意味着我们可以没有最后三个维度,因为它们不会给我们提供任何新的信息:
# Define individual features
x1 = np.random.normal(size=250)
x2 = np.random.normal(size=250)
x3 = 3*x1 + 2*x2
x4 = 6*x1 - 2*x2
x5 = 3*x3 + x4
- 让我们创建具有这些特征的数据库:
# Create dataset with the above features
X = np.c_[x1, x3, x2, x5, x4]
- 创建一个 PCA 对象:
# Perform Principal Component Analysis
pca = decomposition.PCA()
- 在输入数据上拟合 PCA 模型:
pca.fit(X)
- 打印维度的方差:
# Print variances
variances = pca.explained_variance_
print('Variances in decreasing order:\n', variances)
- 如果某个维度是有用的,那么它将为方差提供一个有意义的值。让我们设置一个阈值并识别重要的维度:
# Find the number of useful dimensions
thresh_variance = 0.8
num_useful_dims = len(np.where(variances > thresh_variance)[0])
print('Number of useful dimensions:', num_useful_dims)
- 正如我们之前讨论的那样,PCA 已经确定在这个数据集中只有两个维度是重要的:
# As we can see, only the 2 first components are useful
pca.n_components = num_useful_dims
- 让我们将数据集从五维集转换为二维集:
XNew = pca.fit_transform(X)
print('Shape before:', X.shape)
print('Shape after:', XNew.shape)
- 如果你运行这段代码,你将在你的终端上看到以下内容:
Variances in decreasing order:
[2.77392134e+02 1.51557851e+01 9.54279881e-30 7.73588070e-32 9.89435444e-33]
Number of useful dimensions: 2
Shape before: (250, 5)
Shape after: (250, 2)
如我们所见,前两个成分包含了模型的所有方差。
它是如何工作的...
PCA 生成了一组新的变量,其中包含不相关的变量,也称为主成分。每个主成分是原始变量的线性组合。所有主成分都是相互正交的,因此没有冗余信息。
主成分作为一个整体构成了数据空间的正交基。PCA 的目标是用最少的几个主成分解释最大的方差。PCA 是一种多维缩放,其中变量被线性变换到低维空间,从而保留关于变量的最大信息量。因此,主成分是原始变量经过线性变换后的组合。
还有更多…
方差衡量一组数字从它们的平均值偏离的程度。它表示各个值从算术平均值偏差的平方的平均值。
参见
-
sklearn.decomposition.PCA函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html -
主成分分析(由斯坦福大学的 Andrew Ng 提供):
cs229.stanford.edu/notes/cs229-notes10.pdf -
主成分分析(来自印第安纳大学):
scholarwiki.indiana.edu/Z604/slides/week4-PCA.pdf
执行核主成分分析
PCA 擅长减少维度数量,但它以线性方式工作。如果数据不是以线性方式组织的,PCA 将无法完成所需的工作。这就是核 PCA 出现的地方。
准备工作
在这个菜谱中,我们将看到如何对输入数据进行核 PCA,并将结果与 PCA 在相同数据上的表现进行比较。
如何操作...
让我们看看我们如何执行核 PCA:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
kpca.py文件中给出):
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA, KernelPCA
from sklearn.datasets import make_circles
- 定义随机数生成器的
seed值。这是生成分析数据样本所需的:
# Set the seed for random number generator
np.random.seed(7)
- 生成分布在内切圆中的数据以展示 PCA 在这种情况下不起作用:
# Generate samples
X, y = make_circles(n_samples=500, factor=0.2, noise=0.04)
- 对这些数据进行 PCA 分析:
# Perform PCA
pca = PCA()
X_pca = pca.fit_transform(X)
- 对这些数据进行核 PCA 分析:
# Perform Kernel PCA
kernel_pca = KernelPCA(kernel="rbf", fit_inverse_transform=True, gamma=10)
X_kernel_pca = kernel_pca.fit_transform(X)
X_inverse = kernel_pca.inverse_transform(X_kernel_pca)
- 绘制原始输入数据:
# Plot original data
class_0 = np.where(y == 0)
class_1 = np.where(y == 1)
plt.figure()
plt.title("Original data")
plt.plot(X[class_0, 0], X[class_0, 1], "ko", mfc='none')
plt.plot(X[class_1, 0], X[class_1, 1], "kx")
plt.xlabel("1st dimension")
plt.ylabel("2nd dimension")
- 绘制 PCA 转换后的数据:
# Plot PCA projection of the data
plt.figure()
plt.plot(X_pca[class_0, 0], X_pca[class_0, 1], "ko", mfc='none')
plt.plot(X_pca[class_1, 0], X_pca[class_1, 1], "kx")
plt.title("Data transformed using PCA")
plt.xlabel("1st principal component")
plt.ylabel("2nd principal component")
- 绘制核 PCA 转换后的数据:
# Plot Kernel PCA projection of the data
plt.figure()
plt.plot(X_kernel_pca[class_0, 0], X_kernel_pca[class_0, 1], "ko", mfc='none')
plt.plot(X_kernel_pca[class_1, 0], X_kernel_pca[class_1, 1], "kx")
plt.title("Data transformed using Kernel PCA")
plt.xlabel("1st principal component")
plt.ylabel("2nd principal component")
- 使用核方法将数据转换回原始空间,以显示逆变换是保持不变的:
# Transform the data back to original space
plt.figure()
plt.plot(X_inverse[class_0, 0], X_inverse[class_0, 1], "ko", mfc='none')
plt.plot(X_inverse[class_1, 0], X_inverse[class_1, 1], "kx")
plt.title("Inverse transform")
plt.xlabel("1st dimension")
plt.ylabel("2nd dimension")
plt.show()
- 完整的代码在提供的
kpca.py文件中给出,供您参考。如果您运行此代码,您将看到四个图。第一个图是原始数据:
第二个图展示了使用 PCA 转换后的数据:
第三个图展示了使用核 PCA 转换后的数据。注意图中点在右侧是如何聚集的:
第四个图展示了数据逆变换回原始空间:
它是如何工作的...
核主成分分析(核 PCA)基于 PCA,同时使用核方法的技术。在 PCA 中,原始的线性 PCA 操作是在再生核 Hilbert 空间中执行的。
核方法是一类用于分析和模式方案的算法,其中最著名的元素是 SVMs。核方法通过将数据映射到多维特征空间来解决一个问题,在这个空间中,每个坐标对应于元素数据的特征,将数据转换为一组欧几里得空间点。由于映射可以是通用的(例如,不一定是线性的),因此以这种方式找到的关系因此非常通用。
还有更多...
核方法以核函数命名,核函数用于在特征空间上操作,而不必在空间中计算数据坐标,而是通过在函数空间中计算所有数据副本的图像之间的内积。核方法通常比显式计算坐标的计算成本低。核技巧将这种方法称为问题解决。
参考内容
-
sklearn.decomposition.KernelPCA函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.decomposition.KernelPCA.html -
核主成分分析(由多伦多大学的 Max Welling 提出):
www.ics.uci.edu/~welling/classnotes/papers_class/Kernel-PCA.pdf -
核 PCA(由海法大学的 Rita Osadchy 提供):
www.cs.haifa.ac.il/~rita/uml_course/lectures/KPCA.pdf
进行盲源分离
盲源分离指的是从混合信号中分离信号的过程。假设有一组不同的信号发生器生成信号,一个共同的接收器接收所有这些信号。现在,我们的任务是利用这些信号的特性,从混合中分离这些信号。我们将使用独立成分分析(ICA)来实现这一点。
准备工作
在这个菜谱中,我们将使用.txt文件中的数据,使用ICA来分离其中的信号。
如何操作...
让我们看看我们如何进行盲源分离:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
blind_source_separation.py文件中):
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA, FastICA
- 我们将使用提供的
mixture_of_signals.txt文件中的数据。让我们加载数据:
# Load data
input_file = 'mixture_of_signals.txt'
X = np.loadtxt(input_file)
- 创建 ICA 对象:
# Compute ICA
ica = FastICA(n_components=4)
- 根据 ICA 重建信号:
# Reconstruct the signals
signals_ica = ica.fit_transform(X)
- 提取混合矩阵:
# Get estimated mixing matrix
mixing_mat = ica.mixing_
- 进行 PCA 比较:
# Perform PCA
pca = PCA(n_components=4)
# Reconstruct signals based on orthogonal components
signals_pca = pca.fit_transform(X)
- 定义一个信号列表以绘制它们:
# Specify parameters for output plots
models = [X, signals_ica, signals_pca]
- 指定图表的颜色:
colors = ['blue', 'red', 'black', 'green']
- 绘制输入信号:
# Plotting input signal
plt.figure()
plt.title('Input signal (mixture)')
for i, (sig, color) in enumerate(zip(X.T, colors), 1):
plt.plot(sig, color=color)
- 绘制 ICA 分离的信号:
# Plotting ICA signals
plt.figure()
plt.title('ICA separated signals')
plt.subplots_adjust(left=0.1, bottom=0.05, right=0.94,
top=0.94, wspace=0.25, hspace=0.45)
- 使用不同颜色的子图绘制:
for i, (sig, color) in enumerate(zip(signals_ica.T, colors), 1):
plt.subplot(4, 1, i)
plt.title('Signal ' + str(i))
plt.plot(sig, color=color)
- 绘制 PCA 分离的信号:
# Plotting PCA signals
plt.figure()
plt.title('PCA separated signals')
plt.subplots_adjust(left=0.1, bottom=0.05, right=0.94,
top=0.94, wspace=0.25, hspace=0.45)
- 在每个子图中使用不同的颜色:
for i, (sig, color) in enumerate(zip(signals_pca.T, colors), 1):
plt.subplot(4, 1, i)
plt.title('Signal ' + str(i))
plt.plot(sig, color=color)
plt.show()
如果你运行此代码,你会看到三个图表。第一个图表展示了输入,它是由信号混合而成的:
第二个图表展示了使用 ICA 分离的信号:
第三张图展示了使用 PCA 分离的信号:
它是如何工作的...
ICA 是一种计算处理方法,用于将多变量信号分离成其加性子分量,假设非高斯信号的来源之间存在相互的统计独立性。这是盲源分离的一个特例。该方法通过最大化估计组件的统计独立性来找到独立成分。
更多内容...
ICA 算法的应用示例在脑电图(EEG)领域,但它也被广泛用于从mother.ICA技术中分离出胎儿的心电图(ECG)。这可以扩展到对非物理数据的分析,这些数据可以是语义的或语言的。例如,ICA 已被应用于使计算机理解一组新闻列表存档中的讨论主题。
相关内容
-
sklearn.decomposition.FastICA函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.decomposition.FastICA.html -
盲源分离:主成分分析和独立成分分析原理(来自麻省理工学院):
www.mit.edu/~gari/teaching/6.555/LECTURE_NOTES/ch15_bss.pdf
使用局部二值模式直方图构建人脸识别器
我们现在准备好构建人脸识别器了。我们需要一个用于训练的人脸数据集,所以我们提供了一个名为faces_dataset的文件夹,其中包含足够用于训练的一小部分图像。这个数据集是www.vision.caltech.edu/Image_Datasets/faces/faces.tar中可用数据集的一个子集。这个数据集包含足够多的图像,我们可以用它们来训练一个人脸识别系统。
我们将使用局部二值模式直方图来构建我们的人脸识别系统。在我们的数据集中,你会看到不同的人。我们的任务是构建一个能够学会将这些人区分开来的系统。当我们看到一张未知图像时,我们的系统会将其分配到现有的某个类别中。
准备工作
在这个菜谱中,我们将看到如何使用局部二值模式直方图构建人脸识别器,并使用人脸数据集来训练模型。
如何做到这一点...
让我们看看如何使用局部二值模式直方图构建人脸识别器:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
face_recognizer.py文件中给出):
import os
import cv2
import numpy as np
from sklearn import preprocessing
- 让我们定义一个类来处理与标签编码相关的所有任务:
# Class to handle tasks related to label encoding
class LabelEncoder(object):
- 定义一个方法来编码标签。在输入训练数据中,标签由单词表示。然而,我们需要数字来训练我们的系统。此方法将定义一个预处理对象,该对象可以通过维护正向和反向映射以有组织的方式将单词转换为数字:
# Method to encode labels from words to numbers
def encode_labels(self, label_words):
self.le = preprocessing.LabelEncoder()
self.le.fit(label_words)
- 定义一个方法将单词转换为数字:
# Convert input label from word to number
def word_to_num(self, label_word):
return int(self.le.transform([label_word])[0])
- 定义一个方法将数字转换回原始单词:
# Convert input label from number to word
def num_to_word(self, label_num):
return self.le.inverse_transform([label_num])[0]
- 定义一个方法从输入文件夹中提取图像和标签:
# Extract images and labels from input path
def get_images_and_labels(input_path):
label_words = []
- 递归遍历输入文件夹并提取所有图像路径:
# Iterate through the input path and append files
for root, dirs, files in os.walk(input_path):
for filename in (x for x in files if x.endswith('.jpg')):
filepath = os.path.join(root, filename)
label_words.append(filepath.split('/')[-2])
- 初始化变量:
# Initialize variables
images = []
le = LabelEncoder()
le.encode_labels(label_words)
labels = []
- 解析输入目录以进行训练:
# Parse the input directory
for root, dirs, files in os.walk(input_path):
for filename in (x for x in files if x.endswith('.jpg')):
filepath = os.path.join(root, filename)
- 以灰度格式读取当前图像:
# Read the image in grayscale format
image = cv2.imread(filepath, 0)
- 从文件夹路径中提取标签:
# Extract the label
name = filepath.split('/')[-2]
- 在此图像上执行人脸检测:
# Perform face detection
faces = faceCascade.detectMultiScale(image, 1.1, 2, minSize=(100,100))
- 提取 ROI 并返回它们,以及标签编码器:
# Iterate through face rectangles
for (x, y, w, h) in faces:
images.append(image[y:y+h, x:x+w])
labels.append(le.word_to_num(name))
return images, labels, le
- 定义主函数和面部级联文件的路径:
if __name__=='__main__':
cascade_path = "cascade_files/haarcascade_frontalface_alt.xml"
path_train = 'faces_dataset/train'
path_test = 'faces_dataset/test'
- 加载人脸级联文件:
# Load face cascade file
faceCascade = cv2.CascadeClassifier(cascade_path)
- 为人脸识别器对象创建局部二值模式直方图:
# Initialize Local Binary Patterns Histogram face recognizer
recognizer = cv2.face.createLBPHFaceRecognizer()
- 提取此输入路径的图像、标签和标签编码器:
# Extract images, labels, and label encoder from training dataset
images, labels, le = get_images_and_labels(path_train)
- 使用我们提取的数据训练人脸识别器:
# Train the face recognizer
print "\nTraining..."
recognizer.train(images, np.array(labels))
- 在未知数据上测试人脸识别器:
# Test the recognizer on unknown images
print '\nPerforming prediction on test images...'
stop_flag = False
for root, dirs, files in os.walk(path_test):
for filename in (x for x in files if x.endswith('.jpg')):
filepath = os.path.join(root, filename)
- 加载图像:
# Read the image
predict_image = cv2.imread(filepath, 0)
- 使用人脸检测器确定人脸的位置:
# Detect faces
faces = faceCascade.detectMultiScale(predict_image, 1.1,
2, minSize=(100,100))
- 对于每个面部 ROI,运行人脸识别器:
# Iterate through face rectangles
for (x, y, w, h) in faces:
# Predict the output
predicted_index, conf = recognizer.predict(
predict_image[y:y+h, x:x+w])
- 将标签转换为单词:
# Convert to word label
predicted_person = le.num_to_word(predicted_index)
- 在输出图像上叠加文本并显示:
# Overlay text on the output image and display it
cv2.putText(predict_image, 'Prediction: ' + predicted_person,
(10,60), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255), 6)
cv2.imshow("Recognizing face", predict_image)
- 检查用户是否按下了Esc键。如果是,则跳出循环:
c = cv2.waitKey(0)
if c == 27:
stop_flag = True
break
if stop_flag:
break
如果你运行此代码,你将得到一个输出窗口,显示测试图像的预测输出。你可以按空格按钮以保持循环。测试图像中有三个人。
它是如何工作的...
本地二值模式直方图算法基于一个非参数算子,该算子综合了图像的局部结构。在特定的像素点,LBP 算子将该像素与属于考虑的邻域像素之间的颜色强度比较的有序二进制序列关联起来。特别是,如果中心像素的强度大于或等于相邻像素的强度,则分配一个值为 1。否则,分配 0。因此,对于 8 像素的邻域,例如,将有 2⁸种可能的组合。
还有更多...
要将此算子应用于面部识别问题,想法是将图像划分为m个局部区域,并从每个区域中提取直方图。要提取的特征向量由这些局部直方图的连接组成。
参见
使用基于 HOG 的模型进行面部识别
通过面部识别,我们指的是返回图像中存在的人脸位置的过程。在使用 Haar 级联构建面部检测器的配方中,我们已经讨论了这个问题。在这个配方中,我们将使用face_recognition库对这些面部执行一系列操作。
面部识别的主要目标是检测面部特征,忽略围绕它的所有其他内容。这是许多商业设备上的一个特性,它允许你确定何时以及如何在一个图像中应用焦点,以便你可以捕捉到它。在计算机视觉的世界里,通常将面部检测算法家族分为两大类。这两个类别之间的区别在于它们对信息的不同使用,这些信息来自对面部结构和特性的先验知识:
-
第一类包括基于提取规格特征的算法
-
第二类采用全局方法进行图像分析
准备中
在这个菜谱中,我们将了解如何使用face_recognition库从复杂图像中执行面部识别。在继续之前,请安装face_recognition库。这个库基于dlib库,我们必须在继续之前安装它。dlib是一个现代的 C++工具包,它包含机器学习算法和用于在 C++中创建复杂软件以解决现实世界问题的工具。您可以在pypi.org/project/face_recognition/找到有关安装该包的信息。
如何做到这一点...
让我们看看如何使用基于 HOG 的模型来识别面部:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
FaceRecognition.py文件中给出):
from PIL import Image
import face_recognition
Python 图像库(PIL)是 Python 编程语言的免费库,它增加了打开、操作和保存许多不同图像文件格式的支持。face_recognition是一个 Python 库,可以从 Python 脚本或命令行中识别和操作面部。
- 让我们将
family.jpg文件加载到 NumPy 数组中:
image = face_recognition.load_image_file("family.jpg")
- 我们现在将使用默认的基于 HOG 的模型在图像中找到所有的面部:
face_locations = face_recognition.face_locations(image)
- 定义一种将单词转换为数字的方法:
print("Number {} face(s) recognized in this image.".format(len(face_locations)))
- 打印图像中每个面部位置:
for face_location in face_locations:
top, right, bottom, left = face_location
print("Face location Top: {}, Left: {}, Bottom: {}, Right: {}".format(top, left, bottom, right))
- 最后,我们需要访问实际的面部本身:
face_image = image[top:bottom, left:right]
pil_image = Image.fromarray(face_image)
pil_image.show()
将返回每个识别出的面部的缩略图。
它是如何工作的...
方向梯度直方图(HOG)是一种用于物体识别的特征描述符。该算法计算图像局部区域中梯度方向的出现的次数。与其他用于相同目的的技术(尺度不变特征变换、边缘方向直方图、形状上下文)不同,因为它使用均匀分布的密集网格单元,并使用局部叠加归一化来提高准确性。
还有更多...
首次介绍这项技术的是 Navneet Dalal 和 Bill Triggs(2005),他们是国家信息与自动化研究所(INRIA)的研究员,当时他们正在研究静态图像中行人检测的问题。
参见
-
face_recognition库的官方文档:github.com/ageitgey/face_recognition -
面向人类检测的定向梯度直方图(由 INRIA 的 Navneet Dalal 和 Bill Triggs 编写):
lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf
面部特征点识别
由于其方向性,面部识别也很复杂。同一个面部,从观察者的不同方向看去,可能会让算法将其识别为不同的面部。为了解决这个问题,我们可以使用面部特征点,这些点位于面部上的特定位置,如眼睛、眉毛、嘴唇、鼻子等。通过使用这种技术,你可以在任何面部上识别多达 68 个点。
准备工作
在这个菜谱中,我们将看到如何提取面部特征作为面部特征点。
如何做到这一点...
让我们看看如何执行面部特征点识别:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
FaceLandmarks.py文件中给出):
from PIL import Image, ImageDraw
import face_recognition
- 让我们将
ciaburro.jpg文件加载到 NumPy 数组中:
image = face_recognition.load_image_file("ciaburro.jpg")
- 让我们在图像中的所有面部中找到所有面部特征:
FaceLandmarksList = face_recognition.face_landmarks(image)
- 打印图像中识别到的面部数量:
print("Number {} face(s) recognized in this image.".format(len(FaceLandmarksList)))
返回以下结果:
Number 1 face(s) recognized in this image
- 创建一个 PIL imagedraw 对象,以便我们可以在图片上绘制:
PilImage = Image.fromarray(image)
DrawPilImage = ImageDraw.Draw(PilImage)
- 在这一点上,我们将插入一个循环,返回列表中每个面部特征的点位置,并在图像上绘制线条:
for face_landmarks in FaceLandmarksList:
- 首先,我们打印出此图像中每个面部特征的位置:
for facial_feature in face_landmarks.keys():
print("{} points: {}".format(facial_feature, face_landmarks[facial_feature]))
- 然后我们用线条在图像中追踪每个面部特征:
for facial_feature in face_landmarks.keys():
DrawPilImage.line(face_landmarks[facial_feature], width=5)
- 最后,我们绘制带有突出显示特征点的图像:
PilImage.show()
在以下图像中,我们可以看到输入图像和带有突出显示特征点的图像:
此外,特征点的位置如下打印:
它是如何工作的...
在这个菜谱中,我们学习了如何从图像中提取面部特征点,以及如何在同一图像上绘制这些点。以下特征点被检测到:
-
下巴 -
左眉毛 -
右眉毛 -
鼻梁 -
鼻尖 -
左眼 -
右眼 -
上嘴唇 -
下嘴唇
对于检测到的每个特征,连接检测点的线条被绘制出来以显示轮廓。
更多内容...
为了提取面部特征点,使用了face_recognition库。这个库通过使用 Vahid Kazemi 和 Josephine Sullivan 在以下论文中介绍的方法来完成这项任务:One Millisecond Face Alignment with an Ensemble of Regression Trees。为了估计面部特征点的位置,使用了一个回归树集成。
参见
-
face_recognition库的官方文档:github.com/ageitgey/face_recognition -
One Millisecond Face Alignment with an Ensemble of Regression Trees(由 Vahid Kazemi 和 Josephine Sullivan):
www.csc.kth.se/~vahidk/papers/KazemiCVPR14.pdf
通过面部识别进行用户身份验证
基于面部识别的认证技术已经是一个巩固的现实了几十年。如果我们如此体贴地经常更换它,我们不再需要携带口袋卡,存储在手机上,或使用记忆术来记住每次都不同的一个。我们需要做的是验证我们已有的东西。为此,我们只需看看我们的网络摄像头。基于面部识别的识别系统试图通过将刚刚获取的面部图像与数据库中现有的图像进行比较来识别一个人,以找到可能的对应关系。这会导致允许或禁止访问。
准备工作
在这个菜谱中,我们将看到如何使用face_recognition库构建基于面部识别的识别系统。
如何做...
让我们看看如何通过使用面部识别来执行用户认证:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
UserAuthentification.py文件中给出):
import face_recognition
- 让我们将所有图像文件加载到 NumPy 数组中:
Image1 = face_recognition.load_image_file("giuseppe.jpg")
Image2 = face_recognition.load_image_file("tiziana.jpg")
UnknownImage = face_recognition.load_image_file("tiziana2.jpg")
已加载三张图像:前两张图像指的是我们之前看到的面孔,而第三张是要比较的图像(tiziana)。
- 获取每个图像文件中每个面孔的面部编码:
try:
Image1Encoding = face_recognition.face_encodings(Image1)[0]
Image2Encoding = face_recognition.face_encodings(Image2)[0]
UnknownImageEncoding = face_recognition.face_encodings(UnknownImage)[0]
except IndexError:
print("Any face was located. Check the image files..")
quit()
- 让我们定义已知的面孔:
known_faces = [
Image1Encoding,
Image2Encoding
]
- 让我们比较已知面孔与我们刚刚加载的未知面孔:
results = face_recognition.compare_faces(known_faces, UnknownImageEncoding)
- 最后,我们将打印比较的结果:
print("Is the unknown face a picture of Giuseppe? {}".format(results[0]))
print("Is the unknown face a picture of Tiziana? {}".format(results[1]))
print("Is the unknown face a new person that we've never seen before? {}".format(not True in results))
返回以下结果:
Is the unknown face a picture of Giuseppe? False
Is the unknown face a picture of Tiziana? True
Is the unknown face a new person that we've never seen before? False
如我们所见,认证系统识别用户为 Tiziana。
它是如何工作的...
在这个菜谱中,我们学习了如何构建基于面部识别的识别系统。为此,我们从已知数据库中的每个面孔中提取了一些基本度量。通过这样做,我们能够将这些基本度量与其他需要认证的面孔的基本度量进行比较。
这些度量是使用深度卷积神经网络进行的。学习过程通过同时分析三张图像来工作:
-
包含已知人员面孔的图像(锚点)
-
同一已知人员的另一张图像(正面)
-
一个完全不同的人的图像(负面)
在这个阶段,算法会检查为这三张图像各自生成的度量。然后它调整神经网络的权重,以确保为面孔 1 和 2 生成的度量稍微接近一些,而面孔 2 和 3 的度量稍微远一些。这种技术被称为Triplet Loss。
更多...
到目前为止,我们说过,基于机器学习的算法成功的关键在于学习阶段使用的示例数量。数量越多,模型的准确性就越高。在我们本章处理的情况中,这不能被认为是有效的。这是因为,在面部识别算法中,我们可用的示例非常有限。
因此,典型的卷积神经网络的结构和形成将不会工作,因为它无法使用现有数据量学习所需的功能。在这些情况下,我们采用单次学习方法,其中我们构建一个相似度函数,该函数比较两个图像并告诉你是否存在匹配。
参见
-
face_recognition库的官方文档:github.com/ageitgey/face_recognition -
单次学习(来自维基百科):
en.wikipedia.org/wiki/One-shot_learning -
简单视觉概念的单次学习(来自麻省理工学院):
web.mit.edu/jgross/Public/lake_etal_cogsci2011.pdf -
Siamese/Triplet 网络(来自弗吉尼亚理工大学):
filebox.ece.vt.edu/~jbhuang/teaching/ece6554/sp17/lectures/Lecture_08_Siamese_Triplet_Networks.pdf
第十二章:强化学习技术
在本章中,我们将涵盖以下食谱:
-
使用 MDP 进行天气预报
-
使用 DP 优化金融投资组合
-
寻找最短路径
-
使用 Q 学习决定折现因子
-
实现深度 Q 学习算法
-
开发基于 AI 的动态建模系统
-
基于 Double Q 学习的深度强化学习
-
带有对抗性 Q 学习的深度 Q 网络算法
技术要求
为了处理本章中的食谱,你需要以下文件(可在 GitHub 上找到):
-
MarkovChain.py -
KPDP.py -
DijkstraNX.py -
FrozenQlearning.py -
FrozenDeepQLearning.py -
dqn_cartpole.py -
DoubleDQNCartpole.py -
DuelingDQNCartpole.py
简介
强化学习代表了一类能够学习和适应环境变化的算法。它基于算法选择的外部刺激的概念。正确的选择将导致奖励,而错误的选择将导致惩罚。系统的目标是实现最佳结果。
在监督学习中,正确的输出是明确指定的(有教师指导的学习)。但并非总是可能这样做。通常,我们只有定性信息。可用的信息被称为强化信号。在这些情况下,系统不会提供有关如何更新智能体行为的任何信息(例如,权重)。无法定义成本函数或梯度。系统的目标是创建能够从经验中学习的智能体。
在下面的屏幕截图中,我们可以看到一个流程图,显示了强化学习与环境之间的交互:
下面是正确应用强化学习算法的步骤:
-
智能体的准备
-
环境观察
-
选择最优策略
-
执行动作
-
计算相应的奖励(或惩罚)
-
更新策略的开发(如有必要)
-
重复步骤 2 到 5,直到智能体学习到最优策略
强化学习试图最大化执行动作或动作集以实现目标所获得的奖励。
使用 MDP 进行天气预报
为了避免负载问题和计算困难,将智能体-环境交互视为一个马尔可夫决策过程(MDP)。MDP 是一个离散时间随机控制过程。
随机过程是用于研究遵循随机或概率定律的现象演化的数学模型。众所周知,在所有自然现象中,无论是由于其本质还是由于观测误差,都存在一个随机或偶然的成分。
这个组件导致以下情况:在t的每一个实例中,对现象的观察结果是随机数或随机变量,st。不可能确定结果会是什么;你只能声明它将取几个可能值中的一个,每个值都有给定的概率。
当观察到一个特定的t实例时,如果随机过程的演变,从t开始,只依赖于t而不依赖于任何先前的实例,则称该随机过程为马尔可夫。因此,当给定观察时刻时,只有这个实例决定了过程的未来演变,而这种演变不依赖于过去。
准备工作
在这个菜谱中,我们想要构建一个统计模型来预测天气。为了简化模型,我们假设只有两种状态:晴天和雨天。让我们进一步假设我们已经进行了一些计算,并发现明天的时刻某种程度上基于今天的时间。
如何操作...
让我们看看如何使用 MDP 进行天气预报:
- 我们将使用已经为你提供的
MarkovChain.py文件作为参考。首先,我们导入numpy、time和matplotlib.pyplot包:
import numpy as np
import time
from matplotlib import pyplot
- 让我们设置随机数生成器的种子和天气状态:
np.random.seed(1)
states = ["Sunny","Rainy"]
- 在这一点上,我们必须定义天气条件的可能转移:
TransStates = [["SuSu","SuRa"],["RaRa","RaSu"]]
TransnMatrix = [[0.75,0.25],[0.30,0.70]]
- 然后,我们插入以下检查以验证我们没有在定义转移矩阵时出错:
if sum(TransnMatrix[0])+sum(TransnMatrix[1]) != 2:
print("Warning! Probabilities MUST ADD TO 1\. Wrong transition matrix!!")
raise ValueError("Probabilities MUST ADD TO 1")
- 让我们设置初始条件:
WT = list()
NumberDays = 200
WeatherToday = states[0]
print("Weather initial condition =",WeatherToday)
- 现在,我们可以预测
NumberDays变量设置的每一天的天气条件。为此,我们将使用以下while循环:
i = 0
while i < NumberDays:
if WeatherToday == "Sunny":
TransWeather = np.random.choice(TransStates[0],replace=True,p=TransnMatrix[0])
if TransWeather == "SuSu":
pass
else:
WeatherToday = "Rainy"
elif WeatherToday == "Rainy":
TransWeather = np.random.choice(TransStates[1],replace=True,p=TransnMatrix[1])
if TransWeather == "RaRa":
pass
else:
WeatherToday = "Sunny"
print(WeatherToday)
WT.append(WeatherToday)
i += 1
time.sleep(0.2)
它由一个控制条件和循环体组成。在循环的入口处以及每次执行循环体内的所有指令后,都要验证控制条件的有效性。当由布尔表达式组成的条件返回false时,循环结束。
- 在这一点上,我们已经为未来 200 天生成了预报。让我们使用以下代码绘制图表:
pyplot.plot(WT)
pyplot.show()
下面的图显示了从晴天开始的未来 200 天的天气条件:
初看之下,似乎晴天比雨天多。
它是如何工作的...
马尔可夫链是随机现象的数学模型,该现象随时间演变,过去只通过现在影响未来。换句话说,随机模型描述了一系列可能的事件,其中每个事件的概率只依赖于前一个事件达到的状态。因此,马尔可夫链具有无记忆性。
因此,马尔可夫链的结构完全由以下转移矩阵表示:
转移概率矩阵的性质直接来源于组成它们的元素的本质。
还有更多…
将马尔可夫链通过转移矩阵描述的一个非常直观的替代方法是将其与一个有向图(转移图)关联。转移矩阵和转移图提供了关于同一马尔可夫链的相同信息。
参见
-
Keras 强化学习项目,Giuseppe Ciaburro,Packt 出版社
-
马尔可夫模型导论(来自克莱姆森大学):
cecas.clemson.edu/~ahoover/ece854/refs/Ramos-Intro-HMM.pdf -
马尔可夫决策过程(来自卡内基梅隆大学):
egon.cheme.cmu.edu/ewo/docs/SchaeferMDP.pdf
使用动态规划优化金融投资组合
金融投资组合的管理是一种旨在以最能代表投资者需求的方式组合金融产品的活动。这需要评估各种特征的整体评估,例如风险偏好、预期回报和投资者消费,以及未来回报和风险的估计。动态规划(DP)代表了一组算法,可以在形式为 MDP 的环境完美模型下计算最优策略。DP 的基本思想,以及强化学习的一般思想,是使用状态值和动作来寻找好的策略。
准备工作
在这个菜谱中,我们将解决背包问题:一个小偷进入一栋房子,想要偷走贵重物品。他们将它们放入背包中,但受到重量的限制。每个对象都有自己的价值和重量。他们必须选择有价值但重量不过重的对象。小偷不能超过背包的重量限制,同时,他们必须优化他们的收益。
如何做到这一点…
让我们看看我们如何使用动态规划优化金融投资组合:
- 我们将使用已经为你提供的
KPDP.py文件作为参考。此算法从定义一个KnapSackTable()函数开始,该函数将选择满足问题所提出的两个约束条件的对象的最优组合:对象的总重量等于 10,以及所选对象的最大值,如下面的代码所示:
def KnapSackTable(weight, value, P, n):
T = [[0 for w in range(P + 1)]
for i in range(n + 1)]
- 然后,我们在所有对象和所有重量值上设置一个迭代循环,如下所示:
for i in range(n + 1):
for w in range(P + 1):
if i == 0 or w == 0:
T[i][w] = 0
elif weight[i - 1] <= w:
T[i][w] = max(value[i - 1]
+ T[i - 1][w - weight[i - 1]],
T[i - 1][w])
else:
T[i][w] = T[i - 1][w]
- 现在,我们可以记住获得的结果,这代表了可以装入背包的对象的最大价值,如下所示:
res = T[n][P]
print("Total value: " ,res)
- 我们迄今为止遵循的程序并没有表明哪个子集提供了最优解。我们必须使用一种集合程序来提取这个信息:
w = P
totweight=0
for i in range(n, 0, -1):
if res <= 0:
break
- 如果当前元素与上一个元素相同,我们将继续下一个,如下所示:
if res == T[i - 1][w]:
continue
- 如果它们不相同,那么当前对象将被包含在背包中,并将打印出此项目,如下所示:
else:
print("Item selected: ",weight[i - 1],value[i - 1])
totweight += weight[i - 1]
res = res - value[i - 1]
w = w - weight[i – 1]
- 最后,打印出包含的总重量,如下所示:
print("Total weight: ",totweight)
这样,我们就定义了一个函数,允许我们构建表格。
- 现在,我们必须定义输入变量并将它们传递给函数,如下所示:
objects = [(5, 18),(2, 9), (4, 12), (6,25)]
print("Items available: ",objects)
print("***********************************")
- 在这一点上,我们需要从对象中提取权重和变量值。我们将它们放入一个单独的数组中,以便更好地理解步骤,如下所示:
value = []
weight = []
for item in objects:
weight.append(item[0])
value.append(item[1])
- 最后,设置背包可以携带的总重量和可用物品的数量,如下所示:
P = 10
n = len(value)
- 最后,我们打印出结果:
KnapSackTable(weight, value, P, n)
The following results are returned:
Items available: [(5, 18), (2, 9), (4, 12), (6, 25)]
*********************************
Total value: 37
Item selected: 6 25
Item selected: 4 12
Total weight: 10
动态规划算法使我们能够获得最优解,从而节省了计算成本。
它是如何工作的...
例如,考虑找到连接两个位置的最佳路径的问题。最优性原理指出,它包含的每个子路径,在任意中间位置和最终位置之间,必须依次是最优的。基于这个原理,动态规划通过一次做出一个决策来解决问题。在每一步,都会确定未来的最佳策略,而不考虑过去的决策(它是一个马尔可夫过程),假设后者也是最优的。
还有更多...
动态规划是一种更有效地解决递归问题的技术。为什么是这样呢?在递归过程中,我们通常会反复解决子问题。在动态规划中,这种情况不会发生:我们记住这些子问题的解决方案,这样我们就不必再次解决它们。这被称为备忘录化。如果变量的值在给定步骤上依赖于先前计算的结果,并且如果相同的计算反复进行,那么存储中间结果以避免重复计算昂贵的计算是有利的。
参考以下内容
-
参考 Giuseppe Ciaburro 所著的《Keras 强化学习项目》,Packt 出版社
-
参考斯坦福大学的动态规划:
web.stanford.edu/class/cs97si/04-dynamic-programming.pdf -
参考蒂尔堡大学的背包问题:
www.es.ele.tue.nl/education/5MC10/Solutions/knapsack.pdf -
参考拉德福德大学的备忘录化:
www.radford.edu/~nokie/classes/360/dp-memoized.html
寻找最短路径
给定一个加权图和一个指定的顶点 X,我们通常会需要找到从 X 到图中每个其他顶点的路径。识别连接图中的两个或多个节点的路径在许多其他离散优化问题中表现为子问题,并且在现实世界中也有许多应用。
准备工作
在本食谱中,我们将使用Dijkstra算法找到两点之间的最短路径。我们还将使用networkx包在 Python 中表示图。
如何做到这一点…
让我们看看我们如何找到最短路径:
- 我们将使用已经提供的
DijkstraNX.py文件作为参考。首先,我们导入我们将在这里使用的库:
import networkx as nx
import matplotlib.pyplot as plt
- 然后,创建了一个图对象并添加了顶点:
G = nx.Graph()
G.add_node(1)
G.add_node(2)
G.add_node(3)
G.add_node(4)
- 随后,添加了加权边:
G.add_edge(1, 2, weight=2)
G.add_edge(2, 3, weight=2)
G.add_edge(3, 4, weight=3)
G.add_edge(1, 3, weight=5)
G.add_edge(2, 4, weight=6)
- 在这一点上,我们已经通过给边添加带有权重指示的标签来绘制了图:
pos = nx.spring_layout(G, scale=3)
nx.draw(G, pos,with_labels=True, font_weight='bold')
edge_labels = nx.get_edge_attributes(G,'r')
nx.draw_networkx_edge_labels(G, pos, labels = edge_labels)
plt.show()
为了做到这一点,使用了draw_networkx_edge_labels函数。以下图表显示了这一结果:
- 最后,计算从节点一到四的最短路径:
print(nx.shortest_path(G,1,4,weight='weight'))
shortest_path函数计算图中节点之间的最短路径和路径长度。以下结果是:
[1, 2, 3, 4]
- 最后,计算了最短路径的长度:
print(nx.nx.shortest_path_length(G,1,4,weight='weight'))
以下结果是:
7
如我们所验证的,我们已经获得了相同的结果。
它是如何工作的…
Dijkstra 算法能够解决从源点s到所有节点的最短路径问题。算法维护一个标签*d(i)*到节点,表示节点 i 的最短路径长度的上界。
在每一步中,算法将节点集V划分为两个集合:永久标记节点的集合和仍然临时标记的节点的集合。永久标记节点的距离代表从源点到这些节点的最短路径距离,而临时标签包含一个可以大于或等于最短路径长度的值。
还有更多…
算法的基本思想是从源点开始,尝试永久标记后续节点。一开始,算法将源点的距离值设为零,并将其他距离初始化为任意高值(按照惯例,我们将距离的初始值设为:d[i] = + ∞, ∀i ∈ V)。
在每次迭代中,节点标签 i 是包含除了 i 之外只有永久标记节点的源点到路径上的最小距离的值。算法选择标签值最低的临时标记节点,将其永久标记,并更新其相邻节点的所有标签。当所有节点都被永久标记时,算法终止。
参考以下内容
-
查看书籍《Keras 强化学习项目》,作者 Giuseppe Ciaburro,出版社 Packt Publishing
-
查看书籍《解决最短路径问题:Dijkstra 算法》(来自伊利诺伊大学):
www.ifp.illinois.edu/~angelia/ge330fall09_dijkstra_l18.pdf -
查看书籍《图论教程》(来自田纳西大学马丁分校):
primes.utm.edu/graph/index.html
使用 Q 学习决定折扣因子
Q 学习是使用最广泛的强化学习算法之一。这得益于它能够比较可用动作的预期效用,而无需环境模型。多亏了这项技术,我们可以在完成 MDP 的每个给定状态下找到最优动作。
强化学习问题的一般解决方案是通过学习过程估计一个评估函数。这个函数必须能够通过奖励的总和来评估特定策略的便利性或不利性。实际上,Q 学习试图最大化 Q 函数(动作值函数)的值,它表示当我们执行动作a在状态s时,最大化的折现未来奖励。
准备工作
在这个菜谱中,我们将通过提供一个基于 Q 学习的第一个解决方案来处理在网格世界中控制角色移动的问题。
如何做到这一点...
让我们看看我们如何使用 Q 学习来决定折扣因子:
- 我们将使用已经提供的
FrozenQlearning.py文件作为参考。让我们先导入库:
import gym
import numpy as np
- 然后,我们将继续创建环境,通过调用
make方法:
env = gym.make('FrozenLake-v0')
此方法创建我们的智能体将运行的 环境。
- 现在,让我们初始化参数,从
QTable开始:
QTable = np.zeros([env.observation_space.n,env.action_space.n])
- 让我们定义一些参数:
alpha = .80
gamma = .95
NumEpisodes = 2000
在这里,alpha是学习率,gamma是折扣因子,NumEpisodes是剧集数。
- 现在,我们将创建一个列表来包含总奖励:
RewardsList = []
- 在这一点上,在设置参数后,可以开始 Q 学习周期:
for i in range(NumEpisodes):
CState = env.reset()
SumReward = 0
d = False
j = 0
while j < 99:
j+=1
Action = np.argmax(QTable[CState,:] + np.random.randn(1,env.action_space.n)*(1./(i+1)))
NState,Rewards,d,_ = env.step(Action)
QTable[CState,Action] = QTable[CState,Action] + alpha*(Rewards + gamma*np.max(QTable[NState,:]) - QTable[CState,Action])
SumReward += Rewards
CState = NState
if d == True:
break
RewardsList.append(SumReward)
在每个剧集结束时,奖励列表将增加一个新值。
- 最后,我们打印结果:
print ("Score: " + str(sum(RewardsList)/NumEpisodes))
print ("Final Q-Table Values")
print (QTable)
下面的截图显示了最终的 Q 表:
为了提高结果,需要调整配置参数的回退。
它是如何工作的…
FrozenLake环境是一个 4 × 4 的网格,包含四个可能区域:安全(S)、冰冻(F)、洞(H)和目标(G)。智能体控制网格世界中角色的移动,并在网格中移动直到达到目标或洞。网格中的一些方格是可通行的,而其他方格会导致智能体掉入水中。如果掉入洞中,它必须从头开始,并得到 0 的奖励。此外,智能体将移动的方向是不确定的,并且仅部分取决于所选方向。如果智能体找到一个可通行的路径到达目标方格,它将得到奖励。智能体有四种可能的移动:上、下、左和右。这个过程会持续进行,直到它从每个错误中学习并达到目标。
更多内容...
Q-learning 通过增量估计函数值 q(s, a),在环境的每个步骤中更新状态-动作对的值,遵循更新时间差分方法估计值的一般公式的逻辑。Q-learning 具有离线策略特性;也就是说,虽然策略是根据 q(s, a) 估计的值来改进的,但值函数会根据一个严格贪婪的次级策略来更新估计:给定一个状态,选择的行为总是最大化 max q(s, a) 值的那个行为。然而,π 策略在估计值方面起着重要作用,因为通过它确定了要访问和更新的状态-动作对。
参考以下内容
-
查看《Keras 强化学习项目》,作者 Giuseppe Ciaburro,Packt 出版
-
参考《强化学习:教程》(多伦多大学):
www.cs.toronto.edu/~zemel/documents/411/rltutorial.pdf -
查看 gym 库的官方网站:
gym.openai.com/ -
查看 FrozenLake-v0 环境:
gym.openai.com/envs/FrozenLake-v0/
实现深度 Q-learning 算法
深度 Q-learning 代表了基本 Q-learning 方法的演变。状态-动作被神经网络所取代,目的是逼近最优值函数。与 Q-learning 方法相比,它被用来构建网络以请求输入和动作,并提供其期望回报,深度 Q-learning 革新了结构,只请求环境的状态,并提供尽可能多的状态-动作值,这些值对应于环境中可以执行的动作数量。
准备工作
在这个菜谱中,我们将使用深度 Q-learning 方法来控制网格世界中角色的移动。在这个菜谱中,将使用 keras-rl 库;要了解更多信息,请参考《开发基于 AI 的动态建模系统》菜谱。
如何操作...
让我们看看如何实现深度 Q-learning 算法:
- 我们将使用提供的
FrozenDeepQLearning.py文件作为参考。让我们首先导入库:
import gym
import numpy as np
from keras.models import Sequential
from keras.layers.core import Dense, Reshape
from keras.layers.embeddings import Embedding
from keras.optimizers import Adam
from rl.agents.dqn import DQNAgent
from rl.policy import BoltzmannQPolicy
from rl.memory import SequentialMemory
- 然后,我们将定义环境和设置种子:
ENV_NAME = 'FrozenLake-v0'
env = gym.make(ENV_NAME)
np.random.seed(1)
env.seed(1)
- 现在,我们将提取智能体可用的动作:
Actions = env.action_space.n
Actions 变量现在包含在所选环境中可用的所有动作。Gym 不会总是告诉你这些动作的含义,但只会告诉你哪些动作是可用的。
- 现在,我们将使用
keras库构建一个简单的神经网络模型:
model = Sequential()
model.add(Embedding(16, 4, input_length=1))
model.add(Reshape((4,)))
print(model.summary())
现在,神经网络模型已经准备好使用,所以让我们配置和编译我们的智能体。使用 DQN 的问题之一是,算法中使用的神经网络倾向于忘记先前的经验,因为它用新的经验覆盖了它们。
- 因此,我们需要一个包含先前经验和观察结果列表来用先前经验重新构建模型。为此,定义了一个将包含先前经验的内存变量,并设置了一个策略:
memory = SequentialMemory(limit=10000, window_length=1)
policy = BoltzmannQPolicy()
- 我们只需要定义智能体:
Dqn = DQNAgent(model=model, nb_actions=Actions,
memory=memory, nb_steps_warmup=500,
target_model_update=1e-2, policy=policy,
enable_double_dqn=False, batch_size=512
)
- 让我们继续编译和拟合模型:
Dqn.compile(Adam())
Dqn.fit(env, nb_steps=1e5, visualize=False, verbose=1, log_interval=10000)
- 训练结束时,有必要保存获得的权重:
Dqn.save_weights('dqn_{}_weights.h5f'.format(ENV_NAME), overwrite=True)
- 最后,我们将对算法进行 20 个回合的评估:
Dqn.test(env, nb_episodes=20, visualize=False)
我们的智能体现在能够识别出通往目标的路径。
它是如何工作的…
强化学习问题的一般解决方案是,通过学习过程估计一个评估函数。这个函数必须能够通过奖励的总和来评估特定策略的便利性或不利性。实际上,Q 学习试图最大化Q函数(动作值函数)的值,它表示在状态s中执行动作a时的最大折现未来奖励。DQN 代表了基本 Q 学习方法的演变,其中状态-动作被神经网络取代,目的是逼近最优值函数。
还有更多…
OpenAI Gym 是一个帮助我们实现基于强化学习算法的库。它包括一个不断增长的基准问题集合,这些问题提供了一个公共接口,以及一个网站,人们可以在那里分享他们的结果并比较算法性能。
OpenAI Gym 专注于强化学习的回合设置。换句话说,智能体的经验被划分为一系列回合。智能体的初始状态由一个分布随机采样,交互过程一直进行,直到环境达到终端状态。这个程序在每个回合中重复进行,目的是最大化每个回合的总奖励期望,并在尽可能少的回合内达到高水平的表现。
参考以下内容
-
请参考 Keras 强化学习项目,作者 Giuseppe Ciaburro,Packt Publishing
-
请参考 使用深度强化学习学习 2048(来自滑铁卢大学):
cs.uwaterloo.ca/~mli/zalevine-dqn-2048.pdf -
请参考 深度强化学习中的 Q 函数(来自加州大学伯克利分校):
rail.eecs.berkeley.edu/deeprlcourse/static/slides/lec-8.pdf
开发基于 AI 的动态建模系统
Segway是一种利用计算机科学、电子学和机械学创新组合的个人交通工具。它作为身体的延伸;就像舞伴一样,能够预测每一个动作。其工作原理基于反向摆系统。反向摆系统是控制理论和研究文献中常见的例子。它的流行部分原因在于它没有控制是不稳定的,并且具有非线性动态,但更重要的是,它有多个实际应用,例如控制火箭的起飞或 Segway。
准备工作
在这个配方中,我们将分析由连接到车上的刚性杆组成的物理系统的功能,使用不同的方法来模拟系统。杆通过一个铰链连接到车架上,可以自由地围绕它旋转。这个被称为反向摆的机械系统是控制理论中的经典问题。
如何做…
让我们看看我们如何开发一个基于 AI 的动态建模系统:
- 我们将使用已经为你提供的
dqn_cartpole.py文件作为参考。让我们先导入库:
import numpy as np
import gym
from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten
from keras.optimizers import Adam
from rl.agents.dqn import DQNAgent
from rl.policy import BoltzmannQPolicy
from rl.memory import SequentialMemory
- 现在,我们将定义和加载环境:
ENV_NAME = 'CartPole-v0'
env = gym.make(ENV_NAME)
- 为了设置
seed值,使用了 NumPy 库的random.seed()函数,如下所示:
np.random.seed(123)
env.seed(123)
- 现在,我们将提取代理可用的动作:
nb_actions = env.action_space.n
- 我们将使用
keras库构建一个简单的神经网络模型:
model = Sequential()
model.add(Flatten(input_shape=(1,) + env.observation_space.shape))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(nb_actions))
model.add(Activation('linear'))
print(model.summary())
- 将设置一个
memory变量和一个policy:
memory = SequentialMemory(limit=50000, window_length=1)
policy = BoltzmannQPolicy()
- 我们只需要定义代理:
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=10,
target_model_update=1e-2, policy=policy)
- 让我们继续编译和拟合模型:
dqn.compile(Adam(lr=1e-3), metrics=['mae'])
dqn.fit(env, nb_steps=1000, visualize=True, verbose=2)
- 在训练结束时,有必要保存获得的权重:
dqn.save_weights('dqn_{}_weights.h5f'.format(ENV_NAME), overwrite=True)
网络或整个结构的权重保存发生在HDF5文件中,这是一个高效且灵活的存储系统,支持复杂的多维数据集。
- 最后,我们将对算法进行 10 个回合的评估:
dqn.test(env, nb_episodes=5, visualize=True)
它是如何工作的…
在这个配方中,我们使用了keras–rl包来开发一个基于 AI 的动态建模系统。这个包在 Python 中实现了某些深度强化学习算法,并且与 Keras 的深度学习库无缝集成。
此外,keras-rl可以立即与 OpenAI Gym 一起工作。OpenAI Gym 包括一个不断增长的基准问题集合,展示了通用的接口和网站,人们可以在那里分享他们的结果并比较算法性能。这个库将在下一章中适当介绍——现在,我们将限制自己使用它。
还有更多…
这些选择并不限制keras-rl包的使用,从keras-rl的使用可以很容易地适应我们的需求。你可以使用内置的 Keras 回调和指标,或者定义其他的。因此,通过扩展一些简单的抽象类,很容易实现自己的环境,甚至算法。
参考以下内容
-
查看《Keras 强化学习项目》,作者 Giuseppe Ciaburro,Packt Publishing 出版社
-
查看教程:《深度强化学习》(来自 Google DeepMind):
icml.cc/2016/tutorials/deep_rl_tutorial.pdf -
参考书籍《深度强化学习》(作者徐王):
pure.tue.nl/ws/files/46933213/844320-1.pdf
双 Q-learning 的深度强化学习
在 Q-learning 算法中,使用与当前股票选择策略相同的 Q 函数评估未来的最大近似动作值。在某些情况下,这可能会高估动作值,从而减慢学习速度。DeepMind 研究人员在以下论文中提出了一个名为Double Q-learning的变体:Deep reinforcement learning with Double Q-learning,H van Hasselt,A Guez,和 D Silver,2016 年 3 月,在第三十届 AAAI 人工智能会议上。作为解决这个问题的方案,作者们提出了修改 Bellman 更新的方法。
准备就绪
在这个菜谱中,我们将使用 Double Q-learning 算法控制一个倒立摆系统。
如何做…
让我们看看如何使用 Double Q-learning 进行深度强化学习:
- 我们将使用已经为你提供的
DoubleDQNCartpole.py文件作为参考。让我们首先导入库:
import numpy as np
import gym
from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten
from keras.optimizers import Adam
from rl.agents.dqn import DQNAgent
from rl.policy import BoltzmannQPolicy
from rl.memory import SequentialMemory
- 现在,我们将定义和加载环境:
ENV_NAME = 'CartPole-v0'
env = gym.make(ENV_NAME)
- 要设置
seed值,使用 NumPy 库的random.seed()函数,如下所示:
np.random.seed(1)
env.seed(1)
- 现在,我们将提取代理可用的动作:
nb_actions = env.action_space.n
- 我们将使用
keras库构建一个简单的神经网络模型:
model = Sequential()
model.add(Flatten(input_shape=(1,) + env.observation_space.shape))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(nb_actions))
model.add(Activation('linear'))
print(model.summary())
- 将设置一个
memory变量和一个policy:
memory = SequentialMemory(limit=50000, window_length=1)
policy = BoltzmannQPolicy()
- 我们只需要定义代理:
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory,
nb_steps_warmup=10, enable_double_dqn=True,
target_model_update=1e-2,policy=policy)
要启用双网络,我们必须将enable_double_dqn选项设置为True。
- 让我们继续编译和拟合模型:
dqn.compile(Adam(lr=1e-3), metrics=['mae'])
dqn.fit(env, nb_steps=1000, visualize=True, verbose=2)
- 在训练结束时,有必要保存获得的权重:
dqn.save_weights('dqn_{}_weights.h5f'.format(ENV_NAME), overwrite=True)
网络或整个结构的权重保存发生在HDF5文件中,这是一个高效且灵活的存储系统,支持复杂的多元数据集。
- 最后,我们将对算法进行 10 个回合的评估:
dqn.test(env, nb_episodes=5, visualize=True)
它是如何工作的…
动作值的高估是由于在 Bellman 方程中使用的最大运算符。最大运算符在选择和评估动作时使用相同的值。现在,如果我们选择具有最大值的最佳动作,我们最终会选择一个次优动作(错误地假设了最大值)而不是最佳动作。我们可以通过有两个独立的 Q 函数来解决这个问题,每个 Q 函数都独立学习。一个 Q1 函数用于选择动作,另一个 Q2 函数用于评估动作。为此,只需更改目标函数。
还有更多…
实质上,以下两个网络被使用:
-
DQN 网络用于选择在下一个状态中采取的最佳动作(具有最高 Q 值的动作)
-
目标网络,用于计算在下一个状态下执行该动作的目标 Q 值
参见
-
查看Keras 强化学习项目,作者 Giuseppe Ciaburro,Packt Publishing
-
参考文献请见使用双重 Q 学习的深度强化学习:
www.aaai.org/ocs/index.php/AAAI/AAAI16/paper/download/12389/11847
深度 Q 网络算法与对抗 Q 学习
为了通过使我们的网络架构更接近强化学习的最后挑战之一来提高收敛速度,王等人提出了以下论文中 DQN 模型性能的明显改进:*对抗网络架构用于深度强化学习,Z Wang, T Schaul, M Hessel, H van Hasselt, M Lanctot, and N de Freitas, 2015, arXiv 预印本 arXiv:1511.06581。
准备工作
在这个菜谱中,我们将使用对抗 Q 学习算法控制一个倒立摆系统。
如何做…
让我们看看如何使用对抗 Q 学习执行深度 Q 网络算法:
- 我们将使用已提供的
DuelingDQNCartpole.py文件作为参考。让我们首先导入库:
import numpy as np
import gym
from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten
from keras.optimizers import Adam
from rl.agents.dqn import DQNAgent
from rl.policy import BoltzmannQPolicy
from rl.memory import SequentialMemory
- 现在,我们将定义和加载环境:
ENV_NAME = 'CartPole-v0'
env = gym.make(ENV_NAME)
- 要设置
seed值,使用 NumPy 库的random.seed()函数,如下所示:
np.random.seed(2)
env.seed(2)
- 现在,我们将提取智能体可用的动作:
nb_actions = env.action_space.n
- 我们将使用 Keras 库构建一个简单的神经网络模型:
model = Sequential()
model.add(Flatten(input_shape=(1,) + env.observation_space.shape))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(nb_actions))
model.add(Activation('linear'))
print(model.summary())
- 将设置一个
memory变量和一个policy:
memory = SequentialMemory(limit=50000, window_length=1)
policy = BoltzmannQPolicy()
我们只需定义智能体:
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory,
nb_steps_warmup=10, enable_dueling_network=True,
dueling_type='avg',target_model_update=1e-2,
policy=policy)
为了启用对抗网络,我们必须指定dueling_type为以下之一:'avg'、'max'或'naive'。
- 让我们继续编译和拟合模型:
dqn.compile(Adam(lr=1e-3), metrics=['mae'])
dqn.fit(env, nb_steps=1000, visualize=True, verbose=2)
- 训练结束时,有必要保存获得的权重:
dqn.save_weights('dqn_{}_weights.h5f'.format(ENV_NAME), overwrite=True)
网络或整个结构的权重保存发生在HDF5文件中,这是一个高效且灵活的存储系统,支持复杂的多元数据集。
- 最后,我们将对算法进行 10 个回合的评估:
dqn.test(env, nb_episodes=5, visualize=True)
它是如何工作的…
在强化学习中,函数 Q 和值函数扮演着基本角色:
-
Q 函数指定了智能体在状态
s中执行动作的好坏 -
值函数指定了智能体处于状态
s时的好坏
为了进一步提高 DQN 的性能,我们引入了一个新的函数,称为advantage函数,它可以定义为value函数和benefit函数之间的差异。benefit函数指定了智能体在执行动作方面相对于其他动作的好坏。
因此,价值函数指定了状态的好坏,而优势函数指定了动作的好坏。然后,这两个函数的组合告诉我们代理在某个状态下执行动作的好坏,这就是我们的Q函数。因此,我们可以将我们的Q函数定义为价值函数和优势函数的总和。
对抗式 DQN 本质上是一个 DQN,其中完全连接的最终层被分为两个流:
-
一个计算
价值函数 -
另一个计算
优势函数
最后,使用聚合级别将两个流合并以获得Q函数。
还有更多…
通过神经网络对价值函数的近似远非稳定。为了实现收敛,基本算法应通过引入避免振荡和发散的技术进行修改。
最重要的技术称为经验重放。在剧集期间,在每一步,代理的经验被存储在一个数据集中,称为重放记忆。在算法的内部循环中,不是基于刚刚执行的唯一转换在网络上进行训练,而是从重放记忆中随机选择转换的一个子集,并根据这个转换子集计算出的损失进行训练。
重放技术的经验,即从重放记忆中随机选择转换,消除了连续转换之间的相关性问题,并减少了不同更新之间的方差。
参见
-
查看 Giuseppe Ciaburro 的《Keras 强化学习项目》,Packt Publishing
-
更多信息请参阅 《深度强化学习的对抗网络架构》:
arxiv.org/abs/1511.06581
第十三章:深度神经网络
在本章中,我们将介绍以下食谱:
-
构建感知器
-
构建单层神经网络
-
使用深度神经网络构建深度神经网络
-
创建矢量量化器
-
构建用于序列数据分析的循环神经网络
-
可视化 OCR 数据库中的字符
-
使用神经网络构建光学字符识别器
-
在人工神经网络中实现优化算法
技术要求
为了处理本章中的食谱,你需要以下文件(可在 GitHub 上获取):
-
perceptron.py -
single_layer.py -
data_single_layer.txt -
deep_neural_network.py -
vector_quantization.py -
data_vq.txt -
recurrent_network.py -
visualize_characters.py -
ocr.py -
IrisClassifier.py
简介
我们的大脑非常擅长识别和识别事物。我们希望机器能够做到同样的。神经网络是一个模仿人类大脑以模拟我们的学习过程的框架。神经网络被设计用来从数据中学习并识别潜在的模式。与所有学习算法一样,神经网络处理数字。因此,如果我们想要实现任何涉及图像、文本、传感器等现实世界任务,我们必须在将它们输入神经网络之前将它们转换为数值格式。我们可以使用神经网络进行分类、聚类、生成和其他许多相关任务。
神经网络由神经元层组成。这些神经元模仿人类大脑中的生物神经元。每一层基本上是一组独立的神经元,它们与相邻层的神经元相连。输入层对应于我们提供的输入数据,输出层包含我们期望的输出。所有介于输入层和输出层之间的层都称为隐藏层。如果我们设计一个具有更多隐藏层的神经网络,那么我们给它更多的自由度,以更高的精度进行自我训练。
假设我们希望神经网络根据我们的需求对数据进行分类。为了使神经网络按预期工作,我们需要提供标记的训练数据。然后神经网络将通过优化成本函数来自我训练。这个成本函数是实际标签与神经网络预测标签之间的误差。我们持续迭代,直到误差低于某个阈值。
深度神经网络究竟是什么?深度神经网络是由许多隐藏层组成的神经网络。通常,这属于深度学习的范畴。这是一个致力于研究这些由多层组成的神经网络的领域,这些网络被用于许多垂直领域。
构建感知器
让我们以一个感知器开始我们的神经网络冒险。感知器是一个执行所有计算的单一神经元。这是一个非常简单的模型,但它是构建复杂神经网络的基石。以下是其外观:
神经元使用不同的权重组合输入,然后添加一个偏置值来计算输出。这是一个简单的线性方程,将输入值与感知器的输出联系起来。
准备工作
在这个菜谱中,我们将使用一个名为neurolab的库来定义一个具有两个输入的感知器。在继续之前,请确保您已安装它。您可以在pythonhosted.org/neurolab/install.html找到安装说明。让我们继续看看如何设计和开发这个神经网络。
如何操作...
让我们看看如何构建一个感知器:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
perceptron.py文件中):
import numpy as np
import neurolab as nl
import matplotlib.pyplot as plt
- 定义一些输入数据和相应的标签:
# Define input data
data = np.array([[0.3, 0.2], [0.1, 0.4], [0.4, 0.6], [0.9, 0.5]])
labels = np.array([[0], [0], [0], [1]])
- 让我们绘制这些数据以查看数据点的位置:
# Plot input data
plt.figure()
plt.scatter(data[:,0], data[:,1])
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.title('Input data')
- 让我们定义一个具有两个输入的
perceptron。此函数还需要我们指定输入数据中的最小值和最大值:
# Define a perceptron with 2 inputs;
# Each element of the list in the first argument
# specifies the min and max values of the inputs
perceptron = nl.net.newp([[0, 1],[0, 1]], 1)
- 让我们训练
perceptron模型:
# Train the perceptron
error = perceptron.train(data, labels, epochs=50, show=15, lr=0.01)
- 让我们绘制结果,如下所示:
# plot results
plt.figure()
plt.plot(error)
plt.xlabel('Number of epochs')
plt.ylabel('Training error')
plt.grid()
plt.title('Training error progress')
plt.show()
如果您运行此代码,您将看到两个图表。第一个图表显示了输入数据,如下所示:
第二个图表显示了训练错误进度,如下所示:
它是如何工作的...
在这个菜谱中,我们使用了一个执行所有计算的单一神经元。为了训练perceptron,以下参数被设置。epochs的数量指定了通过我们的训练数据集的完整遍历次数。show参数指定了我们希望显示进度的频率。lr参数指定了perceptron的学习率。它是算法在参数空间中搜索时的步长大小。如果这个值很大,那么算法可能会更快地移动,但可能会错过最优值。如果这个值很小,那么算法将击中最优值,但会较慢。因此,这是一个权衡;因此,我们选择0.01的值。
还有更多…
我们可以将perceptron的概念理解为任何接受多个输入并产生一个输出的东西。这是神经网络的最简单形式。1958 年,Frank Rosenblatt 提出了perceptron的概念,它是一个具有输入层和输出层以及旨在最小化错误的训练规则的物体。这个称为误差反向传播的学习函数根据网络的实际输出与给定输入之间的差异,依赖于网络的实际输出和期望输出,来改变连接权重(突触)。
参考以下内容
-
参考
neurolab库的官方文档:pythonhosted.org/neurolab/ -
*参考《神经网络基础入门》(来自威斯康星大学麦迪逊分校):
pages.cs.wisc.edu/~bolo/shipyard/neural/local.html
构建单层神经网络
在之前的配方中,构建感知器,我们学习了如何创建感知器;现在让我们创建一个单层神经网络。单层神经网络由单层中的多个神经元组成。总体而言,我们将有一个输入层、一个隐藏层和一个输出层,如下面的图所示:
准备工作
在这个配方中,我们将学习如何使用neurolab库创建单层神经网络。
如何做到这一点...
让我们看看如何构建一个单层神经网络:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
single_layer.py文件中给出):
import numpy as np
import matplotlib.pyplot as plt
import neurolab as nl
- 我们将使用
data_single_layer.txt文件中的数据。让我们加载这个文件:
# Define input data
input_file = 'data_single_layer.txt'
input_text = np.loadtxt(input_file)
data = input_text[:, 0:2]
labels = input_text[:, 2:]
- 让我们绘制输入数据:
# Plot input data
plt.figure()
plt.scatter(data[:,0], data[:,1])
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.title('Input data')
- 让我们提取最小值和最大值:
# Min and max values for each dimension
x_min, x_max = data[:,0].min(), data[:,0].max()
y_min, y_max = data[:,1].min(), data[:,1].max()
- 让我们定义一个具有隐藏层中两个神经元的单层神经网络:
# Define a single-layer neural network with 2 neurons;
# Each element in the list (first argument) specifies the
# min and max values of the inputs
single_layer_net = nl.net.newp([[x_min, x_max], [y_min, y_max]], 2)
- 训练神经网络 50 个周期:
# Train the neural network
error = single_layer_net.train(data, labels, epochs=50, show=20, lr=0.01)
- 按如下方式绘制结果:
# Plot results
plt.figure()
plt.plot(error)
plt.xlabel('Number of epochs')
plt.ylabel('Training error')
plt.title('Training error progress')
plt.grid()
plt.show()
- 让我们在新的测试数据上测试神经网络:
print(single_layer_net.sim([[0.3, 4.5]]))
print(single_layer_net.sim([[4.5, 0.5]]))
print(single_layer_net.sim([[4.3, 8]]))
如果你运行此代码,你将看到两个图表。第一个图表显示输入数据,如下所示:
第二个图表显示训练错误进度,如下所示:
你将在你的终端上看到以下打印信息,指示输入测试点属于何处:
[[ 0\. 0.]]
[[ 1\. 0.]]
[[ 1\. 1.]]
你可以根据我们的标签验证输出是否正确。
它是如何工作的...
单层神经网络具有以下架构:输入形成输入层,执行处理的中间层称为隐藏层,输出形成输出层。隐藏层可以将输入转换为所需的输出。理解隐藏层需要了解权重、偏置和激活函数。
更多内容...
权重对于将输入转换为影响输出的因素至关重要;它们是监控所有神经元如何影响彼此的数值参数。相关的概念类似于线性回归中的斜率,其中权重乘以输入以相加并形成输出。
偏置类似于添加到线性方程中的截距。它也是一个用于调节输出以及神经元输入加权总和的附加参数。
激活函数是一个数学函数,它将输入转换为输出并确定神经元接收的总信号。没有激活函数,神经网络将表现得像线性函数。
参见
-
参考官方的
neurolab库文档:pythonhosted.org/neurolab/ -
参考《神经网络入门》(来自耶鲁大学):
euler.stat.yale.edu/~tba3/stat665/lectures/lec12/lecture12.pdf
构建深度神经网络
我们现在准备好构建一个深度神经网络。深度神经网络由一个输入层、多个隐藏层和一个输出层组成。这看起来如下所示:
上述图表描述了一个具有一个输入层、一个隐藏层和一个输出层的多层神经网络。在深度神经网络中,输入层和输出层之间有许多隐藏层。
准备工作
在这个菜谱中,我们将构建一个深度神经网络。深度学习形成了一个具有众多隐藏层的先进神经网络。深度学习是一个庞大的主题,并且在构建人工智能中是一个重要的概念。在这个菜谱中,我们将使用生成的训练数据和定义一个具有两个隐藏层的多层神经网络。
如何操作...
让我们看看如何构建一个深度神经网络:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
deep_neural_network.py文件中):
import neurolab as nl
import numpy as np
import matplotlib.pyplot as plt
- 让我们定义参数以生成一些训练数据:
# Generate training data
min_value = -12
max_value = 12
num_datapoints = 90
- 这组训练数据将包括我们定义的函数,该函数将转换值。我们期望神经网络能够根据我们提供的输入和输出值自行学习:
x = np.linspace(min_value, max_value, num_datapoints)
y = 2 * np.square(x) + 7
y /= np.linalg.norm(y)
- 重新塑形数组:
data = x.reshape(num_datapoints, 1)
labels = y.reshape(num_datapoints, 1)
- 绘制输入数据:
plt.figure()
plt.scatter(data, labels)
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.title('Input data')
- 定义一个具有两个隐藏层的深度神经网络,其中每个隐藏层包含 10 个神经元,输出层包含一个神经元:
multilayer_net = nl.net.newff([[min_value, max_value]], [10, 10, 1])
- 将训练算法设置为梯度下降:
multilayer_net.trainf = nl.train.train_gd
- 训练网络:
error = multilayer_net.train(data, labels, epochs=800, show=100, goal=0.01)
- 预测训练输入的输出以查看性能:
predicted_output = multilayer_net.sim(data)
- 绘制训练错误:
plt.figure()
plt.plot(error)
plt.xlabel('Number of epochs')
plt.ylabel('Error')
plt.title('Training error progress')
- 让我们创建一组新的输入并运行神经网络以查看其性能:
x2 = np.linspace(min_value, max_value, num_datapoints * 2)
y2 = multilayer_net.sim(x2.reshape(x2.size,1)).reshape(x2.size)
y3 = predicted_output.reshape(num_datapoints)
- 绘制输出:
plt.figure()
plt.plot(x2, y2, '-', x, y, '.', x, y3, 'p')
plt.title('Ground truth vs predicted output')
plt.show()
如果你运行此代码,你将看到三个图表。第一个图表显示了输入数据,如下所示:
第二个图表显示了训练错误进度,如下所示:
第三个图表显示了神经网络的输出,如下所示:
你将在你的终端上看到以下内容:
Epoch: 100; Error: 4.634764957565494;
Epoch: 200; Error: 0.7675153737786798;
Epoch: 300; Error: 0.21543996465118723;
Epoch: 400; Error: 0.027738499953293118;
Epoch: 500; Error: 0.019145948877988192;
Epoch: 600; Error: 0.11296232736352653;
Epoch: 700; Error: 0.03446237629842832;
Epoch: 800; Error: 0.03022668735279662;
The maximum number of train epochs is reached
它是如何工作的...
在这个菜谱中,我们将使用生成的训练数据来训练一个具有两个隐藏层的多层深度神经网络。为了训练模型,使用了梯度下降算法。梯度下降是一种迭代方法,用于任何学习模型的错误校正。梯度下降方法是一个迭代过程,通过更新权重和偏差的误差乘以激活函数的导数(反向传播)。在这个方法中,最陡下降步长被替换为上一步的类似大小。梯度是曲线的斜率,因为它就是激活函数的导数。
更多内容...
每一步寻找梯度下降的目标是找到全局成本最小值,其中错误最低。这正是模型与数据拟合良好,预测更准确的地方。
参考以下内容
-
参考官方的
neurolab库文档:pythonhosted.org/neurolab/lib.html -
关于梯度下降的一些笔记(由斯图加特大学的 Marc Toussaint 提供):
ipvs.informatik.uni-stuttgart.de/mlr/marc/notes/gradientDescent.pdf
创建一个向量量化器
你也可以使用神经网络进行向量量化。向量量化是N维度的舍入。这在计算机视觉、NLP 和机器学习等多个领域都非常常用。
准备工作
在之前的菜谱中,我们已经讨论了向量量化的概念:使用向量量化压缩图像和使用视觉代码簿和向量量化创建特征。在这个菜谱中,我们将定义一个具有两层的人工神经网络——输入层有 10 个神经元,输出层有 4 个神经元。然后我们将使用这个网络将空间划分为四个区域。
在开始之前,你需要进行更改以修复库中的错误。你需要打开以下文件:neurolab | net.py。然后找到以下内容:
inx = np.floor (cn0 * pc.cumsum ()). astype (int)
将前面的行替换为以下内容:
inx = np.floor (cn0 * pc.cumsum ())
如何操作...
让我们看看如何创建一个向量量化器:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
vector_quantization.py文件中给出):
import numpy as np
import matplotlib.pyplot as plt
import neurolab as nl
- 让我们加载
data_vq.txt文件中的输入数据:
input_file = 'data_vq.txt'
input_text = np.loadtxt(input_file)
data = input_text[:, 0:2]
labels = input_text[:, 2:]
- 定义一个学习向量量化(LVQ)神经网络,具有两层。最后一个参数中的数组指定了每个输出的百分比权重(它们应该加起来为 1):
net = nl.net.newlvq(nl.tool.minmax(data), 10, [0.25, 0.25, 0.25, 0.25])
- 训练 LVQ 神经网络:
error = net.train(data, labels, epochs=100, goal=-1)
- 创建一个用于测试和可视化的值网格:
xx, yy = np.meshgrid(np.arange(0, 8, 0.2), np.arange(0, 8, 0.2))
xx.shape = xx.size, 1
yy.shape = yy.size, 1
input_grid = np.concatenate((xx, yy), axis=1)
- 在这个网格上评估网络:
output_grid = net.sim(input_grid)
- 定义我们数据中的四个类别:
class1 = data[labels[:,0] == 1]
class2 = data[labels[:,1] == 1]
class3 = data[labels[:,2] == 1]
class4 = data[labels[:,3] == 1]
- 定义所有这些类别的网格:
grid1 = input_grid[output_grid[:,0] == 1]
grid2 = input_grid[output_grid[:,1] == 1]
grid3 = input_grid[output_grid[:,2] == 1]
grid4 = input_grid[output_grid[:,3] == 1]
- 绘制输出:
plt.plot(class1[:,0], class1[:,1], 'ko', class2[:,0], class2[:,1], 'ko',
class3[:,0], class3[:,1], 'ko', class4[:,0], class4[:,1], 'ko')
plt.plot(grid1[:,0], grid1[:,1], 'b.', grid2[:,0], grid2[:,1], 'gx',
grid3[:,0], grid3[:,1], 'cs', grid4[:,0], grid4[:,1], 'ro')
plt.axis([0, 8, 0, 8])
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.title('Vector quantization using neural networks')
plt.show()
如果你运行这段代码,你会看到以下图表,其中空间被划分为区域。每个区域对应于空间中向量量化区域列表中的一个桶:
它是如何工作的...
在这个菜谱中,我们定义了一个具有两层的人工神经网络:输入层有 10 个神经元,输出层有 4 个神经元。这个神经网络首先被训练,然后用来将空间划分为四个区域。每个区域对应于空间中向量量化区域列表中的一个桶。
更多内容...
向量量化基于将大量点(向量)划分为显示相同数量点更接近它们的组。每个组由其质心点标识,这与大多数聚类算法类似。
参考以下内容
- 请参考
neurolab库的官方文档:pythonhosted.org/neurolab/
构建用于序列数据分析的循环神经网络
循环神经网络在分析序列和时间序列数据方面非常出色。循环神经网络(RNN)是一种信息双向流动的神经网络模型。换句话说,在前馈网络中,信号的传播仅以连续的方式进行,从输入到输出,而循环网络则不同。在它们中,这种传播也可以发生在前一个神经层之后的神经层之间,或者在同一层内的神经元之间,甚至是一个神经元与其自身之间。
准备工作
当我们处理序列和时间序列数据时,我们不能仅仅扩展通用模型。数据中的时间依赖性非常重要,我们需要在我们的模型中考虑这一点。让我们使用neurolab库构建一个循环神经网络。
如何操作...
让我们看看如何构建用于序列数据分析的循环神经网络:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
recurrent_network.py文件中,请查阅):
import numpy as np
import matplotlib.pyplot as plt
import neurolab as nl
- 定义一个基于输入参数创建波形的函数:
def create_waveform(num_points):
# Create train samples
data1 = 1 * np.cos(np.arange(0, num_points))
data2 = 2 * np.cos(np.arange(0, num_points))
data3 = 3 * np.cos(np.arange(0, num_points))
data4 = 4 * np.cos(np.arange(0, num_points))
- 为每个区间创建不同的振幅以创建随机波形:
# Create varying amplitudes
amp1 = np.ones(num_points)
amp2 = 4 + np.zeros(num_points)
amp3 = 2 * np.ones(num_points)
amp4 = 0.5 + np.zeros(num_points)
- 将数组组合以创建输出数组。这些数据对应于输入,振幅对应于标签:
data = np.array([data1, data2, data3, data4]).reshape(num_points * 4, 1)
amplitude = np.array([[amp1, amp2, amp3, amp4]]).reshape(num_points * 4, 1)
return data, amplitude
- 定义一个函数,用于在数据通过训练好的神经网络后绘制输出:
# Draw the output using the network
def draw_output(net, num_points_test):
data_test, amplitude_test = create_waveform(num_points_test)
output_test = net.sim(data_test)
plt.plot(amplitude_test.reshape(num_points_test * 4))
plt.plot(output_test.reshape(num_points_test * 4))
- 定义
main函数,并开始创建样本数据:
if __name__=='__main__':
# Get data
num_points = 30
data, amplitude = create_waveform(num_points)
- 创建一个具有两层结构的循环神经网络:
# Create network with 2 layers
net = nl.net.newelm([[-2, 2]], [10, 1], [nl.trans.TanSig(), nl.trans.PureLin()])
- 为每一层设置初始化函数:
# Set initialized functions and init
net.layers[0].initf = nl.init.InitRand([-0.1, 0.1], 'wb')
net.layers[1].initf= nl.init.InitRand([-0.1, 0.1], 'wb')
net.init()
- 训练循环神经网络:
# Training the recurrent neural network
error = net.train(data, amplitude, epochs=1000, show=100, goal=0.01)
- 从网络中计算训练数据的输出:
# Compute output from network
output = net.sim(data)
- 绘制训练误差图:
# Plot training results
plt.subplot(211)
plt.plot(error)
plt.xlabel('Number of epochs')
plt.ylabel('Error (MSE)')
- 绘制结果:
plt.subplot(212)
plt.plot(amplitude.reshape(num_points * 4))
plt.plot(output.reshape(num_points * 4))
plt.legend(['Ground truth', 'Predicted output'])
- 创建一个随机长度的波形,并查看网络能否预测它:
# Testing on unknown data at multiple scales
plt.figure()
plt.subplot(211)
draw_output(net, 74)
plt.xlim([0, 300])
- 创建另一个较短长度的波形,并查看网络能否预测它:
plt.subplot(212)
draw_output(net, 54)
plt.xlim([0, 300])
plt.show()
如果您运行此代码,您将看到两个图表。第一个图表显示了训练误差和训练数据的性能,如下所示:
第二个图显示了训练好的循环神经网络在任意长度序列上的表现,如下所示:
在您的终端上,您将看到以下内容:
Epoch: 100; Error: 1.2635865600014597;
Epoch: 200; Error: 0.4001584483592344;
Epoch: 300; Error: 0.06438997423142029;
Epoch: 400; Error: 0.03772354900253485;
Epoch: 500; Error: 0.031996105192696744;
Epoch: 600; Error: 0.011933337009068408;
Epoch: 700; Error: 0.012385370178600663;
Epoch: 800; Error: 0.01116995004102195;
Epoch: 900; Error: 0.011191016373572612;
Epoch: 1000; Error: 0.010584255803264013;
The maximum number of train epochs is reached
它是如何工作的...
在这个菜谱中,首先,我们创建了一个具有波形特性的合成信号,即表示给定时间波形形状的曲线。然后我们构建了一个循环神经网络,以查看网络能否预测随机长度的波形。
更多内容...
递归网络与前馈网络的区别在于它们与过去决策相关的反馈循环,因此将它们的输出暂时作为输入接受。可以说,递归网络具有记忆功能。序列中存在信息,并且这些信息被用来执行前馈网络无法执行的任务。
参考以下内容
-
参考官方
neurolab库文档:pythonhosted.org/neurolab/ -
参考耶鲁大学的递归神经网络(
euler.stat.yale.edu/~tba3/stat665/lectures/lec21/lecture21.pdf)
可视化 OCR 数据库中的字符
我们现在将探讨如何使用神经网络进行光学字符识别(OCR)。这指的是在图像中识别手写字符的过程。我们一直特别关注自动识别书写的问题,以便实现人与机器之间更简单的交互。特别是在过去几年里,由于强大的经济利益和现代计算机处理数据能力的不断提高,这个问题已经得到了有趣的发展,并且越来越高效的解决方案。特别是,一些国家,如日本,以及亚洲国家,在研究和财务资源方面投入了大量资金,以实现最先进的 OCR。
准备工作
在这个菜谱中,我们将显示数据集中包含的手写数字。我们将使用在ai.stanford.edu/~btaskar/ocr可用的数据集。下载后的默认文件名是letter.data。首先,让我们看看如何与数据交互并可视化它。
如何操作...
让我们看看如何可视化 OCR 数据库中的字符:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
visualize_characters.py文件中给出):
import cv2
import numpy as np
- 定义输入文件名:
# Load input data
input_file = 'letter.data'
- 定义可视化参数:
# Define visualization parameters
scaling_factor = 10
start_index = 6
end_index = -1
h, w = 16, 8
- 保持循环读取文件,直到用户按下Escape键。将行分割为制表符分隔的字符:
# Loop until you encounter the Esc key
with open(input_file, 'r') as f:
for line in f.readlines():
data = np.array([255*float(x) for x in line.split('\t')[start_index:end_index]])
- 将数组重塑为所需的形状,调整大小,并显示:
img = np.reshape(data, (h,w))
img_scaled = cv2.resize(img, None, fx=scaling_factor, fy=scaling_factor)
cv2.imshow('Image', img_scaled)
- 如果用户按下Escape,则中断循环:
c = cv2.waitKey()
if c == 27:
break
如果运行此代码,你将看到一个显示字符的窗口。
工作原理...
在这个菜谱中,我们展示了数据集中包含的手写数字。为此,执行以下任务:
-
加载输入数据
-
定义可视化参数
-
循环直到遇到Escape键
还有更多...
OCR 问题的方法基本上有两种类型:一种是基于模式匹配或模型比较,另一种是基于结构分析。通常,这两种技术会结合使用,并在识别和速度方面提供显著的结果。
参考以下内容
- 参考官方的
OpenCV库文档:opencv.org/
使用神经网络构建光学字符识别器
现在我们知道了如何与数据交互,让我们构建一个基于神经网络的 OCR 系统。图像分类和索引的操作基于对图像内容的自动分析,这构成了图像分析的主要应用领域。自动图像识别系统的目标是,通过数学模型和计算机实现,描述图像的内容,同时尽可能遵守人类视觉系统的原则。
准备工作
在这个食谱中,我们将构建一个基于神经网络的 OCR 系统。
如何做到这一点...
让我们看看如何使用神经网络构建光学字符识别器:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
ocr.py文件中给出):
import numpy as np
import neurolab as nl
- 定义输入文件名:
input_file = 'letter.data'
- 当我们处理涉及大量数据的神经网络时,训练需要花费很多时间。为了演示如何构建这个系统,我们将只使用
20个数据点:
num_datapoints = 20
-
如果你查看数据,你会看到有七个不同的字符在
前二十行。让我们定义它们:
orig_labels = 'omandig'
num_output = len(orig_labels)
- 我们将使用 90%的数据进行训练,剩余的 10%用于测试。定义训练和测试参数:
num_train = int(0.9 * num_datapoints)
num_test = num_datapoints - num_train
- 数据集文件中每行的起始和结束索引被指定:
start_index = 6
end_index = -1
- 创建数据集:
data = []
labels = []
with open(input_file, 'r') as f:
for line in f.readlines():
# Split the line tabwise
list_vals = line.split('\t')
- 添加一个错误检查来查看字符是否在我们的标签列表中(如果标签不在我们的地面真实标签中,则跳过它):
if list_vals[1] not in orig_labels:
continue
- 提取标签,并将其追加到主列表中:
label = np.zeros((num_output, 1))
label[orig_labels.index(list_vals[1])] = 1
labels.append(label)
- 提取字符,并将其追加到主列表中:
cur_char = np.array([float(x) for x in list_vals[start_index:end_index]])
data.append(cur_char)
- 一旦我们有足够的数据,就退出循环:
if len(data) >= num_datapoints:
break
- 将此数据转换为 NumPy 数组:
data = np.asfarray(data)
labels = np.array(labels).reshape(num_datapoints, num_output)
- 提取我们数据中的维数:
num_dims = len(data[0])
- 训练神经网络直到
10,000个 epoch:
net = nl.net.newff([[0, 1] for _ in range(len(data[0]))], [128, 16, num_output])
net.trainf = nl.train.train_gd
error = net.train(data[:num_train,:], labels[:num_train,:], epochs=10000,
show=100, goal=0.01)
- 预测测试输入的输出:
predicted_output = net.sim(data[num_train:, :])
print("Testing on unknown data:")
for i in range(num_test):
print("Original:", orig_labels[np.argmax(labels[i])])
print("Predicted:", orig_labels[np.argmax(predicted_output[i])])
- 如果你运行此代码,你将在训练结束时在你的终端上看到以下内容:
Epoch: 5000; Error: 0.032178530603536336;
Epoch: 5100; Error: 0.023122560947574727;
Epoch: 5200; Error: 0.040615342668364626;
Epoch: 5300; Error: 0.01686314983574041;
The goal of learning is reached
神经网络的输出如下所示:
Testing on unknown data:
Original: o
Predicted: o
Original: m
Predicted: m
它是如何工作的...
在这个食谱中,我们使用神经网络来识别手写数字。为此,执行以下任务:
-
加载和处理输入数据
-
创建数据集
-
将数据和标签转换为 NumPy 数组
-
提取维数
-
创建和训练神经网络
-
预测测试输入的输出
更多内容...
术语手写识别(HWR)指的是计算机接收并解释来自纸张文件、照片和触摸屏等来源的作为文本的清晰手写输入的能力。书写文本可以通过光学扫描(OCR)或智能文字识别在纸张上检测到。
参考以下内容
-
参考官方的
neurolab库文档:pythonhosted.org/neurolab/ -
参考光学字符识别(来自维基百科):
en.wikipedia.org/wiki/Optical_character_recognition -
参考手写识别(来自维基百科):
en.wikipedia.org/wiki/Handwriting_recognition
在人工神经网络中实现优化算法
到目前为止,我们已经构建了几个神经网络并获得了令人满意的总体性能。我们使用loss函数来评估模型的性能,这是一种数学方法,用来衡量我们的预测有多错误。为了提高基于神经网络的模型性能,在训练过程中,通过修改权重来尝试最小化loss函数,使我们的预测尽可能正确。为此,使用优化器:它们是调节模型参数的算法,根据loss函数返回的结果来更新模型。在实践中,优化器通过克服权重来以尽可能准确的形式塑造模型:loss函数告诉优化器它是在正确的还是错误的方向上移动。
准备工作
在这个菜谱中,我们将使用 Keras 库构建一个神经网络,并通过采用几个优化器来提高模型的性能。为此,将使用iris数据集。我指的是鸢尾花数据集,这是一个由英国统计学家和生物学家罗纳德·费希尔在 1936 年的论文《The use of multiple measurements in taxonomic problems as an example of linear discriminant analysis》中引入的多变量数据集。
如何做到这一点...
让我们看看如何在人工神经网络中实现优化算法:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
IrisClassifier.py文件中):
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from keras.models import Sequential
from keras.layers import Dense
- 从
sklearn数据集导入数据:
IrisData = load_iris()
- 将数据分为输入和目标:
X = IrisData.data
Y = IrisData.target.reshape(-1, 1)
对于目标,数据被转换为单列。
- 让我们将类别标签编码为
One Hot Encode:
Encoder = OneHotEncoder(sparse=False)
YHE = Encoder.fit_transform(Y)
- 将数据拆分为训练和测试:
XTrain, XTest, YTrain, YTest = train_test_split(X, YHE, test_size=0.30)
- 让我们构建模型:
model = Sequential()
- 将添加三个层:输入层、隐藏层和输出层。
model.add(Dense(10, input_shape=(4,), activation='relu'))
model.add(Dense(10, activation='relu'))
model.add(Dense(3, activation='softmax'))
- 让我们编译模型:
model.compile(optimizer='SGD',loss='categorical_crossentropy', metrics=['accuracy'])
以下三个参数被传递:
-
optimizer='SGD':随机梯度下降优化器。包括对动量、学习率衰减和 Nesterov 动量的支持。 -
loss='categorical_crossentropy':我们在这里使用了categorical_crossentropy参数。当使用categorical_crossentropy时,你的目标应该以分类格式(我们有三个类别;每个样本的目标必须是一个三维向量,除了对应于样本类别的索引处的 1 之外,其余都是 0)。 -
metrics=['accuracy']:metric是一个用于评估的函数在训练和测试期间评估你的模型性能。
- 让我们训练模型:
model.fit(XTrain, YTrain, verbose=2, batch_size=5, epochs=200)
- 最后,使用未见过的数据测试模型:
results = model.evaluate(XTest, YTest)
print('Final test set loss:' ,results[0])
print('Final test set accuracy:', results[1])
返回以下结果:
Final test set loss: 0.17724286781416998
Final test set accuracy: 0.9555555568801032
- 现在我们来看看如果我们使用不同的优化器会发生什么。为此,只需在编译方法中更改优化器参数,如下所示:
model.compile(optimizer='adam',loss='categorical_crossentropy', metrics=['accuracy'])
adam优化器是一种基于一阶、梯度优化随机目标函数的算法,它基于低阶矩的自适应估计。
返回以下结果:
Final test set loss: 0.0803464303414027
Final test set accuracy: 0.9777777777777777
它是如何工作的...
正如我们在构建深度神经网络配方中所说的,梯度下降是一种用于任何学习模型中错误校正的迭代方法。梯度下降方法是通过迭代更新权重和偏置与误差乘以激活函数的导数(反向传播)的过程。在此方法中,最陡下降步长被替换为上一步的类似大小。梯度是曲线的斜率,正如它是激活函数的导数一样。 SGD 优化器基于这种方法。
还有更多...
优化问题通常非常复杂,以至于无法通过解析方法确定解决方案。复杂性主要取决于变量的数量和约束条件,这些定义了问题的大小,然后是可能存在的非线性函数。只有当变量数量很少且函数极其简单时,才能找到解析解。在实践中,为了解决优化问题,必须求助于迭代算法,即给定当前解的近似值,通过适当的操作序列确定新的近似值。因此,从初始近似值开始,确定了一系列近似值。
参考以下内容
-
参考 Keras 优化器的官方文档:
keras.io/optimizers/ -
参考来自芝加哥大学的《深度神经网络优化》:
ttic.uchicago.edu/~shubhendu/Pages/Files/Lecture6_pauses.pdf