Gandiva,使用LLVM和Arrow来JIT和计算Pandas表达式

简介

这是2020年的职位,所以新年快乐敬你们所有人!

自从11年前我开始玩LLVM以来,我就是它的忠实粉丝JIT数据结构比如avl,然后再到JIT受限AST树来自TensorFlow图的JIT本地代码.从那时起,LLVM发展成为最重要的编译器框架生态系统之一,现在许多重要的开源项目都在使用它。

我最近注意到的一个很酷的项目是Gandiva.刚帝娃是由Dremio之后捐赠给阿帕奇箭向Dremio团队致敬).Gandiva的主要思想是,它提供了一个编译器来生成LLVM IR,可以操作批量的Apache箭头.Gandiva是用c++编写的,它提供了许多不同的函数来构建表达式树,可以使用LLVM进行JIT化。该设计的一个很好的特性是,它可以使用LLVM自动优化复杂表达式,在操作Arrow批处理时添加本机目标平台向量化(如AVX),并执行本机代码来计算表达式。

下图是Gandiva的概述:

Gandiva的工作原理概述。图片来自:https://www.dremio.com/announcing-gandiva-initiative-for-apache-arrow

在这篇文章中,我将构建一个非常简单的表达式解析器,它亚洲金博宝支持有限的操作集,我将用它来筛选Pandas DataFrame。

用Gandiva构建简单的表达式

在本节中,我将展示如何使用Gandiva中的树构建器手动创建一个简单的表达式。

使用Gandiva Python绑定到JIT和表达式

在为表达式构建解析器和表达式构建器之前,让我们用Gandiva手动构建一个简单的表达式。首先,我们将创建一个简单的Pandas数据帧,从0.0到9.0:

导入熊猫为pd导入pyarrow为pa导入pyarrow。gandivaas gandiva # Create a simple Pandas DataFrame df = pd.DataFrame({"x": [1.0 * i for i in range(10)]}) table = pa.Table.from_pandas(df) schema = pa.Schema.from_pandas(df)

我们将数据帧转换为箭头表需要注意的是,在本例中这是一个零拷贝操作,Arrow并没有从Pandas复制数据并复制DataFrame。之后我们得到模式从表中,它包含列类型和其他元数据。

之后,我们想用Gandiva构建下面的表达式来过滤数据:

(x > 2.0)和(x < 6.0)

这个表达式将使用Gandiva中的节点来构建:

builder = gandiva.TreeExprBuilder() #引用列"x" node_x = builder.make_field(table.schema.field("x")) #创建两个字面量:2.0和6.0 two = builder.make_literal(2.0, pa.float64()) six = builder.make_literal(6.0, pa.float64()) #为"x > 2.0" gt_five_node = builder.make_field()make_function("greater_than", [node_x, two], pa.bool_()) #为"x < 6.0"创建一个函数lt_ten_node = builder。make_function("less_than", [node_x, six], pa.bool_()) #为"(x > 2.0) and (x < 6.0)" and_node = builder创建一个"and"节点。make_and([gt_five_node, lt_ten_node]) #将表达式作为一个条件,并创建一个过滤条件= builder.make_condition(and_node) filter_ = gandiva.make_filter(table. filter)模式、条件)

这段代码现在看起来有点复杂,但很容易理解。我们基本上是在创建树的节点,这些节点将表示我们前面展示的表达式。下面是它的图形表示:

检查生成的LLVM IR

不幸的是,还没有找到转储使用Arrow的Python绑定生成的LLVM IR的方法,然而,我们可以使用c++ API构建相同的树,然后查看生成的LLVM IR:

Auto field_x = field("x", float32());自动模式=箭头::模式({field_x});autonode_x = TreeExprBuilder::MakeField(field_x);auto two = TreeExprBuilder::MakeLiteral((float_t)2.0);auto six = TreeExprBuilder::MakeLiteral((float_t)6.0);auto gt_five_node = TreeExprBuilder::MakeFunction("greater_than", {node_x, two}, arrow::boolean());auto lt_ten_node = TreeExprBuilder::MakeFunction("less_than", {node_x, six}, arrow::boolean());auto and_node = TreeExprBuilder::MakeAnd({gt_five_node, lt_ten_node});autocondition = TreeExprBuilder::MakeCondition(and_node);std::要查看> <过滤器过滤; auto status = Filter::Make(schema, condition, TestConfiguration(), &filter);

上面的代码与Python代码相同,但使用的是c++ Gandiva API。现在我们已经用c++构建了树,我们可以获得LLVM模块并转储它的IR代码。生成的IR充满了来自Gandiva注册表的样板代码和JIT函数,但是重要的部分如下所示:

;函数Attrs: alwaysinline norecurse nounwind readnone ssp uwtable define internal zeroext i1 @less_than_float32_float32(float, float) local_unnamed_addr #0 {%3 = fcmp olt float %0, %1 ret i1 %3};函数Attrs: alwaysinline norecurse nounwind readnone ssp uwtable定义内部zeroext i1 @greater_than_float32_float32(浮动,浮动)local_unnamed_addr # 0 {% 3 = fcmp油气痕迹浮动% 0,% 1 i1 ret % 3  } (...) % x =负载浮子,浮子* % 11% greater_than_float32_float32 =叫i1 @greater_than_float32_float32 (% x浮动,浮动2.000000 e + 00)(…)% x11 =负载浮子,浮子* % 15% less_than_float32_float32 =叫i1 @less_than_float32_float32 (% x11浮动,浮动6.000000 e + 00)

如你所见,在IR上我们可以看到对函数的调用less_than_float32_float_32而且greater_than_float32_float32它们是(在本例中非常简单的)用于进行浮点数比较的Gan亚洲金博宝diva函数。通过查看函数名前缀,注意函数的专门化。

非常有趣的是,LLVM将在这段代码中应用所有优化,它将为目标平台生成高效的本机代码,而Godiva和LLVM将负责确保内存对齐是正确的,以便用于向矢量化的扩展(如AVX)。

我展示的这个IR代码实际上并不是执行的那个,而是优化的那个。在优化后的代码中,我们可以看到LLVM内联了函数,如下面的部分优化代码所示:

% x。我们=加载浮动,浮动* %10,对齐4 %11 = FCMP ogt浮动%x。我们,2.000000e+00 %12 = FCMP olt浮动%x。我们,6.000000e+00 %not.or。Cond =和i1 %12, %11

您可以看到,经过优化之后,表达式现在简单多了,因为LLVM应用了强大的优化并内联了许多Gandiva函数。

用Gandiva构建Pandas过滤器表达式JIT

现在我们希望能够实现一些类似于Pandas的东西DataFrame.query ()函数使用Gandiva。我们将面临的第一个问题是,我们需要解析一个字符串,例如(x > 2.0)和(x < 6.0)之后,我们将不得不使用Gandiva中的树构建器构建Gandiva表达式树,然后在箭头数据上计算该表达式。

现在,我不实现表达式字符串的完整解析,而是使用Python AST模块来解析有效的Python代码并构建该表达式的抽象语法树(AST),稍后我将使用它来发出Gandiva/LLVM节点。

解析字符串的繁重工作将委托给Python AST模块,我们的工作将主要是遍历该树并基于该语法树发出Gandiva节点。访问此Python AST树的节点并发出Gandiva节点的代码如下所示:

类LLVMGandivaVisitor(ast.NodeVisitor): def __init__(self, df_table): self。表= df_table self。生成器= gandiva.TreeExprBuilder()自身。columns = {f.name: self.builder.make_field(f) for f in self.table.schema} self.compare_ops = { "Gt": "greater_than", "Lt": "less_than", } self.bin_ops = { "BitAnd": self.builder.make_and, "BitOr": self.builder.make_or, } def visit_Module(self, node): return self.visit(node.body[0]) def visit_BinOp(self, node): left = self.visit(node.left) right = self.visit(node.right) op_name = node.op.__class__.__name__ gandiva_bin_op = self.bin_ops[op_name] return gandiva_bin_op([left, right]) def visit_Compare(self, node): op = node.ops[0] op_name = op.__class__.__name__ gandiva_comp_op = self.compare_ops[op_name] comparators = self.visit(node.comparators[0]) left = self.visit(node.left) return self.builder.make_function(gandiva_comp_op, [left, comparators], pa.bool_()) def visit_Num(self, node): return self.builder.make_literal(node.n, pa.float64()) def visit_Expr(self, node): return self.visit(node.value) def visit_Name(self, node): return self.columns[node.id] def generic_visit(self, node): return node def evaluate_filter(self, llvm_mod): condition = self.builder.make_condition(llvm_mod) filter_ = gandiva.make_filter(self.table.schema, condition) result = filter_.evaluate(self.table.to_batches()[0], pa.default_memory_pool()) arr = result.to_array() pd_result = arr.to_numpy() return pd_result @staticmethod def gandiva_query(df, query): df_table = pa.Table.from_pandas(df) llvm_gandiva_visitor = LLVMGandivaVisitor(df_table) mod_f = ast.parse(query) llvm_mod = llvm_gandiva_visitor.visit(mod_f) results = llvm_gandiva_visitor.evaluate_filter(llvm_mod) return results

正如您所看到的,代码非常简单,因为我并没有支持所有可能的Python表达式,而是支持其中的一个小子集。亚洲金博宝我们在这个类中所做的基本上是将Python AST节点(如Comparators和BinOps(二进制操作))转换为Gandiva节点。我也改变了语义|运算符分别表示AND和OR,例如Pandas查询()函数。

注册为Pandas扩展

方法创建一个简单的Pandas扩展gandiva_query ()我们创建的方法:

@ pdf .api.extensions.register_dataframe_accessor("gandiva")类gandivaaccessor: def __init__(self, pandas_obj): self。pandas_obj = pandas_obj def query(self, query):返回llvmgandivavisator .gandiva_query(self。pandas_obj、查询)

就是这样,现在我们可以使用这个扩展来做事情,如:

Df = pd。DataFrame({"a": [1.0 * i for i in range(nsize)]}) results = df.gandiva.query("a > 10.0")

因为我们已经注册了一个熊猫扩展称为gandiva它现在是熊猫数据框架的一级公民。

