MAChineLearning
Mac 上的机器学习
简介
MAChineLearning (发音为ˈmækʃiːn ˈlɜːnɪŋ) 是一个框架,提供了在 Mac 上快速简捷地使用原生代码进行机器学习实验的方法,并提供了一些特定于自然语言处理的支持。它使用 Objective-C 编写,但与 Swift 兼容。
目前该框架支持以下内容:
与其他许多适用于 macOS 和 iOS 的机器学习库不同,MAChineLearning 包含了其神经网络的全部训练实现。您不需要另外一种语言或另一个框架来训练网络,所有需要的工具都在这里。
在 iOS 上使用
该框架已在 iOS 上进行了测试,并且与之兼容。此外,该框架现在通过 CocoaPods 进行分发,其目标包括 macOS 和 iOS。
在您的设备上直接训练神经网络!祝您玩得开心!
神经网络
有关神经网络介绍,请参阅维基百科的人工神经网络。
当前MAChineLearning中的神经网络支持
- 多层感知机的任何深度(仅限于内存限制)。
- 5种激活函数
- 线性。
- 修正线性(也称为ReLU).
- 阶梯(如果输出小于0.5则为0,如果大于则为1)。
- sigmoid(也称为logistic).
- TanH(也称为双曲正切).
- 2种损失函数
- 均方误差。
- 交叉熵.
- 2种反向传播
- 标准。
- 弹性(也称为RPROP).
- 按样本或批量进行训练。
- 从/到字典的加载/保存网络状态。
- 单/双精度(需要重新编译,默认为单精度)。
内部代码大量使用Accelerate框架,特别是vDSP和vecLib函数。它在CPU上尽可能快。当然在GPU上会更快,但它已经非常快(比Java等效速度快20倍)。
教程
设置网络
设置网络只需两行代码
#import <MAChineLearning/MAChineLearning.h>
// Create a perceptron with 3 input lines and 1 output neuron
MLNeuralNetwork *net= [NeuralNetwork createNetworkWithLayerSizes:@[@3, @1]
outputFunctionType:MLActivationFunctionTypeStep];
[net randomizeWeights];
这些行创建一个具有3个输入和1个输出、具有阶梯激活函数的单层感知器,并随机其初始权重。请参阅以下图示
网络对象公开了您控制它所需的所有内容,即
- 输入向量。
- 输出向量。
- 期望输出向量。
- 前向传播、反向传播和更新权重的函数。
- 保存状态和从保存的状创建新网络的函数。
出于性能原因,将向量公开为C缓冲区(数组)。它们是`MLReal`类型,默认情况下是`float`的别名。要使用双精度,可以在`MLReal.h`文件中将此类型重新定义为`double`,然后重新编译。只需遵循注释。
加载输入
这是如何在输入缓冲区中加载数据的方法
// Clear the input buffer
for (int i= 0; i < net.inputBuffer; i++)
net.inputBuffer[i]= 0.0;
// Fill appropriate buffer elements
net.inputBuffer[0]= 1.0;
net.inputBuffer[2]= 0.5;
您可以使用加速框架以更快的速度清除缓冲区。在这种情况下,您可以使用在 MLReal.h
中定义的宏,以防将来从单精度移动到双精度时更改函数名称。
// Clear the input buffer using Accelerate
ML_VCLR(net.inputBuffer, 1, net.inputSize);
// Fill appropriate buffer elements
net.inputBuffer[0]= 1.0;
net.inputBuffer[2]= 0.5;
计算输出
一旦输入缓冲区被填满,计算输出就很简单了
// Compute the output
[net feedForward];
// Log the output
NSLog(@"Output: %.2f", net.outputBuffer[0]);
训练
如果输出不符合预期,您可以在特定的缓冲区中设置期望的输出,并要求网络反向传播错误。
// Clear the expected output buffer using Accelerate
ML_VCLR(net.expectedOutputBuffer, 1, net.outputSize);
// Set the expected output
net.expectedOutputBuffer[0]= 0.5;
// Backpropagate the error
[net backPropagateWithLearningRate:0.1];
网络自动计算错误并应用梯度下降算法以获得新的权重。《学习率》参数在较大值时使学习更快(但更不确定),在较小值时使学习更慢(但更确定)。在使用弹性反向传播时,由于RPROP算法自动设置学习率,因此必须将学习率指定为0。
新权重不会立即应用:它们被存储在网络上,这样您可以在应用之前运行多个前向和反向传递(即批量训练)。
一旦您的训练批处理完成,请以下方式更新权重
// Update weights
[net updateWeights];
训练循环
在训练期间,您通常会将整个样本集多次提供给网络,以提高其预测能力。对样本集的一次完整遍历称为一个周期。在输送周期时,您可以在每个样本后更新权重,或者等到一批样本(例如10个样本)被输送,以略微提高训练性能。
典型的训练循环如下
BOOL finished= NO;
do {
// Load the sample set
// ...
MLReal error= 0.0;
for (int i= 0; i < numberOfSamples; i++) {
// Clear the input buffer with vDSP
ML_VCLR(net.inputBuffer, 1, net.inputSize);
// Load the i-th sample
net.inputBuffer[0]= 1.0;
net.inputBuffer[2]= 0.5;
// ...
// Feed the network and compute the output
[net feedForward];
// Set the expected output for the i-th sample
net.expectedOutputBuffer[0]= 0.5;
// ...
// Add the error (cost) for the i-th sample
error += net.cost;
[net backPropagateWithLearningRate:0.1];
// Update weights
[net updateWeights];
}
// Compute the average error
error /= (MLReal) numberOfSamples;
// Check if average error is below the expected threshold
finished= (error < 0.0001);
} while (!finished);
具有批量更新的训练循环如下
BOOL finished= NO;
do {
// Load the sample set
// ...
MLReal error= 0.0;
for (int i= 0; i < numberOfSamples; i++) {
// Clear the input buffer with vDSP
ML_VCLR(net.inputBuffer, 1, net.inputSize);
// Load the i-th sample
net.inputBuffer[0]= 1.0;
net.inputBuffer[2]= 0.5;
// ...
// Feed the network and compute the output
[net feedForward];
// Set the expected output for the i-th sample
net.expectedOutputBuffer[0]= 0.5;
// ...
// Add the error (cost) for the i-th sample
error += net.cost;
[net backPropagateWithLearningRate:0.1];
if ((i +1) % 10 == 0) {
// Update weights for this batch
[net updateWeights];
}
}
// Compute the average error
error /= (MLReal) numberOfSamples;
// Check if average error is below the expected threshold
finished= (error < 0.0001);
} while (!finished);
网络通过使用简单的状态机强制正确的调用顺序。请查看以下状态图
如果你尝试调用与上面图中所示状态转换不相对应的调用,网络将抛出异常。
示例
框架包含一些单元测试,展示了如何使用它,请参阅NeuralNetTests.m。
其中之一是关于维基百科上讨论的NAND逻辑引脚。测试包括一些注释行,如果取消注释,则在每个训练后都会打印出网络状态。请注意,如果您与维基百科示例进行比较,数字可能会有所不同。这是由于使用了偏差,而维基百科示例没有包括偏差。
MNIST
包含手写数字识别的MNIST示例的完整实现,请参阅main.m。它将自动下载数据集并在达到一定置信度时训练网络。期望运行时间大约为2分钟,结果错误率为2.8%。
参考文献
有很多文章解释神经网络是如何工作的,但我发现这两篇特别写得很好,足够清楚,可以作为编码的基础。
我要感谢这些人抽出时间分享他们的知识。
词袋模型
词袋模型是一种表达能力很强的将文本转换成数字的方法,以便使用神经网络进行训练。它基于一个数字向量,其中每个元素代表文本中的一个词,并且在最简单的形式中,如果是待表示的文本中包含该词,则将其设置为1或0。尽管自从词向量引入以来,它被认为已经过时,但它仍然可以在许多任务中表现出色。
构建词袋模型首先需要一个词库,每个词对应其向量中的索引。给定一段文本后,将其拆分为单独的单词(称为分词过程),然后对于每个单词,在词典中查找并设置相应数组元素。
可以通过多种方式优化此过程,包括移除高频单词(称为停用词)、较复杂或简单的分词、词袋向量规范化等。
MAChineLearning中的词袋工具包目前支持以下功能
- 12种西方语言的停用词(带有语言猜测功能)。
- 两种不同的分词算法。
- 语言学标注。
- 简单分词。
- 5种不同的规范化方式。
- 布尔型。
- L1规范。
- L2规范。
- 带有TF-IDF的L1规范。
- 带有TF-IDF的L2规范。
- 为情感分析和主题分类提供的预先构建的配置。
教程
建立词袋
在MAChineLearning中,随着文本的分词,词袋的词典是逐步构建的,您只需要从开始设定其最大规格。虽然词典包括所有的词袋向量,但每个词袋实例仅代表一个文本。
以下示例展示了如何为电影评论集建立词袋向量。
MLMutableWordDictionary *dictionary= [MLMutableWordDictionary dictionaryWithMaxSize:5000];
// Load texts
NSArray *movieReviews= // ...
for (NSString *movieReview in movieReviews) {
// Extract the bag of words for the current text
MLBagOfWords *bag= [MLBagOfWords bagOfWordsForSentimentAnalysisWithText:movieReview
documentID:nil
dictionary:dictionary
language:@"en"
featureNormalization:FeatureNormalizationTypeNone];
// Dump the extracted words and their occurrences
for (NSString *word in bag.words) {
MLWordInfo *info= [dictionary infoForWord:word];
MLReal occurrencies= net.outputBuffer[info.position];
NSLog(@"Occurrences for word '%@': %.0f", word, occurrencies);
}
}
每个分词循环都会向字典中添加单词。当遇到新的单词时,它被分配到字典的末尾位置,除非字典已满。在这种情况下,单词将被丢弃。
语言猜测
在跳过停用词时,分词过程必须知道文本所使用的语言。对于语言猜测,有两种实用方法可供选择,一种使用MacOS集成的语言学标记者,另一种使用统计停用词出现的替代算法。
#import <MAChineLearning/MAChineLearning.h>
// Guess the language with linguistic tagger
NSString *lang1= [MLBagOfWords guessLanguageCodeWithLinguisticTaggerForText:@"If you're not failing every now and again, it's a sign you're not doing anything very innovative."];
// Guess the language with stop words
NSString *lang2= [MLBagOfWords guessLanguageCodeWithStopWordsForText:@"If you're not failing every now and again, it's a sign you're not doing anything very innovative."];
语言以ISO-639-1代码的形式表示,比如“en”代表英语、“fr”代表法语等。
在神经网络中使用词袋模型
在大多数情况下,词袋模型向量被作为输入提交给神经网络。在MAChineLearning中,您可以指定词袋模型的输出缓冲区是神经网络的输入缓冲区。这可以减少内存和时间消耗。
for (NSString *movieReview in movieReviews) {
// Extract the bag of words for the current text
MLBagOfWords *bag= [MLBagOfWords bagOfWordsForSentimentAnalysisWithText:movieReview
documentID:nil
dictionary:dictionary
language:@"en"
featureNormalization:MLFeatureNormalizationTypeNone
outputBuffer:net.inputBuffer]; // Use network input buffer
// You may run the network immediately
[net feedForward];
// Evaluate the result
// ...
}
选择标记化选项
MLBagOfWords类提供了两个预配置的工厂方法,用于情感分析和主题分类,但您可能需要根据您的需求微调标记化器。
有两种标记化器
- 简单标记化器通过空白字符和换行符分割文本。
- 语言标记器使用iOS/macOS集成语言标记器,提供对每个找到标记的更深入的了解。
后者的效果当然更好,但速度也较慢。
对于简单标记化器,标记化选项包括
- 省略停用词。
- 保留所有二元组。
- 保留所有三元组。
- 保留表情符号和emoji。
对于语言标记器,标记化选项包括
- 省略停用词。
- 省略形容词。
- 省略副词。
- 省略名词。
- 省略姓名。
- 省略数字。
- 省略其他(连词、介词等)。
- 保留动词+形容词组合(例如“是好的”)。
- 保留形容词+名词组合(例如“一个好的电影”)。
- 保留副词+名词组合(例如“较早的评论”)。
- 保留名词+名词组合(例如“人类思想”)。
- 保留名词+动词组合(例如“电影是”)。
- 保留双词姓名(例如“艾伦·图灵”)。
- 保留三词姓名(例如“亚瑟·柯南·道尔”)。
- 保留所有二元组。
- 保留所有三元组。
- 保留表情符号和emoji。
使用枚举MLWordExtractorType
和MLWordExtractorOption
分别指定标记化器及其选项。
MLBagOfWords工厂方法最扩展的版本包括指定标记化器及其标记化选项的参数
MLBagOfWords *bag= [MLBagOfWords bagOfWordsWithText:text
documentID:documentID
dictionary:dictionary
buildDictionary:YES
language:@"en"
wordExtractor:MLWordExtractorTypeLinguisticTagger
extractorOptions:MLWordExtractorOptionOmitStopWords | MLWordExtractorOptionKeepNounNounCombos | MLWordExtractorOptionKeep2WordNames | MLWordExtractorOptionKeep3WordNames
featureNormalization:FeatureNormalizationTypeNone
outputBuffer:net.inputBuffer];
默认配置如下
- 对于情感分析
- 简单标记化器。
- 省略停用词。
- 保留表情符号和emoji。
- 保留所有二元组。
- 对于主题分类
- 语言标记器。
- 省略停用词。
- 省略动词、形容词、副词、名词和其他。
- 保留形容词+名词组合。
- 保留副词+名词组合。
- 保留名词+名词组合。
- 保留双词姓名。
- 保留三词姓名。
您可能需要做一点实验,以找到适合您任务的正确配置。
选择规范化选项
通过对词袋模型向量进行规范化,您可以保持其值有限,提高神经网络收敛的可能性。
可以应用以下规范化
- 无归一化:词袋(Bag of Words)向量的每个位置指定文本中对应单词的出现次数。
- 布尔归一化:不计算出现次数,每个位置只能持有1(如果单词至少出现一次)或0(如果未出现)。
- L1归一化:每个向量位置除以向量的L1范数(即链接[HREF](http://mathworld.wolfram.com/L1-Norm.html))。结果向量的长度始终为1。
- L2归一化:每个向量位置除以向量的L2 norm(即欧几里得范数,链接[HREF](http://mathworld.wolfram.com/L2-Norm.html))。结果向量的长度始终为1。
- TF-iDF与L1归一化:单词的出现次数根据
算法按整个文本语料库的频率进行逆比例缩放。这意味着经常出现的单词比很少出现的单词权重小。然后,每个向量位置再除以向量的L1长度。结果向量的长度始终为1。 - TF-iDF与L2归一化:单词的出现次数根据
算法按整个文本语料库的频率进行逆比例缩放。这意味着经常出现的单词比很少出现的单词权重小。然后,每个向量位置再除以向量的L2长度。结果向量的长度始终为1。
归一化选项使用枚举MLFeatureNormalizationType
来指定。
以下是一句示例句子
- "我认为大多数人似乎没有搞清楚这部电影"
上述句子对应的词袋(Bag of Words)向量对于无归一化、布尔归一化和L2归一化的结果如下
例子
框架包含一些单元测试,展示了如何使用它,请参阅BagOfWordsTests.m。
参考文献
以下两个资源提供了对词袋及其实现的清晰介绍
词向量
词向量,也称为 词嵌入,是一种使用高维向量来表示词汇的方法,这些向量嵌入了它们之间的语义关系。2013年,随着Word2vec 模型的引入,它们被认为是自然语言处理领域的最佳实践。
在词袋模型(Bag of Words)中,向量代表整个文本,而词向量则仅代表一个单词,更准确地说,代表了一个含义。实际上,词向量可以相加和相减以形成新的含义,以下是一些著名的例子:
- "king" - "man" + "woman" = "queen"
- "paris" - "france" + "italy" = "rome"
- "bought" - "buy" + "sell" = "sold"
MAChineLearning 工具箱中的词向量工具支持加载以下模型预计算的词向量字典:
- Word2vec (Google),文本和二进制格式。
- GloVe (Stanford),仅支持文本格式。
- fastText (Facebook),仅支持文本格式。
注意:虽然尝试使用 MAChineLearning 的神经网络从文本语料库构建词向量字典已展开,但实际上速度过慢。从头开始计算词向量实际上需要专门对此任务进行优化的代码,因为每个文本都是一个稀疏向量(实际上是一个词袋),而通用神经网络会浪费大量时间计算零值元素。
教程
加载词向量字典
MLWordVectorDictionary 类提供了加载预构建字典的工厂方法。
// Load a Word2vec dictionary in binary format
MLWordVectorDictionary *dictionary= [MLWordVectorDictionary createFromWord2vecFile:word2vecSamplePath
binary:YES];
使用词向量形成含义
从字典中轻松获取特定单词的词向量。每个向量都提供方法将某个向量加到另一个向量上或从另一个向量中减去向量,而字典提供方法在vector中搜索最近的词。
// Philadelphia is related to Pennsylvania as Miami is related to...?
MLWordVector *philadelphia= [map vectorForWord:@"philadelphia"];
MLWordVector *pennsylvania= [map vectorForWord:@"pennsylvania"];
MLWordVector *miami= [map vectorForWord:@"miami"];
MLWordVector *result= [[pennsylvania subtractVector:philadelphia] addVector:miami];
// Search for the word nearest to the resulting vector, hopefully "florida"
NSArray *similarWords= [map mostSimilarWordsToVector:result];
NSLog(@"Result: %@", [similarWords objectAtIndex:0]);
使用神经网络与词向量
每个词向量都以C缓冲区(数组)的形式公开其完整向量,准备好输入神经网络。
// Philadelphia is related to Pennsylvania as Miami is related to...?
// ...
MLWordVector *result= [[pennsylvania subtractVector:philadelphia] addVector:miami];
// Clear the input buffer with Accelerate
ML_VCLR(net.inputBuffer, 1, net.inputSize);
// Load the vector in the input buffer with Accelerate
ML_VADD(result.vector, 1, net.inputBuffer, 1, net.inputBuffer, 1, result.size);
// ...
// Run the network
[net feedForward];
// Evaluate the result
// ...
示例
框架包含一些单元测试,展示了如何使用它,请参见WordVectorTests.m。
参考文献
以下两个资源可能有助于理解如何使用词向量以及它们是如何产生的
关于我
我是一名专业开发者,但不是数据科学家。我编写了这个库,因为,你知道的,他们说,直到你能将它编码才能真正理解它。所以,这就是它。用这个库进行实验和乐趣,如果你觉得它有用,我将很高兴听到你的反馈。
已经尽了最大努力保证框架无错误,包括与其他开源软件的并排权重/结果比较。如果你发现错误或概念错误,请报告它们。这样我可以修复它们,最重要的是,我会学到一些新东西。
享受。