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

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

加速您的算法第 2 部分— Numba

原文:https://towardsdatascience.com/speed-up-your-algorithms-part-2-numba-293e554c5cc1?source=collection_archive---------4-----------------------

使用 Numba 获得 C++/Fortran 般的速度

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

“brown snake” by Duncan Sanchez on Unsplash

这是我写的系列文章中的第三篇。所有帖子都在这里:

  1. 加速您的算法第 1 部分— PyTorch
  2. 加速你的算法第二部分——Numba
  3. 加速您的算法第三部分——并行化
  4. 加速你的算法第 4 部分— Dask

而这些与 相配套的 Jupyter 笔记本 可在此处获得:

[Github-speedupyourlightms和**[ka ggle]**

索引

  1. 介绍
  2. 为什么是 Numba?
  3. Numba 是如何工作的?
  4. 使用基本的 numba 功能(就@jit 吧!)
  5. @矢量化包装器
  6. 在 GPU 上运行您的函数
  7. 进一步阅读
  8. 参考
***NOTE:*** This post goes with ***Jupyter Notebook*** available in my Repo on Github:[[SpeedUpYourAlgorithms-Numba](https://nbviewer.jupyter.org/github/PuneetGrov3r/MediumPosts/blob/master/SpeedUpYourAlgorithms/2%29%20Numba.ipynb)]

1.介绍

N umba 是一个即时的 python 编译器,也就是说,每当你调用一个 python 函数时,你的全部或部分代码都会被转换成机器码“即时”执行,然后它就会以你的本机机器码速度运行!它由 Anaconda 公司赞助,并得到了许多其他组织的支持。

有了 Numba,你可以加速所有计算密集型的 python 函数(比如循环)。它还支持 numpy 库!因此,您也可以在计算中使用 numpy,并加快整体计算速度,因为 python 中的循环非常慢。你也可以使用 python 标准库的数学库的许多功能,比如 sqrt 等。有关所有兼容功能的完整列表,请查看此处的。

2.为什么是 Numba?

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

[ 来源 ]

S o,为什么 numba?当有很多其他的编译器像 cython ,或者任何其他类似的编译器或者类似 pypy 的时候。

原因很简单,在这里你不必离开用 python 写代码的舒适区。是的,你没看错,你根本不需要为基本的加速而改变你的代码,这可以和你从类似的带有类型定义的 cython 代码中得到的加速相媲美。这不是很好吗?

你只需要添加一个熟悉的 python 功能,一个围绕你的函数的装饰器(包装器)。一个用于类的包装器也正在开发中。

所以,你只需要添加一个装饰就可以了。例如:

from numba import jit@jit
def function(x):
    # your loop or numerically intensive computations
    return x

它看起来仍然像一个纯 python 代码,不是吗?

3.numba 是如何工作的?

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

“question mark neon signage” by Emily Morter on Unsplash

N umba 使用 LLVM 编译器基础设施从纯 Python 代码生成优化的机器码。使用 numba 运行代码的速度与 C、C++或 Fortran 中的类似代码相当。

下面是代码的编译方式:

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

[ 来源

首先,Python 函数被获取、优化并转换成 Numba 的中间表示,然后在类似 Numpy 的类型推理(因此 python float 是 float64)的类型推理之后,它被转换成 LLVM 可解释代码。然后,这些代码被送入 LLVM 的实时编译器,以给出机器码。

您可以根据自己的喜好,在 CPU(默认)或 GPU 上运行时或导入时生成代码。

4.使用基本的 numba 功能(就@jit 吧!)

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

Photo by Charles Etoroma on Unsplash

小菜一碟!

或者最佳性能 numba 建议在你的 jit 包装器中使用nopython = True参数,这样它就不会用到 Python 解释器。或者你也可以使用@njit。如果你的nopython = True包装器因错误而失败,你可以使用简单的@jit包装器,它将编译你的部分代码,循环它可以编译,并把它们变成函数,编译成机器码,把剩下的交给 python 解释器。
所以,你只需要做:

from numba import njit, jit@njit      # or @jit(nopython=True)
def function(a, b):
    # your loop or numerically intensive computations
    return result

当使用@jit时,确保你的代码有 numba 可以编译的东西,比如一个计算密集型的循环,可能有它支持的库(numpy)和函数。否则,它将无法编译任何内容。

最重要的是,numba 还会在函数首次作为机器码使用后对其进行缓存。所以在第一次之后,它会更快,因为它不需要再次编译代码,因为你使用的参数类型与你之前使用的相同。

如果您的代码是可并行化的,您也可以将parallel = True作为参数传递,但是它必须与nopython = True一起使用。目前,它只在 CPU 上工作。

你也可以指定你希望你的函数拥有的函数签名,但是它不会为你给它的任何其他类型的参数进行编译。例如:

from numba import jit, int32@jit(int32(int32, int32))
def function(a, b):
    # your loop or numerically intensive computations
    return result**#** or if you haven't imported type names
**#** you can pass them as string@jit('int32(int32, int32)')
def function(a, b):
    # your loop or numerically intensive computations
    return result

现在你的函数将只接受两个 int32 并返回一个 int32。这样,你可以更好地控制你的功能。如果你愿意,你甚至可以传递多个功能签名。

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

您也可以使用 numba 提供的其他包装器:

  1. @矢量化:允许标量参数用作 numpy ufunc s,
  2. @ gu 矢量化:产生 NumPy 个广义ufunc s,
  3. @stencil :声明一个函数作为类模板操作的内核,
  4. @jitclass :对于 jit 感知类,
  5. @cfunc :声明一个函数作为本机回调使用(从 C/C++等调用),
  6. @overload :注册自己的函数实现,用于 nopython 模式,例如@overload(scipy.special.j0)

Numba 还有提前**(AOT)编译,产生一个不依赖 Numba 的编译后的扩展模块。但是:**

  1. 它只允许常规函数(非 ufuncs),
  2. 您必须指定一个函数签名。您只能指定一个,因为许多指定在不同的名称下。

它还为您的 CPU 架构家族生成通用代码。

5.@矢量化包装器

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

“gray solar panel lot” by American Public Power Association on Unsplash

通过使用@vectorize wrapper,你可以将只在标量上操作的函数转换为数组,例如,如果你正在使用只在标量上工作的 python 的math库。这提供了类似于 numpy 数组操作(ufuncs)的速度。例如:

@vectorize
def func(a, b):
    # Some operation on scalars
    return result

您还可以将target参数传递给这个包装器,对于并行化代码,它的值可以等于parallel,对于在 cuda/GPU 上运行代码,它的值可以等于cuda

@vectorize(target="parallel")
def func(a, b):
    # Some operation on scalars
    return result

如果您的代码计算量足够大或者数组足够大,使用target = “parallel”“cuda”进行矢量化通常会比 numpy 实现运行得更快。如果不是这样,那么它会带来创建线程和为不同线程拆分元素的时间开销,这可能比整个进程的实际计算时间要长。因此,工作应该足够繁重以获得加速。

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

这个伟大的视频有一个例子,加速纳维尔斯托克斯方程计算流体力学与 Numba:

6.在 GPU 上运行您的函数

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

“time-lapsed of street lights” by Marc Sendra martorell on Unsplash

你也可以通过@jit 这样的包装器在 cuda/GPU 上运行函数。为此,您必须从numba库中导入cuda。但是在 GPU 上运行你的代码不会像以前那么容易了。在 GPU 上数百甚至数千个线程上运行函数需要进行一些初始计算。您必须声明和管理网格、块和线程的层次结构。也没那么难。

要在 GPU 上执行一个函数,你必须定义一个叫做**kernel function****device function**的东西。首先让我们看一个**kernel function**

关于内核函数,需要记住以下几点:

a)内核在被调用时显式声明它们的线程层次,即块数和每个块的线程数。您可以编译一次内核,然后使用不同的块和网格大小多次调用它。

b)内核不能返回值。所以,要么你必须对原始数组进行修改,要么传递另一个数组来存储结果。为了计算标量,您必须传递一个 1 元素数组。

# Defining a kernel function
from numba import cuda@cuda.jit
def func(a, result):
    # Some cuda related computation, then
    # your computationally intensive code.
    # (Your answer is stored in 'result')

所以为了启动一个内核,你必须传递两个东西:

  1. 每个块的线程数,
  2. 块数。

例如:

threadsperblock = 32
blockspergrid = (array.size + (threadsperblock - 1)) // threadsperblock
func[blockspergrid, threadsperblock](array)

每个线程中的内核函数必须知道它在哪个线程中,知道它负责数组的哪些元素。Numba 使获取这些元素的位置变得很容易,只需一次调用。

@cuda.jit
def func(a, result):
    pos = cuda.grid(1)  # For 1D array
    # x, y = cuda.grid(2) # For 2D array
    if pos < a.shape[0]:
        result[pos] = a[pos] * (some computation)

为了节省将 numpy 数组复制到特定设备,然后再将结果存储到 numpy 数组中所浪费的时间,Numba 提供了一些函数来声明和发送数组到特定设备,如:numba.cuda.device_arraynumba.cuda.device_array_likenumba.cuda.to_device等。为了节省不必要的拷贝到 cpu 的时间(除非必要)。

另一方面,**device function**只能从设备内部调用(通过内核或另一个设备函数)。有利的一点是,你可以从一个**device function**返回值。因此,您可以使用函数的返回值来计算kernel functiondevice function中的内容。

from numba import cuda@cuda.jit(device=True)
def device_function(a, b):
    return a + b

你也应该看看 Numba 的 cuda 库支持的功能。

Numba 在其 cuda 库中还实现了原子操作随机数生成器共享内存实现(以加速数据访问)等。

ctypes/cffi/cython 互操作性:

  • cffi—nopython 模式支持调用 CFFI 函数。
  • ctypes——在 nopython 模式下支持调用 ctypes 包装函数…
  • Cython 导出函数可调用

7.进一步阅读

  1. https://nb viewer . jupyter . org/github/continuum io/GTC 2017-numba/tree/master/
  2. https://devblogs.nvidia.com/seven-things-numba/
  3. https://devblogs.nvidia.com/numba-python-cuda-acceleration/
  4. https://jakevdp . github . io/blog/2015/02/24/optimizing-python-with-numpy-and-numba/
  5. https://www.youtube.com/watch?v=1AwG0T4gaO0

8.参考

  1. http://numba.pydata.org/numba-doc/latest/user/index.html
  2. https://github.com/ContinuumIO/gtc2018-numba
  3. http://Stephan hoyer . com/2015/04/09/numba-vs-cy thon-how-to-choose/
Suggestions and reviews are welcome.
Thank you for reading!

签名:

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

加速您的算法第 3 部分—并行化

原文:https://towardsdatascience.com/speed-up-your-algorithms-part-3-parallelization-4d95c0888748?source=collection_archive---------5-----------------------

Python 并行编程简介

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

Photo by drmakete lab on Unsplash

这是我写的系列文章中的第三篇。所有帖子都在这里:

  1. 加速您的算法第 1 部分— PyTorch
  2. 加速你的算法第二部分——Numba
  3. 加速您的算法第三部分——并行化
  4. 加速你的算法第 4 部分— Dask

而这些与 相配套的 Jupyter 笔记本 可在此处获得:

[Github-speedupyourlightms和**[ka ggle]**

索引

  1. 简介
  2. 池和过程
  3. 穿线
  4. 达斯克
  5. torch.multiprocessing
  6. 延伸阅读
  7. 参考文献
***NOTE:*** This post goes with ***Jupyter Notebook*** available in my Repo on **Github**:[[SpeedUpYourAlgorithms-Parallelization](https://nbviewer.jupyter.org/github/PuneetGrov3r/MediumPosts/blob/master/SpeedUpYourAlgorithms/3%29%20Prallelization.ipynb)]
and on **Kaggle**:
[[SpeedUpYourAlgorithms-Parallelization](https://www.kaggle.com/puneetgrover/speed-up-your-algorithms-prallelization)]

1.简介 ^

随着时间的推移,数据呈指数级增长,而处理器计算能力的增长却停滞不前,我们需要找到高效处理数据的方法。我们做什么呢

GPU 是一种解决方案,而且非常有效。但是,GPU 不是为了机器学习的目的而制造的,它们是专门为复杂的图像处理和游戏而制造的。我们让我们的算法在现有的 GPU 上工作,它实际上得到了回报。现在,谷歌推出了一款名为 TPU(张量处理单元)的新设备,它是为 TensorFlow 上的机器学习工作负载量身定制的,结果看起来很有希望。英伟达也没有退缩。

但是我们会在未来的某个时候碰到天花板。即使我们采用当今可用的任何巨大数据集,单个机器或计算单元也不足以处理这样的负载。我们将不得不使用多台机器来完成任务。我们将不得不并行化我们的任务。

在本帖中,我们将探讨一些你在 Python 中大部分时间会用到的方法。然后稍微介绍一下 Dasktorch.multiprocessing

2.池和进程 ^

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

Photo by Alexander Popov on Unsplash

Python 的multiprocessing库的PoolProcess方法都为我们的任务启动了一个新的进程,但是方式不同。Process每次呼叫只进行一个过程:

import multiprocessing as mp
p = mp.Process(target= ##target-function,
               args=   ##args-to-func)
# This call will make only one process, which will process
# target-function with given arguments in background.

但是这个过程还没有开始。要启动它,您必须:

p.start()

现在,您可以将它留在这里,或者通过以下方式检查流程是否完成:

p.join()
# Now it will wait for process to complete.

不检查进程是否已经完成有许多用途。例如,在客户端-服务器应用程序中,数据包丢失或无响应进程的概率非常低,我们可以忽略它,这可以为我们带来可观的加速。[取决于应用程序的流程]

对于多个进程,你将不得不制作多个Process。你可以随意制作多个。当您调用它们上的.start()时,它们都将启动。

processes =[mp.Process(target=func, args=(a, b)) for (a, b) in list]for p in processes: p.start()
for p in processes: p.join()

另一方面,Pool启动固定数量的进程,然后我们可以给这些进程分配一些任务。因此,在特定的时刻,只有固定数量的进程在运行,其余的都在等待。进程的数量通常被选择为设备的内核数量,如果将该参数留空,这也是默认行为。

pool = mp.Pool(processes=2)

现在有很多方法可以让你使用这个Pool。在数据科学中,我们可以不用担心的是Pool.applyPool.map,因为它们在任务完成后立即返回结果。Pool.apply只接受一个参数,只使用一个进程,而Pool.map接受许多参数,并将它们放到我们的Pool进程中。

results = [pool.apply(func, (x)) for x in X]
# Or 
results = pool.map(func, (arg)) **#** Takes only one argument

但是Pool.map只接受一个参数(iterable ),它将这个参数分成若干块。要发送许多参数,你可以像 this 这样做。

考虑我们前面的客户机-服务器应用程序的例子,这里要运行的最大进程数是预定义的,所以如果我们有很多请求/包,一次只有其中的n(池中的最大进程数)会运行,而其他的会在队列中的进程槽中等待轮到它。

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

Squaring of all elements of a vector

**# How can we use it with Data Frame?
# A:** You can use some parallelizable functiondf.shape
# (100, 100)
dfs = [df.iloc[i*25:i*25+25, 0] for i in range(4)]with Pool(4) as p:
    res = p.map(np.exp, dfs)
for i in range(4): df.iloc[i*25:i*25+25, 0] = res[i]**#** It can come in handy for preprocessing of data.

用什么,什么时候用?

如果你有很多任务,但其中没有多少是计算密集型的,你应该使用Process。因为如果它们是计算密集型的,它们可能会阻塞您的 CPU,您的系统可能会崩溃。如果您的系统可以一次性处理它们,它们就不必排队等待机会。

当你有固定数量的任务并且它们是计算密集型的,你应该使用一个Pool。因为如果你一下子放开它们,你的系统可能会崩溃。

3.穿线 ^

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

Photo by Hello I’m Nik on Unsplash

穿线!用 python?

python 中的线程名声不好。人们是正确的。实际上,线程在大多数情况下并没有正常工作。那么问题是什么呢?

问题是 GIL(全局解释器锁)。GIL 是在 Python 开发的早期引入的,当时操作系统中甚至没有线程的概念。选择它是因为它简单。

GIL 一次只允许一个 CPU 绑定的进程。也就是说,它一次只允许一个线程访问 python 解释器。因此,线程Lock是整个解释器,直到它完成。

对于单线程程序来说,它很快,因为只有一个Lock需要维护。随着 python 的流行,很难有效地消除 GIL 而不损害所有相关的应用程序。这就是它还在的原因。

但是,如果你的任务不受 CPU 限制,你仍然可以使用多线程并行(y)。也就是说,如果你的任务是 I/O 受限的,你可以使用多线程并获得加速。因为大部分时间这些任务都在等待其他代理(如磁盘等)的响应。)在此期间,他们可以释放锁,让其他任务同时获取它。

**NOTE: (From official page** [**here**](https://wiki.python.org/moin/GlobalInterpreterLock)**)** The GIL is controversial because it prevents multithreaded CPython programs from taking full advantage of multiprocessor systems in certain situations. Note that potentially blocking or long-running operations, such as **I/O**, **image processing**, and [**NumPy**](https://wiki.python.org/moin/NumPy) **number crunching**, happen ***outside*** the GIL. Therefore it is only in multithreaded programs that spend a lot of time inside the GIL, interpreting CPython bytecode, that the GIL becomes a bottleneck.

所以,如果你的任务是 IO 绑定的,比如从服务器下载一些数据,读/写磁盘等等。,您可以使用多线程并获得加速。

from threading import Thread as t
import queue
q = queue.Queue()  # For putting and getting results of threadfunc_ = lambda q, args: q.put(func(args))threads = [t(target=func_, args=(q, args)) for args in args_array]
for t in threads: t.start()
for t in threads: t.join()
res = []
for t in threads: res.append(q.get()) 
**#** These results won't necessarily be in order

为了保存线程的结果,你可以使用类似Queue的东西。为此,你必须像上面那样定义你的函数,或者你可以在你的函数中使用Queue.put(),但是你必须改变你的函数定义,以包含Queue作为参数。

现在,你在队列中的结果不一定是有序的。如果你希望你的结果是有序的,你可以传入一些计数器作为参数,作为 id,然后使用这些 id 来识别结果来自哪里。

threads = [t(func_, args = (i, q, args)) for i, args in 
                                         enumerate(args_array)]
# And update function accordingly***NOTE:*** Multiprocessing with Pandas 'read.csv' method doesn't give much speedup for some reason. As an alternative you can use [**Dask**](/speeding-up-your-algorithms-part-4-dask-7c6ed79994ef).

线程 vs 进程?

一个进程是重量级的,因为它可能包含许多自己的线程(至少包含一个),并且它有自己分配的内存空间,而线程是重量级的,因为它在父进程的内存区域工作,因此执行速度更快。

进程内线程之间的通信更容易,因为它们共享相同的内存空间。,而进程间的通信(IPC-进程间通信)较慢。不过话说回来,共享相同数据的线程可能会进入竞争状态,应该使用Locks或类似的解决方案来处理。

4.达斯克 ^

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

Photo by Trevor Cole on Unsplash

Dask是一个并行计算库,它不仅帮助并行化现有的机器学习工具(PandasNumpy )[ ,即使用高级集合 ],还帮助并行化低级任务/功能,并可以通过制作任务图来处理这些功能之间的复杂交互。[ 即使用低级调度程序 ]这类似于 Python 的线程或多处理模块。

他们还有一个独立的机器学习库dask-ml,它与现有的库如sklearnxgboosttensorflow集成在一起。

from dask import delayed as delay@delay
def add(x, y):
    return x+y
@delay
def sq(x):
    return x**2# Now you can use these functions any way you want, Dask will 
# parallelize your execution. And as the name suggest Dask 
# will not execute your function callings right away, rather
# it will make a computational graph depending on the way you are
# calling functions on inputs and intermediate results. To compute
# final result:
result.compute()

Dask 做任何事情都有一种天生的并行性。对于它如何处理数据帧,您可以将它看作是一种分而治之的方法,它将您的数据帧分成块,然后并行应用您给定的函数。

df = dask.DataFrame.read_csv("BigFile.csv", chunks=50000)
# Your DataFrame has been divided into chunks and every function
# you apply will be applied to all chunks separately and in 
# parallel.# It has most of Pandas functions which you can use:
agg = df.groupby(["column"]).aggregate(["sum", "mean"])
agg.columns = new_column_namesdf_new = df.merge(agg.reset_index(), on="column", how="left")
# It have not compute result up until now,# but with .compute() it will compute now in parallel.
df_new.compute().head()

它们也有在机器集群上运行它们接口。

关于Dask的完整介绍,请看我的帖子这里

5.火炬.多重处理 ^

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

Photo by Matthew Hicks on Unsplash

torch.multiprocessing是 Python multiprocessing模块的包装器,其 API 与原始模块 100%兼容。所以可以用Queue的、Pipe的、Array的等等。这些都在 Python 的多重处理模块中。除此之外,为了使它更快,他们增加了一个方法share_memory_(),它允许数据进入任何进程都可以直接使用它的状态,因此将该数据作为参数传递给不同的进程不会复制该数据。

你可以分享Tensors,model 的parameters,你可以随心所欲的在 CPU 或者 GPU 上分享。

**Warning from Pytorch: (Regarding sharing on GPU)
**  CUDA API requires that the allocation exported to other processes remains valid as long as it’s used by them. You should be careful and ensure that CUDA tensors you shared don’t go out of scope as long as it’s necessary. This shouldn’t be a problem for sharing model parameters, but passing other kinds of data should be done with care. Note that this restriction doesn’t apply to shared CPU memory.

你可以在这里的“池和进程”部分使用上面的方法,为了获得更快的速度,你可以使用share_memory_()方法在所有进程之间共享一个Tensor(比方说)而不被复制。

**# Training a model using multiple processes:**import torch.multiprocessing as mp
def train(model):
    for data, labels in data_loader:
        optimizer.zero_grad()
        loss_fn(model(data), labels).backward()
        optimizer.step()  ***#*** *This will update the shared parameters*model = nn.Sequential(nn.Linear(n_in, n_h1),
                      nn.ReLU(),
                      nn.Linear(n_h1, n_out))model.share_memory() **#** Required for 'fork' method to workprocesses = []
for i in range(4): # No. of processes
    p = mp.Process(target=train, args=(model,))
    p.start()
    processes.append(p)for p in processes: p.join()

您也可以使用一组机器。更多信息请参见此处

**NOTE:** For little introduction (kind of) on usage of **Pycuda**, see Jupyter Notebook's PyCuda section [here](https://nbviewer.jupyter.org/github/PuneetGrov3r/MediumPosts/blob/master/SpeedUpYourAlgorithms/3%29%20Prallelization.ipynb#5.-Pycuda-(Optional)).

6.延伸阅读 ^

  1. https://blog . rise ml . com/comparising-Google-TPU v2-对抗-NVIDIA-v100-on-resnet-50-C2 BBB 6 a 51 e 5 e
  2. https://medium . com/synced review/Google s-TPU-chip-goes-public-in-challenge-to-NVIDIA-s-GPU-78 ced 56776 b5
  3. https://sebastianraschka . com/Articles/2014 _ multi processing . html
  4. https://towards data science . com/how-I-learn-to-love-parallelised-apply-with-python-pandas k-and-numba-f 06 b0b 367138
  5. https://www.geeksforgeeks.org/multiprocessing-python-set-2/
  6. https://www . geeksforgeeks . org/multi threading-in-python-set-2-synchron ization/
  7. https://medium . com/idealo-tech-blog/parallelisation-in-python-an-alternative-approach-b 2749 b 49 a 1 e
  8. https://stack overflow . com/questions/990102/python-global-interpreter-lock-Gil-workrance-on-multi-core-systems-using-task
  9. https://stack overflow . com/questions/38666078/fast-queue-of-read-only-numpy-arrays
  10. https://medium . com/@ rvprasad/data-and-chunk-size-matter-when-using-multi processing-pool-map-in-python-5023 c 96875 ef
  11. https://stackabuse.com/parallel-processing-in-python/

7.参考文献 ^

a)池和过程:

  1. https://docs.python.org/3/library/multiprocessing.html
  2. https://www . elli cium . com/python-multi processing-pool-process/

b)穿线:

3.https://realpython.com/python-gil/

4.https://stack overflow . com/questions/29270818/why-a-python-I-o-bound-task-not-blocked-by-the-Gil

5.https://stack overflow . com/questions/27455155/python-multi processing-combined-with-threading

6.https://stack overflow . com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python

7.https://stack overflow . com/questions/200469/a-process-and-a-thread 的区别是什么

c)达斯克

8.【https://ml.dask.org

9.https://docs.dask.org/en/latest/

d)火炬.多重处理:

9.https://pytorch.org/docs/stable/multiprocessing.html

10.https://pytorch.org/docs/stable/notes/multiprocessing.html

d) Pycuda:

11.https://documen.tician.de/pycuda/tutorial.html

12.https://github.com/inducer/pycuda/tree/master/examples

13.https://www3.nd.edu/~zxu2/acms60212-40212-S12/Lec-12-02.pdf

Suggestions and reviews are welcome.
Thank you for reading!

签名:

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

使用自适应 softmax,第 1 部分,将深度学习语言模型的速度提高 1000%

原文:https://towardsdatascience.com/speed-up-your-deep-learning-language-model-up-to-1000-with-the-adaptive-softmax-part-1-e7cc1f89fcc9?source=collection_archive---------5-----------------------

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

您希望将语言建模(LM)任务的速度提高 1000%,而准确性几乎没有下降吗?最近,脸书人工智能研究小组(FAIR) 的一篇论文,作者是格雷夫等人(2017) ,名为“GPU 的高效 softmax 近似”,展示了如何通过他们的“自适应 softmax”,在语言建模最耗时的方面之一,计算繁重的 softmax 步骤中获得大规模加速。使用 adaptive softmax 带来的巨大加速只带来了最小的准确性成本,因此任何正在进行语言建模的人都应该考虑使用它。在这篇博文的第 1 部分,我将全面解释自适应 softmax,然后在第 2 部分我将带你一步一步地完成 Pytorch 实现(附带 Jupyter 笔记本,它使用 Pytorch 的内置AdaptiveLogSoftmaxWithLoss函数。

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

In my experiment (see Part 2 of this blog), the adaptive softmax version of my model trained 3x faster than the same model using a traditional softmax. Final accuracy was nearly the same.

您可能知道,语言建模是自然语言处理(NLP)中最基本和最重要的任务之一。它包括创建一个模型,当给定一个单词序列作为输入时,该模型可以预测序列中的下一个单词(这就是在手机键盘上启用预测文本功能的原因)。为了做到这一点,语言模型中的最后一个计算步骤涉及从语言模型的词汇表中的大量单词中选择要选择的单词。由于词汇表通常包含 10 或 100 个成千上万的单词,这最后一步是非常计算和耗时的。

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

Predictive texting… Did you mean “auto cucumber” or “autocorrect”?

那么语言模型如何从词汇表中选择一个单词作为它的预测呢?在标记化/数值化预处理步骤之后,模型的典型输入可能是大小为 [seq_length,bs] 的张量,目标也可能是大小为 [seq_length,bs] (实际上是将输入移动到未来的一个时间点),其中 bs 是给定迷你批次中的示例数量, seq_length 是任何给定示例的长度。在访问嵌入后(数据现在的大小为*【seq _ length,bs,emb _ SZ】),通常数据通过多层使用 LSTM 或 GRU 单元的递归神经网络(RNN),得到大小为【seq _ length,bs,NH】*的最终隐藏状态输出,其中 nh 是最终层的维度。目标是计算 p(w|h) …即给定最终隐藏状态 h 的单词 w 的概率(这里, h 是给定示例和时间点的最终隐藏状态输出,长度为 nh 的向量)。

从隐藏状态输出中获得预测的 softmax 解决方案包括首先使用完全连接的线性层转换输出,输出特征的数量等于词汇表的大小(因此从大小 [seq_length,bs,nh] 到大小 [seq_length,bs,vs] ,其中 vs 是词汇表的大小)。然后将 softmax 公式 exp(x[i]) / sum(exp(x)) 应用于词汇表中的所有单词,为每个单词产生一个概率值(在 0 和 1 之间)。最后,通常使用负对数似然(NLL)损失将这些预测与目标进行比较。

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

Model design for a small RNN-network, with vs=25520 and nh=300. Notice that the last step is a fully-connected linear layer, that transforms the dimensionality from 300 to 25520.

将输出从大小 [seq_length,bs,nh] 转换为大小 [seq_length,bs,vs] 的步骤非常庞大!这最后一步的计算成本与你的词汇量成线性关系,如前所述,如果你想要一个像样的语言模型,词汇量将会很大。对于 nhvs 的典型值,此变换的权重矩阵可以是 size [1,000 x 200,000],它应用于数据集中每个示例的每个时间步长。这一步主导了训练和测试时的计算,对于任何语言模型来说都是一个主要的耗时步骤。

Grave 等人的 adaptive softmax 是解决这个问题的一个非常好的解决方案。要理解它,你首先需要理解层次化的 softmax,这是之前加速 softmax 的尝试(例如 Morin & Bengio,2005 )。你首先将每个单词 w 分配到一个唯一的簇 C(w) 。分层的 softmax 然后将 softmax 分成两个阶段:首先预测聚类 C ,然后对该聚类的所有单词进行 softmax。你可以将给定最终隐藏状态 h 的单词 w 的条件概率因式分解为 p(w|h) = p(w | C(w),h) * p( C(w) | h) …即给定隐藏状态 h 的单词 w 的概率,就是给定 C(w) 的概率简单来说,首先只需预测簇,然后预测簇中的哪个单词。这将把计算时间从与词汇表大小成线性减少到词汇表大小的平方根数量级。

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

Grave et al., 2017

然而,正如 Grave 等人的论文中所解释的,分层的 softmax 实际上在 GPU 上并不工作得那么好(对此的解释超出了本文的范围)。他们的自适应 softmax 是为 GPU 定制的分层 softmax 的简单变体。它利用了 Zipf 定律…观察到在任何语料库中,单词分布的大部分概率质量只被一小部分词汇覆盖。例如,在 Penn Treebank 数据集中,文档中 87%的单词仅被 20%的词汇覆盖。adaptive softmax 利用这一信息,根据单词的常见程度将词汇表中的单词分配到聚类中。

为了理解这种方法,最好从最简单的自适应 softmax 开始,其中词汇表被划分为两个集群,Vᴴᴱᴬᴰ和 Vᵀᴬᴵᴸ.在你的语料库中,少量的最频繁出现的单词应该进入 Vᴴᴱᴬᴰ,而 Vᵀᴬᴵᴸ应该包含剩余的不频繁出现的单词。所以在你的语料库中,一个词出现在 Vᴴᴱᴬᴰ的可能性比出现在 Vᵀᴬᴵᴸ(即 p(Vᴴᴱᴬᴰ) > p(Vᵀᴬᴵᴸ的可能性大得多,但是 Vᵀᴬᴵᴸ的词应该比 Vᴴᴱᴬᴰ.的多得多

在这个 2-聚类示例中的第一步是计算头部中所有单词的 softmax,加上对应于选择尾部聚类的 1 个额外的“单词”。也就是说,首先对 Vᴴᴱᴬᴰ + 1 个单词进行 softmax,其中额外的“单词”是它属于尾簇的概率。对于 Vᴴᴱᴬᴰ的单词, p(w|h) 简单来说就是 pᴴᴱᴬᴰ(w|h) …也就是说,只要计算给定 h 的单词在 Vᴴᴱᴬᴰ出现的概率。如果选择了与尾类别相对应的“单词”,那么只需要对 Vᵀᴬᴵᴸ).中的所有单词进行额外的 softmax 对于这些单词, p(w|h)是 pᴴᴱᴬᴰ(tail|h) * pᵀᴬᴵᴸ(w|h) …即,如果 pᴴᴱᴬᴰ(w|h) 表示“尾簇”,则取“尾簇”在头部的概率,乘以所选单词在尾部的概率。对于仅使用这种双集群方法,Grave 等人观察到比完整的 softmax 快 5 倍!

但是,通过使用更多的集群,您可以获得更大的收益…最佳组合似乎是 2-5 个集群。该方法与 2-聚类示例相同,除了第一个 softmax 将覆盖头部中的所有单词,再加上表示尾部聚类的许多附加“单词”。例如,对于 5 个集群的实现,初始 softmax 将超过 Vᴴᴱᴬᴰ + 4 个字。如果在初始 softmax 中选择了尾部聚类“单词”中的一个,那么将只在所选尾部中的单词上计算额外的 softmax。使用与上述相同的原理,最频繁的单词被分配到最小的簇中…头部和第一个尾部将具有更频繁的单词但具有更小的词汇量,最后一个尾部将具有不太频繁的单词和更大的词汇量。

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

Figure 3 from Grave et al., 2017. This figure shows a clustering with 4 clusters: a head and 3 tails. The head contains a small number of the most frequent words, whereas the tails contain larger numbers of less frequent words.

还有一个以最小的准确性代价提高速度的窍门,那就是每个集群被赋予不同的容量。以上, nh 是最终隐藏状态输出容量的度量。您需要高容量来正确预测最频繁出现的单词。然而,根据定义,生僻字只出现几次,因此无法通过您的模型很好地学习……为这些字使用高容量是一种浪费。因此,Grave 等人通过应用一个投影矩阵,将每个额外的尾部聚类的维数减少了 4 倍,从而减少了尾部聚类的 softmax 的最终隐藏状态输入的维数(一旦我们通过这篇博客文章的第 2 部分中的示例实现,这将变得更加清楚)。

就是这样!您可以使用上面概述的原则(或通过使用动态编程),试验聚类的数量和单词在聚类之间的分布,以找出哪些单词最适合您的数据集。总体而言,自适应 softmax 提供了巨大的速度提升(高达 10 倍!)在整个 softmax 上,以最小的精度代价。[查看我在 CNN 的博客文章,它也可以加速 NLP 的训练]。当你需要从大量可能的类别中进行选择时,自适应 softmax 会很有帮助,其中一些类别比其他类别更常见。这不仅适用于语言建模,也适用于许多其他 NLP 任务(神经机器翻译(NMT)、语音到文本等)。),可能对于许多非语言应用程序也是如此。查看这篇博文的第 2 部分来一步步了解自适应 softmax 的 Pytorch 实现,它使用了 Pytorch 的内置AdaptiveLogSoftmaxWithLoss函数。

参考资料:

Grave,Edouard 等人,“GPU 的高效 softmax 近似”arXiv 预印本 arXiv:1609.04309v3 (2017)。

莫兰,弗雷德里克,约舒厄·本吉奥。"分层概率神经网络语言模型."Aistats。第五卷。2005.

使用自适应 softmax,第 2 部分:Pytorch 实现,将深度学习语言模型的速度提高 1000%

原文:https://towardsdatascience.com/speed-up-your-deep-learning-language-model-up-to-1000-with-the-adaptive-softmax-part-2-pytorch-d47fe9a56152?source=collection_archive---------6-----------------------

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

在这篇博文的第一部分中,我解释了自适应 softmax 是如何工作的,以及它是如何将你的语言模型加速 1000%的。在第 2 部分中,我将带您一步一步地完成 Pytorch 实现(附带一个 Jupyter 笔记本,它使用 Pytorch 的内置AdaptiveLogSoftmaxWithLoss函数。

对于预处理,你将需要 fastai(见https://docs.fast.ai/),一个运行在 Pytorch 之上的深度学习库,它简化了训练神经网络。【对于想学习最前沿深度学习技术的人,我强烈推荐网上免费提供的杰瑞米·霍华德的 fast.ai 课程:https://course.fast.ai/】。我决定使用 Wikitext-2 数据集,这是一个相对较小的数据集,包含大约 200 万个令牌和大约 3.3 万个词汇。一旦数据被下载并正确格式化为 csv 文件,fastai 就可以轻松地快速标记、数字化并创建一个用于训练的数据加载器。我还下载了 GloVe 预训练单词向量,用于模型的单词嵌入。最后,我创建了一个处理训练的 modeler 类。

对于模型,我创建了一个简单版本的准递归神经网络(QRNN),它比传统的递归神经网络(RNNs)具有更好的预测精度,并且速度快 16 倍(参见 Bradbury 等人,2016)。我构建的网络相对较小,只有 4 层,每层有 300 个隐藏单元…我的目的是创建一个可以相对较快地达到一个不错的(如果不是最先进的)结果的网络。梯度裁剪的使用允许使用大的学习率( lr =1),这与内斯特罗夫的动量一起帮助训练快速收敛。

第 1 部分描述了数据如何通过网络传输。最后一个 QRNN 层的输出 x 的大小为*【seq _ length = 70,bs=50,NH = 300】*,其中 seq_length 为序列的长度, bs 为批量大小, nh 为最后一层的维数。

x, new_h = self.rnn(x, self.hidden)

常规 Softmax

对于常规的 softmax,最后一个 QRNN 层之后的下一步是完全连接的线性层(这里称为 fc1 ),它将输出转换为具有与词汇表大小相等的多个输出特征(因此从大小*【seq _ length,bs,nh】】到大小*【seq _ length,bs,vs】,其中 vs 是您的词汇表大小)。对于大多数语言模型来说,计算时间由这个操作决定。

#self.fc1 = nn.Linear(nh, vs)
x= self.fc1(x)

然后,模型输出这些值的 log-softmax,负 log-likelihood 用于计算损失。

return F.log_softmax(x.view(-1,vs),dim=1)
...
loss = self.criterion(output, target)

自适应 Softmax

自适应 softmax 的方法略有不同。最后一个 QRNN 层的输出 x 仍然是大小*【序列长度=70,bs=50,NH = 300】。然后将输出调整到 [seq_lengthbs,nh] ,并输入到AdaptiveLogSoftmaxWithLoss函数,该函数为您计算损失。

x,new_h = self.rnn(x, self.hidden)
x=x.view(-1,x.size()[2])
#self.out=nn.AdaptiveLogSoftmaxWithLoss(nh, vs, cutoffs=[round(vs/15), 3*round(vs/15)], div_value=4)
return self.out(x,target)
...
loss= output.loss

让我们看看 AdaptiveLogSoftmaxWithLoss 函数是做什么的。你可以看到我指定了截止值= [round(vs/15),3*round(vs/15)]。这意味着我将把我的 25520 个单词分成 3 组。头部聚类包含 1701 个最常见的单词(即,词汇表的 1/15),加上两个“单词”来指示两个尾部聚类的可能性,总共 1703 个单词。第一个尾簇包含接下来的 3402 个最常用的单词(即词汇表的 2/15),第二个尾簇包含剩余的 20417 个最不常用的单词(即词汇表的 12/15)。为了做到这一点,我需要确保我的词汇表(这里称为 itos )是按照从最常见到最不常见的单词的顺序组织的。

正如我在第 1 部分中提到的,这些集群中的每一个都被赋予了不同的容量,其相对大小由赋予 AdaptiveLogSoftmaxWithLoss 函数的 div_value 指定。为 4 的 div_value 意味着每个附加集群的容量将比前一个集群小 4 倍。在这种情况下,首簇的容量为 300,第一个尾簇的容量为 75,第二个尾簇的容量仅为 18。这意味着对于头部聚类中最常见的单词,它将直接将维度从 300 转换为 1703 输出特征(因为 1703 是头部中词汇单词的数量)。然而,对于第一个尾部聚类,在第二个线性层将维度转换为 3402 个输出特征之前,初始线性层将首先将维度减少到 75 个特征。维度的初始减少加快了处理时间(与从 300 个输入要素到 3402 个输出要素的直接线性变换相比)。对于最后一个尾部聚类中最不常用的单词,发生类似的过程。

(out): AdaptiveLogSoftmaxWithLoss(
    (head): Linear(in_features=300, out_features=1703, bias=False)
    (tail): ModuleList(
      (0): Sequential(
        (0): Linear(in_features=300, out_features=75, bias=False)
        (1): Linear(in_features=75, out_features=3402, bias=False)
      )
      (1): Sequential(
        (0): Linear(in_features=300, out_features=18, bias=False)
        (1): Linear(in_features=18, out_features=20417, bias=False)
      )
    )

结果

传统 softmax 网络的最终损耗为 4.84(困惑度= 127),而自适应 softmax 网络的最终损耗为 4.88(困惑度= 132)。这是不到 1%的最终损失差异(和 4%的困惑差异)。另一方面,自适应 softmax 网络在 7.80 分钟(7 分 48 秒)完成计算,而传统 softmax 网络在 24.55 分钟完成计算。因此,两种方法的结果在准确性方面非常相似,但自适应 softmax 网络的运行速度快 3 倍!这些结果与 Grave 等人在 Text8 数据集(与我使用的 Wikitext-2 数据集大小相似)上的实验相匹配…与完整的 softmax 相比,最终损失几乎相同,但加速几乎是 3 倍。

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

Plotting time vs loss for the two approaches. Even though the final result is nearly the same, the adaptive softmax network completes training 3x quicker than an identical traditional softmax network.

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

Plotting epoch vs loss for the traditional and adaptive softmax approaches. Training proceeds almost identically between the two approaches, although the traditional softmax network completes with a 1% lower loss than the adaptive softmax network.

那么自适应的 softmax 相比传统的 softmax 有多好呢?如果我决定进行更多时期的训练,很可能最终的损失会更大。然而,Grave 等人(2017)和其他 NLP 研究人员(例如 Dauphin 等人,2016)表明,他们实际上能够使用 adaptive softmax 在各种数据集上实现最先进的语言建模结果。因此,您应该考虑将自适应 softmax 添加到您的语言建模工具箱中,因为它以最小的准确性成本提供了超过完整 softmax 的巨大速度。参见第 1 部分了解自适应 softmax 如何工作的完整解释。

参考资料:

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

多芬,扬恩 n .等人,《用门控卷积网络进行语言建模》arXiv 预印本 arXiv:1612.08083 (2016)。

格雷夫,爱德华,等,“GPU 的有效软最大近似”arXiv 预印本 arXiv:1609.04309v3 (2017)。

用广播和 PyTorch 加速你的 Python 代码

原文:https://towardsdatascience.com/speed-up-your-python-code-with-broadcasting-and-pytorch-64fbd31b359?source=collection_archive---------6-----------------------

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

Image credit: Saffu at https://unsplash.com/photos/E4kKGI4oGaU

广播使得向量化您的代码成为可能,在 Numpy 的底层 C 实现中执行数组运算,而不必制作不必要的数据副本。根据不同的情况,这可以给你的代码带来显著的加速。此外,事实证明,如果您在 Numpy 中使用广播,您可以轻松地将代码移植到 PyTorch,然后在您的 GPU 上运行相同的代码,从而获得更高的速度!

当我做硕士论文的时候,我花了很多时间处理大量的激光雷达数据。其中一个步骤是移除属于场景中静态对象(建筑物、栅栏等)的所有点测量。场景中的每个静态对象都被建模为一个矩形对象,这实质上意味着我必须检查每个激光雷达测量值是否落在任何矩形内。我论文中使用的激光雷达工作在 10Hz,每次扫描包含大约 100,000 到 150,000 次测量,这意味着一秒钟的激光雷达数据对应于需要处理的 1-150 万个激光雷达点。那时我不了解 Python 或广播,所以我的这个处理步骤的实现不是那么快或有效。现在,我将以这个问题为例,展示如何使用 broadcasting 和 PyTorch 编写一个非常快速的算法实现。

如何确定一个点是否在矩形中

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

有一个简单的公式可以用来确定一个点是否在矩形内。如果我们假设矩形的每个角(x,y)被表示为 A,B,C,D,并且我们所讨论的点位于(x,y)点 P,那么公式可以被表示为

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

AP denotes the vector from point A to point P. The · sign denotes dot multiplication between the vectors.

如果这两个条件都满足,则该点位于矩形内,否则不满足。

没有广播的实现

如果我们以“标准”的方式实现算法,我们会得到这样的结果:

def in_boxes(boxes, points):
  # (N, 4, 2) = boxes.shape
  # (M, 2) = points.shape w = np.zeros(points.shape[0])
  for (i, point) in enumerate(points):
   in_box = False
   for box in boxes:
     (A, B, C, D) = box
     AP = (point — A)
     AB = (B — A)
     AD = (D — A)
     cond0 = 0 < np.dot(AP, AB) < np.dot(AB, AB)
     cond1 = 0 < np.dot(AP, AD) < np.dot(AD, AD)
     in_box = in_box or (cond0 and cond1)
   if in_box:
     w[i] = 1
   else:
     w[i] = 0
  return w

输入参数对应于出现的框和我们想要检查的点。盒子由形状为(N,4,2)的数组表示,其中N 对应于盒子的数量,4反映了我们有角(A,B,C,D),而2反映了每个角的 xy 位置。我们想要研究的点由形状为(M, 2)的数组表示,其中M对应于点的数量,2反映每个点的 xy 位置。

在我的 MBP 2014 上用 3 个盒子和 1,000,000 个积分运行这个功能大约需要 **21.47 秒。**让我们看看使用 Numpy 广播重写函数时会发生什么。

广播,我怎么用?

在我们继续实现之前,让我们快速讨论一下如何使用广播。判断一个点是否在矩形中的算法的核心部分是计算向量AP,即每个点和每个坐标的P-A。所有的 A 坐标可以存储在一个形状为(N, 2)的数组A中。类似地,我们有M点,这些点存储在形状为(M, 2)的数组P中。现在,如果我们以下面的方式给数组增加一个额外的维度,我们将增加秩(维度的数量),数组将有新的形状

>>> A.shape
(M, 2)
>>> P.shape
(N, 2)
>>> A = A[:, None, :]
>>> P = P[None, ...]
>>> A.shape
(M, 1, 2)
>>> P.shape
(1, N, 2)

正是在这个阶段,广播开始发挥作用。如果我们现在选择计算两个数组之间的差,我们将得到一个形状为(M, N, 2)的数组

>>> (P - A).shape
(M, N, 2)

因此,Numpy 正在广播大小为 1 的每个维度中的数据,以便匹配两个数组的大小。这意味着 Numpy 本质上在第一维度上重复了 N 次A数组,在第 0 维度上重复了 M 次P数组。这种数据重复是在底层 C 实现中执行的,并且利用了指针,这意味着不会为了执行重复而复制数据(也就是说,我们不会因为必须复制数据而耗尽空间)。

这基本上展示了如何使用广播。通过向我们的数组添加额外的 1 维,并确保它们以相同的秩结束,我们可以执行任何类型的算术运算,并以我们想要的方式结束结果排序,而不必使用 for 循环。至此,让我们看看如何使用广播实现in_boxes算法

广播的实现

使用广播实现算法非常简单。为了更容易理解发生了什么,我在每一行都添加了注释,描述每个操作的结果形状。由于 Numpy 的np.dot()方法不支持广播,我将数组逐元素相乘(因为它支持广播),然后在最后一个维度上求和,因为这个操作对应于点积。

def in_boxes_bc(boxes, points):
  # (N, 4, 2) = boxes.shape
  # (M, 2) = points.shape (A, B, C, D) = np.split(boxes, 4, axis=1)  # (N, 1, 2)
  AM = (points[None, ...] - A)  # (N, M, 2)
  AB = (B - A)  # (N, 1, 2)
  AD = (D - A)  # (N, 1, 2) 

  AM_AB = np.sum(AM * AB, axis=-1)  # (N, M)
  AB_AB = np.sum(AB * AB, axis=-1)  # (N, 1)
  AM_AD = np.sum(AM * AD, axis=-1)  # (N, M)
  AD_AD = np.sum(AD * AD, axis=-1)  # (N, 1)

  cond0 = (0 < AM_AB) & (AM_AB < AB_AB)  # (N, M)
  cond1 = (0 < AM_AD) & (AM_AD < AD_AD)  # (N, M)

  in_box = cond0 & cond1  # (N, M) = in_box.shape
  w = np.any(in_box, axis=0)
  return w

在我的 MBP 2014 上用 3 个盒子和 1,000,000 个点运行这个版本的算法大约需要 0.36 秒。与之前花费的时间 21.47 秒相比,这意味着我们实现了到 60 倍的速度提升!

使用 PyTorch 在 GPU 上运行

该算法的广播变体的真正巧妙之处在于,它非常容易移植到 PyTorch,这允许我们在 GPU 上运行相同的算法,从而可以进一步加快算法的速度!Numpy 和 PyTorch 都以相同的方式实现广播,唯一的警告是 PyTorch 对一些数学函数及其参数有稍微不同的命名方案。(cuda()函数用于将数据移动到 GPU,而cpu()用于将数据从 GPU 移回)。

def in_boxes_torch(boxes, points):
  boxes = torch.DoubleTensor(boxes).cuda()
  points = torch.DoubleTensor(points).cuda()

  dd = torch.chunk(boxes, 4, dim=1)
  (A, B, C, D) = dd AM = (points[None, ...] - A)
  AB = (B - A)
  AD = (D - A) AM_AB = torch.sum(AM * AB, dim=-1)
  AB_AB = torch.sum(AB * AB, dim=-1)
  AM_AD = torch.sum(AM * AD, dim=-1)
  AD_AD = torch.sum(AD * AD, dim=-1) cond0 = (0 < AM_AB) & (AM_AB < AB_AB)
  cond1 = (0 < AM_AD) & (AM_AD < AD_AD)

  in_box = cond0 & cond1
  # PyTorch does not have anything corresponding to np.any()
  w = (torch.sum(in_box, dim=0) > 0)
  return w.cpu()

使用与之前相同的场景(3 个盒子和 1,000,000 个点),但这次在 GTX 980 Ti 上运行大约需要 0.026 秒* ,与使用广播的版本相比速度增加了 ~14x ,与算法的第一个版本相比速度增加了 ~826x

广播可以用来使某些类型的算法运行得更快,这很酷(尽管应该注意到情况并不总是这样,所以在开始使用广播进行任何类型的计算之前要小心)。能够快速移植代码在 GPU 上运行的额外好处是非常惊人的,特别是因为 ML/DL 领域的大多数人通常都可以使用非常强大的 GPU。速度上的差异很重要,因为这可能是等待几分钟算法完成与等待几小时(甚至几天)算法完成之间的差异!).

非常感谢 Adam Fjeldsted 校对这篇文章!

关于在 GPU 上运行代码,有两点值得注意:

  1. 功能 *in_boxes_torch* 包括将数据移动到 GPU
  2. 第一次使用 PyTorch 将数据移动到 GPU 比后续的移动花费更多的时间,因为建立到 GPU 的连接会有一些开销(这种开销与您试图移动到 GPU 的数据大小无关,在我的机器上大约是 2 秒)。因此,在运行和计时 *in_boxes_torch* 函数之前,我先将一些虚拟数据移入和移出 GPU。

加速卷积神经网络

原文:https://towardsdatascience.com/speeding-up-convolutional-neural-networks-240beac5e30f?source=collection_archive---------2-----------------------

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

Source: http://yesofcorsa.com/wp-content/uploads/2017/04/High-Speed-Photo.jpg

加速卷积神经网络训练而不显著影响精度的方法综述。

有趣的是,完全连接的层是神经网络占用大量内存的主要原因,但速度很快,而卷积虽然参数数量很少,但却消耗了大部分计算能力。实际上,卷积是如此的计算饥渴,以至于它们是我们需要如此多的计算能力来训练和运行最先进的神经网络的主要原因。

我们能设计出既快速又高效的卷积吗?

某种程度上——是的!

有一些方法可以在不严重降低模型精度的情况下加速卷积。在这篇博文中,我们将考虑以下方法。

  • 卷积核的因子分解/分解
  • 瓶颈层
  • 更宽的回旋
  • 深度可分卷积

下面,我将深入研究所有这些方法的实现和背后的原因。

简单因式分解

让我们从 NumPy 中的以下示例开始

>>> from numpy.random import random
>>> random((3, 3)).shape == (random((3, 1)) * random((1, 3))).shape
>>> True

你可能会问,为什么我要给你看这个愚蠢的片段?答案是,它表明你可以写一个 NxN 矩阵,把卷积核想象成 2 个较小的矩阵/核的乘积,形状为 Nx1 和 1xN。回想一下,卷积运算需要in_channels * n * n * out_channels 参数或权重。此外,请记住,每个重量/参数都需要激活。因此,参数数量的任何减少都将减少所需的操作数量和计算成本。

假设卷积运算实际上是使用张量乘法来完成的,而张量乘法是多项式的,依赖于张量的大小,正确应用因式分解应该会产生明显的加速。

在喀拉斯,它看起来像这样:

# k - kernel size, for example 3, 5, 7...
# n_filters - number of filters/channels
# Note that you shouldn't apply any activation
# or normalization between these 2 layers
fact_conv1 = Conv(n_filters, (1, k))(inp)
fact_conv1 = Conv(n_filters, (k, 1))(fact_conv1)

不过,请注意,不建议使用最接近输入卷积层的因子。此外,分解 3x3 卷积甚至会损害网络性能。最好为更大的内核保留它们。

在我们深入这个主题之前,有一个更稳定的方法来分解大内核:只是堆叠较小的内核。例如,不使用 5x5 卷积,而是堆叠两个 3x3 卷积,或者如果您想要替换 7x7 内核,则堆叠 3 个卷积。有关更多信息,请参见[4]。

瓶颈层

瓶颈层背后的主要思想是通过减少输入通道的数量(也称为输入张量的深度)来减少内核大于 1×1 的卷积层中的输入张量的大小。

这是它的 Keras 代码:

from keras.layers import Conv2D# given that conv1 has shape (None, N, N, 128)conv2 = Conv2D(96, (1, 1), ...)(conv1) # squeeze
conv3 = Conv2D(96, (3, 3), ...)(conv2) # map
conv4 = Conv2D(128, (1, 1), ...)(conv3) # expand

几乎所有的 CNN,从革命性的概念 1 到现代的 DenseNet,都在以这样或那样的方式使用瓶颈层。这种技术有助于保持参数的数量,从而降低计算成本。

更宽的回旋

另一种加速卷积的简单方法是所谓的宽卷积层。你看,你的模型卷积层数越多,速度就越慢。然而,你需要大量卷积的表示能力。你是做什么的?你使用更少但更胖的层,其中脂肪意味着每层更多的果仁。为什么有效?因为对于 GPU 或其他大规模并行机器来说,处理单个大块数据比处理大量较小的数据更容易。更多信息可以在[6]中找到。

# convert from
conv = Conv2D(96, (3, 3), ...)(conv)
conv = Conv2D(96, (3, 3), ...)(conv)
# to
conv = Conv2D(128, (3, 3), ...)(conv)
# roughly, take the sqrt of the number of layers you want
# to merge and multipy the number to
# the number of filters/channels in the initial convolutions
# to get the number of filters/channels in the new layer

深度可分卷积

在深入研究这种方法之前,要知道它非常依赖于可分离卷积在给定框架中的实现方式。就我而言,TensorFlow 可能会对这种方法进行一些特定的优化,而对于其他后端,如 Caffe、CNTK 或 PyTorch,还不清楚。

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

Vincent Vanhoucke, April 2014, “Learning Visual Representations at Scale”

这个想法是,不是在图像的所有通道上联合卷积,而是在每个深度为channel_multiplier的通道上运行单独的 2D 卷积。in_channels * channel_multiplier个中间通道连接在一起,并使用 1x1 卷积映射到out_channels。[5]这样一来,需要训练的参数就少得多。[2]

# in Keras
from keras.layers import SeparableConv2D
...
net = SeparableConv2D(32, (3, 3))(net)
...
# it's almost 1:1 similar to the simple Keras Conv2D layer

事情没那么简单。请注意,可分卷积有时不是训练。在这种情况下,将深度倍增从 1 修改为 4 或 8。还要注意,这些算法在小数据集上效率不是很高,比如 CIFAR 10,此外还有 MNIST。另一件要记住的事情是,不要在网络的早期阶段使用可分卷积。

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

Source: V. Lebedev et al, Speeding-up Convolutional Neural Networks Using Fine-tuned CP-Decomposition

CP 分解和高级方法

上述因式分解方案在实践中工作良好,但是非常简单。它们可以工作,但是还没有达到极限。有许多工作,包括 V. Lebedev 等人的[3],向我们展示了不同的张量分解方案,这些方案大大减少了参数的数量,从而减少了所需的计算量。

受[1]启发,下面是如何在 Keras 中进行 CP 分解的代码片段:

# **kwargs - anything valid for Keras layers,
# like regularization, or activation function
# Though, add at your own risk# Take a look into how ExpandDimension and SqueezeDimension
# are implemented in the associated Colab Notebook
# at the end of the articlefirst = Conv2D(rank, kernel_size=(1, 1), **kwargs)(inp)
expanded = ExpandDimension(axis=1)(first)
mid1  = Conv3D(rank, kernel_size=(d, 1, 1), **kwargs)(exapanded)
mid2  = Conv3D(rank, kernel_size=(1, d, 1), **kwargs)(mid1)
squeezed = SqueezeDimension(axis=1)(mid2)
last  = Conv2D(out,  kernel_size=(1, 1), **kwargs)(squeezed)

遗憾的是,它不起作用,但是它给了你在代码中应该是什么样子的直觉。顺便说一下,文章顶部的图片是 CP 分解如何工作的图形解释。

应该注意诸如 TensorTrain 分解和 Tucker 这样的方案。对于 PyTorch 和 NumPy,有一个名为 Tensorly 的很棒的库,它为你做所有的底层实现。在 TensorFlow 中没有与之接近的东西,但还是有一个 TensorTrain(又名 TT 方案)的实现,这里是。

收场白

完整的代码目前可以作为一个带有特斯拉 K80 GPU 加速器的合作笔记本获得。给自己做一份拷贝,享受修改代码的乐趣。

如果你正在读这篇文章,我想感谢你,并希望上面写的对你有很大的帮助,就像对我一样。请在评论区让我知道你的想法。你的反馈对我很有价值。

另外,如果你喜欢这篇文章,别忘了鼓掌😏或者关注我更多类似的文章。

参考

[1]https://medium . com/@ krishnate jakrothapalli/hi-rain-4e 76039423 e 2
【2】f . Chollet,Xception:具有深度方向可分离卷积的深度学习,https://arxiv.org/abs/1610.02357v2
【3】v . lebe dev 等人,使用微调 CP 分解加速卷积神经网络,https://arxiv.org/abs/1412.6553
【4】c . Szegedy 等人,重新思考 Inception 架构

加速您的算法第 4 部分— Dask

原文:https://towardsdatascience.com/speeding-up-your-algorithms-part-4-dask-7c6ed79994ef?source=collection_archive---------4-----------------------

与 Dask 并行运行您的 Pandas/Numpy/Sklearn/Python 代码

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

Photo by Raul Cacho Oses on Unsplash

这是我写的系列文章中的第四篇。所有帖子都在这里:

  1. 加速你的算法第一部分— PyTorch
  2. 加速你的算法第二部分—数字
  3. 加速您的算法第 3 部分—并行化
  4. 加速您的算法第 4 部分— Dask

而这些与 Jupyter 笔记本 搭配在这里可以得到:

[Github-speedupyourlightms和**[ka ggle】**

(Edit-1/2/2019)添加了 dask . distributed . local cluster 的更多信息/可能用法

索引

  1. 简介
  2. 数据类型
  3. 延迟
  4. 分发
  5. 机器学习
  6. 延伸阅读
  7. 参考文献
***NOTE:*** This post goes with ***Jupyter Notebook*** available in my Repo on **Github**:[[SpeedUpYourAlgorithms-Dask](https://nbviewer.jupyter.org/github/PuneetGrov3r/MediumPosts/blob/master/SpeedUpYourAlgorithms/4%29%20Dask.ipynb)]
and on **Kaggle:** [[SpeedUpYourAlgorithms-Dask](https://www.kaggle.com/puneetgrover/speed-up-your-algorithms-dask)]

1.简介 ^

随着对机器学习算法并行化需求的增加,由于数据大小甚至模型大小的指数增长,如果我们有一个工具可以帮助我们并行处理Pandas的数据帧,可以并行处理Numpy的计算,甚至可以并行处理我们的机器学习算法(可能是来自sklearntensorflow的算法),而没有太多麻烦,这将是非常有用的。

但这样的库确实存在,它的名字叫DaskDask是一个并行计算库,它不仅帮助并行化现有的机器学习工具(PandasNumpy )[ ,即使用高级集合 ],还帮助并行化低级任务/功能,并可以通过制作任务图来处理这些功能之间的复杂交互。[ 即使用低级调度程序 ]这类似于 Python 的线程或多处理模块。

他们还有一个独立的机器学习库dask-ml,它与现有的库如sklearnxgboosttensorflow集成在一起。

Dask 通过绘制任务之间的交互图,将分配给它的任务并行化。通过使用Dask.visualize()方法来可视化您正在做的事情将会非常有帮助,该方法可用于它的所有数据类型和您计算的复杂任务链。该方法将输出您的任务的图表,如果您的任务在每一层都有许多节点(即您的任务链结构在许多层都有许多独立的任务,例如数据块上的可并行化任务),那么Dask将能够并行化它们。

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

**Note:**
Dask is still a relatively new project. It has a long way to go. Still if you don't want to go through learning a completely new API (like in case of PySpark) Dask is your best option, which surely will get better and better in future. 
Still Spark/PySpark is ways ahead and will still keep on improving. It is a well established Apache project. I will publish a post on PySpark in coming months.(Today: April'19)
If you want to start with PySpark, read this comment [here](https://medium.com/@grover.puneet1995/i-have-stated-in-third-section-4f8206c4f081).

2.数据类型 ^

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

Photo by Kai Oberhäuser on Unsplash

Dask中的每种数据类型都提供了现有数据类型的分布式版本,如Pandas中的DataFramenumpy中的ndarrayPython中的list。这些数据类型可能比你的内存大,Dask将以Blocked的方式对你的数据并行(y)运行计算。Blocked通过执行许多小计算来执行大计算,即以块为单位,块的数量是chunks的总数。

a)数组:

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

Many Numpy arrays in a grid as Dask Array

Dask Array 通过将非常大的数组分成块并并行执行这些块来对它们进行操作。它有许多可用的 numpy 方法,你可以用它们来加速。但是其中的部分没有实现。

Dask 数组可以从任何类似数组的结构中读取,只要它支持类似 numpy 的切片,并且通过使用dask.array.from_array方法具有.shape属性。也可以从.npy.zarr文件中读取。

import dask.array as da
import numpy as nparr = numpy.random.randint(1, 1000, (10000, 10000))darr = da.from_array(arr, chunks=(1000, 1000))
# It will make chunks, each of size (1000, 1000)
darr.npartitioins
# 100

当你的数组非常大的时候(也就是说,它们放不进内存)可以使用它,而numpy对此无能为力。所以,Dask把它们分成数组块,给你并行操作。

现在,Dask对每一个方法进行懒惰的评估。所以,要真正计算一个函数的值,你必须使用.compute()方法。它将在块中并行计算结果,同时并行处理每个独立的任务。

result = darr.compute()

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

  1. Numpy faster than Dask for smaller number of elements; 2) Dask taking over Numpy for around 1e7 elements; 3) Numpy not able to produce results for higher number of elements as it is not able to put them on memory.

b)数据帧:

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

5 Pandas’ DataFrames each providing monthly data (can be from diff files) in one Dask DataFrame

Dask Arrays类似,Dask DataFrame通过将文件划分为块,并对这些块并行地执行计算功能,来对无法容纳在内存中的非常大的数据文件进行并行计算。

import dask.dataframe as dd
df = dd.read_csv("BigFile(s).csv", blocksize=50e6)

现在,您可以应用/使用pandas库中的大多数功能,并在此处应用。

agg = df.groupby(["column"]).aggregate(["sum", "mean", "max", "min"])
agg.columns = new_column_names # see in notebook
df_new = df.merge(agg.reset_index(), on="column", how="left")
df_new.compute().head()

c)袋子:

Dask Bag s 对包含多种数据类型元素的Python``listlike 对象进行并行化计算。当您试图处理 JSON blobs 或日志文件之类的半结构化数据时,这很有用。

import dask.bag as db
b = db.from_txt("BigSemiStructuredData.txt")
b.take(1)

Dask bags 逐行读取,.take方法输出指定行数的元组。

Dask Bag对这类 Python 对象集合执行mapfilterfoldgroupby等操作。它使用 Python 迭代器以较小的内存占用并行完成这一任务。它类似于 PyToolz T21 的平行版本或者 PySpark RDD 的 Pythonic 版本。

filtered = b.filter(lambda x: x["Name"]=="James")\
                     .map(lambda x: x["Address"] = "New_Address")
filtered.compute()

3.延期 ^

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

Photo by Andrea Cau on Unsplash

如果您的任务有点简单,并且您不能或不想使用这些高级集合来完成,那么您可以使用低级调度程序来帮助您使用dask.delayed接口并行化您的代码/算法。dask.delayed也做懒计算。

import dask.delayed as delay@delay
def sq(x):
    return x**2
@delay 
def add(x, y):
    return x+y
@delay 
def sum(arr):
    sum=0
    for i in range(len(arr)): sum+=arr[i]
    return sum

您可以根据需要在这些函数之间添加复杂的交互,使用前一个任务的结果作为下一个任务的参数。Dask不会立即计算这些函数,而是会为您的任务制作一个图表,有效地整合您使用的函数之间的交互。

inputs = list(np.arange(1, 11))#Will be addin' dask.delayed to list
temp = []
for i in range(len(inputs)):
    temp.append(sq(inputs[i]))  # Compute sq of inputs and save 
                                # delayed in list
inputs=temp; temp = []
for i in range(0, len(inputs)-1, 2):
    temp.append(add(inputs[i]+inputs[i+1])) # Add two consecutive
                                            # results from prev step
inputs = temp
result = sum(inputs) # Sum all results from prev step
results.compute()

您可以用许多可能的小块为任何可并行化的代码增加延迟,并获得加速。它可以是你想要计算的许多函数,比如上面的例子,或者使用pandas.read_csv并行读取许多文件。

4.分发 ^

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

Photo by Chinh Le Duc on Unsplash

首先,到目前为止,我们一直使用Dask的默认调度程序来计算任务的结果。但是您可以根据自己的需要从Dask的可用选项中进行更改。

Dask附带四个可用的调度程序:

  • threaded”:由线程池支持的调度程序
  • processes”:由进程池支持的调度程序
  • single-threaded”(又名"sync"):一个同步调度器,适合调试
  • distributed:用于在多台机器上执行图形的分布式调度程序
result.compute(scheduler="single-threaded") # for debugging
# Or
dask.config.set(scheduler="single-threaded")
result.compute()**NOTE: (from official page** [**here**](https://render.githubusercontent.com/view/ipynb?commit=33efceb9ba76b16ce3bf51d6210546e257f0874b&enc_url=68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f6461736b2f6461736b2d7475746f7269616c2f333365666365623962613736623136636533626635316436323130353436653235376630383734622f30355f64697374726962757465642e6970796e62&nwo=dask%2Fdask-tutorial&path=05_distributed.ipynb&repository_id=39199909&repository_type=Repository#Some-Questions-to-Consider:)**)**
Threaded tasks will work well when the functions called release the [GIL](https://wiki.python.org/moin/GlobalInterpreterLock), whereas multiprocessing will always have a slower start-up time and suffer where a lot of communication is required between tasks.# And you can get the scheduler by the one of these commands:
dask.threaded.get, dask.multiprocessing.get, dask.local.get_sync
# last one for "single-threaded"

但是,Dask多了一个调度器,dask.distributed,出于以下原因,可以优先选择它:

  1. 它提供对异步 API 的访问,特别是 Futures
  2. 它提供了一个诊断控制面板,可以提供关于性能和进度的宝贵见解
  3. 它处理数据局部性更加复杂,因此在需要多个进程的工作负载上比多处理调度程序更有效。

您可以通过导入和创建一个Client来创建Daskdask.distributed调度程序。

from dask.distributed import Client
client = Client() # Set up a local cluster# You can navigate to [http://localhost:8787/status](http://localhost:8787/status) to see the 
# diagnostic dashboard if you have Bokeh installed.

现在,您可以通过使用client.submit方法将您的任务提交给这个集群,将函数和参数作为它的参数。然后我们可以通过使用client.gather.result方法来收集我们的结果。

sent = client.submit(sq, 4) # sq: square function
result = client.gather(sent) # Or sent.result()

您还可以通过使用dask.distributed.progress来查看当前单元格中的任务进度。您还可以通过使用dask.distributed.wait明确选择等待任务完成。

更多信息请看这里

**Note: (Local Cluster)** At times you will notice that **Dask** is exceeding memory use, even though it is dividing tasks. It could be happening to you because of the function you are trying to use on your **dataset** wants most of your data for processing, and multiprocessing can make things worse as all workers might try to copy **dataset** to memory. This can happen in aggregating cases.
Or maybe you want to restrict Dask to use only specific amount of memory. In these cases you can use **Dask.distributed.LocalCluster** parameters and pass them to **Client**() to make a **LocalCluster** using cores of your Local machines.**from** dask.distributed **import** Client, LocalCluster
client = **Client**(n_workers=1, threads_per_worker=1, processes=False,
                memory_limit='25GB', scheduler_port=0, 
                silence_logs=True, diagnostics_port=0)
client 'scheduler_port=0' and 'diagnostics_port=0' will choose random port number for this particular client. With 'processes=False' **dask**'s client won't copy dataset, which would have happened for every process you might have made.
You can tune your client as per your needs or limitations, and for more info you can look into parameters of **LocalCluster.** You can also use multiple clients on same machine at different ports.

5.机器学习 ^

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

Photo by James Pond on Unsplash

Dask也有帮助并行运行最流行的机器学习库的库,比如sklearntensorflowxgboost

在机器学习中,你可能会面临几个不同的缩放问题。扩展策略取决于您面临的问题:

  1. 大型模型:数据适合 RAM,但训练时间太长。许多超参数组合、许多模型的大型集合等。
  2. 大型数据集:数据比 RAM 大,采样是不可行的。

因此,您应该:

  • 对于内存适配问题,只需使用 scikit-learn(或者你最喜欢的 ML 库);
  • 对于大型模型,使用dask_ml.joblib和您最喜欢的 scikit-learn 估算器;和
  • 对于大型数据集,使用dask_ml估算器。

a)预处理:

dask_ml.preprocessing包含sklearn中的一些功能,如RobustScalarStandardScalarLabelEncoderOneHotEncoderPolynomialFeatures等。,还有一些自己的如CategorizerDummyEncoderOrdinalEncoder等。

您可以像使用Pandas数据框一样使用它们。

from dask_ml.preprocessing import RobustScalardf = da.read_csv("BigFile.csv", chunks=50000)rsc = RobustScalar()
df["column"] = rsc.fit_transform(df["column"])

您可以使用DaskDataFrame上的Dask的预处理方法,从sklearnmake_pipeline方法制作一个管道。

b)超参数搜索:

Dask有来自sklearn的用于超参数搜索的方法,如GridSearchCVRandomizedSearchCV等。

from dask_ml.datasets import make_regression
from dask_ml.model_selection import train_test_split, GridSearchCV
X, y = make_regression(chunks=50000)
xtr, ytr, xval, yval = test_train_split(X, y)gsearch = GridSearchCV(estimator, param_grid, cv=10)
gsearch.fit(xtr, ytr)

如果你使用partial_fit和你的估算器,你可以使用dask-mlIncrementalSearchCV

**NOTE: (from Dask)**
If you want to use post-fit tasks like scoring and prediction, then underlying estimators scoring method is used. If your estimator, possibly from sklearn is not able to handle large dataset, then wrap your estimator around "dask_ml.wrappers.ParallelPostFit". It can parallelize methods like "predict", "predict_proba", "transform" etc.

c)模型/估计器:

Dask有一些线性模型(LinearRegressionLogisticRegression等)。)、一些聚类模型(KmeansSpectralClustering)、一个用Tensorflow 聚类操作的方法、使用Dask训练XGBoost 模型的方法。

如果你的训练数据很少,你可以使用sklearn的模型和Dask,或者使用ParallelPostFit的包装器(如果你的测试数据很多)。

from sklearn.linear_model import ElasticNet
from dask_ml.wrappers import ParallelPostFitel = ParallelPostFit(estimator=ElasticNet())el.fit(Xtrain, ytrain)
preds = el.predict(Xtest)

如果你的数据集不大但是你的模型很大,那么你可以使用joblib。许多sklearns算法是为并行执行而编写的(您可能已经使用了n_jobs=-1参数),使用joblib来利用线程和进程来并行化工作负载。要使用Dask进行并行化,您可以创建一个Client(您必须这样做),然后将您的代码包装在with joblib.parallel_backend('dask'):周围。

import dask_ml.joblib
from sklearn.externals import joblibclient = Client()with joblib.parallel_backend('dask'):
    # your scikit-learn code**NOTE:** Note that the Dask joblib backend is useful for scaling out CPU-bound workloads; workloads with datasets that fit in RAM, but have many individual operations that can be done in parallel. To scale out to RAM-bound workloads (larger-than-memory datasets) you should use Dask's inbuilt models and methods.

如果你的训练数据太大,无法放入内存,那么你应该使用Dask的内置估算器来加速。您也可以使用Daskwrapper.Incremental,它使用底层估计器的partial_fit方法对整个数据集进行训练,但它本质上是顺序的。

Dask的内置估算器通过各种优化算法(如admmlbfgsgradient_descent等)很好地扩展了大型数据集。以及L1L2ElasticNet等正则化子。

from dask_ml.linear_model import LogisticRegressionlr = LogisticRegression()
lr.fit(X, y, solver="lbfgs")

再举一个使用Dask的例子,你可以在这里阅读我的帖子中的Dask部分。这是一个从探索到训练模型的完整过程。

6.延伸阅读 ^

  1. https://mybinder.org/v2/gh/dask/dask-examples/master?urlpath=lab
  2. https://towards data science . com/how-I-learn-to-love-parallelised-apply-with-python-pandas-dask-and-numba-f 06 b0b 367138
  3. https://docs.dask.org/en/latest/
  4. https://ml.dask.org

7.参考文献 ^

  1. https://ml.dask.org
  2. https://docs.dask.org/en/latest/
Suggestions and reviews are welcome.
Thank you for reading!

签名:

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

加速你的代码(1):庞加莱球空间中均值漂移聚类的例子

原文:https://towardsdatascience.com/speeding-up-your-code-1-the-example-of-the-mean-shift-clustering-in-poincaré-ball-space-d46169bfdfc8?source=collection_archive---------7-----------------------

来自本系列:

  1. 庞加莱球空间中均值漂移聚类的例子(本帖)
  2. 用 Numpy 对循环进行矢量化
  3. 批处理和多线程
  4. 用 Numba 实时编译

你好社区!

这是一系列文章中的第一篇,我将描述使用 Python 和 Numpy 构建快速聚类算法的步骤,该算法将在特定的“双曲线”空间中使用。

因为我想展示一个真实的开发案例,所以在第一篇文章中,我将描述我必须达到的目标,详细描述我必须实现目标的环境以及我在那里的原因,然后是代码的第一个原始版本。

所以让我们开始吧!

为什么会这样?

2017 年 5 月,在《人工智能世界》杂志上,来自脸书人工智能研究所的 Maximilian Nickel 和 Douwe Kiela 发表了一篇论文。这篇论文的内容有望对代表学习领域产生巨大的影响。

严格来说,表示学习技术的目标是翻译属于一个集合(例如,一本书)的对象(例如,单词)的“含义”,考虑到它们之间存在的属性(例如,语义),目标是具有一个随后可以被 AI 算法使用的基础。对象表现得越好,后续的算法就越有效。

这些技术的尖端使用“嵌入”:每个对象由多维空间中的一个点来表示,并且该点将理想地“嵌入”该对象相对于集合所具有的所有属性。因此,具有相似含义的对象将发现它们的嵌入在几何上彼此接近。这就是为什么我必须建立一个聚类算法。

这种多维空间通常是“平面”型的,也就是说,具有我们在现实空间中看到的几何性质,也就是我们在学校里学过的那些。相反,在提到的论文中,他们使用了一个特殊的“弯曲”空间:庞加莱球模型。这是因为这种空间非常适合表示层次结构,因此集合中的潜在层次结构很容易表示。事实上,它们使用低得多的维度在许多领域显示了最先进的结果。

这个庞加莱球是什么样子的?作为例子,下面有两个二维球(即一个圆)表示。你看到的弧线就是所谓的“测地线,也就是两点之间最短的步行距离。这是相对于“平坦的”欧几里得空间的最明显的区别之一,在欧几里得空间中,点之间的最短穿行是一条直线。另一个很大的不同是,在圆之外没有任何东西可以存在:实际上,离中心很远的点(“在无穷远处”)位于圆盘圆周上。因此,连接你在左图中看到的点的弧线都有相同的长度。令人兴奋,不是吗?

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

Left: the lines connecting the points have all the same distance. Right: the animation of a rotation within the Poincaré disk. Credits: Wolfram.com

目标

我必须建立一个算法,将空间中彼此靠近的点组合在一起。我需要这个算法在没有预先指定聚类数的情况下工作。然后我选择均值漂移程序。

该过程将每个点向其最近的点群移动,并且单个参数定义了最近的点群有多远。如果最近的大块太远,该点将自己形成一个孤立的簇。

这是二维庞加莱盘中结果的动画,其中算法将以 13 个聚类结束:

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

Mean shift clustering in Poicaré disk.

您可以注意到,圆边界附近的点(相对距离似乎很小)不会聚集。相反,靠近圆心的点,其相对距离似乎比圆周上的点要大,实际上会聚集在一起。

这是我们所在的弯曲空间的影响,庞加莱球:正如我已经提到的,你离中心越远,距离实际上就越大,比你在照片上看到的要大。

可以说很多细节,但那超出了本帖的范围。但是,如果你想了解更多关于均值漂移过程和花哨的庞加莱球空间模型,只需搜索网络,有大量的资源。

履行

描述性地,为了实现均值漂移过程,我们必须用所有其他点的加权和来代替每个点 P 。应用于每个点的权重取决于它与所考虑的点的距离( P )。并且必须重复这个过程,直到所有的点都聚集在一起。

您可以在网上找到该算法的几种实现,基本上适用于每种编程语言。但是没有人会在这个空间工作!

原因是我们计算点与点之间距离的方式。所有这些算法都是为在“平坦的”欧几里得空间中工作而设计的,因此它们以欧几里得方式计算点之间的距离。相反,在这个特定的空间中,距离是更复杂的。

这是用 Python 和 Numpy 写的基本代码。我从杰瑞米·霍华德的精彩课程中得到这个版本:

The basic code

这是一个通用的 meanshift 实现。对于我们的例子,区别在于我们定义点之间的区别的方式:

如您所见,Numpy 的广播功能在编写简单代码方面帮助很大。为了理解广播,请看高斯函数:它接受两个元素作为输入( d 作为距离, bw 作为带宽)。它们都可以是数字,或者任何 n 维数组(向量、矩阵、张量),如果它们的形状兼容,Numpy 会知道如何以适当的方式处理它们。在我们的例子中,我们为函数提供一个向量( dists )和一个数字(sigma)。

为了完整起见,庞加莱球模型中两点之间的距离定义为:

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

Definition of distance in the Poincaré Ball space

那么,搞定了?是也不是。它是有效的,你之前看到的动画就是用这种算法制作的。但是很慢:在我的笔记本电脑(i7–4750 HQ CPU @ 2.00 GHz,8GB RAM)上,对于集群 1200 点,每步需要 14 秒,算法收敛需要 10 步。

所以我需要改进代码。

加快代码速度

可以做很多很好的尝试来改进它:使用近似技术、随机矩阵、多线程、GPU 等等。

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

但我们是程序员,有义务多动脑,少蛮力。所以我想尝试的第一个尝试是向量化两个循环:一个在 dist_poinc 函数中,另一个在 mean 算法中。

在下一篇文章中,我会展示原因和结果。但是作为开胃菜,看看这个情节:

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

Percentage of execution time of a vectorized product with respect to a looped one.

是的,你读得不错:一个简单操作的矢量化版本的执行时间不到完成循环所需时间的 1.3%。

难道不值得一试吗?

加速您的代码(3):批处理和多线程

原文:https://towardsdatascience.com/speeding-up-your-code-3-batches-and-multiprocess-52d2d34a4091?source=collection_archive---------3-----------------------

来自本系列:

  1. 庞加莱球空间中均值漂移聚类的例子
  2. 用 Numpy 对循环进行矢量化
  3. 批处理和多线程(这篇文章)
  4. 用 Numba 实时编译

在上一篇文章中,我们展示了我们算法的矢量化版本会随着向量数量的增加而变慢,我们将这一特性与以下事实相关联:对于 N 个向量,我们处理的是 N 个矩阵。

现在,我们将实现一种算法,每次处理一批向量,即批处理版本。利用这个技巧,我们将对顺序为 n x N 的矩阵进行计算,其中小的 n 是一批中要考虑的向量的数量。这个技巧的一个很好的副作用是,我们还可以并行计算。

更新代码

均值漂移过程中的第一个操作是计算每个向量到所有其他向量的距离,在庞加莱球空间中,距离为:

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

Distance in Poincaré ball

按照我们的 1000 个二维向量的例子,为了计算距离公式的分子,我们需要第一个 n 元素的这个 n x 1000 矩阵:

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

Batched version of the vectorized numerator of the distance formula.

然后像以前一样,得到的矩阵中每个元素的分量(都是向量,还记得隐藏维吗?)都得平方总结。

在分母上,我们必须对这个结构进行编码:

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

Batched version of the vectorized denominator of the distance formula.

距离函数的其余部分很容易。使用 Numpy 语言,下面是计算数据集子样本(批处理)和整个数据集之间的距离所需的代码。只有修改过的行被注释:

Batched version of the code for the Poincaré ball distance.

整个 meanshift 算法几乎保持不变:

Code for one iteration of the meanshift algorithm, batched version

最大的不同是,这里我们动态地更新矢量,也就是说,我们根据已经移动的矢量计算出的距离来移动矢量。实际上:在第一批中,距离是以传统方式计算的,产生的移位向量被放置在原始数据集中。因此,我们将有一个数据集,其中第一个 n 个向量被移位,而其他向量没有被移位。当我们考虑第二批时,将针对该混合数据集计算距离,对于其他批也是如此。

虽然这个版本的算法的稳定性需要更好的研究,但我们可以相当肯定的是,使用小高斯宽度()我们不会遇到问题。

最后,在我们在前一篇文章中介绍的位置循环=bad 之后,我们回到另一个循环,因为为了移动所有的向量,我们必须为每一个可能的批次运行这个过程。但是好消息是每个批处理都是相互独立的,所以我们可以并行化这个循环!

为了利用我们的多核处理器,我们将使用 Python 的ThreadPoolExecutor函数:

关于 ThreadPoolExecutor 的细节超出了本系列文章的范围,但简而言之:

  1. 修改 __shift 函数,以恢复该函数已移动的向量的位置;这是追踪移动的向量并将其放入数据集中正确位置所必需的。
  2. 定义了一个字典(future _ shift),它包含所有要并行执行的函数,每个批处理一个;函数的数量将取决于所选择的批量大小,例如,如果数据集包含 1000 个向量,并且选择的批量大小为 200,则字典将包含 5 个函数。
  3. 一旦执行了每个函数,数据集中由跟踪号指定的位置(此列表中的点 1)处的相对位移矢量就会更新。

仅此而已!现在,我们在速度执行方面获得了多少?让我们用通常的二维向量集合来检查一下:

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

Comparison of the execution time for the mean shift algorithm, absolute on the left, relative on the right.*

不出所料,水货版更快。数据集越大,它的性能就越好:对于移动一个由 18k 个向量组成的数据集,并行版本只需要一次移动所有向量所需时间的 5%。这是因为旧版本在多项式时间 O(N)中执行,而并行版本在线性时间中执行:不管数据集大小如何,我们总是使用固定大小的矩阵执行操作( n_batches x N ,在前面的示例中 n_batches =100)。

结论

所有的数值算法都有一个要达到的目标:在合理的时间内获得合理精确的结果。所以,一般来说,在保持精度不变的情况下,加快算法的执行速度就像金子一样。如果考虑生产环境,更快的执行意味着更早地释放硬件资源;在相同的单位时间内可以执行更多的线程,这意味着省钱。更多:在实时应用中,执行速度可以区分可能和不可能。

对于“我如何才能提高速度?”这个问题,第一个天真的答案是通常是“购买更快的硬件!”,但看看接下来的图,我们比较了算法的原始版本(在第一篇文章的中描述)和这里解释的版本:

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

Comparison between the algorithm in the first post (‘looped’) and the one in the present post (‘batched & parallel’).*

我们需要 0.4%多一点的时间来执行一次迭代,这意味着并行矢量化算法比循环算法快 250 倍。或者,如果你喜欢,快 25000%。

你能想到购买硬件的速度快 250 倍吗?这就像从 2009 年构建的英特尔赛扬处理器(~4 GFLOPS)过渡到 2017 年构建的英特尔 I9(~ 1000 GFLOPS)。是的,你需要一个时间机器。

更好的优化代码,不是吗?

如果你想自己试试,可以在这里找到代码

使用并行技术的快速计算机视觉流水线

原文:https://towardsdatascience.com/speedy-cv-pipelines-using-parallelism-d7bebad2ff5f?source=collection_archive---------12-----------------------

如果你曾经使用 OpenCV、MoviePy 或任何其他海量的库编写过自己的代码来处理视频,你可能会面临处理速度非常慢的问题。

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

Unfortunate is ze who has to wait 20 minutes to watermark a 5 minute 4k video

这是因为我们编写的代码基本上是为了在单核上运行而编写的。我们有一个庞大的任务,需要处理大量的数据,没有现成的解决方案可以简化我们的生活。然而,我们想要的是一个完全不同的故事。蠢朋克的一些歌词最好地概括了这一点

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

努力吧,努力吧……更难、更好、更快、更强

看到*更快这个词,*你脑子里第一个想到的可能就是排比。当我们考虑并行视频处理时,这是必须的,对吗?

方法 1

我们可以将帧一个接一个地分配给空闲的内核。似乎是一个简单而明显的解决方案,不是吗?

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

First thoughts: How to NOT parallelize video processing across cores

然而不幸的是,这并不十分奏效。事实上,在很多情况下,这最终会比我们简单的单核代码的性能更差。要了解原因,我们必须深入了解视频本身是如何存储的。由于视频编码是顺序的,当一个内核解码一帧时,其他内核必须处于空闲状态。他们不能开始处理下一帧,直到该核心解码了前一帧(这里是一个关于视频压缩如何工作的技术快速介绍)。

joblib这样的库也试图以类似的方式并行化作业,因此没有提供我们希望看到的那种加速。这意味着我们必须寻找更好的替代方案

方法 2

聪明的阿德里安在他的博客中强调了另一种加速视频处理的方法。在这篇博文中,他讨论了将帧解码过程转移到另一个线程,并存储解码后的帧,直到主处理线程需要检索它。

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

New frames are decoded in a dedicated thread and enqueued to the back of a Queue. They are then dequeued from the front of the list as and when needed by the main processing thread (source: pyimagesearch)

尽管这本身是一个绝妙的想法,但是这种方法的一个很大的局限性是它只能同时利用机器的两个线程/内核。此外,出于某种原因,Adrian 博客中的代码似乎没有直接扩展到 Python 3(参见帖子上的评论。或许 Python 改变了他们的multithreading库的内部工作方式?)不管怎样,如果我们朝这个方向努力,我们应该能够达到一个水平,至少比我们开始的时候稍微好一点。

但是仍然没有达到我们期望的速度。我们希望让 Threadripper 或 Xeon 在同一视频中大规模分配工作负载。为了能够做到这一点,让我们回头看看方法 1。我们第一种方法的主要问题是内核之间的相互依赖。这是不可避免的,因为视频编码从根本上来说是“分块”的。

然而,从逻辑上讲,通过强制内核在完全不同的预分配片段上工作,应该很容易解决这个问题。这是我们最后的方法

方法 3

为了做到这一点,我们所要做的就是让每个内核寻找视频的一个完全不同的片段,并对其进行操作

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

A better way to distribute video processing across your cores

假设我们有 10,000 帧和 4 个内核,这意味着我们将每个内核专用于这 10,000 帧中固定的连续季度(=2500)。这样,内核就不必相互等待来处理下一帧。在此之后,剩下的唯一任务是以正确的顺序重新组装已处理的帧。遵循这种方法可以让我们轻松地并行处理我们的视频处理流水线

在 Python 中做到这一点非常简单。作为参考,这可能是您的普通视频处理代码的样子:

A normal code to perform an operation on a video

如果我们必须并行化这段代码,我们必须使用 python multiprocessing

Parallelizing the video processing by distributing video fragments across cores

大部分已经完成了。然而,仍然遗漏了一个重要的部分。我们只剩下加工过的碎片。我们仍然需要合并这些碎片。这可以通过以下方式使用ffmpeg轻松完成:

Merging the video fragments that are generated by our parallel processing code

我们做到了!一种并行化视频处理管道的方法,可根据我们投入的内核数量进行扩展。

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

A speed-up of 6x is observed with 6 cores and 2x with 2 cores for the simple task of video blurring

你不必相信我的话,😄。自己试试吧,在下面的评论中告诉我。所有的代码都可以在 https://github.com/rsnk96/fast-cv 的 GitHub 中找到

备注:

  • 如果你想知道cv2.set(CAP_PROP_POS_FRAMES)如何跳到一个特定的帧,而不必使用前面的帧解码,这是因为它跳到了最近的关键帧。查看视频链接,更好地理解这一点
  • cv2.set(CAP_PROP_POS_FRAMES) 已知没有准确寻找指定帧。这可能是因为它寻找最近的关键帧。这意味着在视频片段的地方可能会有几帧重复。所以不建议对关键帧用例采用这种方法。
  • 最佳性能实际上可能通过方法 2 和 3 的组合来获得。但是要把它编码起来需要更多的努力。如果有人设法拼凑出一个代码,请在评论中告诉我!😃
  • **:包含对if ret==False: break的检查是正常的做法,但是为了简单起见,我在这里避免了它
  • 这篇文章是我最近在 Pysangamam 和 Pycon India 发表的演讲的摘要。https://www.youtube.com/watch?v=v29nBvfikcE

特朗普总统每周讲话中的人际动机!

原文:https://towardsdatascience.com/spiking-interpersonal-motives-in-this-weekly-address-by-president-trump-d9acb03f0ba1?source=collection_archive---------3-----------------------

发现总统的思想和内心

我分析了唐纳德·j·特朗普总统(dt。2017 年 4 月 14 日),你可以在访问这个白宫网站链接。在我的观察中,我发现,除了更高水平的安全和社会福祉相关的价值观飙升之外,这个每周讲话还有一个重要的人际/社会动机。

你可以通过人类动机、价值和人格特质的镜头,在下面找到我分析的细节。该分析是基于对每周讲话的语音记录的认知计算和分析。

首先,这是每周电视讲话的要点(摘要)
几个世纪以来,犹太人经历了一次又一次的迫害——然而,他们坚持下来,繁荣起来,提升了这个世界。这是一个神圣的崇敬和崇拜的日子;这是一个神圣的时刻,用我们人民的信念来充实我们国家的精神。我们祈求力量和智慧来实现一个更美好的明天——一个所有信仰的善良的人们,基督教徒、穆斯林、犹太教徒和印度教徒,都能随心所欲,凭良心礼拜的明天。我还想给那些挣扎中的美国人一个特别的信息,他们长期以来一直感受到艰难困苦的滋味。我们为所有公民享有安全与和平的权利而战,为所有上帝的孩子有权知道的有尊严地工作和生活而战。

认知分析

1.预测的动机

人类的动机就是渴望或寻求改变。激励情绪是从一个人的内心和思想中唤起的情感力量。

我发现养成(7.5)和归属(8.4/10)的动机很重要(在上周的演讲中并不重要)。).这些动机与众不同,表明在游戏中有更高的人际/社会动机,而不是只有工具性动机。(物质世界方面)

这意味着什么?

  • 养育动机表明了美国公民的关怀态度,这种态度更多的是在当前的背景下体验(相对于过去或未来的焦点)
  • 归属动机是面向未来的(期待…).这在心理上意味着总统渴望成为社区的一员。(即成为人民主席)

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

2.预测的价值取向(信念和目标)

价值观传达了对一个人来说什么是最重要的。它们是“合意的、跨情境的目标,其重要性各不相同,是人们生活中的指导原则”——它们与具体的行为状态密切相关——沙洛姆·施瓦茨博士。

被发现在安全、刺激和普遍主义维度上占主导地位的预测价值取向(沙洛姆·施瓦茨博士模型)表示以下方面。,

*社会秩序、家庭安全、归属感、健康/幸福

*尊重传统、国家安全

*社会公正、世界和平、诚实、宽容

3.预测的人格倾向

基于海洋模型的人格相关性表明了以下几个关键方面

  • 更高的平等主义取向
  • 不那么占优势,也不那么保守的防御姿态
  • 适度的合作&友好的态度(Vs)与对立的态度*(例如,采取不太严厉措施的国家)*
  • 更高水平的保守(计划可预测结果的能力)以及承担风险和/或运用创造力的意愿。
  • 对负面情绪(风险/损失)更敏感。

4.发现语境主题:独特而重要的方面**(词云)**

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

5.总结——将动机、价值观和个性倾向联系起来

  1. 特朗普总统正在通过加强与美国人民的联系和关心美国人民来强调公民福利。这将如何显化和进化将会很有意思。
  2. 似乎强调国家安全、秩序和人民福祉,我认为这与保卫国家/公民免受外来威胁有关。
  3. 鉴于美国复杂和不祥的外交形势,我预计至少会有一些认知信号,可能表明某种专制和防御/战争姿态。例如,上一次每周讲话传达了更高层次的自信和意志坚定。本周更多的是温柔!特别是关于朝鲜局势,这可能意味着他可能会采取与美国盟友和其他国家建立和平的方式,而不是继续咄咄逼人或好战的姿态。

随着未来几周事件的展开,看看我们是否能找到任何基于上述推测的预测将会很有趣。

限制

这种分析可能有如下所述的局限性,因此我的发现和推测可能不正确。

  1. 以人民为中心的演讲恰逢犹太人的逾越节和复活节。由于这一点,认知取向可能不会为总统深入参与的其他政治和国家方面提供线索。
  2. 每周讲话(文本数据)内容的大小可能较小,可能无法进行统计意义上的预测。

声明:
此处提及的观点均为本人观点。
本文使用的信息/数据来自公共领域的可用信息。但对其准确性、完整性、及时性或正确性不做任何明示或暗示的陈述或保证。我不对任何错误或不准确负责,不管是什么原因造成的。(读者)

脉冲神经网络,下一代机器学习

原文:https://towardsdatascience.com/spiking-neural-networks-the-next-generation-of-machine-learning-84e167f4eb2b?source=collection_archive---------1-----------------------

每个远程关注机器学习最新进展的人都听说过当前用于机器学习的第二代人工神经网络。这些一般都是全连接的,接受连续值,输出连续值。虽然它们让我们在许多领域取得了突破性进展,但它们在生物学上是不准确的,并没有真正模仿我们大脑神经元的实际机制。

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

第三代神经网络,即脉冲神经网络,旨在弥合神经科学和机器学习之间的差距,使用生物逼真的神经元模型来执行计算。一个脉冲神经网络(SNN)从根本上不同于机器学习社区所知道的神经网络。snn 使用尖峰信号运行,尖峰信号是在时间点发生的离散事件,而不是连续值。锋电位的出现是由代表各种生物过程的微分方程决定的,其中最重要的是神经元的膜电位。本质上,一旦神经元达到某个电位,它就会出现尖峰,该神经元的电位就会被重置。最常见的模型是泄漏集成点火(LIF)模型。此外,snn 通常稀疏连接,并利用专门的网络拓扑。

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

Differential equation for membrane potential in the LIF model

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

Membrane potential behavior during a spike

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

Spike trains for a network of 3 neurons

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

A full spiking neural network

乍一看,这似乎是一种倒退。我们已经从连续输出转向二进制输出,这些尖峰脉冲串不太好解释。然而,尖峰脉冲序列为我们提供了处理时空数据的增强能力,换句话说,就是现实世界的感官数据。空间方面指的是这样一个事实,即神经元只连接到它们本地的神经元,所以这些神经元天生就单独处理输入的大块(类似于 CNN 使用过滤器的方式)。时间方面指的是尖峰脉冲序列随着时间的推移而出现,因此我们在二进制编码中失去的,是尖峰脉冲的时间信息。这允许我们自然地处理时态数据,而没有 RNNs 增加的额外复杂性。事实上,已经证明,脉冲神经元从根本上来说是比传统人工神经元更强大的计算单元。

鉴于这些社交网络在理论上比第二代网络更强大,人们很自然会想为什么我们看不到它们的广泛使用。当前 SNNs 实际使用中的主要问题是训练的问题。虽然我们有无监督的生物学习方法,如赫比学习和 STDP,但没有已知的有效的监督训练方法,用于 SNNs,提供比第二代网络更高的性能。由于尖峰序列是不可微的,我们不能使用梯度下降训练 snn 而不丢失尖峰序列中的精确时间信息。因此,为了将 SNNs 正确地用于现实世界的任务,我们需要开发一种有效的监督学习方法。鉴于这些网络中的生物现实主义,这是一项非常困难的任务,因为这样做将涉及确定人类大脑实际上是如何学习的。

我们即将解决的另一个问题是,在普通硬件上模拟 SNNs 是非常计算密集型的,因为它需要模拟微分方程。然而,神经形态硬件,如 IBM 的 TrueNorth,旨在通过使用专门的硬件模拟神经元来解决这一问题,这些硬件可以利用神经元尖峰行为的离散和稀疏特性。

因此,社交网络的未来仍不明朗。一方面,它们是我们当前神经网络的自然继承者,但另一方面,它们远不是大多数任务的实用工具。在实时图像和音频处理中有一些当前真实世界的 SNNs 应用,但是关于实际应用的文献仍然很少。大多数关于 SNNs 的论文要么是理论性的,要么显示了在简单的全连接第二代网络下的性能。然而,有许多团队致力于开发 SNN 监督学习规则,我对社交网络的未来保持乐观。

Garoppolo 效应:使用 Python 探索 NFL 数据教程

原文:https://towardsdatascience.com/sports-analytics-exploring-nfl-data-using-python-3f70721a9c60?source=collection_archive---------10-----------------------

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

Photo by Ashton Clark on Unsplash

吉米·加罗波洛刚刚与旧金山 49 人队签署了 NFL 历史上最大的合同。在赛季中期被交易后,他为他们首发了 5 场比赛,并带领 49 人队赢得了所有这些比赛。

我想探究加罗波洛被交易后 49 人队发生了什么变化,并更多地了解他是如何帮助球队的。为了进行这一分析,我使用了一个关于 2017 年 NFL 比赛的数据集。你可以使用 NFL scrapeR 获取数据。我用这个工具下载了 NFL 2017 年的比赛数据,并包含了一个直接的链接,可以在那里下载 2017 年的 NFL 数据。

出于我们的目的,我们将专注于分析 2017 赛季的 49 人队数据,这是一个关于如何使用 Python 2.7 完成这项工作的演练。我在代码中加入了一些注释来帮助您理解。由于吉米·加罗波洛只为 49 人队首发了 5 场比赛,这些情节不会都有类似的观察次数,但让我们看看我们可以学到什么有趣的东西。

**import** **pandas** **as** **pd # data manipulation library**
**import** **numpy** **as** **np # numerical computation library**
**import** **datetime** **as** **dt** 

**import** **matplotlib.pyplot** **as** **plt # plotting library**
**from** **matplotlib** **import** cm # color maps for plotting
plt.style.use('ggplot') # use the ggplot plotting style

%matplotlib inline # show plots in line in a jupyter notebook

**from** **__future__** **import** division # division without truncating decimals 

现在,让我们将数据读入一个名为 nfl 的变量。

nfl = pd.read_csv('https://raw.githubusercontent.com/ryurko/nflscrapR-data/master/data/season_play_by_play/pbp_2017.csv', low_memory=False)

因为我们想比较球队在交易前后的表现,所以我们可以添加一个额外的列,表明某个日期是在 Jimmy Garoppolo 为 49 人队开始比赛的日期之前或之后。这样做将允许我们比较 Jimmy 开始工作之前和之后的数据,因为我们可以基于该列的值聚集数据。

首先,我们将 date 列转换为 datetime 格式,这样我们就可以将它们与我们想要检查的日期进行比较。

nfl['Date'] = pd.to_datetime(nfl['Date'])

然后我们可以创建一个名为 Jimmy 的列,如果比赛日期在 2017 年 12 月 3 日或之后,则为 yes,否则为 no。

nfl['Jimmy'] = np.where( nfl['Date']>=pd.datetime(2017,12,3), 'yes', 'no')

现在,如果我们检查我们的 nfl 数据框架,我们会看到最后一列现在是吉米。

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

我们可以从 2017 年的 NFL 数据中获得 49 人的特定数据,方法是对 NFL 数据进行子集化,使主队或客场队为 SF 49 人。

niners = nfl[ (nfl["HomeTeam"] == 'SF') | (nfl["AwayTeam"] == 'SF') ]

接下来,我们可以看看达阵得分。为了检查触地得分信息,我们可以检查主队或客队是 SF,发生了得分比赛,发生了触地得分,进攻的球队是 SF,并且没有拦截者,拦截者是拦截球的球员。

niners_td = nfl[((nfl["HomeTeam"] == 'SF') | (nfl["AwayTeam"] == 'SF')) & (nfl["sp"] == 1) & (nfl["Touchdown"] == 1) & (nfl["DefensiveTeam"] != 'SF') & pd.isnull(nfl["Interceptor"]) ]

本赛季我们有 31 次触地得分。

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

现在,我们可以通过对数据帧进行分组来检查在没有吉米和有他的情况下有多少次触地得分。

niners_td.groupby('Jimmy').Touchdown.sum()

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

吉米·加洛波洛出场和不出场都是达阵

现在让我们来绘制吉米开始和没有开始比赛时的达阵次数。

tds = niners_td.groupby('Jimmy').Touchdown.sum() *# score the touchdown information in tds*

fig, ax = plt.subplots(figsize=(8, 6), dpi = 72) *# Get access to the figure and axes to modify their attributes later*

ax.set_title("Total Number of Touchdowns", fontsize = 18) *# Chart title*
ax.set_xlabel('Jimmy', fontsize = 15) *# X-axis label*
ax.set_ylabel('Number of Touchdowns', fontsize = 15) *# Y-axis label*
plt.xticks(fontsize = 13)
plt.yticks(fontsize = 13)

mycolors = ['#A6192E', '#85714D'] *# Using scarlet and gold colors*

tds.plot(kind='bar', alpha = 0.9, rot=0, color = mycolors) *# Plot a Bar chart*
plt.show()

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

虽然这个图很好,但我们也应该检查一下每场比赛的达阵次数,因为吉米只打了 5 场比赛。

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

我们可以看到,当吉米·加罗波洛开始时,49 人队每场比赛大约多得分 1 次。这并不意味着他要对他打球时的每一次触地得分负责,但这只是显示了他打球时触地得分的次数。

随着时间的推移,触地得分和拦截

为了从不同的角度来看触地得分的情况,我们可以采用时间序列的方法来观察一段时间内触地得分和拦截的次数。我们可以标记出 Garoppolo 开始游戏的时间点,看看我们可以观察到什么变化。

*# get sum of touchdowns by game day*
td_by_date = niners.groupby('Date')['Touchdown'].sum()
td_by_date;*# get sum of interceptions by game day*
inter_by_date = niners.groupby('Date')['InterceptionThrown'].sum()
inter_by_date;

现在让我们把它画出来。

fig, ax = plt.subplots(figsize=(8, 6), dpi = 80) *# set plot size* 

mycolors = ['#A6192E', '#85714D'] *# Using scarlet and gold colors*

f1 = td_by_date.plot(color = mycolors[0]) *# plot the touchdowns*
f2 = inter_by_date.plot(color = mycolors[1]) *# plot the interceptions*

ax.set_title("Touchdowns and Interceptions over Time", fontsize = 18) *# Chart title*
ax.set_xlabel('Game Date', fontsize = 15) *# X-axis label*
ax.set_ylabel('Count', fontsize = 15) *# Y-axis label*
plt.xticks(fontsize = 12)
plt.yticks(fontsize = 12)

plt.axvline(dt.datetime(2017, 12, 3), color = 'black') *# add a vertical line*
plt.legend(loc='upper center', frameon=True, facecolor="white") *# add a legend with a white background*

plt.show()

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

黑色竖线右边的线是吉米开始的游戏。我们注意到,在他开始为 49 人队比赛之前,他们在触地得分方面处于下降趋势,在他开始后,他们的进攻开始再次起飞。

不同游戏类型的比较

我们还可以比较加罗波洛没有首发和首发时不同类型的打法。这可以给我们一个总体的感觉,当他开始比赛时,某些比赛的流行程度如何改变,而当他没有开始比赛时,因为比赛的类型可以随着不同的四分卫而改变。为了比较不同的打法类型,我们使用 niners 数据框架,而不是上面定义的 niners_td 数据框架,因为我们关心的是比赛中的整体打法,而不仅仅是 49 人队进攻时的打法。

fig, ax = plt.subplots(2, 1, figsize=(10, 8), dpi = 85) *# specify a plot with 2 rows and 1 column*

*# get plays where Jimmy did not start and did start*
f1 = niners[niners['Jimmy']=='no']['PlayType'].value_counts().plot(kind='barh', ax=ax[0]) 
f2 = niners[niners['Jimmy']=='yes']['PlayType'].value_counts().plot(kind='barh', ax=ax[1])

f1.set(title = "Before Jimmy's Starts", xlabel='Count', ylabel='Play Type')
f2.set(title = "After Jimmy's Starts", xlabel='Count', ylabel='Play Type')f1.set_xlim(0,805) # use the same scale for both plots
f2.set_xlim(0,805)fig.tight_layout() *# prevent overlapping axis labels*

plt.show()

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

当然,比赛的数量不同,因此比赛的次数也不同,但是如果我们使用两个图中的颜色来匹配哪种比赛最频繁,我们会看到在两个图中,最常见的比赛是传球、跑动和开球,但是我们注意到,在吉米开始之前,打门更常见,而在吉米开始之后,射门更常见。这可能表明,吉米的发挥可以帮助 49 人队更接近得分位置,并有机会获得更多的投篮机会。

最后,让我们更深入地研究一下 49 人队的进攻数据。

让我们来看看 49 人队进攻时获得的码数排名靠前的比赛。像以前一样,我们将子集化我们的数据,以获得 49 人队进攻时的数据。

niners_offense = nfl[((nfl["HomeTeam"] == 'SF') | (nfl["AwayTeam"] == 'SF')) & (nfl["DefensiveTeam"] != 'SF') ]

我们可以创建一个名为 most_yards 的新数据框架,它采用了 49 人队进攻获得最多码数的 50 次观察。

most_yards = niners_offense.sort_values(by='Yards.Gained', ascending=False)[:50]

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

我们可以看到,这 50 个按码数计算的顶级游戏中有 20 个发生在 Jimmy 开始时,但是由于出现的次数不同,我们将查看整体值,而不是像以前一样将图分开,因为这些不同的观察结果会导致条形图中条形数的变化。

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

我们注意到马奎斯·古德温是上个赛季获得最多码数的接球手。

passes = most_yards[most_yards["PlayType"] == 'Pass']fig, ax = plt.subplots(figsize=(8, 6), dpi = 75)

f1 = passes['Receiver'].value_counts().plot(kind='barh')
f1.set(title = "Players with the most Yards after receiving Passes", xlabel='Count', ylabel='Player Name')

plt.show()

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

我们可以看到马特·布雷达和卡洛斯·海德是上赛季最成功的跑垒者之一。

runs = most_yards[most_yards['PlayType'] == 'Run']fig, ax = plt.subplots(figsize=(6, 5), dpi = 75)

f1 = runs['Rusher'].value_counts().plot(kind='barh')
f1.set(title = "Players with the most Yards from Rushing", xlabel='Count', ylabel='Player Name')

plt.show()

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

结论

我希望您喜欢这篇使用 NFL 数据的 Python 数据分析指南。现在你可以去下载 NFL 的数据,用不同的信息进行试验,看看你会发现什么有趣的东西!

感谢您花时间阅读这篇文章,并随时在 LinkedIn 上发表评论或联系。

参考资料:

  1. 熊猫数据框文档
  2. 熊猫剧情文档
  3. Matplotlib 文档
  4. NFL 刮刀
  5. 个人 Github 知识库与本代码笔记本

用 R +谷歌云机器学习进行 App Store 评论挖掘

原文:https://towardsdatascience.com/spotify-app-review-mining-with-r-google-cloud-machine-learning-feb6f9c3b75f?source=collection_archive---------8-----------------------

这篇文章描述了如何用 R 和 itunesr(由 阿卜杜勒马耶德拉贾 )从 iTunes 中导出数据,然后是可视化的评分和评论。它还涵盖了如何在进行基本的情感分析之前,通过使用 Google Cloud 机器学习 API 进行语言处理,使用 googleLanguageR 来翻译评论。

为什么要在意 app 评分和评论?

评论包含了关于什么有效,什么无效的有价值的见解,因此应该被视为一座金矿。评论可能是一个应用程序性能不佳的唯一信息来源。因此,如果有人问为什么要关心免费、有价值和主动提供的用户反馈 2018,你最好确保你在正确的地方。只要跟随你的网购行为,你就会找到答案。

关于应用评级和评论重要性的关键见解:

用户共享意味着公司关怀

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

购买非定制的应用分析工具或构建定制平台

处理评论挖掘可能具有挑战性,但会给你提供有价值的见解,有助于创造更好的服务,并有望提高用户忠诚度。为了获得积极的效果,你需要一种工作方法。不幸的是,随着时间的推移,App Store 无法帮助你区分正面和负面评论。此外,您需要定义一个系统化的流程,在对类似的反馈进行分析之前对其进行分组。对正面和负面的评论、合格的 UX 改进中的错误或者更大的特性建议进行分类,将会提高效率并促进未来的优先级。

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

那么,从哪里开始呢?你可以选择使用 Appbot、MobileAction、App Annie 或 SensorTower 等非定制但功能强大的工具支付有时很昂贵的订阅费用,或者自己创建一个定制的解决方案并节省数千美元。相反,我会使用这些工具导出历史数据,然后在 Tableau 中构建我的个人挖掘解决方案,或者在 r。

***免责声明:***App Store Connect 的一个缺点是,你只能导出每个国家/市场的最新 500 条评论,并且只能导出与评论相关的评分。这将影响所有的汇总报告,因为美国的最后 500 个评论可以在一周内收集,但像匈牙利这样的较小市场可能涵盖前 12 个月的观察。可以每天或每周转换数据以保存历史数据。如果不是,小心你的发现。

创建灵活的解决方案的好处是有机会交叉检查数据,并根据不同的场景安排自动化任务。此外,通过减少代码和可视化的数量,您可以在一秒钟内对应用程序进行免费的基准研究。

用 R 和 itunesr 查看分析

为什么选择 R?r 是一种用于定性和定量分析的统计编程语言,由科学家开发,具有大量用于统计、机器学习和数据科学的库。R 的一个巨大优势是可以用 R Markdown 创建交互式 web 应用程序。因此,这是在组织中快速、交互式地分发数据的有效方式。如果你对 R 完全陌生,可以在 Coursera 上看看 R 编程课程。

我的灵感来自阿卜杜勒·马耶德·拉吉,他也是 itunesr 软件包的作者,在阅读了分析 iOS 应用商店 iTunes 评论的之后。Gregory E Kanevsky 的文章分析了 PodCruncher 应用程序在 iTunes 上的评分和评论,这是另一个很好的启发。

今天,我使用 itunesr 获取应用洞察,以进行基准测试和确定未来发展的优先顺序。而且还可以通过 Slackr 将负面评价自动化并直接推送到 Slack。我希望你能找到自己的创造性的方法,在你的组织内分发评审数据。

我还是一个 R 的新手用户,所以就冒昧的拉伸一下干巴巴(不要重复自己)的原则。请耐心等待。

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

Selected tools and packages for analysis and visualization (iTunes, RStudio, Google Translate API, Google Cloud, ggplot2 & dplyr.)

我们到底在找什么?

探索性评论可以通过多种方式和技术来完成。我们的目的是通过分析来熟悉每个市场的实际表现:

  • 应用评级分布
  • 用户感受(负面/正面)
  • 每个应用版本的平均评分
  • 每月每天的平均评分
  • 平均评论字符长度
  • 使用 googleLanguageR 进行文本翻译
  • 文本情感分析

1.RStudio 中的准备工作

从下载 R 和 IDER studio开始。其次,我们需要为我们的市场可视化收集所有需要的 Apple Store 国家代码的列表。要获取应用程序数据,我们需要 iOS 应用程序 ID,它可以在 iTunes URL 中找到。只需谷歌你喜欢的应用程序,并复制 ID。

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

现在,安装并加载所需的库。您可以通过编写 Install (- library)在源代码中完成此操作,或者从 CRAN 存储库中单击 Packages > Install。itunesr 可以直接从 CRAN 安装,但是我确实推荐从 Github 安装最新的开发版本。

# Install and load libraries
devtools::install_github("amrrs/itunesr")
remotes::install_github("ropensci/googleLanguageR")library(itunesr)
library(googleLanguageR)
library(openxlsx)
library(ggplot2)
library(writexl)
library(tidyr)
library(dplyr)
library(sentimentr)
library(scales)
library(tidyverse)
library(tidytext)
library(DataExplorer)# Spotify iOS App ID
appstoreID = 324684580# Get information about the Spotify App
getAttributes(appstoreID,'se')# Create this df for later purpose
df_App_allMarkets = NULL

函数getAttributes在我们的 RStudio 控制台中显示应用程序、开发者、包、URL 和类别的标题。

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

2.收集市场评论和评级

好吧,让我们踢它!接下来,我们将创建一个空数据框,用于保存 Apple 允许我们导入的最后 500 条评论。 (Spotify 在 65 个市场 都有存在,但对于这个例子,我只用了其中的 12 个)。然后我们为 iTunes 中的第二个和最后一个评论页面设置变量,然后创建一个 for 循环,该循环将遍历每个市场的所有评论,并将它们绑定到一个主数据框架中。

## 1\. Prepare to loop through last 500 reviews per market# Spotify App Store Country Codes 
appMarketsSpotify <- c("se", "us", "dk", "de", "hu", "it", "nl", "no", "br", "ca", "ch")# Each page has 51 reviews between each pagination
no_ratings <- 500 #500
no_reviews_per_page <- 51
ratings <- no_ratings/no_reviews_per_page# Round up pagination & set variables for second and last page
ratings <- ceiling(ratings) #round up to 10
reviewStartPage <- 2
reviewEndPage <- ratings

在合并之前,下一段代码将遍历给定市场的每个评论页面。然后,我们将变量df_App$Date从默认的 POSIXt 转换为 Date,在最后创建本地市场数据框之前,按索引对列进行排序和重新排序。最后,我们使用rbind(df_App_allMarkets, df_App_Market).将所有数据框绑定在一起,因为您可能会收到重复的评论,所以有必要使用命令unique(data frame)删除它们,这取决于评论和市场的数量,脚本可能需要几分钟时间。☕

## 2\. Loop through all App markets and bind them together
for (appMarket in appMarketsSpotify) {

# Creates a df with first review page (1)                        
df_App <- getReviews(appstoreID,appMarket,1)# Create a for loop and merge all tables in to one single df
for (page in reviewStartPage:reviewEndPage){
  df_App <- rbind(df_App, getReviews(appstoreID,appMarket, page))
}# Convert 'Date' from POSIXt to Date and sort df by date (ascending)
df_App$Date <- as.Date(df_App$Date)
df_App <- df_App[order(df_App$Date),]# Reorder columns in our df by column index and add market suffix 
df_App <- df_App[c(7, 4, 5, 1, 6, 2, 3)]
df_App$Market=appMarket# Create df for each local market
df_App_Market <- print(appMarket)
df_App_Market <- df_App# Bind all markets together into one single df
df_App_allMarkets <- rbind(df_App_allMarkets, df_App_Market)# Remove dublicated reviews
df_App_allMarkets <- unique(df_App_allMarkets)### End loop
}

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

Loop through each market and bind all data frames to one

在可视化我们的数据框之前,让我们检查输入数据集的维度,以了解我们正在处理的内容。我使用库 DataExplorer 通过编写对我们的数据框架进行快速 EDA。plot_str(df_App_allMarkets)

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

Explore your variables with DataExplorer

现在,让我们简单地通过编写View(df_App$Market)并按回车键来预览和探索我们的聚合数据框架df_App$Market。在 RStudio GUI 中的全局环境下,我们可以研究我们的数据框,看到有 6908 个收集的观察值,分为 8 列。

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

Master data frame including all ratings and reviews in RStudio

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

Simple qplot showing the distribution of ratings

要了解每个市场的评论数量,请使用table(df_App_allMarkets$Market)table(df_App_allMarkets$Rating)查看评分分布。用qplot(df_App_allMarkets$Rating)进行图形可视化绘图审查。

2.1 将数据框导出到 Excel

我们可能需要稍后返回并处理我们的数据框,因此,与其偶尔重新运行上面的脚本,不如将所有检查导出到 SQL 数据库、Excel 或 CSV 文件中,这样会更有效。在此之前,我们需要做一些小的调整,例如,将国家后缀改为国家名称。

# Sort df by rating 1-5
df_App_allMarkets$Rating <- factor(df_App_allMarkets$Rating, 
levels = c("1", "2", "3", "4", "5"))# Convert data types & rename country codes before visualization 
df_App_allMarkets$Rating <- as.numeric(df_App_allMarkets$Rating)
df_App_allMarkets$Date <- as.Date(df_App_allMarkets$Date)df_App_allMarkets$Market <- revalue(df_App_allMarkets$Market, 
  c("se" = "Sweden", 
    "us" = "USA", 
    "de" = "Germany",
    "hu" = "Hungary",
    "it" = "Italy",
    "nl" = "Netherlands",
    "no" = "Norway",
    "br" = "Brazil",
    "ch" = "Switzerland",
    "gb" = "Great Britain",
    "ca" = "Canada",
    "dk" = "Denmark"))

现在,让我们使用 R 包openxlsx将 df 导出为 Excel 文件。文件 AppStoreReviews.xlsx 将存储在您的本地工作目录中。如果你不确定你的归途,写下getwd()并按回车键。

#Save as Excel to back up collected reviews
write_xlsx(df_App_allMarkets, path = ("AppStoreReviews.xlsx"), col_names = TRUE)

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

Save a local XLSX file as a backup for the future

现在,我们甚至可以在 R 中创建预定任务,并在我们的主电子表格中添加新的评论,无论是在本地还是在 Google Drive 上。

恭喜,您刚刚为自己节省了数百美元,因为您没有购买一个或多或少做同样事情的工具。

2.2 可视化评级和评论

现在是可视化的时候了,我们将使用 ggplot2 进一步探索,并希望从我们收集的评论中获得一些有价值的见解。注意我们只处理每个市场最新的 500 条评论,所有评分都与一条评论配对。Spotify 音乐在 App Store 的评分是 4.7 星。这比平均评分为 3.9 的点评高出 20 %,mean(df_App_allMarkets$Rating)如前所述,撰写点评的用户通常比那些只给出评分的用户更不满意。

2.3 缺乏足够的数据

由于每个市场内提交 Spotify 应用评论的时间段不同,你不能根据最近 500 条评论得出任何重要结论。如果我们继续我们的主数据框架,我们会注意到,用户评论和评级更频繁的市场将比那些评论较少的市场分布相对不均匀。因此,在汇总所有市场的数据时,我们需要小心我们的假设。让我们通过qplot(df_App_allMarkets$Date).来形象化这个模式

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

Reviews by date

很明显,大多数审查是在过去 60 天内进行的。实际上只有在美国,在过去的 7 天里有+500 条评论。(但这不是一个普通的应用程序,🇸🇪).加油

如果你在每个市场总共只有大约 200 条评论,那么你是安全的,但是在我们的情况下,我们在过去的 365 天里有 166,952 条评论和 2,858,871 个评级,因此我们被敦促收集更多的数据。因为这篇文章的目的是展示什么是可能的原则,而不是确切的数字,所以我把钱花在了昂贵的许可证以外的东西上。

3.审查数据分析

3.1 应用评级分布

**问题:**我们需要比较每个市场的收视率分布,以了解差异。关于评级的一个常见假设是,要么你是一个快乐的用户,要么是一个愤怒的批评者,这将反映出图表中你看到大量的 1 星和 5 星评级。

**解法:**让我们用geom_bar.来绘制分布图

### App rating distribution per marketggplot(df_App_allMarkets, aes(x=as.factor(Rating), fill=as.factor(Rating))) +
geom_bar(col="white")+
    theme_bw() +
    labs(title="App rating distrubution per market", 
    x="Ratings 1-5", y="No of Ratings")+theme(plot.title = element_text(family = "Circular Std",     color="black", face="bold", size=22, hjust=0))+scale_fill_manual("Ratings", values = c("1" = "#DA393B", "2" = "#EE6D45", "3" = "#F7E458", "4" = "#68E194", "5" = "#5F9CEF"))+
facet_wrap(~Market, scales = 'free_x')

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

App rating (1–5) distribution per market

见解: 1 星或 5 星评级似乎是最受欢迎的评级。最满意的用户来自匈牙利、瑞典、挪威和意大利。然而,大多数不满意的用户是在巴西、加拿大、德国和瑞士。我会分析为什么这么多匈牙利用户乐于给出 5 星评级,并对德国所有 1 星评级进行关键字分析。此外,用每周的平均评分绘制一个线形图,以了解历史发展情况。

3.2 用户感受(负面/正面)

**问题:**在同一个地块上用 1-5 颗星来比较 12 个不同的市场可能很难读懂。因此,让我们去掉所有中性的 3 星评级,将其分解为负面(1-2 星)和正面(4-5 星)评级。

**解决方案:**首先基于df_App_allMarkets.创建一个新的数据框,接下来我们将变量 Rating 从数值转换为字符,然后用正负字符替换 ratings。最后,将结果绘制为每个市场的总份额。

### User feeling for each market# Create a new df based on df_App_allMarkets
df_Ratings_Simplified <- data.frame("Date" = df_App_allMarkets$Date, "Rating" = df_App_allMarkets$Rating, "AppVersion" = df_App_allMarkets$App_Version, "Market" = df_App_allMarkets$Market)# Convert ratings to vector and replace ratings with text
df_Ratings_Simplified$Rating <- as.character(df_Ratings_Simplified$Rating)# Remove all ratings with 3-stars from df
df_Ratings_Simplified <- df_Ratings_Simplified[!df_Ratings_Simplified$Rating == "3", ]# Replace 1-2 star ratings with text Negative, and 4-5 stars with text Positivedf_Ratings_Simplified$Rating[df_Ratings_Simplified$Rating == '1'] 
<- 'Negative'+df_Ratings_Simplified$Rating[df_Ratings_Simplified$Rating == '2'] 
<- 'Negative'+df_Ratings_Simplified$Rating[df_Ratings_Simplified$Rating == '4'] 
<- 'Positive'+df_Ratings_Simplified$Rating[df_Ratings_Simplified$Rating == '5'] 
<- 'Positive'# Plot user feelings for each market
ggplot(df_Ratings_Simplified, aes(Rating, group = Market)) + 
geom_bar(aes(y = ..prop.., fill = factor(..x..)), stat="count") + 
geom_text(aes( label = scales::percent(..prop..), y= ..prop.. ), size = 4, stat= "count", vjust = -0.4) +theme_bw() +
theme(legend.position="none")+
scale_fill_manual("Ratings", values = c("1" = "#ED5540", "2" = "#68E194"))+
labs(y = "Rating", fill="Rating") +
scale_y_continuous(labels=scales::percent, limits = c(0, 1)) +ylab("relative frequencies") +
xlab("Procent") +  labs(title="User feeling per market", x="Reviews", y="Amount")+
labs(caption = "(Negative = 1-2 stars, Positive = 4-5 stars)")+facet_wrap(~Market, scales = 'free_x')

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

Distribution between negative and positive ratings

**见解:**理解满意和失望用户的动机和区别同样重要。除了美国、加拿大、巴西和德国的用户更不满意之外,每个市场的结果似乎是一样的。通过分析这些评论,你可能会发现相似之处。

3.3 每个工作日的用户感受

**问题:**正面或负面的评级可以很容易地分割,以报告每月、每周或每周的数据。如果我们需要了解评论何时发布,以及用户在一周中的感受有何不同,我们可以通过可视化每个工作日的感受来做到这一点。

**解决方案:**首先,我们将可变日期(2018–08–23)转换为工作日,例如周六。然后我们用一个堆叠的条形图来绘制结果,显示正面和负面评论的数量,包括总数。如果你需要比较市场的不同,只需取消下面最后一行的注释。

### Plot feelings by weekday 
df_Ratings_Feeling_Week <- df_Ratings_Simplified
df_Ratings_Feeling_Week$Date <- format(as.Date(df_Ratings_Feeling_Week$Date), '%A')ggplot(df_Ratings_Feeling_Week, aes(x = as.factor(Date), fill = Rating, label = Rating)) +
  geom_bar(stat = "count")+
  theme_bw() +
  scale_fill_manual("Ratings", values = c("Positive" = "#68E194", "Negative" = "#ED5540"))+
   theme(plot.title = element_text(family = "Circular Std", color="black", face="bold", size=26, hjust=0)) +ylab("relative frequencies")+
  xlab("Procent")+ 
  labs(title="User feeling per weekday", x="Weekday", y="Ratings")+
  labs(caption = "(Negative = 1-2 stars, Positive = 4-5 stars)")+
     scale_x_discrete(limits=c("Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag","Söndag"))#facet_wrap(~Market, scales = 'free_x')

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

见解:工作日的分布是均匀的。评论的数量在周三和周日给出,负面和正面评论的份额在周一有一个小的例外。

3.4 每个应用版本的平均评分

**问题:**为了了解用户对每个版本及其功能的接受程度,我们可以按市场绘制每个应用版本的平均评分。

**解决方案:**首先,我们汇总每个应用版本的所有评分,并计算平均评分。然后,我们重命名列,并按升序排列等级。最后,删除评分为零的行,在我们的例子中是第 10、11、12、16、22 和 38 行。然后画出结果。

### Average ratings per App version
# Creates a df with mean values for each app version
df_MeanRatingsVersion <- aggregate(df_App_allMarkets$Rating ~ df_App_allMarkets$App_Version, df_App_allMarkets, mean)# Rename df columns
names(df_MeanRatingsVersion)[1]<- "Version"
names(df_MeanRatingsVersion)[2]<- "Rating"# Sort by ratings ascending
df_MeanRatingsVersion$Version <- factor(df_MeanRatingsVersion$Version, levels = df_MeanRatingsVersion$Version[order(-df_MeanRatingsVersion$Rating)])# Strip specific rows and round mean values
df_MeanRatingsVersion <- 
df_MeanRatingsVersion[-c(10, 11, 12, 16,22,38), ]df_MeanRatingsVersion$Rating <- 
round(df_MeanRatingsVersion$Rating, digits = 2)# Plot average ratings for each app version
ggplot(df_MeanRatingsVersion, aes(x = Version, y = Rating, label=Rating)) +
  geom_bar(fill = "#29E58E", stat = "identity")+
  geom_text(position = 'identity', stat = 'identity', size = 4, vjust = -0.4)+

theme_bw() +
  labs(title="Average ratings for each App Version", size=60) +
  labs(x="App Version", y="Avg. Rating")

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

Plot showing App average rating sorted both by version and rating

**见解:**基于上述数据的故事,希望由 Spotify 内部的人讲述会比外部的人更好。无论如何,很难把握这个评级是与评论时的特定应用版本有关,还是更具一般性。但是,从长期角度来看平均评级,我们可以看到平均评级会随着时间的推移而提高。

3.5 每月每天的平均评分

**问题:**根据你的应用类别和对周期性行为的敏感度,了解平均评分在每个月是否有变化可能会很有趣。例如,研究高使用率和高/低收视率之间是否有关联是很有价值的?

**解决方案:**绘制该月每天的平均评分。开始计算平均评分和重命名列。然后,我们使用POSIXlt将日期转换为一个月中的某一天,并将日期和评级拆分到不同的列中。将评级四舍五入到 1 位数,然后绘制数据。

### Calculate average ratings for each day and change column names
df_MeanRatingsDays <- aggregate(df_App_allMarkets$Rating ~ df_App_allMarkets$Date, df_App_allMarkets, mean)
names(df_MeanRatingsDays)[1]<- "Date"
names(df_MeanRatingsDays)[2]<- "Rating"# Convert dates to day of  month
df_MeanRatingsDays$Date <- unclass(as.POSIXlt(df_MeanRatingsDays$Date))$mday# Split Day of month and avg. rating to separate columns 
df_MeanRatingsDays <- aggregate(df_MeanRatingsDays$Rating ~ df_MeanRatingsDays$Date, df_MeanRatingsDays, mean)
names(df_MeanRatingsDays)[1]<- "Day"
names(df_MeanRatingsDays)[2]<- "Rating"# Round Ratings to 1 digit
df_MeanRatingsDays$Rating <- 
round(df_MeanRatingsDays$Rating, digits = 1)# Plot mean ratings for each day of month
ggplot(df_MeanRatingsDays, aes(x = Day, y = Rating, label = Rating))+
  geom_bar(fill = "#29E58E", stat = "identity")+
  theme_bw() +
  geom_text(position = 'identity', stat = 'identity', size = 4, vjust = -0.4)+labs(title="Average ratings by day of month", size=60) +
  theme(plot.title = element_text(family = "Circular Std", color="black", face="bold", size=26, hjust=0)) +
  labs(x="Day of Month", y="Avg. Rating")+scale_x_discrete(limits=df_MeanRatingsDays$Day)+
  scale_y_continuous(limits = c(0,5))

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

Plot showing how the monthly rating changes during the month

**见解:**除了相当均匀的分布(这只是一个月中某一天不会影响平均评级的标志)之外,没有令人惊叹的见解可以建立其他东西。如果你在金融或医疗行业,这种模式可能会有所不同。我还建议按时间、工作日和月份来研究评级。

3.6 平均评论字符长度

我们的下一个图可能更好地定义为 EDA(探索性数据分析),而不是解决问题,但我想通过查看密度和平均评论长度来了解每个市场的评论长度之间是否有任何差异。因为我们已经知道每个市场的评级分布,所以看看它是否与评论长度相关会很有趣。

所以让我们先统计一下每篇评论的时长,然后按市场统计平均时长。接下来,我们重命名我们的列,并将它们与我们的主数据框架合并在一起,然后对数字进行舍入,然后绘制评论密度,包括市场的平均长度。(现在你可以呼吸了)

### Count length of reviews and create a sorted df_App_allMarkets$Review <- as.character(df_App_allMarkets$Review)
df_App_allMarkets$ReviewLength <- nchar(df_App_allMarkets$Review)# Count the average review lenght for each market
df_MeanLengthMarket <- aggregate(df_App_allMarkets$ReviewLength ~ df_App_allMarkets$Market, df_App_allMarkets, mean)names(df_MeanLengthMarket)[1]<- "Market"
names(df_MeanLengthMarket)[2]<- "AvgReviewLength"df2_App_allMarkets <- 
merge(df_App_allMarkets,df_MeanLengthMarket, by  = "Market")# Round numbers before visualizing
df2_App_allMarkets$AvgReviewLength <- round(df2_App_allMarkets$AvgReviewLength, digits = 2)ggplot(data=df2_App_allMarkets, aes(x=ReviewLength)) + 
  geom_density(aes(y = ..count..), color="#1F3161", fill = "#68E193", alpha=0.6) +
  geom_vline(aes(xintercept = df2_App_allMarkets$AvgReviewLength), linetype = "dashed", size = 0.5)+

facet_wrap(~Market, scales = 'free')+
  geom_text(data=df2_App_allMarkets, mapping=aes(x=AvgReviewLength, y=2, label=AvgReviewLength), check_overlap = TRUE, size=5, angle=0, vjust=1, hjust=-0.5)+
    ylim(0,5)+
    xlim(5,600)+theme_minimal()+
    labs(title="Review Character Length", subtitle = "The average length per review for each market", x="Review Length", y="")+
     theme(plot.title = element_text(family = "Circular Std", color="black", face="bold", size=22, hjust=0)) +
     theme(axis.title = element_text(family = "Circular Std", color="black", face="bold", size=12)) +
     theme(plot.subtitle = element_text(family = "Helvetica", color="black", face="plain", size=14))+
     theme(strip.text = element_text(face="bold", size=12))  +
     theme(panel.border = element_blank(), panel.grid.major = element_blank(),
     panel.grid.minor = element_blank(), axis.line = element_line(colour = "black"))

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

Plot split by market showing the average review length

而且没错,似乎是高收视率和人物少的搭配。我们之前知道的是,美国、加拿大、巴西和德国是最不满意的,并期望巴西是平均字符长度最高的市场。此外,我们匈牙利快乐的营员似乎除了“tkéletes”和“hibátlan”之外没有更多的话要说。

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

Distribution of review length for each rating

为了扩大假设,即消极用户有更多的事情要说,反之亦然,让我们删除geom_vline,从facet_wrap切换到Rating

很明显,快乐的用户没什么可说的,尽管一星评分者写的单词更少,如“垃圾”、“劣质应用”等。给出 2-3-4 星的用户实际上是值得一读的,因为他们经常激励他们的评级。当分析一个不太受欢迎的应用程序时,请注意高评分的长评论。这些评论可能是假的。

4.文本情感分析

4.1 使用 googleLanguageR 翻译评论

在我们的书面评论中可以找到更多的黄金,而不仅仅是每个评级的平均长度。为了进行文本挖掘和基本情感分析,我们必须将所有 12 个市场的书面评论翻译成英语。这可以通过作者马克·埃德蒙森的 googleLanguageR 包的谷歌云机器学习 API 来完成。在进一步操作之前,请遵循认证指南

谷歌是伟大的,但没有什么是免费的,所以在翻译+100.000 评论之前,请确保你知道成本。在我的情况下,我得到了一些免费的学分,但如果你没有,我会建议你从你的数据中随机选取一个样本,如下所示。

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

Translating reviews with Google Cloud Machine Learning API

首先加载并运行您的*。json 身份验证文件,然后选择一个样本大小,并创建一个新的向量,在翻译之前将对其进行翻译。(注意,这可能需要几分钟时间)。

### Prepare for translation of market reviews with Google Translate# Include Gooogle Cloud Service Identity
  gl_auth("API Translate-xxxxxxxxxxxx.json")# Sample 5000 random rows
  df2_App_allMarkets_sample <- sample_n(df2_App_allMarkets, 5000)
  View(df2_App_allMarkets_sample)# Convert Reviews to character vector
  text <- as.character(df2_App_allMarkets_sample$Review)# Create a new df from translated reviews (automatic lang.detection)
  df_Translation <- gl_translate(text, target = "en")# Add english translated reviews to original df
df2_App_allMarkets_sample$Review_translated <- df_Translation$translatedText

要预览和检查结果,请编写:head(df2_App_allMarkets_sample$Review_translated, 10)

4.2 使用 sentimentr 执行情感分析

sentimentr 旨在快速计算句子级别的文本极性情感,并选择性地按行聚合。让我们继续进行情感分析,对每篇翻译后的评论进行正面或负面评分。最后,我们将这些情绪得分与我们的主数据框架绑定在一起,用于未来的绘图。

## 4\. Perform sentiment analysis on each review

# Create a copy of df2_App_allMarkets_sample
  df_ReviewSentiment <- df2_App_allMarkets_sample

# Check  names of the columns and drop those not needed
  names(df_ReviewSentiment)
  df_ReviewSentiment <- subset(df_ReviewSentiment, select = -
  c(Author_URL, Author_Name, ReviewLength, Title))

# Add translated reviews in our data frame
df_ReviewSentiment$ReviewTranslated <-
  as.character(df2_App_allMarkets_sample$Review_translated)

# Perform sentiment analysis and round values
df_ReviewSentiment$reviews_sentiment <- reviews_sentiment %>%    
  sentiment_by(by=NULL)df_ReviewSentiment$reviews_sentiment <- 
  round(df_ReviewSentiment$reviews_sentiment, digits = 2)

查看情感评分最高:head(df_ReviewSentiment$reviews_sentiment, 3)和最低:tail(df_ReviewSentiment$reviews_sentiment, 3).的评论

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

Top 3 positive reviews and bottom 3 most negative

4.3 用星级评定验证情感得分

从上图来看,一切似乎都功能齐全,但进行情感分析远比这复杂。所以问题是——我们能在多大程度上根据客户的书面意见预测他们的评级?

让我们检查一下我们的评分是否与书面评论的平均情感分数相关。

# Correlate sentiment score with ratingsggplot(df_ReviewSentiment, aes(Rating, reviews_sentiment$ave_sentiment, group = Rating)) +
    geom_boxplot(fill="#29E58E") +
    theme_minimal()+
    labs(title="App reviews sentiment score per market", y="Average sentiment score")+
    geom_jitter(shape=16, size=0.7, position=position_jitter(0.3))

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

Boxplot showing the review distribution for each rating

箱线图是一种通过四分位数以图形方式描绘数字数据组的方法。它帮助我们将平均分和每个评论可视化为一个单独的点。它可能不适合挂在你的墙上,但结果是令人满意的。虽然我们有太多得分低于零的五星异常值,但平均得分与星级相关。还有,接近平均水平的评论分布清晰,这很好。

4.3 审查每个市场的情绪得分

为了全面了解应用的感知如何随着时间的推移而变化,我们可以将每个市场从第一次收集评论到最后一次收集评论的应用评论数据中的平均情绪得分可视化。如果这是你的 polestar 指标,我会建议监控平均情绪得分,并使用 Twitter 的 AnomalyDetection 包在得分下降到定义的具体阈值时发出警报。

(注意:小心你的建议,因为每个市场的周期不同。提高声音前先检查 x 音阶)。

# App reviews sentiment score
  ggplot(test, aes(x = Date, y = reviews_sentiment$ave_sentiment, fill=Market)) + 
    geom_smooth(colour="black", size=1) +
    theme_bw() +
    theme_minimal()+
    labs(title="App reviews sentiment score per market", 
          subtitle = "Time period differs due to the amount of reviews in the near future", 
          x="Date", 
          y="Reviews Sentiment Scores")+
    facet_wrap(~Market, scales = "free_x")

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

Visualization of the sentiment score by date and market

4.4 文本挖掘——如何将评论分解成有价值的见解

为了充分理解每个评论的核心,以及每个单词如何影响应用程序的整体感知,我们需要将评论句子分解成单词。这可以通过标记化来实现。在标记化的过程中,一些像标点符号和停用词的字符被丢弃,以将噪声变成信号。

我们想要实现的目标:

  • 把句子分成单个的单词
  • 删除停用词
  • 画出 100 个最常见的单词
  • 给单词添加情感分数
  • 画出最消极和积极的单词
  • 创建一个显示两极分化的词云
# Create a new data frame with only words
  TranslatedText <- as.vector(df_Translation$translatedText)
  TranslatedText <- data_frame(line = 1:5000, text = TranslatedText)# Split reviews to individual words - "Tokenization"
  tidy_df <- TranslatedText %>%
    unnest_tokens(word, text)

# Remove stop words
  data(stop_words)

  tidy_df <- tidy_df %>%
    anti_join(stop_words)

  tidy_df %>%
    count(word, sort = TRUE)

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

# Visualize words that occur +100 times
tidy_df %>%
count(word, sort = TRUE) %>%
  filter(n > 100) %>%
  mutate(word = reorder(word, n)) %>%

 ggplot(aes(word, n)) +
  theme_minimal()+
  labs(title="Words that occur more than 100 times", subtitle = "Occurring individual words in our sampled reviews", x="", y="Contribution to sentiment")+
  geom_col() +
  xlab(NULL) +
  coord_flip()

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

How often each word occur in the review

毫不奇怪,像“应用程序”、“音乐”、“Spotify”和“歌曲”这样的词出现得更频繁。但更有意思的是,“溢价”似乎和“付出”、“讨厌”一样,都是热门话题。我会过滤掉所有出现这些词的评论,然后进行进一步的分析。

现在,让我们在 bing 词典的帮助下,将这些单词分成两个独立的组,积极的和消极的。

# Add sentiment scores to each word
  get_sentiments("bing") 

  bing_word_counts <- tidy_df %>%
    inner_join(get_sentiments("bing")) %>%
    count(word, sentiment, sort = TRUE) %>%
  ungroup()

  bing_word_countsbing_word_counts %>%
    group_by(sentiment) %>%
    top_n(25) %>%
    ungroup() %>%
    mutate(word = reorder(word, n)) %>%# Visualize the distrubution of word sentiment
    ggplot(aes(word, n, fill = sentiment)) +
    geom_col(show.legend = FALSE) +
    theme_minimal()+
    labs(title="Distribution of word sentiment", subtitle = "Words that contribute to positive and negative sentiment", x="", y="Contribution to sentiment")+
    facet_wrap(~sentiment, scales = "free_y") +
  coord_flip()

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

Plot showing the most frequent negative and positive words

我会让你分析这个,但有趣的是,人们对这个应用程序有着根本不同的看法。研究对立面并观察它们如何随着时间的推移而变化将会很有趣。

最后,我们将使用相同的数据,但使用一个词云的可视化,其中一个词的频率由大小反映。

library(reshape2)
library(wordcloud)

# Word cloud showing 200 words
 tidy_df %>%
    anti_join(stop_words) %>%
    count(word) %>%
    with(wordcloud(word, n, use.r.layout=FALSE,max.words = 200))# Word cloud showing 200 words by sentiment score
  tidy_df %>%
    inner_join(get_sentiments("bing")) %>%
    count(word, sentiment, sort = TRUE) %>%
    acast(word ~ sentiment, value.var = "n", fill = 0) %>%
    comparison.cloud(colors = c("#D9383A", "#68E193"),
                     use.r.layout=FALSE,
                     max.words = 200)

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

尽管每个单词的大小不同还有改进的空间,但是很容易理解哪些单词是最常用的。这些云可以按市场和/或应用程序版本划分,以跟踪变化和趋势。

是我中途把你弄丢了,还是你读完了整篇文章?无论如何,我真的很喜欢你关于评级和评论挖掘的反馈和想法。

弗雷德里克·塞德洛夫
收藏家银行的数字分析主管&用户研究

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值