TowardsDataScience 博客中文翻译 2016~2018(一百四十一)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

构建用于自然语言处理的卷积神经网络

原文:https://towardsdatascience.com/how-to-build-a-gated-convolutional-neural-network-gcnn-for-natural-language-processing-nlp-5ba3ee730bfb?source=collection_archive---------2-----------------------

如何从零开始建立一个门控卷积神经网络(GCNN),用 Pytorch 实现

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

具有 LSTM 或 GRU 单元的递归神经网络(RNNs)是 NLP 研究人员的首选工具,并在许多不同的 NLP 任务上提供最先进的结果,包括语言建模(LM)、神经机器翻译(NMT)、情感分析等。然而,RNNs 的一个主要缺点是众所周知的训练速度慢,所以过去几年的努力都集中在试图加快它们的速度上。有多种方法可以做到这一点,包括使用预训练模型的,使用更快的 softmax ,以及使用不同的架构,如卷积神经网络(CNN),准递归神经网络(qrns,或变压器架构。在这篇博文中,我将告诉你来自脸书人工智能研究(FAIR)小组的最新研究,他们首次能够使用 CNN 获得语言建模任务的最先进结果。我将解释他们的方法,然后带您一步一步地完成我写的 Pytorch 实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

RNNs are inherently sequential. Image from https://vimeo.com/238222385

您可能知道,当给定一个单词序列作为输入时,语言建模包括预测序列中的下一个单词。rnn 较慢一个主要原因是输入字符串中的每个单词必须顺序处理:在句子“并行化很有趣”中,单词“并行化”和“是”必须在单词“有趣”之前处理。相比之下,CNN 中的所有元素都是同时处理的,这可以极大地加快处理速度。之前使用 CNN 进行语言建模的尝试明显不如 RNNs 的结果,但在最近一篇名为“带门控卷积网络的语言建模”的论文中,Dauphin 等人(2016)构建了一个卷积语言模型,其产生的结果与 RNNs 具有竞争力,并可以大幅降低计算成本。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CNNs can process information in parallel. Image from https://vimeo.com/238222385

快速提醒一下信息如何通过 RNN 语言模型将会很有帮助。模型的输入是由大小为[ seq_length,emb_sz ]的单词嵌入 X 表示的单词序列,其中 seq_length 是序列长度, emb_sz 是你的嵌入的维度。在 X 通过多个 LSTM 层之后,最终层的输出是大小为[ seq_length,c_out ]的隐藏状态表示 H ,其中 c_out 是该最终层的维度。然后,softmax 步骤为序列中的每一步生成对整个词汇表的预测。

Dauphin 等人的 CNN 类似地将大小为[ seq_length,emb_sz ]的嵌入激活作为输入,但随后使用多层门控卷积来产生最终的隐藏状态输出 H (也是大小为[ seq_length,c_out )。每层包括 1)产生两个独立卷积输出的卷积块,以及 2)使用一个卷积输出来选通另一个卷积输出的选通块。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Figure 1 from Dauphin, et al. (2016), showing GCNN architecture.

卷积块对输入执行“因果卷积”(对于第一层,其大小为[ seq_length,emb_sz ])。正常卷积的窗口宽度为 k ,以当前时间步长为中心(因此包括来自未来和过去时间步长的输入),而因果卷积的窗口仅与当前和先前时间步长重叠(见下图)。这可以通过简单地计算一个输入的正常卷积来实现,该输入已经用左边的 k -1 个元素进行了零填充。因果卷积是必要的,因为如果 CNN 能够从它试图预测的未来时间步长中“看到”信息,那就是欺骗。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

TOP: Normal convolution. The convolutional window (width k=3) is centered on the current timestep. BOTTOM: Causal convolution. The input is zero-padded on the left with k-1=2 elements.

如上所述,卷积模块实际上会产生两个独立的卷积输出, AB ,其中一个将通过逐元素乘法来选通另一个。第一卷积输出被计算为 A = XW+b* 。因为 X 是一个大小为[ seq_length,emb_sz ]的张量,所以在用零进行左填充之后,它的大小将为[ seq_length+k-1,emb_sz ]。w 是大小为[ k,in_c,out_c ]的卷积滤波器,其中 in_c 是输入通道数(这里等于第一层的 emb_sz ),而 out_c 是输出通道数。 b 是长度 out_c 的偏置向量。在应用卷积之后,输出 A 将是 size [ seq_length,out_c ]。最后,对 B= XV+c* …执行相同的步骤,即使用不同的卷积滤波器 V 和偏置向量 c 产生第二个输出 B

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Gated Linear Unit (GLU), with residual skip connection. A convolutional block with window k=3 produces two convolutional outputs, A and B. A is element-wise multiplied with sigmoid(B), and the residual is added to the output. Image modified from https://vimeo.com/238222385

卷积块的两个输出 AB 然后被传递到门控块。在这一点上,提醒一下 CNN 在图像处理方面发生了什么是很有帮助的…卷积输出 A 可能会通过一个激活机制,如 ReLU:、A . k . A .【X * W+b】。然而,对于 GCNN 的门控块,Dauphin 等人使用了一种他们称之为“门控线性单元”(GLU)的机制,这种机制涉及到将 A 乘以 sigmoid(B ):

乙状窦

或者相当于,

(XW+b)乙状结肠(XV+c)

这里, B 包含“门”,它控制着从 A 到下一层的信息传递。从概念上讲,门控机制很重要,因为它允许选择对预测下一个单词很重要的单词或特征,并提供了一种学习和传递相关信息的机制。类似于 ReLU,门控机制也为层提供非线性能力,同时在反向传播期间为梯度提供线性路径(从而减少消失梯度问题)。

Dauphin 等人还为每一层增加了一个残差 skip 连接(见上图),类似于 ResNet 架构。剩余连接最小化了消失梯度问题,允许网络被建得更深(更多层)。该层的输入( X )在被卷积输出 B. 选通之后,被添加到卷积输出 A

X + (A乙状结肠(B) )

或者相当于,

X + ( (XW+b)乙状结肠(XV+c) )

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Bottleneck structure of a visual processing ResNet layer [Figure 5 from He, et al. (2016)]. A k=1 convolution reduces dimensionality from 256 to 64, then a k=3 convolution is performed in the lower dimensional space, then a k=1 convolution increases the dimensionality back to 256.

Dauphin 等人有时在层内使用瓶颈结构,也类似于 ResNet 架构。瓶颈结构的目的是通过首先降低数据的维度来降低卷积运算的计算成本。假设您想要对大小为[seq _ length= 70,in _ c= 600]的输入执行一次 k =4 卷积,输出通道有 600 个。这通常需要大小为[4,600,600]的卷积权重矩阵。相反,瓶颈层首先将数据的维度减少到例如[70,30],然后在更低维度的空间中执行 k =4 卷积,然后最终将数据的维度增加回[70,600]。这种方法只需要[4,30,30]的卷积权重矩阵大小,这在计算上更有效。瓶颈通过使用 k=1 卷积来增加和减少维度(参见下面的 Pytorch 实现)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Stacking multiple layers, and left-padding the output of each layer. Image from https://vimeo.com/238222385

不管一个层中是否使用了瓶颈结构,GCNN 层都可以堆叠,确保在每一层都左填充前一层的输出。每个层的输入将是 size[seq _ length+k-1,in_c ],每个层的输出将是 size [ seq_length,out_c ]。Dauphin 等人将这些层中的至少 5 层堆叠在一起,以产生最终的隐藏状态输出 h。然后,他们使用自适应 softmax 从语言模型的词汇表中的巨大单词列表中选择要选择的单词(查看我的博客帖子如何将耗时的 softmax 步骤加速高达 1000%)。

Pytorch 实现

经过一番搜索,我没有找到一个可以工作的 Pytorch 实现,所以我决定从头构建一个。查看随附的 Jupyter 笔记本以便跟随。对于预处理,你将需要 fastai ,一个运行在 Pytorch 之上的深度学习库,它简化了神经网络的训练。【对于那些想学习最先进的深度学习技术的人,我强烈推荐杰瑞米·霍华德的 fast.ai 课程,在线免费提供】。我决定使用 Wikitext-2 数据集,这是一个相对较小的数据集,包含大约 200 万个标记和大约 3.3 万个词汇,是本文中使用的较大 Wikitext-103 数据集的子集。

如论文中所述,我将数据分成段落。为了有一个标准化的序列长度,我删除了少于 10 个单词或多于 300 个单词的段落。一旦数据被正确格式化为 csv 文件,fastai 就可以很容易地对其进行快速标记和数值化。我创建了定制的数据集、采样器和整理函数,它们可以将数据正确地格式化到数据加载器中。每个迷你批次随机采样序列长度大致相同的段落。我还下载了 GloVe 预训练单词向量,用于模型的单词嵌入。最后,我创建了一个处理训练的 modeler 类。在训练期间,梯度裁剪允许使用大的学习率(lr=1),它与内斯特罗夫动量和权重归一化一起帮助训练快速收敛。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

The first few layers of the architecture.

如上所述,迷你批次之间的序列长度不同,但在本文的其余部分,假设 seq_length 等于 70。在嵌入和调整大小之后,第一层的输入 x 是 size [ bs=50,emb_sz= 300 ,seq_length= 70,1],其中 bs 是批量大小, emb_sz 是嵌入维数。我使用一个卷积核大小为 4 的 k ,所以在用 k -1=3 个元素对输入进行左填充后,输入大小为[50,300,73,1]。

我在架构的每一层都使用了瓶颈结构。在第一层,(用 self.inlayer = GLUblock(k,emb_sz,nh,downbot) 调用),我产生两个卷积输出, x1x2 。为了产生 x1 ,第一个 k=1 卷积将输入 x 的维数从[50,300,73,1]降低到[50,15,73,1]。然后,在这个低维空间中执行 k =4 卷积,以产生大小为[50,15,70,1]的输出。最后,一个 k =1 卷积将 x1 的维数增加到大小[50,600,70,1]。同样的三个步骤用于产生 x2 ,然后通过一个 sigmoid 并用于通过逐元素乘法选通 x1 ,以产生输出 x (大小也是[50,600,70,1])。通过使用 k =1 卷积,将残差*(在块开始时设置为等于 x )从大小【50,300,70,1】转换为大小【50,600,70,1】,并添加到输出 x 。*

