C# 机器学习实用指南(二)
在本章中,我们投入了大量时间来介绍决策树;它们是什么,我们如何使用它们,以及它们如何在我们应用中带来好处。在下一章中,我们将进入深度信念网络DBNs)的世界,了解它们是什么,以及我们如何使用它们。我们甚至还会谈谈计算机做梦时的情况!好吧,朋友们,这就是全部内容!在本章中,你学习了 RBMs、一点图论,以及如何在 C# 中创建和训练深度信念网络。你的 buzzword-compliant 检查清单几
原文:
annas-archive.org/md5/88e1c6764480ef08707c80e193f734c3译者:飞龙
第九章:我应该接受这份工作吗——决策树的实际应用
本章将重点关注:
-
决策树
-
如何为您的应用程序创建决策树
-
理解真值表
-
关于假阴性和假阳性的视觉直觉
在我们深入探讨之前,让我们获取一些对我们有帮助的背景信息。
为了使决策树完整且有效,它必须包含所有可能性,这意味着每个进入和离开的路径。事件序列也必须提供,并且是互斥的,这意味着如果一个事件发生,另一个事件就不能发生。
决策树是一种监督式机器学习的形式,这意味着我们必须解释输入和输出应该是什么。决策树中有决策节点和叶子节点。叶子节点是决策点,无论是最终决策还是非最终决策,而节点是决策分支发生的地方。
虽然有许多算法可供我们使用,但我们将使用迭代二分器 3(ID3)算法。在每次递归步骤中,根据一个标准(信息增益、增益率等)选择最佳分类我们正在处理的输入集的属性。必须指出的是,无论我们使用哪种算法,都没有保证能够产生可能的最小树。这直接影响了我们算法的性能。记住,在决策树中,学习仅基于启发式,而不是真正的优化标准。让我们用一个例子来进一步解释这一点。
以下例子来自jmlr.csail.mit.edu/papers/volume8/esmeir07a/esmeir07a.pdf,它说明了 XOR 学习概念,这是我们所有开发者(或应该熟悉)的。你将在后面的例子中看到这一点,但现在,**a[3]和a[4]对我们试图解决的问题完全无关。它们对我们的答案没有影响。话虽如此,ID3 算法将选择其中之一属于树,实际上,它将使用a[4]**作为根!记住,这是算法的启发式学习,而不是优化发现:
希望这个视觉图能更容易地理解我们的意思。这里的目的是不要深入决策树的机械和理论。在所有这些之后,你可能会问为什么我们还在谈论决策树。尽管它们可能存在任何问题,决策树仍然是许多算法的基础,特别是那些需要人类描述结果的算法。它们也是我们在前一章中使用过的 Viola & Jones(2001)实时面部检测算法的基础。作为一个更好的例子,微软 Xbox 360 的Kinect也使用了决策树。
我们将再次转向 Accord.NET 开源框架来阐述我们的概念。在我们的示例中,我们将处理以下决策树对象,因此最好提前讨论它们。
决策树
这是我们的主要类。
决策节点
这是我们的决策树中的一个节点。每个节点可能或可能没有与之关联的子节点。
决策变量
这个对象定义了树和节点可以处理的每个决策变量的性质。这些值可以是范围、连续或离散的。
决策分支节点集合
这个集合包含了一组决策节点,以及关于它们决策变量的额外信息,以便进行比较。
这里是一个用于确定财务风险的决策树的示例。通过简单地导航节点,决定走哪条路,直到得到最终答案,它非常容易跟随。在这种情况下,有人正在申请信贷,我们需要对其信用度做出决定。决策树是解决这个问题的绝佳方法:
在我们有了这个简单的视觉图之后,让我们来看看我们试图解决的问题。这是我们所有开发者(希望如此)时不时都会遇到的问题。
我是否应该接受这份工作?
你刚刚得到了一份新的工作,你需要决定是否接受这份工作。有一些事情对你来说很重要,所以我们将使用这些作为决策树的输入变量或特征。以下是你认为重要的因素:薪酬、福利、公司文化和当然,我能否在家工作?
我们将不会从磁盘存储加载数据,而是将创建一个内存数据库,并通过这种方式添加我们的特征。我们将创建DataTable并创建Columns作为特征,如下所示:
DataTable data = new DataTable("Should I Go To Work For Company
X");
data.Columns.Add("Scenario");
data.Columns.Add("Pay");
data.Columns.Add("Benefits");
data.Columns.Add("Culture");
data.Columns.Add("WorkFromHome");
data.Columns.Add("ShouldITakeJob");
在此之后,我们将加载几行数据,每行都有不同的特征集,以及我们的最后一列ShouldITakeJob,它可以是是或否,作为我们的最终决定:
data.Rows.Add("D1", "Good", "Good", "Mean", "Yes", "Yes");
data.Rows.Add("D2", "Good", "Good", "Mean", "No", "Yes");
data.Rows.Add("D3", "Average", "Good", "Good", "Yes", "Yes");
data.Rows.Add("D4", "Average", "Good", "Good", "No", "Yes");
data.Rows.Add("D5", "Bad", "Good", "Good", "Yes", "No");
data.Rows.Add("D6", "Bad", "Good", "Good", "No", "No");
data.Rows.Add("D7", "Good", "Average", "Mean", "Yes", "Yes");
data.Rows.Add("D8", "Good", "Average", "Mean", "No", "Yes");
data.Rows.Add("D9", "Average", "Average", "Good", "Yes", "No");
data.Rows.Add("D10", "Average", "Average", "Good", "No", "No");
data.Rows.Add("D11", "Bad", "Average", "Good", "Yes", "No");
data.Rows.Add("D12", "Bad", "Average", "Good", "No", "No");
data.Rows.Add("D13", "Good", "Bad", "Mean", "Yes", "Yes");
data.Rows.Add("D14", "Good", "Bad", "Mean", "No", "Yes");
data.Rows.Add("D15", "Average", "Bad", "Good", "Yes", "No");
data.Rows.Add("D16", "Average", "Bad", "Good", "No", "No");
data.Rows.Add("D17", "Bad", "Bad", "Good", "Yes", "No");
data.Rows.Add("D18", "Bad", "Bad", "Good", "No", "No");
data.Rows.Add("D19", "Good", "Good", "Good", "Yes", "Yes"); data.Rows.Add("D20", "Good", "Good", "Good", "No", "Yes");
一旦所有数据都创建并放入我们的表中,我们需要将我们之前的功能以计算机可以理解的形式表示出来。由于所有我们的特征都是类别,如果我们保持一致,那么我们如何表示它们并不重要。由于数字更容易处理,我们将通过称为编码的过程将我们的特征(类别)转换为代码簿。这个代码簿有效地将每个值转换为一个整数。请注意,我们将我们的data类别作为输入传递:
Codification codebook = new Codification(data);
接下来,我们需要为我们的决策树创建决策变量。树将试图帮助我们确定是否应该接受我们的新工作邀请。对于这个决定,将有几个输入类别,我们将它们指定在我们的决策变量数组中,以及两个可能的决策,是或否。
DecisionVariable数组将保存每个类别的名称以及该类别可能属性的总量。例如,Pay类别有三个可能的值,Good、Average或Poor。因此,我们指定类别名称和数字3。然后我们对所有其他类别重复此操作,除了最后一个,即我们的决策:
DecisionVariable[] attributes =
{
new DecisionVariable("Pay", 3),
new DecisionVariable("Benefits", 3),
new DecisionVariable("Culture", 3),
new DecisionVariable("WorkFromHome", 2)
};
int outputValues = 2; // 2 possible output values: yes or no
DecisionTree tree = new DecisionTree(attributes, outputValues);
现在我们已经创建了决策树,我们必须教会它我们要解决的问题。在这个阶段,它实际上什么也不知道。为了做到这一点,我们必须为树创建一个学习算法。在我们的例子中,那将是之前讨论过的 ID3 算法。由于这个样本只有分类值,ID3 算法是最简单的选择。请随意将其替换为 C4.5、C5.0 或您想尝试的任何其他算法:
ID3Learning id3 = new ID3Learning(tree);
Now, with our tree fully created and ready to go, we
are ready to classify new samples.
// Translate our training data into integer symbols using our codebook:
DataTable symbols = codebook.Apply(data);
int[][] inputs = symbols.ToArray<int>("Pay", "Benefits", "Culture",
"WorkFromHome");
int[] outputs = symbols.ToIntArray("ShouldITakeJob").GetColumn(0);
// Learn the training instances!
id3.Run(inputs, outputs);
一旦运行了学习算法,它就被训练好并准备好使用。我们只需向算法提供一个样本数据集,它就能给我们一个答案。在这种情况下,工资好,公司文化好,福利好,我可以在家工作。如果决策树训练得当,答案将是响亮的Yes:
int[] query = codebook.Translate("Good", "Good", "Good", "Yes");
int output = tree.Compute(query);
string answer = codebook.Translate("ShouldITakeJob", output);
// answer will be "Yes".
接下来,我们将关注使用numl开源机器学习包来向您展示另一个训练和使用决策树的示例。
numl
numl是一个非常著名的开源机器学习工具包。与大多数机器学习框架一样,它也使用Iris数据集的许多示例,包括我们将用于决策树的示例。
这里是numl输出的一个示例:
让我们看看那个示例背后的代码:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var description = Descriptor.Create<Iris>();
Console.WriteLine(description);
var generator = new DecisionTreeGenerator();
var data = Iris.Load();
var model = generator.Generate(description, data);
Console.WriteLine("Generated model:");
Console.WriteLine(model);
Console.ReadKey();
}
这绝对不是最复杂的函数,对吧?这就是在您的应用程序中使用numl的美丽之处;它极其容易使用和集成。
上述代码创建了一个描述符和DecisionTreeGenerator,加载了Iris数据集,然后生成了一个模型。这里只是加载的数据的一个样本:
public static Iris[] Load()
{
return new Iris[]
{
new Iris { SepalLength = 5.1m, SepalWidth = 3.5m, PetalLength =
1.4m, PetalWidth = 0.2m, Class = "Iris-setosa" },
new Iris { SepalLength = 4.9m, SepalWidth = 3m, PetalLength =
1.4m, PetalWidth = 0.2m, Class = "Iris-setosa" },
new Iris { SepalLength = 4.7m, SepalWidth = 3.2m, PetalLength =
1.3m, PetalWidth = 0.2m, Class = "Iris-setosa" },
new Iris { SepalLength = 4.6m, SepalWidth = 3.1m, PetalLength =
1.5m, PetalWidth = 0.2m, Class = "Iris-setosa" },
new Iris { SepalLength = 5m, SepalWidth = 3.6m, PetalLength =
1.4m, PetalWidth = 0.2m, Class = "Iris-setosa" },
new Iris { SepalLength = 5.4m, SepalWidth = 3.9m, PetalLength =
1.7m, PetalWidth = 0.4m, Class = "Iris-setosa" },
等等…
Accord.NET 决策树
Accord.NET 框架也有自己的决策树示例,我们应该指出这一点。它采用不同的、更图形化的方法来处理决策树,但选择权在您手中,您可以选择您喜欢和感觉最舒适的方法。
一旦数据加载完毕,您就可以创建决策树并为其学习做好准备。您将看到类似于这里的数据图,使用两个类别 X 和 Y:
下一个标签页将让您查看树节点、叶子和决策。在右侧还有一个树的从上到下的图形视图。最有用的信息位于左侧的树视图中,您可以在这里看到节点、它们的值和所做的决策:
最后,最后一个标签页将允许您执行模型测试:
学习代码
以下是一些学习代码:
// Specify the input variables
DecisionVariable[] variables =
{
new DecisionVariable("x", DecisionVariableKind.Continuous),
new DecisionVariable("y", DecisionVariableKind.Continuous),
};
// Create the C4.5 learning algorithm
var c45 = new C45Learning(variables);
// Learn the decision tree using C4.5
tree = c45.Learn(inputs, outputs);
// Show the learned tree in the view
decisionTreeView1.TreeSource = tree;
// Get the ranges for each variable (X and Y)
DoubleRange[] ranges = table.GetRange(0);
// Generate a Cartesian coordinate system
double[][] map = Matrix.Mesh(ranges[0],
200, ranges[1], 200);
// Classify each point in the Cartesian coordinate system
double[,] surface = map.ToMatrix().
InsertColumn(tree.Decide(map));
CreateScatterplot(zedGraphControl2, surface);
//Testing
// Creates a matrix from the entire source data table
double[][] table = (dgvLearningSource.DataSource as
DataTable).ToJagged(out columnNames);
// Get only the input vector values (first two columns)
double[][] inputs = table.GetColumns(0, 1);
// Get the expected output labels (last column)
int[] expected = table.GetColumn(2).ToInt32();
// Compute the actual tree outputs and turn
a Boolean into a 0 or 1
int[] actual = tree.Decide(inputs);
// Use confusion matrix to compute some statistics.
ConfusionMatrix confusionMatrix =
new ConfusionMatrix(actual, expected, 1, 0);
dgvPerformance.DataSource = new [] { confusionMatrix };
// Create performance scatter plot
CreateResultScatterplot(zedGraphControl1,
inputs, expected.ToDouble(), actual.ToDouble());
// As you can see above, the tree is making
the decision via the code line
int[] actual = tree.Decide(inputs);
这个值随后被输入到ConfusionMatrix中。对于那些不熟悉这个的人来说,让我简要解释一下。
混淆矩阵
混淆矩阵是一个用于描述分类模型性能的表格。它在一个已知真实值的测试数据集上运行。这就是我们得到如下内容的方式。
真阳性
这是一个我们预测会发生,而且事实确实发生了的情况。
真阴性
这是一个我们预测不会发生,而且事实确实发生了的情况。
假阳性
这是一个我们预测会发生的但事实并非如此的情况。你有时可能会看到这种情况被称为I 型错误。
假阴性
这是一个我们预测不会发生但事实确实发生了的情况。你有时可能会看到这种情况被称为II 型错误。
现在,说到这里,我们需要讨论另外两个重要的术语,精确度和召回率。
让我们这样描述它们。在过去的一周里,每天都下雨。那就是 7 天中的 7 天。很简单。一周后,你被问及上周下雨的频率是多少?
召回率
这是你在那一周内正确回忆起下雨的天数与总正确事件的数量的比率。如果你说下了 7 天雨,那就是 100%。如果你说下了 4 天雨,那么那就是 57%的召回率。在这种情况下,这意味着你的回忆并不那么精确,所以我们有精确度来识别。
精确度
这是你在那一周内正确回忆起将要下雨的次数与那一周总天数的比率。
对于我们来说,如果我们的机器学习算法在召回率方面做得好,这并不意味着它在精确度方面也做得好。这说得通吗?这让我们进入了其他事物,比如 F1 分数,我们将在另一天讨论。
错误类型可视化
这里有一些可能有助于理解的可视化:
真阳性与假阴性的识别:
在使用混淆矩阵计算统计数据后,会创建散点图,并识别所有内容:
摘要
在本章中,我们投入了大量时间来介绍决策树;它们是什么,我们如何使用它们,以及它们如何在我们应用中带来好处。在下一章中,我们将进入深度信念网络(DBNs)的世界,了解它们是什么,以及我们如何使用它们。我们甚至还会谈谈计算机做梦时的情况!
参考文献
-
Bishop, C. M., 2007. 模式识别与机器学习(信息科学和统计学). 第 1 版,2006 年校对第 2 次印刷版。s.l.: Springer.
-
Fayyad, U. M. & Irani, K. B., 1992.
deepblue.lib.umich.edu/bitstream/2027.42/46964/1/10994_2004_Article_422458.pdf. 机器学习, 1 月,8(1),第 87-102 页。 -
Quinlan, J. R., 1986.
www.dmi.unict.it/~apulvirenti/agd/Qui86.pdf. Machine Learning, 第 1 卷, 第 1 期, 第 81-106 页. -
Quinlan, J. R., 1993. C4.5: 机器学习程序 (Morgan Kaufmann 机器学习系列). 第 1 版. s.l.: Morgan Kaufmann.
-
Shotton, J. 等人, 2011.
research.microsoft.com/apps/pubs/default.aspx?id=145347. s.l., s.n. -
Viola, P. & Jones, M., 2001. 鲁棒实时目标检测. s.l., s.n.
-
Mitchell, T. M., 1997. 决策树学习. In:: 机器学习 (McGraw-Hill 计算机科学系列). s.l.: McGraw Hill.
-
Mitchell, T. M., 1997. 机器学习 (McGraw-Hill 计算机科学系列). 波士顿 (MA): WCB/McGraw-Hill.
-
Esmeir, S. & Markovitch, S., 2007.
jmlr.csail.mit.edu/papers/volume8/esmeir07a/esmeir07a.pdf. J. Mach. Learn. Res., 五月, 第 8 卷, 第 891-933 页. -
Hyafil, L. & Rivest, R. L., 1976. 构建最优二叉决策树是 NP 完全的. 信息处理快报, 第 5 卷, 第 1 期, 第 15-17 页.
第十章:深度信念 – 深度网络与梦境
我们都听说过深度学习,但有多少人知道深度信念网络是什么?让我们从这个章节开始,回答这个问题。深度信念网络是一种非常高级的机器学习方法,其含义正在迅速演变。作为一名机器学习开发者,了解这个概念很重要,这样当你遇到它或它遇到你时,你会熟悉它!
在机器学习中,深度信念网络在技术上是一种深度神经网络。我们应该指出,当提到深度学习或深度信念时,“深度”的含义是指网络由多个层(隐藏单元)组成。在深度信念网络中,这些连接在层内的每个神经元之间进行,但不在不同层之间。深度信念网络可以被训练以无监督地学习,以便以概率重建网络的输入。然后这些层作为“特征检测器”来识别或分类图像、字母等。你还可以观察深度信念网络做梦,这是一个非常有趣的话题。
在本章中,我们将涵盖:
-
受限玻尔兹曼机
-
使用 C#创建和训练深度信念网络
受限玻尔兹曼机
构建深度信念网络的一种流行方法是将其构建为一个由受限玻尔兹曼机(RBMs)组成的分层集合。这些 RBMs 作为自编码器运行,每个隐藏层都作为下一层的可见层。这种结构导致了一种快速、逐层和无监督的训练过程。深度信念网络在预训练阶段将包含 RBM 层,然后在微调阶段使用前馈网络。训练的第一步是从可见单元学习一层特征。下一步是将之前训练的特征的激活作为新的可见单元。然后我们重复这个过程,以便在第二个隐藏层中学习更多特征。然后这个过程会继续应用于所有隐藏层。
在这里,我们应该提供两条信息。
首先,我们应该解释一下什么是自编码器以及它做什么。自编码器是所谓表示学习的核心。它们编码输入,通常是显著特征的压缩向量,以及通过无监督学习重建的数据。
其次,我们应该注意,在深度信念网络中堆叠 RBMs 只是处理这个问题的一种方法。堆叠带有 dropout 的受限线性单元(ReLUs)并进行训练,然后配合反向传播,这再次成为了最先进的技术。我说再次是因为 30 年前,监督方法是唯一可行的方法。与其让算法查看所有数据并确定感兴趣的特征,有时我们作为人类实际上可以更好地找到我们想要的特征。
我认为深度信念网络最显著的两个特性如下:
-
存在一个高效、逐层的学习过程,用于学习自上而下的生成权重。它决定了某一层的变量如何依赖于其上层的变量。
-
学习完成后,可以通过从底层观察到的数据向量开始的单个自下而上的遍历,很容易地推断出每一层变量的值,并使用生成权重反向重建数据。
话虽如此,现在让我们也来谈谈 RBM 以及一般的霍尔兹曼机。
霍尔兹曼机是一种具有二元单元和单元之间无向边的循环神经网络。对于那些在图论课程中没注意听讲的同学,无向意味着边(或链接)是双向的,它们不指向任何特定方向。对于那些不熟悉图论的人来说,以下是一个具有无向边的无向图的示意图:
霍尔兹曼机是第一批能够学习内部表示的神经网络之一,并且如果给定足够的时间,它们可以解决难题。然而,它们在扩展方面并不擅长,这让我们转向下一个主题,即 RBMs。
RBM 被引入来处理霍尔兹曼机无法扩展的问题。它们有隐藏层,隐藏单元之间的连接受到限制,但不在这些单元之外,这有助于高效学习。更正式地说,我们必须稍微深入研究一下图论,才能正确解释这一点。
RBM 必须让它们的神经元形成所谓的二分图,这是一种更高级的图论形式;两组单元(可见层和隐藏层)中的每一对节点之间可能存在对称连接。任何一组内的节点之间不能有连接。二分图,有时称为双图,是一组图顶点分解为两个不相交的集合,使得同一集合内的两个顶点不相邻。
这里有一个很好的例子,可以帮助可视化这个主题。
注意,同一集合内(左侧的红色或右侧的黑色)没有连接,但两个集合之间存在连接:
更正式地说,RBM 被称为对称二分图。这是因为所有可见节点的输入都传递给所有隐藏节点。我们称之为对称,因为每个可见节点都与一个隐藏节点相关联;二分是因为有两个层次;而图是因为,嗯,它是一个图,或者如果你更喜欢,它是一组节点!
想象一下,我们的 RBM 被呈现了猫和狗的图像,并且我们有两个输出节点,一个用于每种动物。在我们的正向学习过程中,我们的 RBM 会问自己“看到这些像素,我应该为猫还是狗发送更强的权重信号?”在反向过程中,它会思考“作为一个狗,我应该看到什么样的像素分布?”朋友们,这就是今天关于联合概率的教训:给定A的X和给定X的A的同时概率。在我们的案例中,这个联合概率以两层之间的权重表示,并且是 RBMs 的一个重要方面。
在掌握了今天的联合概率和图论的小课程之后,我们现在将讨论重建,这是 RBMs(限制玻尔兹曼机)所做的重要部分。在我们一直在讨论的例子中,我们正在学习哪些像素组在一系列图像中发生(意味着处于开启状态)。当一个隐藏层节点被一个显著权重激活(无论这个权重是如何确定的以将其开启),它代表了某些事件同时发生的共现,在我们的案例中,是狗或猫。如果图像是一只猫,尖耳朵+圆脸+小眼睛可能就是我们要找的特征。大耳朵+长尾巴+大鼻子可能使图像成为一只狗。这些激活代表了我们的 RBM“认为”原始数据看起来像什么。从所有目的和用途来看,我们实际上正在重建原始数据。
我们还应该迅速指出,RBM 有两个偏差而不是一个。这一点非常重要,因为它将 RBM 与其他自动编码算法区分开来。隐藏偏差帮助我们的 RBM 在正向过程中产生所需的激活,而可见层偏差帮助在反向过程中学习正确的重建。隐藏偏差很重要,因为它的主要任务是确保在数据可能非常稀疏的情况下,某些节点仍然会激活。你将在稍后看到这如何影响深度信念网络做梦的方式。
层次化
一旦我们的 RBM 学会了输入数据的结构,这与我们在第一隐藏层中做出的激活有关,数据就会传递到下一个隐藏层。第一隐藏层随后成为新的可见层。我们在隐藏层中创建的激活现在成为我们的输入。它们将被新的隐藏层中的权重相乘,产生另一组激活。这个过程会继续通过我们网络中的所有隐藏层。隐藏层变成可见层,我们有了另一个我们将使用的权重的隐藏层,我们重复这个过程。每个新的隐藏层都会导致权重的调整,直到我们达到可以识别来自前一层的输入的点。
为了更详细地说明(帮助你保持术语的合规性),这从技术上讲被称为无监督、贪婪、分层训练。不需要输入来改进每一层的权重,这意味着没有任何类型的外部影响。这进一步意味着我们应该能够使用我们的算法在之前未见过的不监督数据上进行训练。正如我们一直强调的那样,数据越多,我们的结果越好!随着每一层变得更好,希望也更准确,我们就有更好的位置通过每一隐藏层增加我们的学习,权重在这个过程中负责引导我们到达正确的图像分类。
但当我们讨论重建时,我们应该指出,在我们重建努力中,每当一个数字(权重)不为零,这表明我们的 RBM 已经从数据中学习到了一些东西。从某种意义上说,你可以将返回的数字当作你对待百分比指示器一样来处理。数字越高,算法对其所看到的东西就越有信心。记住,我们有一个主数据集,我们试图恢复到这个数据集,并且我们有一个参考数据集用于我们的重建工作。随着我们的 RBM 遍历每一张图像,它还不知道它正在处理什么图像;这正是它试图确定的事情。
让我们简要澄清一下。当我们说我们使用贪婪算法时,我们真正意思是我们的 RBM 将采取最短路径以实现最佳结果。我们将从我们看到的图像中采样随机像素,并测试哪些像素能引导我们到达正确答案。RBM 将测试每个假设与主数据集(测试集)的对比,这是我们正确的最终目标。记住,每张图像只是我们试图分类的一组像素。这些像素包含了数据的特征和特性。例如,一个像素可以有不同亮度的阴影,其中深色像素可能表示边界,浅色像素可能表示数字,等等。
但当事情不按我们的意愿发展时会发生什么?如果我们在任何给定步骤中学到的任何东西都不正确,会发生什么?如果发生这种情况,这意味着我们的算法猜错了。我们的行动方案是回过头去再试一次。这并不像看起来那么糟糕,也不那么耗时。当然,一个错误假设有一个时间成本,但最终目标是我们必须提高我们的学习效率并减少每个阶段的错误。每个错误的加权连接将像我们在强化学习中做的那样受到惩罚。这些连接的权重将减少,不再那么强大。希望下一次遍历将提高我们的准确性,同时减少错误,权重越强,它的影响就越大。
因此,让我们假设一个场景,并且稍微思考一下。假设我们正在对数字图像进行分类,即数字。一些图像会有曲线,例如 2、3、6、8、9 等等。其他数字,如 1、4 和 7,则不会有。这样的知识非常重要,因为我们的 RBM 将利用它来继续改进其学习并减少错误。如果我们认为我们正在处理数字 2,那么指向这个方向的权重将比其他权重更重。这是一个极端的简化,但希望这足以帮助你理解我们即将开始的事情。
当我们将所有这些放在一起时,我们现在有了深度信念网络的理论框架。尽管我们比其他章节更深入地探讨了理论,但当你看到我们的示例程序运行时,一切都将开始变得有意义。而且你将更好地准备好在你的应用中使用它,了解幕后发生的事情。记住,黑洞与黑箱!
为了展示深度信念网络和 RBMs,我们将使用由 Mattia Fagerlund 编写的出色开源软件 SharpRBM。这款软件是对开源社区的巨大贡献,我毫不怀疑你将花费数小时,甚至数天的时间与之共事。这款软件附带了一些非常令人惊叹的演示。对于本章,我们将使用字母分类演示。
以下截图是我们深度信念测试应用。你是否好奇当计算机在睡眠时它在想什么?好吧,我的朋友,你即将找到答案!
和往常一样,我们也会使用 ReflectInsight 来提供幕后发生的事情的视角:
你首先会注意到我们的演示应用中有很多事情在进行。让我们花点时间将其分解成更小的部分。
在程序屏幕的左上角是我们要指定的层,我们有三层隐藏层,所有这些层在测试之前都需要适当的训练。我们可以一次训练一层,从第一层开始。你可以根据自己的喜好训练多长时间,但训练越多,你的系统会越好:
在我们的训练选项之后是下一节,我们的进度。在我们训练的过程中,所有相关信息,如生成、重建错误、检测器错误和学习率,都显示在这里:
下一节是关于我们特征检测器的绘图,如果勾选了“绘制”复选框,它将在整个训练过程中更新自己:
当你开始训练一层时,你会注意到重建和特征检测器基本上是空的。随着你的训练进展,它们会自我完善。记住,我们正在重建我们已经知道是真实的内容!随着训练的继续,重建的数字变得越来越清晰,我们的特征检测器也随之变得更加明显:
这是应用程序在训练过程中的一个快照。正如你所见,它处于第 31 代,重建的数字非常清晰。它们仍然不完整或不正确,但你可以看到我们正在取得多大的进步:
计算机做梦是什么?
“当计算机做梦时,它在想什么?” 这是一句著名的话。对我们来说,这将是一个特性,它允许我们在计算机重建阶段看到它在想什么。当程序试图重建我们的数字时,特征检测器本身在整个过程中会呈现各种形式。我们就是在梦境窗口(用红色圆圈表示)中显示这些形式:
好吧,我们已经花了很多时间查看我们应用程序的截图。我认为现在是时候看看代码了。让我们先看看我们是如何创建 DeepBeliefNetwork 对象本身的:
DeepBeliefNetwork = new DeepBeliefNetwork(28 * 29, 500, 500, 1000);
创建完成后,我们需要创建我们的网络训练器,我们根据正在训练的层的权重来做这件事:
DeepBeliefNetworkTrainer trainer = new
DeepBeliefNetworkTrainer(DeepBeliefNetwork,
DeepBeliefNetwork?.LayerWeights?[layerId], inputs);
这两个对象都在我们的主 TrainNetwork 循环中使用,这是我们应用程序中活动发生的主要部分。这个循环将持续进行,直到被通知停止。
private void TrainNetwork(DeepBeliefNetworkTrainer trainer)
{
try
{
Stopping = false;
ClearBoxes();
_unsavedChanges = true;
int generation = 0;
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS
| EXECUTION_STATE.ES_SYSTEM_REQUIRED);
while (Stopping == false)
{
Stopwatch stopwatch = Stopwatch.StartNew();
TrainingError error = trainer?.Train();
label1.Text = string.Format(
"Gen {0} ({4:0.00} s): ReconstructionError=
{1:0.00}, DetectorError={2:0.00},
LearningRate={3:0.0000}",
generation, error.ReconstructionError,
error.FeatureDetectorError,
trainer.TrainingWeights.AdjustedLearningRate,
stopwatch.ElapsedMilliseconds / 1000.0);
Application.DoEvents();
ShowReconstructed(trainer);
ShowFeatureDetectors(trainer);
Application.DoEvents();
if (Stopping)
{
break;
}
generation++;
}
DocumentDeepBeliefNetwork();
}
finally
{
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
}
}
在前面的代码中,我们突出了 trainer.Train() 函数,这是一个基于数组的机器学习算法,其形式如下:
public TrainingError Train()
{
TrainingError trainingError = null;
if (_weights != null)
{
ClearDetectorErrors(_weights.LowerLayerSize,
_weights.UpperLayerSize);
float reconstructionError = 0;
ParallelFor(MultiThreaded, 0, _testCount,
testCase =>
{
float errorPart =
TrainOnSingleCase(_rawTestCases,
_weights?.Weights, _detectorError,
testCase, _weights.LowerLayerSize,
_weights.UpperLayerSize, _testCount);
lock (_locks?[testCase %
_weights.LowerLayerSize])
{
reconstructionError += errorPart;
}
});
float epsilon =
_weights.GetAdjustedAndScaledTrainingRate(_testCount);
UpdateWeights(_weights.Weights,
_weights.LowerLayerSize, _weights.UpperLayerSize,
_detectorError, epsilon);
trainingError = new
TrainingError(_detectorError.Sum(val =>
Math.Abs(val)), reconstructionError);
_weights?.RegisterLastTrainingError(trainingError);
return trainingError;
}
return trainingError;
}
这段代码使用并行处理(突出部分)来并行训练单个案例。这个函数负责处理输入和隐藏层的转换,正如我们在本章开头所讨论的。它使用 TrainOnSingleCase 函数,其形式如下:
private float TrainOnSingleCase(float[] rawTestCases, float[] weights, float[] detectorErrors, int testCase,
int lowerCount, int upperCount, int testCaseCount)
{
float[] model = new float[upperCount];
float[] reconstructed = new float[lowerCount];
float[] reconstructedModel = new float[upperCount];
int rawTestCaseOffset = testCase * lowerCount;
ActivateLowerToUpperBinary(rawTestCases, lowerCount,
rawTestCaseOffset, model, upperCount, weights); // Model
ActivateUpperToLower(reconstructed, lowerCount, model,
upperCount, weights); // Reconstruction
ActivateLowerToUpper(reconstructed, lowerCount, 0,
reconstructedModel, upperCount, weights); //
Reconstruction model
return AccumulateErrors(rawTestCases, lowerCount,
rawTestCaseOffset, model, upperCount, reconstructed,
reconstructedModel, detectorErrors); // Accumulate
detector errors
}
最后,我们在处理过程中累积错误,这是我们的模型应该相信的内容与它实际执行的内容之间的差异。显然,错误率越低越好,这样我们才能更准确地重建我们的图像。AccumulateErrors 函数如下所示:
private float AccumulateErrors(float[] rawTestCases, int lowerCount, int rawTestCaseOffset, float[] model,
int upperCount, float[] reconstructed, float[] reconstructedModel, float[] detectorErrors)
{
float reconstructedError = 0;
float[] errorRow = new float[upperCount];
for (int lower = 0; lower < lowerCount; lower++)
{
int errorOffset = upperCount * lower;
for (int upper = 0; upper < upperCount; upper++)
{
errorRow[upper] = rawTestCases[rawTestCaseOffset +
lower] * model[upper] +
// What the model should believe in
-reconstructed[lower] *
reconstructedModel[upper];
// What the model actually believes in
}
lock (_locks[lower])
{
for (int upper = 0; upper < upperCount; upper++)
{
detectorErrors[errorOffset + upper] -=
errorRow[upper];
}
}
reconstructedError +=
Math.Abs(rawTestCases[rawTestCaseOffset + lower] -
reconstructed[lower]);
}
return reconstructedError;
}
摘要
好吧,朋友们,这就是全部内容!在本章中,你学习了 RBMs、一点图论,以及如何在 C# 中创建和训练深度信念网络。你的 buzzword-compliant 检查清单几乎已经完成!我建议你尝试代码,将网络层训练到不同的阈值,并观察你的计算机在重建过程中是如何“做梦”的。记住,训练越多越好,所以花时间在每个层上,确保它有足够的数据来完成准确的重建工作。
一个简短的警告:如果你启用了绘制你的特征检测器和重建输入的功能,你会注意到性能会有大幅下降。如果你正在尝试训练你的层,你可能希望在首先不进行可视化的情况下训练它们,以减少所需的时间。相信我,如果你将每个级别训练到高迭代次数,那么使用可视化将感觉像永恒!在你进步的过程中,随时保存你的网络。祝你好运,祝你梦想成真!
在下一章中,我们将学习微基准测试,并有机会使用有史以来最强大的开源微基准测试工具包之一!
参考文献
-
Mattias Fagerlund:
lotsacode.wordpress.com/2010/09/14/sharprbm-restricted-boltzmann-machines-in-c-net/#comments -
Nykamp DQ, 无向图定义, 来自 Math Insight:
mathinsight.org/definition/undirected_graph
第十一章:微基准测试和激活函数
在本章中,我们将学习以下内容:
-
什么是微基准测试
-
如何将其应用到您的代码中
-
激活函数是什么
-
如何绘制和基准测试激活函数
每个开发者都需要一个良好的基准测试工具在手。定性基准测试无处不在;你每天都会听到,“我们减少了 10%,增加了 25%”。记住那句古老的谚语,“当你听到一个数字被抛出来时,98.4%的情况下那个数字是假的”?顺便说一句,那个数字也是我随便编的。当你听到这样的引用时,要求那个人证明它,你得到的是什么?任务管理器吗?作为数据科学家,我们不需要定性结果;我们需要可以证明并一致复制的定量结果。可复现的结果非常重要,不仅因为一致性,还因为可信度和准确性。这正是微基准测试发挥作用的地方。
我们将使用无可替代的BenchmarkDotNet库,您可以在以下链接找到它:github.com/dotnet/BenchmarkDotNet.
如果您还没有使用这个库,您需要立即放下手头的工作并安装它。我认为这是您可以使用的最无可替代的框架之一,并且我认为它在重要性上与单元测试和集成测试并列。
为了展示这个工具的价值,我们将绘制几个激活函数并比较它们的运行时间。作为其中的一部分,我们将考虑预热、遗留和RyuJIT、冷启动以及程序执行的更多方面。最后,我们将得到一组定量结果,证明我们函数的确切测量值。如果,比如说在 2.0 版本中,我们发现某些东西运行得更慢,我们可以重新运行基准测试并比较。
我强烈建议将此集成到您的持续集成/持续构建过程中,以便在每次发布时,您都有基准数字进行比较。这不仅仅是我们自己的代码。我创建了一个庞大的 CI/CD 系统,涵盖了大量的程序、微服务、环境和构建及部署步骤。我们还会定期基准测试我们经常使用的某些.NET 库函数,以验证;在.NET 框架版本之间,没有任何变化。
在本章中,我们将展示两个示例。第一个是一个激活函数查看器;它将绘制每个激活函数,以便您可以查看其外观。您可以在我认为最有价值的开源程序之一,由科林·格林先生开发的SharpNEAT中找到它。这个包绝对令人难以置信,我几乎每天都在使用它。我还在此基础上创建了新的用户界面以及满足我需求的先进版本,这是一个非常灵活的工具。我每天都在研究将镜像神经元和规范神经元集成到可扩展基板中的工作,像 SharpNEAT 这样的工具是不可思议的。未来的高级书籍将更多地突出 SharpNEAT,所以现在就熟悉它吧!这个第一个示例应用程序包含在最新的 SharpNEAT 包中,您可以在github.com/colgreen/sharpneat找到它。
可视化激活函数绘制
这是 SharpNEAT 自定义版本绘制的局部和全局最小值的图。在这个领域,您可以用这个产品做很多事情,真是太令人惊叹了!
正如我提到的,我们将绘制并基准测试几个激活函数。我们到处都在听到这个术语“激活函数”,但我们真的知道它是什么意思吗?让我们先快速解释一下,以防您不熟悉。
激活函数用于决定神经元是否被激活。有些人喜欢用“激活”这个词来替换“触发”。无论哪种方式,它最终决定了某物是开启还是关闭,是否触发,是否激活。
让我们从向您展示单个激活函数的图开始:
当单独绘制时,这是Logistic Steep近似和Swish 激活函数的外观,因为存在许多类型的激活函数,所以当它们一起绘制时,这就是我们所有的激活函数将看起来像什么:
在这一点上,您可能想知道,“我们为什么甚至关心这些图看起来像什么?”这是一个很好的问题。我们关心,因为一旦您进入神经网络等领域,您将大量使用这些函数。知道您的激活函数是否会将神经元的值置于开启或关闭状态,以及它将保持或需要的值范围是非常有用的。毫无疑问,您作为机器学习开发者将在职业生涯中遇到并/或使用激活函数,了解TanH和LeakyReLU激活函数之间的区别非常重要。
绘制所有函数
所有激活函数的绘制都是在单个函数中完成的,这个函数令人惊讶地被命名为PlotAllFunctions:
private void PlotAllFunctions()
{
// First, clear out any old GraphPane's from the MasterPane
collection MasterPane master = zed.MasterPane;
master.PaneList.Clear();
// Display the MasterPane Title, and set the
outer margin to 10 points
master.Title.IsVisible = true;
master.Margin.All = 10;
// Plot multiple functions arranged on a master pane.
PlotOnMasterPane(Functions.LogisticApproximantSteep,
"Logistic Steep (Approximant)");
PlotOnMasterPane(Functions.LogisticFunctionSteep,
"Logistic Steep (Function)");
PlotOnMasterPane(Functions.SoftSign, "Soft Sign");
PlotOnMasterPane(Functions.PolynomialApproximant,
"Polynomial Approximant");
PlotOnMasterPane(Functions.QuadraticSigmoid,
"Quadratic Sigmoid");
PlotOnMasterPane(Functions.ReLU, "ReLU");
PlotOnMasterPane(Functions.LeakyReLU, "Leaky ReLU");
PlotOnMasterPane(Functions.LeakyReLUShifted,
"Leaky ReLU (Shifted)");
PlotOnMasterPane(Functions.SReLU, "S-Shaped ReLU");
PlotOnMasterPane(Functions.SReLUShifted,
"S-Shaped ReLU (Shifted)");
PlotOnMasterPane(Functions.ArcTan, "ArcTan");
PlotOnMasterPane(Functions.TanH, "TanH");
PlotOnMasterPane(Functions.ArcSinH, "ArcSinH");
PlotOnMasterPane(Functions.ScaledELU,
"Scaled Exponential Linear Unit");
// Refigure the axis ranges for the GraphPanes.
zed.AxisChange();
// Layout the GraphPanes using a default Pane Layout.
using (Graphics g = this.CreateGraphics()) {
master.SetLayout(g, PaneLayout.SquareColPreferred);
}
主要的绘图函数
在幕后,Plot函数负责执行和绘制每个函数:
private void Plot(Func<double, double> fn, string fnName,
Color graphColor, GraphPane gpane = null)
{
const double xmin = -2.0;
const double xmax = 2.0;
const int resolution = 2000;
zed.IsShowPointValues = true;
zed.PointValueFormat = "e";
var pane = gpane ?? zed.GraphPane;
pane.XAxis.MajorGrid.IsVisible = true;
pane.YAxis.MajorGrid.IsVisible = true;
pane.Title.Text = fnName;
pane.YAxis.Title.Text = string.Empty;
pane.XAxis.Title.Text = string.Empty;
double[] xarr = new double[resolution];
double[] yarr = new double[resolution];
double incr = (xmax - xmin) / resolution;
double x = xmin;
for(int i=0; i < resolution; i++, x+=incr)
{
xarr[i] = x;
yarr[i] = fn(x);
}
PointPairList list1 = new PointPairList(xarr, yarr);
LineItem li = pane.AddCurve(string.Empty, list1, graphColor,
SymbolType.None);
li.Symbol.Fill = new Fill(Color.White);
pane.Chart.Fill = new Fill(Color.White,
Color.LightGoldenrodYellow, 45.0F);
}
在此代码中,值得关注的主要部分用黄色突出显示。这是执行我们传递的激活函数并使用其值作为Y轴绘图值的地方。著名的ZedGraph开源绘图包用于所有图形绘制。每个函数执行后,相应的绘图将被制作。
基准测试
BenchmarkDotNet生成多个报告,其中之一是类似于您在这里看到的 HTML 报告:
Excel 报告提供了运行程序所使用的每个参数的详细信息,这是您最全面的信息来源。在许多情况下,这些参数的大部分将使用默认值,并且可能比您需要的更多,但至少您将有机会移除不需要的部分:
在下一节中,当我们回顾创建您所看到内容的源代码时,我们将描述一些这些参数:
static void Main(string[] args)
{
var config = ManualConfig.Create(DefaultConfig.Instance);
// Set up an results exporter.
// Note. By default results files will be located in
.BenchmarkDotNet.Artifactsresults directory.
config.Add(new CsvExporter(CsvSeparator.CurrentCulture,
new BenchmarkDotNet.Reports.SummaryStyle
{
PrintUnitsInHeader = true,
PrintUnitsInContent = false,
TimeUnit = TimeUnit.Microsecond,
SizeUnit = BenchmarkDotNet.Columns.SizeUnit.KB
}));
// Legacy JITter tests.
config.Add(new Job(EnvMode.LegacyJitX64,
EnvMode.Clr, RunMode.Short)
{
Env = { Runtime = Runtime.Clr, Platform = Platform.X64 },
Run = { LaunchCount = 1, WarmupCount = 1,
TargetCount = 1, RunStrategy =
BenchmarkDotNet.Engines.RunStrategy.Throughput },
Accuracy = { RemoveOutliers = true }
}.WithGcAllowVeryLargeObjects(true));
// RyuJIT tests.
config.Add(new Job(EnvMode.RyuJitX64, EnvMode.Clr,
RunMode.Short)
{
Env = { Runtime = Runtime.Clr, Platform = Platform.X64 },
Run = { LaunchCount = 1, WarmupCount = 1,
TargetCount = 1, RunStrategy =
BenchmarkDotNet.Engines.RunStrategy.Throughput },
Accuracy = { RemoveOutliers = true }
}.WithGcAllowVeryLargeObjects(true));
// Uncomment to allow benchmarking of non-optimized assemblies.
//config.Add(JitOptimizationsValidator.DontFailOnError);
// Run benchmarks.
var summary = BenchmarkRunner.Run<FunctionBenchmarks>(config);
}
让我们更深入地分析这段代码。
首先,我们将创建一个手动配置对象,用于保存我们用于基准测试的配置参数:
var config = ManualConfig.Create(DefaultConfig.Instance);
接下来,我们将设置一个导出器来保存我们将用于导出结果的参数。我们将使用微秒作为时间单位和千字节作为大小来将结果导出到.csv文件:
config.Add(new CsvExporter(CsvSeparator.CurrentCulture,
new BenchmarkDotNet.Reports.SummaryStyle
{
PrintUnitsInHeader = true,
PrintUnitsInContent = false,
TimeUnit = TimeUnit.Microsecond,
SizeUnit = BenchmarkDotNet.Columns.SizeUnit.KB
}));
接下来,我们将创建一个基准作业,用于处理LegacyJitX64在 x64 架构上的测量。您可以随意更改此参数或其他任何参数以进行实验,或包括您测试场景中需要或想要的任何结果。在我们的案例中,我们将使用 x64 平台;LaunchCount、WarmupCount和TargetCount均为1;以及RunStrategy为Throughput。我们也将对 RyuJIT 做同样的处理,但在此不展示代码:
config.Add(new Job(EnvMode.LegacyJitX64, EnvMode.Clr,
RunMode.Short)
{
Env = { Runtime = Runtime.Clr, Platform = Platform.X64 },
Run = { LaunchCount = 1, WarmupCount = 1, TargetCount = 1,
RunStrategy = Throughput },
Accuracy = { RemoveOutliers = true }
}.WithGcAllowVeryLargeObjects(true));
最后,我们将运行BenchmarkRunner以执行我们的测试:
// Run benchmarks.
var summary = BenchmarkRunner.Run<FunctionBenchmarks>(config);
BenchmarkDotNet将以 DOS 命令行应用程序的形式运行,以下是一个执行先前代码的示例:
让我们来看一个激活函数绘制的例子:
[Benchmark]
public double LogisticFunctionSteepDouble()
{
double a = 0.0;
for(int i=0; i<__loops; i++)
{
a = Functions.LogisticFunctionSteep(_x[i % _x.Length]);
}
return a;
}
您会注意到使用了[Benchmark]属性。这表示对于BenchmarkDotNet来说,这将是一个需要基准测试的测试。内部,它调用以下函数:
对于LogisticFunctionSteep函数,其实现方式,就像大多数激活函数一样,很简单(假设你知道公式)。在这种情况下,我们不是在绘制激活函数,而是在对其进行基准测试。你会注意到该函数接收并返回double类型。我们还通过使用和返回float变量对相同的函数进行了基准测试,因此我们正在基准测试使用double和float之间的差异。因此,人们可以看到,有时性能影响可能比他们想象的要大:
摘要
在本章中,我们学习了如何将微基准测试应用于你的代码。我们还看到了如何绘制和基准测试激活函数,以及如何使用微基准测试进行这些操作。你现在拥有了一个非常强大的基准测试库,你可以将其添加到所有代码中。在下一章中,我们将深入探讨直观深度学习,并展示一个针对 C#开发者可用的最强大的机器学习测试框架之一。
第十二章:C# .NET 中的直观深度学习
本章的目标是向您展示 Kelp.Net 提供的强大功能。
在本章中,你将学习:
-
如何使用 Kelp.Net 进行自己的测试
-
如何编写测试
-
如何对函数进行基准测试
-
如何扩展 Kelp.Net
Kelp.Net[4]是一个用 C#和.NET 编写的深度学习库。它能够将函数链入函数堆栈,提供了一个非常灵活和直观的平台,具有极大的功能。它还充分利用了 OpenCL 语言平台,以实现 CPU 和 GPU 设备上的无缝操作。深度学习是一个功能强大的工具,对 Caffe 和 Chainer 模型加载的原生支持使这个平台更加强大。正如你将看到的,你只需几行代码就可以创建一个拥有 100 万个隐藏层的深度学习网络。
Kelp.Net 还使得将模型保存到磁盘存储和从磁盘加载变得非常容易。这是一个非常强大的功能,允许你进行训练、保存模型,然后根据需要加载和测试。它还使得将代码投入生产并真正分离训练和测试阶段变得更加容易。
在其他方面,Kelp.Net 是一个功能强大的工具,可以帮助你更好地学习和理解各种类型的函数、它们的交互和性能。例如,你可以对同一网络使用不同的优化器进行测试,通过更改一行代码来查看结果。你也可以轻松设计测试,以查看使用不同批量大小、隐藏层数量、epoch 等时的差异。在.NET 中,几乎没有提供 Kelp.Net 所具有的强大功能和灵活性的深度学习工作台。
让我们先简单谈谈深度学习。
深度学习是什么?
要讨论深度学习,我们需要回顾一下,不久前,大数据出现在我们面前。这个术语当时,现在仍然无处不在。这是一个每个人都必须拥有的技能,一个符合流行术语的清单项。但这个术语究竟意味着什么呢?嗯,它只是意味着,我们不再使用孤立的 SQL 数据库和文件通过 FTP 传输来使用,而是从社交媒体、互联网搜索引擎、电子商务网站等地方爆发了大量的数字数据。当然,这些数据以各种形式和格式出现。更正式地说,我们突然开始处理非结构化数据。不仅由于 Facebook、Twitter、Google 等应用程序的数据爆炸,而且爆炸还在继续。越来越多的人相互连接并保持联系,分享大量关于自己的信息,这些信息他们如果通过电话询问,是绝对不敢提供的,对吧?我们对这些数据的格式和质量几乎没有控制权。随着我们继续前进,这将成为一个重要的观点。
现在,这庞大的数据量是很好的,但人类几乎无法吸收他们每天所接触到的,更不用说数据爆炸了。因此,在这个过程中,人们意识到机器学习和人工智能可以适应这样的任务。从简单的机器学习算法到多层网络,人工智能和深度学习诞生了(至少企业界喜欢相信是这样发生的!)。
深度学习,作为机器学习和人工智能的一个分支,使用许多层级的神经网络层(如果你喜欢,可以称之为分层)来完成其任务。在许多情况下,这些网络被构建来反映我们对我们所了解的人脑的认识,神经元像复杂层叠的网一样连接各个层。这使得数据处理可以以非线性方式发生。每一层处理来自前一层的(当然,第一层除外)数据,并将信息传递给下一层。如果有任何运气,每一层都会改进模型,最终,我们达到目标并解决问题。
OpenCL
Kelp.Net 大量使用开放计算语言,即 OpenCL。根据维基百科:
“OpenCL 将计算系统视为由多个计算设备组成,这些设备可能是中央处理器(CPU),或者是连接到主机处理器(CPU)的加速器,如图形处理单元(GPU)。在 OpenCL 设备上执行的功能称为内核。单个计算设备通常由多个计算单元组成,这些计算单元又由多个处理元素(PE)组成。单个内核执行可以在所有或许多 PE 上并行运行。”
在 OpenCL 中,任务是在命令队列上安排的。每个设备至少有一个命令队列。OpenCL 运行时将安排的数据并行任务分解成片段,并将任务发送到设备处理元素。
OpenCL 定义了一个内存层次结构:
-
Global: 由所有处理元素共享,具有高延迟
-
Read-only: 较小,延迟较低,可由主机 CPU 写入但不能由计算设备写入
-
Local: 由进程元素组共享
-
Per-element: 私有内存
OpenCL 还提供了一个更偏向数学的 API。这可以从固定长度向量类型(如 float4,单精度浮点数的四个向量)的暴露中看出,长度为 2、3、4、8 和 16。随着你对 Kelp.Net 的更多了解以及开始创建自己的函数,你将遇到 OpenCL 编程。现在,只需知道它存在并且被广泛使用就足够了。
OpenCL 层次结构
在 Kelp.Net 中,各种 OpenCL 资源的层次结构如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/hsn-ml-csp/img/637115a5-7fd0-4e75-9e81-73caec8cee8c.pnghttps://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/hsn-ml-csp/img/c519fb66-bcbc-4634-b380-1b6c1fdf9bec.png
让我们更详细地描述这些。
计算内核
内核对象封装了程序中声明的特定内核函数以及执行此内核函数时要使用的参数值。
计算程序
由一组内核组成的 OpenCL 程序。程序还可以包含由内核函数调用的辅助函数和常量数据。
计算采样器
一个描述如何在内核中读取图像时进行采样的对象。图像读取函数接受一个采样器作为参数。采样器指定图像寻址模式(意味着如何处理超出范围的坐标)、过滤模式以及输入图像坐标是归一化还是未归一化的值。
计算设备
计算设备是一组计算单元。命令队列用于向设备排队命令。命令的例子包括执行内核或读取/写入内存对象。OpenCL 设备通常对应于 GPU、多核 CPU 以及其他处理器,如数字信号处理器(DSP)和 cell/B.E. 处理器。
计算资源
一个应用程序可以创建和删除的 OpenCL 资源。
计算对象
在 OpenCL 环境中通过其句柄识别的对象。
计算上下文
计算上下文是内核实际执行的环境以及定义同步和内存管理的域。
计算命令队列
命令队列是一个对象,它包含将在特定设备上执行的操作。命令队列在上下文中的特定设备上创建。对队列的命令按顺序排队,但可以按顺序或非顺序执行。
计算缓冲区
一个存储字节线性集合的内存对象。缓冲区对象可以通过在设备上执行的内核中的指针访问。
计算事件
事件封装了操作的状态,如命令。它可以用于在上下文中同步操作。
计算图像
一个存储 2D 或 3D 结构化数组的内存对象。图像数据只能通过读取和写入函数访问。读取函数使用采样器。
计算平台
主机加上由 OpenCL 框架管理的设备集合,允许应用程序共享资源并在平台上的设备上执行内核。
计算用户事件
这代表一个用户创建的事件。
Kelp.Net 框架
函数
函数是 Kelp.Net 神经网络的基本构建块。单个函数在函数栈中链在一起,以创建强大且可能复杂的网络链。你需要了解四种主要类型的函数,以及它们的目的,应该是显而易见的:
-
单输入函数
-
双输入函数
-
多输入函数
-
多输出函数
当从磁盘加载网络时,函数也会被链在一起。
每个函数都有一个正向和反向方法,你将在创建自己的函数时实现:
public abstract NdArray[] Forward(params NdArray[] xs);
public virtual void Backward([CanBeNull] params NdArray[] ys){}
函数栈
函数栈是在一个正向、反向或更新过程中同时执行的一组函数层。当你创建测试或从磁盘加载模型时,会创建函数栈。以下是一些函数栈的示例。
它们也可以很小且简单:
FunctionStack nn = new FunctionStack(
new Linear(2, 2, name: "l1 Linear"),
new Sigmoid(name: "l1 Sigmoid"),
new Linear(2, 2, name: "l2 Linear"));
它们可以稍微大一点:
FunctionStack nn = new FunctionStack(
new Convolution2D(1, 2, 3, name: "conv1", gpuEnable: true),// Do not forget the GPU flag if necessary
new ReLU(),
new MaxPooling(2, 2),
new Convolution2D(2, 2, 2, name: "conv2", gpuEnable: true),
new ReLU(),
new MaxPooling(2, 2),
new Linear(8, 2, name: "fl3"),
new ReLU(),
new Linear(2, 2, name: "fl4")
);
或者,它们也可以非常大:
FunctionStack nn = new FunctionStack(
new Linear(neuronCount * neuronCount, N, name: "l1 Linear"), // L1
new BatchNormalization(N, name: "l1 BatchNorm"),
new LeakyReLU(slope: 0.000001, name: "l1 LeakyReLU"),
new Linear(N, N, name: "l2 Linear"), // L2
new BatchNormalization(N, name: "l2 BatchNorm"),
new LeakyReLU(slope: 0.000001, name: "l2 LeakyReLU"),
new Linear(N, N, name: "l3 Linear"), // L3
new BatchNormalization(N, name: "l3 BatchNorm"),
new LeakyReLU(slope: 0.000001, name: "l3 LeakyReLU"),
new Linear(N, N, name: "l4 Linear"), // L4
new BatchNormalization(N, name: "l4 BatchNorm"),
new LeakyReLU(slope: 0.000001, name: "l4 LeakyReLU"),
new Linear(N, N, name: "l5 Linear"), // L5
new BatchNormalization(N, name: "l5 BatchNorm"),
new LeakyReLU(slope: 0.000001, name: "l5 LeakyReLU"),
new Linear(N, N, name: "l6 Linear"), // L6
new BatchNormalization(N, name: "l6 BatchNorm"),
new LeakyReLU(slope: 0.000001, name: "l6 LeakyReLU"),
new Linear(N, N, name: "l7 Linear"), // L7
new BatchNormalization(N, name: "l7 BatchNorm"),
new LeakyReLU(slope: 0.000001, name: "l7 ReLU"),
new Linear(N, N, name: "l8 Linear"), // L8
new BatchNormalization(N, name: "l8 BatchNorm"),
new LeakyReLU(slope: 0.000001, name: "l8 LeakyReLU"),
new Linear(N, N, name: "l9 Linear"), // L9
new BatchNormalization(N, name: "l9 BatchNorm"),
new PolynomialApproximantSteep(slope: 0.000001, name: "l9 PolynomialApproximantSteep"),
new Linear(N, N, name: "l10 Linear"), // L10
new BatchNormalization(N, name: "l10 BatchNorm"),
new PolynomialApproximantSteep(slope: 0.000001, name: "l10 PolynomialApproximantSteep"),
new Linear(N, N, name: "l11 Linear"), // L11
new BatchNormalization(N, name: "l11 BatchNorm"),
new PolynomialApproximantSteep(slope: 0.000001, name: "l11 PolynomialApproximantSteep"),
new Linear(N, N, name: "l12 Linear"), // L12
new BatchNormalization(N, name: "l12 BatchNorm"),
new PolynomialApproximantSteep(slope: 0.000001, name: "l12 PolynomialApproximantSteep"),
new Linear(N, N, name: "l13 Linear"), // L13
new BatchNormalization(N, name: "l13 BatchNorm"),
new PolynomialApproximantSteep(slope: 0.000001, name: "l13 PolynomialApproximantSteep"),
new Linear(N, N, name: "l14 Linear"), // L14
new BatchNormalization(N, name: "l14 BatchNorm"),
new PolynomialApproximantSteep(slope: 0.000001, name: "l14 PolynomialApproximantSteep"),
new Linear(N, 10, name: "l15 Linear") // L15
);
函数字典
函数字典是一个可序列化的函数字典(之前已描述)。当从磁盘加载网络模型时,将返回一个函数字典,可以像在代码中直接创建函数栈一样对其进行操作。函数字典主要用于与 Caffe 数据模型加载器一起使用。
Caffe1
Kelp.Net 在 Caffe 风格的开发基础上得到了强化,并支持其许多特性。
Caffe 为多媒体科学家和从业者提供了一个干净且可修改的框架,用于最先进的深度学习算法和一系列参考模型。该框架是一个带有 Python 和 MATLAB 绑定的 BSD 许可证 C++ 库,用于在通用架构上高效地训练和部署通用卷积神经网络和其他深度模型。通过 CUDA GPU 计算,Caffe 满足行业和互联网规模媒体需求,单个 K40 或 Titan GPU 每天处理超过 4000 万张图片(每张图片大约 2 毫秒)。通过分离模型表示和实际实现,Caffe 允许实验,并在平台之间无缝切换,便于开发和部署,从原型机到云环境。
Chainer
根据 Chainer 文档[2]:
“Chainer 是一个灵活的神经网络框架。一个主要目标是灵活性,因此它必须能够让我们简单直观地编写复杂的架构。”
Chainer 采用定义-运行方案,即网络通过实际的正向计算动态定义。更精确地说,Chainer 存储计算的历史而不是编程逻辑。例如,Chainer 不需要任何魔法就可以将条件语句和循环引入网络定义中。定义-运行方案是 Chainer 的核心概念。这种策略也使得编写多 GPU 并行化变得容易,因为逻辑更接近网络操作。
Kelp.Net 可以直接从磁盘加载 Chainer 模型。
损失
Kelp.Net 由一个单一的抽象 LossFunction 类组成,该类旨在实现你的特定实例,以确定你如何评估损失。
在机器学习中,损失函数或代价函数是一个将事件或一个或多个变量的值映射到实数的函数,直观地表示与事件相关的某些成本。Kelp.Net 提供了两种现成的损失函数:均方误差和 softmax 交叉熵。你可以轻松扩展这些函数以满足你的需求。
模型保存和加载
Kelp.Net 通过调用一个简单的类即可轻松保存和加载模型。ModelIO 类公开了 Save 和 Load 方法,以便轻松地将模型保存到磁盘。以下是一个在训练后保存模型、重新加载并随后对该模型进行测试的非常简单的示例:
优化器
优化算法根据模型的参数最小化或最大化误差函数。参数的例子可以是权重和偏差。它们帮助计算输出值,并通过最小化损失来更新模型,使其朝向最优解的位置。将 Kelp.Net 扩展以添加自己的优化算法是一个简单的过程,尽管添加 OpenCL 和资源方面需要协调努力。
Kelp.Net 随带许多预定义的优化器,例如:
-
AdaDelta
-
AdaGrad
-
Adam
-
GradientClipping
-
MomentumSGD
-
RMSprop
-
SGD
这些都是基于抽象优化器类。
数据集
Kelp.Net 本地支持以下数据集:
-
CIFAR
-
MNIST
CIFAR
CIFAR 数据集有两种风味,CIFAR-10 和 CIFAR 100,区别在于每个数据集中的类别数量。让我们简要讨论一下两者。
CIFAR-10
CIFAR-10 数据集由 10 个类别的 60,000 张 32 x 32 彩色图像组成,每个类别有 6,000 张图像。有 50,000 张训练图像和 10,000 张测试图像。数据集分为五个训练批次和一个测试批次,每个批次有 10,000 张图像。测试批次包含每个类别恰好 1,000 张随机选择的图像。训练批次包含剩余的图像,以随机顺序排列,但某些训练批次可能包含比另一个类别更多的图像。在这些批次之间,训练批次包含每个类别恰好 5,000 张图像。
CIFAR-100
CIFAR-100 数据集与 CIFAR-10 类似,但它有 100 个类别,每个类别包含 600 张图片。每个类别有 500 张训练图片和 100 张测试图片。CIFAR-100 的 100 个类别被分为 20 个超级类别。每张图片都附有一个精细标签(它所属的类别)和一个粗略标签(它所属的超级类别)。以下是 CIFAR-100 中类别的列表:
| 超级类别 | 类别 |
|---|---|
| 水生哺乳动物 | 海狸、海豚、水獭、海豹和鲸鱼 |
| 鱼类 | 水族馆鱼类、扁鱼、鳐鱼、鲨鱼和鲑鱼 |
| 花卉 | 兰花、罂粟花、玫瑰、向日葵和郁金香 |
| 食品容器 | 瓶子、碗、罐头、杯子和盘子 |
| 水果和蔬菜 | 苹果、蘑菇、橙子、梨和甜椒 |
| 家用电器 | 时钟、电脑键盘、灯、电话和电视 |
| 家具 | 床、椅子、沙发、桌子和衣柜 |
| 昆虫 | 蜜蜂、甲虫、蝴蝶、毛毛虫和蟑螂 |
| 大型食肉动物 | 熊、豹、狮子、老虎和狼 |
| 大型人造户外物品 | 桥梁、城堡、房屋、道路和摩天大楼 |
| 大型自然户外景观 | 云、森林、山脉、平原和海洋 |
| 大型杂食性和草食性动物 | 骆驼、牛、黑猩猩、大象和袋鼠 |
| 中型哺乳动物 | 狐狸、刺猬、负鼠、浣熊和臭鼬 |
| 非昆虫无脊椎动物 | 螃蟹、龙虾、蜗牛、蜘蛛和蠕虫 |
| 人群 | 婴儿、男孩、女孩、男人和女人 |
| 爬行动物 | 鳄鱼、恐龙、蜥蜴、蛇和乌龟 |
| 小型哺乳动物 | 仓鼠、老鼠、兔子、鼩鼱和松鼠 |
| 树木 | 榉树、橡树、棕榈、松树和柳树 |
| 车辆 1 | 自行车、公共汽车、摩托车、皮卡和火车 |
| 车辆 2 | 草割机、火箭、电车、坦克和拖拉机 |
MNIST
MNIST 数据库是一个包含大量手写数字的大型数据库,通常用于训练各种图像处理系统。该数据库在机器学习领域的训练和测试中也得到了广泛的应用。它包含 60,000 个示例的训练集和 10,000 个示例的测试集。数字已经被标准化到固定大小的图像中,并进行了居中处理,这使得它成为想要尝试各种学习技术而不需要预处理和格式化工作的人们的首选标准:
MNIST 示例
测试
测试是实际的执行事件,可以说是小型程序。由于使用了 OpenCL,这些程序在运行时进行编译。要创建一个测试,您只需要提供一个静态的Run函数,该函数封装了您的代码。Kelp.Net 附带了一个预配置的测试器,这使得添加您自己的测试变得非常简单。我们将在编写测试的章节中详细探讨这一点,现在,这里有一个简单的 XOR 测试程序的示例:
public static void Run()
{
const int learningCount = 10000;
Real[][] trainData =
{
new Real[] { 0, 0 },
new Real[] { 1, 0 },
new Real[] { 0, 1 },
new Real[] { 1, 1 }
};
Real[][] trainLabel =
{
new Real[] { 0 },
new Real[] { 1 },
new Real[] { 1 },
new Real[] { 0 }
};
FunctionStack nn = new FunctionStack(
new Linear(2, 2, name: "l1 Linear"),
new ReLU(name: "l1 ReLU"),
new Linear(2, 1, name: "l2 Linear"));
nn.SetOptimizer(new AdaGrad());
RILogManager.Default?.SendDebug("Training...");
for (int i = 0; i < learningCount; i++)
{
//use MeanSquaredError for loss function
Trainer.Train(nn, trainData[0], trainLabel[0], new MeanSquaredError(), false);
Trainer.Train(nn, trainData[1], trainLabel[1], new MeanSquaredError(), false);
Trainer.Train(nn, trainData[2], trainLabel[2], new MeanSquaredError(), false);
Trainer.Train(nn, trainData[3], trainLabel[3], new MeanSquaredError(), false);
//If you do not update every time after training, you can update it as a mini batch
nn.Update();
}
RILogManager.Default?.SendDebug("Test Start...");
foreach (Real[] val in trainData)
{
NdArray result = nn.Predict(val)[0];
RILogManager.Default?.SendDebug($"{val[0]} xor {val[1]} = {(result.Data[0] > 0.5 ? 1 : 0)} {result}");
}
}
监控 Kelp.Net
ReflectSoftware 的 ReflectInsight 无疑是当今最好的实时日志记录和丰富可视化框架。Kelp.Net 原生支持此框架,因此您很容易看到测试内部的运行情况。
下面是 ReflectInsight 主屏幕的样貌:
Reflect Insight 主屏幕的一个示例
表盘
监视器允许您在测试执行过程中关注特定的数据元素。在机器学习领域,理解和看到算法内部的确切运行情况至关重要,而监视面板正是实现这一目标的地方:
消息
消息面板是测试执行期间显示每个消息的地方。可用的信息完全取决于您。消息文本左侧显示的图像基于您发送的消息类型(信息、调试、警告、错误等):
属性
每个消息都有预定义的属性,可以通过属性面板查看。有标准属性,如下所示,适用于每个消息。然后还有可自定义的消息属性,可以应用:
消息属性示例
Weaver
Weaver 是 Kelp.Net 的关键组件,当你运行测试时,你将首先调用这个对象。这个对象包含各种 OpenCL 对象,例如:
-
计算上下文
-
一组计算设备
-
计算命令队列
-
一个布尔标志,指示是否启用 GPU
-
计算平台
-
核心源字典
Weaver 是你告诉程序是否将使用 CPU 或 GPU,以及你将使用哪个设备(如果你的系统具有多个设备)的地方。你只需要在程序开始时调用一次 weaver,就像你在这里看到的那样:
Weaver.Initialize(ComputeDeviceTypes.Gpu);
你还可以避免使用 weaver 的初始化调用,并允许它自动确定需要发生什么。
这里是 weaver 的基本内容。其目的是构建(在运行时动态编译)将要执行的程序:
/// <summary> The context. </summary>
internal static ComputeContext Context;
/// <summary> The devices. </summary>
private static ComputeDevice[] Devices;
/// <summary> Queue of commands. </summary>
internal static ComputeCommandQueue CommandQueue;
/// <summary> Zero-based index of the device. </summary>
private static int DeviceIndex;
/// <summary> True to enable, false to disable. </summary>
internal static bool Enable;
/// <summary> The platform. </summary>
private static ComputePlatform Platform;
/// <summary> The kernel sources. </summary>
private static readonly Dictionary<string, string> KernelSources = new Dictionary<string, string>();
编写测试
为 Kelp.Net 编写测试非常简单。你编写的每个测试只需要公开一个 Run 函数。其余的是你想要网络如何运行的逻辑。你的 Run 函数的一般指南将是:
- 加载数据(真实或模拟):
Real[][] trainData = new Real[N][];
Real[][] trainLabel = new Real[N][];
for (int i = 0; i < N; i++)
{
//Prepare Sin wave for one cycle
Real radian = -Math.PI + Math.PI * 2.0 * i / (N - 1);
trainData[i] = new[] { radian };
trainLabel[i] = new Real[] { Math.Sin(radian) };
}
- 创建你的函数堆栈:
FunctionStack nn = new FunctionStack(
new Linear(1, 4, name: "l1 Linear"),
new Tanh(name: "l1 Tanh"),
new Linear(4, 1, name: "l2 Linear")
);
- 选择你的优化器:
nn.SetOptimizer(new SGD());
- 训练你的数据:
for (int i = 0; i < EPOCH; i++)
{
Real loss = 0;
for (int j = 0; j < N; j++)
{
//When training is executed in the network, an error is returned to the return value
loss += Trainer.Train(nn, trainData[j], trainLabel[j], new MeanSquaredError());
}
if (i % (EPOCH / 10) == 0)
{
RILogManager.Default?.SendDebug("loss:" + loss / N);
RILogManager.Default?.SendDebug("");
}
}
- 测试你的数据:
RILogManager.Default?.SendDebug("Test Start...");
foreach (Real[] val in trainData)
{
RILogManager.Default?.SendDebug(val[0] + ":" + nn.Predict(val)[0].Data[0]);
}
基准测试函数
KelpNetTester 类中的 SingleBenchmark 类允许对各种激活、噪声和其他函数进行简单的基准测试。如果一个函数具有 GPU 功能,那么它将被基准测试,CPU 功能也是如此。时间精度在微秒级别,因为 ReLU 前向通常总是低于 1 毫秒的粒度。
启用 CPUhttps://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/hsn-ml-csp/img/e2b3619a-941f-49cf-8c3f-116833511bf8.pngGPU**<httphttps://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/hsn-ml-csp/img/03550d87-07a5-4a7e-86b1-e3a64b026410.png
现在我们来谈谈如何运行单个基准测试。
运行单个基准测试
当你运行 SingleBenchmark 类时,你将在即将出现的图像中看到的功能将被计时。将提供前向和反向 CPU 和 GPU 的时间(当适用时)。以下是基准测试的折叠视图:
这里是基准测试的展开视图:
摘要
在本章中,我们欢迎你进入直观深度学习的世界。我们展示了你如何使用 Kelp.Net 作为你的研究平台来测试几乎任何假设。我们还展示了 Kelp.Net 的强大功能和灵活性。在我们的下一章中,我们将进入量子计算的世界,并展示计算的一小部分未来。戴上你的帽子,这一章是不同的!
参考文献
-
快速特征嵌入的卷积架构,Y Jia, E Shelhamer, J Donahue, S Karayev, J Long,第 22 届 ACM 国际会议论文集,2014
-
Chainer 在
docs.chainer.org/en/stable/index.html -
从微小图像中学习多层特征,Alex Krizhevsky,2009,见
www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf -
原始 Kelp.Net 在
github.com/harujoh -
(Filice '15) Simone Filice, Giuseppe Castellucci, Danilo Croce, Roberto Basili,Kelp:一种基于核的自然语言处理学习平台,ACL 系统演示会议论文集,北京,中国(2015 年 7 月)
第十三章:量子计算 – 未来
我们将以另一个话题的开始来结束这个系列,量子计算。量子计算是未来,它正在到来,它是真实的。它使用量子力学现象,如叠加和纠缠,其计算基于称为量子比特(qubits)的东西。普通计算机是基于晶体管,并使用著名的 0 和 1,而量子计算使用量子比特,它们可以处于状态的叠加,而不仅仅是开或关。
在撰写本文时,量子计算仍处于起步阶段,但正在取得进展。因此,这一章将会很短,但我们希望让你了解未来,以便你有所了解。
在本章中,我们将涵盖:
-
叠加
-
传送
正在进行许多实验,微软发布了其量子计算软件开发工具包(SDK)。微软和 IBM 都在开发他们自己的量子计算机版本。但在我们到达那里之前,让我们回顾一下你需要了解的一些术语。这些将进入你的超级术语清单!
这里有一个布洛赫球面,它是希尔伯特空间中量子比特的表示,它是量子计算最基本的部分:
一个经典计算机的内存由比特组成——1 和 0。然而,量子计算机由一系列量子比特组成。单个量子比特可以代表一个 1、一个 0,或者这两个量子比特状态的任何量子叠加。单个量子比特可以处于两种状态中的任何一种。一对量子比特可以处于两种状态的任何叠加,三个量子比特可以处于八种状态的任何叠加。因此,量子计算机可以处于许多不同状态的叠加,而传统计算机在任何给定时刻只能处于这些状态中的任何一个。
量子计算机通过量子门和量子逻辑门(类似于传统计算机的逻辑门)运行,它试图解决的问题是通过设置量子比特的初始值来编码的,就像传统计算机一样。量子算法被认为是大多数概率性的,即在已知概率下提供正确解决方案:
量子比特由受控粒子和控制手段(例如,捕获粒子并将它们从一个状态切换到另一个状态的设备)组成。
叠加
维基百科将叠加定义为:
“…量子力学的一个基本原理。它指出,与经典物理学中的波类似,任何两个(或更多)量子状态可以相加(“叠加”),其结果将是另一个有效的量子状态;反之,每个量子状态都可以表示为两个或更多其他不同状态的和。在数学上,它指的是薛定谔方程解的性质;由于薛定谔方程是线性的,任何解的线性组合也将是解。”
这实际上意味着,当量子系统没有被观察时,它可以同时处于多个状态。它们可以同时存在于所有可能的状态中。想象一下风大的日子里池塘上有很多波浪。毫无疑问,你看到它们在某些时候是重叠的。这就是量子叠加的简单粗暴的解释。
超光速
在量子计算中,超光速指的是将量子状态从一个位置移动到另一个位置的方法,而不需要移动任何物理粒子。这个过程通常伴随着发送和接收位置之间的纠缠。量子状态的传输使用量子纠缠现象(我们下一个话题)作为手段。当两个或更多粒子纠缠在一起时,它们的量子状态是相互依赖的,无论它们相隔多远。实际上,它们作为一个单一的量子对象起作用。
量子纠缠
量子纠缠是一种物理现象,当成对或成组的粒子以某种方式生成或相互作用,使得每个粒子的状态不能独立于其他粒子的状态来描述,即使粒子之间相隔很远——相反,必须为整个系统描述一个量子状态。
或许需要一个更直观的例子。假设你有一份今天的报纸,它有 100 页长。如果你阅读了 10 页,你就知道了 10%的内容。如果你再阅读另外 10 页,你现在就知道了 20%的内容,以此类推。然而,如果报纸非常复杂,如果你阅读了 10 页,你几乎什么也不知道。为什么?因为信息分布在那些页面之间,而不是在页面上。所以你必须想出一个方法一次性阅读所有页面。
现在我们已经描述了这些术语,让我们向您展示一个来自微软量子计算 SDK 的快速示例。正如我们提到的,在撰写本文时,量子计算还处于起步阶段,所以我们能做的最好的就是向您展示它的方向。我们将用一个非常简短的例子来做到这一点,然后如果您愿意,您可以继续学习。
那么,量子计算程序看起来是什么样子呢?就像这样:
class Program
{
static void Main(string[] args)
{
using (var sim = new QuantumSimulator())
{
var rand = new System.Random();
foreach (var idxRun in Enumerable.Range(0, 8))
{
var sent = rand.Next(2) == 0;
var received = TeleportClassicalMessage.Run(sim, sent).Result;
System.Console.WriteLine($"Round {idxRun}:tSent {sent},tgot
{received}.");
System.Console.WriteLine(sent == received ? "Teleportation
successful!!n" : "n");
}
}
System.Console.WriteLine("nnPress Enter to continue...nn");
System.Console.ReadLine();
}
}
嗯,就是这样!好吧,差不多。你看,使用微软量子 SDK 的量子计算程序由两部分组成。第一部分是你在这里看到的 C#组件。实际上,前端可以是 C#、Python 以及几种其他语言。后端,我们稍后会看到,是量子部分,用 Q#编写;这是微软的新量子计算语言。每个 Q#操作都会生成一个同名的 C#类,该类将有一个Run方法。这个方法是异步的,因为操作将在量子计算机上异步运行。
根据微软关于 Q#的文档:
"Q#(Q-Sharp)是一种用于表达量子算法的领域特定编程语言。它用于编写在经典主机程序和计算机控制下运行的子程序,在辅助量子处理器上执行。
Q#提供了一组原始类型,以及两种创建新结构类型的方式(数组和解包)。它支持一个基本的程序模型来编写程序,包括循环和 if/then 语句。Q#中的顶级构造是用户定义的类型、操作和函数。
那么,让我们来谈谈我们的 C#代码做了什么。它只是通过量子传输(现在你知道我们为什么从术语开始!)发送一条消息。让我们看看后端,看看发生了什么:
operation Teleport(msg : Qubit, there : Qubit) : ()
{
body
{
using (register = Qubit[1])
{
// Ask for an auxillary qubit that we can use to prepare
// for teleportation.
let here = register[0];
// Create some entanglement that we can use to send our message.\
H(here);
CNOT(here, there);
// Move our message into the entangled pair.
CNOT(msg, here);
H(msg);
// Measure out the entanglement.
if (M(msg) == One) { Z(there); }
if (M(here) == One) { X(there); }
// Reset our "here" qubit before releasing it.
Reset(here);
}
}
}
现在许多想法在你的脑海中涌现。问题比答案多,对吧?别担心;这确实是一种更技术性的软件编写方法,但我们会给你一点启示,让你明白这一切。
H、CNOT、M,发生了什么?这些都是 Q#定义的函数,并将存在于你的项目中的 Q#组件文件中。让我们看看其中一个,并解释一下发生了什么。
CNOT
这将CNOT门(一个受控非门)应用于一个量子比特。CNOT 门是经典门的“量子化”,可以用来纠缠和解纠缠 EPR 状态。对于那些对 EPR 感兴趣的人,我建议阅读一下爱因斯坦-波多尔斯基-罗森佯谬(EPR)。
CNOT门是一组行和列的集合,类似于这样:
H
这个函数将哈达玛变换应用于单个量子比特。它基本上会翻转量子比特的一半,而不是全部。哈达玛变换用于数据加密,以及 JPEG XR 和 MPEG-4 音频视频编解码器等信号处理算法。在视频压缩中,它通常用于绝对变换差分的总和。哈达玛变换还用于质谱学、晶体学等科学方法中。
目前,哈达玛函数定义为:
M
这测量单个量子比特在泡利 Z基下的状态,输出结果由分布给出。M操作定义为:
Pr(Zero||ψ
⟩
)=
⟨
ψ|0
⟩⟨
0|ψ
⟩
Q#语言与 C#有何不同?以下是一些要点。
using语句与 C#中的不同。它用于为处理分配量子比特数组。与 C#中的using语句类似,量子比特在using语句结束时被释放。在整个应用程序的生命周期中,没有量子比特被使用。
Q#有一个不同的for循环,用于遍历范围。没有 C# for循环的直接等效。
默认情况下,Q#中的所有变量都是不可变的,这意味着一旦它们被分配,就不能更改。有一个let关键字可以用来绑定变量。操作参数始终是不可变的。尽管如此,(在撰写本文时)有声明变量并使用set语句稍后设置其值的能力。
摘要
好吧,我希望你们阅读这本书的乐趣和我写作时的乐趣一样。记住,还有很多东西要介绍,而且每天都有新的发展和变化!在系列书的下一本书中,我计划深入探索深度学习的世界,并真正探索一些有趣事物的内部。
现在,我希望你们已经找到了一种方法来接受这些宝贵的开源项目,并将它们融入到你们的日常生活中。无论你是机器学习开发者、数据科学家,还是对上述所有内容都感兴趣的普通 C#开发者,这本书中都有适合每个人的内容。探索开源项目、它们的示例和测试用例;构建一个框架,允许你将它们整合到你的日常生活中。
现在,非常感谢您阅读这本书,并祝您在未来的机器学习努力中一切顺利!我想感谢所有参与创建这本书并将其推向市场的人。Packt 团队非常乐于助人且礼貌,使整个过程变得简单而有趣。对所有校对者,非常感谢您的时间和努力。您的评论帮助使这本书变得更好。
更多推荐



所有评论(0)