跳转至

以太坊区块链上的机器学习

区块链和人工智能近年来一直是最有趣的话题,这有一个很好的理由:它们是最先进的技术,被创造出来扰乱大多数既定的业务。事实上,我们可以教会计算机自己学习是一件非常强大的事情,这意味着未来的机器学习系统将继续发展。区块链也是如此:分布式计算领域才刚刚起步,它将成为未来大多数问题的默认解决方案。那么,为什么不将两者结合起来,成为一项革命性的发明呢?事实证明,它们可以很好地合作,我们可以创建非常有趣的 dApps,从两个世界受益,特别是通过使用它们来创建解决机器学习问题的分散市场,奖励用户的计算能力。

在本章中,我们将讨论以下主题:

  • 理解机器学习
  • 分散式机器学习市场
  • 构建智能合同机器学习市场

理解机器学习

机器学习 ( ML )是人工智能 ( AI )的子集,而后者又是更广泛的数据科学学科中的一个领域。ML 专注于创建程序,这些程序通过自我学习来解决特定的问题,而不必编写所有的逻辑;我们只需要给他们大量的输入。试错是机器慢慢学会如何对问题做出正确输出的主要机制。

计算机被创造出来的那一刻,就是科学家问自己:“我们如何才能让这台机器像人一样思考和行动?”。这就是为什么理解计算机如何学习要从理解人类如何看待世界开始。

想一想:你认为动物和人类是如何学会在我们生活的危险和混乱的世界中生存的?通过向别人学习?嗯,这是一个有效的学习系统,但所有对我们真正知道的东西的理解都来自面对不确定性的实验。想象以下场景:你身处一个尚未发明语言的原始世界——我们说的是几千年前。你看到地上有一个扁平闪亮的红色物体,对你来说是全新的。你是怎么开始理解它的?它可能会杀死你,也可能为你提供新的材料来源。你还不知道,所以你开始尝试不同的事情,总是小心翼翼,因为你的主要目标是生存。你用棍子碰它:什么也没发生。你用手触摸它:它摸起来很温暖。你抓住它:它感觉很强,所以你试图打破它,没有成功。经过更多的实验后,你得出结论,你手中的是一个坚固的、自然形成的金属盘,你可以用它来利用太阳能烹饪食物。

所有这些特定的知识都来自于使用试错机制的实验。我的观点是,这就是我们如何发现我们在当前世界中所知道的一切,这也是机器学习算法用来自行解决问题的系统。你给他们很多信息,他们用他们的工具进行实验,通常是逐像素读取图像和数据字节,以产生一个结果。它们用于在给定一些初始条件的情况下预测未来,用于理解经典编程无法解决的复杂问题,以及用于创建工具来帮助我们做得更好。

从技术上讲,创建机器学习系统有三个步骤,如下:

  1. 收集关于某个主题的大量信息,例如 2,000,000 张独特水瓶的图像。
  2. 开发一个生成期望输出的机器学习模型。在我们的示例中,假设我们想要创建一个模型,根据水瓶的形状、大小、颜色、化学成分和纯度对其进行分类,因为我们需要找到最适合人类使用的水。这些属性被称为标签,因为它们是每个组件的精确描述。
  3. 该模型在一个名为训练的过程中消耗所有数据,在这个过程中,它调整我们的水瓶的每个组件的重要性,以计算哪些因素决定了最好的水。在某个时候,它将接受训练,这意味着它将理解什么属性构成对人类来说最好的水,生成一个程序,我们可以用它来快速确定特定的新水瓶有多好。

这只是我们如何使用机器学习为复杂问题提供解决方案的一个例子,例如,为了最佳健康,我可以喝什么水?,一个危险的人长什么样?,我如何教我的相机确定它看到的是一只狗还是一只猫?

一般来说,步骤是获取数据->创建一个使用该数据创建程序的模型->在特定情况下使用该程序。还有许多其他不同的系统,在这些系统中,程序通过反复试验自己获取数据来学习。其他有趣的机器学习算法在生物层面上工作,教机器人充当现实生活中的动物,以便像它们一样学习和看待世界。

这是一个非常热门的话题,将在未来几年继续发展,为人类可能提出的最复杂的问题提供答案。这就是为什么我推荐你去探索广阔的人工智能世界。在与区块链结合之前,先看看那里有什么。

分散式机器学习市场

