前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >剖析源码讲解Numpy模块中的tile函数

剖析源码讲解Numpy模块中的tile函数

作者头像
触摸壹缕阳光
发布2020-04-08 17:52:13
1.1K0
发布2020-04-08 17:52:13
举报
前言

函数格式tile(A,reps),A和reps都是array_like类型:   1. 参数A几乎所有类型都可以:array, list, tuple, dict, matrix这些序列化类型以及Python中基本数据类型int,float,string,bool类型。   2. 参数reps可以是tuple,list, dict, array, int, bool。但不可以是float, string, matrix(多维度的ndarray数组)类型。

tile函数的功能是重复某个数组。比如tile(A, reps),它的作用就是把A重复reps次,这也可以理解为什么参数reps不能是float、string以及matrix类型 ,对于参数reps不能为float和string类型很好理解,这里不再赘述,后面将介绍为什么参数reps不能是matrix类型。

其实如果可以使用Python广播机制的话是没有必要使用tile函数的。下面就来通过源码来简单分析tile函数的运作,以及如何简单的使用它。

a

tile 源码分析

这里通过tile函数的源码来分析tile函数,为什么能实现复制的功能。

代码语言:javascript
复制
def tile(A, reps):
    try:
        tup = tuple(reps)
    except TypeError:
        tup = (reps,)
    d = len(tup)
    if all(x == 1 for x in tup) and isinstance(A, _nx.ndarray):
        # Fixes the problem that the function does not make a copy if A is a
        # numpy array and the repetitions are 1 in all dimensions
        return _nx.array(A, copy=True, subok=True, ndmin=d)
    else:
        # Note that no copy of zero-sized arrays is made. However since they
        # have no data there is no risk of an inadvertent overwrite.
        c = _nx.array(A, copy=False, subok=True, ndmin=d)
    if (d < c.ndim):
        tup = (1,)*(c.ndim-d) + tup
    shape_out = tuple(s*t for s, t in zip(c.shape, tup))
    n = c.size
    if n > 0:
        for dim_in, nrep in zip(c.shape, tup):
            if nrep != 1:
                c = c.reshape(-1, n).repeat(nrep, 0)
            n //= dim_in
    return c.reshape(shape_out)
  • 首先是函数体的头部
代码语言:javascript
复制
#定义了函数体的名称tile
#参数:
#------A:进行操作的对象
#------reps:重复的次数     
def tile(A, reps):
  • 对reps进行处理
代码语言:javascript
复制
try:
   tup = tuple(reps)
except TypeError:
   tup = (reps,)

这个地方很好理解,它是把传进来的reps通过tuple函数转换成Python元组。下面我们一个个类型来看。

代码语言:javascript
复制
import numpy as np

print("list to tuple:",tuple([1,2]))
print("dict to tuple:",tuple({'A':1,'B':2}))
print("ndarray to tuple:",tuple(np.array([1,2])))
# print("int to tuple:",tuple(1))#error抛出TypeError异常执行tup = (reps,)
# print("bool to tuple:",tuple(True))##error抛出TypeError异常执行tup = (reps,)
#不可以作为reps参数的类型
# print("float to tuple:",tuple(1.2))#error抛出TypeError异常执行tup = (reps,)
print("string to tuple:",tuple('12'))
print("matrix to tuple:",tuple(np.array([[1,2],[3,4]])))

▲result

可以看出前面介绍的不可以作为参数reps类型在这里可以安全的执行通过,所以这个地方并不是限制参数reps类型的根源所在。

▲reps可以为的参数类型

▲reps不可以为的参数类型

其实使用tuple函数转换成元组失败是因为tuple函数它需要的是一个可迭代的参数类型,如果不是的话就会抛出Typeerror的异常,抛出异常在源码中就会把值直接放入元组的第一个位置,这里的(reps,)也是一个元组类型。其实抛出异常对应的无非就是一些标量值,像int,True以及不能作为参数的float类型。

  • 获取元素的长度
代码语言:javascript
复制
#这个其实很好理解
#要注意len((reps,))就是reps的元素个数
d = len(tup)
print(len((True,)))#1

对应上面的分析,这里无非也就是两种情况:

  1. 像int,True这样的标量值,它们被转换成的元素是(value, )这种形式,所以获取长度肯定得到的是1;
  2. 剩下的一些序列化的参数,它们的len长度>=1,不确定,这就需要看这些参数中有多少个元素。
  • 判断语句
