假设您要制作一个可以实时识别音频信号,并有触发动作的iOS应用-这需要探索机器学习与移动开发的交叉路口。
为了识别声音信号,首先必须收集用于训练机器学习模型的数据。幸运的是,我发现了一个非常相关的数据集:50种环境声音的集合(//ESC-50)
对于每种类别或类型的声音,都有40条记录,每条记录5秒。结果,2000条有分类标记的数据记录,可用来训练我们的模型。数据合格(即,数据所属类别清晰)这一事实非常重要。该模型将基于此,并将学习区分不同的类别。
但是,在训练模型之前,我们必须经历“特征工程阶段”。此步骤涉及转换数据以简化数据并删除冗余信息。这以及随后的模型结构,使得归纳学习结果成为可能。
的确,目标不是要拥有一个能够完全了解训练数据的模型,而是要拥有一个与训练数据充分独立,能够识别出从未听过的声音的模型。“特征工程”通常是成功完成这项工作的关键。
在信号处理领域中,并且特别是在声音信号处理领域中,通常的做法是使用频率变换进行特征提取。该频率使,例如可以将以低频为特征的低音与以高频为特征的锐音区分开。
对我们来说幸运的是,为我们完成了所有繁重的工作。
步骤1:资料准备
出于本文的目的,我将专注于四种明显不同的声音,所使用的数据来自ESC-10数据集,并按原样使用:
狗的声音波形
公鸡声音波形
直升机声波
打喷嚏的声音波形
我确保选择的声音彼此截然不同。
步骤2:训练模型
在开始编写分类器之前,我们需要确保数据平衡,这意味着该模型将为每个类获取相同数量的数据。
图说:训练数据的分布(仅限四个类别)
现在我们知道我们的数据是干净且平衡的,我们可以开始训练模型了。
这是所有步骤:
GPU的使用:我正在使用计算机中所有的GPU,但是您可以将使用率更改为(0),如果不使用或(n)更改您要使用的GPU的数量。加载数据:加载包含所有音频数据的文件夹。加载元数据:加载概述文件名和相应标签的.csv文件。仅使用ESC-10:由于我的计算机不是很强大,因此我只使用了ESC-10,并排除了不属于它的所有记录。您绝对可以使用过滤器功能跳过这一部分。拆分数据:仅使用一小部分数据进行训练(折1仅为ESC-10)和进行测试(折1)。创建模型:Turi 将为我们完成工作-我们只需要训练数据和迭代次数(允许通过数据的最大次数)。我选择了100次迭代,因为默认值只有10次,但这不够。保存预测:这些预测将用于评估模型的性能。评估模型:模型将尝试预测测试数据并将其与真实数据进行比较。保存模型:保存模型,以便以后以其他格式导出时可以使用它。导出一个.文件:这个文件格式,可以作为Xcode为我们的iOS应用程序解析。
import turicreate as tc
from os.path import basename
tc.config.set_num_gpus(-1)
# Load the audio data and meta data.
data = tc.load_audio('/ESC-50/audio/')
meta_data = tc.SFrame.read_csv('/ESC-50/meta/esc50.csv')
# Join the audio data and the meta data.
data['filename'] = data['path'].apply(lambda p: basename(p))
data = data.join(meta_data)
# Drop all records which are not part of the ESC-10.
data = data.filter_by('True', 'esc10')
# Make a train-test split, just use the first fold as our test set.
test_set = data.filter_by(1, 'fold')
train_set = data.filter_by(1, 'fold', exclude=True)
# Create the model.
model = tc.sound_classifier.create(train_set,
target='category',
feature='audio',
max_iterations=100,
custom_layer_sizes=[200, 200])
# Generate an SArray of predictions from the test set.
predictions = model.predict(test_set)
# Evaluate the model and print the results
metrics = model.evaluate(test_set)
print(metrics)
# Save the model for later use in Turi Create
model.save('SoundClassification.model')
# Export for use in Core ML
model.export_coreml('SoundClassification.mlmodel')
怎么运行的:
预处理以975毫秒的音频形式的音频数据作为输入(确切的输入长度取决于采样率),并生成形状数组(96,64)。声音工程领域的人可能会熟悉这一部分。
在这个阶段还没有学习。
在这里,我们使用VGG特征提取。 VGG是提供的用于图像分类和识别的预训练卷积神经网络。我强烈建议您阅读的文章,以更好地了解这种架构。(文章下载附后)
Turi 使用三层,前两层是密集层(每个神经元都简单地连接到上一层的所有神经元。我们也可以将该层称为“完全连接”),具有100个单元测试(我已经将其更改为200,可以提高准确性)。
第三个是层。 将十进制概率分配给多类问题的每个类。这些十进制概率的总和必须等于1。这种额外的限制使学习收敛比以前更快。
iOS应用
首先,我们需要使用单个视图应用程序创建一个iOS项目:
图说:创建一个单视图应用程序
现在,我们的项目已准备就绪。我不喜欢自己使用情节提要,因此本教程中的应用程序是通过编程方式构建的,这意味着没有按钮或开关可切换-只是纯代码 。
要采用这种方法,您必须删除main.,并按以下方式设置.swift文件:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let controller = ViewController()
window?.makeKeyAndVisible()
window?.rootViewController = controller
return true
}
标签
我们需要一个标签,通过适当的预测来更新视图:
lazy var label = UILabel()
///////////////////////////////////////////////////////////
// MARK: - Setup the label layout and add it to the subview
///////////////////////////////////////////////////////////
private func setupLabel() {
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont(name: "Avenir-Heavy", size: 100)
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 120).isActive = true
}
获取推断并更新视图:
为了获得预测并更新视图,我们需要执行一些步骤:使用()实例化模型。出于本教程的目的,我添加了一个计时器来检查不同的声音类型。获取音频文件(请记住,它必须大于975ms)。读取音频文件。创建一个(这是我们输入的多维数组),并设置为Core ML模型。获取所有预测。获得具有最大预测的标签。更改视图颜色,并用最佳预测标记文本。
/////////////////////////////////////////////////////////////////////////////
// MARK: - Get the inference for each sound and change the layout accordingly
private func getInference() {
//200 layers 100 iterations
let model = SoundClassification()
var count = 0
_ = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { t in
var wav_file: AVAudioFile!
do {
let fileUrl = URL(fileReferenceLiteralResourceName: "(self.output[count])_1_converted.wav")
wav_file = try AVAudioFile(forReading:fileUrl)
} catch {
fatalError("Could not open wav file.")
}
print("wav file length: (wav_file.length)")
assert(wav_file.fileFormat.sampleRate==16000.0, "Sample rate is not right!")
let buffer = AVAudioPCMBuffer(pcmFormat: wav_file.processingFormat,
frameCapacity: UInt32(wav_file.length))
do {
try wav_file.read(into:buffer!)
} catch{
fatalError("Error reading buffer.")
}
guard let bufferData = buffer?.floatChannelData else { return }
// Chunk data and set to CoreML model
let windowSize = 15600
guard let audioData = try? MLMultiArray(shape:[windowSize as NSNumber],
dataType:MLMultiArrayDataType.float32)
else {
fatalError("Can not create MLMultiArray")
}
// Ignore any partial window at the end.
var results = [Dictionary]()
let windowNumber = wav_file.length / Int64(windowSize)
for windowIndex in 0..<Int(windowNumber) {
let offset = windowIndex * windowSize
for i in 0...windowSize {
audioData[i] = NSNumber.init(value: bufferData[0][offset + i])
}
let modelInput = SoundClassificationInput(audio: audioData)
guard let modelOutput = try? model.prediction(input: modelInput) else {
fatalError("Error calling predict")
}
results.append(modelOutput.categoryProbability)
}
var prob_sums = Dictionary()
for r in results {
for (label, prob) in r {
prob_sums[label] = (prob_sums[label] ?? 0) + prob
}
}
var max_sum = 0.0
var max_sum_label = ""
for (label, sum) in prob_sums {
if sum > max_sum {
max_sum = sum
max_sum_label = label
}
}
let most_probable_label = max_sum_label
let probability = max_sum / Double(results.count)
print("(most_probable_label) predicted, with probability: (probability)")
let prediction: classes = classes.init(rawValue: most_probable_label)!
switch prediction {
case .dog:
self.label.text = most_probable_label
self.view.backgroundColor = #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1)
case .helicopter:
self.label.text = most_probable_label
self.view.backgroundColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
case .rooster:
self.label.text = most_probable_label
self.view.backgroundColor = #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1)
case .sneezing:
self.label.text = most_probable_label
self.view.backgroundColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
}
print(count)
if count >= 3 {
t.invalidate()
}
count += 1
}
}
确保转换文件,因为输入只能是一个通道(单声道不是立体声),并且采样率也必须是 Hz。
我已经使用该网站转换了所有音频文件(/-to-wav)。
但是不用担心,我为每个类别提供了三个示例,以便您可以测试应用程序。
最后一件事(非常重要):
使用模型之前,必须下载g.dylib。信号预处理阶段需要此文件,该文件已实现为自定义Core ML模型。可以在Turi 的版本页面(/apple////5.7/g.zip)上找到g.dylib文件。下载该文件后,将arm64文件作为文件夹引用拖放到Xcode项目中。
在 > Build 里,将g.dylib添加到“Copy “。 > 下,将g.dylib添加到 和 and 。
3. 在 > Build ,将g.dylib添加到Embed 。
重要说明:⚠️
该项目将无法在模拟器中运行-您必须在上进行测试。这是因为模拟器需要 g.dylib文件,才能在计算机的模拟器上运行。
最好的解决方案是使用您自己的手机。
最后结果:
当然,有许多改进之处。该系统适用于从训练数据集派生的测试数据集,但在实际情况下,性能仍不能令人满意。我认为在探索其他模型之后,我可以着手进行信号预处理和特征工程。
最后,我想强调两件事:
第一件事是,数据科学/机器学习项目,可以比学习型数据科学挑战走得更远。
它代表了在丰富的学习资源的帮助下,探索新领域的一种方法,但是机器学习不仅是寻找最佳预测模型性能的挑战。可以说,除了“探索和预测”范式之外,还有一些更大的项目需要走出数据科学家的舒适区。
其次,即使简化,该项目也代表了大数据项目所需的工作,其中,数据量大得多,分布在集群中(通常在Linux上运行),自动化流程投入生产后可重复运行。在这些情况下,机器学习任务的可重复性和清洁度至关重要。
在这种情况下,对于数据科学家来说,与周围的数据工程师进行互动也很重要,这样项目才能成功。在工业环境中,此类项目越来越多,其业务挑战也很大。
我还想指出,为了获得有用且接近现实的场景,您需要与声音工程领域的人们一起工作,以真正理解问题的各个方面并从整体上看待问题。
所有代码可下载://
1609.09430.pdf
1.7M
·
百度网盘