我们将建立一个市场,从拥有强大 GPU 并希望帮助其他人执行机器学习的用户那里购买和销售计算能力,以教会他们的算法完成基于监督学习的任务,其中程序学习从给定目标的大量输入中生成所需的输出,以便它对自己进行编程。

当我们需要处理存储在我们的 ML 市场中发生的交易的永久记录以及买方从他们的参数中请求的训练模型时,以太坊进入了等式,以便它可以随时访问。这个想法是创造一个地方,让来自世界各地的人们可以开始从他们的硬件的新用途中赚钱,作为采矿的替代,同时也为 ML 算法提供一个安全的系统。

我们将使用 GPU 来训练我们的机器学习程序,因为它们擅长同时处理大量并行操作,因此我们可以快速处理大量输入,比使用 CPU 更快。我们还将使用以太坊作为默认支付货币来轻松处理分散交易。

如今大多数机器学习模型都是基于神经网络 ( NN ),这是人脑如何工作的抽象,被翻译成计算机。它基于虚拟的单个神经元,这些神经元接收输入,并在满足条件的情况下产生输出。例如,假设一个简单的神经元包含以下语句:

if(input > 10) return output = true;

如果输入大于 10,该语句将返回一个正值。这个函数就是所谓的激活函数,这是有意义的,因为如果函数满足条件,它就会激活。我们可以用不同的参数和配置将许多这些神经元组合在一起,得到所谓的神经网络,它处理复杂的输入,产生精确的输出。训练时,我们会重新调整激活功能,以更好地适应我们想要的目标。一旦在我们的模型中设置好,这些都是自动完成的。最后,我们得到一个训练有素的程序,它能够回答复杂的问题,而不必为每个特定的场景编写代码。

一旦从我们的训练数据集调整了模型,我们就可以用来自不同来源的新输入来测试它,以确定它是否生成了最佳输出。这很重要,因为存在过度拟合的风险,即机器学习程序优化得太多,变得对我们的初始输入太具体,这使得它无法从新数据中产生有效的结果。这就像一个外科医生必须从零开始成为一名普通医生:它不会产生很大的结果,因为它太专业化了。