代码语言:javascript
复制
if all(x == 1 for x in tup) and isinstance(A, _nx.ndarray):
        # Fixes the problem that the function does not make a copy if A is a
        # numpy array and the repetitions are 1 in all dimensions
        return _nx.array(A, copy=True, subok=True, ndmin=d)
    else:
        # Note that no copy of zero-sized arrays is made. However since they
        # have no data there is no risk of an inadvertent overwrite.
        c = _nx.array(A, copy=False, subok=True, ndmin=d)

这里有几个函数需要注意:

  1. all()函数。all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否有 0、''、False 或者 iterable 为空。如果没有这些返回 True,否则返回 False。
  2. _nx.array(A, copy=False, subok=True, ndmin=d)函数,简单来说就是创建一个ndarray数组。
    1. copy参数:bool,可选。如果为True(默认值),那么对象被复制。否则,副本将仅当__array__返回副本。
    2. subok参数:bool,可选。如果为True,则子类将被传递,通过,否则返回数组将被迫成为一个基类数组(默认)。
    3. ndmin:int,可选。指定结果数组应有尺寸的最小数目。
  3. isinstance(A, _nx.ndarray)函数。isinstance(object, classinfo)函数就是判断object对象类型是否是classinfo类型相同,相同则返回True,否则返回False。
代码语言:javascript
复制
#可以把这个先看成是import numpy as np
import numpy.core.numeric as _nx
print(_nx.ndarray)

#从输出可以看出,isinstance(A, _nx.ndarray)判断A是不是ndarray类型的数据
'''
<class 'numpy.ndarray'>
'''

这里的all(x == 1 for x in tup)就是为什么参数reps不能使用matrix类型的根源所在。

代码语言:javascript
复制
import numpy as np

reps = np.array([[1,2],[3,4]])
print(all((x == 1 for x in reps)))

'''
Traceback (most recent call last):
  File "G:/Python源码/numpy_test/numpy_test.py", line 1763, in <module>
    print(all((x == 1 for x in reps)))
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
'''

然后我们来分析这个判断语句把那些情况筛选出去了:

  1. all(x == 1 for x in tup) and isinstance(A, _nx.ndarray)。通过上面的分析,我们可以知道,如果传入的tup中的值都是1并且(and)我们的A是ndarray数组类型的话,直接return _nx.array(A, copy=True, subok=True, ndmin=d)。直接返回的是A这个ndarray数组,当然如果维度小于d的话,会自动增加d维度。
  2. 如果不是上面那种情况的话。执行c = _nx.array(A, copy=False, subok=True, ndmin=d)。ndmin参数说的就是c的ndim最少就是d,当然这里很自然的就会有三种情况:
    1. c.ndim == d(也就是ndmin)
    2. c.ndim > d[这个时候会继续执行5中的判断语句]
    3. c.ndim < d(这种情况不可能发生,因为指定了ndmin = d)[所以不考虑]
  • 判断语句
代码语言:javascript
复制
if (d < c.ndim):
    tup = (1,)*(c.ndim-d) + tup

这是什么样的情况呢?因为c.ndim也就是c的维度与d也就是元组中的元素个数不匹配,或者说是要进行重复的A的维度和reps重复次数不匹配,这样可想而知是不可以的,所以加入了一个进行处理的代码。

代码语言:javascript
复制
import numpy as np
import numpy.core.numeric as _nx

A = np.array([[1,2],[3,4]])
tup = (2,)
d = len(tup)
c = _nx.array(A, copy=False, subok=True, ndmin=d)
if (d < c.ndim):
    tup = (1,)*(c.ndim-d) + tup

print(d)#1
print(c.ndim)#2
print(tup)#(1, 2)

▲result

  • 形成最终的shape_out
代码语言:javascript
复制
shape_out = tuple(s*t for s, t in zip(c.shape, tup))

因为我们在第五步的时候,已经将我们的c的ndim与我们的tup的维度匹配。我们把shape属性和我们需要进行重复次数的tup中对应的元素相乘形成新的数组,这个结果作为我们最终的shape。

  • tile函数的核心代码-对C的维度进行重复
