原文:
annas-archive.org/md5/24329c3bf9f3c672f8b5b2fa4cd1802e
译者:飞龙
第七章:使用 WaveNet 模型进行语音转文本的 TensorFlow 移动端实现
在本章中,我们将学习如何使用 WaveNet 模型将音频转换为文本。然后,我们将构建一个模型,使用 Android 应用程序将音频转换为文本。
本章基于 Aaron van den Oord、Sander Dieleman、Heiga Zen、Karen Simonyan、Oriol Vinyals、Alex Graves、Nal Kalchbrenner、Andrew Senior 和 Koray Kavukcuoglu 的论文《WaveNet: A Generative Model for Raw Audio》。您可以在arxiv.org/abs/1609.03499
找到这篇论文。
本章将涵盖以下主题:
-
WaveNet 及其工作原理
-
WaveNet 架构
-
使用 WaveNet 构建模型
-
数据集预处理
-
训练 WaveNet 网络
-
将语音 WAV 文件转换为英文文本
-
构建 Android 应用程序
让我们深入探讨 WaveNet 究竟是什么。
WaveNet
WaveNet 是一个深度生成网络,用于生成原始音频波形。WaveNet 通过模拟人声来生成声音波形。与当前任何现有的语音合成系统相比,这种生成的声音更为自然,缩小了系统与人类表现之间的差距达 50%。
使用单一 WaveNet,我们可以以相同的保真度区分多个说话者。我们还可以根据说话者的身份在不同的说话者之间切换。该模型是自回归的和概率性的,可以在每秒处理成千上万的音频样本时高效训练。单一 WaveNet 能够以相同的保真度捕捉多个不同说话者的特点,并且可以通过条件化说话者身份来在它们之间切换。
如电影《她》中所示,人机交互的长期梦想就是让人类与机器对话。近年来,随着深度神经网络(例如,Google 助手、Siri、Alexa 和 Cortana)的发展,计算机理解语音的能力大幅提高。另一方面,计算机生成语音的过程被称为语音合成或文本转语音。在文本转语音方法中,一个说话者录制大量短音频片段,然后将这些片段拼接成所需的语句。这一过程非常困难,因为我们不能更换说话者。
这一难题促使了对其他生成语音方法的巨大需求,其中生成数据所需的所有信息都存储在模型的参数中。此外,使用输入数据,我们可以控制语音的内容和各种属性。当通过拼接音频片段生成语音时,会生成归属图。
以下是生成的1 秒语音归属图:
以下是生成的100 毫秒语音归属图:
以下是生成10 毫秒语音的归因图:
以下是生成1 毫秒语音的归因图:
谷歌的像素递归神经网络(PixelRNN)和像素卷积神经网络(PixelCNN)模型确保能够生成包含复杂形态的图像——不是一次生成一个像素,而是同时生成整个颜色通道。在任何时刻,一个颜色通道至少需要对每个图像进行一千次预测。通过这种方式,我们可以将二维的 PixelNet 转换为一维的 WaveNet;这一思路如图所示:
上图展示了 WaveNet 模型的结构。WaveNet 是一个完整的卷积神经网络(CNN),其中卷积层包含多种扩张因子。这些因子帮助 WaveNet 的感受野随着深度的增加呈指数增长,并且有助于覆盖成千上万个时间步长。
在训练过程中,人工语音记录输入序列以创建波形。训练完成后,我们通过从网络中采样生成合成语音。每次采样时,从网络计算出的概率分布中选取一个值。接收到的值作为下一步的输入,然后进行新的预测。在每一步生成这些样本是非常昂贵的;然而,为了生成复杂且逼真的音频,这是必需的。
有关 PixelRNN 的更多信息,请访问arxiv.org/pdf/1601.06759.pdf
,而关于条件图像生成与 PixelCNN 解码器的信息请访问arxiv.org/pdf/1606.05328.pdf
。
架构
WaveNet 神经网络的架构通过生成音频和语音合成翻译展示了惊人的效果,因为它直接生成原始音频波形。
当输入包括之前的样本和额外的参数时,网络通过条件概率生成下一个音频样本,输出为音频波形。
作为输入的波形被量化为固定范围的整数。这发生在音频预处理之后。通过对这些整数幅度进行独热编码,生成张量。因此,卷积层仅访问当前和先前的输入,从而减少了通道的维度。
以下是 WaveNet 架构的示意图:
使用一堆因果扩张卷积层来构建网络核心。每一层都是一个带孔的扩张卷积层,仅访问过去和当前的音频样本。
然后,所有层接收到的输出被合并,并通过一系列密集的后处理层,输入到原始通道中。之后,softmax 函数将输出转换为分类分布。
损失函数是通过计算每个时间步的输出与下一个时间步输入之间的交叉熵来得到的。
WaveNet 中的网络层
在这里,我们将重点介绍生成过滤器大小为 2 的扩张因果卷积网络层。注意,这些概念也适用于更大的过滤器大小。
在此生成过程中,用于计算单个输出值的计算图可以看作是一个二叉树:
图中底层的输入节点是树的叶子节点,而输出层是根节点。中间的计算由输入层上方的节点表示。图中的边对应于多个矩阵。由于计算是二叉树结构,因此图的总体计算时间为O(2^L)。当L较大时,计算时间会呈指数级增长。
然而,由于该模型是随着时间反复应用的,因此存在大量的冗余计算,我们可以缓存这些计算以提高生成单个样本的速度。
关键的洞察是这样的——给定图中的某些节点,我们已经拥有计算当前输出所需的所有信息。我们通过类比 RNNs,称这些节点为递归状态。这些节点已经被计算过,因此我们所需要做的就是将它们缓存到不同的层中,如下图所示:
注意,在下一个时间点,我们将需要不同子集的递归状态。因此,我们需要在每一层缓存多个递归状态。我们需要保留的数量等于该层的扩张值,如下图所示:
如前图所示,带有箭头标记的地方,递归状态的数量与层中的扩张值相同。
算法的组件
构建语音检测器的算法包含两个组件:
-
生成模型
-
卷积队列
这两个组件在下图中展示:
生成模型可以视为 RNN 的一个步骤。它以当前观测值和若干递归状态作为输入,然后计算输出预测和新的递归状态。
卷积队列存储了由下面的层计算得到的新递归状态。
让我们开始构建模型。
构建模型
我们将使用 DeepMind 的 WaveNet 实现句子级别的英语语音识别。但是,在构建模型之前,我们需要考虑一些数据点:
-
首先,虽然 WaveNet 论文(在本章开头提供)使用了 TIMIT 数据集进行语音识别实验,但我们将使用免费的 VCTK 数据集代替。
-
其次,论文在扩张卷积层后添加了一个均值池化层进行降采样。我们已经从
.wav
文件中提取了梅尔频率倒谱系数(MFCC),并删除了最后的均值池化层,因为原始设置在我们的 TitanX 图形处理单元(GPU)上无法运行。 -
第三,由于 TIMIT 数据集具有音素标签,论文使用了两个损失项来训练模型:音素分类和下一个音素预测。相反,我们将使用单一的连接时序分类(CTC)损失,因为 VCTK 提供了句子级标签。因此,我们只使用扩张的 Conv1D 层,而没有任何扩张的 Conv1D 层。
-
最后,由于时间限制,我们不会进行定量分析,如双语评估下游评分(BLEU)得分以及通过结合语言模型的后处理。
依赖项
下面是所有需要首先安装的依赖库列表:
-
tensorflow
-
sugartensor
-
pandas
-
librosa
-
scikits.audiolab
如果你遇到librosa
库的问题,可以尝试使用pip
安装ffmpeg
。
数据集
我们使用了 VCTK、LibriSpeech 和 TED-LIUM 发布 2 的数据集。训练集中的句子总数由这三个数据集组成,总计 240,612 个句子。验证集和测试集仅使用 LibriSpeech 和 TED-LIUM 语料库构建,因为 VCTK 语料库没有验证集和测试集。下载每个语料库后,将它们提取到asset/data/VCTK-Corpus
、asset/data/LibriSpeech
和asset/data/TEDLIUM_release2
目录中。
你可以在这里找到这些数据集的链接:CSTR VCTK 语料库: homepages.inf.ed.ac.uk/jyamagis/page3/page58/page58.html
LibriSpeech ASR 语料库: www.openslr.org/12
TED-LIUM: www-lium.univ-lemans.fr/en/content/ted-lium-corpus
数据集预处理
TED-LIUM 发布 2 数据集提供的是 SPH 格式的音频数据,因此我们应该将其转换为librosa
库能够处理的格式。为此,请在asset/data
目录下运行以下命令,将 SPH 格式转换为 WAV 格式:
find -type f -name '*.sph' | awk '{printf "sox -t sph %s -b 16 -t wav %s\n", $0, $0".wav" }' | bash
如果你没有安装sox
,请先安装它。
我们发现训练中的主要瓶颈是磁盘读取时间,因为音频文件的大小。在处理之前,最好将音频文件缩小,以便更快地执行。因此,我们决定将所有音频数据预处理为 MFCC 特征文件,这些文件要小得多。此外,我们强烈建议使用固态硬盘(SSD),而不是传统硬盘。
在控制台中运行以下命令以预处理整个数据集:
python preprocess.py
使用处理过的音频文件,我们现在可以开始训练网络。
训练网络
我们将通过执行以下命令开始训练网络:
python train.py ( <== Use all available GPUs )
如果你正在使用启用了 CUDA 的机器,请使用以下命令:
CUDA_VISIBLE_DEVICES=0,1 python train.py ( <== Use only GPU 0, 1 )
你可以在asset/train
目录中看到生成的.ckpt
文件和日志文件。启动tensorboard--logdir asset/train/log
来监控训练过程。
我们已经在 3 台 Nvidia 1080 Pascal GPU 上训练了这个模型 40 小时,直到达到 50 个纪元,然后选择了验证损失最小的纪元。在我们的例子中,这是第 40 个纪元。如果你看到内存溢出的错误,请在train.py
文件中将batch_size
从 16 减少到 4。
每个纪元的 CTC 损失如下:
纪元 | 训练集 | 验证集 | 测试集 |
---|---|---|---|
20 | 79.541500 | 73.645237 | 83.607269 |
30 | 72.884180 | 69.738348 | 80.145867 |
40 | 69.948266 | 66.834316 | 77.316114 |
50 | 69.127240 | 67.639895 | 77.866674 |
在这里,你可以看到训练数据集和测试数据集的值之间的差异。这个差异主要是由于训练数据集的体积更大。
测试网络
在训练网络之后,你可以使用以下命令检查验证集或测试集的 CTC 损失:
python test.py --set train|valid|test --frac 1.0(0.01~1.0)
如果你只想测试数据集的一部分以进行快速评估,frac
选项将非常有用。
将一个语音 WAV 文件转换成英文文本
接下来,你可以通过执行以下命令将语音 WAV 文件转换为英文文本:
python recognize.py --file
这将把语音 WAV 文件转换为英文句子。
结果将会打印在控制台上;尝试以下命令作为示例:
python recognize.py --file asset/data/LibriSpeech/test-clean/1089/134686/1089-134686-0000.flac
python recognize.py --file asset/data/LibriSpeech/test-clean/1089/134686/1089-134686-0001.flac
python recognize.py --file asset/data/LibriSpeech/test-clean/1089/134686/1089-134686-0002.flac
python recognize.py --file asset/data/LibriSpeech/test-clean/1089/134686/1089-134686-0003.flac
python recognize.py --file asset/data/LibriSpeech/test-clean/1089/134686/1089-134686-0004.flac
结果如下:
他希望晚餐有炖菜,胡萝卜和萝卜,还有破损的土豆和肥羊肉块,放入浓厚的胡椒面调料中,一勺勺地倒进你肚子里。他劝告自己,在早早的夜幕降临后,黄色的灯光会在这里和那里亮起,肮脏的妓院区,嘿,伯蒂,脑袋里有好主意吗?10 号新鲜的内莉在等你,晚安,丈夫。
真实情况如下:
他希望晚餐有炖菜,胡萝卜和萝卜,还有破损的土豆和肥羊肉块,放入浓厚的胡椒面调料中,一勺勺地倒进你肚子里。他劝告自己,在早早的夜幕降临后,黄色的灯光会在这里和那里亮起,肮脏的妓院区,嘿,伯蒂,脑袋里有好主意吗?10 号新鲜的内莉在等你,晚安,丈夫。
正如我们之前提到的,由于没有语言模型,因此在某些情况下会出现大写字母和标点符号误用,或者单词拼写错误。
获取模型
与图像问题不同,找到一个提供检查点的语音转文本预训练深度学习模型并不容易。幸运的是,我找到了以下的 WaveNet 语音转文本实现。为了将模型导出以进行压缩,我运行了 Docker 镜像,加载了检查点,并将其写入了协议缓冲文件。运行此操作时,请使用以下命令:
python export_wave_pb.py
我们将为推理构建图,加载检查点,并将其写入协议缓冲文件,如下所示:
batch_size = 1 # batch size
voca_size = data.voca_size
x = tf.placeholder(dtype=tf.sg_floatx, shape=(batch_size, None, 20))
# sequence length except zero-padding
seq_len = tf.not_equal(x.sg_sum(axis=2), 0.).sg_int().sg_sum(axis=1)
# encode audio feature
logit = get_logit(x, voca_size)
# ctc decoding
decoded, _ = tf.nn.ctc_beam_search_decoder(logit.sg_transpose(perm=[1, 0, 2]), seq_len, merge_repeated=False)
# to dense tensor
y = tf.add(tf.sparse_to_dense(decoded[0].indices, decoded[0].dense_shape, decoded[0].values), 1, name="output")
with tf.Session() as sess:
tf.sg_init(sess)
saver = tf.train.Saver()
saver.restore(sess, tf.train.latest_checkpoint('asset/train'))
graph = tf.get_default_graph()
input_graph_def = graph.as_graph_def()
with tf.Session() as sess:
tf.sg_init(sess)
saver = tf.train.Saver()
saver.restore(sess, tf.train.latest_checkpoint('asset/train'))
# Output model's graph details for reference.
tf.train.write_graph(sess.graph_def, '/root/speech-to-text-wavenet/asset/train', 'graph.txt', as_text=True)
# Freeze the output graph.
output_graph_def = graph_util.convert_variables_to_constants(sess,input_graph_def,"output".split(","))
# Write it into .pb file.
with tfw.gfile.GFile("/root/speech-to-text-wavenet/asset/train/wavenet_model.pb", "wb") as f:
f.write(output_graph_def.SerializeToString())
接下来,我们需要构建一个 TensorFlow 模型并对其进行量化,以便在移动应用中使用。
使用 Bazel 构建 TensorFlow 并量化模型
要使用 TensorFlow 对模型进行量化,你需要安装 Bazel 并克隆 TensorFlow 仓库。我建议创建一个新的虚拟环境,在其中安装和构建 TensorFlow。完成后,你可以运行以下命令:
bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph \
--in_graph=/your/.pb/file \
--outputs="output_node_name" \
--out_graph=/the/quantized/.pb/file \
--transforms='quantize_weights'
你可以查看 TensorFlow 网站上的官方量化教程,了解变换中的其他选项。量化后,模型大小减小了 75%,从 15.5 MB 降到 4 MB,原因是进行了 8 位转换。由于时间限制,我还没有通过测试集计算字母错误率,以量化量化前后准确率的下降。
关于神经网络量化的详细讨论,Pete Warden 写了一篇很好的文章,名为 使用 TensorFlow 进行神经网络量化 (petewarden.com/2016/05/03/how-to-quantize-neural-networks-with-tensorflow/
)。
请注意,你也可以按照本节中的说明进行完整的 8 位计算图转换。
经过此转换后,模型的大小减少到 5.9 MB,推理时间加倍。这可能是由于 8 位计算没有针对 macOS 平台上的 Intel i5 处理器进行优化,而该平台用于编写应用程序。
现在我们已经有了一个压缩的预训练模型,让我们看看在 Android 上部署该模型还需要做些什么。
TensorFlow 操作注册
在这里,我们将使用 Bazel 构建 TensorFlow 模型,创建一个 .so
文件,该文件可以通过 Java 原生接口(JNI)调用,并包括我们在预训练 WaveNet 模型推理中需要的所有操作库。我们将在 Android 应用程序中使用构建好的模型。
如需了解更多关于 Bazel 的信息,可以参考以下链接:docs.bazel.build/versions/master/bazel-overview.html
。
首先,通过取消注释并更新软件 开发 工具包(SDK)和原生开发工具包(NDK)的路径,编辑克隆的 TensorFlow 仓库中的 WORKSPACE 文件。
接下来,我们需要找出在预训练模型中使用了哪些操作,并生成一个包含这些信息的 .so
文件。
首先,运行以下命令:
bazel build tensorflow/python/tools:print_selective_registration_header && \
bazel-bin/tensorflow/python/tools/print_selective_registration_header \
--graphs=path/to/graph.pb > ops_to_register.h
所有.pb
文件中的操作将列在ops_to_register.h
中。
接下来,移动op_to_register.h
到/tensorflow/tensorflow/core/framework/
,并运行以下命令:
bazel build -c opt --copt="-DSELECTIVE_REGISTRATION" \
--copt="-DSUPPORT_SELECTIVE_REGISTRATION" \
//tensorflow/contrib/android:libtensorflow_inference.so \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
--crosstool_top=//external:android/crosstool --cpu=armeabi-v7a
不幸的是,虽然我没有收到任何错误消息,但是.so
文件仍然没有包含在头文件中列出的所有操作:
Modify BUILD in /tensorflow/tensorflow/core/kernels/
如果您尚未尝试第一个选项并且已经获取了模型中操作的列表,则可以通过使用tf.train.write_graph
命令并在终端中键入以下内容来获取操作:
grep "op: " PATH/TO/mygraph.txt | sort | uniq | sed -E 's/^.+"(.+)".?$/\1/g'
接下来,在 Android 库部分的android_extended_ops_group1
或android_extended_ops_group2
中添加缺失的操作,并使.so
文件更小化,删除任何不必要的操作。现在,运行以下命令:
bazel build -c opt //tensorflow/contrib/android:libtensorflow_inference.so \
--crosstool_top=//external:android/crosstool \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
--cpu=armeabi-v7a
您将在以下位置找到libtensorflow_inference.so
文件:
bazel-bin/tensorflow/contrib/android/libtensorflow_inference.so
请注意,在 Android 上运行此命令时,我们遇到了与sparse_to_dense
操作相关的错误。如果您希望重复此工作,请在第 153 行将REGISTER_KERNELS_ALL(int64);
添加到sparse_to_dense_op.cc
中,并重新编译。
除了.so
文件外,我们还需要一个 JAR 文件。您可以简单地将其添加到build.gradle
文件中,如下所示:
allprojects {
repositories {
jcenter()
}
}
dependencies {
compile 'org.tensorflow:tensorflow-android:+'
}
或者,您可以运行以下命令:
bazel build //tensorflow/contrib/android:android_tensorflow_inference_java
您会在以下代码块中找到该文件:
bazel-bin/tensorflow/contrib/android/libandroid_tensorflow_inference_java.jar
现在,将这两个文件移动到您的 Android 项目中。
构建一个 Android 应用程序
在本节中,我们将构建一个 Android 应用程序,将用户的语音输入转换为文本。基本上,我们将构建一个语音到文本的转换器。我们已经修改了 TensorFlow 语音示例,放置在 TensorFlow Android 演示库中以供本练习使用。
您可以在github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android
找到 TensorFlow Android 演示应用程序。
在演示的build.gradle
文件实际上帮助您构建.so
和 JAR 文件。因此,如果您想要使用自己的模型开始演示示例,您只需获取您的操作列表,修改 BUILD 文件,并让build.gradle
文件处理其余部分。我们将在以下部分详细介绍设置 Android 应用程序的细节。
要求
构建 Android 应用程序所需的要求如下:
-
TensorFlow 1.13
-
Python 3.7
-
NumPy 1.15
-
python-speech-features
TensorFlow 链接:github.com/tensorflow/tensorflow/releases
Python 链接:pip.pypa.io/en/stable/installing/
NumPy 链接:docs.scipy.org/doc/numpy-1.13.0/user/install.html
Python-speech-features 链接:github.com/jameslyons/python_speech_features
现在,让我们从头开始构建 Android 应用程序。在此应用程序中,我们将录制音频然后将其转换为文本。
根据您的操作系统设置 Android Studio,方法是访问以下链接:developer.android.com/studio/install
。本项目中使用的代码库已从此处提供的 TensorFlow 示例进行修改:github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android.
我们将使用 TensorFlow 示例应用程序并根据我们的需求进行编辑。
添加应用程序名称和公司域名,如下图所示:
下一步,选择目标 Android 设备的版本。我们将选择最低版本为 API 15:
接下来,我们将添加 Empty Activity 或 No Activity:
现在,让我们开始添加活动并使用生成的 TensorFlow 模型来获取结果。我们需要启用两个权限,以便在应用程序中使用它们,如下代码块所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mlmobileapps.speech">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-sdk
android:minSdkVersion="25"
android:targetSdkVersion="25" />
<application android:allowBackup="true"
android:debuggable="true"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:theme="@style/MaterialTheme">
<activity android:name="org.tensorflow.demo.SpeechActivity"
android:screenOrientation="portrait"
android:label="@string/activity_name_speech">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
我们将为应用程序提供一个最小的 UI,包含几个TextView
组件和一个Button
:
以下 XML 布局模仿了上面截图中的 UI:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#6200EE"
tools:context="org.tensorflow.demo.SpeechActivity">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:textColor="#fff"
android:layout_marginLeft="10dp"
android:layout_marginTop="30dp"
android:text="Talk within 5 seconds"
android:textAlignment="center"
android:textSize="24dp" />
<TextView
android:id="@+id/output_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:layout_gravity="top"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:textAlignment="center"
android:textSize="24dp" />
</LinearLayout>
<Button
android:id="@+id/start"
android:background="#ff0266"
android:textColor="#fff"
android:layout_width="wrap_content"
android:padding="20dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="50dp"
android:text="Record Voice" />
</FrameLayout>
让我们添加语音识别活动的步骤,如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
// Set up the UI.
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_speech);
startButton = (Button) findViewById(R.id.start);
startButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
startRecording();
}
});
outputText = (TextView) findViewById(R.id.output_text);
// Load the Pretrained WaveNet model.
inferenceInterface = new TensorFlowInferenceInterface(getAssets(), MODEL_FILENAME);
requestMicrophonePermission();
}
请注意,我们这里不会讨论 Android 的基础知识。
接下来,我们将启动录音机,具体步骤如下:
public synchronized void startRecording() {
if (recordingThread != null) {
return;
}
shouldContinue = true;
recordingThread =
new Thread(
new Runnable() {
@Override
public void run() {
record();
}
});
recordingThread.start();
}
以下代码展示了record()
方法的实现:
private void record() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
// Estimate the buffer size we'll need for this device.
int bufferSize =
AudioRecord.getMinBufferSize(
SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) {
bufferSize = SAMPLE_RATE * 2;
}
short[] audioBuffer = new short[bufferSize / 2];
AudioRecord record =
new AudioRecord(
MediaRecorder.AudioSource.DEFAULT,
SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize);
if (record.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(LOG_TAG, "Audio Record can't initialize!");
return;
}
record.startRecording();
Log.v(LOG_TAG, "Start recording");
while (shouldContinue) {
int numberRead = record.read(audioBuffer, 0, audioBuffer.length);
Log.v(LOG_TAG, "read: " + numberRead);
int maxLength = recordingBuffer.length;
recordingBufferLock.lock();
try {
if (recordingOffset + numberRead < maxLength) {
System.arraycopy(audioBuffer, 0, recordingBuffer, recordingOffset, numberRead);
} else {
shouldContinue = false;
}
recordingOffset += numberRead;
} finally {
recordingBufferLock.unlock();
}
}
record.stop();
record.release();
startRecognition();
}
以下代码展示了音频识别方法的实现:
public synchronized void startRecognition() {
if (recognitionThread != null) {
return;
}
shouldContinueRecognition = true;
recognitionThread =
new Thread(
new Runnable() {
@Override
public void run() {
recognize();
}
});
recognitionThread.start();
}
private void recognize() {
Log.v(LOG_TAG, "Start recognition");
short[] inputBuffer = new short[RECORDING_LENGTH];
double[] doubleInputBuffer = new double[RECORDING_LENGTH];
long[] outputScores = new long[157];
String[] outputScoresNames = new String[]{OUTPUT_SCORES_NAME};
recordingBufferLock.lock();
try {
int maxLength = recordingBuffer.length;
System.arraycopy(recordingBuffer, 0, inputBuffer, 0, maxLength);
} finally {
recordingBufferLock.unlock();
}
// We need to feed in float values between -1.0 and 1.0, so divide the
// signed 16-bit inputs.
for (int i = 0; i < RECORDING_LENGTH; ++i) {
doubleInputBuffer[i] = inputBuffer[i] / 32767.0;
}
//MFCC java library.
MFCC mfccConvert = new MFCC();
float[] mfccInput = mfccConvert.process(doubleInputBuffer);
Log.v(LOG_TAG, "MFCC Input======> " + Arrays.toString(mfccInput));
// Run the model.
inferenceInterface.feed(INPUT_DATA_NAME, mfccInput, 1, 157, 20);
inferenceInterface.run(outputScoresNames);
inferenceInterface.fetch(OUTPUT_SCORES_NAME, outputScores);
Log.v(LOG_TAG, "OUTPUT======> " + Arrays.toString(outputScores));
//Output the result.
String result = "";
for (int i = 0;i<outputScores.length;i++) {
if (outputScores[i] == 0)
break;
result += map[(int) outputScores[i]];
}
final String r = result;
this.runOnUiThread(new Runnable() {
@Override
public void run() {
outputText.setText(r);
}
});
Log.v(LOG_TAG, "End recognition: " +result);
}
模型通过TensorFlowInferenceInterface
类运行,如上面的代码所示。
一旦我们完成代码并成功运行,启动应用程序。
在第一次运行时,您需要允许应用程序使用手机的内部麦克风,如下图所示:
一旦我们授权使用麦克风,点击“录音”按钮并在 5 秒内输入语音。在以下截图中显示了使用印度口音输入how are you
关键字的两次尝试。对于美国和英国口音效果更好。
第一次尝试如下:
第二次尝试如下:
您应该尝试使用您自己的口音来获取正确的输出。这是开始构建您自己语音检测器的一个非常简单的方法,您可以进一步改进。
总结
在本章中,你学习了如何独立构建一个完整的语音检测器。我们详细讨论了 WaveNet 模型的工作原理。有了这个应用,我们可以使一个简单的语音转文本转换器运行;然而,要获得完美的结果,还需要做很多改进和更新。你也可以通过将模型转换为 CoreML,在 iOS 平台上构建相同的应用。
在下一章中,我们将继续构建一个手写数字分类器,使用修改版国家标准与技术研究所(MNIST)模型。
第八章:使用 GAN 识别手写数字
在本章中,我们将构建一个安卓应用程序,该程序通过使用对抗学习来检测手写数字并识别数字是什么。我们将使用修改后的国家标准与技术研究院 (MNIST) 数据集进行数字分类。我们还将了解生成对抗网络 (GANs)的基本知识。
在本章中,我们将更详细地探讨以下主题:
-
GAN 简介
-
理解 MNIST 数据库
-
构建 TensorFlow 模型
-
构建安卓应用程序
本应用程序的代码可以在github.com/intrepidkarthi/AImobileapps
找到。
GAN 简介
GAN 是一类机器学习 (ML) 算法,用于无监督学习。它们由两个深度神经网络组成,这两个网络相互竞争(因此被称为对抗式)。GAN 是由 Ian Goodfellow 和其他研究人员(包括 Yoshua Bengio)于 2014 年在蒙特利尔大学提出的。
Ian Goodfellow 关于 GAN 的论文可以在arxiv.org/abs/1406.2661
找到。
GAN 具有模拟任何数据的潜力。这意味着 GAN 可以被训练生成任何数据的相似版本,例如图像、音频或文本。下图展示了 GAN 的简单工作流程:
GAN 的工作流程将在接下来的章节中解释。
生成算法与判别算法
为了理解 GAN,我们必须知道判别算法和生成算法是如何工作的。判别算法试图预测一个标签并对输入数据进行分类,或者将它们归类到数据所属的类别中。另一方面,生成算法则尝试预测特征,以给出某个特定标签。
例如,一个判别算法可以预测一封邮件是否是垃圾邮件。这里,垃圾邮件是标签之一,邮件中提取的文本被认为是输入数据。如果将标签看作y,将输入看作x,我们可以将其表示如下:
另一方面,生成算法则尝试预测这些输入特征(在前面的公式中是x)的可能性。生成模型关注的是如何获得x,而判别模型关注的是x与y之间的关系。
以 MNIST 数据库为例,生成器将生成图像并传递给判别器。如果图像确实来自 MNIST 数据集,判别器将验证该图像。生成器生成图像的目的是希望它能够通过判别器的验证,即使它是假的(如上图所示)。
GAN 如何工作
根据我们的示例,我们将假设输入的是数字:
-
生成器接受随机数作为输入,并返回图像作为输出
-
输出图像传递给判别器,同时,判别器也从数据集中接收输入
-
判别器接受真实和假输入图像,并返回一个 0 到 1 之间的概率(其中 1 表示真实性的预测,0 表示假图像的预测)
使用本章中讨论的示例应用程序,我们可以使用相同的步骤,将用户手绘的图像作为假图像之一,并尝试找出其正确性的概率值。
理解 MNIST 数据库
MNIST 数据集包含 60,000 个手写数字。它还包含一个由 10,000 个数字组成的测试数据集。虽然它是 NIST 数据集的一个子集,但该数据集中的所有数字都进行了大小归一化,并且已被居中在一个 28 x 28 像素的图像中。在这里,每个像素包含一个 0-255 的值,对应于其灰度值。
MNIST 数据集可以在yann.lecun.com/exdb/mnist/
找到。NIST 数据集可以在www.nist.gov/srd/nist-special-database-19
找到。
构建 TensorFlow 模型
在这个应用程序中,我们将构建一个基于 MNIST 数据集的 TensorFlow 模型,并将在我们的 Android 应用程序中使用它。一旦我们有了 TensorFlow 模型,我们将把它转换成 TensorFlow Lite 模型。下载模型并构建 TensorFlow 模型的步骤如下:
这是我们的模型如何工作的架构图。实现这一点的方式如下所示:
使用 TensorFlow,我们可以通过一行 Python 代码下载 MNIST 数据,如下所示:
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
#Reading data
mnist = input_data.read_data_sets("./data/", one_hot-True)
现在,我们已经下载了 MNIST 数据集。之后,我们将按照之前的代码读取数据。
现在,我们可以运行脚本来下载数据集。我们将从控制台运行该脚本,如下所示:
> python mnist.py
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting MNIST_data/train-images-idx3-ubyte.gz Successfully downloaded train-labels-idxl-ubyte.gz 28881 bytes.
Extracting MNIST_data/train -labels -idxl -ubyte.gz
Successfully downloaded tlOk -images -idx3 -ubyte.gz 1648877 bytes. Extracting MNIST_data/t1Ok -images -idx3 -ubyte.gz
Successfully downloaded tlOk -labels -idxl -ubyte.gz 4542 bytes. Extracting MNIST_data/t1Ok -labels -idxl -ubyte.gz
一旦我们准备好数据集,我们将添加一些将在应用程序中使用的变量,如下所示:
image_size = 28
labels_size = 10
learning_rate = 0.05
steps_number = 1000
batch size = 100
我们需要定义这些变量,以控制构建模型时所需的参数,这是 TensorFlow 框架要求的。这个分类过程很简单。28 x 28 图像中存在的像素数量是 784。因此,我们有相应数量的输入层。设置好架构后,我们将训练网络并评估结果,以了解模型的有效性和准确性。
现在,让我们定义在前面的代码块中添加的变量。根据模型处于训练阶段还是测试阶段,不同的数据将传递到分类器中。训练过程需要标签,以便能够将其与当前预测进行匹配。这个变量如下所定义:
#Define placeholders
training_data = tf.placeholder(tf.float32, [None, image_size*image_size])
labels = tf.placeholder(tf.float32, [None, labels_size])
随着计算图的评估进行,占位符将被填充。在训练过程中,我们调整偏置和权重的值,以提高结果的准确性。为了实现这一目标,我们将定义权重和偏置参数,如下所示:
#Variables to be tuned
W = tf.Variable(tf.truncated_normal([image_size*image_size, labels_size], stddev=0.1))
b = tf.Variable(tf.constant(0.1, shape-[labels_size]))
一旦我们有了可以调节的变量,我们就可以一步完成输出层的构建:
#Build the network
output = tf.matmul(training_data, W) + b
我们已经成功地使用训练数据构建了网络的输出层。
训练神经网络
通过优化损失,我们可以使训练过程有效。我们需要减少实际标签值与网络预测值之间的差异。定义这种损失的术语是交叉熵。
在 TensorFlow 中,交叉熵通过以下方法提供:
tf.nn.softmax_cross_entropy_with_logits
该方法将 softmax 应用于模型的预测。Softmax 类似于逻辑回归,输出一个介于 0 和 1.0 之间的小数。例如,电子邮件分类器的逻辑回归输出 0.9 表示邮件为垃圾邮件的概率为 90%,不为垃圾邮件的概率为 10%。所有概率的总和为 1.0,如下表所示:
Softmax 通过神经网络层实现,就在输出层之前。Softmax 层必须与输出层具有相同数量的节点。
损失使用tf.reduce_mean
方法定义,并且在训练步骤中使用GradientDescentOptimizer()
方法来最小化损失。如下所示的代码演示了这一过程:
#Defining the loss
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels-labels, logits-output))
# Training step with gradient descent
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
GradientDescentOptimizer
方法会通过调整输出中的w和b(权重和偏置参数)的值,经过多步迭代,直到我们减小损失并更接近准确的预测,具体如下:
# Accuracy calculation
correct_prediction = tf.equal(tf.argmax(output, 1), tf.argmax(labels, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
我们通过初始化会话和变量来开始训练,如下所示:
# Run the training
sess = tf.InteractiveSession() sess.run(tf.global_variables_initializer())
基于之前定义的步数(steps_number
)参数,算法将循环运行。然后我们将运行优化器,如下所示:
for i in range(steps_number):
# Get the next batch input_batch,
labels_batch = mnist.train.next_batch(batch_size)
feed_dict = {training_data: input_batch, labels: labels_batch}
# Run the training step
train_step.run(feed_dict=feed_dict)
使用 TensorFlow,我们可以衡量算法的准确性并打印准确率值。只要准确度提高并找到停止的阈值,我们可以继续优化,如下所示:
# Print the accuracy progress on the batch every 100 steps
if i%100 == 0:
train_accuracy = accuracy.eval(feed_dict=feed_dict)
print("Step %d, batch accuracy %g %%"%(i, train_accuracy*100))
一旦训练完成,我们可以评估网络的性能。我们可以使用训练数据来衡量性能,如下所示:
# Evaluate on the test set
test_accuracy = accuracy.eval(feed_dict=ftraining_data: mnist.test.images, labels: mnist.test.labels})
print("Test accuracy: %g %%"%(test_accuracy*100))
当我们运行 Python 脚本时,控制台上的输出如下:
Step 0, training batch accuracy 13 %
Step 100, training batch accuracy 80 %
Step 200, training batch accuracy 87 %
Step 300, training batch accuracy 81 %
Step 400, training batch accuracy 86 %
Step 500, training batch accuracy 85 %
Step 600, training batch accuracy 89 %
Step 700, training batch accuracy 90 %
Step 800, training batch accuracy 94 %
Step 900, training batch accuracy: 89.49 %
Test accuracy 91 %
现在,我们的准确率已经达到了 89.2%。当我们尝试进一步优化结果时,准确度反而下降;这就是我们设置阈值停止训练的原因。
让我们为 MNIST 数据集构建 TensorFlow 模型。在 TensorFlow 框架中,提供的脚本将 MNIST 数据集保存为 TensorFlow(.pb
)模型。相同的脚本附在本应用程序的代码库中。
此应用程序的代码可以在 github.com/intrepidkarthi/AImobileapps
找到。
我们将从以下 Python 代码行开始训练模型:
$:python mnist.py
现在我们将运行脚本生成我们的模型。
以下脚本帮助我们通过添加一些额外参数来导出模型:
python mnist.py --export_dir /./mnist_model
可以在时间戳目录 /./mnist_mode1/
下找到保存的模型(例如,/./mnist_model/1536628294/
)。
获得的 TensorFlow 模型将使用 toco
转换为 TensorFlow Lite 模型,如下所示:
toco \
--input_format=TENSORFLOW_GRAPHDEF
--output_format=TFLITE \
--output_file=./mnist.tflite \
--inference_type=FLOAT \
--input_type=FLOAT
--input_arrays=x \
--output_arrays=output
--input_shapes=1,28,28,1 \
--graph_def_file=./mnist.pb
Toco 是一个命令行工具,用于运行 TensorFlow Lite 优化转换器(TOCO),将 TensorFlow 模型转换为 TensorFlow Lite 模型。上述 toco
命令会生成 mnist.tflite
作为输出,我们将在下一节中在我们的应用程序中使用它。
构建 Android 应用程序
让我们按照我们建立的模型逐步创建 Android 应用程序。我们将从在 Android Studio 中创建一个新项目开始:
- 在 Android Studio 中创建一个新应用程序:
- 将创建的 TensorFlow Lite 模型拖到
assets
文件夹中,以及labels.txt
文件。我们将从 assets 文件夹中读取模型和标签:
前面的屏幕截图显示了项目中的文件结构。如果需要,我们也可以将模型文件存储在辅助存储器中。
FreeHandView 的一个优点是我们可以创建一个简单的视图,用户可以在其中绘制任意数量的数字。除此之外,屏幕上的条形图将显示检测到的数字的分类。
我们将使用逐步过程来创建分类器。
这是我们将用来绘制数字的 FreeHandView
构造方法。我们使用必要的参数初始化 Paint
对象,如下所示:
public FreeHandView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(DEFAULT_COLOR);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setXfermode(null); mPaint.setAlpha(Oxff);
mEmboss = new EmbossMaskFilter(new float[] I1, 1, 1}, 0.4f, 6, 3.5f);
mBlur = new BlurMaskFilter(5, BlurMaskFilter.Blur.NORMAL);
}
上述代码块中每个参数的功能如下所述:
-
mPaint.setAntiAlias(true)
:setFlags()
的辅助函数,用于设置或清除ANTI_ALIAS_FLAG
位。抗锯齿会使所绘制内容的边缘更加平滑,但不影响形状的内部。 -
mPaint.setDither(true)
:setFlags()
的辅助函数,用于设置或清除DITHER_FLAG
位。抖动会影响高于设备精度的颜色如何被降低采样。 -
mPaint.setColor(DEFAULT_COLOR)
: 设置画笔的颜色。 -
mPaint.setStyle(Paint.Style.STROKE)
: 设置画笔的样式,用于控制如何解释基元的几何图形(除了drawBitmap
,它总是假定Fill
)。 -
mPaint.setStrokeJoin(Paint.Join.ROUND)
: 设置画笔的Join
。 -
mPaint.setStrokeCap(Paint.Cap.ROUND)
: 设置画笔的Cap
。 -
mPaint.setXfermode(null)
: 设置或清除传输模式对象。 -
mPaint.setAlpha(Oxff)
:一个辅助方法,用于setColor()
,它仅分配颜色的alpha
值,保持其r
、g
和b
值不变。
在视图生命周期的 init()
方法内部,我们将初始化 ImageClassifier
,并传入 BarChart
对象:
public void init(DisplayMetrics metrics, ImageClassifier classifier, BarChart barChart) {
int height = 1000;
int width = 1000;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
currentColor = DEFAULT_COLOR; strokeWidth = BRUSH_SIZE;
mClassifier = classifier;
this.predictionBar = predictionBar;
this.barChart = barChart; addValuesToBarEntryLabels();
}
我们将使用以下库中的图表:github.com/PhilJay/MPAndroidChart
。
我们将初始化 BarChart
视图,x 轴包含从零到九的数字,y 轴包含从 0 到 1.0 的概率值:
BarChart barChart = (BarChart) findViewByld(R.id.barChart);
barChart.animateY(3000);
barChart.getXAxis().setEnabled(true);
barChart.getAxisRight().setEnabled(false);
barChart.getAxisLeft().setAxisMinimum(0.0f); // start at zero
barChart.getAxisLeft().setAxisMaximum(1.0f); // the axis maximum is 100
barChart.getDescription().setEnabled(false);
barChart.getLegend().setEnabled(false);
// the labels that should be drawn on the X-Axis final String[] barLabels = new String[]{"0", "1", "2", "3", "4", "5", "6", n7,1, 118n, n9,1};
//To format the value as integers
IAxisValueFormatter formatter = new IAxisValueFormatter() {
@Override public String getFormattedValue(float value, AxisBase axis) {
return barLabels(int) value); }
};
barChart.getXAxis().setGranularity(0f); // minimum axis-step (interval) is 1
barChart.getXAxis().setValueFormatter(formatter);
barChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
barChart.getXAxis().setTextSize(5f);
一旦我们初始化了 BarChart
视图,我们将调用视图生命周期中的 OnDraw()
方法,按照用户手指的移动路径绘制笔画。OnDraw()
方法是视图生命周期方法的一部分,一旦 BarChart
视图初始化完成,就会被调用。
在 OnDraw
方法中,我们将跟踪用户的手指移动,并将相同的动作绘制到画布上,如下所示:
@Override protected void onDraw(Canvas canvas) {
canvas.save();
mCanvas.drawColor(backgroundColor);
for (FingerPath fp : paths) {
mPaint.setColor(fp.color);
mPaint.setStrokeWidth(fp.strokeWidth);
mPaint.setMaskFilter(null);
if (fp.emboss)
mPaint.setMaskFilter(mEmboss);
else if (fp.blur)
mPaint.setMaskFilter(mBlur);
mCanvas.drawPath(fp.path, mPaint);
}
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); canvas. restore();
}
在 onTouchEvent()
方法中,我们可以通过移动、抬起和按下事件跟踪用户的手指位置,并基于此触发相应的动作。这是视图生命周期中的一个方法,用于跟踪事件。当你触摸手机时,会根据手指的移动触发三个事件。在 action_down
和 action_move
的情况下,我们将处理事件,在视图上绘制手指的移动轨迹,使用初始的画笔对象属性。当 action_up
事件被触发时,我们会将视图保存为文件,并将文件图像传递给分类器识别数字。之后,我们将使用 BarChart
视图表示概率值。这些步骤如下:
@Override public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
BarData exampleData;
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN :
touchStart(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE :
touchMove(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP :
touchUp();
Bitmap scaledBitmap = Bitmap.createScaledBitmap(mBitmap, mClassifier.getImageSizeX(), mClassifier.getImageSizeY(), true);
Random rng = new Random();
try {
File mFile;
mFile = this.getContext().getExternalFilesDir(String.valueOf (rng.nextLong() + ".png"));
FileOutputStream pngFile = new FileOutputStream(mFile);
}
catch (Exception e){ }
//scaledBitmap.compress(Bitmap.CompressFormat.PNG, 90, pngFile);
Float prediction = mClassifier.classifyFrame(scaledBitmap);
exampleData = updateBarEntry();
barChart.animateY(1000, Easing.EasingOption.EaseOutQuad);
XAxis xAxis = barChart.getXAxis();
xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override public String getFormattedValue(float value, AxisBase axis) {
return xAxisLabel.get((int) value);
});
barChart.setData(exampleData);
exampleData.notifyDataChanged(); // let the data know a // dataset changed
barChart.notifyDataSetChanged(); // let the chart know it's // data changed
break;
}
return true;
}
在 ACTION_UP
动作中,有一个 updateBarEntry()
方法调用。在这里,我们调用分类器来获取结果的概率值。这个方法还会根据分类器的结果更新 BarChart
视图,如下所示:
public BarData updateBarEntry() {
ArrayList<BarEntry> mBarEntry = new ArrayList<>();
for (int j = 0; j < 10; ++j) {
mBarEntry.add(new BarEntry(j, mClassifier.getProbability(j)));
}
BarDataSet mBarDataSet = new BarDataSet(mBarEntry, "Projects");
mBarDataSet.setColors(ColorTemplate.COLORFUL_COLORS);
BarData mBardData = new BarData(mBarDataSet);
return mBardData;
}
FreeHandView 看起来像这样,并附有一个空的柱状图:
- 在某些情况下,分类会因为部分匹配而降低概率,以下截图展示了这种情况:
- 还有其他情况,概率会出现多个部分匹配。以下截图展示了这种情况:
任何此类情况都需要对模型进行更严格的训练。
- 点击 RESET 按钮将清除视图,以便你重新绘制。我们将使用以下代码行来实现此功能:
resetButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
paintView.clear();
}
}) ;
一旦点击 RESET 按钮,前面的代码会清除 FreeHandView 区域,如下所示:
你还可以通过写入数字以外的字符并检查条形图上的输出性能,来验证应用程序是否正常工作。
在本节中,我们学习了应用程序如何对手绘的不同数字进行分类,并提供这些数字是否正确的概率。
摘要
使用这个 Android 应用程序,我们可以学习如何使用 TensorFlow Lite 编写一个手写分类器。随着更多手写字母数据集的加入,我们应该能够使用 GANs 识别任何语言的字母。
在下一章中,我们将构建一个情感分析模型,并在其基础上构建一个应用程序。
第九章:使用 LinearSVC 进行文本情感分析
在这一章中,我们将构建一个 iOS 应用程序,通过用户输入对文本和图像进行情感分析。我们将使用现有的数据模型,这些模型是通过使用 LinearSVC 为同一目的构建的,并将这些模型转换为核心机器学习(ML)模型,以便在我们的应用程序中更易于使用。
情感分析是识别任何给定数据(如文本、图像、音频或视频)所激发的情感或观点的过程。情感分析有很多应用场景。即使现在,政党也能轻松识别出选民的普遍心态,他们还有潜力改变这一心态。
让我们来看看如何从现有的数据集中构建我们自己的情感分析 ML 模型。在这一章中,我们将探讨以下主题:
-
使用 scikit-learn 构建 ML 模型
-
线性支持向量分类(LinearSVC)
-
构建一个 iOS 应用程序
使用 scikit-learn 构建 ML 模型
在这一部分中,我们将构建我们自己的模型。有现成的数据集可供使用,这些数据集与 Twitter 动态数据相关,主题是产品和电影评论。你可以选择适合你自己的数据集;在这一章中,我们将选择一个包含客户评论的数据集。
一个包含正面和负面客户评论的数据集可以在boston.lti.cs.cmu.edu/classes/95-865-K/HW/HW3/
找到。你可以通过以下链接下载该数据集:boston.lti.cs.cmu.edu/classes/95-865-K/HW/HW3/epinions3.zip
。
上述数据集包含有关产品的正面和负面反馈,如下图所示:
我们将使用 scikit-learn 管道和 LinearSVC 来训练数据集。让我们更详细地了解这两者。
Scikit-learn
这是一个基于 NumPy、SciPy 和 Matplotlib 构建的数据挖掘和数据分析 Python 库。它有助于解决与分类、回归、聚类和降维相关的 ML 问题。
scikit-learn 管道
scikit-learn 管道的主要目的是将 ML 步骤组合在一起。可以通过交叉验证来设置各种参数。scikit-learn 提供了一系列转换器,用于数据预处理(数据清理)、核近似(扩展)、无监督的降维(减少)和特征提取(生成)。该管道包含一系列转换器,并最终产生一个估算器。
管道按顺序应用一系列变换,然后是最终的估算器。在管道中,fit
和 transform
方法在中间步骤中实现。fit
方法仅在管道操作的最后由最终估算器实现。为了缓存管道中的变换器,使用了 memory
参数。
用于分类的估算器是一个 Python 对象,它实现了方法的 fit (x, y) 和 predict (T) 值。例如,class sklearn.svm.SVC
实现了 SVC。模型的参数作为估算器构造函数的参数传递。scikit-learn 中的 memory
类具有 class sklearn.utils.Memory(*args, **kwargs)
签名。它有缓存、清除、减少、评估和格式化内存对象的方法。cache
方法用于计算函数的返回值。返回的对象是一个 MemorizedFunc
对象,它的行为类似于一个函数,并提供额外的方法用于缓存查找和管理。cache
方法接受诸如 func=None, ignore=None, verbose=None, mmap_mode=False
等参数。
class signature
管道如下:
class sklearn.pipeline.Pipeline(steps, memory=None)
接下来,让我们看看下一个重要组件。
LinearSVC
scikit-learn 库中的一个类是 LinearSVC,它支持稀疏和密集类型的输入。使用一对多方案处理多类支持。LinearSVC 类似于 SVC,其中参数为 kernel = linear
,但在 LinearSVC 中使用的是 liblinear
来实现该参数,而不是 SVC 中使用的 libvsm
。这为我们提供了更多选择惩罚和损失函数的灵活性,并且有助于更好地对大量样本进行缩放。
class
签名如下:
class sklearn.svm.LinearSVC(penalty=’l2’, loss=’squared_hinge’, dual=True, tol=0.0001, C=1.0, multi_class=’ovr’, fit_intercept=True, intercept_scaling=1, class_weight=None, verbose=0, random_state=None, max_iter=1000)
现在是时候开始构建我们的模型了,具体如下:
- 我们将从导入所有必要的库开始,具体如下:
import re
import coremltools
import pandas as pd
import numpy as np
from nltk.corpus import stopwords
from nltk import word_tokenize
from string import punctuation
from sklearn.feature_extraction import DictVectorizer
from sklearn.pipeline import Pipeline
from sklearn.svm import LinearSVC
from sklearn.model_selection import GridSearchCV
re
库是提供匹配操作的正则表达式库,使得处理文本数据变得更加容易。nltk
库用于根据我们的需求格式化文本,而 sklearn
提供了所需的机器学习工具。coremltools
库帮助我们将 sklearn
模型转换为 Core ML 模型。
- 现在,让我们开始读取输入,具体如下:
# Read reviews from CSV
reviews = pd.read_csv('epinions.csv')
reviews = reviews.as_matrix()[:, :]
print "%d reviews in dataset" % len(reviews)
上述代码读取了 CSV 文件,并将其转换为一个包含所有行和列的 numpy
数组。现在,我们已经准备好数据集,可以开始从数据中提取特征。
- 现在,让我们进行特征选择,具体如下:
# Create features
def features(sentence):
stop_words = stopwords.words('english') + list(punctuation)
words = word_tokenize(sentence)
words = [w.lower() for w in words]
filtered = [w for w in words if w not in stop_words and not
w.isdigit()]
words = {}
for word in filtered:
if word in words:
words[word] += 1.0
else:
words[word] = 1.0
return words
- 我们将从向量化
features
函数开始。然后,我们将提取 DataFrame 中每个句子的特征,并将它们存储在X
变量中。之后,我们将设置目标变量。目标变量将是输出。在我们的案例中,我们将为每个句子获取一个标签,指示其中的情感:
# Vectorize the features function
features = np.vectorize(features)
# Extract the features for the whole dataset
X = features(reviews[:, 1])
# Set the targets
y = reviews[:, 0]
- 在我们的例子中,我们将创建一个包含
DictVectorizer
和LinearSVC
的管道。DictVectorizer
,顾名思义,将字典转换为向量。我们选择了GridSearchCV
来从一系列通过参数网格化的模型中选择最佳模型:
# Do grid search
clf = Pipeline([("dct", DictVectorizer()), ("svc", LinearSVC())])
params = {
"svc__C": [1e15, 1e13, 1e11, 1e9, 1e7, 1e5, 1e3, 1e1, 1e-1, 1e-3,
1e-5]
}
gs = GridSearchCV(clf, params, cv=10, verbose=2, n_jobs=-1)
gs.fit(X, y)
model = gs.best_estimator_
- 然后,我们将打印出结果,如下所示:
# Print results
print model.score(X, y)
print "Optimized parameters: ", model
print "Best CV score: ", gs.best*score*
- 现在,我们可以将 scikit-learn 模型转换为
mlmodel
,如下所示:
# Convert to CoreML model
coreml_model = coremltools.converters.sklearn.convert(model)
coreml_model.short_description = 'Sentiment analysis AI projects.'
coreml_model.input_description['input'] = 'Features extracted from
the text.'
coreml_model.output_description['classLabel'] = 'The most likely polarity (positive or negative or neutral), for the given input.'
coreml_model.output_description['classProbability'] = 'The probabilities for each class label, for the given input.'
coreml_model.save('Sentiment.mlmodel')
一旦我们有了模型,就可以开始构建应用程序了。
构建 iOS 应用程序
让我们开始构建 iOS 应用程序,使用在上一阶段构建的模型。该模型将根据输入文本的性质(积极、中性或消极)预测输出。
要构建此应用程序,应使用 Xcode 版本 10.1:
- 创建一个新的项目,选择单视图应用,如以下截图所示:
-
在下一屏幕上提到我们的应用程序名称。
-
在下一个向导屏幕上,为你的应用程序选择一个合适的名称。
-
填写其余的字段,包括组织名称以及组织标识符。
-
我们在此应用程序中不使用核心数据,因此跳过该选项。
-
让我们从在 Xcode 中创建一个新应用开始。以下截图展示了如何在 Xcode 中创建一个新项目:
- 接下来,创建一个故事板,如以下截图所示:
- 一旦你选择了保存应用程序的文件位置,你将能够看到带有新应用程序信息的“常规”选项卡,如以下截图所示:
- 我们将创建一个简单的 UI,在底部放置一个按钮来显示情感:
//initialize Ui components
private lazy var textView: UITextView = self.makeTextView() private lazy var accessoryView = UIView()
private lazy var resultLabel: UILabel = self.makeResultLabel() private lazy var clearButton: UIButton = self.makeClearButton()
private let padding = CGFloat(16)
private var textViewBottomConstraint: NSLayoutConstraint?
- 我们将定义
sentiments
作为枚举类型,如下所示:
enum Sentiment {
case neutral
case positive
case negative
var emoji: String {
switch self {
case .neutral:
return ""
case .positive:
return ""
case .negative:
return ""
}
}
var color: UIColor? {
switch self {
case .neutral:
return UIColor(named: "NeutralColor")
case .positive:
return UIColor(named: "PositiveColor")
case .negative:
return UIColor(named: "NegativeColor")
}
}
}
- 让我们编写
ClassificationService
来获取我们构建的模型的结果:
//variables initialization
private let options: NSLinguisticTagger.Options = [.omitWhitespace, .omitPunctuation, .omitOther]
private lazy var tagger: NSLinguisticTagger = .init(
tagSchemes: NSLinguisticTagger.availableTagSchemes(forLanguage: "en"),
options: Int(self.options.rawValue)
)
private extension ClassificationService {
func features(from text: String) -> [String: Double] {
var wordCounts = [String: Double]()
tagger.string = text
let range = NSRange(location: 0, length: text.utf16.count)
// let's tokenize the input text and count the sentence
tagger.enumerateTags(in: range, scheme: .nameType, options: options) { _, tokenRange, _, _ in
let token = (text as NSString).substring(with: tokenRange).lowercased()
// Skip small words
guard token.count >= 3 else {
return
}
if let value = wordCounts[token] {
wordCounts[token] = value + 1.0
} else {
wordCounts[token] = 1.0
}
}
return wordCounts
}
- 输入被传递到
prediction
方法,以将语句过滤为positive
、negative
或neutral
情感:
func predictSentiment(from text: String) -> Sentiment {
do {
let inputFeatures = features(from: text)
// Make prediction only with 2 or more words
guard inputFeatures.count > 1 else {
throw Error.featuresMissing
}
let output = try model.prediction(input: inputFeatures)
switch output.classLabel {
case "Positive":
return .positive
case "Negative":
return .negative
default:
return .neutral
}
} catch {
return .neutral
}
}
}
- 让我们通过初始化
view
组件来编写ViewController
,如下所示:
override func viewDidLoad() {
super.viewDidLoad()
title = "Sentiment Analysis".uppercased()
view.backgroundColor = UIColor(named: "BackgroundColor")
view.addSubview(textView)
accessoryView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 60)
accessoryView.addSubview(resultLabel)
accessoryView.addSubview(clearButton)
textView.inputAccessoryView = accessoryView
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardDidShow(notification:)),
name: .UIKeyboardDidShow,
object: nil
)
setupConstraints()
//Show default sentiment as neutral
show(sentiment: .neutral)
}
- 按钮和标签上的初始
setupConstraints
定义如下:
func setupConstraints() {
//input textview
textView.translatesAutoresizingMaskIntoConstraints = false
textView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80).isActive = true
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding).isActive = true
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding).isActive = true
textViewBottomConstraint = textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
textViewBottomConstraint?.isActive = true
//result label at the bottom
resultLabel.translatesAutoresizingMaskIntoConstraints = false
resultLabel.centerXAnchor.constraint(equalTo: accessoryView.centerXAnchor).isActive = true
resultLabel.centerYAnchor.constraint(equalTo: accessoryView.centerYAnchor).isActive = true
//Clear button at the bottom right
clearButton.translatesAutoresizingMaskIntoConstraints = false
clearButton.trailingAnchor.constraint(
equalTo: accessoryView.trailingAnchor,
constant: -padding
).isActive = true
clearButton.centerYAnchor.constraint(equalTo: accessoryView.centerYAnchor).isActive = true
}
- 定义
Show()
方法,如下所示:
private func show(sentiment: Sentiment) { accessoryView.backgroundColor = sentiment.color
resultLabel.text = sentiment.emoji
}
- 让我们在模拟器上运行应用程序。你可以在以下截图中看到输出:
- 现在,让我们使用不同的输入来运行我们的应用程序并获取输出,如下所示:
- 使用相同输出的消极输入示例语句显示在以下截图中:
- 使用中性文本的示例输入显示在以下截图中:
在这里,我们能够从给定的文本输入中获取情感。现在,你可以通过改进现有模型,进一步提升模型的效果。
你可以在多个来源中深入探索如何在图像上识别情感。一个示例应用是 Fine-tuning CNNs for Visual Sentiment Prediction。你可以在 github.com/imatge-upc/sentiment-2017-imavis
阅读关于这个应用的详细信息。
总结
到此为止,你应该能够构建自己的 TensorFlow 模型,并将其转换为 Core ML 模型,以便在 iOS 应用中使用。相同的 TensorFlow 模型可以转换为 TensorFlow Lite 模型,然后可以在 Android 应用或 iOS 应用中使用。现在,你可以开始这个任务并尝试不同的结果。话虽如此,我们现在准备进入下一章。
在下一章,你将使用本书中学到的知识,继续探索如何构建自己的应用。
第十章:下一步是什么?
计算机日新月异,设备的外形和功能发生了巨大变化。过去,我们只会在办公室看到计算机;然而现在,我们在家庭办公桌上、膝上、口袋里、甚至手腕上都能看到它们。随着机器不断融入更多智能,市场也变得越来越多样化。
目前几乎每个成年人都会随身携带一部设备,据估计我们每天至少会查看智能手机 50 次,无论是否有需要。这些设备影响着我们的日常决策过程。如今,设备上都配备了像 Siri、Google Assistant、Alexa 或 Cortana 这样的应用程序——这些功能旨在模仿人类的智能。能够回答任何提问的能力使得这些技术显得比人类更为强大。在这一过程中,这些系统通过使用所有用户获得的集体智能不断提升。你与虚拟助手互动的次数越多,它们提供的结果就会越好。
尽管取得了这些进展,我们距离通过机器真正创建一个人脑还差多远?我们现在已经是 2019 年了;如果科学能够找到控制大脑神经元的方法,这在不久的将来可能会成为现实。模拟人类能力的机器正在帮助解决复杂的文本、视觉和音频问题。它们与人脑每天执行的任务类似;举个例子,平均而言,人脑每天大约做出 35,000 个决策。
虽然我们将来能够模拟人脑,但这会付出相应的代价。目前我们还没有更便宜的解决方案。人脑模拟的功耗级别限制了与实际人脑相比的开发进程。人脑的功耗大约是 20 瓦,而模拟程序的功耗约为 1 兆瓦或更多。人脑中的神经元以 200 赫兹的速度工作,而典型的微处理器速度为 2 吉赫,约为人脑神经元速度的 1000 万倍。
虽然我们距离克隆人脑仍然遥远,但我们可以实现一种基于先前数据以及来自类似设备的数据做出决策的算法。这时,人工智能(AI)的子集就派上用场了。通过预定义的算法识别我们所拥有的复杂数据中的模式,这些类型的智能能够为我们提供有用的信息。
当计算机开始在没有每次明确指令的情况下做出决策时,我们就实现了机器学习(ML)功能。现在,机器学习无处不在,包括通过诸如识别电子邮件垃圾邮件、推荐电子商务网站上最适合购买的产品、自动在社交媒体照片中标记你的面孔等功能。所有这些都是通过识别历史数据中的模式来完成的,同时也通过减少数据中不必要的噪声并生成优质输出的算法来实现的。随着数据的不断积累,计算机可以做出更好的决策。
手机已经成为大多数今天正在生产的数字产品的默认消费媒介。随着数据消费的增加,我们必须尽可能快地将结果呈现给用户。例如,当你滚动浏览 Facebook 动态时,很多内容将基于你的兴趣以及你的朋友们喜欢的内容。由于用户在这些应用上花费的时间有限,因此服务器端和客户端有许多算法在运行,用以根据你在 Facebook 动态中偏好的内容加载和组织内容。如果所有的算法都能在本地设备上运行,我们就不需要依赖互联网来更快地加载内容。这只有通过在客户端设备上执行这些过程,而不是在云端处理,才能实现。
随着移动设备处理能力的提高,我们将被鼓励在移动设备本身上运行所有 ML 模型。现在,已经有很多服务在客户端设备上处理,例如从照片中识别面孔(如苹果的 Face ID 功能),它使用本地设备上的机器学习。
虽然诸如人工智能(AI)、增强现实(AR)、虚拟现实(VR)、机器学习(ML)和区块链等多个话题正处于趋势之中,但机器学习的增长速度超过了其他技术,且在各个领域中已经有了明确的应用案例。机器学习算法正被应用于图像、文本、音频和视频,以获取我们所期望的输出。
如果你是初学者,那么有多种方式可以开始你的工作,利用所有正在构建的免费和开源框架。如果你担心自己构建模型,你可以从 Google 的 Firebase ML Kit 开始。或者,你可以使用 TensorFlow 构建自己的模型,然后将该模型转换为 TensorFlow Lite 模型(适用于 Android)和 Core ML 模型(适用于 iOS)。
本章将涵盖以下主题:
-
-
流行的基于 ML 的云服务
-
当你构建第一个基于机器学习的移动应用时,从哪里开始
-
-
进一步阅读的参考资料
流行的基于 ML 的云服务
要开始你的机器学习之旅,你可以使用现有的基于云的机器学习服务。机器学习即服务(MLaaS)在所有现代商业领域中得到了广泛应用。数据变得更加便宜,数据量呈指数级增长。因此,设备的处理能力正在以更快的速度增长。这一趋势促使了多种基于云的服务的出现,如软件即服务(SaaS)、平台即服务(PaaS)和基础设施即服务(IaaS),现在又加入了 MLaaS。
尽管我们可以在移动设备上运行机器学习模型,但仍然受到内存、CPU 和 图形处理单元(GPU)资源的限制。在这种情况下,云服务变得非常有用。
从云端的机器学习开始,提供多种服务,如面部识别、光学字符识别、图像识别、地标识别、数据可视化以及自然语言处理(NLP)。所有这些选项都得到了深度神经网络、卷积神经网络(CNNs)、概率模型等技术的支持。大多数云服务提供商采用商业模式,提供一些免费的限制额度供开发者探索,然后决定哪种服务最适合开发他们的应用程序。
以下部分将解释现在可用的四大服务,这些服务在开发者和企业中也非常流行。作为初学者,你可以探索每个提供商的功能,并选择适合自己应用程序的服务。
IBM Watson 服务
IBM Watson 通过多种产品提供深度学习服务。它提供一个名为 AI 助手的文本机器人服务,支持移动平台和聊天服务;还有一个名为 Watson Studio 的服务,帮助构建和分析模型。IBM Watson 还拥有另一个独立的 API 服务,处理文本、视觉和语音。
可以找到一个示例应用程序,用于使用 Core ML 开发视觉应用程序。可以在github.com/watson-developer-cloud/visual-recognition-coreml
查看。
微软 Azure 认知服务
微软提供了五个类别的即用型认知服务,如下所示:
-
视觉 API
-
语音 API
-
知识 API
-
搜索 API
-
语言 API
这些 API 的功能将在以下部分中介绍。
视觉 API
视觉 API 是用于图像处理的算法,能够智能地识别、生成图像描述并对图片进行审核。
语音 API
通过语音 API,语音音频可以转换为文本。这些 API 使用语音印刷进行验证,或为应用程序添加说话人识别功能。
知识 API
为了解决智能推荐和语义搜索等任务,我们使用知识 API,帮助我们将复杂的信息和数据映射出来。
搜索 API
搜索 API 提供 Bing 网络搜索 API 给您的应用。它还利用将数十亿个网页、图像、视频和新闻结合在一个 API 调用中的能力。
语言 API
语言 API 允许您的应用处理自然语言,使用预构建脚本,评估情感,并学习如何识别用户需求。
针对上述 API,提供了多个示例应用。这些示例可以在 azure.microsoft.com/en-in/resources/samples/?%20sort=0&sort=0
中找到。
亚马逊机器学习
亚马逊 Web 服务 (AWS) 提供了多个基于机器学习的服务。所有这些服务都紧密结合,以便在 AWS 云中高效运行。以下部分将重点介绍其中一些服务。
视觉服务
AWS 提供了 Amazon Rekognition,这是一种基于深度学习的服务,旨在处理图像和视频。我们还可以将该服务集成到移动设备上。
聊天服务
Amazon Lex 帮助构建聊天机器人。这个行业仍在增长,随着越来越多数据的涌入,服务将变得更加智能,能够更好地回答查询。
语言服务
一些语言服务的示例包括 Amazon Comprehend,它帮助揭示文本中的洞察和关系;Amazon Translate,它帮助进行流畅的文本翻译;Amazon Transcribe,它帮助进行自动语音识别;Amazon Polly,它帮助将自然听起来的文本转化为语音。
您可以在 github.com/aws-samples/machine-learning-samples
中查看一些示例应用。
Google Cloud 机器学习
如果您想在云中运行模型,Google Cloud ML Engine 提供了 TensorFlow、scikit-learn 和 XGBoost 在云中的强大功能和灵活性。如果这不适合您的需求,您可以选择最适合您场景的 API 服务。在 Google Cloud ML 中,提供了多个 API 供您选择。这些 API 分为以下四大类:
-
视觉:Cloud Vision API 帮助进行图像识别和分类;Cloud Video Intelligence API 帮助进行场景级别的视频标注;AutoML Vision 帮助进行自定义图像分类模型。
-
对话:Dialogflow 企业版构建对话界面;Cloud Text-to-Speech API 将文本转为语音;Cloud Speech-to-Text API 将语音转为文本。
-
语言:Cloud Translation API 用于语言检测和翻译;Cloud Natural Language API 用于文本解析和分析;AutoML Translation 用于自定义领域特定的翻译;AutoML Natural Language 帮助构建自定义文本分类模型。
-
知识:Cloud Inference API 从时间序列数据集中提取洞察。
你可以在github.com/GoogleCloudPlatform/cloud-vision
找到许多 Google Vision API。
还有一些开发者常用的其他服务,包括Dialogflow和Wit.ai。
构建你的第一个机器学习模型
通过这本书所获得的知识,你可以开始开发运行在手机上的自己的模型。你需要首先确定问题陈述。有许多使用场景不需要机器学习模型;我们不能把机器学习强行应用到所有事情中。因此,在构建自己的模型之前,你需要遵循一个循序渐进的步骤:
-
确定问题。
-
规划模型的有效性;决定数据是否能在预测未来类似案例的输出时发挥作用。例如,收集相似年龄、性别和位置的人的购买历史,将有助于预测新客户的购买偏好。然而,如果你要预测的是新客户的身高,那么这些数据将不起作用。
-
开发一个简单的模型(可以基于 SQL)。这将有助于减少构建实际模型时的工作量。
-
验证数据并丢弃不必要的数据。
-
构建实际的模型。
随着各类参数(来自多个传感器的数据)的数据量在本地设备以及云服务提供商处呈指数级增长,我们可以构建更多个性化内容的更好应用场景。已经有很多应用程序在使用机器学习,比如邮件服务(Gmail 和 Outlook)和出租车服务(Uber、Ola 和 Lyft)。
构建自己模型的限制
尽管机器学习(ML)越来越流行,但在移动平台上运行机器学习模型以便普及到大众尚不可行。当你为移动应用构建自己的模型时,也存在一些限制。虽然可以在本地设备上进行预测而无需依赖云服务,但不建议构建一个根据当前操作做出预测并在本地设备上积累数据的不断演化的模型。由于移动设备的内存和处理能力的限制,目前我们只能在移动设备上运行预先构建的模型并从中获得推理结果。一旦移动设备有了更强大的处理器,我们就可以在本地设备上训练和改进模型。
与此相关的使用案例有很多。Apple 的 Face ID 就是一个例子,它在本地设备上运行一个需要 CPU 或 GPU 计算的模型。当设备能力在未来得到提升时,将有可能在设备上构建一个全新的模型。
准确性是人们避免在移动设备上开发模型的另一个原因。由于我们目前无法在移动设备上进行繁重的运算,与基于云的服务相比,准确性看起来十分有限,原因在于内存和计算能力的限制。你可以运行 TensorFlow 和 Core ML 库中适用于移动设备的模型。
TensorFlow Lite 模型可以在www.tensorflow.org/lite/models
找到;Core ML 模型可以在github.com/likedan/Awesome-CoreML-Models
找到。
个性化用户体验
个性化的用户体验(UX)将是任何基于移动设备的消费类业务的基本用例,以为移动应用的用户提供更具针对性和个性化的体验。这可以通过分析以下数据点来实现:
-
谁是你的客户?
-
他们从哪里来?
-
他们的兴趣是什么?
-
他们怎么评价你?
-
他们在哪里找到你?
-
他们有什么痛点吗?
-
他们能承担得起你的产品吗?
-
他们有购买或搜索的历史记录吗?
例如,考虑零售公司或餐厅的客户。如果我们能回答上述问题,我们就能获得关于客户的丰富数据,从中我们可以构建数据模型,提供更个性化的体验(借助于机器学习)。我们可以分析并识别相似的客户,为所有用户提供更好的用户体验(UX),并精准地锁定未来的潜在客户。
让我们在接下来的部分中进一步详细探讨这些想法。
提供更好的搜索结果
提供更好的搜索结果是其中一个主要用例,尤其是在移动应用中,以提供更多上下文相关的结果,而不是基于文本的结果。这将有助于改善业务的客户基础。机器学习算法应从用户的搜索中学习并优化结果。甚至拼写错误也可以通过直观的方式进行修正。此外,收集用户的行为数据(关于他们如何使用你的应用)将有助于提供最佳的搜索结果,从而按个性化的方式对结果进行排名。
精准定位正确的用户
大多数应用在用户第一次安装时都会收集用户的年龄和性别数据。这将帮助你了解应用的常见用户群体。你还将获得用户的数据,这能让你了解用户使用应用的频率和时长,以及位置信息(如果用户允许的话)。这些数据将有助于你在未来预测客户目标。例如,你将能够看到你的用户群体是否来自 18 到 25 岁之间,并且以女性为主。在这种情况下,你可以制定吸引更多男性用户的策略,或者只专注于吸引女性用户。这个算法应该能够预测和分析所有这些数据,这将有助于你进行市场营销并扩大用户基础。
机器学习驱动的移动应用在许多小众的使用场景中能够发挥巨大作用,其中一些应用场景如下:
-
自动产品标签
-
用于计步器、Uber 和 Lyft 的时间估算
-
基于健康的推荐
-
运输成本估算
-
供应链预测
-
财务管理
-
物流优化
-
提高生产力
总结
有了本书中获得的所有基本概念,你可以开始构建具有机器学习能力的应用程序。此外,随着与设备如 Amazon Alexa、Google Home 或 Facebook Portal 的互动方式不断增加,你会发现更多的应用场景来构建机器学习应用。最终,我们正在朝着一个更加互联的世界迈进,这将使得连接和交流变得更紧密,并引领我们创造更好的机器学习体验。
进一步阅读
现在有很多机器学习课程可以在线学习。以下是一些课程的列举:
-
如果你是初学者,可以从 Andrew Ng 在 Coursera 上讲授的机器学习教程开始,链接地址是
www.coursera.org/learn/machine-learning
-
可以在
developers.google.com/machine-learning/crash-course/
找到谷歌提供的机器学习速成课程。 -
Adam Geitgey 写的最好的(也是最启发性的)基于机器学习的博客系列之一,可以在
medium.comi@ageitgey/machine-learning-is-fun-80ea3ec3c471
找到。 -
你可以在
codelabs.developers.google.com/codelabs/tensorflow-for-poets
开始学习 TensorFlow 的技能。 -
对 TensorFlow 的更深入了解可以参考
petewarden.com/2016/09/27/tensorflow-for-mobile-poets/