接下来,我将数据 x 通过 4 个门控卷积层。每层都有一个大小为[50,600,70,1]的输入,并产生相同大小的输出。由于输入和输出的维数是相同的(即 in_c==out_c ,所以可以简单地复制残差并将其搁置,直到块结束。如上所述,输入被左填充为大小[50,600,73,1],产生瓶颈卷积输出 x1x2 (大小均为[50,600,70,1]),并且在添加残差以产生该层的输出之前, x1sigmoid(x2) 选通。经过 4 个卷积层后,最终输出通过自适应 softmax 产生模型预测。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Perplexity=76.6 on epoch 49.

我在 Wikitext-2 数据集上运行了这个网络 50 个时期,在第 49 个时期产生了 4.34 的最小损失(困惑=76.6)。这与 Dauphin 等人的结果相比相当不错,他们在更大的 Wikitext-103 数据集上实现了 37.2 的最终困惑度。通过增加模型容量(更多的层,每层更多的隐藏单元),通过训练更长的时间,通过在瓶颈层中使用不太积极的维度减少,以及通过使用更大的数据集(例如 Wikitext-103),我的模型的性能可能更接近于论文中发现的性能。

* [## 用门控卷积网络进行语言建模

vimeo.com](https://vimeo.com/238222385)

Dauphin 等人的结果与最先进的模型具有竞争力,要么接近匹配(Google 十亿字数据集),要么超过(Wikitext-103)基于 LSTM 的模型的最佳发布结果。此外,他们的方法大大降低了计算成本。与配备完整 softmax 的 LSTM 模型相比,他们使用的自适应 softmax 将训练速度提高了约 500%,因此达到给定的困惑值所需的操作明显减少。由于卷积方法可在序列和序列内的标记上并行化,因此 GCNN 模型将处理单个句子的延迟降低了一个数量级*。

Dauphin 等人的论文是许多最近的出版物之一,这些出版物完全放弃了用于 NLP 任务的 rnn,而是依赖于纯粹的注意力卷积结构。诸如准递归神经网络( QRNN )的其他方法部分地放弃了递归,并且将训练速度提高了一个数量级。看到这些新方法达到最先进的结果,同时大幅降低计算成本,真是令人兴奋!

*然而,作者指出,由于 cuDNN 的 GPU 加速功能针对 LSTM 进行了优化,而不是针对 GCNN 所需的卷积类型,因此处理文本语料库所需的总时间在 RNN 和 GCNN 方法之间仍然相似。对 cuDNN 卷积实现的改进可能会为 GCNN 带来更好的计算性能。

参考文献:

布拉德伯里,詹姆斯,等。“准递归神经网络。”arXiv 预印本 arXiv:1611.01576 (2016)。

Dauphin,Yann .等人,《门控卷积网络的语言建模》arXiv 预印本 arXiv:1612.08083 (2016)。

盖林、乔纳斯等人《卷积序列到序列学习》arXiv 预印本 arXiv:1705.03122 (2017)。

何,,等.“深度残差学习在图像识别中的应用”IEEE 计算机视觉和模式识别会议录。2016.

瓦斯瓦尼、阿希什等人,“你所需要的只是关注。”神经信息处理系统进展。2017.*

如何使用 Tensorflow 对象检测 Api 构建手势控制的网络游戏

原文:https://towardsdatascience.com/how-to-build-a-gesture-controlled-web-based-game-using-tensorflow-object-detection-api-587fb7e0f907?source=collection_archive---------4-----------------------

通过在网络摄像头前挥动你的手来控制游戏手柄。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 TensorFlow 对象检测 api,我们已经看到了模型被训练来检测图像中的自定义对象的例子(例如,检测玩具浣熊mac n cheese )。自然,下一个有趣的步骤是探索如何在真实世界用例中部署这些模型——例如,交互设计

在这篇文章中,我将介绍一个基本的身体作为输入交互示例,其中来自手部跟踪模型(作为输入的网络摄像头流)的实时结果被映射到基于网络的游戏(Skyfall)的控件上。该系统演示了如何集成一个相当精确的轻量级手部检测模型来跟踪玩家的手部,并实现实时的身体作为输入交互。

想试试吗?项目代码可在 Github 上获得。

[## 维克托迪亚/天崩地裂

skyfall -使用 Tensorflow 对象检测 Api 的手势控制网络游戏

github.com](https://github.com/victordibia/skyfall)

始终可用(Body as)输入

使用人体部分作为输入的好处是始终可用,因为用户不需要携带任何辅助设备。重要的是,利用人体部分进行基于手势的交互已经被证明可以改善用户体验[2]和整体参与度[1]。虽然身体作为输入的想法并不是全新的,但利用计算机视觉、可穿戴设备和传感器(kinect、wii、[5])等的现有方法有时会遇到准确性挑战,不总是可移植的,并且与第三方软件集成也很困难。轻量级深度神经网络(DNNs)的进展,特别是对象检测(见[3])和关键点提取(见[4])的模型,有望解决这些问题,并进一步实现始终可用(身体作为)输入的目标。这些模型使我们能够使用 2D 图像以良好的精确度跟踪人体,并具有与一系列应用程序和设备(桌面、网络、移动设备)轻松集成的优势。虽然从 2D 图像中进行追踪并不能给我们提供太多的深度信息,但它在构建互动方面仍然有着惊人的价值,正如 Skyfall 游戏示例中所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Body as input on a large display. Results from a realtime object detection model (applied to webcam feed) are mapped to the controls of a game.

游戏机制

Skyfall 是一个简单的基于网络的游戏,使用 2D 的物理引擎 planck.js 创建。天崩地裂的玩法很简单。3 种类型的球从屏幕顶部随机落下——白色球(值 10 分)、绿色球(值 10 分)和红色球(值-10 分)。球员通过移动球拍抓住好球(白色和绿色球)并避开坏球(红色球)来得分。在下面的例子中,玩家可以通过移动鼠标或在移动设备上触摸(拖动)来控制球拍。

Try out a mouse controlled version of SkyFall: Catch good balls (white, green), avoid bad balls (red) by moving the mouse. Press space bar to pause game and key s to toggle sound. View on Codepen here.

这是一个非常简单有趣的游戏。然而,我们可以让用户用他们的身体(手)来控制球拍,这样会更吸引人。目标是通过使用网络摄像头视频流检测手的位置来实现这一点——不需要额外的传感器或可穿戴设备。

添加基于手势的交互

为了增加手势交互,我们用一个将玩家手的运动映射到游戏手柄位置的系统来代替上面的鼠标控制。在当前的实现中,python 应用程序(app.py)使用 TensorFlow 对象检测 api 检测玩家的手,并通过 websockets 将手的坐标传输到游戏界面——一个使用 FLASK 服务的 web 应用程序。

手检测/跟踪

[## 如何在 Tensorflow 上使用神经网络(SSD)构建实时手部检测器

这篇文章记录了使用 Tensorflow(对象检测 API)训练手部检测器的步骤和脚本。我在……

towardsdatascience.com](/how-to-build-a-real-time-hand-detector-using-neural-networks-ssd-on-tensorflow-d6bac0e4b2ce)

之前的一篇文章中,我介绍了如何使用 Tensorflow 对象检测 api 构建一个实时手部检测器。请查看的博文,了解更多关于手部跟踪模型是如何构建的。有关加载手模型的任何错误或问题,请参见手跟踪 Github repo问题。本例采用了类似的方法,多线程 python 应用程序读取网络摄像头视频,并为检测到的每只手输出边界框。

注意,手检测是在逐帧的基础上完成的,并且系统不会跨帧自动跟踪手。然而,这种类型的帧间跟踪是有用的,因为它可以实现多用户交互,我们需要跨帧跟踪手(想象一群朋友挥舞着他们的手或其他一些共同的对象,每个人都控制着自己的球拍)。为此,当前的实现包括基于朴素欧几里德距离的跟踪,其中在跨帧的相似位置看到的手被分配相同的 id。

一旦帧中的每只手都被检测到(并且分配了跟踪 id),手的坐标就被发送到网络套接字服务器,该服务器将它发送到连接的客户端。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Additional hands, additional paddles … potential for some engaging multiplayer games with friends. A tracking id of 0 is assigned to one hand and 1 to the other. These are then mapped to the paddles on the game interface.

游戏界面

游戏接口连接到网络套接字服务器并监听手部检测数据。每个检测到的手用于生成一个球拍,并且手在视频帧中的坐标用于在游戏屏幕上相对定位球拍。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Realtime detected hand coordinates (center of bounding boxes) are mapped to paddle positions as controls.

后续步骤

当前的实现有一些限制——所以贡献,拉请求是最受欢迎的!

手部检测器改进 这需要收集额外的训练数据并利用数据增强策略来改进手部检测器。这很重要,因为整个交互(和用户体验)依赖于准确和鲁棒的手部跟踪(误报、漏报会导致糟糕的 UX)。

帧间跟踪

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当前的实现使用简单的基于欧几里德的度量来跨帧跟踪手(当前帧中的手基于其与先前帧中的手的距离来识别)。如果有几只手重叠,事情会变得复杂——需要一种更鲁棒的跟踪算法。也许集成了 OpenCV 或其他来源的快速跟踪算法…

Tensorflowjs 实现
用一个 TensorFlowjs 实现进行一些实验,它允许整个交互完全在浏览器中原型化!!!Tensorflowjs 带来了许多好处——易于部署(无需 python 或 Tensorflow 安装),无需 websocket 服务器和客户端,易于复制,更多潜在用户…

更新…Tensorflow.js 浏览器实现如何可用。你可以在 CodePen 上试试这里的,完整源代码如下:

[## victordibia/handtrack.js

一个用于直接在浏览器中原型化实时手部检测(边界框)的库。- victordibia/handtrack.js

github.com](https://github.com/victordibia/handtrack.js/)

结论

一些相关的作品。

有一些现有的项目在设计交互时应用了机器学习模型。一个常见的例子是使用 LSTMs 自动完成文本并创建快速电子邮件回复(参见 Google 的智能回复论文),以及最近的实验,其中图像分类模型被用作游戏控制(参见 Google Pair 的可教机器和其他 Tensorflowjs 演示)。这项工作通过探索在创建交互中使用对象检测模型来建立这些趋势。

最后的想法

随着人工智能算法的不断成熟(准确性、速度),有可能利用这些进步来建立更好的交互。这可以是为用户定制内容或预测其预期交互的生成界面、支持基于视觉的新型交互的模型、对话式 UI 等。在这些领域中,越来越重要的是研究这种交互的机制,严格测试这些交互,并创建设计模式,告知如何在交互设计中使用人工智能模型作为一等公民。

收到反馈、评论,想要合作吗?随时联系——Twitter, linkedin

注:Skyfall 的早期版本于 2018 年 1 月提交给英伟达杰特森挑战赛。

参考

[1]d . m . Shafer,c . p . Carbonara 和 Popova,L. 2011 年。作为基于运动的视频游戏享受的预测因素的空间存在和感知现实。存在:遥操作者和虚拟环境20(6)591–619。

[2]m . Birk 和 r . l . mandrik,2013 年。控制你的游戏自我:游戏中控制者类型对快乐、动机和个性的影响。关于计算系统中人的因素的 SIGCHI 会议记录—CHI ’ 13685–694。

[3]黄,j .,拉特霍德,v .,孙,c .,朱,m .,科拉迪卡拉,a .,法蒂,a .,菲舍尔,I .,沃伊纳,z .,宋,y .,s .,墨菲,K. 2017。现代卷积目标探测器的速度/精度权衡 CVPR

[4]曹,z .,西蒙,t .,魏,s-e,和谢赫,Y. 2016。使用局部相似场的实时多人 2D 姿态估计。

[5]c .哈里森、d .谭和 d .莫里斯,2010 年。Skinput:将身体作为输入表面。第 28 届计算机系统中人的因素国际会议论文集—中国’10453。

我如何使用 Flask 构建一个基于 REST 端点的计算机视觉任务

原文:https://towardsdatascience.com/how-to-build-a-machine-learning-as-a-rails-developer-739cc57ea01c?source=collection_archive---------2-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是我熟悉计算机视觉和机器学习技术的过程的后续。作为一名 web 开发人员(理解为“rails 开发人员”),我发现这个不断发展的领域令人兴奋,但是不要每天都使用这些技术。这是探索这一领域的两年旅程的第三个月。如果你还没有阅读,你可以在这里看到第一部分:从 webdev 到计算机视觉和地理第二部分:两个月探索深度学习和计算机视觉

总体思路

Rails 开发人员擅长毫不费力地快速构建 web 应用程序。在支架、清晰的模型-视图-控制器逻辑和大量的 ruby gems 之间,具有复杂逻辑的 Rails 应用程序可以在很短的时间内完成。例如,我会毫不犹豫地构建一些需要用户帐户、文件上传和各种数据源的东西。我甚至可以用优秀的文档使它高度可测试。在 Devise、Carrierwave(或许多其他文件上传 gem)、Sidekiq 和所有其他可访问的 gem 之间,我将在 15 分钟内在 Heroku 上启动并运行。

现在,加上一个计算机视觉或机器学习任务,我就不知道该去哪里了。即使我在探索这个领域,除了单词联想或图像分析,我仍然很难找到机器学习概念(神经网络和深度学习)的实际应用。也就是说,有趣的想法(我还没有找到实际应用)是围绕趋势检测和生成对抗网络。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Google search for “how to train a neural network”

作为一名软件工程师,我发现很难理解机器学习在我构建的应用程序中的实用价值。有很多关于模型(机器学习意义上,而不是 web 应用程序/数据库意义上)、神经网络架构和研究的文章,但我没有看到像我这样的 web 开发人员的实际应用。因此,我决定将我已经思考了一段时间的项目的一小部分构建出来。

该项目旨在检测 Instagram 上的优秀涂鸦。最初的想法是使用机器学习来限定“好的涂鸦”看起来是什么样的,然后运行机器学习模型来检测和收集图像。从概念上来说,这个想法听起来很棒,但我不知道如何“训练一个机器学习模型”,我对从哪里开始没有什么感觉。

我开始构建项目的一个简单部分,因为我知道我需要在好的涂鸦上“训练”我的“模型”。我挑选了一些优秀涂鸦艺术家的 Instagram 账户,我知道我可以在那里找到高质量的图像。在抓取 Instagram 账户(由于 Instagram 的 API 限制,这比预期的时间长得多)并分析图片后,我意识到手头的一个大问题。所选的账户很棒,但有许多非涂鸦的图像,主要是人。为了得到“好的涂鸦”图像,我首先需要过滤出人的图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

The application I built to crawl Instagram created a frontend that displayed graffiti.

通过查看这些图片,我发现每 10 张图片中有多达 4 张是一个人的或者里面有一个人。结果,在开始“训练”一个“好涂鸦”的“模型”之前,我只需要得到一组不包含任何人的图片。

(对非机器学习人员的补充说明:我在某些单词周围使用引号,因为你和我可能对这些单词的实际意思有相同的理解。)

与其有一个复杂的机器学习应用程序来完成一些复杂的神经网络-深度学习-人工智能-随机梯度下降-线性回归-贝叶斯机器学习魔法,我决定简化这个项目,建立一些检测照片中的人并标记他们的东西。我意识到,我之前读过的许多机器学习教程的例子都向我展示了如何做到这一点,所以问题是要让那些教程真正有用。

应用程序(带有代码链接)

我将 Ruby on Rails 用于管理数据库和呈现内容的 web 应用程序。我使用 Ruby 通过 Redis 库 Sidekiq 完成了 Instagram 的大部分图片抓取。这使得运行延迟任务变得容易。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

The PyImageSearch article used as reference is great and can be found at https://www.pyimagesearch.com/2017/09/11/object-detection-with-deep-learning-and-opencv/

对于机器学习逻辑,我有一个使用 OpenCV 进行对象检测的代码示例,来自 PyImageSearch.com 教程的。该代码示例并不完整,因为它检测了定型图像模型中 30 个不同项目中的一个,其中一个是人,并在检测到的对象周围画了一个框。在我的例子中,我稍微修改了这个例子,并把它放在一个基于 Flask 的简单 web 应用程序中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Link to Github: The main magic of the app

我制作了一个 Flask 应用程序,其端点接受带有图像 URL 的 JSON blob。应用程序下载了图像 URL,并通过在检测到的对象周围绘制一个边界框的代码示例对其进行处理。我只关心检测人的代码示例,所以我创建了一个基本条件,为检测人给出特定的响应,为其他所有事情给出一般的响应。

这个简单的端点就是机器学习的魔力。可悲的是,这也是我第一次看到复杂的机器学习“东西”如何与 web 应用程序的其余部分集成的实际可用的例子。

对于那些感兴趣的人来说,这些代码如下。

[## 记住 lenny/Flask-Person 探测器

基于 Flask 的 web 应用程序,使用 OpenCV 的深度神经网络提供了一个 REST 端点…

github.com](https://github.com/rememberlenny/Flask-Person-Detector)

总结体会

我很惊讶我以前没有见过一个简单的基于 Flask 的深度神经网络的实现。我还觉得基于这种实现,当不涉及训练模型时,将机器学习应用到任何应用程序中就像拥有一个具有有用功能的库一样。我假设在未来,模型和利用模型的库的分离将会被简化,类似于一个库如何被“导入”或者使用捆绑器添加。我的猜测是这些工具中的一些是存在的,但是我还没有深入了解它们。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

https://www.tensorflow.org/serving/

通过回顾如何访问对象检测逻辑,我发现了一些似乎相关的服务,但最终并不是我所需要的。具体来说,有一个名为 Tensorflow Serving 的工具,它似乎应该是 Tensorflow 的一个简单的 web 服务器,但还不够简单。这可能是我所需要的,但是拥有一个单独运行 Tensorflow 的服务器或 web 应用程序的想法很难实现。

基于 Web 服务的机器学习

我在网上找到的很多机器学习的例子都是非常完备的例子。示例从问题开始,然后提供在本地运行示例的代码。通常,图像是由 file path 通过命令行界面提供的输入,而输出是 python 生成的窗口,显示经过处理的图像。作为一个 web 应用程序,这不是很有用,所以创建一个 REST 端点似乎是一个基本的下一步。

将机器学习逻辑构建到 REST 端点中并不难,但是需要考虑一些事情。在我的例子中,服务器运行在一台台式计算机上,有足够的 CPU 和内存来快速处理请求。情况可能并不总是这样,所以未来的端点可能需要使用 Redis 之类的东西异步运行任务。这里的 HTTP 请求很可能会挂起并超时,因此对于慢速查询,需要考虑一些基本的微服务逻辑。

二元期望和机器学习品牌

最终应用程序的一个大问题是,处理后的涂鸦图像有时会被错误地标记为人。当这幅画包含看起来像一个人的特征时,例如一张脸或身体,物体分类器错误地标记了这幅画。相反,有时人的照片没有正确地将图像标记为包含人。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

[GRAFFITI ONLY] List of images that were noted to not have people. Note the images with the backs of people.

Web 应用程序需要二元结论来采取行动。图像分类器将提供关于检测到的物体是否存在的百分比等级。在较大的对象检测模型中,分类器将有一个以上的对象被推荐为可能被检测到。例如,照片中有 90%的可能性是一个人,有 76%的可能性是一架飞机,有 43%的可能性是一只巨大的香蕉。当处理响应的应用程序只需要知道某些东西是否存在时,这不是很有用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

[PEOPLE ONLY] List of images that were classified as people. Note the last one is a giant mural with features of a face.

这带来了质量在任何基于机器学习的过程中的重要性。假设很少有对象分类器或基于图像的过程是 100%正确的,那么 API 的质量是很难衡量的。当谈到这些对象分类器 API 的商业实现时,服务的品牌将在很大程度上受到少数请求的边缘情况的影响。因为机器学习本身是如此不透明,服务提供商的品牌在确定这些服务的可信度方面将更加重要。

相反,由于机器学习任务的质量差异如此之大,一个品牌可能很难向用户展示其价值。当解决机器学习任务的二进制质量与美元金额挂钩时,例如每个 API 请求,免费做某事的能力将很有吸引力。从价格的角度来看,滚动自己的免费对象分类器会比使用第三方服务更好。在成为明显优于自托管实现的首选之前,品牌机器学习服务市场还有很长的路要走。

物体分类的特异性非常重要

最后,当涉及到任何机器学习任务时,特异性是你的朋友。具体来说,当谈到涂鸦时,很难界定形式各异的东西。涂鸦本身是一个包含大量视觉作品的类别。甚至一个人可能很难界定什么是涂鸦,什么不是涂鸦。与检测人脸或水果相比,类别的特异性非常重要。

WordNet 和 ImageNet 的卓越之处在于类别特异性的力量。通过用词和词与词之间的关系对世界进行分类,有一种方法可以证明图像的相似性和差异性。例如,鸽子是一种鸟,但不同于鹰。与此同时,它与飞机或蜜蜂完全不同。这些事物之间的关系允许对它们进行清楚的分类。涂鸦中不存在这种特异性,但需要这种特异性来适当地改进对象分类器。

最终决赛

总的来说,这个应用程序运行良好,非常有帮助。这消除了更多关于机器学习和图像识别服务如何工作的神秘。正如我上面提到的,这个过程也让我更加意识到这些服务的不足和这个领域尚未定义的地方。我绝对认为这是所有软件工程师都应该学会的。在可用的工具变得简单易用之前,我想复杂的生态系统将会有一个很好的导航期。类似于网络标准形成之前的浏览器战争,机器学习提供商之间将会有很多争夺市场份额的竞争。你已经可以在亚马逊、谷歌和苹果等大公司的服务之间看到这一点。在软硬件层面,这一点在 Nvidia 的 CUDA 和 AMD 的价格吸引力之间也非常明显。

更多即将推出!

如何使用马尔可夫链和 Python 构建市场模拟器

原文:https://towardsdatascience.com/how-to-build-a-market-simulator-using-markov-chains-and-python-7923256f8d29?source=collection_archive---------4-----------------------

建模客户行为,可视化 A/B 测试结果,预测用户指标…所有这些都使用一个简单的马尔可夫框架!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

final product

在本文中,我的目标是向您介绍(不考虑您的技术能力)马尔可夫链,并使用它来模拟客户行为。

这不会是一个传统的每两行代码片段的“如何做”教程。我写这篇文章的主要目的是为您提供一个可以灵活使用的概念框架,这样您就不必为了学习新东西而编码了。技术细节会不时出现,但我会尽可能地为他们提供直觉。

数据处理

在这次分析中,我将使用我在一次数据马拉松中获得的 Credit Sesame 的分析数据。你可以使用任何你感兴趣的时间范围内的用户数据(例如一周/月/年的数据)。它应该遵循类似于下面的结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

example data

动作的例子可以是“点击报价/广告”、“点击订阅”等。列也可以是其他指标,如页面浏览量或收入。包括任何你认为对你的建模计划有用的专栏——在我的例子中,就是用户参与。

客户细分

在您的数据集中选择一个特定的日期,并获取该特定日期的新用户数据。我正在模拟新用户在使用 Credit Sesame 网站 30 天内的行为。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

new user data on a particular day

接下来,我们将客户分为不同的类别或状态。有许多方法可以做到这一点。

  1. 您可以应用一个评分函数:给每个用户一个分数,表示他们的整体参与度。您可以为您认为影响更高用户参与度的操作(如“会话长度”)赋予更高的权重。
    然后,您可以根据自己的经验将分布划分为 3 个部分(不活跃、活跃和非常活跃)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Distribution of applied score function

2.应用无监督的算法,比如 k-means:你可以使用聚类算法,比如 k-means,来对参与度相似的客户进行聚类。每个集群都有自己独特的属性,希望这些属性就是您想要建模的属性。您甚至可以将该算法应用于您之前计算的得分函数(单变量数据),使其更加简单。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

K-means segments visualized on the score function

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

after segmentation on first-day data. 1 = inactive user, 2 = active user, 3 = very active user.

分割第一天的数据后,你选择一个时间框架。我选择了一个月,因为我相信 Credit Sesame 有很多回头客,一个月的数据可以捕捉到他们的数量。30 天后,用户将有机会在各个细分市场之间转换:非常活跃的用户可能会变得不活跃,中度活跃的用户可能会变得非常活跃,等等。

对此 30 天后的数据进行分段。确保您考虑了时间框架(例如,平均 30 天的参与度分数)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

segmentation applied to 30-day data

让我们来看看结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如预期的那样,在 30 天内变得不活跃的用户数量上升,而保持活跃和非常活跃的用户数量下降。

应用马尔可夫框架

马尔可夫链基础

马尔可夫链是简单的数学系统,它使用一定的概率规则和固定的假设来模拟状态到状态的运动。

更简单地说,当您有一个具有固定状态(或段)的系统,并且代理/用户可以以一定的固定概率在这些状态之间移动时,您可以使用马尔可夫链对其进行建模。

但是让我们首先看看我们的系统是否满足马尔可夫模型的假设:

  • 假设 1: 有一组有限的状态。在我们的系统中,只有 3 类客户可以进出。
  • 假设 2: 状态间移动的概率是固定的。我承认,这是一个很强的假设。虽然我的系统确实考虑了成千上万的用户数据点,但很容易相信不同的 30 天时间框架的概率方差不应太大。但是,即使有大量的数据,正如我们将在本文后面看到的,我们也必须谨慎。
  • **假设三:**状态可达性。任何细分市场中的用户都可以移动到不同的细分市场,而不受任何外部限制。
  • 假设 4: 非循环。在我们的系统中,片段到片段的移动决不是“自动的”,所以这个假设是成立的。

我们的系统对马尔可夫链的大多数假设表现良好。这给了我们对模型估计的一些信心,一旦我们建立了模型,我们就会得到这些。

构建马尔可夫链

马尔可夫链有三个部分,最好用矩阵向量乘法来表示。如果你对线性代数完全陌生,我建议你在阅读本文之前浏览一下这个链接

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

N represents the number of segments

我们系统中的初始状态是一个 3×1 向量,表示每个分段中的用户数量。结束状态也是一个 3x1 向量,显示第一个月之后每个细分市场中的用户数量(在我们将初始状态向量乘以概率矩阵之后)。转移概率矩阵是一个 3×3 的矩阵,代表了不同客户群之间的固定转移概率。

那么我们如何计算这些固定概率呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从我们记录的片段移动来看。我们观察第一天每个细分市场的用户在 30 天后如何移动到不同的细分市场,并相应地计算概率(相当于比例)。

0.89(图中)指的是第一天第一个细分市场中的某个人在 30 天后仍在同一细分市场中的概率,即不活跃用户在 30 天后仍不活跃的概率。请注意,每列中的概率总和必须为 1。我们对所有细分市场重复这一过程,并构建最终转换矩阵:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Deconstructing the transition matrix for a clearer understanding

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

different way to visualize the transition matrix

最后,我们把它们组合成这个漂亮的形式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在我们已经建模了,我们可以开始有趣的事情了。

绘图结果

给定马尔可夫链,让我们从绘制客户流动趋势开始。

a_val,b_val,c_val = [],[],[]# initial state
init_state = np.array([188969, 81356, 14210])# transition matrix
a = np.array([[ 0.89, 0.75 ,0.49], [ 0.10, 0.22 ,0.44], [ 0.01, 0.03 ,0.07]])for x in range(10):
    a_val.append(init_state[0])
    b_val.append(init_state[1])
    c_val.append(init_state[2])
    b = init_state
    init_state = a.dot(b)# plotting
plt.figure(figsize=(11,8))
plt.plot( [x for x in range(10)], a_val, marker='o', markerfacecolor='blue', markersize=12, color='skyblue', linewidth=4,label='Inactive users')
plt.plot( [x for x in range(10)], b_val, marker='o', markerfacecolor='red', markersize=12, color='pink', linewidth=4,label='Active users')
plt.plot( [x for x in range(10)], c_val, marker='o', markerfacecolor='orange', markersize=12, color='yellow', linewidth=4,label='Very active users')
plt.legend(loc='best')
plt.xlabel('Months')
plt.ylabel('Number of customers')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

output

从中我们可以推断出什么?

我们看到,大约 2 个月后,所有三个细分市场都达到了平衡,也就是说,虽然有用户从一个细分市场转移到另一个细分市场,但每个细分市场的客户总数保持不变。

但是有一个条件。我们在这里做了一个非常强有力的假设——我们相信,无论我们预测用户行为的未来有多远,概率都将是固定的。这是非常不可信的。我举个例子:假设你拥有一个销售冬装的网站。在 12 月份购买了几件外套的活跃用户不太可能在 7 月份遵循同样的购物模式。模式可能是季节性的,而我们的马尔可夫模型没有捕捉到这一点!

那我们该怎么办?

我们可以为每个月计算不同的转换矩阵来捕捉这些趋势,也可以将我们的分析范围限制在一个月。由于我只有一个月的数据,我选择后者。如果您确实有更长时间的数据,我会鼓励您将真实数据结果与马尔可夫模型输出进行比较,看看转移概率是否在几个月内保持不变,并相应地进行调整。

我得到的结果如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Single month analysis of customer flow

应用:

A/B 测试

既然您已经有了一个描述客户行为的模型,我们如何引入“变化”或“干预”,并对它们的影响建模呢?我们的马尔可夫模型识别了两种类型的变化:

1。初始状态的改变:

假设你拥有一家披萨店,你的网站在 12 月 1 日提供买二送一的优惠。你怀疑这种变化会带来更多的活跃用户访问你的网站。当你有 1000 个不同的新访客时,你的假设被证实了,其中有 300 人在同一天真的买了披萨(非常活跃的用户)。你又惊又喜,心想“嗯,我习惯了一天接到 100 个订单:一个月后会怎么样呢?”你只需要调整马尔可夫链的初始状态来解释这种变化。

# CHANGES TO INITIAL STATE - NUMBERS RECEIVED FROM MATRIX VECTOR PRODUCTbefore = (855,130,15) # Without any change
after = (509,389,102) # After placing deal# create plot
plt.figure(figsize=(11,8))
index = np.arange(3)
bar_width = 0.35
opacity = 0.8

rects1 = plt.bar(index, before, bar_width,
                 alpha=opacity,
                 color='b',
                 label='Without deal')

rects2 = plt.bar(index + bar_width, after, bar_width,
                 alpha=opacity,
                 color='g',
                 label='With deal')

plt.xlabel('Segments')
plt.ylabel('Number of Users')
plt.title('Customer behavior after a month')
plt.xticks(index + bar_width- 0.18, ('Inactive','Active','Very Active'))
plt.legend()

plt.tight_layout()
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从初始状态由(不活跃=500,活跃=400,非常活跃=100)变为(100,600,300),我们得到上面的结果。在月初有更多活跃和非常活跃的用户,你就增加了在这个月中拥有更多回头客的机会。

2.转移概率的变化:

你意识到这些交易很好,但它们只影响初始状态,这些交易的好处不会持续几个月,即它们是扩大参与用户的短期解决方案。为了有更持久的变化,我们必须引入改变概率的行动。假设你完全重新设计了你的披萨外卖网站,使结账速度提高了 10 倍。这是一种会持续影响每个细分市场**中用户数量的行动。**为了说明这种类型的变化,我们改变了转移矩阵中的概率。
你可以通过在用户子样本上对新网站进行 A/B 测试来计算改变的概率,或者,你可以使用你自己的试探法和假设来手动改变它们。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

假设概率保持不变,我们可以看到客户流如何随着新的转移矩阵而变化。

预测其他指标

您可以预测其他几个用户指标,如客户终身价值、每用户收入等。使用我们上面描述的技术。例如,预测收入变化,计算每个细分市场的平均收入,计算输出状态,并将收入乘以各自细分市场中的用户数。

结论

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果你已经走了这么远,我推荐你。至少,我希望我已经传达了马尔可夫链的美妙之处,以及尽管它们很简单却能提供关键见解的能力(假设关键假设得到满足)。我希望你找到自己的创造性的方法来应用这个模型!

如何使用 LightFm 在 Python 中构建电影推荐系统

原文:https://towardsdatascience.com/how-to-build-a-movie-recommender-system-in-python-using-lightfm-8fa49d7cbe3b?source=collection_archive---------3-----------------------

在这篇博文中,我们将用 python 创建一个电影推荐系统,根据用户的观看历史向他们推荐新电影。在我们开始之前,让我们快速了解一下什么是推荐系统。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

courtesy: https://ghanatalksbusiness.com/use-ratings-carefully-performance-reviews/

推荐系统

你可能还不知道推荐系统的定义,但你肯定遇到过。这是因为推荐系统在互联网上无处不在。推荐系统的目的是根据用户的兴趣或使用历史向他们推荐一些东西。所以,下次亚马逊向你推荐一种产品,或者网飞向你推荐一个电视节目,或者在你的 feed 上展示一个很棒的帖子,要明白在引擎盖下有一个推荐系统在工作。

有两种类型的推荐系统。他们是

  1. 基于内容的推荐系统
  2. 协同推荐系统

基于内容的推荐系统

基于内容的推荐系统对用户产生的数据进行处理。数据可以显式生成(如点击赞),也可以隐式生成(如点击链接)。该数据将用于为用户创建用户简档,该用户简档包含用户交互的项目的元数据。它接收的数据越多,系统或引擎就变得越精确。

协同推荐系统

一个协作推荐系统根据相似用户对商品的喜欢程度做出推荐。这个系统会把爱好相似的用户分组。除了用户相似性之外,推荐系统还可以使用项目相似性来执行协同过滤(比如“喜欢这个项目 X 的用户也喜欢 Y”)。

大多数系统将是这两种方法的结合。

以免开始编码

电影推荐系统

首先,我们需要安装一些软件包。

LightFm

LightFM 是许多流行推荐算法的 Python 实现。LightFM 包括 BPR 和 WARP 排序损失的实现(损失函数是预测模型在预测预期结果方面表现如何的度量。).

**BPR:贝叶斯个性化排序成对损失:**最大化正例与随机选择的负例之间的预测差异。当只有积极的互动存在时,这是有用的。

**WARP:加权近似秩成对损失:**通过重复采样负样本直到找到违反秩的样本,最大化正样本的秩

LightFm 还包含大量与电影分级相关的数据集。我们将研究这个数据集。所以我们也将安装这个库。

pip 安装灯 fm

接下来,我们将安装两个数学运算包,即 numpyscipy

pip 安装数量

安装 scipy

我们将创建一个名为 recommender.py 的 python 文件。

我们可以从将库导入这个文件开始

import numpy as npfrom lightfm.datasets import fetch_movielensfrom lightfm import LightFM

fetch_movielens 方法是来自 lightfm 的方法,可用于获取电影数据。我们可以获取最低评级为 4 的电影数据。

data = fetch_movielens(min_rating = 4.0)

“数据”变量将包含电影数据,分为许多类别测试和训练。

在监督学习中,使用包含结果的训练数据集来训练机器。然后使用没有结果的测试数据集来预测结果。训练数据是用于构建模型的数据,测试数据是用于测试模型的数据。

我们可以通过打印这些数据来检查这一点

print(repr(data[‘train’]))
print(repr(data[‘test’]))

我们可以看到,训练数据量远大于测试数据量。这是因为通常当您将数据集分为定型集和测试集时,大部分数据都用于定型。

接下来,我们将创建一个带有“扭曲”损失函数的 lightfm 模型

model = LightFM(loss = ‘warp’)

现在,我们可以使用训练数据来训练这个模型,其中历元或迭代值为 30。

model.fit(data[‘train’], epochs=30, num_threads=2)

现在,让我们构建处理这些数据的函数,为任意数量的用户推荐电影。我们的函数将接受模型数据和一组用户标识

def sample_recommendation(model, data, user_ids):

在函数定义中,首先,我们需要得到所有用户和电影的数量。

n_users, n_items = data[‘train’].shape

接下来,我们需要迭代 user_ids。

for user_id in user_ids:

对于每个用户,我们需要找到他们喜欢的已知的正面或电影。这可以从我们掌握的数据中得到。

known_positives = data[‘item_labels’][data[‘train’].tocsr()[user_id].indices]

接下来,我们需要找到用户喜欢的电影。这可以通过 LightFm 的预测方法来完成。该函数的参数是 numpy arrange 的 user_idn_items 变量 arrange

scores = model.predict(user_id, np.arange(n_items))

我们现在可以根据最喜欢到最不喜欢的顺序对分数进行排序。

top_items = data[‘item_labels’][np.argsort(-scores)]

现在预测已经完成,我们可以打印前 3 个已知的阳性和 3 个预测。

print(“User %s” % user_id)print(“ Known positives:”)for x in known_positives[:3]:
 print(“ %s” % x)print(“ Recommended:”)for x in top_items[:3]:
 print(“ %s” % x)

这样,我们的样本 _ 推荐方法就变成了

def sample_recommendation(model, data, user_ids):
    n_users, n_items = data['train'].shape
    for user_id in user_ids:
        known_positives = data['item_labels'][data['train'].tocsr()                                    
                          [user_id].indices]

        scores = model.predict(user_id, np.arange(n_items))

        top_items = data['item_labels'][np.argsort(-scores)]

        print("User %s" % user_id)
        print("     Known positives:")

        for x in known_positives[:3]:
            print("        %s" % x)

        print("     Recommended:")

        for x in top_items[:3]:
            print("        %s" % x)

我们可以通过调用程序中的函数,提供三个随机的 user _ ids 来完成。

sample_recommendation(model, data, [3, 25, 451])

让我们通过命令在终端中运行代码

python recommender.py

我们得到了输出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

凉爽的…我们可以看到我们的系统向我们的用户推荐电影。

这是一个推荐系统可以多么容易实现的例子。希望你觉得有用。

代码可以在https://github.com/amkurian/movie-recommendation-system找到

如何建立一个新媒体公司

原文:https://towardsdatascience.com/how-to-build-a-new-media-company-ef3b10ba9b6f?source=collection_archive---------2-----------------------

互联网媒体公司正处于困难时期。旧的商业模式已经跟不上互联网和脸书、谷歌等内容聚合商带来的速度和竞争。由于内容聚合商及其广告服务平台的近乎垄断地位,内容制作商获得的广告销售额比例越来越小。鉴于我们对媒体行业面临的挑战的了解,下面我试着想象一下一家今天成立的媒体公司如何成为这个行业的主要参与者。

在线印刷媒体目前问题的根源在于,为了接触到用户,媒体被迫通过脸书和谷歌这样的内容聚合器来收集内容。在这样做的过程中,他们面临着日益激烈的竞争,因为用户体验被商品化了,许多类似的故事同时出现,并且“追加销售”用户阅读同一出版商的另一个故事变得更加困难。

简单来说,这个问题只有两个答案。一个是成为你的类别中最好的内容提供商,并试图在内容聚合平台上获得巨大的浏览量——最有可能的方式是简单地生产比其他生产商更多的内容。这是 Buzzfeed 选择的道路。这也是一场逐底竞争,因为问题的根源是内容聚合者控制了供求,而这一解决方案根本没有改变这一点。随着内容创作者开始最大化产出,成本上升,假设所有内容提供商都努力最大化产出,收入将基本保持不变。这种策略的受益者是内容聚合者,他们接收更多的内容来吸引更多的观众,这反过来又吸引了更多的内容提供商,并使该行业保持循环。

另一种选择是离开内容聚合器。看看目前在线印刷媒体的参与者,如果没有脸书、谷歌或苹果新闻,没有多少(如果有的话)可以吸引大量观众。原因是在线印刷媒体与离线印刷媒体仍然没有太大区别。一页上有文字,页边空白处有广告。基本上这个行业拿了一份报纸,扫描了一下,然后就退出了。如果媒体想要捍卫他们的利润,那么他们需要建造一条护城河。每个公司的护城河都可能不同,但每个公司的护城河都将基于技术,智能手机和人工智能是一个关键因素。

为了对即将到来的观点转变有一个正确的认识,让我们考虑一个成功的现代媒体公司的案例——Yelp。以为 Yelp 是一家科技公司?再想想。Yelp 是为现代世界设计的城市美食指南。Yelp 有几个关键的特点,使它与你可能仍然在星巴克或报纸 E5 页上找到的离线城市美食指南有很大的不同:

  1. 用户简档(对推荐有用)
  2. 公开评论/评论
  3. 不断更新菜单、开门/关门时间、收视率、位置等。
  4. 定位服务

因为 Yelp 不仅仅是页面上的文字和空白处的一些广告,它已经设法避免了聚合。Yelp 已经将被动阅读城市指南变成了一种动态体验,人们首先打开 Yelp,目标是找到吃饭的地方,但不是用户做所有的工作,而是一系列的给予和索取导致最终的结果。

Yelp 拥有海量数据,包括图片、餐厅排名、评论、餐厅位置信息等。最重要的是,这些数据最好以多种方式分享——创造一种全面的体验,这种体验很难在应用程序之外获得。换句话说,它不仅仅是一页纸上的文字。截至目前,脸书还没有办法将 Yelp 的数据和排名与其他数据和排名进行对比。如果你想要 Yelp 的体验,你必须访问他们的网站或打开他们的应用程序。这是 Yelp 最基本的护城河。一旦你打开应用程序,Yelp 就开始积累你喜欢什么的数据,以便他们能够提供更准确的推荐。这加强了 Yelp 的护城河。

相比之下,《华盛顿邮报》有一系列按类别分组的文章,这些类别是在报纸仍是主要用户接触点的时候定义的,最好通过写在纸上的文字来分享。过去,他们的护城河是由进入壁垒构成的,主要包括送货距离的物理限制以及运行印刷机的成本。同样,城市餐馆指南的护城河是当地的知识和印刷一本杂志的成本。Yelp 通过向用户评论、照片和评级开放平台,对当地的知识加倍投入,并用收集大量餐馆和用户数据的成本取代了印刷杂志的入门成本。

那么,如果《华盛顿邮报》今天才刚刚起步,他们应该如何着手建立一个可持续发展的企业呢?首先,他们需要一个让用户打开《华盛顿邮报》而不是脸书的理由。他们需要利用其内容的独特方面来为用户量身定制体验。

我会假装对《华盛顿邮报》比实际上更熟悉,并推荐一些他们可以创造出无法移植到其他平台的体验的方法。我将从进一步研究他们过去使用的护城河开始。上面我提到了进入壁垒,但我没有提到其他护城河,如本地知识、政府关系、高质量的编辑和专业摄影。在媒体行业,高质量的编辑和专业的摄影在很大程度上是可以想象的。这些是保护新进入者的护城河,但对于与已经建立的媒体竞争来说不是很有用。《华盛顿邮报》与《纽约时报》和《三藩市纪事报》等其他媒体不同的两个方面是对 DC 地区的了解和与政府的联系。

可以效仿 Yelp 的做法,为 DC 居民创建一个开源的、用户生成的内容平台,当地人可以在这个平台上讨论所有事件,从而创造出无与伦比的大量 DC 本地知识。就像 Yelp 一样,评论和帖子的编辑标准会下降,摄影质量也会下降。这个想法与 DC 的 Reddit 页面或带有 DC 过滤器的 Twitter 没有太大区别。

人工智能驱动的语法纠正、人工智能生成的推荐以及基于位置/用户兴趣的内容调用可以进一步推动内容生成。人工智能驱动的语法纠正是相当自明的,它保持了平台和编辑标准的可读性,同时允许更多的内容。人工智能生成的推荐可以想象为当你正在创建一个帖子时,一个弹出窗口会出现,其中有一个与你当前正在写的内容相关的主题和几个问题,以激发你的创造力。从技术角度来说,这仍然有一点距离,但它可以让像我们的华盛顿邮报这样的平台支持缺乏内容的领域,并指导贡献者创建更好、更全面的帖子。基于位置和用户兴趣的内容调用与下面讨论的用户使用数据相关,但通过利用应用使用数据,可以确定每个用户对哪些类别感兴趣和具有哪些专业知识。类似地,通过位置数据,可以知道每个用户经常去的位置。当讨论某个用户子集特别感兴趣的话题时,可以向子集的每个成员发送内容请求,从而提高内容的质量和数量。此外,上述每个功能都是为内容生成者服务的,并将它们保留在《华盛顿邮报》而不是其他平台上,类似于 Snapchat 和 Instagram 过滤器如何帮助用户留存。

由于联邦政府位于 DC,《华盛顿邮报》事实上肯定比非 DC 媒体有更好的政府关系。这可以用来管理政府法案、投票和其他关键信息的数据库,以及从内部关系中收集的笔记。该数据库可以开放为一个 API,供上述在《华盛顿邮报》应用程序中发帖的 DC 居民使用,或者作为一个按使用付费的 API,供需要政府数据的其他媒体使用。具体来说,我正在想象一个华盛顿邮报政府数据机器人,它可以监听平台上发布的内容,当一项法案或立法者的名字被提及时,它可以立即用数据库中的相关信息做出回应。

现在是护城河的新元素:位置和用户数据。通过应用程序的使用模式或直接的用户输入,《华盛顿邮报》应该能够确定每个用户经常光顾的城市地区,以及每个用户的家庭地址和工作地址。因此,当你的邻居发生犯罪时,你可以收到《邮报》的推送通知,并通过应用程序中的订阅源跟踪故事,实时更新并推送到你的手机上。当交通拥挤或你通常去上班的路线上有施工时,邮政可以提前提醒你,并帮助你计划一条新的路线,或者只是让你知道你可能想早点离开。当政府计划在你的公寓旁边建造一个新的地铁站时,猜猜会发生什么——来自邮局的警报,只需回复推送通知,向你的代表发送一封支持或反对建设的信。对于货币化努力来说,最重要的是,当你办公室旁边的餐厅推出新的午餐时间交易时,你可以收到《华盛顿邮报》的推送,里面有优惠券的二维码。

通过跟踪每个用户经常查看、张贴和评论的内容,可以确定每个用户的一些特征。最明显的是每个用户对哪个类别感兴趣。不太明显的是偏好的写作风格、语调、观点和对各种主题的理解程度。通过实验和对人工智能当前极限的一些推动,有可能首先开始根据用户偏好建议内容,最终将各种帖子融入为每个用户定制的定制文章中,在内容质量和用户理解方面具有无与伦比的精确度。由于文章的实时定制/生成以及与用户位置数据和使用数据的密切联系,华盛顿邮报提供的体验只能通过打开华盛顿邮报应用程序来获得。

总之,我们的新华盛顿邮报与今天的华盛顿邮报有很大不同。它雇佣很少真正的编辑或记者。相反,内容是众包和人工智能推动的。其实公司的核心是用户生成的数据和 AI。内容被分解成片段,钉在一起,以一种不可能在任何其他平台上复制的方式动态呈现,迫使观众访问《华盛顿邮报》的应用程序,而不是内容生成器。

应该指出的是,在这种商业模式中仍然有记者和记者的空间,但他们不是负责完整的故事,而是也会贡献一小部分内容——只是比其他用户更频繁。把它看作是一种搅拌锅的方式,也做更多的调查研究,普通人可能没有受过适当的训练。

我相信这个话题还有很多话要说。如果你有评论,就在下面留下,我们继续对话。

如何构建非地理地图#1

原文:https://towardsdatascience.com/how-to-build-a-non-geographical-map-1-8d3373e83d6c?source=collection_archive---------7-----------------------

或者如何映射相似性以进行偶然的数据探索(使用 Python)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

地图是数据可视化的强大设计对象。它们通常用于强调元素之间的空间关系和比较多个变量。在非地理数据的情况下,维度减少技术允许我们在二维空间中映射相似性,而不损害初始信息的丰富性。

🔗链接到#第二部分:如何将散点图变成交互式地图

#第 1 部分:降维和可视化

第一步。设置特征和初始尺寸

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

In a geographical map, each element’s coordinates are inherently defined by its position. An element’s position is made of three parameters: latitude, longitude and elevation. To represent locations on Earth, the geographic coordinate system uses either a spherical coordinate system (globe) or a cartesian coordinate system (flat map). To transform the Earth’s three-dimensional space into a two-dimensional map, we use map projection techniques, the most common of which is the Mercator’s cylindrical projection.

为了以类似地图的方式可视化您的非地理数据,您需要将每个元素(或地图上的点)视为由一组特定的特征(或属性)定义的。

这组要素在整个数据集中必须是常量。这意味着每个元素可以通过给相同的特征赋予不同的值来描述。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

This is what your dataset should look like.

要确定哪些属性最能描述您的数据,您可以问自己:

“是什么直观地让两个元素相似,又是什么把它们区分开来?”

在地图上,越靠近的两个元素越相似。距离越远,差异越大。

示例- 根据技能和知识相似性绘制职业图

在本例中,我想使用 ONET 开放式数据库,根据所需技能和知识的相似性来表示职位。

我非常幸运,因为这个数据集组织得非常好。所有的职业都由相同的一套技能(知识也是一样)来描述,并且每个职业都根据重要性(这项技能对完成工作有多重要)和级别(工作所需的专业知识水平)来细分。“数据值”栏指定各自的等级。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ONET skills.xlsx

我将每个技能和知识项目定义为一个单独的特性:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

This is what my dataset should look like after cleaning

由于原始数据集的质量,这一部分相当容易:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

📝>> Check out the full notebooks here

我现在只剩下一个矩阵,它的行向量是工作(n=640),列是技能和知识参数(p=134)。

在机器学习中,“维度”只是指数据集中的特征(或属性,或变量)的数量。

由于每个元素都是用 134 个参数来描述的,所以我们需要一个 134 维的空间来完整的表示它……很难描绘,不是吗?

让我们看看如何因式分解这个矩阵,以便将其表示为一个二维空间。

第二步。降低维度

我们想要创建一个较低维度的表示,在这里我们保留一些来自较高维度空间的结构。

本能地,我们希望在原始空间中彼此靠近的点在 2D 中彼此靠近。这同样适用于远点。

手工完成这项任务是不可能的。所以我四处寻找降维算法。

没有免费的午餐

在我们讨论它们之前,请注意没有一种算法适合所有问题。这个简单(但经常被遗忘)的事实被幽默地称为“没有免费的午餐”定理**。每个模型都依赖于一些假设来简化现实。但是在某些情况下,这些假设会失效。因此,它们产生了不准确的现实版本。**

因此,您必须根据数据的约束和问题的性质选择一些合适的算法。然后,尝试一下,看看哪一个最适合你。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Dimensionality reduction — Purpose & method (far from exhaustive)

例如,在我的项目中,我使用降维的唯一目的是数据可视化,而不是作为应用模式识别算法之前的预备步骤。另外,我想制作一张地图。因此,我对作为相似性代理的点之间距离的准确性感兴趣,但我并不真正关心新轴集的可解释性。

这就引出了特征提取算法

a .线性变换

  • 主成分分析

一种众所周知且常用的线性变换算法是主成分分析(PCA)。

在上面的矩阵中,每项工作由 134 个维度描述。很可能在某种程度上,列是线性相关的。当两列完全相关时,我只需要一列来描述另一列。为了在删除维度时降低新表示的不准确性,我最好保留描述数据集中大多数变化的维度。换句话说,去掉不必要的、嘈杂的维度,只保留最有信息量的维度。但是,降低 132 个维度已经很多了。

PCA 不是识别差异最大的维度,而是识别差异最大的方向,即数据最分散的地方。

这些方向被称为主成分(或特征向量),由初始维度的“组合”组成。因此,它们比任何单独的维度提供的信息都多,但与初始维度不同,它们不能被标上特定的特征(因此很难解释)。

PCA 找到一个由一对具有最大方差的正交(不相关)主分量构成的新坐标系**,并为数据集中的每个点分配新值以相应地定位它们。**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Illustration of what PCA does

本质上, PCA 保留了数据的全局结构,因为它以一种“一刀切”的方式找到了将为整个数据集产生最高方差的坐标系。它不考虑点相对于彼此的初始位置。相反,考虑到维数减少,它关注于尽可能地传播数据。

如果你很难想象我刚才解释的内容,看一看这个可怕的 PCA 互动可视化

好吧,我们来试试:

1/用 PCA 创建一组“新”特征

首先,您需要从 scikit-learn 库中导入 PCA。

from sklearn.decomposition import PCA

然后,设置n_components。如果你想建立一个二维坐标系统,你设置它,使 PCA 找到 2 个主成分。

pca = PCA(n_components=2)

使用fit_transform将模型与您的数据框架(X)相匹配,并应用维度缩减。

pc = pca.fit_transform(X)

PCA 分别沿着第一和第二主分量建立 640 对坐标的阵列。

2/用 Plotly 可视化

一旦你导入了 plotly…

from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplotinit_notebook_mode(connected=True)import plotly.graph_objs as go

…使用每对坐标的第一个和第二个元素分别作为 x 和 y,绘制散点图:

data = [go.Scatter(x=pc[:,0], y=pc[:,1], mode=’markers’)]

我的数据没有标记,所以检查其表示是否准确的唯一方法是将鼠标悬停在点上,并检查它们的邻居是否“有意义”。

See notebook (Github) >> See graph (Plotly)

如您所见,数据分布得很好,但似乎有很多“异常值”。请注意,我对该算法是否适用于该数据集的判断是高度主观的,取决于我自己对最终可视化的预期。

对于这个项目,我对保存数据的本地结构非常感兴趣。我希望数据集中相邻的聚类在降维后仍然是相邻的。但如上所述,这不是 PCA 的最佳资产。

b .非线性变换

非线性降维(NLDR)方法假设在高维空间中,数据具有一些位于嵌入式非线性流形上的底层低维结构。

我不是数学家,拓扑学是个相当复杂的领域,我就试着打个比方解释一下。

流形在每个点附近类似欧几里得空间。欧几里得空间是你在几何学中学习的最早的定理,如泰勒斯定理,适用的空间。二维流形包括曲面(它们类似于每个点附近的欧几里得平面)。

NLDR 假设,如果嵌入流形是二维的(即使数据是三维或多维的),那么数据也可以在二维空间上表示。

一个很好的方法是在你铺床之前看看你的羽绒被。想象你度过了一个糟糕的夜晚,你的床很乱。如果你要描述羽绒被上的每一点,你需要三个维度。但是羽绒被就像每个点附近的欧几里得平面。所以,可以展平成平面(二维)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Illustration of non-linear dimensionality reduction, from 3D to 2D

非线性算法擅长保持数据的局部结构,因为它们适应底层数据,对流形的不同区域执行不同的变换。

  • T-分布随机邻居嵌入(t-SNE)

TSNE 是最近流行的基于局部距离测量的非线性可视化方法,因此保留了近邻。然而,它不一定保留全局结构。这意味着它并不总是捕捉集群之间的距离。在我的项目中,这可能是一个问题。

如果你想更好地理解 t-SNE 是如何工作的:这篇文章解释了如何正确阅读 t-SNE 的结果

1/创建嵌入

就像对 PCA 所做的那样,从 scikit-learn 库中导入 SNE,将n_components设置为 2,并用fit_transform创建嵌入:

from sklearn.manifold import TSNEtsne = TSNE(n_components=2)embedding = tsne.fit_transform(X)

2/看看结果:

See notebook (Github) >> See graph (Plotly)

看起来已经很不错了。让我们试试最后一种方法。

  • 【均匀流形逼近与投影】(UMAP)

新来的 UMAP 试图在保持本地和全球距离之间取得平衡,这正是我所需要的。

UMAP 是相当容易使用的,有几个参数你可以调整(默认是相当好的):

  • n_neighbors:当试图学习数据的流形结构时,限制局部邻域的大小
  • n_components:表示目标空间的维数
  • min_dist:设置低维表示中点之间的最小距离
  • metric:控制如何在输入数据的环境空间(特征空间)中计算距离

更多细节,你可以在这里找到文档(链接到 UMAP)

1/创建嵌入

一旦导入了 UMAP,就可以创建嵌入并使用UMAPfit_transform 函数来创建最终的坐标数组(完整的笔记本在这里):

import umapembedding = umap.UMAP(n_neighbors=15, n_components=2, min_dist=0.3, metric=’correlation’).fit_transform(X.values)

2/可视化

分数的分配对我来说似乎很合理。集群在逻辑上相对于彼此定位:

See notebook (Github) >> See graph (Plotly)

包裹

谢谢你看完😃

到目前为止,我们已经构建了一个散点图,其坐标系本身并没有任何明显的意义,而是使用距离作为数据要素之间相似性的代理。可视化是非常基本的,但它展示了地图的前提。

接下来,我们将看到如何给它一个在外观和使用上都类似于地图的格式(链接到#第 2 部分)

与此同时,请随意分享您鼓舞人心的非地理地图和其他维度缩减方法。

🔗链接到#第 2 部分:如何将散点图变成交互式地图

👉看看我是如何在实践中使用它的:www.tailoredpath.com

📫让我知道你的想法:tailoredpath@gmail.com

如何构建 PostgreSQL 数据库来存储推文

原文:https://towardsdatascience.com/how-to-build-a-postgresql-database-to-store-tweets-1be9c1d48c7?source=collection_archive---------13-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

了解如何从 Twitter API 进行流式传输

人们每天都用 witter 来表达他们的感受或想法,尤其是关于此刻正在发生或刚刚发生的事情;由公司推广产品或服务;被记者用来评论事件或撰写新闻,这样的例子不胜枚举。毫无疑问,分析 Twitter 和 tweets 是一个强大的工具,可以让我们了解我们感兴趣的话题的舆论。

但是我们如何进行这种类型的分析呢?幸运的是,Twitter 为我们提供了一个 API(代表“应用编程接口”),我们可以通过它来创建一个应用程序,并以这种方式访问和过滤公共推文。

第一步是注册我们的 app 为此,我们需要访问 twitter 开发者网站,使用我们的 Twitter 帐户登录,并请求 Twitter 批准开发者身份(从 2018 年 7 月起,Twitter 改变了他们的政策,任何想要访问 Twitter API 和创建应用程序的人都需要申请一个开发者帐户,提供他们打算如何使用它的详细信息,并等待申请获得批准)。在我们收到批准后,我们可以继续创建一个新的应用程序,填写详细信息: Name (其他人没有使用过的 Twitter 应用程序的唯一名称)DescriptionWebsite (这应该是应用程序的主页,但我们也可以将我们的个人网站或 GitHub 存储库 URL。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Creating an app in Twitter Developer account

之后,我们需要创建我们的访问令牌。访问令牌将允许我们的 Twitter 应用程序读取 Twitter 信息,如推文、提及、朋友等:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于整个分析,我们将使用 JupyterLab 和 Python。因为任何拥有这些信息的人都可以使用它来授权应用程序连接到 Twitter,所以我们将创建一个 python 文件(。py),在这里我们可以存储消费者密钥消费者秘密OAuth 访问令牌OAuth 访问令牌秘密,然后在我们的主 Jupyter 笔记本文件中调用它。

现在我们已经注册了我们的应用程序,获得了我们的令牌/密钥,并将它们存储在一个单独的文件中,第二步是决定一旦我们获得它们,我们将在哪里存储我们的推文。我们应该将它们存储在一个文件中,一个 NoSQL 类型的数据库中,还是一个关系数据库?为了回答这个问题,我们需要了解我们从 twitter 应用程序中获得的信息是如何提供给我们的,以及我们想要用这些信息做什么。

Twitter API总是返回使用 JavaScript 对象符号 (JSON)编码的推文,这是一种非结构化和灵活的类型,基于具有描述对象的属性和关联值的键值对。每条推文包含作者、消息、唯一 ID、时间戳和发布时的创建日期等;每个用户都有一个姓名、id 和关注者数量。正因为如此,我们会立即想到将 tweets 存储在已构建的数据库管理系统(DBMS)中,如 MongoDB,这是一个被视为原生 JSON 数据库的开源数据库。这种类型的数据库的优点是,它们被设计为在使用动态模式时是敏捷的和可伸缩的,而无需首先定义结构。

另一方面,关系数据库更多的是与标准的一致性和可扩展性相关,因此,在如何存储数据上没有给我们自由。它们使用动态和静态模式,这有助于我们在数据是关系型的时候链接数据。相反,由于非结构化方法,我们不能在 MongoDB 中执行此操作。使用关系数据库的一些其他优点是,我们只需要改变其中一个表中的数据,然后它就会自我更新( 数据完整性 ),并且它们确保没有属性重复( 数据冗余 )。如前所述,关系数据库是以列和行的形式组织的,我们可以通过使用键来链接不同表中的信息,这些键唯一地标识表中的任何数据,并被其他表用来指向它们。

尽管 MongoDB 被设计得很快,对非结构化数据有很好的性能,但 PostgreSQL 等关系数据库在处理 JSON 时有很好的性能。这一事实以及结构化数据给我们带来的可能性导致我们使用关系数据库,特别是PostgreSQL 10(通常只作为 Postgres 提及),因为它是一个免费、可靠和高效的 SQL 数据库,最重要的是,它有一个非常强大和有用的 python API,称为 psycopg2 。为了以更友好的方式管理我们的数据库,我们还将使用 pgadmin 来创建一个带有用户名和密码的数据库,以保护我们的信息。同样,我们将把这些凭证存储在另一个 python 文件中,这样,一旦我们将主文件推送到 git 存储库,我们就可以对它们保密。

我们需要引入分析的最后一个组件是 Tweepy ,一个 python 库,它将成为我们代码中的主要参与者,将帮助我们访问 Twitter API、处理授权请求、捕获和流式传输 tweets 等,以及 json ,它将管理从 API 获得的 json 文件。

因此,我们需要在代码中做的第一件事是导入我们创建的所有库和文件:

之后,我们将定义一个函数,该函数将授权我们的应用程序连接到 Twitter API。OAuth 是一种开放的访问标准,主要由互联网用户用来授权应用程序或网站访问他们在其他网站上的信息,而无需向他们提供密码,而是允许批准的访问令牌访问由资源服务器托管的受保护资源。在 Twitter 的例子中,我们已经请求了令牌和密钥,所以我们将使用 Tweepy,这使得 OAuth 授权很容易用 tweepy 来处理。OAuthHandler 类。因此,首先,我们将consumer keyconsumer secret传递给这个函数,然后,我们将访问令牌设置为我们从 Twitter 获得的access tokenaccess token secret

正如我们以前讨论过的,关系数据库将信息存储在结构化的表中,所以下一步是决定数据库的模式。这里有一个很重要的概念需要我们考虑: 归一化。 规范化数据库需要一个根据某种范式构建关系数据库的过程,这种范式遵循减少数据冗余和提高数据完整性的目标。规范化数据库是这样一种数据库,即表之间的关系与数据之间真实存在的关系相匹配。简而言之,规则规定行中的唯一键和事件应该对此有所说明,与键无关的事实属于不同的表,并且表不应该暗示不存在的关系。

在我们的例子中,我们将创建两个表:第一个将包含关于 Twitter 用户的信息:user id,这将是我们的主键(对于每个记录都是唯一的键),以及user name。另一方面,我们将创建第二个表来存储关于 tweet 的信息:creation datetext(tweet 本身)、user id,这将是我们的外键(唯一标识另一个表的主键的键),将这个表与我们的用户表相关联,还有retweet count

我们将定义一个函数,一旦调用该函数,它将使用凭证连接到我们的数据库(使用命令 pyscopg2.connect ),并创建一个包含我们要搜索的术语名称的表。对于这最后一步,Postgres 为我们提供了设置一个 游标 的可能性,该游标封装查询并一次读取几行结果,而不是一次执行整个查询。因此,我们将利用这一点,创建一个游标(使用 database.cursor() ),然后执行我们的查询来创建用户表和包含 tweets 的表。这里我们需要考虑一些要点:当我们执行对CREATE TABLE的查询时,使用IF NOT EXISTS命令是很重要的,否则,Postgres 可能会出现表已经创建的错误,并会停止代码执行;我们需要澄清每一列包含哪种类型的变量(VARCHAR、TIMESTAMP 等。),哪一列是主键和外键,在这最后一种情况下,哪一列REFERENCES为;在我们执行完查询之后,一定要提交它( database.commit() ),否则,不会保存任何更改,并关闭到游标和数据库的连接。

之后,我们需要定义一个函数来帮助我们存储推文。这个函数将遵循我们用来创建表的相同逻辑(连接到数据库、创建游标、执行查询、提交查询、关闭连接),但是我们将使用[INSERT INTO](https://www.postgresql.org/docs/10/sql-insert.html)命令。当创建用户表时,我们声明user id将是我们的主键。因此,当我们存储推文时,我们需要小心如何将它插入表中。如果同一个用户有两条 tweets,第二次执行这个函数时,它会产生一个错误,因为它检测到特定的user id已经在表中,因为主键必须是惟一的。因此,我们可以在这里使用[ON CONFLICT](https://www.postgresql.org/docs/10/sql-insert.html)命令告诉 postgres,如果user id已经在表中,它就不必再次插入它。相反,tweet 将被插入到 tweets 表中,并被用户表中的那个user id引用。

有两种方法可以用 Tweepy 捕捉推文。第一个是使用 REST 搜索 API, tweepy。API,,搜索过去 7 天内发布的公共 tweets 样本。第二个是通过使用与 REST api 不同的 Twitter 流 api 来流式传输实时推文,这种方式从 Twitter 中提取数据,而流 api 将消息推送到持久会话。

为了在 tweepy 中传输 Tweepy,创建了类 的一个实例。stream建立流式会话,并向stream listener类的实例发送消息。在这个类中,有几个处理 tweets 的方法。根据我们想要获得的信息类型,我们需要覆盖不同的方法:如果我们只想要状态,那么我们将重载 on_status 方法。因为我们想要关于 tweet 的详细信息(创建日期、用户 id、用户名、转发次数),所以我们将重载负责接收所有消息并根据消息类型调用函数的 on_data 方法。

因此,我们将创建类 MyStreamListener ,它将继承自 tweepy。StreamListener 类,我们将覆盖 on_data 方法。我们将获取包含 tweet 的 json 文件( json.load(raw_data) )并解析它以将值存储在不同的变量中(例如:user_id = data[‘user’][‘id_str’]),然后将它们传递给函数以存储我们之前创建的 tweet。

重要是要小心此时可能发生的错误或异常。为此,我们将用一个 try/except 块将代码括起来,以便在发生异常时,它会被打印出来,这样我们就可以知道发生了什么。此外,连接到流式 API 的尝试次数有限,这将显示错误 420。我们可以通过重载 on_error 方法来处理这个错误,并在出现这个错误时断开 API。

那么,还剩下什么?我们需要创建一个 api ( tweepy。API() )之后,创建我们的流对象( tweepy.stream() ),传递我们已经创建的授权和监听器。我们将使用 过滤器 功能来流式传输所有包含感兴趣的单词( track = [‘word’] )并用英语编写的推文( languages = ['en] )

现在,是时候开始发送推文了!!我特别想知道人们对《复仇者联盟》是什么感觉。因此,我将使用“复仇者联盟”作为我的兴趣术语,并开始捕捉实时推文,以创建一个良好的数据库,这将有助于我稍后的情绪分析,您可以在此处阅读,并可视化 Twitter 与 Networkx 的交互,您可以在此处找到。你对什么感兴趣?

如何用 Go 构建一个简单的人工神经网络

原文:https://towardsdatascience.com/how-to-build-a-simple-artificial-neural-network-with-go-ac2e8c49ae37?source=collection_archive---------11-----------------------

从基础数学到用它识别笔迹的一步一步

我在职业生涯中写过很多计算机程序,大部分时间是为了解决各种问题或者执行一些任务(或者有时候只是为了好玩)。在大多数情况下,除了 bug 之外,只要我非常清楚地告诉计算机该做什么(无论我使用哪种编程语言),它就会乖乖地听从我的指令。

这是因为计算机程序非常擅长执行算法——遵循精确且经常重复的既定步骤和模式的指令。在大多数情况下,它们对我们处理数字运算或重复枯燥的工作很有帮助。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

The ENIAC was one of the first general-purpose, programmable computers ever made (public domain from https://commons.wikimedia.org/wiki/File:Eniac.jpg)

然而,计算机程序不擅长做的事情是那些没有很好定义的任务,并且不遵循精确的模式。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

In the 60s, Marvin Minsky assigned a couple of undergrads to spend the summer programming a computer to use a camera to identify objects in a scene. He figured they’d have the problem solved by the end of the summer. Half a century later, we’re still working on it. (from Explain XKCD — 1425: Tasks)

那么我们如何使用计算机来完成这样的任务呢?想想你如何完成这项任务。你可能在年轻的时候了解过鸟类,你被告知某些动物是鸟类,而某些动物不是,大部分是通过在现实生活中或通过图画书看到的。当你做错的时候,你会被告知并且记住。久而久之,你就有了一个心理模型,知道什么是鸟,什么不是。每次你看到一只鸟的某些部分(有爪的脚,有羽毛的翅膀,锋利的喙)你甚至不需要再看到整个动物,你会通过与你的心理模型进行比较来自动正确地识别它。

那么我们如何用计算机程序做到这一点呢?基本上我们做同样的事情。我们试图创建一个模型,通过试错过程,我们可以用它来比较输入。由于计算机程序都是数学,你可以猜到这将是我们将要谈论的一个数学模型

猜谜游戏

让我们举一个简单的例子,创建一个接受输入并试图预测输出的黑盒。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

A simple predictor

我们给它一个输入,然后从这个预测器得到输出。既然我们知道实际输出应该是什么,我们就可以知道预测输出与实际输出有多大的不同。实际输出和预测输出之间的差异成为误差

当然,如果预测器是静态的,不能改变,那就没什么意义了。当我们向预测器提供输入时,会产生一个带有错误的输出,这就是故事的结尾。不是很有用。

为了让我们的预测器更有用,让我们给它一个可配置的参数,我们可以用它来影响输出。因为它只有在没有错误的情况下才能正确预测,所以我们希望更改参数,以便随着我们不断向预测器提供数据,错误会缩小。目的是得到一个预测器,它在大多数情况下预测正确的输出,而实际上不需要给预测器明确的指令。

换句话说,这很像一个数字猜谜游戏。

让我们从更实际的角度来看这个问题。假设我们有一个带有简单数学公式o = i x c的预测器,其中o是输出,i是输入,c是可配置参数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

A simple predictor with a configurable parameter

我们还得到了一个给定输入的确认有效输出,即我们知道i是否为 10,o是否为 26。我们如何使用预测器找到c

首先,我们需要进行随机预测,假设c是 2。让我们输入 10,启动预测器。输出o为 20。由于误差e = t - o其中t是真值(或目标),这意味着e = 26 - 20 = 6。我们的误差e是 6,我们想达到 0,所以我们再试一次。

让我们把c设为 3。然后输出为30并且e现在为-4。哎呀,我们超过了!我们倒回去一点,让c为 2.5。这使得o为 25,而e为 1。最后,我们尝试将c设为 2.6,我们得到的误差e为 0!

一旦我们知道c是什么,我们就可以使用预测器来预测其他输入的输出。假设输入i现在是 20,那么我们可以预测o是 52。

正如你所看到的,这种方法试图迭代地寻找答案,并不断改进自己,直到我们找到最佳答案。这本质上就是机器学习是什么。计算机程序试图迭代地寻找答案,并通过它的错误“学习”,直到它获得一个可以产生最佳答案的模型。一旦它有了正确的模型,我们就可以使用该模型来正确地猜测答案。这非常类似于我们人类所做的(从过去的错误中学习并纠正自己),但我们具体是如何做的呢?

人类是如何做到的

让我们出去一点。我们谈了一点机器如何利用数学函数学习。人类如何做同样的事情(正如多年来的研究表明的那样)是使用一种叫做 神经元 的东西。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Drawing of neurons in the pigeon cerebellum, by Spanish neuroscientist Santiago Ramón y Cajal in 1899 (public domain from https://commons.wikimedia.org/wiki/File:PurkinjeCell.jpg)

神经元或神经细胞是一种接收信息、处理信息并通过电信号和化学信号进行传输的细胞。我们的大脑和脊髓(我们的中枢神经系统的一部分)由神经元组成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

A neuron with dendrites, a cell body and an axon

神经元由细胞体、树突和轴突组成,可以相互连接形成神经网络。在神经网络中,神经元的轴突连接到下一个神经元的树突,突触信号从一个神经元通过其轴突传输,并由下一个神经元通过其树突接收。轴突和树突之间的连接是突触。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Synapses are the connections between neurons

通过树突传入的信号根据突触连接的使用频率而加强或减弱,这些加强或减弱的信号在细胞体中汇集在一起。

如果接收到的汇集信号足够强,它将触发一个新的信号,通过轴突发送到其他神经元。

如你所见,神经元工作方式有点类似于我们之前的预测器。它通过树突处理大量输入,通过轴突输出。每个输入都与突触连接的强度(或权重)配对,而不是一个可配置的参数。

有了这些信息,让我们回到我们的预测器并做一些改变。

人工神经元

我们从建立一个模拟真实生物神经元的人工神经元开始。这个人工神经元是我们升级的预测器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

An artificial neuron mimicking a biological one

我们有一堆输入,而不是单个输入,每个输入都有一个权重(代替一个可配置的参数)。这些修改后的输入被累加并通过触发或激活功能传递,该功能确定是否应该发送输出。

那么,为什么会有激活功能呢(除了生物神经元的行为方式相同这一事实之外)?有几个很好的理由,但最重要的一个是激活函数将非线性引入网络。没有激活函数(或线性激活函数)的神经网络基本上只是一个线性回归模型,不能完成更复杂的任务,如语言翻译和图像分类。稍后您将看到非线性激活函数是如何实现反向传播的。

现在,我们将假设使用一个普通的激活函数, sigmoid 函数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Sigmoid function

关于这个函数值得注意的有趣的事情是,输出总是在 0 和 1 之间的范围内,但从来没有达到任何一个。

人工神经网络

就像我们有神经元形成神经网络一样,我们也可以将我们的人工神经元连接起来,形成人工神经网络。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Artificial neural network with 3 layers

现在看起来有点复杂了!

然而,我们只是将神经元堆叠在不同的层中。所有输入都通过输入层进入,输入层将其输出发送到隐藏层,隐藏层又将其输出发送到最终输出层。虽然来自每个节点的输出是相同的(只有一个输出),但是到下一层中神经元的连接被不同地加权。例如,隐藏层中第一个节点的输入将是(w11 x i1) + (w21 x i2)

用矩阵简化

如果我们必须一次计算一个,计算这个网络中的最终输出可能会有点乏味,特别是如果我们有很多神经元。幸运的是,有一个更简单的方法。如果我们将输入和权重表示为矩阵,我们可以使用矩阵运算来简化计算。事实上,我们不再需要做单个神经元的输入求和和输出激活,我们只需一层一层地做。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Using matrices

正如您将看到的,这将对代码的后续部分有很大帮助。

我们使用了矩阵点积来处理输入和权重的乘法和求和,但是对于激活函数,我们需要对每个矩阵元素应用 sigmoid 函数。我们将不得不对每个隐藏层和输出层做同样的操作。

调整重量

在这个时候,你可能会意识到,我们的神经网络(在概念上)只是神经元的一个更大的版本,因此非常像我们之前的预测器。就像我们的预测器一样,我们希望训练我们的神经网络,通过向它传递输入和已知输出来从它的错误中学习。然后利用已知和实际输出之间的差异(误差),我们改变权重以最小化误差。

然而,你可能会意识到,神经网络比我们的预测器要复杂得多。首先,我们有多层排列的多个神经元。因此,虽然我们知道最终目标输出,但我们不知道中间不同层的中间目标输出。第二,虽然我们的预测器是线性的,但是我们的神经元通过一个非线性的激活函数,所以输出是非线性的。那么我们如何改变不同连接的权重呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Weights and outputs in artificial neuron

我们从前面的预测器中知道,我们希望通过改变连接隐藏层和输出层之间的各种输出权重来最小化最终输出误差Ek

这很好,但是我们如何通过改变输入变量来最小化一个函数的值呢?

让我们从不同的角度来看这个问题。我们知道最后的输出误差Ek是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然而,仅仅从tk中减去ok并不是一个好主意,因为这通常会导致负数。如果我们试图找出网络的最终输出误差,我们实际上是将所有误差相加,因此如果其中一些误差是负数,就会导致错误的最终输出误差。一种常见的解决方案是使用平方误差,顾名思义就是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

同时我们知道:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

因此,我们知道(粗略地说),如果我们将Ekwjk对应起来,我们将得到一系列数值(蓝线)绘制在图表上(实际上这是一个多维图表,但为了保持我们的集体理智,我将使用一个二维图表):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Charting final output error to weights

如你所见,为了达到最小值Ek,我们沿着梯度或负梯度向下。换句话说,我们试图找到负梯度,相应地改变权重,然后再次找到负梯度,直到我们到达最小点Ek。这个算法叫做 梯度下降

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Gradient descent

你可能还记得中学微积分,为了找到函数中点的梯度,我们使用微分来得到函数的导数。这让我们能够发现我们需要调整wjk到什么程度。为了找到Ek的最小值,我们从wjk中减去这个量,并重复这样做。

让我们做数学。

为了计算输出权重wjk所需的变化,我们应该计算最终输出误差Ek相对于输出权重wjk的导数。这意味着:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这很好,但是我们如何使用其他变量得到我们的结果呢?为此我们需要使用 链式法则 :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这看起来稍微好一点,但是我们可以更进一步:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们开始工作吧。首先,我们需要找到Ek相对于最终输出ok的导数。

从前面,我们知道Ek是平方误差:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是为了更好的区分,我们把它缩小了一半(我知道这有点像作弊,但这让我们的生活更轻松):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它的导数是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这很简单!让我们看看最终输出ok相对于中间输出和权重sumk的乘积总和的导数。我们知道求和是通过一个 sigmoid 函数sig来得到最终输出ok:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

因此,最终输出ok相对于总和sumk的导数为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是因为我们知道 sigmoid 的导数是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我前面提到过,我们使用 sigmoid 函数是有充分理由的——简单微分就是其中之一!这一点的证明可以在这里找到。现在既然:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以将等式进一步简化为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后,我们想找到总和sumk相对于输出权重wjk的导数。我们知道总和是输出权重wjk和先前输出oj的乘积之和:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

因此,总和sumk相对于输出权重wjk的导数为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在我们有了所有的 3 个导数,让我们把它们放在一起。之前,我们说过:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

因此:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样我们就有了改变输出层权重的公式。隐藏层的权重是多少?我们简单地使用相同的等式,但是后退一层。该算法被称为 反向传播 ,因为它从最终输出反向计算权重。

但是等等。我们没有隐藏层的目标输出。那么我们如何得到隐藏层的误差呢?我们必须找到另一种方法。

反向传播误差

仔细想想,输出层的误差是由隐藏层的误差根据前一个隐藏层的连接造成的。换句话说,隐藏层的误差组合形成了输出层的误差。由于权重代表输入的重要性,它也代表误差的贡献。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Contribution of errors

因此,我们可以使用重量的比率来计算每个重量的变化。因为分母是常数,我们可以通过去掉分母来进一步简化。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Back propagating errors

现在,让我们看看如何使用矩阵从输出层反向传播误差。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一旦我们有了隐藏层的误差,我们就可以使用和以前一样的等式,但是用隐藏的输出误差代替最终的输出误差。

学习率

因此,人工神经网络通过使用梯度下降的反向传播进行学习。在梯度下降迭代过程中,经常很容易超调,这导致移动太快并跨过最小值wjk。为了防止这种情况,我们使用一个学习率 l来缩小我们想要为权重改变的量。这导致我们之前等式的改变:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

l通常是一个很小的值,因此我们在超过最小值时会更加小心,但也不能太小,否则训练时间会很长。有很多关于设定最佳学习率的研究文献。

偏见

对于我们当前的神经网络,激活函数是在 0.5 处穿过y的 s 形曲线。对权重的任何改变仅仅改变了 s 形的陡度。因此,神经元的触发方式是有限制的。例如,当x为 2 时,让 sigmoid 返回 0.1 的低值是不可能的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Sigmoid functions without bias

然而,如果我们给x加上一个 偏置 值,事情就完全变了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Sigmoid functions with bias

我们如何做到这一点是通过在神经网络中添加一个叫做偏置神经元的东西。这个偏置神经元总是输出 1.0,并且被添加到一个层,但是没有任何输入。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Artificial neural network with bias

不是所有的神经网络都需要偏向神经元。在我们稍后要写的简单神经网络中,我们不会使用任何偏向神经元(并且它工作得相当好)。

终于有代码了!

所以我们终于来了!在所有的概念和数学之后,我们现在要开始一些实现!

这篇文章中的代码片段并不完整,所以不要简单地从这里剪切和粘贴来运行它。这里的所有代码都可以在这个 Github 资源库中找到:

https://github.com/sausheong/gonn

与 Python 不同,Go 目前在机器学习的库方面没有太多支持。然而有一个非常有用的库叫做 Gonum ,它提供了我们最需要的东西——矩阵操作。

此外,虽然 Gonum 有非常好的包,但我认为 Gonum 中的一些怪癖使它变得不必要的冗长,所以我创建了自己的助手函数来克服它。

矩阵助手

我们将首先从助手函数开始。Gonum 用于矩阵操作的主包叫做mat。我们将使用的主要是mat.Matrix接口及其实现mat.Dense

mat包有一个怪癖,它要求我们在对矩阵执行操作之前,先创建一个包含正确行和列的新矩阵。对多个操作这样做相当烦人,所以我用自己的函数包装了每个函数。

例如,Gonum Product函数允许我们对两个矩阵执行点积运算,我创建了一个助手函数,它找出矩阵的大小,创建它并在返回结果矩阵之前执行运算。

这有助于节省大约 1-3 行代码,具体取决于操作。

func dot(m, n mat.Matrix) mat.Matrix {
	r, _ := m.Dims()
	_, c := n.Dims()
	o := mat.NewDense(r, c, nil)
	o.Product(m, n)
	return o
}

apply功能允许我们对矩阵应用函数。

func apply(fn func(i, j int, v float64) float64, m mat.Matrix) mat.Matrix {
	r, c := m.Dims()
	o := mat.NewDense(r, c, nil)
	o.Apply(fn, m)
	return o
}

scale功能允许我们缩放矩阵,即将矩阵乘以标量。

func scale(s float64, m mat.Matrix) mat.Matrix {
	r, c := m.Dims()
	o := mat.NewDense(r, c, nil)
	o.Scale(s, m)
	return o
}

multiply函数将 2 个函数相乘(这不同于点积`)。

func multiply(m, n mat.Matrix) mat.Matrix {
	r, c := m.Dims()
	o := mat.NewDense(r, c, nil)
	o.MulElem(m, n)
	return o
}

addsubtract功能允许增加或减少一个功能。

func add(m, n mat.Matrix) mat.Matrix {
	r, c := m.Dims()
	o := mat.NewDense(r, c, nil)
	o.Add(m, n)
	return o
}func subtract(m, n mat.Matrix) mat.Matrix {
	r, c := m.Dims()
	o := mat.NewDense(r, c, nil)
	o.Sub(m, n)
	return o
}

最后,addScalar函数允许我们向矩阵中的每个元素添加一个标量值。

func addScalar(i float64, m mat.Matrix) mat.Matrix {
	r, c := m.Dims()
	a := make([]float64, r*c)
	for x := 0; x < r*c; x++ {
		a[x] = i
	}
	n := mat.NewDense(r, c, a)
	return add(m, n)
}

神经网络

开始了。

我们将创建一个非常简单的 3 层前馈神经网络(也称为多层感知器)。我们从定义网络开始:

type Network struct {
	inputs        int
	hiddens       int
	outputs       int
	hiddenWeights *mat.Dense
	outputWeights *mat.Dense
	learningRate  float64
}

字段inputshiddensoutput定义了每个输入、隐藏和输出层中的神经元数量(记住,这是一个 3 层网络)。hiddenWeightsoutputWeights字段是矩阵,分别表示从输入层到隐藏层以及从隐藏层到输出层的权重。最后,学习率是网络的学习率。

接下来,我们有一个简单的方法来创建神经网络。

func CreateNetwork(input, hidden, output int, rate float64) (net Network) {
	net = Network{
		inputs:       input,
		hiddens:      hidden,
		outputs:      output,
		learningRate: rate,
	}
	net.hiddenWeights = mat.NewDense(net.hiddens, net.inputs, randomArray(net.inputs*net.hiddens, float64(net.inputs)))
	net.outputWeights = mat.NewDense(net.outputs, net.hiddens, randomArray(net.hiddens*net.outputs, float64(net.hiddens)))
	return
}

输入、隐藏和输出神经元的数量以及学习速率从调用者传入以创建网络。然而,隐藏和输出权重是随机创建的。

如果您还记得上面的内容,我们创建的权重是一个矩阵,其列数由来自层的表示,行数由层表示。这是因为权重中的行数必须与层中的神经元数量相同,列数必须与层中的的神经元数量相同(以便与层中的的输出相乘)。花点时间再看看下面的图表——它会更有意义。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Neural network and matrices

用一组随机数字初始化权重是一个重要的参数。为此,我们将使用函数randomArray来创建 float64 的随机数组。

func randomArray(size int, v float64) (data []float64) {
	dist := distuv.Uniform{
		Min: -1 / math.Sqrt(v),
		Max: 1 / math.Sqrt(v),
	} data = make([]float64, size)
	for i := 0; i < size; i++ {
		data[i] = dist.Rand()
	}
	return
}

randomArray函数使用 Gonum 中的distuv包在-1/sqrt(v)1/sqrt(v)之间创建一组均匀分布的值,其中v是来自层的的大小。这是一个相当常用的分布。

现在我们有了神经网络,我们可以要求它做的两个主要功能是用一组训练数据训练自己,或者根据一组测试数据预测值。

从我们之前的努力工作中,我们知道预测意味着通过网络的前向传播,而训练意味着首先是前向传播,然后是后向传播,以使用一些训练数据来改变权重。

因为训练和预测都需要前向传播,所以让我们先从它开始。我们定义了一个名为Predict的函数,使用训练好的神经网络来预测这些值。

func (net Network) Predict(inputData []float64) mat.Matrix {
	// forward propagation
	inputs := mat.NewDense(len(inputData), 1, inputData)
	hiddenInputs := dot(net.hiddenWeights, inputs)
	hiddenOutputs := apply(sigmoid, hiddenInputs)
	finalInputs := dot(net.outputWeights, hiddenOutputs)
	finalOutputs := apply(sigmoid, finalInputs)
	return finalOutputs
}

我们首先从输入开始,通过创建一个名为inputs的矩阵来表示输入值。接下来,我们通过应用隐藏权重和输入之间的点积来找到隐藏层的输入,创建一个名为hiddenInputs的矩阵。换句话说,给定一个 2 神经元输入层和一个 3 神经元隐藏层,我们得到的是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,我们将激活函数sigmoid应用于隐藏输入,以产生hiddenOutputs

func sigmoid(r, c int, z float64) float64 {
	return 1.0 / (1 + math.Exp(-1*z))
}

我们对最终输入和最终输出重复这两个动作,分别产生finalInputsfinalOutputs,预测就是最终输出。

这就是我们如何使用正向传播算法进行预测。让我们看看我们在训练中是如何进行前向和后向传播的。

func (net *Network) Train(inputData []float64, targetData []float64) {
	// forward propagation
	inputs := mat.NewDense(len(inputData), 1, inputData)
	hiddenInputs := dot(net.hiddenWeights, inputs)
	hiddenOutputs := apply(sigmoid, hiddenInputs)
	finalInputs := dot(net.outputWeights, hiddenOutputs)
	finalOutputs := apply(sigmoid, finalInputs) // find errors
	targets := mat.NewDense(len(targetData), 1, targetData)
	outputErrors := subtract(targets, finalOutputs)
	hiddenErrors := dot(net.outputWeights.T(), outputErrors) // backpropagate
	net.outputWeights = add(net.outputWeights,
		scale(net.learningRate,
			dot(multiply(outputErrors, sigmoidPrime(finalOutputs)),
				hiddenOutputs.T()))).(*mat.Dense)

	net.hiddenWeights = add(net.hiddenWeights,
		scale(net.learningRate,
			dot(multiply(hiddenErrors, sigmoidPrime(hiddenOutputs)),
				inputs.T()))).(*mat.Dense)
}

正向传播部分与Predict功能完全相同。我们在这里没有调用Predict,因为我们仍然需要其他的中间值。

获得最终输出后,我们需要做的第一件事是确定输出误差。这相对简单,我们简单地从最终输出中减去我们的目标数据,得到outputErrors:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

隐藏层隐藏的错误有点棘手。还记得这个吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们使用反向传播通过在输出权重和输出误差的转置上应用点积来计算隐藏误差。这会给我们hiddenErrors

现在我们有了误差,我们简单地使用我们之前推导的公式(包括学习率)来改变我们需要做的权重:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请记住,我们是从重量中减去这个数字。因为这是一个负数,我们最后把它加到权重上,这就是我们所做的。

为了简化计算,我们使用了一个sigmoidPrime函数,它无非是做sigP = sig(1 - sig):

func sigmoidPrime(m mat.Matrix) mat.Matrix {
	rows, _ := m.Dims()
	o := make([]float64, rows)
	for i := range o {
		o[i] = 1
	}
	ones := mat.NewDense(rows, 1, o)
	return multiply(m, subtract(ones, m)) // m * (1 - m)
}

你可能还会看到,我们正在做前一个输出转置的点积——这是因为我们在跨层相乘。

最后,我们这样做两次,为我们的神经网络获得新的隐藏和输出权重。

这就是对Train函数的总结。

保存培训结果

在我们继续使用神经网络之前,我们将看看如何保存我们的训练结果并加载它以供以后使用。我们当然不希望每次想做预测时都要从头开始训练——训练网络通常需要很长时间。

func save(net Network) {
	h, err := os.Create("data/hweights.model")
	defer h.Close()
	if err == nil {
		net.hiddenWeights.MarshalBinaryTo(h)
	}
	o, err := os.Create("data/oweights.model")
	defer o.Close()
	if err == nil {
		net.outputWeights.MarshalBinaryTo(o)
	}
}// load a neural network from file
func load(net *Network) {
	h, err := os.Open("data/hweights.model")
	defer h.Close()
	if err == nil {
		net.hiddenWeights.Reset()
		net.hiddenWeights.UnmarshalBinaryFrom(h)
	}
	o, err := os.Open("data/oweights.model")
	defer o.Close()
	if err == nil {
		net.outputWeights.Reset()
		net.outputWeights.UnmarshalBinaryFrom(o)
	}
	return
}

saveload函数是彼此的镜像,我们使用 Gonum mat包中的一个方便的函数将权重矩阵编组为二进制形式,并将相同的形式解组回矩阵。这很普通——唯一值得注意的是,当我们从二进制数据解组回权重矩阵时,我们需要首先将矩阵重置为零值,以便可以重用。

使用我们的神经网络

我们终于来了——使用神经网络!

MNIST 手写识别

让我们从机器学习的“hello world”开始——使用 MNIST 数据集来识别手写数字。MNIST 数据集是一组用于训练的 60,000 个扫描的手写数字图像和用于测试的 10,000 个类似图像。它是 NIST(国家标准与技术研究所)的一个更大的集合的子集,已经过大小标准化和居中。这些图像是黑白的,28 x 28 像素。原始数据集以一种更难处理格式存储,所以人们想出了更简单的 CSV 格式的数据集,这就是我们正在使用的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

MNIST dataset

在 CSV 格式中,每一行都是一幅图像,除了第一列以外的每一列都代表一个像素。第一列是标签,这是图像应该表示的实际数字。换句话说,这就是目标输出。由于有 28 x 28 个像素,这意味着每行有 785 列。

让我们从培训开始。我们创建一个名为mnistTrain的函数,它接收一个神经网络,并使用它来训练 MNIST 数据集:

func mnistTrain(net *Network) {
	rand.Seed(time.Now().UTC().UnixNano())
	t1 := time.Now() for epochs := 0; epochs < 5; epochs++ {
		testFile, _ := os.Open("mnist_dataset/mnist_train.csv")
		r := csv.NewReader(bufio.NewReader(testFile))
		for {
			record, err := r.Read()
			if err == io.EOF {
				break
			} inputs := make([]float64, net.inputs)
			for i := range inputs {
				x, _ := strconv.ParseFloat(record[i], 64)
				inputs[i] = (x / 255.0 * 0.99) + 0.01
			} targets := make([]float64, 10)
			for i := range targets {
				targets[i] = 0.01
			}
			x, _ := strconv.Atoi(record[0])
			targets[x] = 0.99 net.Train(inputs, targets)
		}
		testFile.Close()
	}
	elapsed := time.Since(t1)
	fmt.Printf("\nTime taken to train: %s\n", elapsed)
}

我们打开 CSV 文件并读取每条记录,然后处理每条记录。对于我们读入的每条记录,我们创建一个表示输入的数组和一个表示目标的数组。

对于inputs数组,我们从记录中取出每个像素,并将其转换为 0.0 到 1.0 之间的值,0.0 表示没有值的像素,1.0 表示完整的像素。

对于targets数组,数组的每个元素代表索引成为目标数字的概率。例如,如果目标数字是 3,那么第 4 个元素targets[3]将具有 0.99 的概率,而其余的将具有 0.01 的概率。

一旦我们有了输入和目标,我们就调用网络的Train函数,并将输入和目标传递给它。

你可能会注意到我们在“时代”中运行这个。基本上,我们所做的是运行多次,因为我们运行训练的次数越多,神经网络的训练就越好。然而,如果我们过度训练它,网络将过度适应,这意味着它将很好地适应训练数据,最终将在处理它以前从未见过的数据时表现不佳。

预测手写图像基本上是同样的事情,除了我们只使用输入来调用Predict函数。

func mnistPredict(net *Network) {
	t1 := time.Now()
	checkFile, _ := os.Open("mnist_dataset/mnist_test.csv")
	defer checkFile.Close() score := 0
	r := csv.NewReader(bufio.NewReader(checkFile))
	for {
		record, err := r.Read()
		if err == io.EOF {
			break
		}
		inputs := make([]float64, net.inputs)
		for i := range inputs {
			if i == 0 {
				inputs[i] = 1.0
			}
			x, _ := strconv.ParseFloat(record[i], 64)
			inputs[i] = (x / 255.0 * 0.99) + 0.01
		}
		outputs := net.Predict(inputs)
		best := 0
		highest := 0.0
		for i := 0; i < net.outputs; i++ {
			if outputs.At(i, 0) > highest {
				best = i
				highest = outputs.At(i, 0)
			}
		}
		target, _ := strconv.Atoi(record[0])
		if best == target {
			score++
		}
	} elapsed := time.Since(t1)
	fmt.Printf("Time taken to check: %s\n", elapsed)
	fmt.Println("score:", score)
}

我们得到的结果是一组概率。我们找出概率最高的元素,该数字应该是该元素的索引。如果是的话,我们就认为这是一场胜利。胜利的最终计数是我们的最终得分。

因为我们有 10,000 张测试图像,如果我们能够准确地检测到所有这些图像,那么我们将拥有 100%的准确性。我们来看一下main函数:

func main() {
	// 784 inputs - 28 x 28 pixels, each pixel is an input
	// 200 hidden neurons - an arbitrary number
	// 10 outputs - digits 0 to 9
	// 0.1 is the learning rate
	net := CreateNetwork(784, 200, 10, 0.1) mnist := flag.String("mnist", "", "Either train or predict to evaluate neural network")
	flag.Parse() // train or mass predict to determine the effectiveness of the trained network
	switch *mnist {
	case "train":
		mnistTrain(&net)
		save(net)
	case "predict":
		load(&net)
		mnistPredict(&net)
	default:
		// don't do anything
	}
}

这非常简单,我们首先创建一个神经网络,在输入层有 784 个神经元(每个像素是一个输入),在隐藏层有 200 个神经元,在输出层有 10 个神经元,每个神经元对应一个数字。

然后用 MNIST 训练集训练网络,用测试集预测图像。这是我测试时得到的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

用 60,000 幅图像和 5 个时期训练网络需要 8 分钟,用 10,000 幅图像测试需要 4.4 秒。结果是 9772 幅图像被正确预测,准确率为 97.72%!

预测单个文件

现在,我们已经测试了我们的网络,让我们看看如何使用它对个别图像。

首先,我们从 PNG 文件中获取数据。为此,我们创建了一个dataFromImage函数。

func dataFromImage(filePath string) (pixels []float64) {
	// read the file
	imgFile, err := os.Open(filePath)
	defer imgFile.Close()
	if err != nil {
		fmt.Println("Cannot read file:", err)
	}
	img, err := png.Decode(imgFile)
	if err != nil {
		fmt.Println("Cannot decode file:", err)
	} // create a grayscale image
	bounds := img.Bounds()
	gray := image.NewGray(bounds) for x := 0; x < bounds.Max.X; x++ {
		for y := 0; y < bounds.Max.Y; y++ {
			var rgba = img.At(x, y)
			gray.Set(x, y, rgba)
		}
	}
	// make a pixel array
	pixels = make([]float64, len(gray.Pix))
	// populate the pixel array subtract Pix from 255 because 
	// that's how the MNIST database was trained (in reverse)
	for i := 0; i < len(gray.Pix); i++ {
		pixels[i] = (float64(255-gray.Pix[i]) / 255.0 * 0.99) + 0.01
	}
	return
}

图像中的每个像素代表一个值,但我们不能使用普通的 RGBA,而是需要一个image.Gray。从image.Gray结构中,我们得到了Pix值,并将其转换为float64值。MNIST 图像是黑底白字,所以我们需要从 255 减去每个像素值。

一旦我们有了像素阵列,事情就很简单了。我们使用一个predictFromImage函数,它接收神经网络并从图像文件中预测数字。结果是一个概率数组,其中索引是数字。我们需要做的是找到索引并返回它。

func predictFromImage(net Network, path string) int {
	input := dataFromImage(path)
	output := net.Predict(input)
	matrixPrint(output)
	best := 0
	highest := 0.0
	for i := 0; i < net.outputs; i++ {
		if output.At(i, 0) > highest {
			best = i
			highest = output.At(i, 0)
		}
	}
	return best
}

最后,通过main函数,我们打印图像并从图像中预测数字。

func main() {
	// 784 inputs - 28 x 28 pixels, each pixel is an input
	// 100 hidden nodes - an arbitrary number
	// 10 outputs - digits 0 to 9
	// 0.1 is the learning rate
	net := CreateNetwork(784, 200, 10, 0.1) mnist := flag.String("mnist", "", "Either train or predict to evaluate neural network")
	file := flag.String("file", "", "File name of 28 x 28 PNG file to evaluate")
	flag.Parse() // train or mass predict to determine the effectiveness of the trained network
	switch *mnist {
	case "train":
		mnistTrain(&net)
		save(net)
	case "predict":
		load(&net)
		mnistPredict(&net)
	default:
		// don't do anything
	} // predict individual digit images
	if *file != "" {
		// print the image out nicely on the terminal
		printImage(getImage(*file))
		// load the neural network from file
		load(&net)
		// predict which number it is
		fmt.Println("prediction:", predictFromImage(net, *file))
	}
}

假设网络已经被训练过,这就是我们得到的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就这样,我们用 Go 从头开始编写了一个简单的 3 层前馈神经网络!

参考

这里是我写这篇文章和代码时参考的一些资料。

  • 塔里克·拉希德的制作你自己的神经网络是一本学习神经网络基础知识的好书,其简单的解释风格
  • 迈克尔·尼尔森的神经网络和深度学习免费在线书籍是学习构建神经网络复杂性的另一个惊人资源
  • 丹尼尔·怀特纳克写了一本关于用围棋进行机器学习的书,他关于 T2 在围棋中从头开始构建神经网络的帖子很有教育意义
  • Ujjwal Karn 的数据科学博客有一篇关于神经网络的很好的介绍文章

如何建立一个简单的歌曲推荐系统

原文:https://towardsdatascience.com/how-to-build-a-simple-song-recommender-296fcbc8c85?source=collection_archive---------0-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这篇博客的灵感来自 Siraj Raval 在 Udacity 的深度学习基金会纳米学位。那么回购这种做法可以在这里找到。

建立推荐系统是亚马逊、网飞、Spotify 和谷歌面临的共同任务。推荐系统的基本目标是为我们的观众个性化内容和识别相关数据。这些内容可以是文章、电影、游戏等

有 3 种类型的推荐系统:基于内容的、协作的和流行的。

在本练习中,我们将学习如何使用真实数据构建音乐推荐系统。我们的百万首歌曲数据集包含两个文件:三元组 _ 文件元数据 _ 文件。三元组文件包含用户 id、歌曲 id 和收听时间。元数据文件包含歌曲 id、标题、发行人和艺术家姓名。百万歌曲数据集是来自不同网站的歌曲的混合,用户在听完歌曲后给出评级。例如 Last.fmthisismyjammusixmatch

我们的第一项工作是整合我们的数据集,这在每次我们想要构建数据处理管道时都非常重要。为了集成三元组文件和元数据文件,我们将使用一个流行的 Python 库,名为 pandas

我们首先定义将要使用的两个文件:

triplets_file = 'https://static.turi.com/datasets/millionsong/10000.txt'songs_metadata_file = 'https://static.turi.com/datasets/millionsong/song_data.csv'

然后,我们使用 pandas 读取三元组文件表,并将这 3 列定义为 user_id、song_id 和 listen_count ( df 在这里表示数据帧)

song_df_1 = pandas.read_table(triplets_file,header=**None**)
song_df_1.columns = ['user_id', 'song_id', 'listen_count']

我们还读取了 metadat_file,并打算将 metadat _ file 与 triplets_file 合并。每当组合两个或更多数据集时,都会有重复的列。这里,我们使用 song_id 删除两个数据集之间的重复项

 song_df_2 =  pandas.read_csv(songs_metadata_file)

song_df = pandas.merge(song_df_1, song_df_2.drop_duplicates(['song_id']), on="song_id", how="left")

使用命令song_df.head()允许我们可视化组合数据集:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里有歌曲索引、用户标识、歌曲标识、收听次数、标题、发行版本和艺术家名称。运行len(song_df)返回这个数据集按歌曲索引的总长度是 2,000,000。

本练习的第二步是数据转换,我们将选择该数据的一个子集(前 10,000 首歌曲)。然后,我们将歌曲和 artist_name 合并到一个列中,按照特定歌曲被所有用户收听的次数进行汇总。下面代码中的第一行按照 listen_count 的升序对 song_df 进行分组。第二行通过对每首歌曲的 listen_count 求和来计算 group_sum。第三行添加了一个名为percentage的新列,通过将 listen_count 除以所有歌曲的 listen_count 之和然后乘以 100 来计算这个百分比。最后一行按给定歌曲流行度的升序列出歌曲

song_grouped = song_df.groupby(['song']).agg({'listen_count': 'count'}).reset_index()
grouped_sum = song_grouped['listen_count'].sum()
song_grouped['percentage']  = song_grouped['listen_count'].div(grouped_sum)*100
song_grouped.sort_values(['listen_count', 'song'], ascending = [0,1])

以下是转换步骤后数据集的示例:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

进行数据转换使我们能够进一步简化数据集,并使其易于理解。

下一步,在构建推荐系统时,我们将遵循一种简单的方法。我们将统计数据子集中独立用户和歌曲的数量

users = song_df['user_id'].unique()
len(users) ## return 365 unique userssongs = song_df['song'].unique()
len(songs) ## return 5151 unique songs

然后,我们通过将数据集分成训练和测试数据来创建歌曲推荐器。我们使用scikit-learn库的train_test_split功能。值得注意的是,每当我们构建机器学习系统时,在训练我们的模型之前,我们总是希望将我们的数据分成训练和测试数据集

train_data, test_data = train_test_split(song_df, test_size = 0.20, random_state=0)

我们任意选择 20%作为我们的测试规模。然后,我们使用基于流行度的推荐器类作为黑盒来训练我们的模型。我们创建了一个基于流行度的推荐类的实例,并用我们的训练数据来填充它。下面的代码实现了以下目标:基于每首歌曲的流行度,创建一个推荐器,它接受一个user_id作为输入,并输出该用户的推荐歌曲列表

pm = Recommenders.popularity_recommender_py()
pm.create(train_data, 'user_id', 'song')#user the popularity model to make some prediction
user_id = users[5]
pm.recommend(user_id)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

推荐系统模型的代码如下。这个系统是一个幼稚的方法,没有个性化。它首先获得每首歌曲的 user_id 的唯一计数(即该歌曲通常被所有用户收听的次数),并将其标记为推荐分数。然后,recommend函数接受一个 user_id,并为任何给定用户输出前十首推荐歌曲。因为这是幼稚的方法,所以推荐不是个性化的,对所有用户都是一样的。

#Class for Popularity based Recommender System modelclass popularity_recommender_py():    
    def __init__(self):        
    self.train_data = None        
    self.user_id = None        
    self.item_id = None        
    self.popularity_recommendations = None                #Create the popularity based recommender system model    
    def create(self, train_data, user_id, item_id): 
        self.train_data = train_data
        self.user_id = user_id        
        self.item_id = item_id         

        #Get a count of user_ids for each unique song as   recommendation score
        train_data_grouped = train_data.groupby([self.item_id]).agg({self.user_id: 'count'}).reset_index()        
        train_data_grouped.rename(columns = {'user_id': 'score'},inplace=True)                    #Sort the songs based upon recommendation score
        train_data_sort = train_data_grouped.sort_values(['score', self.item_id], ascending = [0,1])                    #Generate a recommendation rank based upon score
        train_data_sort['Rank'] = train_data_sort['score'].rank(ascending=0, method='first') #Get the top 10 recommendations
        self.popularity_recommendations = train_data_sort.head(10)        #Use the popularity based recommender system model to    
        #make recommendations        def recommend(self, user_id):                    user_recommendations = self.popularity_recommendations                         #Add user_id column for which the recommendations are being generated                user_recommendations['user_id'] = user_id                    #Bring user_id column to the front        
        cols = user_recommendations.columns.tolist()        
        cols = cols[-1:] + cols[:-1]        
        user_recommendations = user_recommendations[cols]
        return user_recommendations

本练习的第二部分是通过利用基于 相似性的协同过滤模型来创建一个 ML 个性化歌曲推荐系统。回想一下,推荐系统分为两种类型:基于内容的基于协作的。基于内容的系统根据用户过去喜欢什么来预测用户喜欢什么。基于协作的系统基于其他相似用户的喜好来预测特定用户的喜好。像网飞和 Hulu 这样的大多数公司都使用混合方法,根据用户过去喜欢的内容以及其他类似用户喜欢的内容来提供推荐。

根据剑桥编码学院的 Agnes jóhannsdóttir(Twitter:@ agnesjohanns)的说法,基于记忆的协同过滤可以分为两种主要方法:用户-项目过滤和项目-项目过滤。

项目-项目过滤方法包括基于用户喜欢的歌曲定义一个共现矩阵。我们试图回答一个问题,对于每首歌曲,已经听过该歌曲的用户还会听另一组其他歌曲多少次。为了进一步简化这一点,根据你过去喜欢什么,根据其他类似用户喜欢什么,你会喜欢什么其他类似的歌曲。让我们将它应用到我们的代码中。首先,我们创建一个基于实例项目相似性的推荐器类,并向它提供我们的训练数据。

is_model = Recommenders.item_similarity_recommender_py()
is_model.create(train_data, 'user_id', 'song')

请注意,在推荐系统的源代码中,generate_top_recommendations 函数计算了所有用户歌曲在同现矩阵中得分的加权平均值。这个共现矩阵将趋向于稀疏矩阵,因为不可能预测用户是否喜欢特定歌曲,他/她是否会喜欢一百万首其他歌曲。可能性是如此之大。使用我们的模型,我们将能够预测用户喜欢的歌曲列表

*#Print the songs for the user in training data*
user_id = users[5]
user_items = is_model.get_user_items(user_id)
*#*
print("------------------------------------------------------------------------------------")
print("Training data songs for the user userid: **%s**:" % user_id)
print("------------------------------------------------------------------------------------")

**for** user_item **in** user_items:
    print(user_item)

print("----------------------------------------------------------------------")
print("Recommendation process going on:")
print("----------------------------------------------------------------------")

*#Recommend songs for the user using personalized model*
is_model.recommend(user_id)

输出:

------------------------------------------------------------------------------------
Training data songs for the user userid: 4bd88bfb25263a75bbdd467e74018f4ae570e5df:
------------------------------------------------------------------------------------
Just Lose It - Eminem
Without Me - Eminem
16 Candles - The Crests
Speechless - Lady GaGa
Push It - Salt-N-Pepa
Ghosts 'n' Stuff (Original Instrumental Mix) - Deadmau5
Say My Name - Destiny's Child
My Dad's Gone Crazy - Eminem / Hailie Jade
The Real Slim Shady - Eminem
Somebody To Love - Justin Bieber
Forgive Me - Leona Lewis
Missing You - John Waite
Ya Nada Queda - Kudai
----------------------------------------------------------------------
Recommendation process going on:
----------------------------------------------------------------------
No. of unique songs for the user: 13
no. of unique songs in the training set: 4483
Non zero values in cooccurence_matrix :2097

我们还可以使用我们的基于项目相似性的协作过滤模型来查找与我们数据集中的任何歌曲相似的歌曲:

is_model.get_similar_items(['U Smile - Justin Bieber'])

这个输出

no. of unique songs in the training set: 4483
Non zero values in cooccurence_matrix :271

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

值得注意的是,这种方法不是深度学习,而是纯粹基于线性代数。

概括地说,在本练习中,我们讨论了两种模型。第一种模型是基于流行度的推荐器,这意味着它不会针对任何用户进行个性化,并且会输出相同的推荐歌曲列表。第二个模型是个性化推荐器,利用基于 *相似性的协同过滤模型(*即共现矩阵)来基于其他相似用户已经喜欢的歌曲找到用户可能喜欢的歌曲的个性化列表。

接下来我们将讨论如何使用精确召回曲线来量化比较基于流行度的模型和个性化协同过滤模型,从而衡量这两个模型的性能。

为了定量地衡量推荐系统的性能,我们使用了三种不同的指标:精确度、召回率和 F-1 分数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source: http://aimotion.blogspot.com/2011/05/evaluating-recommender-systems.html

根据 Marcel Caraciolo 的说法, Precision 是“相关的顶级结果的比例,考虑到与您的问题领域相关的一些定义”。在我们的案例中,对于我们问题领域相关的定义是一首歌被听的时长,若干用户都喜欢过这首歌。回忆将“测量所有相关结果在顶部结果中所占的比例”。在我们的例子中,这意味着 precision 试图衡量歌曲与推荐歌曲前十名结果的相关性,而 recall 试图衡量歌曲与所有歌曲的相关性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

观察我们的基于流行度的模型和个性化项目相似性模型的精确召回曲线,项目相似性模型在精确召回曲线的某个点上表现更好(即具有更高的召回数和精确度)。

最后一类推荐系统是*基于矩阵分解的推荐系统。*这种类型的推荐系统使用原始相似矩阵的所谓奇异值分解(SVD)分解矩阵来构建推荐系统。

为了计算 SVD 和建议,我们使用以下代码:

*#constants defining the dimensions of our User Rating Matrix (URM)* MAX_PID = 4 
MAX_UID = 5 *#Compute SVD of the user ratings matrix* **def** computeSVD(urm, K):     
    U, s, Vt = sparsesvd(urm, K)      
    dim = (len(s), len(s))     
    S = np.zeros(dim, dtype=np.float32)     
    **for** i **in** range(0, len(s)):         
        S[i,i] = mt.sqrt(s[i])      
        U = csc_matrix(np.transpose(U), dtype=np.float32)     
        S = csc_matrix(S, dtype=np.float32)     
        Vt = csc_matrix(Vt, dtype=np.float32)          
        **return** U, S, Vt

在这段代码中,U 代表用户向量,S 代表项目向量。Vt 将这两个向量的结合点表示为 2 维空间中的点的集合(即向量)。我们将使用这些向量来衡量一个用户的偏好与另一个用户的偏好之间的距离。

换句话说,我们对矩阵进行矢量化,以计算矩阵之间的距离。为了进一步阐明这一点,我们将通过一个例子来说明。假设我们有一个用户歌曲矩阵如下:

 Song0   Song1   Song2   Song3 
User0   3       1       2       3
User1   4       3       4       3
User2   3       2       1       5
User3   1       6       5       2
User4   0       0       5       0

一旦我们执行 SVD,输出将是向量,测量向量之间的距离给我们建议

#Compute estimated rating for the test user
def computeEstimatedRatings(urm, U, S, Vt, uTest, K, test):
    rightTerm = S*Vt estimatedRatings = np.zeros(shape=(MAX_UID, MAX_PID), dtype=np.float16)
    for userTest in uTest:
        prod = U[userTest, :]*rightTerm
        #we convert the vector to dense format in order to get the     #indices
        #of the movies with the best estimated ratings 
        estimatedRatings[userTest, :] = prod.todense()
        recom = (-estimatedRatings[userTest, :]).argsort()[:250]
    return recom #Used in SVD calculation (number of latent factors)
K=2#Initialize a sample user rating matrix
urm = np.array([[3, 1, 2, 3],[4, 3, 4, 3],[3, 2, 1, 5], [1, 6, 5, 2], [5, 0,0 , 0]])
urm = csc_matrix(urm, dtype=np.float32)#Compute SVD of the input user ratings matrix
U, S, Vt = computeSVD(urm, K)#Test user set as user_id 4 with ratings [0, 0, 5, 0]
uTest = [4]
print("User id for whom recommendations are needed: %d" % uTest[0])#Get estimated rating for test user
print("Predictied ratings:")
uTest_recommended_items = computeEstimatedRatings(urm, U, S, Vt, uTest, K, True)
print(uTest_recommended_items)

将输出:

User id for whom recommendations are needed: 4
Predictied ratings:
[0 3 2 1]

接下来,我们讨论真实世界的例子,Hulu 如何将深度学习应用于协同过滤,以建立其行业领先的推荐系统。在 Hulu,像个性化报头、观察列表和最佳选择等功能都是由协同过滤提供支持的。

Hulu 使用的方法是 CF-NADE。我们举个例子。假设我们有 4 部电影:《变形金刚》、《海绵宝宝》、《忍者神龟》、《星际穿越》,评分分别为 4、2、3、5。在 CF-NADE 中,向量(4,2,3,5)的联合概率通过链规则分解为条件的乘积,条件如下:

1/ The probability that the user gives “Transformers” 4-star conditioned on nothing;2/ The probability that the user gives “SpongeBob” 2-star conditioned on giving “Transformers” 4-star;3/ The probability that the user gives “Teenage Mutant Ninja Turtles” a 3-star conditioned on giving 4-star and 2-star to “Transformers” and “SpongeBob”, respectively;4/ The probability that the user gives “Interstellar” a 5-star conditioned on giving 4-star, 2-star and 3-star to “Transformers”, “SpongeBob” and “Teenage Mutant Ninja Turtles”, respectively;

总而言之,这是基于先前发生的事情的概率链。每个条件由其自己的神经网络建模,并且所有这些神经网络的参数在所有模型之间共享。

来源:

1/Siraj Raval 的深度学习基金会 nano degree(https://www . uda city . com/course/Deep-Learning-nano degree-Foundation-nd 101)

2/【https://www.youtube.com/watch?v=18adykNGhHU

3/https://github.com/llSourcell/recommender_live

4/将深度学习应用于协同过滤:Hulu 如何建立其行业领先地位(http://tech.hulu.com/blog/2016/08/01/cfnade.html)

5/用 Python 实现自己的推荐系统(http://online-dev . Cambridge coding . com/notebooks/eWReNYcAfB/Implementing-your-own-recommender-systems-in-Python-2)

如何在组织中建立影响分析团队

原文:https://towardsdatascience.com/how-to-build-an-analytics-team-for-impact-in-an-organization-21bb05925587?source=collection_archive---------2-----------------------

分析价值生命周期是一个框架,可以帮助分析团队的设计和结构

他们说智慧来自经验,我不得不同意这一点。我花了两年时间建立了一个分析团队,主要目标是对业务产生影响,只是现在我觉得我可以谈谈如何做到这一点。

我花了一些时间来形成一个观点的原因之一是,我们已经试验了一些不同的模型和结构,以便找出哪些最有效。另一个原因是,没有一个人有答案,我们的模型需要时间通过与团队的关键成员和业务中重要的利益相关者进行协商来开发。

也就是说,分析团队在企业或组织中的运作模式现在在我的脑海中已经更加清晰了。它有六个步骤,围绕业务决策形成了一个“生命周期”。这六个步骤告知团队应该如何运作,需要什么技能,团队应该如何构建,以及团队应该具有什么类型的特征和技能。我意识到,许多阅读这篇文章的人不一定能够获得完全遵循这种模型所需的资源,这通常是因为他们所服务的组织太小,不值得这样做,但尽管如此,我相信总体情况仍然是令人感兴趣的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Diagram of the analytics value lifecycle

步骤 1:了解企业的决策需求

*为什么?*分析团队无法发挥最佳功能的一个常见原因是缺乏对他们所服务的业务中发生的决策的理解。如果一个分析团队配备了过多的技术或学术人才,这种“概念差距”很可能会影响团队的潜在影响,因为分析通常是由团队成员的个人利益驱动的,而不是违背关键内部客户的要求。

*如何?*分析团队中至少有一名高级成员应与企业的决策机构保持密切联系,无论是首席财务官、CHRO 还是其他负责做出关键业务决策的委员会或小组。应建立定期论坛,在论坛上交流业务的主要优先事项,并就分析情报的不足之处提供反馈。这将允许形成一个分析议程,并建立最能满足关键内部客户未来需求的数据结构和工具。负责此事的个人最好来自业务本身,具有分析和战略头脑(尽管不一定技术熟练),并有热情和动力在业务内开发分析和分析能力。

步骤 2:构建支持业务决策的方法

*为什么?*我的老读者可能会厌倦听我说这些,但分析的成功取决于有效的衡量。如今,许多分析团队举步维艰,因为他们无法获得准确的测量数据。在了解业务的决策需求后,要问的第一个问题应该是“我们需要衡量什么才能理解这一点?”。测量可能是一个极其困难的问题。确定适当的衡量标准是数学和统计原则的微妙平衡,同时还要考虑数据系统和数据捕获流程以及人类行为。这通常是一种妥协,但需要有很强的专业知识和判断力才能做好。

如何?确定解决特定业务问题的衡量方法需要分析团队中多种技能的参与。一个测量专家是绝对必要的。例如,如果是人员和技能问题,这可能是一个心理计量学专家;如果是销售或营销问题,这可能是一个营销计量学专家。除了度量专家之外,还需要数据专家的输入,通常是了解交易系统和数据流的工程师、了解如何在进行分析时处理数据的分析专家(如数据科学家),以及第 1 步中能够向技术专家正确解释和解释业务决策需求的人员。

步骤 3:在事务系统中捕获数据,以便跟踪度量

*为什么?*如果第二步产生的度量是新的,那么通常需要在组织内的原子级别输入新类型的数据。这将需要在系统级别实现,还需要理解所需的规则和逻辑,以及对人工流程所需的调整,以确保准确捕获数据。

如何?数据工程师在任何分析团队中都是一个基础角色。他们将充当与系统管理员和专家的主要联系人,并且他们需要对如何在事务系统中构造字段和输入规则以允许准确和可靠的数据流有重要的发言权。这不是他们唯一的关键角色(见下一步)。

步骤 4:为定期报告和度量分析设计数据

*为什么?*将交易业务数据转化为对分析和决策有用的衡量标准的过程可能会很费力。围绕这一点的一些仔细思考可以对分析操作的效率和可靠性产生巨大的影响。能否定期(每小时、每天、每周、每月)对事务性数据进行预处理,以创建表或视图,这些表或视图在一定级别上进行聚合,从而实现更快速的分析和洞察?这些视图应该如何设计?应该提供什么样的人口统计或削减?

*如何?*一个有能力的数据工程师可以在理解数据预处理需求以及实际创建和设计所需的聚合数据源方面创造奇迹。

步骤 5:进行分析以解决业务问题

*为什么?*这部分显而易见。

如何?这值得更全面的讨论,我将在以后的文章中重点讨论。需要划清界限

  • 定期标准化报告,这需要数据科学、数据工程、可视化、UX 和数据自动化和供应方面的软件开发技能。理想情况下,几乎没有团队时间应该花费在手工交付定期报告上。外部供应商的产品可能有助于填补这方面的空白,但我的经验是,没有一个供应商的工具能够满足这里的全部需求。最好的团队会为自己设计这个。
  • 即席分析,要求商业智能专业人员了解组织的数据系统,并能够使用它们来满足来自商业客户的特定的、非标准的问题。
  • 高级分析,要求深入了解高级统计方法,熟练掌握高级数据科学工具,以便使用这些高级方法分析和处理数据。

步骤 6:为企业消费者翻译分析

*为什么?*分析在业务和组织中没有影响的主要原因之一是结果没有被很好地理解,并且由于缺乏对结果的有效沟通,经常得出明显错误的结论。为了在组织中产生影响,分析团队需要能够利用有助于以业务领导者可以理解的方式清晰地翻译结果的技能,其中许多人可能不具备为自己翻译的知识或技能。当采用先进的分析方法时,这一点尤为重要。

如何?**翻译是高效分析团队中的关键角色。翻译人员了解组织战略和决策,有很强的解决问题的能力,对分析有热情和兴趣,并有客户服务的心态。分析翻译是目前最难找到的职位,因为这个角色太新了,还没有被很好地理解。根据我的经验,最有效和最有能力的翻译都来自于业务本身。翻译人员提供全面的领导和指导,并与技术人员(数据科学、测量、工程、可视化和设计)合作,找到满足客户需求的最佳解决方案。翻译人员在业务中保持长期的客户关系,以便根据需求的变化调整分析方法。

这是我第一次阐述分析价值生命周期,毫无疑问,它将进一步发展,所以请将它视为一项正在进行的工作。在接下来的几周和几个月里,我还打算花更多的时间来充实生命周期某些部分的细节,以及有效地为这样的行动配备人员所需的角色和个人简介。

最初我是一名纯粹的数学家,后来我成为了一名心理计量学家和数据科学家。我热衷于将所有这些学科的严谨性应用到复杂的人的问题上。我也是一个编码极客和日本 RPG 的超级粉丝。在LinkedInTwitter上找我。

如何用你自己的短信建立一个简单、快速、基本上无用的聊天机器人

原文:https://towardsdatascience.com/how-to-build-an-easy-quick-and-essentially-useless-chatbot-using-your-own-text-messages-f2cb8b84c11d?source=collection_archive---------7-----------------------

使用 elasticsearch 或 python 中的 Doc2Vec 模型来构建一个简单的、愚蠢的聊天机器人,你可以使用你的文本消息与之交谈(iPhone/macOS)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source

*目前特定于 iPhone 和 macOS

对于那些熟悉数据科学的人来说,该领域最大的挑战之一是获取不差的训练数据。拥有可靠的“基础事实”例子是好的机器学习的燃料。特别是对于像自然语言处理(NLP)这样的任务,机器需要大量数据来学习良好的单词上下文、标签或一般的“理解”。

嗯,作为人类,我们一直在生成训练数据——对于一个对话机器人来说,还有什么比你自己的短信更好的训练数据呢?在这篇文章中,我将向你展示如何使用 python 和你自己的文本消息来构建一个非常简单、容易并且不太成功的聊天机器人。

更复杂和先进的聊天机器人可能有一个架构,涉及实体识别,意图识别,某种定制的响应数据库,一些 LSTMs 撒在那里等等…但我不会去任何一个。如果你想从头开始构建一个面向商业的聊天机器人来提供好的信息和回答问题,试试类似拉莎·NLU的东西。如果你只是想找点乐子,那就继续读吧!

重要——对你的文本进行分析很有趣,但要注意其中的信息。我们将使用的模型将永远可以访问来自您的朋友、醉酒之夜、爱人、杀手、父母、毒贩等的信息。不要将您的模型、数据或敏感结果发布到任何地方,以便有人可以访问您的信息。这不安全,而且对你的朋友来说这是一个愚蠢的举动。记住这一点,让我们开始深入研究。

这篇文章有两条途径

因此,有很多简单的方法来制作一个无用的聊天机器人,但你应该用哪种方法取决于你的数据。我将概述两种方法,并随时尝试这两种方法;然而,你选择哪一个取决于你拥有的短信数据的。在一般的准备步骤之后,帖子将会被拆分。

  1. Elasticsearch(基本上是 TF-IDF)——如果你没有太多数据就选择这个选项。这种方法更加基于关键字,并且不需要机器学习上下文
  2. Doc2Vec —如果您有很多数据,请选择此选项。这个模型需要大量的数据来学习单词,所以如果没有很多文本,它就不能很好地工作。

作为参考,我有大约 100k 的短信,第二个选项仍然不起作用太好了,但请随意查看哪个最适合您。他们都很有趣,看看你能得到什么。

一般准备

首先,你应该已经安装了 python(并且肯定有一些使用它的经验)。如果你没有安装 anaconda ,我推荐你安装,但是我不会介绍任何基础知识。我将在一个 jupyter 笔记本(一个让你运行代码块的 python 笔记本)中工作,但是你可以做任何你喜欢的事情。我将在 Python 2 中工作(但在 3 中应该也可以)。

在这篇文章的相应部分,我将详细介绍每个选项的包。您的 python 版本应该已经安装了我们一开始需要的sqlite3pandas包。

在我们获取数据之前,只需将你的 iPhone 插入你的 Mac,这样就可以备份到最新的数据。你需要尽可能多的数据来改进机器人。完成所有这些后,让我们开始编码。首先让我们创建一个项目目录。我是这样做的:

mkdir -p ~/Documents/textbot

然后,我们接下来需要将数据放在一个更容易的地方。你电脑上的位置应该和我的一样。你所有备份的 iPhone 数据都应该在~/Library/Application Support/MobileSync/Backup。知道了这一点,让我们将数据库文件和我们的文本复制到一个不那么荒谬的位置,而不是你在下面看到的那条可怕的路径。在您的终端中键入:

cp ~/Library/Application\ Support/MobileSync/Backup/feda86be0cbd2b3150fc151e8b03289e3c117213/3d/3d0d7e5fb2ce288813306e4d4636395e047a3d28 ~/Documents/textbot

最后但同样重要的是,让我们进入我们的项目目录并启动 jupyter(或者 python shell,如果您愿意的话)。一旦 jupyter 出现,创建一个新的笔记本。

cd ~/Documents/textbot
jupyter notebook

准备数据

好极了,现在我们要开始学习 python 中有趣的部分了。让我们导入前两个包。sqlite3将让我们从数据库文件中读取数据,而pandas将让我们将其加载到一个数据帧中,以便于操作。

import pandas as pd
import sqlite3

这里是我们读入数据的地方。你必须指定绝对路径否则无法工作,所以不能使用~。在下面的代码中,将kylegallatin改为您的主目录的名称。如果你不知道它是什么,打开一个终端,输入~

#replace kylegallatin with your home directory
path = '''/Users/kylegallatin/Documents/textbot/3d0d7e5fb2ce288813306e4d4636395e047a3d28'''db = sqlite3.connect(path)

现在,我们可以使用一些基本的 SQL 命令来获取我们需要的数据,并将其加载到 pandas 中进行处理。我们只需要几列,所以我已经指定了它们是什么。text是信息本身,handle_id是你用整数格式发短信的人,is_from_me是一个二进制标志——当它是 1 时,你发送了文本,如果是 0,那么那一行的handle_id发送了文本,最后date是文本发送的日期。如果你想对你的社交生活方式做更深入的分析,请查看你现有的其他专栏。

cursor = db.cursor()
result = cursor.execute('''SELECT text, handle_id, is_from_me, date FROM message''')#load results into a df
df = pd.DataFrame(result.fetchall())
df.columns = ['text','handle_id','is_from_me','date']

并检查您是否有:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

您应该会看到已备份的最早文本。它应该已经按时间顺序排列了,这是下一部分所必需的。如果你不相信,只需运行df = df.sort_values(by='date')来确定。要查看您有多少条消息,您可以运行len(df)

接下来,我们需要开始考虑训练数据。我们这里有大量的消息,但没有真正以对话训练数据的形式出现——它们只是按时间顺序排列的。我们这篇文章的目标是获得一个由两列组成的数据帧:textresponse,这样我们的聊天机器人就有了可以“学习”的例子。

我们不仅需要按人(或handle_id)对对话进行分组,更仔细地观察数据会发现,我们可以连续收到同一个人的多条短信,这使得我们很难区分哪些应该是短信,哪些应该是回复。我们需要连接所有同时发生的文本,然后将每个df['text'][i]text分配给它的responsedf['text'][i+1]

下面不是我写过的最晦涩的东西,但是它运行得够快了。请随时提出改进意见。

##remove the nulls
df.dropna(subset = ['text'], inplace=True)##helper function
def make_sentences(series):
    return '. '.join(series)##initiliaze empty df
train_data = pd.DataFrame(columns = ['text','response'])##iterate through each convo
for person in pd.unique(df['handle_id']):
    conversation = df[df['handle_id'] == person]
    grouped = (conversation.groupby(conversation.is_from_me.diff()
               .ne(0).cumsum(), as_index=False)
               .agg({'text':make_sentences,'is_from_me':'first',
               'handle_id':'first', 'date':'first'}))

    ##match up texts with responses
    tmp = pd.DataFrame({'text':list(conversation['text'][0:-1]),
                  'response':list(conversation['text'][1:])})

    ##append to our training df
    train_data = train_data.append(tmp[['text','response']], 
                                   ignore_index=True)

最后添加最简单的预处理:

train_data['text'] = train_data['text'].apply(lambda x: x.lower())
train_data['response'] = train_data['response'].apply(lambda x: x.lower())

现在你可能在想“伙计,你忽略了一大堆不同的情况”——你可能是对的。群聊呢?你如何定义一次谈话的开始和结束?这里有很多事情需要考虑,在本教程中我将忽略所有这些。这是一个快速而肮脏的聊天机器人教程,而不是“我必须编写 100 万条 if 语句来创建完美的训练数据”。如果有人想更深入地研究这个问题,我就不必说了,请说吧。

撇开这个障碍不谈,我们现在有了类似训练数据的东西。对于每个handle_id,我们现在将每个text映射到一个response,然后每个response成为下一个text。有了这种格式的数据,我们现在可以建立一种方法,将我们要对机器人说的话映射回半适当的响应。

选项 1:让 Elasticsearch 为您做所有事情

选项 1 在我们的text列中使用基于关键字的搜索,使用我们“发送”给机器人的任何内容。通过将我们的文本与其他文本进行比较,我们可以尝试发送适当的响应。

弹性搜索和 TF-IDF 只需…3 秒钟

Elasticsearch 是一个可扩展的搜索平台,它使用类似于 TF-IDF 的算法,该算法是词频逆文档频率的标准。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source

本质上,它是一个简单的函数,经常在搜索/相似性空间中使用,根据关键字定位文档。它也不太强调频繁出现的单词。例如,因为单词“the”出现在如此多的文本中,我们不希望它被认为是我们搜索查询的重要部分。TF-IDF 在将您的查询与文档进行比较时会考虑这一点。对于它的基本概述,只需查看维基百科。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

[Source](https://www.google.com/url?sa=i&rct=j&q=&esrc=s&source=images&cd=&cad=rja&uact=8&ved=2ahUKEwic6L3k0vTbAhVOwFkKHZZKD7YQjRx6BAgBEAU&url=https%3A%2F%2Fdeeplearning4j.org%2Fbagofwords-tf-idf&psig=AOvVaw3AB8Lk_MHOxMWKOwoYSbRH&ust=1530216484519950)

我们运行的实际比较将基于余弦相似性。由于 TF-IDF 将对我们的文本进行矢量化,因此我们将它与数据中的“最相似”text进行匹配的方式需要基于这一指标。它帮助我们找到在任何大小的向量空间中最接近的向量来表示我们的文档。

选项 1 准备

我们也可以使用类似于sklearn的包来实现 TF-IDF,但是对于我们的目的来说,elastic 在设置、使用和扩展方面更快/更容易。我们需要两样东西 elasticsearch 本身和它的客户。要安装前者,我们将使用软件包管理器自制软件。如果你还没有,从这里的安装

然后打开一个终端:

brew install elasticsearch
pip install elasticsearch

现在你已经安装了 elasticsearch,你只需要启动它。自制软件应该已经把它添加到你的路径中了。这个终端命令应该在端口 9200 上启动 elastic。

elasticsearch

应该就是这样!现在我们可以回到 python 笔记本了。

索引和搜索

为了索引并使我们的数据具有弹性,我们可以使用下面的代码。我们首先导入我们的包,然后创建我们的 python 客户机的一个实例。最后,我们用来将数据上传到 elastic 的函数需要字典格式,所以我们将数据帧转换成字典。

我不会深入讨论这个代码/客户端和 elasticsearch,但是在这里我们所做的只是对我们的数据进行了一点处理,并在我们的 elasticsearch 实例上创建了一个名为textbot的索引,我们现在可以查询它的信息。

from elasticsearch import Elasticsearch
from elasticsearch.helpers import bulkes = Elasticsearch(['localhost:9200'])texts_dict = train_data.to_dict(orient='records')
bulk(es, texts_dict, index='textbot', doc_type='clue', raise_on_error=True)

和 dddd 基本就是这样!现在唯一要做的就是创建一个函数来查询我们的数据。

选项 1 聊天机器人

下面就是了。我有一个简单的函数,运行一个连续的循环。每次,它都提示用户输入,在我们的数据中搜索最相关的text信息,然后从同一行返回response。如果响应为quit(),程序停止。我们从搜索中得到的response变量是由被称为查询 DSL 的语言生成的。你可以用它做很多可笑的事情,但是我还是会根据我们的需要坚持做最基本的事情。

response将是一个 json,包含一堆信息,但我们只想要一个值,所以我们只需要获取并打印它!我还引入了一个可以切换的randomness变量。这样做的目的是,你不必每次给你的机器人发送“文本”都得到相同的结果,因为结果是按顺序返回的。如果在索引itext记录中没有与您发送的查询相匹配的相关内容,那么您将只得到“idk”。

from random import randint
randomness = 1def chatbot():
    quit=False
    while quit== False:
        text = str(raw_input('Me: '))
        ##an optional quit command
        if text == 'quit()':
            quit=True
        else:
            response = es.search(index='textbot', doc_type='clue', body={ "query": {
                "match": {
                    "text":text
                }
            }})
            try:
                ##introduce a bit of randomness into the response 
                i = randint(0,randomness)
                print("Chabot: %s" % response['hits']['hits'][i]['_source']['response'])
            except:
                print("Chatbot: idk")

Lit,现在你可以试一试了。

chatbot()

下面是我在我的机器人上输入一些东西的例子,所以你可以得到我所得到的东西,可以随意使用代码的随机性或其他部分来改进你的查询。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

选项 2:训练您自己的上下文 Doc2Vec 模型

更有趣但不太可靠的选择基本上基于两种模型:word2vec 和 doc2vec。

word2vec 和 doc2vec 只需…5 秒钟

要理解 doc2vec,首先需要看一下 word2vec。最初由谷歌的托马斯·米科洛夫领导的研究团队创建,这是一个试图学习单词上下文的模型。对于那些熟悉神经网络的人来说,其架构如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source

它是一个浅层神经网络,以文本作为训练数据。本质上,它通过迭代试图预测一个单词子集出现在给定单词附近的概率,或者一个单词出现在给定的一个相邻单词窗口中的概率(取决于您使用的架构)。每一次尝试,你都要更新隐藏层中每个单词的权重。最后,你丢弃预测*,但保留隐藏层*的权重。给定足够的文本输入,这些权重应该在某种程度上代表单词的上下文。相似的单词应该具有相似的权重向量,然后可以通过余弦相似度进行比较。想了解更多关于 word2vec 的信息,这位老兄有一篇很棒的文章

Doc2vec 非常类似,你只是有另一个输入,即句子或完整的文本块,而不是单词。此外,不像 word2vec 那样丢弃谓词,这里我们可以选择评估标签。在 word2vec 中,每个单词都已经是它自己的实体,所以不需要这样做。在 doc2vec 中,我们有包含多个实体的句子或段落,所以我们可以给这些组贴上标签进行分类。

就像选项 1 一样,我们这样做的方式是余弦相似性。由于 doc2vec 将为我们发送的 bot 文本提供权重,并对其进行矢量化,因此我们将它与数据中的“最相似”text进行匹配的方式需要基于这一度量。它帮助我们找到在任何大小的向量空间中最接近的向量来表示我们的文档。下图应该完美地解释了所有技术上的东西。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source

要阅读更多关于 doc2vec 的内容,请查看本文。

选项 2 准备

让数学和理论见鬼去吧!让我们编码这个东西。

我们将使用gensim,这是一个很容易使用最初由 google 发布的 word2vec 和 doc2vec 包的包。它很容易通过命令行上的pip安装。

pip install gensim

训练你自己的 Doc2Vec 模型

首先,我们需要定义如何将数据输入到模型中。到目前为止,我甚至还没有意识到我正在使用的内存,因为我只有 10 万条文本,而且真的没有那么多数据。然而,在这里,通过迭代器一次一句地将数据输入模型是一种很好的做法。

import gensim
from gensim.models.doc2vec import Doc2Vec, TaggedDocument
from gensim.utils import simple_preprocess
import multiprocessing
import osclass MyTexts(object):
    def __iter__(self):
        for i in range(len(train_data)):
            yield TaggedDocument(words=simple_preprocess(train_data['text'][i]), tags=[i])

simple_preprocess函数只是做一些简单的事情,比如将文本小写以使其标准化。这里最关键的是标签——我将其设置为[i],而不是实际的response。虽然你可以做后者,这也是更友好的内存,所以我们将继续这样做,并在稍后处理响应。

该训练模型了!

%%timeassert gensim.models.doc2vec.FAST_VERSION > -1print('Doin cool math stuff...')
cores = multiprocessing.cpu_count()texts = MyTexts()
doc2vec_model = Doc2Vec(size=200, workers=cores)
doc2vec_model.build_vocab(texts)
doc2vec_model.train(texts, total_examples=doc2vec_model.corpus_count, epochs=15)if not os.path.exists('models'):
    os.makedirs('models')
    doc2vec_model.save('models/doc2vec.model')
else:
    doc2vec_model.save('models/doc2vec.model')
print('And we did it!')

%%timejupyter 细胞只是时间,所以如果你在壳或其他东西里,就把它取出来。断言语句确保我们有 c 编译器,以便它快速训练,并且设置模型在所有内核上训练确保它甚至更快。然后,我们用大多数默认参数初始化我们的模型。200 只是我们将得到的单词向量的大小——您可以随意更改所有其他调优参数!我们用build_vocab函数让 doc2vec 访问我们的单词库,然后在我们的texts上训练模型 15 个时期或迭代。os包只是让我们创建一个目录来保存我们的模型供以后使用。

就是这样!鉴于我的数据/计算机的大小,我的只花了 2 分钟,我想你的也差不多。但是——我们怎么知道 doc2vec 做了它应该做的事情?

健全性检查

word2vec 和 doc2vec 都带有一个方便的余弦相似度函数,用于检查我们的 200 维空间中单词之间的“距离”。让我们检查一两个词。

doc2vec_model.most_similar(['hey'])[0:2]

我从我的短信中得到什么?

[(u'hi', 0.588394045829773), (u'yo', 0.5450516939163208)]

毒品。如果你的结果看起来不一样,不要气馁——没有足够的训练数据,你一定会看到一些奇怪的结果。老实说,看看你的模型从你和你朋友的对话中学到了什么,这很有趣。

doc2vec_model.most_similar(['drinks'])[0:2]

给了我:

[(u'beers', 0.8393490314483643), (u'lunch', 0.736896276473999)]

看起来我有在工作时间喝酒的倾向。更准确地说,看起来“饮料”、“啤酒”和“午餐”在我的对话中都是类似的用法。这是有道理的,考虑到我可能会说“让我们去喝杯啤酒”,就像我说“让我们去吃顿午饭”一样。你拥有的数据越多,这些词向量就越有可能彼此远离,更多的同义词就会取代它们的位置。

选项 2 聊天机器人

在这一点上,我们是在容易的部分。我们只需要创建一个简单的函数,它接受一个输入,将其标记化,得到最相似的text,并返回适当的response。由于我们的模型可能还没有看到我们可能扔给它的所有输入,我们将使用infer_vector函数向我们的聊天机器人矢量化我们的文本。

##quick and dirty chatbot function
def chatbot():
    quit=False
    while quit == False:
        text = str(raw_input('Me: '))
        ##an optional quit command
        if text == 'quit()':
            quit=True
        else:
            tokens = text.split()
            ##infer vector for text the model may not have seen
            new_vector = doc2vec_model.infer_vector(tokens)
            ##find the most similar [i] tags
            index = doc2vec_model.docvecs.most_similar([new_vector], topn = 10)
            print('Chatbot: ' + train_data.iloc[index[0][0],1])
            print('\n')

现在运行你的聊天机器人,和它说话!根据你的对话,看看你得到的所有奇怪的回答。

chatbot()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

仅此而已!如果我错过了什么,或者你有什么意见,尽管讨厌。这是我的第一个帖子,还在琢磨。希望你喜欢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值