代码语言:javascript
复制
n = c.size
if n > 0:
    for dim_in, nrep in zip(c.shape, tup):
        #nrep == 1就不需要进行复制了
        if nrep != 1:
            c = c.reshape(-1, n).repeat(nrep, 0)
        n //= dim_in

第六步实现了对最终输出结果的shape的形成。之后就是很重要的如何去进行复制呢?这里的c.size得到的结果是c中元素的个数:

  1. n = 0直接输出结果
  2. n > 0执行复制元素的代码。
代码语言:javascript
复制
for dim_in, nrep in zip(c.shape, tup):
    #nrep == 1就不需要进行复制了
    if nrep != 1:
       c = c.reshape(-1, n).repeat(nrep, 0)
    n //= dim_in

从上面的分析我们也可以知道,到这一步,我们的shape和tup中的元素个数是相互匹配的。

  1. 如果tup中的值为1,则直接跳出,也就是不进行任何的复制操作。
  2. 如果tup中的值不为1,则执行c = c.reshape(-1, n).repeat(nrep, 0);n //= dim_in这两句代码。
    1. 这里的 c.reshape(-1,n)直接把c中的全部元素变成是一个一行n列的一个数组。
    2. repeat(nrep, 0)函数会把c.reshape(-1,n)形成的那一行n列的数组复制nrep次,形成一个nrep行n列的数组。并且这里的0是参数axis的值,也就是行的方向进行重复。
  3. n //= dim_in执行。" / "就一定表示 浮点数除法,返回浮点结果;" // "表示整数除法。
  • 返回结果
代码语言:javascript
复制
return c.reshape(shape_out)

b

示例代码

分析完了代码,看看怎么去使用。

  • A = array([1,2]);reps = (1,1)。1或者True(看成是1)...也是同样的步骤

从源码的分析上看:

  1. 首先一个函数体头部
  2. 对reps进行处理
    1. tup = (1,1)
  3. 获取元组的长度d = 2
  4. 判断语句
    1. if all(x == 1 for x in tup) and isinstance(A, _nx.ndarray)
      1. return _nx.array(A, copy=True, subok=True, ndmin=d)。这里因为A的ndim为1,d = 2,所以会把A扩展成ndim为2的数组。
代码语言:javascript
复制
import numpy as np

A = np.array([1,2])
reps = (1,1)
print(np.tile(A,reps))

'''[[1 2]]'''
  • A = array([[1,2],[3,4]]);reps = (1,2)

从源码的分析上看:

  1. 首先一个函数体头部
  2. 对reps进行处理
    1. tup = (1,2)
  3. 获取元组的长度d == 2
  4. 判断语句
    1. if all(x == 1 for x in tup) and isinstance(A, _nx.ndarray),因为tup中含有非1的元素,所以all中返回False,执行else语句。
    2. c = _nx.array(A, copy=False, subok=True, ndmin=d),此时的c和A的shape相同。
  5. 判断语句
    1. if (d < c.ndim),因为d == c.ndim所以不执行。
  6. 形成最终的shape_out
    1. shape_out = tuple(s*t for s, t in zip(c.shape, tup)),这里会返回shape_out是(2,4)的shape。
  7. tile函数的核心代码-对c的维度进行重复
    1. n = c.size = 4
    2. for dim_in, nrep in zip(c.shape, tup)。因为c.shape是(2,2),tup是(1,2),
      1. 执行第一次的时候,n //= dim_in,4 //= 2 ==2。
      2. 所以当执行第二次循环的时候,nrep == 2!1,所以执行 c = c.reshape(-1, n).repeat(nrep, 0)
        1. c.reshap(-1,2)的结果是[[1 2][3 4]]。
        2. [[1 2][3 4]].repeat(2,0)的执行结果是[[1 2] [1 2] [3 4] [3 4]]
  8. 返回结果
    1. return c.reshape(shape_out),也就是array([[1,2,3,4],[1,2,3,4]]).reshape((2,4)),执行的结果是[[1 2 1 2] [3 4 3 4]]
代码语言:javascript
复制
import numpy as np

A = np.array([[1,2],[3,4]])
reps = (1,2)

print(np.tile(A,reps))

'''
[[1 2 1 2]
 [3 4 3 4]]
'''
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AI机器学习与深度学习算法 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档