原文:
annas-archive.org/md5/1a114450f966ee5154c07d3ee2c9ce43译者:飞龙
第八章:语音识别
在本章中,我们将介绍以下食谱:
-
读取和绘制音频数据
-
将音频信号转换到频域
-
使用自定义参数生成音频信号
-
合成音乐
-
提取频域特征
-
构建隐马尔可夫模型(HMMs)
-
构建语音识别器
-
构建一个文本到语音(TTS)系统
技术要求
为了处理本章中的食谱,你需要以下文件(可在 GitHub 上找到):
-
read_plot.py -
input_read.wav -
freq_transform.py -
input_freq.wav -
generate.py -
synthesize_music.py -
extract_freq_features.py -
input_freq.wav -
speech_recognizer.py -
tts.py
介绍语音识别
语音识别指的是识别和理解口语的过程。输入以音频数据的形式出现,语音识别器将处理这些数据以从中提取有意义的信息。这有很多实际应用,例如语音控制设备、将口语转录成文字以及安全系统。
语音信号在本质上非常灵活。同一种语言中有许多不同的语音变体。语音有许多不同的元素,如语言、情感、音调、噪声和口音。严格定义一组可以构成语音的规则是困难的。即使有所有这些变化,人类也非常擅长相对轻松地理解所有这些。因此,我们需要机器以同样的方式理解语音。
在过去的几十年里,研究人员一直在研究语音的各个方面,例如识别说话者、理解单词、识别口音和翻译语音。在这些任务中,自动语音识别一直是许多研究人员的焦点。在本章中,我们将学习如何构建一个语音识别器。
读取和绘制音频数据
让我们看看如何读取音频文件并可视化信号。这将是一个好的起点,并将使我们更好地理解音频信号的基本结构。在我们开始之前,我们需要理解音频文件是实际音频信号的数字化版本。实际的音频信号是复杂的、连续值的波。为了保存数字版本,我们采样信号并将其转换为数字。例如,语音通常以 44,100 Hz 的频率采样。这意味着信号的每一秒被分解成 44,100 个部分,并且在这些时间戳处的值被存储。换句话说,你每 1/44,100 秒存储一个值。由于采样率很高,当我们通过媒体播放器听信号时,我们会感觉到信号是连续的。
准备工作
在这个食谱中,我们将使用wavfile包从.wav输入文件中读取音频文件。因此,我们将用图表绘制信号。
如何操作…
我们将使用以下步骤使用wavfile包读取和绘制音频:
- 创建一个新的 Python 文件并导入以下包(完整的代码在已提供的
read_plot.py文件中):
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
- 我们将使用
wavfile包从已提供的input_read.wav输入文件中读取音频文件:
# Read the input file
sampling_freq, audio = wavfile.read('input_read.wav')
- 让我们打印出这个信号的参数:
# Print the params
print('Shape:', audio.shape)
print('Datatype:', audio.dtype)
print('Duration:', round(audio.shape[0] / float(sampling_freq), 3), 'seconds')
- 音频信号以 16 位有符号整数数据存储;我们需要归一化这些值:
# Normalize the values
audio = audio / (2.**15)
- 现在,让我们提取前 30 个值以进行绘图,如下所示:
# Extract first 30 values for plotting
audio = audio[:30]
- x轴是时间轴。让我们考虑它应该使用采样频率因子进行缩放的事实来构建这个轴:
# Build the time axis
x_values = np.arange(0, len(audio), 1) / float(sampling_freq)
- 将单位转换为秒,如下所示:
# Convert to seconds
x_values *= 1000
- 现在我们按照以下方式绘制:
# Plotting the chopped audio signal
plt.plot(x_values, audio, color='black')
plt.xlabel('Time (ms)')
plt.ylabel('Amplitude')
plt.title('Audio signal')
plt.show()
- 完整的代码在
read_plot.py文件中。如果您运行此代码,您将看到以下信号:
您还将在您的终端上看到以下输出打印:
Shape: (132300,)
Datatype: int16
Duration: 3.0 seconds
它是如何工作的…
波形音频文件是不压缩的文件。该格式在 Windows 3.1 中引入,作为多媒体应用中使用的声音的标准格式。其技术规范和描述可以在多媒体编程接口和数据规范 1.0文档中找到(www.aelius.com/njh/wavemetatools/doc/riffmci.pdf)。它基于 1991 年引入的资源交换文件格式(RIFF)规范,构成了在 Windows 环境中运行的多媒体文件的元格式。RIFF 结构将数据块组织在称为块段的区域中,每个块段描述 WAV 文件的一个特征(如采样率、比特率和音频通道数),或包含样本的值(在这种情况下,我们指的是块数据)。块是 32 位(有一些例外)。
还有更多…
为了读取 WAV 文件,使用了scipy.io.wavfile.read()函数。此函数从 WAV 文件返回数据以及采样率。返回的采样率是一个 Python 整数,数据以与文件对应的 NumPy 数组形式返回。
参见
-
请参考
scipy.io.wavfile.read()函数的官方文档:docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.io.wavfile.read.html -
请参考WAV(来自维基百科):
en.wikipedia.org/wiki/WAV
将音频信号转换为频域
音频信号由不同频率、幅度和相位的正弦波复杂混合而成。正弦波也被称为正弦波。音频信号的频率内容中隐藏着大量信息。事实上,音频信号在很大程度上由其频率内容来表征。整个语音和音乐的世界都基于这一事实。在继续之前,你需要了解一些关于傅里叶变换的知识。
准备工作
在这个菜谱中,我们将看到如何将音频信号转换到频域。为此,我们使用numpy.fft.fft()函数。此函数使用高效的快速傅里叶变换(FFT)算法计算一维n点离散傅里叶变换(DFT)。
如何操作…
让我们看看如何将音频信号转换到频域:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
freq_transform.py文件中):
import numpy as np
from scipy.io import wavfile
import matplotlib.pyplot as plt
- 读取提供的
input_freq.wav文件:
# Read the input file
sampling_freq, audio = wavfile.read('input_freq.wav')
- 按以下方式归一化信号:
# Normalize the values
audio = audio / (2.**15)
- 音频信号只是一个 NumPy 数组。因此,你可以使用以下代码提取长度:
# Extract length
len_audio = len(audio)
- 让我们应用傅里叶变换。傅里叶变换后的信号沿中心对称,所以我们只需要取变换信号的半个部分。我们的最终目标是提取功率信号,因此我们需要在准备阶段对信号中的值进行平方:
# Apply Fourier transform
transformed_signal = np.fft.fft(audio)
half_length = np.ceil((len_audio + 1) / 2.0)
transformed_signal = abs(transformed_signal[0:int(half_length)])
transformed_signal /= float(len_audio)
transformed_signal **= 2
- 按以下方式提取信号的长度:
# Extract length of transformed signal
len_ts = len(transformed_signal)
- 我们需要根据信号的长度将信号加倍:
# Take care of even/odd cases
if len_audio % 2:
transformed_signal[1:len_ts] *= 2
else:
transformed_signal[1:len_ts-1] *= 2
- 使用以下公式提取功率信号:
# Extract power in dB
power = 10 * np.log10(transformed_signal)
- x 轴是时间轴;我们需要根据采样频率对其进行缩放,然后将其转换为秒:
# Build the time axis
x_values = np.arange(0, half_length, 1) * (sampling_freq / len_audio) / 1000.0
- 按以下方式绘制信号:
# Plot the figure
plt.figure()
plt.plot(x_values, power, color='black')
plt.xlabel('Freq (in kHz)')
plt.ylabel('Power (in dB)')
plt.show()
- 如果你运行这段代码,你将看到以下输出:
它是如何工作的…
声音频谱是声音水平的图形表示,通常以分贝(dB)为单位,取决于频率(Hz)。如果要分析的声音是所谓的纯音(随时间恒定的单一频率信号),例如,一个完美的正弦波,信号频谱将在正弦波频率处有一个单一成分,并有一定水平的 dB。在现实中,任何真实信号都由大量幅度随时间连续变化的正弦波成分组成。对于这些信号,由于信号能量中总有难以用正弦波表示的部分,因此无法分析纯音。事实上,根据傅里叶变换定理,将信号表示为正弦谐波成分之和,仅对平稳信号有效,而平稳信号通常并不对应于真实声音。
更多内容…
声音的频率分析基于傅里叶变换定理。也就是说,任何周期性信号都可以通过将具有多个整频的周期性信号的正弦波(称为谐波)相加来生成,这些整频是周期性信号频率的倍数(称为基频)。
参考内容
-
参考官方文档中的
numpy.fft.fft()函数:docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.fft.fft.html -
参考四阶变换:
www.thefouriertransform.com/ -
参考来自阿尔托大学的时频表示(
mycourses.aalto.fi/pluginfile.php/145214/mod_resource/content/3/slides_05_time-frequency_representations.pdf)
使用自定义参数生成音频信号
声音是一种特殊的波,其中由振动体(即声源)引起的压力变化在周围介质(通常是空气)中传播。以下是一些声源的例子:
-
乐器中,振动部分可以是击打的弦(如吉他),或者用弓拉(如小提琴)。
-
我们的声音是由从肺部出来的空气振动我们的声带而产生的。
-
任何导致空气移动的现象(如鸟的拍打翅膀、突破音障的飞机、爆炸的炸弹或锤子敲打砧子)都具有适当的物理特性。
为了通过电子设备再现声音,必须将其转换成模拟声音,即由将声波的机械能转换为电能的转换产生的电流。为了能够使用计算机中的声音信号,必须将模拟信号转换成由将模拟声音转换为表示为 0 和 1(比特)流的音频信号。
准备工作
在这个菜谱中,我们将使用 NumPy 生成音频信号。正如我们之前讨论的,音频信号是正弦波的复杂混合。因此,在生成我们自己的音频信号时,我们要牢记这一点。
如何操作…
让我们看看如何使用自定义参数生成音频信号:
- 创建一个新的 Python 文件,并导入以下包(完整的代码已包含在您提供的
generate.py文件中):
import numpy as np
import matplotlib.pyplot as plt
from scipy.io.wavfile import write
- 我们需要定义输出文件,生成的音频将存储在该文件中:
# File where the output will be saved
output_file = 'output_generated.wav'
- 现在让我们指定音频生成参数。我们想要生成一个 3 秒长的信号,采样频率为 44,100Hz,音调频率为 587Hz。时间轴上的值将从*-2pi到2pi*:
# Specify audio parameters
duration = 3 # seconds
sampling_freq = 44100 # Hz
tone_freq = 587
min_val = -2 * np.pi
max_val = 2 * np.pi
- 让我们生成时间轴和音频信号。音频信号是一个具有之前提到的参数的简单正弦波:
# Generate audio
t = np.linspace(min_val, max_val, duration * sampling_freq)
audio = np.sin(2 * np.pi * tone_freq * t)
- 现在,让我们向信号添加一些噪声:
# Add some noise
noise = 0.4 * np.random.rand(duration * sampling_freq)
audio += noise
- 在存储之前,我们需要将值缩放到 16 位整数:
# Scale it to 16-bit integer values
scaling_factor = pow(2,15) - 1
audio_normalized = audio / np.max(np.abs(audio))
audio_scaled = np.int16(audio_normalized * scaling_factor)
- 将此信号写入输出文件:
# Write to output file
write(output_file, sampling_freq, audio_scaled)
- 使用前 100 个值绘制信号:
# Extract first 100 values for plotting
audio = audio[:100]
- 按如下方式生成时间轴:
# Build the time axis
x_values = np.arange(0, len(audio), 1) / float(sampling_freq)
- 将时间轴转换为秒:
# Convert to seconds
x_values *= 1000
- 按如下方式绘制信号:
# Plotting the chopped audio signal
plt.plot(x_values, audio, color='black')
plt.xlabel('Time (ms)')
plt.ylabel('Amplitude')
plt.title('Audio signal')
plt.show()
- 如果您运行此代码,您将得到以下输出:
它是如何工作的…
在这个菜谱中,我们使用了 NumPy 库来生成音频信号。我们已经看到数字声音是一系列数字,因此生成声音只需要构建一个表示音乐音调的数组。首先,我们将文件名设置为输出保存的位置。然后,我们指定了音频参数。因此,我们使用正弦波生成了音频。然后我们添加了一些噪声,所以我们将它们缩放到 16 位整数值。最后,我们将信号写入输出文件。
更多…
在信号的编码中,分配给单个样本的每个值都用位表示。每个位对应于 6 dB 的动态范围。使用的位数越多,单个样本可以表示的 dB 范围就越高。
一些典型值如下:
-
每个样本 8 位对应于 256 个级别。
-
每个样本 16 位(CD 使用的数量)对应于 65,536 个级别。
参见
-
参考 NumPy 库的官方文档:
www.numpy.org/ -
参考来自维基百科的正弦波:
en.wikipedia.org/wiki/Sine_wave
音乐合成
在传统乐器中,声音是通过机械部件的振动产生的。在合成乐器中,振动是通过时间函数描述的,称为信号,它们表达了声压随时间的变化。声音合成是一个允许您人工生成声音的过程。确定声音音色的参数根据所使用的合成类型而有所不同,可以直接由作曲家提供,或者通过适当的输入设备上的操作,或者从现有声音的分析中得出。
准备工作
在这个菜谱中,我们将看到如何合成一些音乐。为此,我们将使用各种音符,如A、G和D,以及它们相应的频率,来生成一些简单的音乐。
如何做到这一点…
让我们看看如何合成一些音乐:
- 创建一个新的 Python 文件并导入以下包(完整的代码已包含在您提供的
synthesize_music.py文件中):
import json
import numpy as np
from scipy.io.wavfile import write
- 定义一个基于输入参数(如幅度和频率)合成音调的函数:
# Synthesize tone
def synthesizer(freq, duration, amp=1.0, sampling_freq=44100):
- 构建时间轴值:
# Build the time axis
t = np.linspace(0, duration, round(duration * sampling_freq))
- 使用输入参数(如幅度和频率)构建音频样本:
# Construct the audio signal
audio = amp * np.sin(2 * np.pi * freq * t)
return audio.astype(np.int16)
- 让我们定义主函数。您已经提供了一个名为
tone_freq_map.json的 JSON 文件,其中包含一些音符及其频率:
if __name__=='__main__':
tone_map_file = 'tone_freq_map.json'
- 按照以下方式加载该文件:
# Read the frequency map
with open(tone_map_file, 'r') as f:
tone_freq_map = json.loads(f.read())
- 现在,假设我们想要生成一个持续两秒的
G音符:
# Set input parameters to generate 'G' tone
input_tone = 'G'
duration = 2 # seconds
amplitude = 10000
sampling_freq = 44100 # Hz
- 使用以下参数调用该函数:
# Generate the tone
synthesized_tone = synthesizer(tone_freq_map[input_tone], duration, amplitude, sampling_freq)
- 将生成的信号写入输出文件,如下所示:
# Write to the output file
write('output_tone.wav', sampling_freq, synthesized_tone)
生成单个音调的.wav文件(output_tone.wav)。在媒体播放器中打开此文件并聆听。这就是G音符!
- 现在,让我们做一些更有趣的事情。让我们按顺序生成一些音符,以赋予它音乐感。定义一个音符序列及其持续时间(以秒为单位):
# Tone-duration sequence
tone_seq = [('D', 0.3), ('G', 0.6), ('C', 0.5), ('A', 0.3), ('Asharp', 0.7)]
- 遍历此列表并对每个元素调用合成器函数:
# Construct the audio signal based on the chord sequence
output = np.array([])
for item in tone_seq:
input_tone = item[0]
duration = item[1]
synthesized_tone = synthesizer(tone_freq_map[input_tone], duration, amplitude, sampling_freq)
output = np.append(output, synthesized_tone, axis=0)
output = output.astype(np.int16)
- 将信号写入输出文件:
# Write to the output file
write('output_tone_seq.wav', sampling_freq, output)
- 您现在可以在媒体播放器中打开
output_tone_seq.wav文件并聆听。您可以感受到音乐的魅力!
它是如何工作的…
音乐是一种难以简而言之的独创性和创造性的作品。音乐家阅读乐谱时,会识别出音符在乐谱上的位置。通过类比,我们可以将声音的合成视为已知音符特征频率的序列。在这个配方中,我们已经使用这个程序来合成一系列音符。
更多内容…
要人工生成音乐,使用合成器。所有合成器都有以下基本组件,它们协同工作以产生声音:
-
生成波形并改变音调的振荡器
-
一个滤波器用于在波形中去除某些频率以改变音色
-
一个放大器用于控制信号的音量
-
一个调制器用于创建效果
参考以下内容
-
请参考 NumPy 库的官方文档:
www.numpy.org/ -
请参考
scipy.io.wavfile.write()函数的官方文档:docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.io.wavfile.write.html -
请参考音乐音符频率(来自密歇根理工大学):
pages.mtu.edu/~suits/notefreqs.html -
请参考声音合成原理(来自萨福克大学):
www.acoustics.salford.ac.uk/acoustics_info/sound_synthesis/
提取频域特征
在将音频信号转换为频域的配方中,我们讨论了如何将信号转换为频域。在大多数现代语音识别系统中,人们使用频域特征。在将信号转换为频域后,你需要将其转换为可用的形式。梅尔频率倒谱系数(MFCC)是这样做的好方法。MFCC 取信号的功率谱,然后使用滤波器组和离散余弦变换(DCT)的组合来提取特征。
准备中
在这个配方中,我们将看到如何使用python_speech_features包来提取频域特征。你可以在python-speech-features.readthedocs.org/en/latest找到安装说明。所以,让我们看看如何提取 MFCC 特征。
如何操作…
让我们看看如何提取频域特征:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
extract_freq_features.py文件中,已经为你准备好了):
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
from python_speech_features import mfcc, logfbank
- 读取提供的
input_freq.wav输入文件:
# Read input sound file
sampling_freq, audio = wavfile.read("input_freq.wav")
- 按如下方式提取 MFCC 和滤波器组特征:
# Extract MFCC and Filter bank features
mfcc_features = mfcc(audio, sampling_freq)
filterbank_features = logfbank(audio, sampling_freq)
- 打印参数以查看生成了多少个窗口:
# Print parameters
print('MFCC:\nNumber of windows =', mfcc_features.shape[0])
print('Length of each feature =', mfcc_features.shape[1])
print('\nFilter bank:\nNumber of windows =', filterbank_features.shape[0])
print('Length of each feature =', filterbank_features.shape[1])
- 现在,让我们可视化 MFCC 特征。我们需要转换矩阵,以便时域是水平的:
# Plot the features
mfcc_features = mfcc_features.T
plt.matshow(mfcc_features)
plt.title('MFCC')
- 现在,让我们可视化滤波器组特征。同样,我们需要转换矩阵,以便时域是水平的:
filterbank_features = filterbank_features.T
plt.matshow(filterbank_features)
plt.title('Filter bank')
plt.show()
- 如果你运行此代码,你将在以下输出中看到 MFCC 特征:
滤波器组特征将如下所示:
你将在你的终端上看到以下输出:
MFCC:
Number of windows = 40
Length of each feature = 13
Filter bank:
Number of windows = 40
Length of each feature = 26
它是如何工作的…
倒谱是对信号的 dB 频谱应用傅里叶变换的结果。其名称来源于单词spectrum的前四个字母的倒置。它由 Bogert 等人于 1963 年定义。因此,信号的倒谱是信号傅里叶变换的对数值的傅里叶变换。
脉冲谱图用于分析信号频谱内容的变化率。最初,它是为了分析地震、爆炸和对雷达信号的响应而发明的。目前,它是音乐信息学中区分人声的非常有效的工具。对于这些应用,频谱首先通过梅尔尺度上的频带来转换。结果是梅尔频谱系数,或 MFCC。它用于语音识别和音高检测算法。
还有更多…
频谱分析用于将包含激励信息的信号部分与喉部执行的传递函数分离。提升作用(频域中的滤波)的目标是将激励信号从传递函数中分离出来。
参见
-
参考官方的
python_speech_features包文档:python-speech-features.readthedocs.io/en/latest/
构建 HMM
现在,我们已经准备好讨论语音识别了。我们将使用 HMM 进行语音识别;HMM 在建模时间序列数据方面非常出色。由于音频信号是一种时间序列信号,HMM 非常适合我们的需求。HMM 是一个表示观察序列概率分布的模型。我们假设输出是由隐藏状态生成的。因此,我们的目标是找到这些隐藏状态,以便我们可以建模信号。
准备工作
在这个菜谱中,我们将看到如何使用hmmlearn包构建一个 HMM。在继续之前,您需要安装hmmlearn包。让我们看看如何构建 HMM。
如何做到这一点…
让我们看看如何构建 HMM:
- 创建一个新的 Python 文件并定义一个类来建模 HMM(完整的代码在您已经提供的
speech_recognizer.py文件中):
# Class to handle all HMM related processing
class HMMTrainer(object):
- 让我们初始化类;我们将使用高斯 HMM 来建模我们的数据。
n_components参数定义了隐藏状态的数量。cov_type定义了传递矩阵中的协方差类型,而n_iter表示在停止训练之前它将进行的迭代次数:
def __init__(self, model_name='GaussianHMM', n_components=4, cov_type='diag', n_iter=1000):
前述参数的选择取决于具体问题。您需要了解您的数据,以便以明智的方式选择这些参数。
- 初始化变量,如下所示:
self.model_name = model_name
self.n_components = n_components
self.cov_type = cov_type
self.n_iter = n_iter
self.models = []
- 使用以下参数定义模型:
if self.model_name == 'GaussianHMM':
self.model = hmm.GaussianHMM(n_components=self.n_components,
covariance_type=self.cov_type, n_iter=self.n_iter)
else:
raise TypeError('Invalid model type')
- 输入数据是一个 NumPy 数组,其中每个元素都是一个由k个维度组成的特征向量:
# X is a 2D numpy array where each row is 13D
def train(self, X):
np.seterr(all='ignore')
self.models.append(self.model.fit(X))
- 定义一个基于模型提取分数的方法:
# Run the model on input data
def get_score(self, input_data):
return self.model.score(input_data)
- 我们构建了一个类来处理 HMM 训练和预测,但我们需要一些数据来看到它的实际应用。我们将在下一个菜谱中使用它来构建一个语音识别器。
它是如何工作的…
HMM 是一个模型,其中系统被假定为具有不可观察状态的马尔可夫过程。一个随机过程被称为马尔可夫过程,当选择一个特定的t实例进行观察时,该过程的演变,从t开始,只依赖于t,而不依赖于任何以前的实例。因此,一个过程是马尔可夫的,当给定观察时刻时,只有特定的实例决定了过程的未来演变,而这种演变不依赖于过去。
还有更多…
因此,HMM 是一种马尔可夫链,其中状态是不可直接观察的。更确切地说,可以理解为以下内容:
-
该链有多个状态
-
状态根据马尔可夫链演变
-
每个状态都根据一定的概率分布生成一个事件,这个概率分布只依赖于状态
-
事件是可观察的,但状态是不可观察的
HMMs 特别以其在语音演讲、手写、纹理识别和生物信息学中识别时间模式的应用而闻名。
参考以下内容
-
参考以下
hmmlearn包的官方文档:hmmlearn.readthedocs.io/en/latest/ -
参考以下教程:《隐马尔可夫模型教程》(由劳伦斯·R·拉比纳尔编写):
www.robots.ox.ac.uk/~vgg/rg/slides/hmm.pdf
构建语音识别器
语音识别是通过计算机识别人类口语的过程,随后通过计算机或更具体地说,通过专门的语音识别系统进行处理。语音识别系统用于电话应用(如自动呼叫中心)的自动语音应用、允许将演讲口述到计算机的听写系统、导航系统卫星的控制系统或汽车的语音命令电话。
准备工作
我们需要一个语音文件数据库来构建我们的语音识别器。我们将使用在code.google.com/archive/p/hmm-speech-recognition/downloads可用的数据库。该数据库包含 7 个不同的单词,每个单词都与 15 个音频文件相关联。下载 ZIP 文件并解压包含 Python 文件的文件夹(将包含数据的文件夹重命名为data)。这是一个小型数据集,但足以理解如何构建一个可以识别 7 个不同单词的语音识别器。我们需要为每个类别构建一个 HMM 模型。当我们想要识别新输入文件中的单词时,我们需要在该文件上运行所有模型并选择得分最高的模型。我们将使用我们在前面的配方中构建的 HMM 类。
如何做到这一点…
让我们看看如何构建一个语音识别器:
- 创建一个新的 Python 文件并导入以下包(完整代码已在提供的
speech_recognizer.py文件中):(完整代码位于提供的speech_recognizer.py文件中)
import os
import argparse
import numpy as np
from scipy.io import wavfile
from hmmlearn import hmm
from python_speech_features import mfcc
- 定义一个函数来解析命令行中的输入参数:
# Function to parse input arguments
def build_arg_parser():
parser = argparse.ArgumentParser(description='Trains the HMM classifier')
parser.add_argument("--input-folder", dest="input_folder", required=True,
help="Input folder containing the audio files in subfolders")
return parser
- 让我们使用在之前的构建 HMM菜谱中定义的
HMMTrainer类:
class HMMTrainer(object):
def __init__(self, model_name='GaussianHMM', n_components=4, cov_type='diag', n_iter=1000):
self.model_name = model_name
self.n_components = n_components
self.cov_type = cov_type
self.n_iter = n_iter
self.models = []
if self.model_name == 'GaussianHMM':
self.model = hmm.GaussianHMM(n_components=self.n_components,
covariance_type=self.cov_type, n_iter=self.n_iter)
else:
raise TypeError('Invalid model type')
# X is a 2D numpy array where each row is 13D
def train(self, X):
np.seterr(all='ignore')
self.models.append(self.model.fit(X))
# Run the model on input data
def get_score(self, input_data):
return self.model.score(input_data)
- 定义主函数,并解析输入参数:
if __name__=='__main__':
args = build_arg_parser().parse_args()
input_folder = args.input_folder
- 初始化将保存所有 HMM 模型的变量:
hmm_models = []
- 解析包含所有数据库音频文件的输入目录:
# Parse the input directory
for dirname in os.listdir(input_folder):
- 提取子文件夹的名称:
# Get the name of the subfolder
subfolder = os.path.join(input_folder, dirname)
if not os.path.isdir(subfolder):
continue
- 子文件夹的名称是此类别的标签;使用以下代码提取它:
# Extract the label
label = subfolder[subfolder.rfind('/') + 1:]
- 初始化训练变量:
# Initialize variables
X = np.array([])
y_words = []
- 遍历每个子文件夹中的音频文件列表:
# Iterate through the audio files (leaving 1 file for testing in each class)
for filename in [x for x in os.listdir(subfolder) if x.endswith('.wav')][:-1]:
- 按照以下方式读取每个音频文件:
# Read the input file
filepath = os.path.join(subfolder, filename)
sampling_freq, audio = wavfile.read(filepath)
- 按照以下方式提取 MFCC 特征:
# Extract MFCC features
mfcc_features = mfcc(audio, sampling_freq)
- 按照以下方式将此内容追加到
X变量中:
# Append to the variable X
if len(X) == 0:
X = mfcc_features
else:
X = np.append(X, mfcc_features, axis=0)
- 按照以下方式附加相应的标签:
# Append the label
y_words.append(label)
- 一旦从当前类中的所有文件中提取了特征,就训练并保存 HMM 模型。由于 HMM 是无监督学习的一个生成模型,我们不需要为每个类别构建 HMM 模型。我们明确假设将为每个类别构建单独的 HMM 模型:
# Train and save HMM model
hmm_trainer = HMMTrainer()
hmm_trainer.train(X)
hmm_models.append((hmm_trainer, label))
hmm_trainer = None
- 获取未用于训练的测试文件列表:
# Test files
input_files = [
'data/pineapple/pineapple15.wav',
'data/orange/orange15.wav',
'data/apple/apple15.wav',
'data/kiwi/kiwi15.wav'
]
- 按照以下方式解析输入文件:
# Classify input data
for input_file in input_files:
- 按照以下方式读取每个音频文件:
# Read input file
sampling_freq, audio = wavfile.read(input_file)
- 按照以下方式提取 MFCC 特征:
# Extract MFCC features
mfcc_features = mfcc(audio, sampling_freq)
- 定义存储最大分数和输出标签的变量:
# Define variables
max_score = float('-inf')
output_label = None
- 遍历所有模型并将输入文件通过每个模型运行:
# Iterate through all HMM models and pick
# the one with the highest score
for item in hmm_models:
hmm_model, label = item
- 提取分数并存储最大分数:
score = hmm_model.get_score(mfcc_features)
if score > max_score:
max_score = score
output_label = label
- 打印真实标签和预测标签:
# Print the output
print("True:", input_file[input_file.find('/')+1:input_file.rfind('/')])
print("Predicted:", output_label)
- 完整代码在
speech_recognizer.py文件中。使用以下命令运行此文件:
$ python speech_recognizer.py --input-folder data
以下结果将在您的终端返回:
True: pineapple
Predicted: data\pineapple
True: orange
Predicted: data\orange
True: apple
Predicted: data\apple
True: kiwi
Predicted: data\kiwi
工作原理…
在这个菜谱中,我们使用 HMM 创建了一个语音识别系统。为此,我们首先创建了一个分析输入参数的函数。然后,使用一个类来处理所有与 HMM 相关的处理。因此,我们已对输入数据进行分类,并预测了测试数据的标签。最后,我们打印了结果。
还有更多…
语音识别系统基于对适当处理的输入音频与系统训练期间创建的数据库的比较。在实践中,软件应用程序试图识别说话者所说的单词,在数据库中寻找相似的声音,并检查哪个单词对应。这自然是一个非常复杂的操作。此外,它不是在整词上进行的,而是在构成它们的音素上进行的。
参见
-
参考以下
hmmlearn包的官方文档:hmmlearn.readthedocs.io/en/latest/ -
参考以下
python_speech_features包的官方文档:python-speech-features.readthedocs.io/en/latest/ -
参考以下Argparse 教程:
docs.python.org/3/howto/argparse.html -
参考来自密西西比州立大学的《语音识别基础:短期课程》(Fundamentals of Speech Recognition: A Short Course):
www.iitg.ac.in/samudravijaya/tutorials/fundamentalOfASR_picone96.pdf
构建语音合成系统
语音合成是用于人工再现人类声音的技术。用于此目的的系统称为语音合成器,可以通过软件或硬件实现。由于它们能够将文本转换为语音,语音合成系统也被称为 TTS 系统。还有一些系统可以将音标转换为语音。
语音合成可以通过连接存储在数据库中的声音录音来实现。不同的语音合成系统根据存储的语音样本的大小而有所不同。也就是说,存储单个音素或双音素的系统可以在牺牲整体清晰度的前提下获得最大数量的组合,而其他为特定用途设计的系统会重复自己,记录整个单词或整个句子,以达到高质量的结果。
合成器可以使用声音特性和其他人类特征创建一个完全合成的声音。语音合成器的质量是根据其与人类声音的相似程度和可理解性水平来评估的。性能良好的语音转换程序在可访问性方面可以发挥重要作用;例如,允许视力受损或阅读障碍的人收听计算机上编写的文档。对于这类应用(自 1980 年代初以来),许多操作系统都包括了语音合成功能。
准备工作
在这个菜谱中,我们将介绍一个 Python 库,它允许我们创建语音合成系统。我们将运行pyttsx跨平台语音合成包装库。
如何操作…
让我们看看如何构建一个语音合成系统:
- 首先,我们必须为 Python 3 库安装
pyttsx(Python 3 的离线 TTS)及其相关依赖项:
$ pip install pyttsx3
- 为了避免可能的错误,还需要安装
pypiwin32库:
$ pip install pypiwin32
- 创建一个新的 Python 文件并导入
pyttsx3包(完整的代码已包含在您提供的tts.py文件中):
import pyttsx3;
- 我们创建一个将使用指定驱动程序的引擎实例:
engine = pyttsx3.init();
- 要更改语速,请使用以下命令:
rate = engine.getProperty('rate')
engine.setProperty('rate', rate-50)
- 要更改说话者的声音,请使用以下命令:
voices = engine.getProperty('voices')
engine.setProperty('voice', 'TTS_MS_EN-US_ZIRA_11.0')
- 现在,我们将使用
say方法来排队一个命令,以朗读一个语音。语音输出将根据队列中此命令之前设置的属性:
engine.say("You are reading the Python Machine Learning Cookbook");
engine.say("I hope you like it.");
- 最后,我们将调用
runAndWait()方法。此方法在处理所有当前排队的命令时将阻塞,并适当地调用引擎通知的回调。当在此调用之前排队的所有命令从队列中清空时,它将返回:
engine.runAndWait();
在这个阶段,将会有不同的声音来朗读我们提供的文本。
它是如何工作的…
语音合成系统或引擎由两部分组成:前端和后端。前端部分负责将文本转换为音标符号,而后端部分则解释这些音标符号并将它们朗读出来,从而将它们转换成人工语音。前端有两个关键功能;首先,它对书面文本进行分析,将所有数字、缩写和缩写词转换成全词。这一预处理步骤被称为分词。第二个功能包括将每个单词转换成其对应的音标符号,并对修改后的文本进行语言分析,将其细分为韵律单位,即介词、句子和句号。将音标转录分配给单词的过程被称为从文本到音素的转换,或从图形到音素的转换。
更多内容…
经典 TTS 系统的演变被称为WaveNet,它似乎知道如何说话,能够准确发音,并能流畅地朗读整个句子。WaveNet 是一个生成原始音频的深度神经网络。它是由位于伦敦的人工智能公司 DeepMind 的研究人员创建的。WaveNet 使用一个深度生成模型来模拟声波,可以模仿任何人类的语音。WaveNet 朗读的句子听起来比更先进的 TTS 更接近人类语音,相似度提高了 50%。为了证明这一点,创建了英语和普通话的样本,并使用平均意见评分(MOS)系统,这是音频评估的标准,将人工智能生成的样本与正常 TTS、参数化 TTS 以及真实语音的样本进行了比较。
参考以下内容
-
参考官方的
pyttsx3包文档:pyttsx3.readthedocs.io/en/latest/index.html -
参考由 D. Sasirekha 和 E. Chandra 编写的《文本到语音:简单教程》(
pdfs.semanticscholar.org/e7ad/2a63458653ac965fe349fe375eb8e2b70b02.pdf) -
参考来自谷歌 DeepMind 的《WaveNet:原始音频的生成模型》(
deepmind.com/blog/wavenet-generative-model-raw-audio/)
第九章:解构时间序列和顺序数据
在本章中,我们将介绍以下食谱:
-
将数据转换为时间序列格式
-
切分时间序列数据
-
对时间序列数据进行操作
-
从时间序列数据中提取统计数据
-
为顺序数据构建 HMM
-
为顺序文本数据构建 CRF
-
分析股票市场数据
-
使用 RNN 预测时间序列数据
技术要求
为了处理本章中的食谱,你需要以下文件(可在 GitHub 上找到):
-
convert_to_timeseries.py -
data_timeseries.txt -
slicing_data.py -
operating_on_data.py -
extract_stats.py -
hmm.py -
`data_hmm.txt` -
crf.py -
AmazonStock.py -
AMZN.csv -
LSTMstock.py
介绍时间序列
时间序列数据基本上是一系列随时间收集的测量值。这些测量值是在预定变量和固定时间间隔下进行的。时间序列数据的一个主要特征是顺序很重要!
我们收集的观察结果列表按时间线排序,它们出现的顺序在很大程度上揭示了潜在的规律。如果你改变顺序,这将会完全改变数据的意义。顺序数据是一个广义的概念,包括任何以顺序形式出现的数据,包括时间序列数据。
我们的目标是构建一个描述时间序列或任何一般序列模式的模型。这些模型用于描述时间序列模式的重要特征。我们可以使用这些模型来解释过去如何影响未来。我们还可以使用它们来查看两个数据集如何相关联,预测未来的值,或者控制基于某些指标的给定变量。
为了可视化时间序列数据,我们倾向于使用折线图或条形图来绘制。时间序列数据分析常用于金融、信号处理、天气预报、轨迹预测、预测地震或任何需要处理时间数据的领域。我们在时间序列和顺序数据分析中构建的模型应考虑数据的顺序并提取邻居之间的关系。让我们继续查看一些食谱,以分析 Python 中的时间序列和顺序数据。
将数据转换为时间序列格式
时间序列构成了一系列现象的观察结果,这些观察是在连续的瞬间或时间间隔内进行的,通常(即使不是必然的)是均匀分布或长度相同。因此,时间是时间序列分析中的一个基本参数。因此,我们首先必须对代表某种现象长期观察的数据进行操作有一定的信心。
准备工作
我们将首先了解如何将一系列观察结果转换为时间序列数据并可视化它。我们将使用一个名为pandas的库来分析时间序列数据。在继续之前,请确保你已经安装了pandas。你可以在以下链接找到pandas的安装说明:pandas.pydata.org/pandas-docs/stable/install.html。
如何操作…
让我们看看如何将数据转换为时间序列格式:
- 创建一个新的 Python 文件(完整的代码在提供的
convert_to_timeseries.py文件中),并导入以下包:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
- 让我们定义一个函数,该函数读取输入文件并将顺序观察结果转换为时间索引数据:
def convert_data_to_timeseries(input_file, column, verbose=False):
- 我们将使用一个包含四个列的文本文件。第一列表示年份,第二列表示月份,第三和第四列表示数据。让我们将其加载到一个 NumPy 数组中:
# Load the input file
data = np.loadtxt(input_file, delimiter=',')
- 由于这是按时间顺序排列的,第一行包含开始日期,最后一行包含结束日期。让我们提取这个数据集的开始和结束日期:
# Extract the start and end dates
start_date = str(int(data[0,0])) + '-' + str(int(data[0,1]))
end_date = str(int(data[-1,0] + 1)) + '-' + str(int(data[-1,1] % 12 + 1))
- 此函数还有一个
verbose模式。因此,如果将其设置为true,它将打印一些信息。让我们打印出开始和结束日期:
if verbose:
print("Start date =", start_date)
print("End date =", end_date)
- 让我们创建一个包含每月间隔日期序列的
pandas变量:
# Create a date sequence with monthly intervals
dates = pd.date_range(start_date, end_date, freq='M')
- 我们的下一步是将给定的列转换为时间序列数据。你可以使用月份和年份(而不是索引)来访问此数据:
# Convert the data into time series data
data_timeseries = pd.Series(data[:,column], index=dates)
- 使用
verbose模式打印出前 10 个元素:
if verbose:
print("Time series data:\n", data_timeseries[:10])
- 返回时间索引变量,如下所示:
return data_timeseries
- 定义主函数,如下所示:
if __name__=='__main__':
- 我们将使用已经提供给你的
data_timeseries.txt文件:
# Input file containing data
input_file = 'data_timeseries.txt'
- 从这个文本文件中加载第三列并将其转换为时间序列数据:
# Load input data
column_num = 2
data_timeseries = convert_data_to_timeseries(input_file, column_num)
pandas库提供了一个很好的绘图函数,可以直接在变量上运行:
# Plot the time series data
data_timeseries.plot()
plt.title('Input data')
plt.show()
如果你运行代码,你将看到以下输出:
工作原理…
在这个菜谱中,我们学习了如何将一系列观察结果转换为时间序列数据并显示它。为此,我们首先以.txt格式加载了输入文件,因此我们提取了开始和结束日期。然后,我们创建了一个按月间隔的日期序列,并将数据转换为时间序列数据。最后,我们绘制了时间序列数据。
更多内容…
pandas库特别适合处理所有领域的时间序列数据,这得益于其广泛的特性和功能。这些特性利用了 NumPy 的datetime64和timedelta64变量,以及来自其他 Python 库(如scikits.timeseries)的大量功能。这些特性使得pandas在处理时间序列数据时特别高效。
相关内容
-
参考官方的
pandas时间序列和日期功能文档:pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html -
时间序列基础(来自宾夕法尼亚州立大学):
newonlinecourses.science.psu.edu/stat510/node/41/
切片时间序列数据
切片和切块是两个指代数据集的术语,意味着将大的 DataFrame 划分成更小的部分或从不同的角度检查它们以更好地理解。这个术语来自烹饪术语,描述了每个厨师都必须掌握的两种刀工。切片意味着切割,而切块意味着将食物切成非常小且均匀的部分,这两个动作通常按顺序执行。在数据分析中,切片和切块术语通常涉及系统地减少大型数据集以提取更多信息。
准备工作
在这个菜谱中,我们将学习如何切片时间序列数据。这将帮助你从时间序列数据的不同区间中提取信息。我们将学习如何使用日期来处理我们数据的子集。
如何操作…
让我们看看如何执行切片时间序列数据:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
slicing_data.py文件中给出):
import numpy as np
from convert_to_timeseries import convert_data_to_timeseries
在这里,convert_to_timeseries 是我们在前一个菜谱 将数据转换成时间序列格式 中定义的函数,它读取输入文件并将顺序观测值转换为时间索引数据。
- 我们将使用与前一个菜谱中相同的文本文件(
data_timeseries.txt)来切片和切块数据:
# Input file containing data
input_file = 'data_timeseries.txt'
- 我们将只提取第三列:
# Load data
column_num = 2
data_timeseries = convert_data_to_timeseries(input_file, column_num)
- 假设我们想要提取给定
start和end年份之间的数据。让我们定义如下:
# Plot within a certain year range
start = '2000'
end = '2015'
- 在给定的年份范围内绘制数据:
plt.figure()
data_timeseries[start:end].plot()
plt.title('Data from ' + start + ' to ' + end)
- 我们也可以根据一定的月份范围来切片数据:
# Plot within a certain range of dates
start = '2008-1'
end = '2008-12'
- 按以下方式绘制数据:
plt.figure()
data_timeseries[start:end].plot()
plt.title('Data from ' + start + ' to ' + end)
plt.show()
如果你运行代码,你会看到以下截图:
以下截图显示了一个较小的时间框架;因此,它看起来像是我们将其放大了:
工作原理…
在这个菜谱中,我们学习了如何分解时间序列数据。首先,我们导入了包含在 .txt 文件中的数据。这些数据使用我们在前一个菜谱中定义的函数转换成了时间序列格式。因此,我们首先在一定的年份范围内绘制了数据,然后在一定的日期范围内绘制了数据。
更多内容…
为了将数据转换成时间序列格式,我们使用了 pandas 库。这个库在处理时间序列数据方面特别高效。
相关内容
-
请参考 pandas 时间序列和日期功能的官方文档:
pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html -
时间序列(由牛津大学的 Gesine Reinert 教授撰写):
www.stats.ox.ac.uk/~reinert/time/notesht10short.pdf
对时间序列数据进行操作
现在我们知道了如何切片数据和提取各种子集,让我们讨论如何对时间序列数据进行操作。您可以通过许多不同的方式过滤数据。pandas 库允许您以任何您想要的方式对时间序列数据进行操作。
准备工作
在这个菜谱中,我们将使用 .txt 文件中的数据并加载它。然后,我们将使用某个阈值过滤数据,以提取仅满足特定要求的起始数据集的一部分。
如何操作…
让我们看看我们如何对时间序列数据进行操作:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
operating_on_data.py文件中给出):
import pandas as pd
import matplotlib.pyplot as plt
from convert_to_timeseries import convert_data_to_timeseries
在这里,convert_to_timeseries 是我们在上一个菜谱中定义的函数,将数据转换为时间序列格式,该函数读取输入文件并将顺序观测值转换为时间索引数据。
- 我们将使用与之前菜谱中相同的文本文件(记住,Python 从位置 0 开始列出数据,因此第三列和第四列的索引是 2 和 3):
# Input file containing data
input_file = 'data_timeseries.txt'
- 在这个
.txt文件中,我们将使用第三列和第四列(记住,Python 从位置 0 开始列出数据,所以第三列和第四列的索引是 2 和 3):
# Load data
data1 = convert_data_to_timeseries(input_file, 2)
data2 = convert_data_to_timeseries(input_file, 3)
- 将数据转换为
pandasDataFrame:
dataframe = pd.DataFrame({'first': data1, 'second': data2})
- 在给定年份范围内绘制数据:
# Plot data
dataframe['1952':'1955'].plot()
plt.title('Data overlapped on top of each other')
- 假设我们想绘制给定年份范围内两列之间的差异。我们可以使用以下行来完成此操作:
# Plot the difference
plt.figure()
difference = dataframe['1952':'1955']['first'] - dataframe['1952':'1955']['second']
difference.plot()
plt.title('Difference (first - second)')
- 如果我们想根据第一列和第二列的不同条件过滤数据,我们只需指定这些条件并绘制如下:
# When 'first' is greater than a certain threshold
# and 'second' is smaller than a certain threshold
dataframe[(dataframe['first'] > 60) & (dataframe['second'] < 20)].plot(style='o')
plt.title('first > 60 and second < 20')
plt.show()
如果您运行前面的代码,第一个输出将如下所示:
第二张输出截图表示差异,如下所示:
第三张输出截图表示过滤后的数据,如下所示:
工作原理…
在这个菜谱中,我们学习了如何过滤时间序列中的数据。首先,我们在两个年份之间(从 1952 年到 1955 年)绘制了数据。然后,我们在特定时间间隔内(从 1952 年到 1955 年)绘制了两个列中包含的数据之间的差异。最后,我们使用某个阈值绘制数据,以提取仅满足特定要求的起始数据集的一部分——特别是当第一列大于 60 且第二列小于 20 时。
还有更多…
要同时进行两列过滤,使用了&运算符。&(与)运算符是两个命题之间的逻辑运算符(布尔运算符),表示逻辑合取。给定两个命题,A 和 B,逻辑合取确定第三个命题,C*,*,只有当两个命题都为真时,该命题才为真。
参见
- 请参阅时间序列分析的基本概念讲座(来自洛桑大学):
math.unice.fr/~frapetti/CorsoP/chapitre_1_part_1_IMEA_1.pdf
从时间序列数据中提取统计数据
我们想要分析时间序列数据的主要原因是从中提取有趣的统计数据。这提供了有关数据性质的大量信息。
准备工作
在本食谱中,我们将探讨如何提取一些统计数据。
如何操作…
让我们看看如何从时间序列数据中提取统计数据:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
extract_stats.py文件中给出):
import pandas as pd
import matplotlib.pyplot as plt
from convert_to_timeseries import convert_data_to_timeseries
convert_to_timeseries函数是我们之前在将数据转换为时间序列格式食谱中定义的函数,它读取输入文件并将顺序观测值转换为时间索引数据。
- 我们将使用之前食谱中用于分析的相同文本文件(
data_timeseries.txt):
# Input file containing data
input_file = 'data_timeseries.txt'
- 加载数据的列(第三和第四列):
# Load data
data1 = convert_data_to_timeseries(input_file, 2)
data2 = convert_data_to_timeseries(input_file, 3)
- 创建一个
pandas数据结构来存储这些数据。这个 DataFrame 就像一个有键和值的字典:
dataframe = pd.DataFrame({'first': data1, 'second': data2})
- 现在让我们开始提取一些统计数据。要提取最大值和最小值,请使用以下代码:
# Print max and min
print('Maximum:\n', dataframe.max())
print('Minimum:\n', dataframe.min())
- 要打印数据的平均值或仅打印行平均值,请使用以下代码:
# Print mean
print('Mean:\n', dataframe.mean())
print('Mean row-wise:\n', dataframe.mean(1)[:10])
- 滚动平均值是时间序列处理中常用的重要统计量。最著名的应用之一是平滑信号以去除噪声。“滚动平均值”指的是在时间尺度上滑动窗口中信号的均值计算。让我们考虑窗口大小为
24并绘制如下:
# Plot rolling mean
DFMean = dataframe.rolling(window=24).mean()
plt.plot(DFMean)
- 相关系数在理解数据性质方面很有用,如下所示:
# Print correlation coefficients
print('Correlation coefficients:\n', dataframe.corr())
- 让我们使用窗口大小为
60来绘制这个:
# Plot rolling correlation
plt.figure()
DFCorr= dataframe.rolling(window=60).corr(pairwise=False)
plt.plot(DFCorr)
plt.show()
如果运行前面的代码,滚动平均值将如下所示:
第二个输出指示滚动相关性(以下输出是在matplotlib窗口中执行缩放矩形操作的结果):
- 在终端的上半部分,您将看到打印出的最大值、最小值和平均值,如下所示:
- 在终端的下半部分,您将看到打印出行平均值统计和相关性系数,如下所示:
工作原理…
在这个菜谱中,我们学习了如何提取一些统计数据。我们首先计算了从数据集中提取的两个列的最小值、最大值和平均值。然后,我们计算了 DataFrame 前 10 行的每一行的平均值。最后,我们对两个特征进行了相关性分析。
更多内容…
为了进行相关性分析,使用了pandas.DataFrame.corr函数。此函数计算列之间的成对相关性,排除 N/A 或空值。以下方法可用:
-
pearson:这是标准的相关系数 -
kendall:这是肯德尔 tau相关系数 -
spearman:这是斯皮尔曼秩相关系数
相关内容
-
参考官方文档中的
pandas.DataFrame.corr函数:pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.corr.html -
相关性(来自 SRM 大学):
www.srmuniv.ac.in/sites/default/files/downloads/CORRELATION.pdf
为序列数据构建 HMMs
隐藏马尔可夫模型(HMMs)特别适合于序列数据分析问题。它们在语音分析、金融、词序列、天气预报等领域得到广泛应用。
任何产生输出序列的数据源都可以产生模式。请注意,HMMs 是生成模型,这意味着一旦它们学会了底层结构,它们就可以生成数据。HMMs 在其基本形式中不能区分类别。这与可以学习区分类别但不能生成数据的判别模型形成对比。
准备工作
假设我们想要预测明天的天气是晴朗、寒冷还是雨天。为此,我们查看所有参数,如温度、压力等,而底层状态是隐藏的。在这里,底层状态指的是三个可用的选项:晴朗、寒冷或雨天。
如何操作…
让我们看看如何为序列数据构建 HMMs:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
hmm.py文件中给出):
import numpy as np
import matplotlib.pyplot as plt
from hmmlearn.hmm import GaussianHMM
- 我们将使用名为
data_hmm.txt的文件中的数据,该文件已经提供给你。该文件包含逗号分隔的行。每行包含三个值:一个年份、一个月和一段浮点数据。让我们将其加载到一个 NumPy 数组中:
# Load data from input file
input_file = 'data_hmm.txt'
data = np.loadtxt(input_file, delimiter=',')
- 让我们将数据列堆叠起来进行分析。实际上我们不需要技术上列堆叠,因为这只有一个列。然而,如果你有多个列要分析,可以使用以下结构:
# Arrange data for training
X = np.column_stack([data[:,2]])
- 使用四个组件创建和训练 HMM。组件的数量是一个超参数,我们必须选择。在这里,通过选择四个,我们说数据正在使用四个潜在状态生成。我们将看到性能如何随着这个参数的变化而变化:
# Create and train Gaussian HMM
print("Training HMM....")
num_components = 4
model = GaussianHMM(n_components=num_components, covariance_type="diag", n_iter=1000)
model.fit(X)
- 运行预测器以获取隐藏状态:
# Predict the hidden states of HMM
hidden_states = model.predict(X)
- 计算隐藏状态的均值和方差:
print("Means and variances of hidden states:")
for i in range(model.n_components):
print("Hidden state", i+1)
print("Mean =", round(model.means_[i][0], 3))
print("Variance =", round(np.diag(model.covars_[i])[0], 3))
- 如我们之前讨论的,HMMs 是生成模型。所以,让我们生成,例如,
1000个样本并绘制这个:
# Generate data using model
num_samples = 1000
samples, _ = model.sample(num_samples)
plt.plot(np.arange(num_samples), samples[:,0], c='black')
plt.title('Number of components = ' + str(num_components))
plt.show()
完整的代码在提供的hmm.py文件中给出。如果你运行前面的代码,你会看到以下输出:
- 你可以通过实验
n_components参数来观察随着它的增加曲线如何变得更加平滑。你基本上可以给它更多的自由度来训练和定制,允许更多的隐藏状态。如果你将其增加到8,你会看到以下输出:
- 如果你将其增加到
12,它将变得更加平滑:
在终端中,你会得到以下输出:
Training HMM....
Means and variances of hidden states:
Hidden state 1
Mean = 5.592
Variance = 0.253
Hidden state 2
Mean = 1.098
Variance = 0.004
Hidden state 3
Mean = 7.102
Variance = 0.003
Hidden state 4
Mean = 3.098
Variance = 0.003
Hidden state 5
Mean = 4.104
Variance = 0.003
它是如何工作的…
HMM 是一种模型,其中被建模的系统被假定为具有未观察状态的马尔可夫过程。一个随机过程被称为马尔可夫的,当在观察中选择了某个特定的t实例后,该过程的演变,从t开始,只依赖于t,而不以任何方式依赖于之前的实例。因此,一个过程是马尔可夫的,当给定观察时刻时,只有这个实例决定了过程的未来演变,而这一演变不依赖于过去。在这个菜谱中,我们学习了如何使用 HMMs 生成时间序列。
还有更多…
在这个菜谱中,我们使用了hmmlearn来构建和训练 HMMs,它实现了 HMMs。HMM 是一种生成概率模型,其中使用一系列隐藏内部状态计算一系列可观察变量。隐藏状态不能直接观察到。
参见
-
更多信息请参考
hmmlearn库的官方文档:hmmlearn.readthedocs.io/en/latest/ -
《隐藏马尔可夫模型教程》(由牛津大学的劳伦斯·R·拉比纳尔编写):
www.robots.ox.ac.uk/~vgg/rg/slides/hmm.pdf
构建用于序列文本数据的 CRFs
条件随机场(CRFs)是用于分析结构化数据的概率模型。它们通常用于标记和分割序列数据。与生成模型 HMMs 相比,CRFs 是判别模型。CRFs 被广泛用于分析序列、股票、语音、单词等。在这些模型中,给定一个特定的标记观察序列,我们定义这个序列的条件概率分布。这与 HMMs 不同,在 HMMs 中,我们定义标签和观察序列的联合分布。
准备工作
在这个菜谱中,我们将使用一个名为pystruct的库来构建和训练 CRFs。确保你在继续之前安装它。你可以找到安装说明在pystruct.github.io/installation.html。
如何做到这一点…
让我们看看我们如何为序列文本数据构建 CRFs:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
crf.py文件中给出):
import argparse
import numpy as np
from pystruct.datasets import load_letters
from pystruct.models import ChainCRF
from pystruct.learners import FrankWolfeSSVM
- 定义一个参数解析器以接受
C值作为输入参数。在这里,C是一个超参数,它控制你希望你的模型有多具体,同时不失泛化能力:
def build_arg_parser():
parser = argparse.ArgumentParser(description='Trains the CRF classifier')
parser.add_argument("--c-value", dest="c_value", required=False, type=float,
default=1.0, help="The C value that will be used for training")
return parser
- 定义一个
class来处理所有与 CRF 相关的处理:
class CRFTrainer(object):
- 定义一个
init函数来初始化值:
def __init__(self, c_value, classifier_name='ChainCRF'):
self.c_value = c_value
self.classifier_name = classifier_name
- 我们将使用
ChainCRF来分析数据。我们需要添加一个错误检查,如下所示:
if self.classifier_name == 'ChainCRF':
model = ChainCRF()
- 定义我们将与 CRF 模型一起使用的分类器。我们将使用一种 SVM 类型来实现这一点:
self.clf = FrankWolfeSSVM(model=model, C=self.c_value, max_iter=50)
else:
raise TypeError('Invalid classifier type')
- 加载
letters数据集。这个数据集包括分割后的字母及其相关的特征向量。我们不会分析图像,因为我们已经有了特征向量。每个单词的第一个字母已经被移除,所以我们只剩下小写字母:
def load_data(self):
letters = load_letters()
- 将数据和标签加载到各自的变量中:
X, y, folds = letters['data'], letters['labels'], letters['folds']
X, y = np.array(X), np.array(y)
return X, y, folds
- 定义一种训练方法,如下所示:
# X is a numpy array of samples where each sample
# has the shape (n_letters, n_features)
def train(self, X_train, y_train):
self.clf.fit(X_train, y_train)
- 定义一个方法来评估模型的表现:
def evaluate(self, X_test, y_test):
return self.clf.score(X_test, y_test)
- 定义一个方法来分类新数据:
# Run the classifier on input data
def classify(self, input_data):
return self.clf.predict(input_data)[0]
- 字母被索引在一个编号数组中。为了检查输出并使其可读,我们需要将这些数字转换成字母。定义一个函数来做这件事:
def decoder(arr):
alphabets = 'abcdefghijklmnopqrstuvwxyz'
output = ''
for i in arr:
output += alphabets[i]
return output
- 定义主函数并解析输入参数:
if __name__=='__main__':
args = build_arg_parser().parse_args()
c_value = args.c_value
- 使用类和
C值初始化变量:
crf = CRFTrainer(c_value)
- 加载
letters数据:
X, y, folds = crf.load_data()
- 将数据分为训练集和测试集:
X_train, X_test = X[folds == 1], X[folds != 1]
y_train, y_test = y[folds == 1], y[folds != 1]
- 按照以下方式训练 CRF 模型:
print("Training the CRF model...")
crf.train(X_train, y_train)
- 评估 CRF 模型的表现:
score = crf.evaluate(X_test, y_test)
print("Accuracy score =", str(round(score*100, 2)) + '%')
- 让我们随机取一个测试向量并使用模型进行预测:
print("True label =", decoder(y_test[0]))
predicted_output = crf.classify([X_test[0]])
print("Predicted output =", decoder(predicted_output))
- 如果你运行前面的代码,你将在你的终端上得到以下输出。正如我们所见,单词应该是
commanding。CRF 在预测所有字母方面做得相当不错:
Training the CRF model...
Accuracy score = 77.93%
True label = ommanding
Predicted output = ommanging
它是如何工作的…
HMMs 假设当前输出在统计上与先前输出独立。这是 HMMs 确保推理以稳健方式工作所必需的。然而,这个假设并不总是必须成立的!在时间序列设置中,当前输出往往依赖于先前输出。CRFs 相对于 HMMs 的主要优势之一是它们本质上是有条件的,这意味着我们不会假设输出观测之间有任何独立性。使用 CRFs 而不是 HMMs 还有其他一些优势。CRFs 在许多应用中往往优于 HMMs,例如语言学、生物信息学、语音分析和如此等等。在这个菜谱中,我们将学习如何使用 CRFs 来分析字母序列。
还有更多…
PyStruct 是一个易于使用的机器学习算法的结构化库。它实现了最大间隔和感知器方法。PyStruct 中实现的算法示例包括 CRFs、最大间隔马尔可夫 随机字段(M3Ns)和结构化 SVMs。
参见
-
更多信息请参考
pystruct库的官方文档:pystruct.github.io/ -
查看来自圣母大学的条件随机字段讲座(
www3.nd.edu/~dchiang/teaching/nlp/2015/notes/chapter8v1.pdf)
分析股票市场数据
股票市场一直是一个非常热门的话题;这是因为股票市场趋势涉及真正令人印象深刻的交易量。这个话题引起的兴趣显然与通过股票市场标题的良好预测获得财富的机会有关。购买股票价格与卖出股票价格之间的正差价意味着投资者获得了收益。但是,正如我们所知,股票市场的表现取决于多个因素。
准备工作
在这个菜谱中,我们将探讨如何分析一家非常流行的公司的股票价格:我指的是亚马逊,一家位于华盛顿州西雅图的美国电子商务公司,它是世界上最大的互联网公司。
如何操作…
让我们看看我们如何分析股票市场数据:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
AmazonStock.py文件中):
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from random import seed
- 从提供的
AMZN.csv文件中获取股票报价:
seed(0)
Data = pd.read_csv('AMZN.csv',header=0, usecols=['Date', 'Close'],parse_dates=True,index_col='Date')
- 要提取导入数据集的初步信息,我们可以调用
info()函数:
print(Data.info())
返回以下结果:
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 4529 entries, 2000-11-21 to 2018-11-21
Data columns (total 1 columns):
Close 4529 non-null float64
dtypes: float64(1)
memory usage: 70.8 KB
None
此函数打印有关 DataFrame 的信息,包括索引和dtypes列、non-null值和memory usage。
- 要显示导入的 DataFrame 的前五行,我们可以使用
head()函数,如下所示:
print(Data.head())
此函数根据位置返回对象的前 n 行。这对于快速测试对象中是否包含正确的数据类型很有用。默认情况下(如果省略 n),显示前五行。以下结果被返回:
Close
Date
2000-11-21 24.2500
2000-11-22 25.1875
2000-11-24 28.9375
2000-11-27 28.0000
2000-11-28 25.0312
- 要预览其中包含的数据,我们可以计算一系列基本统计量。为此,我们将使用以下方式的
describe()函数:
print(Data.describe())
describe() 函数生成描述性统计量,总结数据集的中心趋势、离散程度和分布形式,排除 NaN 值。此函数分析数值和对象序列,以及混合数据类型的 DataFrame 列集。以下结果被返回:
Close
count 4529.000000
mean 290.353723
std 407.211585
min 5.970000
25% 39.849998
50% 117.889999
75% 327.440002
max 2039.510010
- 现在,我们将对时间序列进行初步的视觉探索性分析:
plt.figure(figsize=(10,5))
plt.plot(Data)
plt.show()
在以下图表中,显示了从 2000 年 11 月 21 日到 2018 年 11 月 21 日的亚马逊股票价格:
从先前图表的分析中,我们可以看到价格随着时间的推移显著增加。特别是,从 2015 年开始,这种增长显示出指数趋势。
- 现在,让我们尝试更深入地了解亚马逊股票随时间记录的变化。为了在 Python 中计算百分比变化,我们将使用
pct_change()函数。此函数返回给定数量的期间的百分比变化:
DataPCh = Data.pct_change()
我们刚刚计算的结果与回报的概念相符。
- 要计算回报的对数,我们将使用来自
numpy的log()函数:
LogReturns = np.log(1 + DataPCh)
print(LogReturns.tail(10))
tail() 函数根据位置从对象返回最后 n 行。这对于快速验证数据很有用——例如,在排序或追加行之后。以下值被返回(LogReturns 对象的最后 10 行):
Close
Date
2018-11-08 -0.000330
2018-11-09 -0.024504
2018-11-12 -0.045140
2018-11-13 -0.003476
2018-11-14 -0.019913
2018-11-15 0.012696
2018-11-16 -0.016204
2018-11-19 -0.052251
2018-11-20 -0.011191
2018-11-21 0.014123
- 现在,我们将绘制我们计算出的回报对数的图表:
plt.figure(figsize=(10,5))
plt.plot(LogReturns)
plt.show()
如我们之前所做的那样,我们首先设置图表的维度,然后我们将绘制图表,最后我们将可视化它。以下图表显示了回报的对数:
它是如何工作的…
要研究一个现象的演变,仅有一个时间序列图是不够的;我们需要比较不同时间点的现象强度,即计算从一个时期到另一个时期的强度变化。此外,分析相邻时间段内现象变化的趋势可能也很有趣。我们用 Y1,…, Yt,…, Yn 表示时间序列。时间序列是变量的实验观察的按时间顺序记录,例如价格趋势、股票市场指数、价差和失业率。因此,它是一系列按时间顺序排列的数据,我们希望从中提取信息以表征观察到的现象,并预测未来的值。
两个不同时间之间发生的变化(让我们用 t 和 t + 1 来表示)可以使用以下比率来衡量:
此指数是一个百分比比率,称为百分比变化。特别是,这是现象 Y 在时间 t + 1 相对于之前时间 t 的百分比变化率。这种方法提供了关于数据随时间变化更详细的解释。使用这种技术,我们可以追踪个别股票和大型市场指数的价格,以及比较不同货币的价值。
更多内容…
与价格相比,使用回报的优势在于标准化,这使得我们能够以可比较的指标来衡量所有变量,从而允许评估两个或更多变量之间的分析关系。
参见
- 参考官方文档的
pandas.DataFrame.pct_change函数:pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.pct_change.html
使用 RNN 预测时序数据
长短期记忆(LSTM)是循环神经网络(RNNs)的一种特定架构。RNNs 基于对过去事件记忆的需求;这种行为在普通网络中是不可能的,这就是为什么 RNNs 被用于经典网络无法产生结果的领域,例如预测与先前数据相关的时序数据(天气、报价等)。
LSTM 网络由相互连接的细胞(LSTM 块)组成。每个细胞反过来又由三种类型的端口组成:输入门、输出门和遗忘门。它们分别对细胞内存执行写入、读取和重置功能,因此 LSTM 模块能够调节存储和删除的内容。这得益于存在各种称为门的元素,这些门由一个 sigmoid 神经网络层和一个逐点乘积组成。每个门的输出在(0,1)范围内,表示其中流动的信息百分比。
准备工作
在这个菜谱中,我们将探讨如何将 LSTM 模型应用于预测一家非常受欢迎的公司的未来股价:我指的是总部位于华盛顿州西雅图的美国电子商务公司亚马逊,它是世界上最大的互联网公司。
如何操作…
让我们看看如何使用 RNN 来预测时序数据:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
LSTMstock.py文件中给出)。文件的前一部分在之前的菜谱中已经处理过,分析股票市场数据。我们只报告它以完整算法:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from random import seed
seed(0)
Data = pd.read_csv('AMZN.csv',header=0, usecols=['Date', 'Close'],parse_dates=True,index_col='Date')
- 在训练 LSTM 算法之前对数据进行缩放是一种良好的实践。通过缩放,消除了数据单位,这使得你可以轻松地比较来自不同位置的数据。在这个例子中,我们将使用最小-最大方法(通常称为特征缩放)来获取所有缩放数据在[0, 1]范围内的数据。为了执行特征缩放,我们可以使用
sklearn库中可用的预处理包:
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
DataScaled = scaler.fit_transform(Data)
- 现在,让我们将数据分为训练和测试模型两部分。训练和测试模型是进一步使用模型进行预测性分析的基础。给定一个包含 4,529 行数据的数据集,我们将其按方便的比例(例如 70:30)分割,并将 3,170 行分配给训练,1,359 行分配给测试:
np.random.seed(7)
TrainLen = int(len(DataScaled) * 0.70)
TestLen = len(DataScaled) - TrainLen
TrainData = DataScaled[0:TrainLen,:]
TestData = DataScaled[TrainLen:len(DataScaled),:]
print(len(TrainData), len(TestData))
以下结果被返回:
3170 1359
- 现在,我们需要输入和输出以训练和测试我们的网络。很明显,输入由数据集中现有的数据表示。因此,我们必须构建我们的输出;我们将通过假设我们想要预测时间t + 1的亚马逊股价相对于时间t存储的值来做到这一点。一个循环网络具有记忆,这是通过固定所谓的步长来维持的。步长是关于反向传播在计算权重更新时的梯度时回溯多长时间的问题。这样,我们设置
TimeStep=1。然后,我们定义一个函数,它接受一个数据集和一个时间步长,然后返回输入和输出数据:
def DatasetCreation(dataset, TimeStep=1):
DataX, DataY = [], []
for i in range(len(dataset)-TimeStep-1):
a = dataset[i:(i+TimeStep), 0]
DataX.append(a)
DataY.append(dataset[i + TimeStep, 0])
return np.array(DataX), np.array(DataY)
在这个函数中,dataX = Input = data(t)是输入变量,DataY = output = data(t + 1)是下一个时间段的预测值。
- 让我们使用这个函数来设置我们在下一阶段(网络建模)中将要使用的训练和测试数据集:
TimeStep = 1
TrainX, TrainY = DatasetCreation(TrainData, TimeStep)
TestX, TestY = DatasetCreation(TestData, TimeStep)
在 LSTM/RNN 网络中,每个 LSTM 层的输入必须包含以下信息:
-
观测数:收集到的观测数
-
时间步长:时间步长是样本中的观测点
-
特征:每步一个特征
因此,有必要为那些经典网络预见的添加一个时间维度。因此,输入形状如下:
(观测数,时间步数,每步特征数)
这样,每个 LSTM 层的输入就变成了三维的。
- 为了将输入数据集转换为 3D 形式,我们将使用
np.reshape()函数,如下所示:
TrainX = np.reshape(TrainX, (TrainX.shape[0], 1, TrainX.shape[1]))
TestX = np.reshape(TestX, (TestX.shape[0], 1, TestX.shape[1]))
- 现在数据已经处于正确的格式,是时候创建模型了。让我们先导入库:
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense
- 我们将使用
Sequential模型,即层的一个线性堆叠。为了创建一个序列模型,我们必须将一个层实例的列表传递给构造函数。我们也可以通过add()方法简单地添加层:
model = Sequential()
model.add(LSTM(256, input_shape=(1, TimeStep)))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='mean_squared_error', optimizer='adam',metrics=['accuracy'])
model.fit(TrainX, TrainY, epochs=100, batch_size=1, verbose=1)
model.summary()
以下结果被打印出来:
- 为了评估我们刚刚调整的模型的表现,我们可以使用
evaluate()函数,如下所示:
score = model.evaluate(TrainX, TrainY, verbose=0)
print('Keras Model Loss = ',score[0])
print('Keras Model Accuracy = ',score[1])
前面的函数在测试模式下显示模型的损失值和指标值。这是分批计算的。以下结果被返回:
Keras Model Loss = 2.4628453362992094e-06
Keras Model Accuracy = 0.0003156565656565657
- 模型现在已准备好使用。因此,我们可以用它来执行我们的预测:
TrainPred = model.predict(TrainX)
TestPred = model.predict(TestX)
- 预测必须以原始形式报告,以便可以与实际值进行比较:
TrainPred = scaler.inverse_transform(TrainPred)
TrainY = scaler.inverse_transform([TrainY])
TestPred = scaler.inverse_transform(TestPred)
TestY = scaler.inverse_transform([TestY])
- 为了验证数据的正确预测,我们现在可以通过绘制适当的图表来可视化结果。为了正确显示时间序列,需要预测偏移。这个操作必须在训练集和测试集上执行:
TrainPredictPlot = np.empty_like(DataScaled)
TrainPredictPlot[:, :] = np.nan
TrainPredictPlot[1:len(TrainPred)+1, :] = TrainPred
- 如我们之前所述,然后必须在测试集上执行相同的操作:
TestPredictPlot = np.empty_like(DataScaled)
TestPredictPlot[:, :] = np.nan
TestPredictPlot[len(TrainPred)+(1*2)+1:len(DataScaled)-1, :] = TestPred
- 最后,我们必须绘制实际数据和预测结果:
plt.figure(figsize=(10,5))
plt.plot(scaler.inverse_transform(DataScaled))
plt.plot(TrainPredictPlot)
plt.plot(TestPredictPlot)
plt.show()
下面的截图显示了实际数据和预测结果:
它是如何工作的…
在本食谱的开头,我们说 LSTM 模块能够调节存储和删除的内容。这要归功于存在各种称为门的元素,它们由一个 sigmoid 神经网络层和一个逐点乘积组成。LSTM 模块的第一部分决定从单元格中删除哪些信息。门接收输入并为每个单元格状态返回一个介于 0 和 1 之间的值。门输出可以取两个值:
-
0:完全重置单元格状态 -
1:单元格值的总存储
数据存储分为两个阶段:
-
第一部分委托给一个名为输入门层的 sigmoid 层;它执行一个操作,确定哪些值需要更新。
-
第二阶段则委托给一个
tanh层,该层创建一个待更新的值向量。为了创建一个更新的值集,将两个层的输出结合起来。
最后,结果将由一个 sigmoid 层给出,该层确定哪些单元格部分将对输出做出贡献,并从当前单元格状态中,通过tanh函数过滤,以获得-1 到 1 的范围。此操作的输出乘以 sigmoid 层的值,以便只给出所需的输出。
更多内容…
RNN 是一种神经网络,其中存在信息双向流动。换句话说,在前馈网络中,信号的传播只在单一方向上连续进行,从输入到输出,而循环网络则不同。在循环网络中,这种传播也可以发生在前一个神经层之后的神经层之间,属于同一层的神经元之间,甚至是一个神经元与其自身之间。
参见
-
请参阅 Keras 库的官方文档:
keras.io/ -
请参阅耶鲁大学的循环神经网络(
euler.stat.yale.edu/~tba3/stat665/lectures/lec21/lecture21.pdf) -
参考来自威斯康星大学麦迪逊分校的 长短期记忆(
pages.cs.wisc.edu/~shavlik/cs638/lectureNotes/Long%20Short-Term%20Memory%20Networks.pdf)
第十章:分析图像内容
本章我们将涵盖以下食谱:
-
使用 OpenCV-Python 操作图像
-
检测边缘
-
直方图均衡化
-
检测角点
-
检测 SIFT 特征点
-
构建星特征检测器
-
使用视觉代码簿和向量量化创建特征
-
使用超随机森林训练图像分类器
-
构建物体识别器
-
使用 LightGBM 进行图像分类
技术要求
要浏览本章的食谱,你需要以下文件(可在 GitHub 上找到):
-
operating_on_images.py -
capri.jpg -
edge_detector.py -
chair.jpg -
histogram_equalizer.py -
sunrise.jpg -
corner_detector.py -
box.png -
feature_detector.py -
table.jpg -
star_detector.py -
trainer.py -
object_recognizer.py -
LightgbmClassifier.py
介绍计算机视觉
计算机视觉是一个研究如何处理、分析和理解视觉数据内容的领域。在图像内容分析中,我们使用大量的计算机视觉算法来构建我们对图像中对象的了解。计算机视觉涵盖了图像分析的各个方面,如物体识别、形状分析、姿态估计、3D 建模、视觉搜索等。人类在识别和识别周围事物方面非常出色!计算机视觉的最终目标是使用计算机准确模拟人类的视觉系统。
计算机视觉包括多个分析层次。在低级视觉中,我们处理像素处理任务,例如边缘检测、形态处理和光流。在中级和高级视觉中,我们处理诸如物体识别、3D 建模、运动分析以及视觉数据的各个方面。随着层次的提高,我们倾向于深入探讨视觉系统的概念性方面,并尝试根据活动和意图提取视觉数据的描述。需要注意的是,高级层次往往依赖于低级层次的输出进行分析。
这里最常见的问题之一是:计算机视觉与图像处理有何不同?图像处理研究像素级别的图像变换。图像处理系统的输入和输出都是图像。一些常见的例子包括边缘检测、直方图均衡化和图像压缩。计算机视觉算法在很大程度上依赖于图像处理算法来执行其任务。在计算机视觉中,我们处理更复杂的事情,包括在概念层面上理解视觉数据。这样做的原因是我们想要构建图像中对象的具有意义的描述。计算机视觉系统的输出是对给定图像中 3D 场景的解释。这种解释可以以各种形式出现,具体取决于任务。
使用 OpenCV-Python 操作图像
在本章中,我们将使用一个名为开源计算机视觉库(OpenCV)的库来分析图像。OpenCV 是世界上最受欢迎的计算机视觉库。由于它针对许多不同的平台进行了高度优化,因此已成为行业中的事实标准。在继续之前,请确保您已安装具有 Python 支持的库。您可以从opencv.org下载并安装 OpenCV。有关各种操作系统的详细安装说明,您可以参考网站上的文档部分。
准备工作
在本食谱中,我们将探讨如何使用 OpenCV-Python 操作图像。在本食谱中,我们将查看如何加载和显示图像。我们还将查看如何裁剪、调整大小并将图像保存到输出文件中。
如何做到这一点…
让我们看看如何使用 OpenCV-Python 操作图像:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
operating_on_images.py文件中给出):
import sys
import cv2
- 将输入图像指定为文件的第一个参数,并使用图像读取函数读取它。我们将使用您提供的
forest.jpg文件,如下所示:
# Load and display an image -- 'forest.jpg'
input_file = sys.argv[1]
img = cv2.imread(input_file)
- 按以下方式显示输入图像:
cv2.imshow('Original', img)
- 我们现在将裁剪此图像。提取输入图像的高度和宽度,然后指定边界:
# Cropping an image
h, w = img.shape[:2]
start_row, end_row = int(0.21*h), int(0.73*h)
start_col, end_col= int(0.37*w), int(0.92*w)
- 使用 NumPy 风格的切片裁剪图像并显示:
img_cropped = img[start_row:end_row, start_col:end_col]
cv2.imshow('Cropped', img_cropped)
- 将图像调整到原始大小的
1.3倍并显示:
# Resizing an image
scaling_factor = 1.3
img_scaled = cv2.resize(img, None, fx=scaling_factor, fy=scaling_factor,
interpolation=cv2.INTER_LINEAR)
cv2.imshow('Uniform resizing', img_scaled)
- 之前的方法将在两个维度上均匀缩放图像。假设我们想要根据特定的输出维度扭曲图像。我们将使用以下代码:
img_scaled = cv2.resize(img, (250, 400), interpolation=cv2.INTER_AREA)
cv2.imshow('Skewed resizing', img_scaled)
- 将图像保存到输出文件中:
# Save an image
output_file = input_file[:-4] + '_cropped.jpg'
cv2.imwrite(output_file, img_cropped)
cv2.waitKey()
waitKey()函数将显示图像,直到你在键盘上按下一个键。
- 我们将在终端窗口中运行此代码:
$ python operating_on_images.py capri.jpg
你将在屏幕上看到以下四幅图像(意大利卡普里岛的法拉乔尼):
它是如何工作的…
在本食谱中,我们学习了如何使用 OpenCV-Python 库操作图像。以下任务被执行:
-
加载和显示图像
-
裁剪图像
-
调整图像大小
-
保存图像
更多内容…
OpenCV 是一个由英特尔和俄罗斯下诺夫哥罗德研究中心最初开发的免费软件库。后来,它由 Willow Garage 维护,现在由 Itseez 维护。主要用于与该库一起开发的编程语言是 C++,但也可以通过 C、Python 和 Java 进行接口。
参见
-
请参阅 OpenCV 库的官方文档
opencv.org -
请参阅 OpenCV 教程
docs.opencv.org/2.4/opencv_tutorials.pdf
检测边缘
边缘检测是计算机视觉中最受欢迎的技术之一。它被用作许多应用的前处理步骤。通过边缘检测,你可以在数字图像中标记光强度突然变化的位置。图像属性的突然变化旨在突出重要的物理世界事件或变化,这些图像是这些事件或变化的表示。这些变化确定了,例如,表面方向不连续性、深度不连续性等。
准备中
在这个菜谱中,我们将学习如何使用不同的边缘检测器来检测输入图像中的边缘。
如何做…
让我们看看如何检测边缘:
- 创建一个新的 Python 文件,并导入以下包(完整的代码在提供的
edge_detector.py文件中给出):
import sys
import cv2
- 加载输入图像。我们将使用
chair.jpg:
# Load the input image -- 'chair.jpg'
# Convert it to grayscale
input_file = sys.argv[1]
img = cv2.imread(input_file, cv2.IMREAD_GRAYSCALE)
- 提取图像的高度和宽度:
h, w = img.shape
- 索贝尔滤波器是一种边缘检测器,它使用一个 3 x 3 核分别检测水平和垂直边缘:
sobel_horizontal = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
- 运行垂直索贝尔检测器:
sobel_vertical = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)
- 拉普拉斯边缘检测器可以在两个方向上检测边缘。我们使用它如下:
laplacian = cv2.Laplacian(img, cv2.CV_64F)
- 尽管拉普拉斯解决了索贝尔的不足,但输出仍然非常嘈杂。Canny 边缘检测器由于处理问题的方法而优于所有这些,它是一个多阶段过程,并使用滞后性来得到干净的边缘:
canny = cv2.Canny(img, 50, 240)
- 显示所有输出图像:
cv2.imshow('Original', img)
cv2.imshow('Sobel horizontal', sobel_horizontal)
cv2.imshow('Sobel vertical', sobel_vertical)
cv2.imshow('Laplacian', laplacian)
cv2.imshow('Canny', canny)
cv2.waitKey()
- 我们将在终端窗口中使用以下命令运行代码:
$ python edge_detector.py siracusa.jpg
你将在屏幕上看到以下五张图像(意大利西西里岛的古代剧院):
截图顶部是原始图像、水平索贝尔边缘检测器输出和垂直索贝尔边缘检测器输出。注意检测到的线条倾向于垂直。这是因为它是一个水平边缘检测器,并且倾向于检测这个方向的变化。截图底部是拉普拉斯边缘检测器输出和 Canny 边缘检测器,它很好地检测到了所有边缘。
它是如何工作的…
索贝尔算子是一个微分算子,它计算表示图像亮度的函数梯度的近似值。在图像的每个点上,索贝尔算子可以对应于梯度向量或该向量的范数。索贝尔算子使用的算法是基于图像与一个分离的、整数值的滤波器的卷积,这个滤波器在垂直和水平方向上应用,因此在计算能力方面是经济的。
拉普拉斯边缘检测器是零交叉方法的一部分,它寻找二阶导数穿过零的点,这通常是拉普拉斯函数或非线性函数的微分表达式。
更多…
Canny 算法使用多阶段计算方法来寻找真实图像中通常存在的许多类型的轮廓。为此,算法必须尽可能多地识别和标记图像中的良好位置。此外,标记的轮廓必须尽可能接近图像的真实轮廓。最后,给定的图像轮廓只能标记一次,并且如果可能的话,图像中存在的噪声不应导致检测到错误的轮廓。
参见
-
拉普拉斯边缘检测器:
homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm -
Canny 边缘检测器:
homepages.inf.ed.ac.uk/rbf/HIPR2/canny.htm -
最常见的边缘检测器(来自明尼苏达大学):
me.umn.edu/courses/me5286/vision/Notes/2015/ME5286-Lecture7.pdf
直方图均衡化
直方图均衡化是修改图像像素强度以增强图像对比度的过程。人眼喜欢对比度!这就是为什么几乎所有的相机系统都使用直方图均衡化来使图像看起来很棒。
准备工作
有趣的是,直方图均衡化过程对于灰度图像和彩色图像是不同的。处理彩色图像时有一个陷阱,我们将在本食谱中看到它。让我们看看如何做到这一点。
如何做到这一点…
让我们看看我们如何执行直方图均衡化:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
histogram_equalizer.py文件中给出):
import sys
import cv2
- 加载输入图像。我们将使用
sunrise.jpg图像:
# Load input image -- 'sunrise.jpg'
input_file = sys.argv[1]
img = cv2.imread(input_file)
- 将图像转换为
灰度并显示:
# Convert it to grayscale
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('Input grayscale image', img_gray)
- 均衡
灰度图像的直方图并显示:
# Equalize the histogram
img_gray_histeq = cv2.equalizeHist(img_gray)
cv2.imshow('Histogram equalized - grayscale', img_gray_histeq)
- OpenCV 默认以
BGR格式加载图像,所以让我们首先将其从BGR转换为YUV:
# Histogram equalization of color images
img_yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
- 按如下方式均衡 Y 通道:
img_yuv[:,:,0] = cv2.equalizeHist(img_yuv[:,:,0])
- 将其转换回
BGR:
img_histeq = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR)
- 显示输入和输出图像:
cv2.imshow('Input color image', img)
cv2.imshow('Histogram equalized - color', img_histeq)
cv2.waitKey()
- 我们将在终端窗口中运行代码:
$ python histogram_equalizer.py gubbio.jpg
您将在屏幕上看到以下四幅图像(意大利古比奥的中世纪城市):
它是如何工作的…
直方图均衡化是一种数字图像处理方法,您可以使用图像直方图来校准对比度。直方图均衡化增加了许多图像的总体对比度,尤其是在可用图像数据由非常接近的强度值表示时。通过这种适应,强度可以在直方图上更好地分布。这样,局部对比度低的区域可以获得更大的对比度。直方图均衡是通过扩展频繁强度的大多数值来实现的。
还有更多…
为了均衡彩色图像的直方图,我们需要遵循不同的步骤。直方图均衡化仅适用于强度通道。RGB 图像由三个颜色通道组成,我们不能单独对这些通道应用直方图均衡化过程。在我们做任何事情之前,我们需要将强度信息从颜色信息中分离出来。因此,我们首先将其转换为 YUV 颜色空间,然后均衡 Y 通道,最后将其转换回 RGB 以获得输出。
参见
-
对比度增强(来自布鲁克林理工学院):
eeweb.poly.edu/~yao/EL5123/lecture3_contrast_enhancement.pdf
检测角点
角点检测是计算机视觉中的一个重要过程。它帮助我们识别图像中的显著点。这是最早用于开发图像分析系统的特征提取技术之一。
准备工作
在这个菜谱中,我们将学习如何通过在识别的点放置标记来检测盒子的角点。
如何做到这一点…
让我们看看我们如何检测角点:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
corner_detector.py文件中给出):
import sys
import cv2
import numpy as np
- 加载输入图像。我们将使用
box.png:
# Load input image -- 'box.png'
input_file = sys.argv[1]
img = cv2.imread(input_file)
cv2.imshow('Input image', img)
- 将图像转换为
灰度并转换为浮点值。我们需要浮点值以便角点检测器工作:
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_gray = np.float32(img_gray)
- 在
灰度图像上运行Harris 角点检测器函数:
# Harris corner detector
img_harris = cv2.cornerHarris(img_gray, 7, 5, 0.04)
- 为了标记角点,我们需要膨胀图像,如下所示:
# Resultant image is dilated to mark the corners
img_harris = cv2.dilate(img_harris, None)
- 让我们阈值化图像以显示重要点:
# Threshold the image
img[img_harris > 0.01 * img_harris.max()] = [0, 0, 0]
- 显示输出图像:
cv2.imshow('Harris Corners', img)
cv2.waitKey()
- 我们将在终端窗口中运行代码:
$ python corner_detector.py box.png
你将在屏幕上看到以下两个图像:
它是如何工作的…
角点检测是计算机视觉中用于提取特征类型并推断图像内容的方法。它常用于运动检测、图像记录、视频跟踪、图像拼接、图像全景创建、3D 建模和物体识别。它是一个与兴趣点检测类似的话题。
更多内容…
角点检测方法可以分为两组:
-
基于提取轮廓和随后识别对应于最大曲率或边缘段相交点的技术的技巧
-
直接从图像像素的灰度强度中搜索角点的算法
参见
-
Harris 角点检测器(来自宾夕法尼亚州立大学):
www.cse.psu.edu/~rtc12/CSE486/lecture06.pdf -
Harris 角检测器的官方文档:
docs.opencv.org/3.0-beta/doc/py_tutorials/py_feature2d/py_features_harris/py_features_harris.html
检测 SIFT 特征点
尺度不变特征变换(SIFT)是计算机视觉领域最受欢迎的特征之一。David Lowe 在他的开创性论文中首次提出了这一概念。它已经成为用于图像识别和内容分析的最有效的特征之一。它对尺度、方向、强度等因素具有鲁棒性。这构成了我们物体识别系统的基础。
准备工作
在这个菜谱中,我们将学习如何检测 SIFT 特征点。
如何操作…
让我们看看如何检测 SIFT 特征点:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
feature_detector.py文件中给出):
import sys
import cv2
import numpy as np
- 加载输入图像。我们将使用
table.jpg:
# Load input image -- 'table.jpg'
input_file = sys.argv[1]
img = cv2.imread(input_file)
- 将此图像转换为灰度:
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
- 初始化 SIFT 检测器对象并提取关键点:
sift = cv2.xfeatures2d.SIFT_create()
keypoints = sift.detect(img_gray, None)
-
关键点是显著点,但它们不是特征。这基本上给出了显著点的位置。SIFT 还充当一个非常有效的特征提取器。
-
按如下方式在输入图像上绘制关键点:
img_sift = np.copy(img)
cv2.drawKeypoints(img, keypoints, img_sift, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
- 显示输入和输出图像:
cv2.imshow('Input image', img)
cv2.imshow('SIFT features', img_sift)
cv2.waitKey()
- 我们将在终端窗口中运行此代码:
$ python feature_detector.py flowers.jpg
你将在屏幕上看到以下两个图像:
它是如何工作的…
对于图像中的每个对象,都会提取一些有趣的点来描述对象的特点。这个从训练图像中获得的特征,用于在包含许多其他对象的测试图像中定位对象时识别对象。为了获得可靠的识别,从训练图像中提取的特征必须可检测,即使存在尺度变化、噪声和光照。这些点通常放置在图像的高对比度区域,如对象轮廓。
还有更多…
在 Lowe 的方法中,SIFT 对象的特征点在第一阶段从一组参考图像中提取出来,然后它们被存储在数据库中。在新的图像中识别对象是通过将新图像的每个特征与之前获得的数据库中的特征逐一比较,并基于特征向量的欧几里得距离寻找特征来实现的。从新图像中的完整匹配集中,识别出与对象及其位置、尺度、方向一致的关键点子集,以过滤出最佳匹配。
参见
-
SIFT(尺度不变特征变换)简介:
docs.opencv.org/3.4/d5/d3c/classcv_1_1xfeatures2d_1_1SIFT.html -
OpenCV.xfeatures2d.SIFT函数的官方文档:docs.opencv.org/3.4/d5/d3c/classcv_1_1xfeatures2d_1_1SIFT.html -
从尺度不变关键点中提取的显著图像特征(由不列颠哥伦比亚大学的 David G Lowe 撰写):
www.cs.ubc.ca/~lowe/papers/ijcv04.pdf
构建星形特征检测器
SIFT 特征检测器在许多情况下都很好。然而,当我们构建对象识别系统时,我们可能在提取 SIFT 特征之前想要使用不同的特征检测器。这将给我们提供灵活性,以级联不同的块以获得最佳性能。
准备工作
在这个菜谱中,我们将使用S****tar 特征检测器从图像中检测特征。
如何操作…
让我们看看我们如何构建一个星形特征检测器:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
star_detector.py文件中给出):
import sys
import cv2
- 定义一个类来处理与星形特征检测相关的所有函数:
class StarFeatureDetector(object):
def __init__(self):
self.detector = cv2.xfeatures2d.StarDetector_create()
- 定义一个函数来在输入图像上运行检测器:
def detect(self, img):
return self.detector.detect(img)
- 在
main函数中加载输入图像。我们将使用table.jpg:
if __name__=='__main__':
# Load input image -- 'table.jpg'
input_file = sys.argv[1]
input_img = cv2.imread(input_file)
- 将图像转换为灰度:
# Convert to grayscale
img_gray = cv2.cvtColor(input_img, cv2.COLOR_BGR2GRAY)
- 使用星形特征检测器检测特征:
# Detect features using Star feature detector
keypoints = StarFeatureDetector().detect(input_img)
- 在输入图像上绘制关键点:
cv2.drawKeypoints(input_img, keypoints, input_img,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
- 显示输出图像:
cv2.imshow('Star features', input_img)
cv2.waitKey()
- 我们将在终端窗口中运行代码:
$ python star_detector.py table.jpg
你将在屏幕上看到以下图像:
它是如何工作的…
在这个菜谱中,我们学习了如何使用 OpenCV-Python 库构建星形特征检测器。以下任务被执行了:
-
加载图像
-
转换为灰度
-
使用星形特征检测器检测特征
-
绘制关键点和显示图像
更多内容…
Star 函数检测器基于CenSurE(中心周围极值)。两个检测器之间的区别在于多边形的选取:
-
CenSurE 使用正方形、六边形和八边形作为圆形的替代品
-
星形通过两个叠加的正方形来近似圆形:一个垂直和一个旋转 45 度
参考内容
-
CenSurE:用于实时特征检测和匹配的中心周围极值,在计算机视觉–ECCV 2008(第 102-115 页)。Springer Berlin Heidelberg:
citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.465.1117&rep=rep1&type=pdf
使用视觉代码簿和向量量化创建特征
要构建一个目标识别系统,我们需要从每张图片中提取特征向量。每张图片都需要一个可以用于匹配的签名。我们使用一个叫做Visual Codebook 的概念来构建图像签名。这个 codebook 基本上是我们用来为训练数据集中的图像签名提供表示的字典。我们使用向量量化来聚类许多特征点并得出质心。这些质心将作为我们 Visual Codebook 的元素。
准备工作
在这个菜谱中,我们将使用 Visual Codebook 和向量量化来创建特征。为了构建一个健壮的目标识别系统,你需要成千上万张图片。有一个叫做Caltech256的数据集在这个领域非常受欢迎!它包含 256 类图片,每类包含数千个样本。
如何操作…
让我们看看如何使用 Visual Codebook 和向量量化来创建特征:
- 这是一个篇幅较长的菜谱,所以我们只看重要的函数。完整的代码在提供的
build_features.py文件中给出。让我们看看定义用于提取特征的类:
class FeatureBuilder(object):
- 定义一个从输入图像中提取特征的方法。我们将使用 Star 检测器来获取关键点,然后使用 SIFT 从这些位置提取描述符:
def extract_ features(self, img):
keypoints = StarFeatureDetector().detect(img)
keypoints, feature_vectors = compute_sift_features(img, keypoints)
return feature_vectors
- 我们需要从所有描述符中提取质心:
def get_codewords(self, input_map, scaling_size, max_samples=12):
keypoints_all = []
count = 0
cur_label = ''
- 每张图片都会产生大量的描述符。我们只需使用少量图片,因为质心在此之后不会变化太多:
for item in input_map:
if count >= max_samples:
if cur_class != item['object_class']:
count = 0
else:
continue
count += 1
- 打印进度如下:
if count == max_samples:
print("Built centroids for", item['object_class'])
- 提取当前标签:
cur_class = item['object_class']
- 读取图片并调整大小:
img = cv2.imread(item['image_path'])
img = resize_image(img, scaling_size)
- 提取特征:
feature_vectors = self.extract_image_features(img)
keypoints_all.extend(feature_vectors)
- 使用向量量化对特征点进行量化。向量量化是N维度的舍入:
kmeans, centroids = BagOfWords().cluster(keypoints_all)
return kmeans, centroids
- 定义一个处理词袋模型和向量量化的类:
class BagOfWords(object):
def __init__(self, num_clusters=32):
self.num_dims = 128
self.num_clusters = num_clusters
self.num_retries = 10
- 定义一个量化数据点的函数。我们将使用k-means 聚类来实现这一点:
def cluster(self, datapoints):
kmeans = KMeans(self.num_clusters,
n_init=max(self.num_retries, 1),
max_iter=10, tol=1.0)
- 提取质心,如下所示:
res = kmeans.fit(datapoints)
centroids = res.cluster_centers_
return kmeans, centroids
- 定义一个数据归一化的方法:
def normalize(self, input_data):
sum_input = np.sum(input_data)
if sum_input > 0:
return input_data / sum_input
else:
return input_data
- 定义一个获取特征向量的方法:
def construct_feature(self, img, kmeans, centroids):
keypoints = StarFeatureDetector().detect(img)
keypoints, feature_vectors = compute_sift_features(img, keypoints)
labels = kmeans.predict(feature_vectors)
feature_vector = np.zeros(self.num_clusters)
- 构建直方图并对其进行归一化:
for i, item in enumerate(feature_vectors):
feature_vector[labels[i]] += 1
feature_vector_img = np.reshape(feature_vector,
((1, feature_vector.shape[0])))
return self.normalize(feature_vector_img)
- 定义一个方法,然后提取 SIFT 特征:
# Extract SIFT features
def compute_sift_features(img, keypoints):
if img is None:
raise TypeError('Invalid input image')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
keypoints, descriptors = cv2.xfeatures2d.SIFT_create().compute(img_gray, keypoints)
return keypoints, descriptors
如我们之前提到的,请参考build_features.py以获取完整的代码。你应该按照以下方式运行代码:
$ python build_features.py --data-folder /path/to/training_images/ --codebook-file codebook.pkl --feature-map-file feature_map.pkl
这将生成两个文件,分别叫做codebook.pkl和feature_map.pkl。我们将在下一个菜谱中使用这些文件。
它是如何工作的…
在这个菜谱中,我们使用了一个视觉教科书作为字典,然后我们使用它来为图像签名创建表示,这些图像签名包含在训练集中。因此,我们使用向量量化来分组许多特征点并创建质心。这些质心作为我们视觉教科书的元素。
还有更多…
我们从图像的各个点提取特征,计算提取特征的值频率,并根据找到的频率对图像进行分类,这是一种类似于在向量空间中表示文档的技术。这是一个向量量化过程,我用它创建一个字典来离散化特征空间的可能值。
参见
-
视觉代码簿(由 Tae-Kyun Kim,来自西德尼·萨塞克斯学院):
mi.eng.cam.ac.uk/~cipolla/lectures/PartIB/old/IB-visualcodebook.pdf -
加州理工学院 Caltech-256 图像库(来自加州理工学院):
www.vision.caltech.edu/Image_Datasets/Caltech256/ -
向量量化概述(来自宾汉顿大学):
www.ws.binghamton.edu/fowler/fowler%20personal%20page/EE523_files/Ch_10_1%20VQ%20Description%20(PPT).pdf
使用极端随机森林训练图像分类器
一个物体识别系统使用图像分类器将图像分类到已知类别。极端随机森林(ERFs)在机器学习领域非常受欢迎,因为它们的速度和准确性。该算法基于决策树。与经典决策树相比,它们的区别在于树分割点的选择。通过为随机选择的每个特征创建随机子划分,并选择这些子划分之间的最佳分割,来完成将节点样本分割成两组的最佳分割。
准备工作
在这个菜谱中,我们将使用 ERFs 来训练我们的图像分类器。我们基本上基于我们的图像特征构建决策树,然后训练森林以做出正确的决策。
如何操作…
让我们看看如何使用 ERFs 训练图像分类器:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
trainer.py文件中给出):
import argparse
import _pickle as pickle
import numpy as np
from sklearn.ensemble import ExtraTreesClassifier
from sklearn import preprocessing
- 定义一个参数解析器:
def build_arg_parser():
parser = argparse.ArgumentParser(description='Trains the classifier')
parser.add_argument("--feature-map-file", dest="feature_map_file", required=True,
help="Input pickle file containing the feature map")
parser.add_argument("--model-file", dest="model_file", required=False,
help="Output file where the trained model will be stored")
return parser
- 定义一个类来处理 ERF 训练。我们将使用标签编码器来编码我们的训练标签:
class ERFTrainer(object):
def __init__(self, X, label_words):
self.le = preprocessing.LabelEncoder()
self.clf = ExtraTreesClassifier(n_estimators=100,
max_depth=16, random_state=0)
- 对标签进行编码并训练分类器:
y = self.encode_labels(label_words)
self.clf.fit(np.asarray(X), y)
- 定义一个函数来编码标签:
def encode_labels(self, label_words):
self.le.fit(label_words)
return np.array(self.le.transform(label_words), dtype=np.float32)
- 定义一个函数来分类未知数据点:
def classify(self, X):
label_nums = self.clf.predict(np.asarray(X))
label_words = self.le.inverse_transform([int(x) for x in label_nums])
return label_words
- 定义
main函数并解析输入参数:
if __name__=='__main__':
args = build_arg_parser().parse_args()
feature_map_file = args.feature_map_file
model_file = args.model_file
- 加载我们在上一个菜谱中创建的特征图:
# Load the feature map
with open(feature_map_file, 'rb') as f:
feature_map = pickle.load(f)
- 提取特征向量:
# Extract feature vectors and the labels
label_words = [x['object_class'] for x in feature_map]
dim_size = feature_map[0]['feature_vector'].shape[1]
X = [np.reshape(x['feature_vector'], (dim_size,)) for x in feature_map]
- 基于训练数据训练 ERF:
# Train the Extremely Random Forests classifier
erf = ERFTrainer(X, label_words)
- 按如下方式保存训练好的 ERF 模型:
if args.model_file:
with open(args.model_file, 'wb') as f:
pickle.dump(erf, f)
- 现在,你应该在终端中运行代码:
$ python trainer.py --feature-map-file feature_map.pkl
--model-file erf.pkl
这将生成一个名为erf.pkl的文件。我们将在下一个菜谱中使用此文件。
它是如何工作的…
在这个菜谱中,我们使用了 ERFs 来训练我们的图像分类器。首先,我们定义了一个参数解析器函数和一个处理 ERF 训练的类。我们使用标签编码器来编码我们的训练标签。然后,我们加载了在使用视觉代码簿和矢量量化创建特征菜谱中获得的特征图。因此,我们提取了特征向量和标签,最后我们训练了 ERF 分类器。
更多内容…
为了训练图像分类器,使用了sklearn.ensemble.ExtraTreesClassifier函数。此函数构建了一个超随机树分类器。
相关内容
-
sklearn.ensemble.ExtraTreesClassifier函数的官方文档:scikit-learn.org/stable/modules/generated/sklearn.tree.ExtraTreeClassifier.html#sklearn.tree.ExtraTreeClassifier -
随机森林(由 Leo Breiman 和 Adele Cutler 撰写,来自加州大学伯克利分校):
www.stat.berkeley.edu/~breiman/RandomForests/cc_home.htm -
超随机树(由 Pierre Geurts、Damien Ernst 和 Louis Wehenkel 撰写,来自机器学习杂志 - 斯普林格):
link.springer.com/content/pdf/10.1007/s10994-006-6226-1.pdf
构建对象识别器
在之前的菜谱使用超随机森林训练图像分类器中,我们使用了 ERFs 来训练我们的图像分类器。现在我们已经训练了一个 ERF 模型,让我们继续构建一个能够识别未知图像内容的对象识别器。
准备工作
在这个菜谱中,我们将学习如何使用训练好的 ERF 模型来识别未知图像的内容。
如何操作…
让我们看看如何构建对象识别器:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
object_recognizer.py文件中):
import argparse
import _pickle as pickle
import cv2
import build_features as bf
from trainer import ERFTrainer
- 定义参数解析器:
def build_arg_parser():
parser = argparse.ArgumentParser(description='Extracts features \
from each line and classifies the data')
parser.add_argument("--input-image", dest="input_image", required=True,
help="Input image to be classified")
parser.add_argument("--model-file", dest="model_file", required=True,
help="Input file containing the trained model")
parser.add_argument("--codebook-file", dest="codebook_file",
required=True, help="Input file containing the codebook")
return parser
- 定义一个类来处理图像标签提取函数:
class ImageTagExtractor(object):
def __init__(self, model_file, codebook_file):
with open(model_file, 'rb') as f:
self.erf = pickle.load(f)
with open(codebook_file, 'rb') as f:
self.kmeans, self.centroids = pickle.load(f)
- 定义一个函数,使用训练好的 ERF 模型来预测输出:
def predict(self, img, scaling_size):
img = bf.resize_image(img, scaling_size)
feature_vector = bf.BagOfWords().construct_feature(
img, self.kmeans, self.centroids)
image_tag = self.erf.classify(feature_vector)[0]
return image_tag
- 定义
main函数并加载输入图像:
if __name__=='__main__':
args = build_arg_parser().parse_args()
model_file = args.model_file
codebook_file = args.codebook_file
input_image = cv2.imread(args.input_image)
- 适当缩放图像,如下所示:
scaling_size = 200
- 在终端上打印输出:
print("Output:", ImageTagExtractor(model_file,
codebook_file).predict(input_image, scaling_size))
- 现在,你应该按照以下方式运行代码:
$ python object_recognizer.py --input-image imagefile.jpg --model-file erf.pkl --codebook-file codebook.pkl
工作原理…
在这个菜谱中,我们使用了一个训练好的 ERF 模型来识别未知图像的内容。为此,我们使用了前两个菜谱中讨论的算法,即使用视觉代码簿和矢量量化创建特征和使用超随机森林训练图像分类器。
更多内容…
随机森林是一个由许多决策树组成的聚合分类器,输出与单个树类输出相对应的类别。随机森林的诱导算法由 Leo Breiman 和 Adele Cutler 开发。它基于创建一组广泛的分类树,每棵树都旨在对单个植物进行分类,该植物任何性质的特征都已被评估。比较森林中每棵树提供的分类建议,将植物归类的类别是获得最多指示或投票的类别。
参见
- 随机森林简介(由加州州立大学长滩分校的 Anthony Anh Quoc Doan 编写):
web.csulb.edu/~tebert/teaching/lectures/551/random_forest.pdf
使用 Light GBM 进行图像分类
梯度提升在回归和分类问题中用于生成一系列弱预测模型的预测模型,通常是决策树。这种方法类似于提升方法,并对其进行了推广,允许优化任意可微分的loss函数。
轻量梯度提升机(LightGBM)是梯度提升的一种特定变体,经过一些修改使其特别有利。它基于分类树,但在每一步选择分割叶子节点的方式更为有效。
准备工作
在这个菜谱中,我们将学习如何使用 LightGBM 对手写数字进行分类。为此,我们将使用修改后的国家标准与技术研究院(MNIST)数据集。这是一个包含大量手写数字的大型数据库。它包含 70,000 个数据示例。它是 NIST 更大数据集的一个子集。这些数字具有 28 x 28 像素的分辨率,并存储在一个 70,000 行和 785 列的矩阵中;784 列形成 28 x 28 矩阵中每个像素的值,一个值是实际的数字。这些数字已被尺寸归一化并居中在固定大小的图像中。
如何做…
让我们看看如何使用 LightGBM 进行图像分类:
- 创建一个新的 Python 文件并导入以下包(完整的代码在提供的
LightgbmClassifier.py文件中给出):
import numpy as np
import lightgbm as lgb
from sklearn.metrics import mean_squared_error
from keras.datasets import mnist
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
- 要导入
mnist数据集,必须使用以下代码:
(XTrain, YTrain), (XTest, YTest) = mnist.load_data()
返回以下元组:
-
XTrain,XTest:一个形状为(num_samples,28,28)的灰度图像数据的uint8数组 -
YTrain,YTest:一个形状为(num_samples)的数字标签(0-9 范围内的整数)的uint8数组
- 因此,每个样本图像由一个 28 x 28 的矩阵组成。为了降低维度,我们将 28 x 28 的图像展平成大小为 784 的向量:
XTrain = XTrain.reshape((len(XTrain), np.prod(XTrain.shape[1:])))
XTest = XTest.reshape((len(XTest), np.prod(XTest.shape[1:])))
- 现在,我们将从包含 0 到 9 数字的数据集中提取,但只提取前两个(0 和 1),因为我们想构建一个二元分类器。为此,我们将使用
numpy.where函数:
TrainFilter = np.where((YTrain == 0 ) | (YTrain == 1))
TestFilter = np.where((YTest == 0) | (YTest == 1))
XTrain, YTrain = XTrain[TrainFilter], YTrain[TrainFilter]
XTest, YTest = XTest[TestFilter], YTest[TestFilter]
- 我们为
lightgbm创建了一个数据集:
LgbTrain = lgb.Dataset(XTrain, YTrain)
LgbEval = lgb.Dataset(XTest, YTest, reference=LgbTrain)
- 现在,我们必须将模型参数指定为字典:
Parameters = {
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'binary_logloss',
'num_leaves': 31,
'learning_rate': 0.05,
'feature_fraction': 0.9,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': 0
}
- 让我们训练模型:
gbm = lgb.train(Parameters,
LgbTrain,
num_boost_round=10,
valid_sets=LgbTrain)
- 我们的模式现在准备好了,我们可以用它来自动分类手写数字。为此,我们将使用
predict()方法:
YPred = gbm.predict(XTest, num_iteration=gbm.best_iteration)
YPred = np.round(YPred)
YPred = YPred.astype(int)
- 现在,我们可以评估模型:
print('Rmse of the model is:', mean_squared_error(YTest, YPred) ** 0.5)
返回以下结果:
Rmse of the model is: 0.05752992848417943
- 要更详细地分析二分类中犯的错误,我们需要计算混淆矩阵:
ConfMatrix = confusion_matrix(YTest, YPred)
print(ConfMatrix)
返回以下结果:
[[ 978 2]
[ 5 1130]]
- 最后,我们将计算模型的准确率:
print(accuracy_score(YTest, YPred))
返回以下结果:
0.9966903073286052
因此,该模型能够以高精度对手写数字的图像进行分类。
它是如何工作的…
在这个食谱中,我们使用了 LightGBM 来分类手写数字。LightGBM 是梯度提升的一种特定变体,经过一些修改使其特别有利。它基于分类树,但在每一步选择分裂叶子节点的方式更为有效。
当提升操作在深度上生长树时,LightGBM 通过结合两个标准来做出这个选择:
-
基于梯度下降的优化
-
为了避免过拟合问题,设置了最大深度的限制
这种增长方式被称为叶式。
还有更多…
Light GBM 有许多优点:
-
所展示的过程平均比类似算法快一个数量级。这是因为它没有完全生长树,而且还利用了变量的分箱(将变量分成子组的过程,既为了加快计算速度,也是一种正则化方法)。
-
更经济的内存使用:分箱过程涉及较少的内存使用。
-
与常规提升算法相比,准确率更高:因为它使用叶式过程,得到的树更复杂。同时,为了避免过拟合,对最大深度进行了限制。
-
该算法易于并行化。
参见
-
LightGBM库的官方文档:lightgbm.readthedocs.io/en/latest/ -
《梯度提升的温和介绍》(来自东北大学):
www.ccs.neu.edu/home/vip/teach/MLcourse/4_boosting/slides/gradient_boosting.pdf -
《LightGBM:高效的梯度提升决策树》(由 Guolin Ke 等人撰写):
papers.nips.cc/paper/6907-lightgbm-a-highly-efficient-gradient-boosting-decision-tree.pdf
2918

被折叠的 条评论
为什么被折叠?