一些众所周知的激活函数是 Sigmoid 和 ReLU。深度学习是堆叠几层神经元的过程,以便将一个神经元的输出传输到另一个神经元,以获得更高级的结果。这些网络被称为深度神经网络 ( DNNs ,因为它们由几层组成。确保自己探索 NNs 的迷人世界,了解未来的技术是如何形成的。

我们不会在这里使用神经网络,因为由于区块链的限制,它们很难从零开始在可靠性上实现,所以我们将使用更简单的算法,您可以根据需要扩展这些算法。我们的协议是这样工作的:

  1. 一个用户向 ETH 中的智能合约发布一组数据,一个评价函数(我们的 ML 模型),以及完成任务的奖励。
  2. 那些想要完成任务的人将从第一个用户下载发布的数据来训练给定的 ML 模型,以便生成将被反馈给智能合同的训练程序。
  3. 外部用户将查看该特定任务的所有已发布的解决方案,以确定谁是赢家。买家将根据他们的偏好决定获胜者。

根据该协议,我们可以建立用户将遵循的以下过程:

  1. 想要训练其模型的购买者部署包含以下数据的智能合约:

    • 他们在构造函数中的模型定义——例如,DNN。
    • 要训练的数据集,例如,由 30 x 30 像素组成的手写数字图像数组。每个图像都是一个 30 x 30 像素(900 像素)的数组,其中每个像素都是另一个数组,包含有关像素位置以及像素是黑还是白的信息(为了避免复杂性,我们不希望图像中出现颜色),例如[[0,true],[1,false]]将表示一个 2 x 1 像素的图像,其中第一个像素是黑的,而另一个是白的。该数据集将发布到一个外部网站,人们可以自由访问该网站来训练模型。在我们的构造函数中,我们将提供一个 URL,即https://example.com/dataset
    • 训练模型的奖励是在以太坊支付的,这种安排设置在可支付的构造器中。
  2. 合同被公布,销售者开始参与训练模型的任务。从数据集中,90%的数据将用于训练模型,而剩余的 10%将用于测试程序的结果,以验证其准确性。为了确保卖家不会互相抄袭,不同的随机数据集将被给予不同的参与者。

  3. 买家决定哪种模式最适合他们,并选择一个赢家。如果到了截止时间,买家还没有选出获胜者,第一个参与者将获得奖励。

对于我们的机器学习市场,我们将在 Solidity 中使用简单的线性回归机器学习算法。用户将提交他们的数据,其中将包含一个名称和两个数字参数来进行预测。线性回归是两个因素之间的关系,例如,网站的销售额和访问者数量。在这种情况下,我们可以建立一个模型,允许我们预测给定数量的访问者的销售数量。

简单的线性回归模型可以应用于许多变量相互依赖的领域,这是最简单的机器学习系统之一。这就是为什么我们将使用它,因为能够在 Solidity 中重新创建它以验证其他用户提供的解决方案是很重要的。理想情况下,我们会实现一个神经网络或更复杂的模型,但考虑到区块链的局限性,这将需要太多的时间来开发。你可以在本章的基础上拓展市场。在下一节中,您将学习如何创建市场所需的代码。

构建智能合同机器学习市场

我们的 ML marketplace 将专门使用线性回归算法来简化这一过程,以便您了解这一切是如何联系在一起的。我鼓励您将解决方案扩展到更高级的模型,以练习您的 ML 和区块链技能。要应用简单的线性回归算法,我们需要以下东西:

  • 从数据生成预测的预测函数
  • 组合预测结果的成本函数
  • 梯度下降训练我们算法的优化算法,这将微调预测以获得更精确的结果
  • 一个训练函数来改进我们算法

预测功能

首先,您需要了解我们的简单线性回归算法使用以下函数预测值:

y = weight * x + bias

如果我们根据网站的访问者数量来预测销售额,我们的预测函数应该是这样的:

Sales = weight * visitors + bias

我们的目标是获得固定的权重和偏差值,以优化我们的预测函数,从而获得对销售额的真实估计。例如,一个训练好的线性回归看起来像这样:

Sales = 0.43 * visitors + 0.9

在从给定的数据集进行训练后,我们得到了权重0.43和偏差0.9。我们应该能够使用这个优化的函数来为我们的特定需求做出准确的预测,并获得很好的结果。我们需要在 Python 和 Solidity 中实现预测功能,因为卖家将使用 Python 来训练模型,而我们将使用 Solidity 来验证这些卖家给出的结果。下面是我们的prediction函数在 Python 和 Solidity 中的表现:

# Python implementation
def prediction(x, weight, bias):
    return weight * x + bias

供您参考,下面是我们将添加的可靠性函数,它允许卖家和买家通过进行预测来验证模型的精确度:

// Solidity implementation
function prediction(uint256 _x, uint256 _weight, uint256 _bias) public pure returns(uint256) {
 return _weight * _x + _bias;
}

成本函数

为了训练我们的线性回归算法来生成准确的预测,我们需要一个成本函数。成本函数是一种分析我们的预测函数对数据集的效果的方法。它给出了一个错误率,本质上是真实世界的结果与预测之间的差异。误差越小,我们的预测就越准确。成本函数采用实际结果和预测来输出我们模型的误差,如下所示:

error = result - prediction

有许多不同类型的成本函数。在我们的例子中,我们将使用均方误差 ( MSE )成本函数,它看起来像这样:

error = sum((result - prediction)2) / numberOfDataPoints

为了更清楚,我们可以添加带有所有参数的预测函数,以便您可以看到变量如何在成本函数中发挥作用,如以下代码所示:

error = sum((result - prediction(x, weight, bias))2) / numberOfDataPoints

这里,sum()是所有实际结果减去预测平方的和,即所有结果数据集值的和。所有这些除以数据点的数量。记住result是我们试图预测的实际值。例如,回到我们之前的例子,我们试图预测每个访问者我们将获得多少销售额,result将是来自 200 个访问者的10销售额,而预测是我们自己根据权重和偏差的估计。

为了帮助您更好地理解该函数,请考虑以下某个国家的假枪拥有者和每个国家的犯罪的示例数据集;在这个例子中,我们感兴趣的是了解枪支数量如何影响每个国家的犯罪数量。使用这些数据,我们可以预测犯罪,以便我们可以动员特定数量的警察来处理这些情况。请记住,这是用来说明成本函数如何工作的虚假数据:

| 国家 | 枪支总数 | 每年的犯罪数量 | | 德国 | Three thousand five hundred and twenty | 20 | | 爱沙尼亚 | 19 2 | three | | 巴哈马 | 91 | 0 | | 巴西 | Nine thousand two hundred and seventy-one | 88 |

我们首先用随机权重和偏差初始化预测函数,如以下代码所示:

// Our prediction function definition for you to remember how it looked like
y = weight * x + bias

// Our prediction function with random weight and bias
prediction = 0.1 * x + 0.4

对德国犯罪的预测如下:

prediction = 0.1 * 3520 + 0.4 = 352.4 crimes per year

我们得到352.4犯罪,我们可以近似为 352,因为用小数点来谈论犯罪是没有意义的。如你所见,我们的预测权重和偏差高于每年 20 起犯罪的真实结果,因为我们的模型尚未训练,所以使用真实值时预计会有巨大差异是正常的。

然后我们计算所有这些值的成本函数。让我们看看德国的情况:

// Our cost function definition for you to remember how it looked like
error = sum((result - prediction)2) / numberOfDataPoints

// Our cost function for the initial dataset
error = sum((20 - 352)2) / 1

我们将成本函数应用于一个数据点,以查看初始预测的误差,这样您就可以看到它是如何应用的。结果如下:

error = (20 - 352)2 / 1 = 110224 

误差是110224,这是一个巨大的数字,因为我们将它应用于一个数据点,而我们的模型还没有被训练。现在对所有数据点进行同样的操作,直到对整个数据集产生错误。希望你能理解这个例子中计算误差的过程。

我们需要计算误差来优化我们的预测函数,以便稍后做出更准确的预测。现在这个概念已经很清楚了,我们可以用 Python 实现这个功能了。在可靠性方面,我们希望它能计算出我们市场的特定解决方案的误差,以便丢弃那些误差过大的解决方案。Python 中的cost函数将被买家用来验证他们的培训结果,而 Solidity 中的卖家将使用它来验证提交的内容。让我们看看下面的代码:

# The cost function implemented in python
def cost(results, weight, bias, xs):
    error = 0.0
    numberOfDataPoints = len(xs)
    for i in range(numberOfDataPoints):
        error += (results[i] - (weight * xs[i] + bias)) ** 2
    return error / numberOfDataPoints

xs参数是独立变量x的数组,我们在预测函数中看到过。这是它看起来坚固的样子;因为这是一个纯函数,所以我们不必担心汽油成本,因为一切都将在本地执行,而不必从区块链修改州:

// The cost function implemented in solidity
function cost(int256[] memory _results, int256 _weight, int256 _bias, int256[] memory _xs) public pure returns(int256) {
    require(_results.length == _xs.length, 'There must be the same number of _results than _xs values');
    int256 error = 0; // Notice the int instead of uint since we want negative values too
    uint256 numberOfDataPoints = _xs.length;
    for(uint256 i = 0; i < numberOfDataPoints; i++) {
        error += (_results[i] - (_weight * _xs[i] + _bias)) * (_results[i] - (_weight * _xs[i] + _bias));
    }
    return error / int256(numberOfDataPoints);
}

如您所见,我们已经在for循环中包含了预测函数来计算结果减去预测平方,这样我们就可以计算cost函数的误差。这将由想要优化来自买方的特定线性回归以做出准确预测的卖方使用。

优化算法

既然我们可以在给定一些参数的情况下进行预测,并使用成本函数计算这些预测的精度,我们必须通过减少误差来改进这些预测。我们如何减少成本函数产生的误差?通过用优化算法调整我们的预测函数的权重和偏差。在这种情况下,我们将使用梯度下降,这使我们能够不断减少误差。下面的图表解释了它的工作原理:

我们从随机权重和偏差值引起的高误差开始,然后通过优化这些参数来减少误差,直到我们达到足够好的预测模型,即图中的局部最小值。这个想法是计算权重偏差的偏导数,看看它们如何影响最终预测,直到我们达到最小值。我们不会讨论计算这些导数的数学,因为这可能会导致混淆,所以偏导数的结果函数如下所示:

weightDerivative = sum(-2x * (result - (x * weight + bias))) / numberOfDataPoints

biasDerivative = sum(-2 * (result - (x * weight + bias))) / numberOfDataPoints

让我们来看看这些函数的实现,以更新我们的机器学习算法的权重和偏差:

# Python implementation, returns the optimized weight and bias for that step
def optimizeWeightBias(results, weight, bias, xs, learningRate):
    weightDerivative = 0
    biasDerivative = 0
    numberOfDataPoints = len(results)
    for i in range(numberOfDataPoints):
        weightDerivative += (-2 * xs[i] * (results[i] - (xs[i] * weight + bias)) / numberOfDataPoints)
        biasDerivative += (-2 * (results[i] - (xs[i] * weight + bias)) / numberOfDataPoints)

    weight -= weightDerivative * learningRate
    bias -= biasDerivative * learningRate
    return weight, bias

在坚固性方面,它看起来像这样:

// Solidity implementation
function optimize(int256[] memory _results, int256 _weight, int256 _bias, int256[] memory _xs, int256 _learningRate) public pure returns(int256, int256) {
    require(_results.length == _xs.length, 'There must be the same number of _results than _xs values');
    int256 weightDerivative = 0;
    int256 biasDerivative = 0;
    uint256 numberOfDataPoints = _xs.length;
    for(uint256 i = 0; i < numberOfDataPoints; i++) {
        weightDerivative += (-2 * _xs[i] * (_results[i] - (_xs[i] * _weight + _bias)) / int256(numberOfDataPoints));
        biasDerivative += (-2 * (_results[i] - (_xs[i] * _weight + _bias)) / int256(numberOfDataPoints));
    }
    _weight = weightDerivative * _learningRate;
    _bias = biasDerivative * _learningRate;
    return (_weight, _bias);
}

如您所见,我们正在使用前面代码块中描述的函数计算两个导数,这样我们就可以用优化值更新权重和偏差。学习率是我们到达图中最小点所走的步数的大小。如果我们迈大步,我们可能会错过最小值,如果我们迈小步,我们可能会花太多时间来达到最小值。无论如何,最好保持均衡的学习速度,尝试不同的步长。现在我们有一种方法来提高我们的预测功能。

训练功能

我们可以开始用一个新的函数来改进我们的模型,这个函数循环通过几个优化调用,直到我们达到最小值,在这个点上模型将被完全优化。它看起来是这样的:

# Python implementation
def train(results, weight, bias, xs, learningRate, iterations):
 error = 0 for i in range(iterations):
    weight, bias = optimizeWeightBias(results, weight, bias, xs, learningRate)
    error = cost(results, weight, bias, xs)
    print("Iteration: {}, weight: {:.4f}, bias: {:.4f}, error: {:.2}".format(i, weight, bias, error))
 return weight, bias

Solidity 实现看起来非常相似,尽管我们必须确保结果和独立变量的值具有相同的长度以避免错误,如以下代码所示:

// Solidity implementation
function train(int256[] memory _results, int256 _weight, int256 _bias, int256[] memory _xs, int256 _learningRate, uint256 _iterations) public pure returns(int256, int256) {
    require(_results.length == _xs.length, 'There must be the same number of _results than _xs values');
    int256 error = 0;
    for(uint256 i = 0; i < _iterations; i++) {
        (_weight, _bias) = optimize(_results, _weight, _bias, _xs, _learningRate);
        error = cost(_results, _weight, _bias, _xs);
    }
    return (_weight, _bias);
}

如您所见,我们使用优化函数和成本函数,通过更新指定迭代次数的权重和偏差参数来不断减少误差。

现在,在使用训练函数训练模型之后,您应该能够创建和训练线性回归模型,以便使用预测函数进行预测。以下是完整的 Python 代码供你参考,虽然你可以在官方 GitHub 上看到更新版本,网址是https://GitHub . com/merlox/machine-learning-ether eum/blob/master/linear regression . py

我们首先创建构造函数,它将使用uniform库用一些初始随机值训练模型,因为它返回 0 和 1 之间的浮点数,如下面的代码所示:

from random import uniform

class LinearRegression:
    xs = [3520, 192, 91, 9271]
    results = [20, 3, 0, 88]

    def __init__(self):
        initialWeight = uniform(0, 1)
        initialBias = uniform(0, 1)
        learningRate = 0.00000004
        iterations = 2000
        print('Initial weight {}, Initial bias {}, Learning rate {}, Iterations {}'.format(initialWeight, initialBias, learningRate, iterations))
        finalWeight, finalBias = self.train(self.results, initialWeight, initialBias, self.xs, learningRate, iterations)
        finalError = self.cost(self.results, finalWeight, finalBias, self.xs)
        print('Final weight {:.4f}, Final bias {:.4f}, Final error {:.4f}, Prediction {:.4f} out of {}, Prediction Two {:.4f} out of {}'.format(finalWeight, finalBias, finalError, self.prediction(self.xs[1], finalWeight, finalBias), self.results[1], self.prediction(self.xs[3], finalWeight, finalBias), self.results[3]))

然后我们在构造函数下面实现predictioncost函数,如您刚刚所学,如下面的代码所示:

    # Python implementation
    def prediction(self, x, weight, bias):
        return weight * x + bias

    # The cost function implemented in python
    def cost(self, results, weight, bias, xs):
        error = 0.0
        numberOfDataPoints = len(xs)
        for i in range(numberOfDataPoints):
            error += (results[i] - (weight * xs[i] + bias)) ** 2
        return error / numberOfDataPoints

之后,我们添加优化的权重和偏差函数,如以下代码所示:

    # Python implementation, returns the optimized weight and bias for that step
    def optimizeWeightBias(self, results, weight, bias, xs, learningRate):
        weightDerivative = 0
        biasDerivative = 0
        numberOfDataPoints = len(results)
        for i in range(numberOfDataPoints):
            weightDerivative += -2 * xs[i] * (results[i] - (xs[i] * weight + bias))
            biasDerivative += -2 * (results[i] - (xs[i] * weight + bias))

        weight -= (weightDerivative / numberOfDataPoints) * learningRate
        bias -= (biasDerivative / numberOfDataPoints) * learningRate

        return weight, bias

最后,我们通过创建train函数来完成代码,并在类的范围之外初始化该类,如下面的代码所示:

    # Python implementation
    def train(self, results, weight, bias, xs, learningRate, iterations):
        error = 0
        for i in range(iterations):
            weight, bias = self.optimizeWeightBias(results, weight, bias, xs, learningRate)
            error = self.cost(results, weight, bias, xs)
            print("Iteration: {}, weight: {:.4f}, bias: {:.4f}, error: {:.2f}".format(i, weight, bias, error))
        return weight, bias

# Initialize the class
LinearRegression()

如您所见,我们已经创建了一个 Python 类,它在构造函数中运行train函数。如果您不熟悉 Python,也不用担心;你只需要明白代码正在训练我们的线性回归算法来进行更精确的计算。创建一个名为linearRegression.py的文件,并在那里编写代码。然后,您可以使用以下命令行运行它:

python linearRegression.py

你会看到程序通过一小步一小步地向最小值前进,不断地减少误差,直到它到达一个没有太大改进的点。没关系:我们期望它做出精确的预测,但没有 100%的准确性。然后你可以用最终的权重和偏差来为你自己的机器学习模型做预测。

让我们来看看智能合约市场,看看用户将如何与之互动。我们的目标是提供一个地方,机器学习开发人员可以在以太坊中付费上传他们的模型,目的是从几个卖家那里获得解决方案,根据错误或买家的选择从中选择一个赢家。让我们来看看下面的代码:

pragma solidity 0.5.5;

contract MachineLearningMarketplace {}

我们可以开始添加变量来创建我们想要的应用程序,如下面的代码所示:

pragma solidity 0.5.5;

contract MachineLearningMarketplace {
    event AddedJob(uint256 indexed id, uint256 indexed timestamp);
    event AddedResult(uint256 indexed id, uint256 indexed timestamp, address indexed sender);
    event SelectedWinner(uint256 indexed id, uint256 indexed timestamp, address indexed winner, uint256 trainedIdSelected);

    struct Model {
        uint256 id;
        string datasetUrl;
        uint256 weight;
        uint256 bias;
        uint256 payment;
        uint256 timestamp;
        address payable owner;
        bool isOpen;
    }
    mapping(uint256 => Model) public models;
    mapping(uint256 => Model[]) public trainedModels;
    uint256 public latestId;
}

我们添加了三个事件来通知用户添加了新的作业或结果,以及何时选择了建议的获胜者。这样,当人们的提议更新时,他们会得到通知。然后我们有一个名为Model的结构,它表示我们想要的线性回归 ML 模型,包括数据集、权重、偏差和支付,以及其他重要变量。最后,我们添加了几个映射来对买家创建的模型(那些付费训练模型的人)和卖家创建的模型进行排序,卖家从数据集训练模型,并上传特定的权重和偏差,以便在买家选中他们时获胜。latestId是一个标识符,表示哪个型号是最新的。

开放的模型意味着它仍在运行,因此您可以发送建议并参与其中,以获得被选中的机会。如果它是关闭的,你将能参加,但是知道你将不能赢,因为获胜者已经被选择了。

让我们继续讨论 ML 市场的三个最重要的功能。上传作业功能如下所示:

/// @notice To upload a model in order to train it
/// @param _dataSetUrl The url with the json containing the array of data
function uploadJob(string memory _dataSetUrl) public payable {
    require(msg.value > 0, 'You must send some ether to get your model trained');
    Model memory m = Model(latestId, _dataSetUrl, 0, 0, msg.value, now, msg.sender, true);
    models[latestId] = m;
    emit AddedJob(latestId, now);
    latestId += 1;
}

这是上传结果函数,添加了一些文档来阐明其中使用的参数:

/// @notice To upload the result of a trained model
/// @param _id The id of the trained model
/// @param _weight The final trained weight, it must be with 10 decimals meaning that 1 weight is 1e10 so that you can do smaller fractions such as 0.01 which would be 1e8 or 100000000
/// @param _bias The final trained bias, it must be with 10 decimals as the weight
function uploadResult(uint256 _id, uint256 _weight, uint256 _bias) public {
    Model memory m = Model(_id, models[_id].datasetUrl, _weight, _bias, models[_id].payment, now, msg.sender, true);
    trainedModels[_id].push(m);
    emit AddedResult(_id, now, msg.sender);
}

最后,这里是 choose results 函数,它相当长,因为我们必须确保该职务是公开的,并且还没有选出获胜者。如果三天后仍未选出获奖者,第一个申请者将获得奖励,以避免损失乙醚:

/// @notice To choose a winner by the sender
/// @param _id The id of the model
/// @param _arrayIdSelected The array index of the selected winner
function chooseResult(uint256 _id, uint256 _arrayIdSelected) public {
    Model memory m = models[_id];
    Model[] memory t = trainedModels[_id];
    require(m.isOpen, 'The job must be open to choose a result');
    // If 3 days have passed the winner will be the first one, otherwise the owner is allowed to choose a winner before 3 full days
    if(now - m.timestamp < 3 days) {
        require(msg.sender == m.owner, 'Only the owner can select the winner');
        t[_arrayIdSelected].owner.transfer(m.payment);
        models[_id].isOpen = false;
        emit SelectedWinner(_id, now, t[_arrayIdSelected].owner, t[_arrayIdSelected].id);
    } else {
        // If there's more than one result, send it to the first
        if(t.length > 0) {
            t[0].owner.transfer(m.payment);
            emit SelectedWinner(_id, now, t[0].owner, t[0].id);
        } else {
            // Send it to the owner if none applied to the job
            m.owner.transfer(m.payment);
            emit SelectedWinner(_id, now, msg.sender, 0);
        }
        models[_id].isOpen = false;
    }
}

买家将使用uploadJob功能来发布他们的数据集和付款,以便让世界各地的参与者训练他们的模型。销售者将使用uploadResult函数来获取关于某个作业的信息,以训练指定的数据集,直到误差最小化。最后,chooseResult函数是购买者用来为一个确定的工作选择一个优胜者提案的函数。工作的创建者有三天时间来选择一个获胜的方案。如果三天后没有人申请,那么付款将退还给业主。如果有参与者,但所有者没有选择一个赢家,奖励将发送给第一个参与者,以补偿他们的速度;在这种情况下,该功能必须由外部用户来执行支付。

这些是让我们的 ML 市场运作的主要组成部分;然而,我们需要一些功能来帮助人们与它交互。以下是 ML marketplace 中添加的新功能,可帮助您更好地理解它们。

首先,我们用完整的文档创建成本函数,以便我们可以理解它在做什么:

/// @notice The cost function implemented in solidity
/// @param _results The resulting uint256 for a particular data element
/// @param _weight The weight of the trained model
/// @param _bias The bias of the trained model
/// @param _xs The independent variable for our trained model to test the prediction
/// @return int256 Returns the total error of the model
function cost(int256[] memory _results, int256 _weight, int256 _bias, int256[] memory _xs) public pure returns(int256) {
    require(_results.length == _xs.length, 'There must be the same number of _results than _xs values');
    int256 error = 0; // Notice the int instead of uint since we want negative values too
    uint256 numberOfDataPoints = _xs.length;
    for(uint256 i = 0; i < numberOfDataPoints; i++) {
        error += (_results[i] - (_weight * _xs[i] + _bias)) * (_results[i] - (_weight * _xs[i] + _bias));
    }
    return error / int256(numberOfDataPoints);
}

然后我们有 get model 函数来检索 struct 模型中包含的变量,因为我们现在不能返回 struct。我们必须使用这些技巧来独立地获取结构值。该函数如以下代码所示:

/// @notice To get a model dataset, payment and timestamp
/// @param id The id of the model to get the dataset, payment and timestamp
/// @return Returns the dataset string url, payment and timestamp
function getModel(uint256 id) public view returns(string memory, uint256, uint256) {
    return (models[id].datasetUrl, models[id].payment, models[id].timestamp);
}

然后,我们添加另一个 getter 函数,为我们提供特定 ID 的所有训练模型,如下面的代码所示。这对于那些想知道他们的特定工作得到了什么建议的卖家来说很有用。如果我们要在 dApp 中实现这个机器学习市场,我们必须为作业和其他映射添加一些 getters:

/// @notice To get all the proposed trained models for a particular id
/// @param _id The id of the model created by the buyer
/// @return uint256[], uint256[], uint256[], uint256[], address[] Returns all those trained models separated in arrays containing ids, weights, biases, timestamps and owners
function getAllTrainedModels(uint256 _id) public view returns(uint256[] memory, uint256[] memory, uint256[] memory, uint256[] memory, address[] memory) {
    uint256[] memory ids;
    uint256[] memory weights;
    uint256[] memory biases;
    uint256[] memory timestamps;
    address[] memory owners;
    for(uint256 i = 0; i < trainedModels[_id].length; i++) {
        Model memory m = trainedModels[_id][i];
        ids[i] = m.id;
        weights[i] = m.weight;
        biases[i] = m.bias;
        timestamps[i] = m.timestamp;
        owners[i] = m.owner;
    }
    return (ids, weights, biases, timestamps, owners);
}

我们有一个cost函数来快速验证由提议的卖家上传的结果,一个getModel函数将主要由想要获得关于模型的更具体信息的卖家使用,还有一个getAllTrainedModels函数返回特定工作的参与者。注意我们是如何返回结构中最重要的变量,而不是整个结构。我们这样做的原因很简单,我们还不能在 Solidity 中返回结构,所以我们必须分离每个变量并为每个变量返回一个数组。

这个市场的一般工作流程如下:

  1. 使用机器学习模型进行训练的买家使用uploadJob功能将他们的数据集和付款上传到市场。
  2. 生成一个AddedJob事件,通知有兴趣加入这个市场的用户新的工作。他们可以通过使用 web3 或外部 dApps 来监听这些事件,因为该合同是开源的。
  3. 销售者读取模型数据——特别是时间戳,因为这是最重要的信息——使用从事件中收到的id模型的getModel函数。然后,他们开始使用我们之前构建的 Python 应用程序或他们自己的应用程序来训练模型,因为有许多不同的方法可以训练线性回归算法。
  4. 他们使用uploadResult功能将训练好的体重和偏差上传到该工作中,作为新的建议。这将触发AddedResult事件,该事件将通知买家他们是否正在收听更新,以便他们可以选择获胜者。
  5. 在工作创建后的三天内,采购员检查建议,将每个建议产生的误差与cost函数或他们自己的实现进行比较。他们几乎肯定会选择误差最小的结果,尽管他们可以选择任何他们想要的结果。选择一个后,模型的状态将变为isOpen = false,这意味着获胜者被选中,并且SelectedWinner事件被触发。

就是这样!现在,您可以在区块链上上传和训练线性回归模型。

摘要

在本章中,您学习了结合区块链和 ML 的基本效用,因为它们几乎是对立的,这意味着它们可以很好地互补以创建最佳的安全性和性能。我们从 ML 的一般解释开始,以便您可以通过快速查看生成和训练机器学习模型的过程来理解所有的宣传。然后,我们更深入地研究应用程序的技术功能,这样你就能清楚地看到机器学习和区块链的结合点。最后,我们建立了机器学习市场,因为它是两种技术的伟大结合。您看到了线性回归算法如何一步一步地在 Python 和 Solidity 中实现。我们建立了市场,来自世界各地的用户在这里为每项任务训练和交换计算资源,创造了一个伟大的安全开源平台,人们在这里互动,没有审查,费用或集中化。

在下一章中,我们将探索与本章类似的高级以太坊实现,但涉及不同的行业,从一个基于区块链的社交媒体平台开始,该平台将去中心化与互联网上的社交互动相结合。


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组