现在让我们创建一个500万浮动的DataFrame,并使用新的查询()过滤方法:

Df = pd。DataFrame({"a": [1.0 * i for i in range(50000000)]}) df.gandiva.query("a < 4.0") # This will output: # array([0, 1, 2, 3], dtype=uint32)

注意,返回值是满足我们实现的条件的索引,因此它与Pandas不同查询()它返回已过滤的数据。

我做了一些基准测试,发现Gandiva通常总是比Pandas快,但是我将把适当的基准测试留到下一篇关于Gandiva的文章中,因为这篇文章将展示如何使用它来JIT表达式。

就是这样!我希望你喜欢这篇文章,就像我喜欢探索刚迪瓦一样。看来我们可能会有越来越多的工具使用Gandiva加速,特别是针对SQL解析/投影/JITing。Gandiva比我刚才展示的要复杂得多,但是您现在可以开始了解它的体系结构以及如何构建表达式树。

——克里斯蒂安·s·佩隆

引用这篇文章为:Christian S. Perone,“Gandiva,使用LLVM和Arrow来JIT和计算Pandas表达式”亚洲金博宝未发现的地域19/01/2020,//www.cpetem.com/2020/01/gandiva-using-llvm-and-arrow-to-jit-and-evaluate-pandas-expressions/

PyTorch内部架构之旅

2019年2月28日更新:我加了一个带有幻灯片的新博客帖子包括我为PyData Montreal做的演示。

简介

这篇文章是一个关于PyTorch代码库的旅行,它旨在成为PyTorch的架构设计和内部结构的指南。我的主要目标是为那些有兴趣了解面向用户API之外发生的事情的人提供一些有用的东西,并展示一些在其他教程中已经介绍过的内容之外的新内容。

注意:PyTorch构建系统广泛使用代码生成,所以我在这里不再重复其他人已经描述过的内容。如果你有兴趣了解这是如何工作的,请阅读以下教程:

C/ c++中Python扩展对象的简短介绍

正如您可能知道的,您可以使用C和c++扩展Python,并开发所谓的“扩展”。所有PyTorch繁重的工作都是用C/ c++而不是纯python实现的。要在C/ c++中定义一个新的Python对象类型,您需要定义一个如下例所示的结构(这是autograd的基础变量类):

//支持torch.autograd.Variable结构体THPVariable {PyObject_HEAD torch::autograd::Variable cdata;PyObject * backward_hooks;};

如您所见,在定义的开头有一个宏,称为PyObject_HEAD这个宏的目标是Python对象的标准化,并将扩展到另一个结构,该结构包含指向类型对象的指针(它定义了初始化方法、分配器等)和带有引用计数器的字段。

在Python API中有两个额外的宏称为Py_INCREF ()而且Py_DECREF (),它们用于增加和减少Python对象的引用计数器。多个实体可以借用或拥有对其他对象的引用(引用计数器增加),只有当该引用计数器为零(当所有引用都被销毁)时,Python才会使用它的垃圾收集器自动删除该对象的内存。

您可以阅读更多关于Python C/ c++扩展的信息在这里

有趣的事实:在许多应用程序亚洲金博宝中,使用小整数作为索引、计数器等是非常常见的。为了效率,官员CPython的翻译缓存从-5到256的整数。因此,声明A = 200;B = 200;A等于b真正的,而声明A = 300;B = 300;A等于b

零拷贝PyTorch Tensor到Numpy,反之亦然

PyTorch有自己的张量表示,它将PyTorch内部表示与外部表示解耦。然而,由于Numpy数组随处可见是很亚洲金博宝常见的,特别是当数据从各种来源加载时,因此我们确实需要在Numpy和PyTorch张量之间进行转换。因此,PyTorch提供了两个方法from_numpy ()而且numpy (),它将Numpy数组转换为PyTorch数组,反之亦然。如果我们看一下将Numpy数组转换为PyTorch张量的调用代码,我们可以对PyTorch的内部表示有更多的了解:

at::Tensor tensor_from_numpy(PyObject* obj) {if (!PyArray_Check(obj)) {throw TypeError("expected np. ")ndarray (got %s)", Py_TYPE(obj)->tp_name);}自动数组= (PyArrayObject*)obj;int ndim = PyArray_NDIM(数组);autosizes = to_aten_shape(ndim, pyarray_dim (array));auto stride = to_aten_shape(ndim, pyarray_stride (array));// NumPy跨步使用字节。火炬跨步使用元素计数。自动element_size_in_bytes = PyArray_ITEMSIZE(数组);For (auto& stride: stride) {stride /= element_size_in_bytes;} //(…)-为简洁省略void* data_ptr = PyArray_DATA(数组); auto& type = CPU(dtype_to_aten(PyArray_TYPE(array))); Py_INCREF(obj); return type.tensorFromBlob(data_ptr, sizes, strides, [obj](void* data) { AutoGIL gil; Py_DECREF(obj); }); }

(代码从tensor_numpy.cpp

从这段代码中可以看到,PyTorch正在从Numpy表示中获取所有信息(数组元数据),然后创建自己的信息。然而,正如您可以从第18行标记的那样,PyTorch正在获得一个指向内部Numpy数组原始数据的指针,而不是复制它。这意味着PyTorch将为该数据创建一个引用,与原始张量数据的Numpy数组对象共享相同的内存区域。

这里还有一点很重要:当Numpy数组对象超出作用域并获得零引用计数时,它将被垃圾收集并且摧毁了,这就是为什么Numpy数组对象的引用计数在第20行有一个增量。

之后,PyTorch将创建一个新的张量对象从这个Numpy blob数据,在这个新成立的张量通过借用内存数据指针,内存大小一起进步以及张量函数将使用后的存储(我们将在下一节讨论这个)发布的数据递减Numpy数组对象的引用计数,让Python照顾这个对象的生命周期。

tensorFromBlob ()方法将创建一个新的张量,但只有在为这个张量创建一个新的“Storage”之后。存储是实际数据指针将被存储的地方(而不是在张量结构本身)。这就把我们带到了下一节张量存储

张量的存储

张量的实际原始数据并不直接保存在张量结构中,而是保存在另一个称为存储的结构中,而存储又是张量结构的一部分。

正如我们在前面的代码中看到的tensor_from_numpy (),有一个电话tensorFromBlob ()它将从原始数据团中创建一个张量。最后一个函数将调用另一个名为storageFromBlob()的函数,该函数将根据数据的类型为该数据创建存储。在CPU浮点类型的情况下,它将返回一个新的CPUFloatStorage实例。

CPUFloatStorage基本上是一个包装器,它带有围绕实际存储结构的实用函数THFloatStorage我们如下所示:

typedef struct THStorage {real *data;ptrdiff_t大小;int refcount;char国旗;THAllocator *分配器;void * allocatorContext;struct THStorage *view;} THStorage;

(代码从THStorage.h

如你所见,这个THStorage保存原始数据的指针、其大小、标志以及一个名为分配器这个我们很快就会讨论。还需要注意的是,没有关于如何解释内部数据的元数据THStorage,这是由于存储是“哑巴”的内容,它是张量的责任,知道如何“查看”或解释这些数据。

从这里,你可能已经意识到我们可以有多个张量指向相同的存储,但对数据有不同的视图,这就是为什么观察一个张量有不同的形状(但保持相同的元素数量)是如此有效。下面这段Python代码显示了在改变Tensor查看数据的方式后,存储中的数据指针正在被共享:

>>> tensor_a = torch。在es((3, 3)) >>> tensor_b = tensor_a.view(9) >>> tensor_a.storage().data_ptr() == tensor_b.storage().data_ptr() True

正如我们在上面的例子中看到的,两个张量的存储上的数据指针是相同的,但是张量代表了存储数据的不同解释。

现在,正如我们在第七行看到的THFloatStorage结构,有一个指向a的指针THAllocator结构。这非常重要,因为它带来了亚洲金博宝可用于分配存储数据的分配器的灵活性。该结构由以下代码表示:

typedef struct THAllocator {void* (*malloc)(void*, ptrdiff_t);Void * (*realloc)(Void *, Void *, ptrdiff_t);Void (*free)(Void *, Void *);} THAllocator;

(代码从THAllocator.h

如您所见,这个结构中有三个函数指针字段来定义分配器的含义:malloc、realloc和free。对于cpu分配的内存,这些函数当然与传统的malloc/realloc/free POSIX函数有关,然而,当我们想在gpu上分配存储时,我们将最终使用CUDA分配器,如cudaMallocHost ()就像我们看到的THCudaHostAllocatorMalloc函数如下:

static void* THCudaHostAllocator_malloc(void* ctx, ptrdiff_t size) {void* ptr;if (size < 0) THError("无效内存大小:%ld", size);if (size == 0)返回NULL;THCudaCheck (cudaMallocHost (ptr、大小));返回ptr;}

(代码从THCAllocator.c

您可能注意到存储库组织中的一种模式,但是在导航存储库时,一定要记住这些约定,总结如下(摘自PyTorch lib自述文件):

  • THT兽人H
  • THCT兽人HC使用uda
  • thcT兽人HC使用uda年代解析
  • THCUNNT兽人HNeuralNetwork
  • T兽人HDistributed
  • THNNT兽人HNeuralNetwork
  • 解说T兽人H年代解析

这种约定也出现在函数/类名和其他对象中,因此一定要时刻记住这些模式。虽然可以在TH代码中找到CPU分配器,但可以在THC代码中找到CUDA分配器。

最后,我们可以看到主张量的组成THTensor结构:

typedef struct THTensor {int64_t *size;int64_t *步;int nDimension;THStorage *存储;ptrdiff_t storageOffset;int refcount;char国旗;} THTensor;

(代码从THTensor.h

如你所见,主要THTensor结构保存大小/跨距/尺寸/偏移量等,以及存储(THStorage)为张量数据。

我们可以在下图中总结所有这些结构:

现在,一旦我们有了像多处理这样的需求,我们想要在多个不同的进程之间共享张量数据,我们需要一个共享内存的方法来解决它,否则,每次另一个进程需要一个张量,甚至当你想要实现的时候亚洲金博宝Hogwild训练过程中,所有不同的进程将写入相同的内存区域(参数所在的区域),您需要在进程之间进行复制,这是非常低效的。亚洲金博宝因此,我们将在下一节中讨论一种特殊的共享内存存储。

共享内存

根据平台支持的不同,共享内存可以以多种不同的方式实现。PyTorch支持其中一些,但为了简单起见,我将在这里讨论在MacOS上使用CPU(而不是GPU)会发生什么。因为PyTorch支持多种共享内存方法,所以这部分要掌握起来有点棘手,因为它在代码中涉及更多的间接级别。

PyTorch提供了Python的包装器多处理模块,并可从中导入torch.multiprocessing.他们在这个包装器中对官方的Python多处理进行了更改,以确保每次一个张量被放到队列中或与另一个进程共享时,PyTorch将确保只共享共享内存的句柄,而不是一个全新的张量的完整副本。亚洲金博宝

很多人都不知道PyTorch中的张量方法叫做share_memory_ ()但是,这个函数是触发特定张量存储内存的整个重建的。这种方法的作用是创建一个共享内存区域,可以在不同的进程之间使用。这个函数最终将调用下面这个函数:

静态THStorage* THPStorage_(newFilenameStorage)(ptrdiff_t size) {int flags = TH_ALLOCATOR_MAPPED_SHAREDMEM | TH_ALLOCATOR_MAPPED_EXCLUSIVE;std::string handle = THPStorage_(__newHandle)();auto ctx = libshm_context_new(NULL, handle.c_str(), flags);返回THStorage_(newWithAllocator)(size, &THManagedSharedAllocator, (void*)ctx);}

(代码从StorageSharing.cpp

如你所见,这个函数将使用一个特殊的分配器创建另一个存储THManagedSharedAllocator.这个函数首先定义一些标志,然后创建一个句柄,句柄是格式为以下的字符串/torch_[进程id]_[随机数]之后,它将使用特殊的THManagedSharedAllocator.这个分配器有指向PyTorch内部库的函数指针libshm,它将实现aUnix域套接字共享共享内存区域句柄的通信。这个分配器实际上是一种特殊情况,它是一种“智能分配器”,因为它包含通信控制逻辑,并使用另一个分配器称为THRefcountedMapAllocator它将负责创建实际的共享内存区域和调用mmap ()将此区域映射到进程虚拟地址空间。

请注意:当一个方法在PyTorch中以下划线结尾时,例如被调用的方法share_memory_ (),这意味着该方法具有原地效果,它将更改当前对象,而不是通过修改创建一个新对象。

现在我将展示一个Python示例,其中一个处理使用来自张量的数据,通过手动交换共享内存句柄分配到另一个进程:

这是在进程A中执行的:

>>> import torch >>> tensor_a = torch。在es((5, 5)) >>> tensor_a 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 [torch.FloatTensor of size 5x5] >>> tensor_a.is_shared() False >>> tensor_a = tensor_a.share_memory_() >>> tensor_a.is_shared() True >>> tensor_a_storage = tensor_a.storage() >>> tensor_a_storage._share_filename_() (b'/var/tmp/tmp.0.yowqlr', b'/torch_31258_1218748506', 25)

在此代码中执行处理一个,我们创建一个新的张量5×5,其中充满了1。之后,我们将其设置为共享,并打印带有Unix域套接字地址和句柄的元组。现在我们可以从另一个记忆区访问这个记忆区进程B如下图:

进程B中执行的代码:

>>> import torch >>> tensor_a = torch. tensor () >>> tuple_info = (b'/var/tmp/tmp.0.)Yowqlr ', b'/torch_31258_1218748506', 25)>>> storage = torch.Storage._new_shared_filename(*tuple_info) >>> tensor_a = torch.Tensor(storage).view((5, 5)) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 [torch.FloatTensor of size 5x5]

正如你所看到的,使用关于Unix域套接字地址和句柄的元组信息,我们能够从另一个进程访问张量存储。如果你改变这里的张量进程B,你也会看到它会反射到处理一个因为这些张量共享同一个内存区域。

DLPack:深度学习框架Babel的希望

现在我想谈谈PyTorch代码库中最近的一些东西,叫做DLPack.DLPack是内存张量结构的开放标准化,允许交换张量数据之间的框架,非常有趣的是,由于这种内存表示是标准化的,非常类似于许多框架已经使用的内存表示,它将允许一个亚洲金博宝框架之间的零拷贝数据共享,这是一个相当惊人的倡议,因为我们今天有各种各样的框架,它们之间没有相互沟通。

这肯定有助于克服我们今天在MXNet、PyTorch等张量表示中存在的“孤岛模型”,并将允许开发人员在框架之间混合框架操作,以及标准化可以给框架带来的所有好处。

DLPack的核心是一个非常简单的结构亚洲金博宝DLTensor,如下所示:

/ * !Plain C Tensor对象,不管理内存。*/类型定义结构{/*!不透明数据指针指向分配的数据。*这将是CUDA设备指针或OpenCL中的cl_mem句柄。*该指针与CUDA一样始终对齐为256字节。*/ void*数据;/ * !张量的设备上下文*/ DLContext ctx;/ * ! \brief Number of dimensions */ int ndim; /*! \brief The data type of the pointer*/ DLDataType dtype; /*! \brief The shape of the tensor */ int64_t* shape; /*! * \brief strides of the tensor, * can be NULL, indicating tensor is compact. */ int64_t* strides; /*! \brief The offset in bytes to the beginning pointer to data */ uint64_t byte_offset; } DLTensor;

(代码从dlpack.h

正如您所看到的,有一个数据指针用于原始数据以及形状/步幅/偏移/GPU vs CPU,以及关于数据的其他元数据信息DLTensor指向。

还有一个管理版本的张量叫做DLManagedTensor在这里,框架可以提供一个上下文和一个“deleter”函数,该函数可以被借用了Tensor的框架调用,以通知另一个框架不再需要这些资源。

在PyTorch中,如果你想从DLTensor格式转换到DLTensor格式,你可以找到C/ c++的两种方法,甚至在Python中你可以这样做,如下所示:

从火炬进口火炬。Utils import dlpack t = torch。在es((5, 5)) dl = dlpack.to_dlpack(t)

这个Python函数将调用toDLPack函数,如下图所示:

DLManagedTensor* toDLPack(const张量& src) {ATenDLMTensor * atDLMTensor(new ATenDLMTensor);atDLMTensor->handle = src;atDLMTensor - >张量。manager_ctx = atDLMTensor;atDLMTensor->张量。deleter = &deleter;atDLMTensor - > tensor.dl_tensor。Data = src.data_ptr();Int64_t device_id = 0;If (src.type().is_cuda()) {device_id = src.get_device();} atDLMTensor - > tensor.dl_tensor。ctx = getDLContext(src.type(), device_id); atDLMTensor->tensor.dl_tensor.ndim = src.dim(); atDLMTensor->tensor.dl_tensor.dtype = getDLDataType(src.type()); atDLMTensor->tensor.dl_tensor.shape = const_cast(src.sizes().data()); atDLMTensor->tensor.dl_tensor.strides = const_cast(src.strides().data()); atDLMTensor->tensor.dl_tensor.byte_offset = 0; return &(atDLMTensor->tensor); }

正如您所看到的,这是一个非常简单的转换,将元数据从PyTorch格式转换为DLPack格式,并分配一个指向内部Tensor数据表示的指针。

我真的希望更多的框架采用这个标准,这肯定会给生态系统带来好处。还值得注意的是,潜在的与Apache箭头会很棒的。

就是这样,我希望你喜欢这篇长文!

——克里斯蒂安·s·佩隆

引用本文为:Christian S. Perone,“PyTorch -内部架构之旅”,在亚洲金博宝未发现的地域12/03/2018,//www.cpetem.com/2018/03/pytorch-internal-architecture-tour/

使用InferSent嵌入和安全两方计算的隐私保护句子语义相似度

保护隐私计算

隐私保护计算或安全计算是密码学的一个子领域,在这个子领域中,两个(两方,或2PC)或多个(多方,或MPC)方可以一起计算一个函数,而不向彼此透露有关各方的私有输入数据的信息。这个问题和第一个解决方案是在1982年由安德鲁·姚的一个惊人的突破提出的,这个突破后来被称为姚明的百万富翁问题”。

姚明的百万富翁问题是两个百万富翁,爱丽丝和鲍勃,他们想知道他们中谁更富有,但是没有透露给对方真正的财富。换句话说,他们想要的可以概括为:Alice和Bob想要联合安全地计算一个函数,除了对输入数据的计算结果(这对他们来说是私有的)之外,不知道其他任何东西。

为了使问题更具体,爱丽丝有一个金额A,比如10美元,鲍勃有一个金额B,比如50美元,他们想知道哪一个更大,而不是鲍勃向爱丽丝透露金额B或爱丽丝向鲍勃透露金额A。还需要注意的是,我们也不希望信任第三方,否则问题将只是与受信任方进行信息交换的简单协议。

形式上,我们想要的是联合计算下面的函数:

r = f(A, B)

比如私有价值一个而且B是私有的,它的唯一所有者和结果在哪里r只有一方或双方都知道。

这样的问题能够被解亚洲金博宝决似乎是非常违反直觉的,但令许多人惊讶的是,在某些安全需求上是可以解决它的。由于最近的技术发展,如FHE (完全同态加密),的转移的电路,这样的问题开始在现实生活中得到实际应用,现在它们被许多公司用于应用程序,如信息交换、安全位置、广告、卫星轨道碰撞避免等。

我不打算深入讨论这些技术的细节,但如果你对OT(无意识转移)背后的直觉感兴趣,你绝对应该阅读Craig Gidney所做的令人惊讶的解释在这里.当然,也有许多不同的协议来实现2PC或MPC,其中每一个都假设了一些安全要求(半诚实的,恶意的,等等),我不打算进入细节,以保持文章的目标,但你应该意识到这一点。

问题是:句子的相似度

我们想要实现的是在不泄露句子内容的情况下,使用隐私保护计算来计算句子之间的相似度。举个具体的例子:Bob拥有一家公司,他有很多不同项目的描述,比如:这个项目是关于建立一个深度学习情绪分析框架,将用于推文,而拥有另一家竞争对手公司的Alice也有类似句子描述的不同项目。他们想要做的是共同计算项目之间的相似性,以确定他们是否应该在一个项目上进行合作,然而,这是重要的一点:Bob不想让Alice知道项目描述,Alice也不想让Bob知道他们的项目,他们想知道他们运行的不同项目之间最接近的匹配,但是没有披露项目想法(项目描述)。

句子相似度比较

现在,我们如何在不公开项目描述信息的情况下交换关于Bob和Alice的项目句子的信息呢?

一种简单的方法是计算句子的散列,然后只比较散列以检查它们是否匹配。然而,这将假设描述是完全相同的,此外,如果句子的熵很小(如小句子),有合理计算能力的人可以尝试恢复句子。

解决这个问题的另一种方法(这是我们将要使用的方法)是在句子嵌入空间中比较句子。我们只需要使用机器学习模型(我们将使用)创建句子嵌入InferSent之后),然后比较句子的嵌入。然而,这种方法也引发了另一个问题:如果Bob或Alice训练一个Seq2Seq模型,该模型将从另一方的嵌入返回到项目的近似描述,那会怎样?

考虑到句子的嵌入,人们可以恢复对句子的大致描述,这并不是不合理的。这就是为什么我们将使用双方安全计算来计算嵌入的相似度,以一种Bob和Alice将计算嵌入的相似度的方式而不暴露它们的内在,确保他们的项目想法的安全。

整个流程如下图所示,Bob和Alice共享相同的机器学习模型,之后他们使用这个模型从句子到嵌入,然后在嵌入空间中安全计算相似度。

整个过程的图表概述。

使用InferSent生成句子嵌入

Bi-LSTM最大池化组网。来源:从自然语言推理数据中监督学习通用句表示。Alexis Conneau等人。

InferSent是Facebook开发的一种用于通用句子表示的NLP技术,它使用监督训练产生高可转移表示。

他们使用了一种双向LSTM,其注意力始终超过了许多非监督训练方法,如SkipThought向量。它们还提供Pytorch实现我们将用它来生成句子嵌入。

注意:即使你没有GPU,你也可以通过几句话的嵌入来获得合理的性能。

生成句子嵌入的第一步是下载并加载一个预先训练好的InferSent模型:

导入模型:https://github.com/facebookresearch/InferSent GLOVE_EMBS 金宝博游戏网址= '../dataset/GloVe/ GloVe . 840b .300d.txt' INFERSENT_MODEL = ' infersent_allnli . 'pickle' #加载训练InferSent模型模型=火炬。load(INFERSENT_MODEL, map_location=lambda storage, loc: storage) model.set_glove_path(GLOVE_EMBS) model.build_vocab_k_words(K=100000)

现在我们需要定义一个相似性度量来比较两个向量,为了达到这个目的,我将余弦相似度(188betcom网页版)因为它很直接:

因为(\ pmb x \ pmb y) = \压裂{\ pmb x \ cdot \ pmb y} {| | \ pmb x | | \ cdot | | \ pmb y | |}

如你所见,如果我们有两个单位向量(范数为1的向量),那么方程分母中的两项为1,我们就可以去掉方程的整个分母,只剩下:

Cos (\hat{x}, \hat{y}) =\hat{x} \cdot\hat{y}

所以,如果我们将向量归一化,得到一个单位范数(这就是为什么向量在上面的等式中戴着帽子),我们可以使余弦相似度的计算变成一个简单的点积。这对我们以后计算相似距离有很大帮助当我们用一个框架来做这个点积的安全计算时。

所以,下一步是定义一个函数,它将取一些句子文本,并将其转发给模型来生成嵌入,然后将其规范化为单位向量:

该函数将文本转发到模型中,并获得嵌入。之后,它将把它规范化为一个单位向量。Def encode(model, text): embedding = model.encode([text])[0] embedding /= np. linalgl .norm(embedding)返回嵌入

如您所见,这个函数非常简单,它将文本输入到模型中,然后将嵌入向量除以嵌入范数。

现在,出于实际原因,我将在后面使用整数计算来计算相似性,但是,由InferSent生成的嵌入当然是实数值。因此,您将在下面的代码中看到我们创建的另一个函数缩放浮点值并移除根号点而且将它们转换为整数。还有另一个重要的问题,我们稍后将使用的框架来进行安全计算不允许有符号整数,因此我们还需要剪辑0.0和1.0之间的嵌入值。这当然会导致一些近似误差,然而,我们仍然可以在有限精度的剪切和缩放之后得到非常好的近似(我使用14位进行缩放,以避免在之后的点积计算中溢出问题):亚洲金博宝

这个函数将缩放嵌入,以#移除基数点。def scale(embedding): scale = 1 << 14 scale_embedding = np。clip(embedding, 0.0, 1.0) * SCALE return scale_embedding.astype(np.int32)

您可以在安全计算中使用浮点数,而且有许多框架支持浮点数,但是,要做到这一点更加棘手,因此,我使用整数算术来简化本教程。上面的函数只是为了简化它而使用的一种方法。很容易看出,我们可以稍后恢复这个嵌入,而不会损失太多的精度。

现在我们只需要创建一些我们将要使用的例句:

# Alice句子列表alice_sentence = ['my cat loves over my keyboard', 'I like to pet my cat',] # Bob句子列表bob_sentence = [' The cat always walking over my keyboard',]

并将它们转换为嵌入:

# Alice句子alice_sentence1 = encode(model, alice_sentence [0]) alice_sentence2 = encode(model, alice_sentence [1]) # Bob句子bob_sentence1 = encode(model, bob_sentence [0])

因为我们现在有了这些句子,而且每个句子都是标准化的,我们可以通过在向量之间亚洲金博宝做点积来计算余弦相似度:

> > > np。圆点(bob_sentence1, alice_sentence1) 0.8798542 >>> np。点(bob_sentence1, alice_sentence2) 0.62976325

我们可以看到,Bob的第一句与Alice的第一句最相似(~0.87),而与Alice的第二句最相似(~0.62)。

既然我们现在有了嵌入,我们只需要将它们转换为缩放的整数:

#缩放Alice句子嵌入alice_sentence1_scaling = Scale (alice_sentence1) alice_sentence2_scaling = Scale (alice_sentence2) #缩放Bob句子嵌入bob_sentence1_scaling = Scale (bob_sentence1) #这是嵌入句子>>> alice_sentence1数组的单位向量([0.01698913,-0.0014404,0.0010993,…, 0.00252409, 0.00828147, 0.00466533], dtype=float32) #这是缩放矢量作为整数>>> alice_sentence1_scaling array([278, 0,18,…, 41, 135, 76], dtype=int32)

现在将这些嵌入作为缩放的整数,我们可以继续进行第二部分,在那里我们将进行双方之间的安全计算。

两方安全计算

为了在双方(Alice和Bob)之间执行安全计算,我们将使用偿框架.ABY实现了许多差分安全计算方案,并允许您将计算描述为如下图所示的电路,其中描述了姚的百万富翁问题:

姚明的百万富翁问题。摘自ABY文档(https://github.com/encryptogro金宝博游戏网址up/ABY)。

正如您所看到的,我们有两个输入进入一个GT GATE(大于GATE),然后是一个输出。这个电路每个输入的比特长度为3,并且将计算Alice输入是否大于Bob输入(GT GATE)。计算方然后秘密地共享它们的私有数据,然后可以使用算术共享、布尔共享或姚共享来安全地评估这些门。

ABY非常容易使用,因为您只需描述您的输入、共享、门,它将为您完成其余的工作,如创建套接字通信通道、在需要时交换数据等。然而,该实现完全是用c++编写的,我不知道它有任何Python绑定(这是一个很好的贡献机会)。

幸运的是,ABY有一个实现的例子,可以为我们做点乘计算例子在这里.我不会在这里复制这个例子,但我们唯一需要改变的部分是读取我们之前创建的嵌入向量,而不是生成随机向量并将位长度增加到32位。

之后,我们只需要在两台不同的机器上执行应用程序(或者像下面这样通过本地模拟):

这将执行服务器部分,-r 0指定角色(服务器)#,-n 4096定义向量的维度(InferSent生成# 4096维嵌入)。~# ./innerproduct -r 0 -n 4096 #在另一个进程(或另一台机器,但对于另一个# machine执行,显然必须指定IP)上也是一样的。~# ./innerproduct -r 1 -n 4096

我们得到了以下结果:

alice_sentence1与bob_sentence1的内积= 226691917 alice_sentence2与bob_sentence1的内积= 171746521

即使在整数表示中,你也可以看到Alice的第一句话和Bob的句子的内积更高,这意味着相似度也更高。但是现在让我们把这个值转换回float:

>>> SCALE = 1 << 14 #这是点积,我们应该得到>>> np。#这是我们在安全计算>>> 226691917 / SCALE**2.0 0.8444931 #这是我们应该得到>>> np的点积。这是我们在安全计算>>> 171746521 / SCALE**2.0 0.6398056上得到的内积

正如您所看到的,我们得到了非常好的近似,即使存亚洲金博宝在低精度的数学和无符号整数的要求。当然,在现实生活中,您不会有两个值和向量,因为它们应该是隐藏的,但适应这些变化是微不足道的,您只需要调整ABY代码来加载它正在执行的一方的向量,并使用双方的正确IP地址/端口。

希望你喜欢!

——克里斯蒂安·s·佩隆

引用这篇文章为:Christian S. Perone,“使用InferSent嵌入和安全的两方计算来保护隐私的句子语义相似性”,在亚洲金博宝未发现的地域22/01/2018,//www.cpetem.com/2018/01/privacy-preserving-infersent/

纳米管道:连接现代巴别塔

logored

欲了解更多信息,请参见官方文档的网站或者官方金宝博游戏网址Github库

拱

大家好,我刚刚亚洲金博宝发布了Nanopipe项目。Nanopipe是一个库,允许您将不同的消息队列系统(但不限于)连接在一起。构建Nanopipe是为了避免现在非常常见的不同类型的通信协议/通道之间的粘合代码。亚洲金博宝这方面的一个例子是:您有一个应用程序正在监听AMQP代理(即AMQP代理)上的消息。RabbitMQ),但你也有一个Redis发布/订阅消息源,也有一个来自奇怪的物联网设备的MQTT源。使用Nanopipe,您可以将MQTT和Redis连接到RabbitMQ,而无需为此做任何粘合代码。您还可以使用Nanopipe构建任何类型的复杂连接方案。

arch2

使用Python和LLVM为TensorFlow计算图生成JIT本地代码

更新:Hacker News在这里讨论

张量流计算图

tensorlogo

最神奇的部分之一TensorFlow体系结构是可以序列化使用的计算图协议缓冲区.此计算图遵循定义明确的格式(点击这里描述你指定的计算(它可以是像CNN这样的深度学习模型,一个简单的逻辑回归,甚至任何你想要的计算)。例如,这是一个非常简单的TensorFlow计算图的例子,我们将在本教程中使亚洲金博宝用(使用TensorFlow Python API):

import tensorflow as tf with tf. session () as sess: input_placeholder = tf.placeholder(tf.int32, 1, name="input") sub_op = tf.int32 . sub_op = tf.int32, 1, name="input")子(input_placeholder特遣部队。c在stant(2, dtype=tf.int32)) add_op = tf.add(sub_op, tf.constant(5, dtype=tf.int32)) output = tf.add(add_op, tf.constant(100, dtype=tf.int32), name="output") tf.train.write_graph(sess.graph_def, ".", "graph.pb", True)
表示计算图。
表示计算图。

如你所见,这是一个非常简单的计算图。亚洲金博宝首先,我们定义占位符来保存输入张量,然后我们指定使用这个输入张量作为输入数据应该进行的计算。这里我们还可以看到我们定义了这个图的两个重要节点,一个叫做"输入(前面提到的占位符),另一个叫做输出,这将保存最终计算的结果。此图与标量的下式相同:输出=(((输入- 2)-5)+100)我故意添加了冗余操作,以便稍后看到LLVM常量传播。

在代码的最后一行中,我们持久化了这个计算图(包括常量值)转换成一个序列化的protobuf文件。最后一个真正的参数是输出文本表示而不是二进制,因此它将产生以下人类可读的输出protobuf文件(为简洁起见,我省略了其中的一部分):

节点{名称:“输入”op:“占位符”attr{关键:“dtype”价值{类型:DT_INT32}} attr{关键:“形状”价值{{昏暗的{形状大小:1  } } } } } 节点{名称:“常量”op:“常量”attr{关键:“dtype”价值{类型:DT_INT32}} attr{关键:“价值”价值{张量{dtype: DT_INT32 tensor_shape {} int_val: 2  } } } } --- >( 为了简便起见,我们省略了)<——节点{名称:“输出”op:“添加”输入:“添加”输入:“Const_2 attr{关键:“T”值{类型:DT_INT32}}}{制作人:9}版本

这是一个非常简单的图亚洲金博宝,TensorFlow图是实际上从来没有那么简单,因为TensorFlow模型可以轻松包含超过300个节点,具体取决于您指定的模型,特别是对于深度学习模型。

我们将使用上面的图来展示如何为这个简单的图使用JIT本机代码LLVM框架

LLVM前端,IR和后端

LLVM-Logo-Derivative-1

LLVM框架是构建编译器和工具链的一个非常好的、模块化和完整的生态系统。下图亚洲金博宝是对LLVM体系结构的一个很好的描述,它对我们很重要:

编译器架构
编译器架构(AOSA/LLVM, Chris latner)

(上图只是LLVM体系结构的一小部分,要全面描述它,请参阅参见这篇精彩的文章选自克里斯·拉特纳写的AOSA的书)

从上图中,我们可以看到LLVM提供了很多核心功能,在左侧你可以看到许多语言可以为它们各自的语言前台编写代码,在那之后,不管你用哪种语言写代码,一切都转换成一种非常强大的语言叫做亚洲金博宝LLVM IR中间表示),正如你所想象的那样,它是位于程序集代码之前的代码的中间表示。在我看来,IR是使LLVM如此神奇的关键组件,因为不管您用哪种语言编写代码(或者即使它是一个JIT IR),所有内容都以相同的表示形式结束,然后这就是神奇发生的地方,因为IR可以利用LLVM优化(也称为亚洲金博宝转换和分析通过).

在这个IR生成之后,你可以将它输入到任何LLVM后端,为LLVM支持的任何体系结构(如x86, ARM, PPC等)生成本地代码,然后你可以最终执行你的代码与本地性能,同时也通过LLVM优化。

为了使用LLVM进行JIT代码,你所需要的就是以编程的方式构建IR,创建一个执行引擎来转换(在执行时)将IR转换为本地代码,获取JIT函数的指针,然后最终执行它。我将在这里使用一个用于LLVM的Python绑定称为llvmlite,它非常python化亚洲金博宝,易于使用。

使用Python和LLVM实现张量流图的JIT化

流

现在让我们使用LLVM和Python来JIT TensorFlow计算图。这是绝不是一个全面的实现,这是一种非常简亚洲金博宝单的方法,一种过度简化的方法,它假设了一些东西:一个整数闭包类型,只是一些TensorFlow操作,还有一个单一的标量支持,而不是高秩张量。

那么,让我们开始构建JIT代码;首先,让我们导入所需的包,初始化一些LLVM子系统,并为TensorFlow整数类型定义LLVM各自的类型:

从ctypes导入CFUNCTYPE, c_int导入tensorflow作为tf从谷歌。Protobuf import text_format from tensorflow.core.framework import graph_pb2 from tensorflow.core.framework import types_pb2 from tensorflow.python.framework import我将导入llvmlite。llvm.initialize() llvm.initialize_native_target() llvm.initialize_native_asmprinter() TYPE_TF_LLVM = {types_pb2. binding as llvmDT_INT32: ll.IntType(32),}

在这之后,让我们定义一个类来打开TensorFlow导出的图,并声明一个方法来通过名称获取图的节点:

类TFGraph(对象):def __init__(self, filename="图形。pb", binary=False): self。graph_def = graph_pb2.GraphDef()pb", "rb")作为f: if binary: self.graph_def. parsefromstring (f.read()) else: text_format.Merge(f.read(), self.graph_def) def get_node(self, name):用于self.graph_def中的节点。Node:如果Node .name == name:返回节点

让我们从定义主函数开始,它将是代码的起点:

def run_main():图= TFGraph("图。pb", False) input_node = graph.get_node("input") output_node = graph.get_node("output") input_type = TYPE_TF_LLVM[input_node.attr["dtype"]。output_type = TYPE_TF_LLVM[output_node.attr["T"]. output_type = TYPE_TF_LLVM。module () func_type = ll. type] module = ll. module () func_type = ll。FunctionType(output_type, [input_type]) func = ll。函数(module, func_type, name='tensorflow_graph') func.args[0].name =' input' bb_entry = func.append_basic_block('entry') ir_builder = ll.IRBuilder(bb_entry)

正如您在上面的代码中所看到的,我们打开序列化的protobuf图,然后获得该图的输入和输出节点。之后,我们还将两个图节点(输入/输出)的类型映射到LLVM类型(从TensorFlow整数到LLVM整数)。然后我们从定义LLVM模块开始,它是所有IR对象的顶级容器。LLVM中的一个模块可以包含许多不同的函数,这里我们只创建一个表示图形的函数,该函数将接收与输入节点相同类型的输入数据作为输入参数,然后它将返回与输出节点相同类型的值。

之后,我们开始创建函数的入口块,并使用这个块实例化我们的IR Builder,它是一个对象,将为我们提供TensorFlow图的JIT操作的构建块。

现在让我们定义一个函数,它将真正完成将TensorFlow节点转换为LLVM IR的工作:

Def build_graph(ir_builder, graph, node): if node。op == "Add": left_op_node = graph.get_node(node.input[0]) right_op_node = graph.get_node(node.input[1]) left_op = build_graph(ir_builder, graph, left_op_node) right_op = build_graph(ir_builder, graph, right_op_node) return ir_builder。添加(left_op, right_op) if节点。op == "Sub": left_op_node = graph.get_node(node.input[0]) right_op_node = graph.get_node(node.input[1]) left_op = build_graph(ir_builder, graph, left_op_node) right_op = build_graph(ir_builder, graph, right_op_node) return ir_builder. get_node(node.input[0])Sub (left_op, right_op) if node。op == "Placeholder": function_args = ir_builder.function. op == "占位符":if arg.name == node.name: return arg raise RuntimeError("Input [{}] not found !".format(node.name))op == "Const": llvm_const_type = TYPE_TF_LLVM[node.attr["dtype"].输出说明类型]const_value = node.attr["value"]. tenor .int_val[0] llvm_const_value = llvm_const__type (const_value) return llvm_const_value

在这个函数中,我们通过参数接收IR Builder、我们前面创建的图类和输出节点。然后,这个函数将通过IR Builder递归地构建LLVM IR。在这里,您可以看到我只实现了TensorFlow图中的Add/Sub/Placeholder和Const操作,只是为了能够支持我们前面定义的图。

之后,我们只需要定义一个函数,它将接受一个LLVM模块,然后创建一个执行引擎,它将在LLVM IR上执行LLVM优化,然后再做艰苦的工作,将IR转换为本地x86代码:

def create_engine(module): features = llvm.get_host_cpu_features().flatten() llvm_module = llvm.parse_assembly(str(module)) target = llvm.Target.from_default_triple() target_machine = target。Create_target_machine (opt=3, features=features) engine = llvm。create_mcjit_compiler(llvm_module, target_machine) engine.finalize_object() print target_machine.emit_assembly(llvm_module) return engine

在上面的代码中,你可以看到我们首先将CPU特性(SSE等)放入一个列表中,然后从模块中解析LLVM IR,然后使用最大优化级别(opt=3,大致相当于GCC -O3参数)创建一个引擎,我们还打印汇编代码(在我的例子中,是由LLVM构建的x86汇编)。

这里我们刚刚完成了run_main ()功能:

ir_builder. Ret = build_graph(ir_builder, graph, output_node)write(str(module)) engine = create_engine(module) func_ptr = engine.get_function_address("tensorflow_graph") cfunc = CFUNCTYPE(c_int, c_int)(func_ptr) ret = cfunc(10) print "执行输出:{}".format(ret)

正如您在上面的代码中所看到的,我们只需调用build_graph ()方法,然后使用IR Builder添加" ret " LLVM IR指令(ret = return),以返回我们刚刚基于TensorFlow图创建的IR函数的输出。我们还在这里将IR输出写入一个外部文件,稍后我将使用这个LLVM IR文件为其他不同的体系结构(如ARM体系结构)创建本机程序集。最后,只需获取本机代码函数地址,为该函数创建一个Python包装器,然后用参数“10”调用它,这将是输入数据,然后输出结果输出值。

就是这样,当然这只是一个过度简化,但是现在我们明白了为我们的TensorFlow模型使用JIT的好处。

输出LLVM IR,优化和多种架构(ARM, PPC, x86等)的优势

例如,让我们创建以下TensorFlow图的LLVM IR(使用我上面显示的代码):

import tensorflow as tf with tf. session () as sess: input_placeholder = tf.placeholder(tf.int32, 1, name="input") sub_op = tf.int32 . sub_op = tf.int32, 1, name="input")子(input_placeholder特遣部队。c在stant(2, dtype=tf.int32)) add_op = tf.add(sub_op, tf.constant(5, dtype=tf.int32)) output = tf.add(add_op, tf.constant(100, dtype=tf.int32), name="output") tf.train.write_graph(sess.graph_def, ".", "graph.pb", True)

生成的LLVM IR如下所示:

;ModuleID = ""目标triple = "unknown-unknown-unknown"目标datalayout = ""定义i32 @"tensorflow_graph"(i32 %"input") {entry: %"。3" =子i32% "输入",2% "。4" =添加i32% "。3", 5% "。5" =添加i32% "。4”,100 ret i32%”。5”}

正如您所看到的,LLVM IR看起来很像汇编代码,但这不是最终的汇编代码,这只是一个尚未优化的IR。就在生成x86汇编代码之前,LLVM会在LLVM IR上运行大量优化传递,它会做一些事情,比如死代码消除、不断传播等。下面是LLVM为上面的TensorFlow图的LLVM IR生成的最终原生x86汇编代码:

.text .file "" .globl tensorflow_graph .align 16, 0x90 .type tensorflow_graph,@function tensorflow_graph: .cfi_startproc leal 103(%rdi), %eax retq .Lfunc_end0: .size tensorflow_graph, .Lfunc_end0-tensorflow_graph .cfi_endproc .section ".note.GNU-stack","",@progbits

如您所见,优化后的代码删除了许多冗余的操作,最后只做了一个103的加法操作,这是我们在图中定义的计算的正确简化。对于大型图,您可以看到这些优化非常强大,因为我们重用了编译器优化已经开发了很多年在机器学习模型计算中

你也可以使用一个名为“llc”的LLVM工具,它可以获取LLVM IR文件,并为任何其他平台生成程序集,例如,下面的命令行将为ARM架构生成原生代码:

llc -O3 out。Ll -march=arm -o sample.s

输出文件如下所示:

。text .syntax统一.eabi_attribute 67年“2.09”@ Tag_conformance .eabi_attribute 6 1 @ Tag_CPU_arch .eabi_attribute 8 1 @ Tag_ARM_ISA_use .eabi_attribute 17日1 @ Tag_ABI_PCS_GOT_use .eabi_attribute 20日1 @ Tag_ABI_FP_denormal .eabi_attribute 21日1 @ Tag_ABI_FP_exceptions .eabi_attribute 23日3 @ Tag_ABI_FP_number_model .eabi_attribute 34岁1 @ Tag_CPU_unaligned_access .eabi_attribute 24日1 @ Tag_ABI_align_needed .eabi_attribute 25日1 @ Tag_ABI_align_preserved .eabi_attribute 38岁1 @ Tag_ABI_FP_16bit_format .eabi_attribute 14,0 @ Tag_ABI_PCS_R9_use .file "输出。l" .globl tensorflow_graph .align 2 .type tensorflow_graph,%function tensorflow_graph: @ @tensorflow_graph .fnstart @ BB#0: @ %entry add r0, r0, #103 mov pc, lr .Lfunc_end0: .size tensorflow_graph, .Lfunc_end0-tensorflow_graph .fnend .section ".note.GNU-stack","",%progbits

正如您在上面看到的,ARM汇编代码也只是一个“添加”汇编指令后面跟着一个返回指令。

这真的很好,因为我们可以自然地利用LLVM框架。例如,今天的ARM刚刚发布了带有可扩展向量扩展(SVE)的ARMv8-A这将支持2048位向量,他们是已经工作LLVM补丁.将来,LLVM的一个很好的补充将是开发LLVM pass来进行分析和转换,这将考虑到机器学习模型的本质。

就这样,我希望你喜欢这篇文章!你可以用几行Python, LLVM和TensorFlow做的事情真的很棒。

2016年8月22日更新:乔什·克朗茨只是指了指他的很酷的项目很可能黑客新闻讨论

2016年8月22日更新:TensorFlow团队实际上正在开发一个JIT(我不知道他们是否在使用LLVM,但在我看来这似乎是最合理的方法)。在他们的论文,关于未来的工作,我还引用了一个非常亚洲金博宝重要的陈述:

“我们也有一些具体的方向来提高TensorFlow的性能。其中一个方向是我们在即时编译器上的初步工作,该编译器可以获取TensorFlow执行的子图,可能带有一些关于张量的典型大小和形状的运行时分析信息,并可以为这个子图生成优化的例程。这个编译器将理解执行许多优化的语义,如循环融合、局部块和平铺、特定形状和大小的专门化等TensorFlow白皮书

完整代码

从ctypes导入CFUNCTYPE, c_int导入tensorflow作为tf从谷歌。Protobuf import text_format from tensorflow.core.framework import graph_pb2 from tensorflow.core.framework import types_pb2 from tensorflow.python.framework import我将导入llvmlite。llvm.initialize() llvm.initialize_native_target() llvm.initialize_native_asmprinter() TYPE_TF_LLVM = {types_pb2. binding as llvmDT_INT32: ll.IntType(32),}类TFGraph(对象):def __init__(self, filename="图。pb", binary=False): self。graph_def = graph_pb2.GraphDef()pb", "rb")作为f: if binary: self.graph_def. parsefromstring (f.read()) else: text_format.Merge(f.read(), self.graph_def) def get_node(self, name):用于self.graph_def中的节点。Node .name == name:返回Node def build_graph(ir_builder, graph, Node):op == "Add": left_op_node = graph.get_node(node.input[0]) right_op_node = graph.get_node(node.input[1]) left_op = build_graph(ir_builder, graph, left_op_node) right_op = build_graph(ir_builder, graph, right_op_node) return ir_builder。添加(left_op, right_op) if节点。op == "Sub": left_op_node = graph.get_node(node.input[0]) right_op_node = graph.get_node(node.input[1]) left_op = build_graph(ir_builder, graph, left_op_node) right_op = build_graph(ir_builder, graph, right_op_node) return ir_builder. get_node(node.input[0])Sub (left_op, right_op) if node。op == "Placeholder": function_args = ir_builder.function. op == "占位符":if arg.name == node.name: return arg raise RuntimeError("Input [{}] not found !".format(node.name))op == "Const": llvm_const_type = TYPE_TF_LLVM[node.attr["dtype"].输出说明type] const_value = node.attr["value"].tensor.int_val[0] llvm_const_value = llvm_const_type(const_value) return llvm_const_value def create_engine(module): features = llvm.get_host_cpu_features().flatten() llvm_module = llvm.parse_assembly(str(module)) target = llvm.Target.from_default_triple() target_machine = target. properties ()Create_target_machine (opt=3, features=features) engine = llvm。create_mcjit_compiler(llvm_module, target_machine) engine.finalize_object() print target_machine.emit_assembly(llvm_module) return engine def run_main(): graph = TFGraph("graph.pb", False) input_node = graph.get_node("input") output_node = graph.get_node("output") input_type = TYPE_TF_LLVM[input_node.attr["dtype"].type] output_type = TYPE_TF_LLVM[output_node.attr["T"].type] module = ll.Module() func_type = ll.FunctionType(output_type, [input_type]) func = ll.Function(module, func_type, name='tensorflow_graph') func.args[0].name = 'input' bb_entry = func.append_basic_block('entry') ir_builder = ll.IRBuilder(bb_entry) ret = build_graph(ir_builder, graph, output_node) ir_builder.ret(ret) with open("output.ir", "w") as f: f.write(str(module)) engine = create_engine(module) func_ptr = engine.get_function_address("tensorflow_graph") cfunc = CFUNCTYPE(c_int, c_int)(func_ptr) ret = cfunc(10) print "Execution output: {}".format(ret) if __name__ == "__main__": run_main()
引用这篇文章为:Christian S. Perone,“使用Python和LLVM为TensorFlow计算图生成JIT本地代码”亚洲金博宝未发现的地域22/08/2016,//www.cpetem.com/2016/08/jit-native-code-generation-for-tensorflow-computation-graphs-using-python-and-llvm/

深度学习——用Python进行卷积神经网络和特征提取

卷积神经网络(或回旋网)是受生物学启发的MLP变体,它们有不同种类的层,每一层的工作原理都不同于通常的MLP层。如果您有兴趣了解更多关于ConvNets的知识,一个很好的课程是CS231n -卷积神经纽托克斯视觉识别.cnn的架构如下图所示:

一个常规的神经网络。
一个常规的神经网络(来自CS231n网站)。
一个ConvNet网络架构(来自CS231n网站)。
一个ConvNet网络架构(来自CS231n网站)。

正如您所看到的,ConvNets可以处理3D体和这些3D体的转换。我不会在这篇文章中重复整个CS231n教程,所以如果你真的感兴趣,请在继续之前花点时间阅读。

千层面和nolearn

我非常喜欢使用的一个用于深度学习的Python包是烤宽面条而且nolearn.Lasagne是基于Theano的,所以GPU的加速真的会有很大的不同,他们的神经网络创建的声明性方法真的很有帮助。nolearn库是一个关于神经网络包(包括Lasagne)的实用程序集合,它可以在创建神经网络体系结构、检查层等过程中帮助我们很多。

在这篇文章中,我要展示的是如何用一些卷积层和池化层构建一个简单的ConvNet体系结构。我还将展示如何使用ConvNet训练特征提取器,然后在将特征输入不同的模型(如支持向量机、逻辑回归等)之前使用它提取特征。许多人使用预先训练的ConvNet模型,然后删除最后的输出层,从在ImageNet数据集上训练的ConvNet中提取特征。这通常被称为迁移学习,因为您可以使用来自其他ConvNets的层作为不同问题的特征提取器,因为ConvNets的第一层过滤器作为边缘检测器,它们可以用作其他问题的通用特征检测器。

加载MNIST数据集

MNIST数据集是最传统的数字分类数据集之一。我们将在Python中使用它的pickle版本,但首先,让我们导入需要使用的包:

导入matplotlib导入matplotlib。Pyplot作为PLT导入matplotlib。cm as cm from urllib import urlretrieve import cPickle as pickle import os import gzip import numpy as np import theano import lasagne from lasagne import layers from lasagne.updates import nesterov_momentum from nolearn.lasagne import NeuralNet from nolearn.lasagne import visualize from sklearn.metrics import classification_report from sklearn.metrics import confusion_matrix

如你所见,我们正在导入matplotlib来绘制一些图像,导入一些本地Python模块来下载MNIST数据集,导入numpy, theano, lasagne, nolearn和一些scikit-learn函数来进行模型评估。

之后,我们定义MNIST加载函数(这与Lasagne教程中使用的函数非常相似):

def load_dataset(): url = 'http://deeplearning.net/data/mnist/mnist.pkl.gz' filename = ' MNIST .pkl.gz'如果不是os.path.exists(filename): print("下载MNIST数据集…")urlretrieve(url, filename) with gzip。open(filename, 'rb') as f: data = pickle.load(f) X_train, y_train = data[0] X_val, y_val = data[1] X_test, y_test = data[2] X_train = X_train。重塑((- 1,1,28,28))X_val = X_val。reshape((-1, 1, 28, 28)) X_test = X_test.reshape((-1, 1, 28, 28)) y_train = y_train.astype(np.uint8) y_val = y_val.astype(np.uint8) y_test = y_test.astype(np.uint8) return X_train, y_train, X_val, y_val, X_test, y_test

正如您所看到的,我们正在下载MNIST pickle数据集,然后将其解压到三个不同的数据集:训练、验证和测试。之后,我们重塑图像内容,准备稍后输入到Lasagne输入层,我们还将numpy数组类型转换为uint8,因为GPU/theano数据类型限制。

之后,我们准备加载MNIST数据集并检查它:

X_train, y_train, X_val, y_val, X_test, y_test = load_dataset()imshow (X_train[0][0],提出= cm.binary)

上面的代码将输出以下图像(我使用的是IPython Notebook):

MNIST数字的一个例子(本例中为5)。
MNIST数字的一个例子(本例中为5)。

ConvNet架构和培训

现在我们可以定义我们的ConvNet架构,然后使用GPU/CPU训练它(我有一个非常便宜的GPU,但它很有帮助):亚洲金博宝

net1 = NeuralNet(layers=[('input', layers. inputlayer), ('conv2d1', layers. conv2dlayer), ('maxpool1', layers. maxpool2dlayer), ('maxpool2', layers. maxpool2dlayer), ('maxpool2', layers. dropoutayer), (' density ', layers. dropoutayer), ('dropout2', layers. dropoutlayer), ('output', layers. denselayer),], #输入层input_shape=(None, 1,28,28), #层conv2d1 conv2d1_num_filters=32, conv2d1_filter_size=(5,5), conv2d1_非线性= lasage .非线性。校正,conv2d1_W= lasage .init. gloriotuniform (), # layer maxpool1 maxpool1_pool_size=(2,2), # layer conv2d2 conv2d2_num_filters=32, conv2d2_filter_size=(5,5), conv2d2_非线性= lasage .非线性。校正,# layer maxpool2 maxpool2_pool_size=(2,2), # dropout1 dropout1_p=0.5, #致密dense_num_units=256, dense_非线性= lasagen .非线性。校正,# dropout2 dropout2_p=0.5, # output output_非线性= lasagen .非线性。softmax, output_num_units=10, #优化方法params update=nesterov_momentum, update_learning_rate=0.01, update_momentum=0.9, max_epoch =10, verbose=1,) #训练网络nn = net1。fit (X_train y_train)

如您所见,在参数中我们用层名/类型定义一个元组字典,然后为这些层定义参数。我们这里的体系结构是使用两个带有池的卷积层,然后是一个完全连接层(密集层)和输出层。在一些层之间也有dropout, dropout层是一个正则化器,它随机地将输入值设置为零,以避免过拟合(见下图)。

退出层效应(来自CS231n网站)。
退出层效应(来自CS231n网站)。

打电话给火车方法,nolearn包将显示学习过程的状态,在我的机器上使用我的简陋的GPU,我得到了以下结果:

##层信息# name size ----------- -------- 0 input 1x28x28 1 conv2d1 32x24x24 2 maxpool1 32x12x12 3 conv2d2 32x8x8 4 maxpool2 32x4x4 5 dropout1 32x4x4 6密256 7 dropout2 256 8 output 10 epoch train loss valid loss train/val valid acc dur ------- ------------ ------------ ----------- ------------ 1 0.85204 0.16707 5.09977 0.95174 33.71s 2 0.27571 0.10732 2.56896 0.96825 33.34s 3 0.20262 0.08567 2.36524 0.97488 33.51s 4 0.165510.07695 2.15081 0.97705 33.50s 5 0.14173 0.06803 2.08322 0.98061 34.38s 6 0.12519 0.06067 2.06352 0.98239 34.02s 7 0.11077 0.05532 2.00254 0.98427 33.78s 8 0.10497 0.05771 1.81898 0.98248 34.17s 9 0.09881 0.05159 1.91509 0.98407 33.80s 10 0.09264 0.04958 1.86864 0.98526 33.40s

如你所见,最终的准确率是0.98526,对于10课时的训练来说,这是一个相当不错的表现。

预测与混淆矩阵

现在我们可以使用模型来预测整个测试数据集:

preds = net .predict(X_test)

我们还可以绘制一个混淆矩阵来检验神经网络分类的性能:

Cm = confusi_matrix (y_test, preds) plt.matshow(Cm)标题('混淆矩阵')plt.colorbar()ylabel('True label')xlabel('预测标签')plt.show()

上面的代码将绘制以下混淆矩阵:

混淆矩阵
混淆矩阵

如您所见,对角线是分类更密集的地方,这显示了我们的分类器的良好性能。

过滤器可视化

我们还可以从第一个卷积层可视化32个过滤器:

visualize.plot_conv_weights (net1.layers_ [' conv2d1 '])

上面的代码将绘制下面的过滤器:

第一层5x5x32滤镜。
第一层5x5x32滤镜。

正如你所看到的,学习者在学习plot_conv_weights绘制我们指定的层中存在的所有过滤器。

层函数和特征提取

现在是创建ano编译函数的时候了,该函数将输入数据前馈到体系结构中,直到您感兴趣的层。我将得到输出层的函数以及输出层之前的密集层的函数:

Dense_layer = layers.get_output(net1。层_['dense'], deterministic=True) output_layer = layers.get_output(net1.layers_['output'], deterministic=True) input_var = net1.layers_['input'].input_var f_output = theano.function([input_var], output_layer) f_dense = theano.function([input_var], dense_layer)

如您所见,我们现在调用了两个theano函数f_output而且f_dense(对于输出和致密层)。请注意为了得到这里的图层我们使用了一个额外的参数叫做"确定的,这是为了避免脱落层影响我们的前馈通道。

现在我们可以将一个示例实例转换为输入格式,然后将其输入输出层的theano函数:

instance = X_test[0][None,:,:] %timeit -n 500 f_output(instance) 500个循环,每个循环最好有3:858个µs

正如你所看到的,f_output函数平均需要858个µs。我们还可以绘制实例的输出层激活:

(实例)N = pred。shape[1] plt.bar(range(N), pre .ravel())

上面的代码将创建以下情节:

输出层激活。
输出层激活。

如你所见,这个数字被识别为数字7。事实上,您可以为网络的任何层创建theano函数是非常有用的,因为您可以创建一个函数(就像我们之前所做的那样)来获得密集层(输出层之前的那个层)的激活,并且亚洲金博宝您可以使用这些激活作为特征,并将您的神经网络作为特征提取器而不是分类器。现在让我们画出密集层的256单位激活量:

(实例)N = pred。shape[1] plt.bar(range(N), pre .ravel())

上面的代码将创建下面的图:

致密层激活。
致密层激活。

现在,您可以使用这256个激活的输出作为线性分类器(如Logistic Regression或SVM)的特征。

我希望你喜欢这个教程!

引用这篇文章为:Christian S. Perone,“深度学习-用Python进行卷积神经网络和特征提取”,在亚洲金博宝未发现的地域19/08/2015,//www.cpetem.com/2015/08/convolutional-neural-networks-and-feature-extraction-with-python/

谷歌的S2,球面上的几何,细胞和希尔伯特曲线

更新- 2017年12月05日:谷歌刚刚宣布它将致力于开发S2库的新发布版本,惊人的消息,库可以找到在这里

谷歌的S2库是一个真正的宝藏,不仅因为它的空间索引能力,而且因为它是一个在4年前发布的库,它没有得到它应有的关注。S2库由谷歌本身在谷歌地图、MongoDB引擎和Foursquare上使用,但除了Foursquare的一篇论文外,你在任何地方都找不到任何关于该库的文档或文章谷歌表示和源代码注释。您还将很难找到库的绑定,官方存储库中缺少Python库的Swig文件一些叉子我们可以有一个Python语言的部分绑定(我将在这篇文章中使用它)。我听说谷歌现在正在积极地开发这个库,当他们发布这个工作时,我们可能很快就会得到更多关于它的细节,但我决定分享一些关于这个库的例子,以及我认为这个库如此酷的原因。

通往牢房的路

您将在S2代码中看到这个“单元格”概念。这些细胞是将球体(在我们的例子中是地球,但你并不局限于它)分层分解为区域或点的紧凑表示。区域也可以用这些相同的细胞来近似,它们有一些很好的特征:

  • 它们是紧凑的(用64位整数表示)
  • 它们对地理特征有分辨力
  • 它们是有层次的(它们有级别,相似的级别有相似的区域)
  • 对任意区域的包含查询非常快

S2库首先将球体的点/区域投影到一个立方体中,立方体的每个面都有一个投影球体点的四叉树。之后,会发生一些转换(有关原因的详细信息,请参阅谷歌表示),然后对空间进行离散化,然后在a上枚举单元格希尔伯特曲线这就是为什么这个库这么好,希尔伯特曲线是一个空间填充曲线,它将多个维度转换为一个具有特殊空间特征的维度:它保留了局部性。

希尔伯特曲线

希尔伯特曲线

希尔伯特曲线是空间填充曲线,这意味着它的范围覆盖了整个n维空间。要理解这是如何工作的,你可以想象一根长弦以一种特殊的方式排列在空间上,这样弦穿过空间的每个方格,从而充满整个空间。要将一个2D点沿希尔伯特曲线转换,你只需要选择弦上这个点所在的点。理解它的一个简单的方法是使用这个迭代的例子你可以点击曲线上的任何一点,它会显示这个点在弦上的位置,反之亦然。

在下图中,希尔伯特曲线(弦)最开始的点也位于沿着曲线的最开始(曲线由图像底亚洲金博宝部的一根长弦表示):

希尔伯特曲线
希尔伯特曲线

下面这张图有更多的点,很容易看出希尔伯特曲线是如何保持空间局部性的。你可以注意到,在曲线(在1D表示中,底部的线)中彼此更接近的点在2D维度空间(在x,y平面)中也更接近。然而,请注意,相反的情况并不完全正确,因为你可以有在xy平面上彼此接近的二维点,但在希尔伯特曲线上并不接近。

希尔伯特曲线
希尔伯特曲线

由于S2使用希尔伯特曲线来枚举单元格,这意味着值接近的单元格值在空间上也彼此接近。当这种思想与层次分解结合起来时,您就拥有了一个用于索引和查询操作的非常快速的框架。亚洲金博宝在开始实际示例之前,让我们看看如何用64位整数表示单元格。

如果你们对希尔伯特曲线感兴趣,我强烈推荐这篇文章,它是非常直观的亚洲金博宝,并显示了曲线的一些性质。

单元格表示

正如我已经提到的,细胞有不同的水平和它们所覆盖的不同区域。在S2库中,您将找到30个层次分解级别。细胞的水平和它们可以覆盖的区域范围显示在谷歌演示幻灯片中,我在下面复制

单元格区域
单元格区域

正如您所看到的,S2几何图形的一个非亚洲金博宝常酷的结果是,地球的每一厘米²都可以用64位整数表示。

这些单元格使用以下模式表示:

单元格表示模式
单元格表示模式(图片来自最初的谷歌演示)

第一个是叶细胞,叶细胞的最小面积通常用来表示点。正如您所看到的,3个初始比特被保留用来存储球体的投影点所在的立方体的面,然后它后面是细胞在希尔伯特曲线中的位置,后面总是跟着一个“1”位,这是一个标识细胞水平的标记。

因此,要检查单元格的级别,所需要做的就是检查最后一个“1”位在单元格表示中的位置。包含的检查,验证一个单元格是否包含在另一个单元格中,你只需要做一个前缀比较。这些运算非常快,而且只有使用希尔伯特曲线枚举和层次分解方法才能实现。

覆盖区域

因此,如果您想要生成单元格来覆盖一个区域,您可以使用库的一种方法,在该方法中指定要使用的单元格的最大数量、最大单元格级别和最小单元格级别,然后算法将使用指定的参数近似该区域。在下面的例子中,我使用S2库来提取一些机器学习二进制特征使用15级单元格:

15级的细胞——机器学习的二进制特征
15级的细胞——机器学习的二进制特征

细胞区域如上图所示,使用透明多边形表示我所在城市的整个区域。因为我使用15级作为最低和最高级别,所以单元格都覆盖了相似的区域。如果我将最小级别更改为8(从而允许使用更大的单元格),算法将以一种方式近似该区域,它将提供更少的单元格数量,并试图保持近似的准确性,如下例所示:

覆盖范围从8到15(级别)
覆盖范围从8到15(级别)

正如你所看到的,我们现在在中心使用了更大的单元格进行覆盖,为了处理边界,我们使用了更小的单元格(还要注意四叉树).

例子

*在本教程中,我使用了Python 2.7绑定在库.编译和安装它的说明在资源库的自述文件中,因此我在这里不再重复。

将纬度/经度点转换为单元格表示的第一步如下所示:

>>> import s2 >>> latlng = s2. s2latlng . fromdegrees (-30.043800, -51.140220) >>> cell = s2. s2cellid . fromlatlng (latlng) >>> cell.level() 30 >>> cell.id() 10743750136202470315 >>> cell. totoken () 951977d377e723ab

如您所见,我们首先创建该类的一个对象S2LatLng来表示lat/lng点,然后我们把它输入S2CellId类来构建单元格表示。之后,我们可以得到类的级别和id。还有一个方法叫做ToToken它将整数表示转换为紧凑的字母数字表示,以便稍后使用它进行解析FromToken方法。

还可以获取该单元格的父单元格(在其上一层),并使用包含方法检查一个单元格是否包含在另一个单元格中:

>>> parent = cell.parent() >>> print parent.level() 29 >>> parent.id() 10743750136202470316 >>> parent. totoken () 951977d377e723ac >>> cell.contains(parent) False >>> parent.contains(cell) True

如您所见,父细胞的级别高于子细胞(在我们的例子中,是叶细胞)。除了单元格的级别和包含检查非常快之亚洲金博宝外,id也非常相似(它只检查父单元格的子单元的范围)。

这些单元格可以存储在数据库中,它们在BTree索引中表现得非常好。方法可以创建覆盖某个区域的单元格集合S2RegionCoverer类,如下例所示:

>>> region_rect = S2LatLngRect(S2LatLng.FromDegrees(-51.264871, -30.241701), S2LatLng.FromDegrees(-51.04618, -30.000003)) >>> coverer = S2RegionCoverer() >>> cover .set_min_level(8) >>> cover .set_max_level(15) >>> cover .set_max_cells(500) >>> coverage = cover . getcoverage (region_rect)

首先,我们定义aS2LatLngRect这是一个矩形,划分了我们想要覆盖的区域。您还可以使用其他类(例如,用于构建多边形),例如S2RegionCoverer方法与使用S2Region类作为基类。定义矩形后,实例化S2RegionCoverer然后设置前面提到的最小/最大水平和我们想要生成的近似的最大单元数。

如果你想绘制封面,你可以使用Cartopy, Shapely和matplotlib,如下例所示:

进口matplotlib。Pyplot作为PLT from s2 import * from shape。几何导入多边形导入cartopy。CCRS导入cartopy.io。img_tiles as cimgt. mapquestosm () plt.figure(figsize=(20,20), dpi=200) ax = plt.axes(projection= project .crs) ax. image (img_tiles = cimgt. mapquestosm () plt.figure(figsize=(20,20), dpi=200)add_image(proj, 12) ax.set_extent([-51.411886, -50.922470, -30.301314, -29.94364]) region_rect = S2LatLngRect(s2latng . fromdegrees (-51.264871, -30.241701), s2latng . fromdegrees (-51.04618, -30.000003)) coverer = S2RegionCoverer() cover .set_min_level(8) cover .set_max_level(15) cover .set_max_cells(500) covered = cover . getcoverage (region_rect) geoms = [] for cellid in covered: new_cell = S2Cell(cellid) vertices = [] for i in xrange(0,4):vertex = new_cell.GetVertex(i) latlng = S2LatLng(vertex) vertex .append((latlng.lat().degrees(), latlng.lng().degrees())) geo = Polygon(vertices) gegs .append(geo)打印"Total Geometries: {}".format(len(geoms)) ax. append()add_geometries(geoms, ccrs.PlateCarree(), facecolor='coral', edgecolor='black', alpha=0.4) plt.show()

结果如下所示:

覆盖细胞。
覆盖细胞。

在S2 API中有很多东西,我真的建议您探索和阅读源代码,它真的很有帮助。S2单元格可以用于索引和键值数据库,它可以非常高效地用于B树,甚至还可以用于机器学习目的(这就是我的情况),无论如何,它是一个非常有用的工具,应该放在您的工具箱中。亚洲金博宝我希望你喜欢这个小教程!

——克里斯蒂安·s·佩隆

引用这篇文章为:Christian S. Perone,“谷歌的S2,球面上的几何,细胞和希尔伯特曲线”,在亚洲金博宝未发现的地域14/08/2015,//www.cpetem.com/2015/08/googles-s2-geometry-on-the-sphere-cells-and-hilbert-curve/

Arduino和OLED显示器监控Redis的乐趣和利润

我正在开发一个新的平台(硬件、固件和软件)来创建。”统计数据集,这是一种带有OLED显示屏和无线功能的微型设备,可以监控服务或任何你想要的东西。在此过程中,我使用Arduino对Redis服务器的统计数据进行了一点概念验证。数据立方将来会是开源的,但是我对使用的PoC代码进行开源Arduino和OLED的监控复述,如果有人感兴趣,使用Python监视器将数据从Redis服务器发送到Arduino。

Stat Cubes的主要想法是,你可以把这些小立方体放在你的桌子上,甚至可以随身携带。在我准备好第一个版本之前还有很长一段路要走,但如果人们对它感兴趣,我肯定会努力加快进度。

下面你可以看到显示工作的视频,你也可以访问存储库查看监控应用程序和Arduino代码的更多截图、信息和源代码。

查看关于该项目的更多信息金宝博游戏网址Github库

截图

面包板上Arduino Pro Mini和OLED显示屏。
面包板上Arduino Pro Mini和OLED显示屏。
最初的面板。
最初的面板。
OLED显示屏尺寸比较。
OLED显示屏尺寸比较。
基本统计面板。
基本统计面板。

希望你喜欢!