Python Grammar
Python
1 | D = collections.defaultdict(list) |
- 创建一个默认映射到list的字典。(帮助避免了很多意外情况)
1 | op,l,r,y,z = map(int,input().split()) |
- 很方便的处理一行的输出,split()默认跳过空白字符。
- 将输入拼成一个字符串,方便处理。
1 | a.sort(key=lambda x:x.r) |
-
对a本生进行排序,这里有key函数:按照key=(比较函数),进行比较。比较函数,可以很方便的用lambda来表示。
-
如果想反过来排序,可以用reverse=True参数。
1 | print("{0} - > {1}".format(i.l,i.r),end="\n") |
- 格式化输出,这里的0和1是format函数的参数,可以用来指定输出的位置。同时python print自带换行,如果不想换行可以自定义end参数。
1 | import bisect |
- 在有序数组中二分查找,right表示严格大于,注意key参数指定的比较大小,所以x.r和3一定是可以比较的类
1 | fav = [False]*26 |
- 开出指定大小的二维数组,且带有初始值.
1 | fav[ord(c)-ord('A')] = True |
利用ord转成ascii码,然后减去’A’的ascii码,得到了一个0-25的数字,可以用来索引数组.
python函数传参规则
在 Python 中,调用函数时参数的传递有特定的规则。特别是关于位置参数和关键字参数的顺序,Python 对此有严格的要求。错误信息 SyntaxError: positional argument follows keyword argument
表示在函数调用时,位置参数出现在了关键字参数之后,这是不允许的。
位置参数和关键字参数
-
位置参数:
- 位置参数是在函数调用时按照函数定义中参数的顺序传递的值。它们不需要使用参数名,因为它们的位置决定了哪个是哪个。
-
关键字参数:
- 关键字参数通过“关键字=值”的形式在函数调用中指定。使用关键字参数时,参数的顺序可以与函数定义中的顺序不同,因为参数值是通过关键字(即参数名)映射的。
规则
在 Python 的函数调用中,关键字参数必须位于所有位置参数之后。这样做的目的是为了避免歧义和提高代码的可读性。如果位置参数出现在关键字参数之后,解释器将无法正确理解每个参数的值应该赋予哪个形式参数。
示例
假设有一个函数定义如下:
1 | def my_function(a, b, c): |
正确的调用方法可以是:
1 | my_function(1, 2, c=3) # 所有位置参数出现在关键字参数之前 |
错误的调用会导致 SyntaxError
:
1 | my_function(1, b=2, 3) # SyntaxError: positional argument follows keyword argument |
在上述错误的例子中,位置参数 3
被放在了关键字参数 b=2
之后,这违反了参数传递的规则。
解决方案
要避免这类错误,确保在任何关键字参数之后不再出现位置参数。如有必要,可以将所有参数都转换为关键字参数,这样顺序就不会影响函数调用了。
总结
当你在编写和调用函数时,应始终保持参数的顺序,使所有位置参数都位于关键字参数之前。这样做不仅符合 Python 的语法规则,还可以使你的代码更加清晰和易于理解。如果遇到 SyntaxError: positional argument follows keyword argument
,检查并调整参数的顺序即可解决问题。
Numpy
1 | array = np.zeros((args.batch_size, args.state_dim)) |
- 创建一个numpy数组,大小由我指定.
1 | s = torch.tensor(self.s, dtype=torch.float) |
- 从numpy数组转换为torch张量. 并且制定dtype的类型.
1 | self.std = np.zeros_like(x) |
- 创建一个和x一样大小的数组,并且初始化为0. 便于快速shape和val的初始化.
1 | #x is list |
- 将list转换为numpy数组,并且深复制一份.
1 | if x.all() == 0: |
numpy有.all(),.any()方法,可以用来判断数组是否全为0,或者是否有0.
进一步的可以有:
1 | # 创建一个示例数组 |
np.nonzero(包含indices使用技巧)
np.nonzero()
是一个非常有用的 NumPy 函数,它返回所有非零元素的索引。在多维数组的情况下,np.nonzero()
会返回一个元组,元组中的每个数组代表了对应维度的索引。
示例:使用多维数组
假设我们有一个 2x3 的二维数组,我们可以使用 np.nonzero()
来找到所有非零元素的位置。下面是具体的代码示例和解释:
1 | import numpy as np |
输出和解释
对于上述代码,输出将是:
1 | (array([0, 1, 1]), array([1, 0, 2])) |
这里的输出解释如下:
- 第一个数组
array([0, 1, 1])
表示非零元素在行上的索引。 - 第二个数组
array([1, 0, 2])
表示非零元素在列上的索引。
这意味着:
- 数组中位置
(0, 1)
的元素是非零的,对应元素3
。 - 数组中位置
(1, 0)
的元素是非零的,对应元素4
。 - 数组中位置
(1, 2)
的元素是非零的,对应元素5
。
访问非零元素
如果你想直接访问这些非零元素,可以使用这些索引:
1 | # 使用索引访问非零元素 |
输出将是包含所有非零元素的一维数组:
1 | [3 4 5] |
小结
通过这个例子,你可以看到 np.nonzero()
如何有效地提供多维数组中所有非零元素的位置。这在处理科学数据或进行特征提取时特别有用,特别是在稀疏数据环境中。
np.random 拓展
NumPy 的 np.random
模块提供了多种生成随机数的功能,能够覆盖从简单的均匀分布到复杂的正态(高斯)分布等。下面我将介绍几种常用的随机数生成函数及其用途:
1. 0 到 1 之间的均匀分布随机数
函数:numpy.random.rand()
此函数生成在[0, 1)区间内的均匀分布的浮点数。这意味着生成的数值大于等于0且小于1。
1 | import numpy as np |
2. 0 到 n 之间的整数随机数
函数:numpy.random.randint()
此函数用于生成一个指定范围内的随机整数。参数包括低(inclusive)和高(exclusive)边界,还可以指定输出数组的形状。
1 | # 生成一个0到10之间的随机整数 |
3. 高斯(正态)分布随机数
函数:numpy.random.randn()
和 numpy.random.normal()
numpy.random.randn()
生成标准正态分布的随机数(均值为0,标准差为1)。numpy.random.normal()
可以生成具有指定均值和标准差的正态分布的随机数。
1 | # 生成一个标准正态分布的随机数 |
4. numpy.random.uniform()
np.random.uniform() 函数用于生成指定区间内的均匀分布的随机数。你可以指定区间的最小值和最大值。
1 | # 生成一个在10到20之间的随机浮点数 |
总结
这些是 NumPy 中常用的几种随机数生成方法,通过它们可以满足绝大多数对随机数的需求,无论是简单的均匀分布、整数随机选择还是复杂的正态分布模拟。每种方法都具有高度的灵活性,可以通过调整参数来满足不同的统计需求和数组形状要求。
nan, inf, 浮点数比较
在NumPy中,np.nan
和np.inf
是用来表示特殊的浮点数值的。np.nan
表示"非数"(Not a Number),而np.inf
表示正无穷大。此外,-np.inf
表示负无穷大。这些特殊值在处理涉及未定义或超出数值范围的运算时非常有用。
np.nan (Not a Number)
np.nan
用于表示未定义或不可表示的值,如0/0
的结果。它是IEEE浮点数规范的一部分,并在NumPy中广泛用于表示缺失数据或异常值。在NumPy中,任何涉及np.nan
的运算通常都会返回np.nan
,除了一些特定的函数,比如np.nanmean
,这些函数可以忽略nan
值进行计算。
np.inf 和 -np.inf
np.inf
表示正无穷大,通常用于表示超出浮点数最大范围的值,比如1/0
的结果。-np.inf
表示负无穷大,用于表示超出浮点数最小范围的值,如-1/0
的结果。在NumPy中,无穷大的值可以参与计算,其行为符合数学上的预期(例如任何正数乘以np.inf
等于np.inf
)。
比较涉及浮点数的值
浮点数因为其表示方式,常常无法精确表示某些值,例如常见的0.1 * 3
不会精确等于0.3
,尽管数学上它们是相等的。这是由于浮点数的精度问题导致的。
如何判断两个浮点数是否相等?
使用NumPy时,可以使用np.isclose()
或np.allclose()
函数来比较两个浮点数或浮点数组是否近似相等,考虑到计算中的精度误差。
1 | import numpy as np |
这个方法提供了一个实用的方式来处理浮点数的比较,通过设定容忍的误差范围,使得在实际应用中可以判断两个数在数值上是否"相等"。
总结
在使用NumPy处理数据时,了解np.nan
和np.inf
的行为非常重要,特别是在数据清洗和预处理阶段。此外,了解如何正确比较浮点数也是科学计算中一个关键的问题。这些工具和函数可以帮助你更有效地进行数据分析和数值计算。
矩阵乘法
- 使用
*
或np.multiply
来进行元素对元素的乘法。 - 使用
dot
或@
来执行矩阵乘法,适用于一维数组时,计算内积。 - 使用
np.matmul
或@
来执行矩阵乘法,不支持一维数组的标量乘积,对高维数组有特定的广播规则。
选择哪种运算取决于您的具体需求,尤其是在处理矩阵运算和更高维度数据时。
np.random.choice
np.random.choice()
是 NumPy 库中一个非常有用的函数,它允许从给定的一维数组或者范围内随机抽取元素,同时也可以指定抽取元素的概率分布和输出样本的数量。这个函数在模拟、抽样调查、随机实验等多种场景中非常实用。
函数基本用法
基本语法:
1 | numpy.random.choice(a, size=None, replace=True, p=None) |
- a:如果是一个一维数组或者列表,随机数将从这些元素中抽取。如果是一个整数,抽取的元素将是从
np.arange(a)
生成的。 - size:输出样本的数量。默认为
None
,这意味着输出一个值。可以是一个整数或元组(用于生成多维数组)。 - replace:布尔值,表示是否可以重复选择同一元素。默认为
True
,即允许重复抽取。如果为False
,则抽取的样本中不会有重复元素,此时size
不能超过a
的长度。 - p:一维数组或列表,指定
a
中每个元素对应的抽取概率。数组的长度必须与a
相匹配,并且概率之和应为1。
示例
从给定数组中随机选择
1 | import numpy as np |
从数字范围内随机选择
1 | # 从0到9中随机选择5个数字,允许重复 |
使用场景
- 模拟实验:在进行科学实验和模拟研究时,
np.random.choice()
可以用来随机选择试验条件或样本。 - 数据采样:在机器学习和统计分析中,从大数据集中随机抽取样本进行训练和测试。
- 概率研究:通过指定不同的概率分布,研究和模拟概率事件的结果。
np.random.choice()
提供了一种灵活的方式来实现和模拟从给定数据集或数值范围内的随机抽样过程,其多功能性使它成为数据科学和统计学中一个不可或缺的工具。
in place operation
在 NumPy 中使用 np.add(A, B, out=B)
和直接使用 B = A + B
来计算两个数组的和虽然都能得到相同的数学结果,但这两种方法在内存使用和计算效率方面存在差异。下面是这两种方法的主要区别:
1. np.add(A, B, out=B)
使用 np.add()
函数并指定 out
参数可以在现有数组上就地进行操作,这意味着结果直接存储在 out
指定的数组中。在这种情况下,out=B
表示结果将被存储在数组 B
中,覆盖原有数据。这种方法的主要优点是减少内存分配,因为不需要创建新的数组来存储结果:
1 | import numpy as np |
这里 B
被更新为 A
和 B
的和。这种方式特别适合处理大数组,因为它可以减少内存消耗和可能的内存分配延迟。
2. B = A + B
这种方式比较直观,是将 A
和 B
相加的结果赋值给 B
。这实际上涉及到先计算 A + B
的和,然后创建一个新的数组存储这个结果,最后将这个新数组的引用赋值给变量 B
。这意味着原始的 B
数组被新的数组引用替代了,原来的数组如果没有其他引用将被垃圾回收:
1 | A = np.array([1, 2, 3]) |
尽管结果相同,但这种方法会使用更多的内存(至少在计算过程中),因为需要临时存储 A + B
的结果,然后再复制给 B
。对于小数组,这种差异可能不明显,但对于处理非常大的数组时,额外的内存分配和释放可能会导致性能下降。
总结
np.add(A, B, out=B)
是一种就地操作,可以帮助节省内存,特别适用于数据量大的数组计算。B = A + B
更直观,适用于快速编程和小规模数据处理,但可能涉及更多的内存分配。
选择哪种方法取决于特定应用的需求,包括对性能的关注、对内存使用的优化,以及代码的可读性。
dtype自定义结构
是的,dtype
参数可以用于指定自定义的数据类型。你可以通过 NumPy 的 dtype
对象来定义自己想要的数据类型,并将其传递给 np.zeros
函数来创建具有自定义数据类型的数组。
下面是一个示例,演示如何创建一个具有自定义数据类型的全零数组:
1 | import numpy as np |
在这个示例中,我们首先定义了一个自定义的数据类型 my_dtype
,其中包含两个字段:'name'
和 'age'
,分别使用 'S10'
和 int
表示它们的数据类型。然后,我们使用 np.zeros
函数创建了一个具有这个自定义数据类型的全零数组,并输出了数组的内容。
这样,你就可以根据需要灵活地定义自己的数据类型,并将其应用到 NumPy 数组中。
定位np多维数组
np.argmin(sample50)
返回的是一个数组中最小元素的索引,而不是一个矩阵。因此,它返回的是一个整数值,表示最小元素在扁平化数组中的索引位置。
如果 sample50
是一个二维数组(即矩阵),np.argmin(sample50)
返回的索引值是基于扁平化后的数组的索引。这意味着它会将二维数组展平成一维数组,然后找到最小值在展平后的一维数组中的索引位置。因此,返回的是一个整数值而不是一个矩阵。
**要想找到最小值在原始矩阵中的索引位置,可以使用 np.unravel_index
函数将扁平化后的索引转换回原始矩阵中的索引位置。**例如:
1 | import numpy as np |
这样就能够找到最小值在原始矩阵中的行和列的索引位置。
numpy array的遍历索引
在 NumPy 中,np.ndindex
是一个非常实用的函数,用于生成多维数组的索引。前缀 nd
代表 “n-dimensional”,即“多维的”。np.ndindex
生成一个迭代器,该迭代器可以产生一个给定形状的多维数组的所有索引组合。这在需要遍历多维数组的每个元素时特别有用。
功能和用法
np.ndindex
通常用于需要遍历多维数组每个元素并进行操作的情况。它简化了获取多维数组索引的过程,使你能够以一种简洁的方式迭代所有的索引组合。
语法:
1 | np.ndindex(*shape) |
- shape:是一个整数序列,指定了要生成索引的数组的形状。
示例
假设你有一个三维数组,并且你想迭代它的所有位置索引:
1 | import numpy as np |
这个代码将输出 (3, 3, 3)
形状数组的所有索引,从 (0, 0, 0)
到 (2, 2, 2)
。
应用场景
np.ndindex
的使用场景非常广泛,特别是在需要对数组的每个元素进行某种操作时。它避免了使用多层嵌套循环来遍历多维数组的复杂性。以下是一些常见的应用场景:
- 初始化多维数组:在数组的每个位置上根据其索引初始化值。
- 数组操作:对数组的每个元素应用复杂的函数或操作,这些操作可能依赖于元素的索引。
- 数据分析:在进行数据分析时,可能需要访问和操作多维数据集的每个数据点。
- 图像处理:在处理图像数据(通常存储为多维数组)时,可能需要遍历像素以应用滤镜、做颜色转换等。
总之,np.ndindex
是一个强大的工具,可以简化多维数组操作的编程,提高代码的清晰度和效率。
np.unique
- 没有
axis
参数的情况:
1 | colors = np.unique(Image.reshape(-1, 3)) |
这种情况下,np.unique
会将数组展平成一维数组,然后找到其中所有唯一的元素。因此,这会返回数组中每个颜色通道的所有唯一值,而不是整个颜色(即RGB三元组)。
假设 Image
是一个形状为 (height, width, 3)
的彩色图像。将 Image
重新调整为 (-1, 3)
的形状会得到一个形状为 (height * width, 3)
的二维数组,但在没有 axis
参数的情况下,np.unique
会将这个数组视为一维数组,然后返回所有唯一的颜色通道值。
例如:
1 | import numpy as np |
输出:
1 | [ 0 255] |
这表示每个颜色通道中唯一的值是0和255。
- 有
axis
参数的情况:
1 | colors = np.unique(Image.reshape(-1, 3), axis=0) |
这种情况下,np.unique
会沿着指定的轴(这里是 axis=0
)查找唯一的行。因此,这会返回图像中所有唯一的颜色(即唯一的RGB三元组)。
例如:
1 | import numpy as np |
输出:
1 | [[ 0 0 255] |
这表示图像中所有唯一的颜色值,即RGB三元组。
总结
- 没有
axis
参数:np.unique
会将输入数组展平成一维数组,返回所有唯一的元素值。对于一个形状为(height, width, 3)
的彩色图像,这将返回所有颜色通道中的唯一值,而不是整个颜色。 - 有
axis
参数:np.unique
会沿着指定的轴查找唯一的行。对于一个形状为(height, width, 3)
的彩色图像,这将返回图像中所有唯一的颜色(即唯一的RGB三元组)。
根据你的具体需求选择相应的用法,以获取所需的唯一元素或唯一颜色。
np.bincount
在 np.bincount
函数中,bin
指的是"箱"或"桶"(bin),这是一个统计学术语,表示将数据按类别或范围进行分组。bincount
是 “bin count” 的缩写,表示对这些分组后的数据进行计数。
np.bincount
的功能
np.bincount
函数用于对非负整数数组中的元素进行计数,并返回每个整数值出现次数的数组。可以通过添加权重来计算加权和。具体用法如下:
1 | np.bincount(x, weights=None, minlength=0) |
x
:非负整数数组。weights
:可选参数。如果提供,则返回的数组是加权和,而不是简单的计数。minlength
:可选参数。指定返回数组的最小长度。
示例解释
以下是两个使用 np.bincount
的示例,分别展示了不带权重和带权重的情况。
示例1:简单计数
1 | import numpy as np |
在这个例子中:
- 数组
x
中包含的元素为 0, 1, 1, 2, 2, 2, 3。 np.bincount(x)
返回一个数组,表示每个非负整数值出现的次数:0
出现1
次1
出现2
次2
出现3
次3
出现1
次
- 结果是
[1, 2, 3, 1]
。
示例2:加权计数
1 | import numpy as np |
在这个例子中:
- 数组
x
中包含的元素为 0, 1, 1, 2, 2, 2, 3。 - 对应的权重数组
weights
为 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1。 np.bincount(x, weights=weights)
返回一个数组,表示每个非负整数值的加权和:0
的权重和为0.5
1
的权重和为0.6 + 0.7 = 1.3
2
的权重和为0.8 + 0.9 + 1.0 = 2.7
3
的权重和为1.1
- 结果是
[0.5, 1.3, 2.7, 1.1]
。
结合你的例子
在你的代码中:
1 | import numpy as np |
D
是一个包含 100 个随机浮点数的数组。S
是一个包含 100 个随机整数(范围是 0 到 9)的数组,表示每个D
值所属的类别。D_sums = np.bincount(S, weights=D)
:- 对于每个类别,计算
D
中对应值的加权和。
- 对于每个类别,计算
D_counts = np.bincount(S)
:- 对于每个类别,计算出现的次数。
D_means = D_sums / D_counts
:- 对于每个类别,计算加权和除以计数,即每个类别的平均值。
总结
np.bincount
函数中的 bin
表示将数据按类别进行分组统计,这在统计分析和数据处理中非常有用。它允许我们对数据进行分类计数或加权和计算,从而简化了很多统计操作。
多维numpy数组的遍历
在 Python 中,遍历数组(NumPy 数组或其他可迭代对象)时,可以使用 enumerate
方法来获取元素的索引和值。enumerate
是一个内置函数,它为可迭代对象生成一个(索引,元素值)对的迭代器。
使用 enumerate
遍历 NumPy 数组
虽然 enumerate
通常用于一维数组,但它也可以与多维数组一起使用。对于多维数组,可以结合 nditer
和 enumerate
使用,以便在遍历时获得元素的索引和值。
示例 1:遍历一维 NumPy 数组
1 | import numpy as np |
输出:
1 | Index: 0, Value: 10 |
示例 2:遍历多维 NumPy 数组
对于多维数组,可以使用 np.ndenumerate
直接获取多维数组的索引和值:
1 | import numpy as np |
输出:
1 | Index: (0, 0), Value: 1 |
使用 ndindex
遍历多维数组
np.ndindex
是另一种在多维数组中获取索引的方法:
1 | import numpy as np |
输出:
1 | Index: (0, 0), Value: 1 |
总结
- 一维数组:使用内置的
enumerate
函数来遍历数组,并获取每个元素的索引和值。 - 多维数组:
- 使用
np.ndenumerate
来遍历,并获取每个元素的多维索引和值。 - 使用
np.ndindex
来遍历数组的索引,然后通过索引访问元素。
- 使用
这些方法提供了灵活的方式来遍历和处理不同维度的 NumPy 数组,帮助你更有效地操作数组数据。
slice
slice
在Python中既不是一个list
函数,也不是一个tuple
函数,而是一个独立的内置类型,用于创建切片对象,这些对象可以描述序列的一部分。
关于 slice
类型:
- 作用:
slice
对象通常用来代表序列中的一段,包括列表(list
)、元组(tuple
)、字符串(str
)等。它通过指定开始索引、结束索引和步长来定义这一段。 - 构造函数:
slice
通过slice(start, stop[, step])
来创建。 - 用途:创建的
slice
对象可以用作任何序列类型的索引。
示例说明:
1 | # 创建一个 slice 对象 |
在这些示例中,slice
对象 my_slice
被用来从列表、元组和字符串中提取特定的元素。通过使用切片对象,你可以非常灵活地从各种序列中提取所需部分,而不必每次都硬编码起止索引和步长。
关于使用场景:
- 数组和矩阵操作:在使用像 NumPy 这样的库进行数组操作时,
slice
对象非常有用,尤其是在处理多维数组时。 - 数据处理:在进行数据处理和数据清洗时,可以通过
slice
对象来选择或排除数据集中的特定部分。
总结来说,slice
是一种非常灵活的工具,可以帮助你在处理Python中的各种序列数据时,以编程方式选择序列的一部分。
sliding_window_view
sliding_window_view
是 NumPy 库中的一个函数,用于创建滑动窗口视图。这允许您以滑动窗口的方式查看数组中的子数组。
要使用 sliding_window_view
,您需要安装 NumPy 库并导入相应的函数。确保您使用的是 NumPy 1.20.0 或更高版本,因为 sliding_window_view
是在这个版本中引入的。
安装 NumPy
如果还没有安装 NumPy,可以使用以下命令安装:
1 | pip install numpy |
使用 sliding_window_view
在 Python 脚本中导入 sliding_window_view
并使用它:
1 | import numpy as np |
示例解释
-
导入 NumPy 和
sliding_window_view
:1
2import numpy as np
from numpy.lib.stride_tricks import sliding_window_view -
创建示例数组:
1
Z = np.random.randint(0, 10, (10, 10))
-
创建滑动窗口视图:
1
windows = sliding_window_view(Z, window_shape=(4, 4))
-
打印结果:
1
print(windows)
sliding_window_view
函数创建了一个新的视图,其中包含了原始数组 Z
的所有可能的 4x4 子数组。这对于图像处理、信号处理和各种数据分析任务非常有用。
确保您使用的是 NumPy 1.20.0 或更高版本,以便使用 sliding_window_view
函数。如果您遇到任何问题,请确认您的 NumPy 版本:
1 | import numpy as np |
如果版本低于 1.20.0,可以通过以下命令升级:
1 | pip install --upgrade numpy |
这应该可以帮助您顺利使用 sliding_window_view
函数。
np.where
np.where
是一个非常强大的函数,它可以返回满足特定条件的数组元素的索引。具体来说,np.where(Z1 == max_value)
返回的是一个元组,其中包含满足条件的元素的索引。
让我们详细解释一下 np.where
的返回值。
示例
假设我们有一个一维数组 Z1
,并且我们想找到其中所有等于最大值的元素的索引:
1 | import numpy as np |
输出解释
输出结果将是:
1 | Array: [1 3 7 9 7 5 9 9] |
详细解释
max_value = Z1.max()
这行代码计算数组Z1
中的最大值,结果是9
。np.where(Z1 == max_value)
这行代码查找数组Z1
中所有等于9
的元素的索引。
np.where(Z1 == max_value)
返回一个元组 (array([3, 6, 7]),)
:
- 这个元组包含一个数组,数组中的每个元素是满足条件
Z1 == max_value
的索引。 - 在这个例子中,最大值
9
出现在索引3
、6
和7
处,所以返回的数组是array([3, 6, 7])
。
如果 Z1
是一个多维数组,那么 np.where
返回的将是多个数组,分别对应不同维度上的索引。
多维数组的示例
让我们看一下多维数组的情况:
1 | import numpy as np |
输出解释
输出结果将是:
1 | Array: |
在这个例子中,最大值 9
出现在 (1, 0)
、(2, 0)
和 (2, 1)
处。所以 np.where
返回两个数组:
- 第一个数组
array([1, 2, 2])
是行索引。 - 第二个数组
array([0, 0, 1])
是列索引。
结合这两个数组,我们可以找到所有满足条件的元素的索引。
结论
np.where(Z1 == max_value)
返回一个包含满足条件的元素索引的元组。在一维数组中,这个元组包含一个数组。在多维数组中,这个元组包含多个数组,分别对应不同维度的索引。这样,我们可以非常方便地找到数组中所有满足特定条件的元素的位置。
np自定义派生类
1 | class Symtric(np.ndarray): |
这段代码定义了一个名为 Symtric
的自定义类,它派生自 np.ndarray
。这个类重写了 __setitem__
方法,以便在设置数组元素时保持对称性。
同时注意view作为类型转化!
在 NumPy 中,将一个数组视图转换为自定义子类(如 Symtric)的主要方式是使用 .view() 方法。如果你不想使用 .view(),可以考虑其他方式来实现类似的功能,例如创建一个新的实例并手动赋值属性。不过,.view() 是最直接和推荐的方法,因为它保持了底层数据的共享,从而避免了额外的内存开销。
利用bool索引,选择性赋值
1 | Z = np.random.randint(1,3,(5,5)) |
np.einsum
知乎总结:https://www.zhihu.com/question/53365039/answer/3167199956
np.einsum
是一个非常强大的工具,能够以非常灵活和高效的方式处理数组的运算。这里,我将通过多个例子来展示它的不同用途:
1. 向量内积(点积)
对于两个向量 ( a ) 和 ( b ),计算它们的点积可以使用 np.einsum
如下:
1 | a = np.array([1, 2, 3]) |
这里的 'i,i->'
表示对两个索引 ( i ) 上的元素进行乘积,并对所有乘积结果求和,输出是一个标量。
2. 矩阵与向量的乘积
对于矩阵 ( A ) 和向量 ( v ),计算它们的乘积可以如下:
1 | A = np.array([[1, 2], [3, 4]]) |
这里 'ij,j->i'
表示对矩阵 ( A ) 的第 ( j ) 列和向量 ( v ) 的第 ( j ) 个元素进行乘积,并对 ( j ) 索引求和,输出是一个向量。
3. 矩阵乘法
对于两个矩阵 ( A ) 和 ( B ),计算它们的矩阵乘积:
1 | A = np.array([[1, 2], [3, 4]]) |
'ik,kj->ij'
指对 ( A ) 的第 ( k ) 列和 ( B ) 的第 ( k ) 行进行乘积,并对 ( k ) 索引求和,输出是一个矩阵。
4. 计算矩阵的对角线
如果你有一个矩阵 ( A ) 并想得到它的对角线元素:
1 | A = np.array([[1, 2], [3, 4]]) |
'ii->i'
指从矩阵 ( A ) 中提取 ( i, i ) 位置的元素(即对角线上的元素)。
5. 计算张量的迹
计算一个张量(多维数组)的迹:
1 | T = np.random.rand(3, 3, 3) |
'iii->'
表示计算多维张量 ( T ) 中 ( (i, i, i) ) 位置的元素之和。
6. 批量矩阵乘法(两批矩阵)
如果你有两批矩阵 ( A ) 和 ( B ) 并想对它们每一对进行矩阵乘法:
1 | A = np.random.rand(10, 3, 4) |
'bij,bjk->bik'
表示对每一批的矩阵进行乘法运算。
这些例子展示了 np.einsum
在数组操作中的多样性和强大功能。它可以用来实现
Torch
一个典型的神经网络forward过程:
1 | def __init__(self,args): |
- 这里定义了一系列全连接层和激活函数,并且通过神经网络拟合了两个参数,通过参数返回一个分布.
1 | self.optimizer_actor = torch.optim.Adam(self.actor.parameters(), lr=self.lr_a, eps=1e-5) |
- 定义了一个Adam优化器,并且指定了学习率和eps.
- self.actor.parameters()指定了所以可训练的参数.
1 | def evaluate(self,s): |
-
将s转换成浮点张量,同时即使是单个数据点,也需要模拟成批量大小为1的数据,用来满足模型输入要求.
-这个evaluate
函数定义了一个评估过程,主要用于获取给定状态s
下的行动者(actor)模型输出的行动a
。该函数通过几个步骤处理输入状态并最终返回一个行动值。下面是对这些步骤的详细解释: -
模型预测:
a = self.actor.mean(s).detach().numpy().flatten()
:这行代码执行了几个操作来获取行动值a
。.detach()
:然后,使用.detach()
将预测结果从当前计算图中分离,这样做是为了避免在这一步对梯度进行追踪,因为我们只是在评估而非训练模型。.numpy()
:接着,使用.numpy()
将结果转换为 NumPy 数组,以便进行进一步的处理或分析。.flatten()
:最后,使用.flatten()
将数组展平,这意味着如果结果是多维的,它将被转换为一维数组。
这里重点再谈谈.flatten()和squeeze()的区别:
是的,在这个特定的情况下,你可以使用 .squeeze()
方法替代 .flatten()
来去除单维度条目。.squeeze()
方法用于从数组的形状中移除单维度条目,不改变数组中元素的总数。如果你的目标是将形状为 (1, n)
的数组转换成 (n,)
,.squeeze()
完全可以胜任,因为它正是用来移除那些大小为1的维度。
使用 .squeeze()
的效果
假设原始的 NumPy 数组形状是 (1, n)
,使用 .squeeze()
后,数组将变为形状 (n,)
。这与 .flatten()
得到的结果相同,但 .squeeze()
更加直观地表达了你的意图,即移除单一维度而非展平整个数组。
.flatten()
与 .squeeze()
的区别
.flatten()
:无论数组的原始形状如何,都会返回一个完全展平的一维数组。.squeeze()
:只移除数组中的单一维度(大小为1的维度),不改变其他维度的大小。
因此,如果你确定需要移除的是单一维度,特别是在处理批量数据(批量大小为1)的场景中,.squeeze()
是一个更合适的选择。它不仅可以达到预期的效果,也使代码的意图更加明确。
总的来说,在这种情况下,.squeeze()
可以替代 .flatten()
来移除数组中的单一维度,使得代码的意图更清晰,同时保持了结果的一致性。
可以看到,GPT告诉我们这里因为我们是评估阶段,一次只会送批量为1的数据去预测,所以返回的也是批量为1的线性动作空间,直观上用squeeze()更加合适. flatten()是将多维数组展平成一维数组,在此刚好也能用罢了.
1 | .squezze() |
.squeeze()
方法在使用时可以不指定参数,也可以指定一个参数来指定需要移除的维度索引。这取决于你的具体需求。
-
不指定参数:当你不指定参数时,
.squeeze()
会移除数组中所有单维度条目。这意味着,如果数组中有任何大小为1的维度,它们都会被移除,结果数组的形状将排除这些维度。1
2
3
4import numpy as np
a = np.array([[1, 2, 3]]) # 形状为 (1, 3)
b = a.squeeze() # b 的形状将变为 (3,) -
指定参数:你可以指定一个维度索引作为参数给
.squeeze()
,这样就只会尝试移除该指定维度。如果指定的维度不是单维度(即其大小不为1),那么数组将不会改变。1
2
3
4import numpy as np
a = np.array([[[1, 2, 3]]]) # 形状为 (1, 1, 3)
b = a.squeeze(0) # 尝试移除第0维,b 的形状变为 (1, 3)如果尝试移除的维度大小不为1,使用
.squeeze()
时指定该维度将不会引发错误,数组也不会改变。这与.reshape()
或其他可能引发异常的方法不同。
总的来说,.squeeze()
方法是灵活的,既可以不带参数使用,也可以通过指定参数来移除特定的单一维度。不指定参数时,它会移除所有单维度条目,而指定参数时,则只针对特定维度操作,这为处理不同的数据形状提供了便利。
1 | with torch.no_grad(): |
- 梯度追踪:PyTorch 会继续追踪操作以计算梯度,这在训练时是必需的,但在模型评估或执行推理时是不必要的。继续追踪梯度会导致不必要的计算资源消耗,增加内存使用,并可能导致在不需要梯度的场景中意外修改权重。
1 | for delta,d in zip(reversed(deltas.flatten().numpy()),reversed(done.flatten().numpy())): |
- 函数在这里被用于同时遍历两个反转后的数组。它会创建一个迭代器,该迭代器生成一对元素的元组,其中每对元素分别来自两个数组的相同位置。因为两个数组都已被反转,所以实际上这个遍历是从原始数组的末尾开始,向前遍历到数组的开头。
- 如果deltas和done不等长,这样的迭代会在短的数组到头后停止.
1 | for index in BatchSampler(SubsetRandomSampler(range(self.batch_size)),self.mini_batch_size,False): |
这段代码,在随机采样上有高的泛用性,继续交给GPT4解释:
这行代码在 PyTorch 中使用了 BatchSampler
和 SubsetRandomSampler
杂交的方式来迭代指定批量大小的索引。这是在处理批量数据时,特别是在机器学习和深度学习训练过程中常见的做法。下面详细解释这个过程:
-
range(self.batch_size)
:- 这部分生成一个从0到
self.batch_size - 1
的整数序列。self.batch_size
表示整个数据集中单次迭代(或称为"批次")的样本数。
- 这部分生成一个从0到
-
SubsetRandomSampler(range(self.batch_size))
:SubsetRandomSampler
接收一个索引列表作为输入,并从这个列表中随机采样,没有替换(即每个样本只能被采样一次)。在这里,它用于从上述范围(即整个批次)中随机选择索引,这种随机性增加了模型训练的泛化能力。
-
BatchSampler(SubsetRandomSampler(...), self.mini_batch_size, False)
:BatchSampler
从其接收到的采样器(这里是SubsetRandomSampler
)中进一步采样,将数据集分成指定大小的小批量(mini-batches)。self.mini_batch_size
表示这些小批量中的样本数。最后一个参数False
表示不允许最后一批小批量的大小小于self.mini_batch_size
(如果为True
,则允许最后一批小于self.mini_batch_size
)。
-
for index in BatchSampler(...)
:- 这个循环遍历
BatchSampler
生成的每个小批量的索引。每次迭代提供的index
是一个包含self.mini_batch_size
大小的小批量索引的列表,可以用这些索引来从数据集中抽取相应的样本进行训练或其他处理。
- 这个循环遍历
总结:这段代码通过结合 SubsetRandomSampler
和 BatchSampler
,实现了对整个批次数据随机采样并分成指定大小的小批量的过程。这种方法常用于机器学习训练中,有助于提升模型的泛化性能并降低训练过程中的内存消耗。
1 | dist_entropy = dist_now.entropy().sum(1,keepdim=True) |
- 求和操作通常会减少维度,我们用keepdim=True来保持维度.
1 | torch.clamp |
torch.clamp
是 PyTorch 中的一个函数,用于将输入张量中的所有元素限制在指定的范围内。如果一个元素的值低于给定的最小值,它会被设置为这个最小值;如果一个元素的值高于给定的最大值,它会被设置为这个最大值。对于在这个范围内的元素,它们的值不会改变。这个操作在深度学习中常用于梯度裁剪、激活函数实现等场景,以防止数值溢出或确保数值稳定性。
函数的基本使用格式如下:
1 | torch.clamp(input, min, max, *, out=None) -> Tensor |
input
:输入张量。min
:范围的下限。所有小于min
的元素都将被设置为min
。max
:范围的上限。所有大于max
的元素都将被设置为max
。out
:输出张量。如果指定,结果将被写入这个张量中。
示例:
1 | import torch |
输出结果将是:
1 | tensor([0.5, 1.0, 0.0, 0.0, 1.0]) |
这个例子中,-0.5
和 -1.5
被设置为了 0
(因为它们小于下限0),2.5
被设置为了 1
(因为它大于上限1),而 0.5
和 1.5
被分别设置为了自己和上限 1
。
1 | for p in self.optimizer_actor.param_groups: |
动态调整指定model的学习率, 也有很强的泛用性.
这段代码遍历 self.optimizer_actor
中的所有参数组(param_groups
),并将每个参数组的学习率('lr'
)设置为当前的学习率值 lr_a_now
。在 PyTorch 中,优化器(如 Adam、SGD 等)可以有多个参数组,每个组可以有自己的学习率和其他优化设置。这种设计允许对模型的不同部分应用不同的学习率和更新策略,提供了更高的灵活性。
解释
-
self.optimizer_actor
是一个 PyTorch 优化器实例,负责更新某个模型(在这个上下文中是“行动者”模型)的参数。这个优化器在初始化时,会被分配一个或多个参数组。 -
param_groups
是优化器实例中的一个属性,包含了所有的参数组。每个参数组是一个字典,包含了参数(如权重)的引用和这组参数的优化设置(如学习率lr
)。 -
循环
for p in self.optimizer_actor.param_groups:
遍历所有的参数组。 -
p['lr'] = lr_a_now
在每次循环中,将当前参数组的学习率设置为lr_a_now
。这允许动态地调整学习率,是实现学习率衰减策略或根据训练进度调整学习率的常用手段。
应用场景
这种做法通常用在需要根据训练进度动态调整学习率的场景中,例如实现学习率衰减(learning rate decay)、预热学习率(learning rate warmup)或其他复杂的学习率调度策略时。动态调整学习率可以帮助模型更好地收敛,避免在训练早期的大幅度参数更新或在训练后期的过度拟合。
直接在GPU上创建新张量
1 | import torch |
.detach()方法的作用与特性
在PyTorch中,.detach()
方法是一个非常重要的功能,尤其是在处理计算图和梯度计算时。了解.detach()
的作用和特性对于有效地控制梯度流和优化内存使用非常关键。
作用
-
分离梯度:
.detach()
方法创建一个新的张量,该张量与原始张量共享数据但不共享历史。换句话说,使用.detach()
方法返回的张量不会在其操作上记录梯度,不参与梯度传播,因此对其的任何操作都不会影响到原始张量的梯度。 -
避免梯度计算: 它常用于防止PyTorch自动计算梯度或保存计算历史,这在某些情况下非常有用,比如在评估模型时不想影响到模型参数的梯度,或者当你只需要张量的数据而不需要其梯度时。
特性
-
共享数据:
.detach()
返回的新张量与原始张量共享内存(即数据),这意味着如果更改了其中一个张量的数据,另一个张量的数据也会相应改变。这有助于减少内存使用,因为不需要复制数据。 -
不参与自动梯度计算: 对
.detach()
返回的张量进行的操作不会被纳入到计算图中,因此这些操作不会影响梯度。这使得.detach()
非常适合用于停止某些变量的梯度跟踪。 -
用途广泛:
.detach()
方法在模型训练、评估、特定操作的梯度屏蔽等多种场合中都非常有用。例如,它可以用于冻结部分模型参数的梯度,或者在计算某些指标时暂时停止跟踪梯度。
使用场景示例
假设你在训练一个模型,并希望使用模型的一部分输出作为后续计算的输入,但你不希望这些后续计算影响到模型参数的梯度。这时,就可以使用.detach()
方法:
1 | import torch |
在上述代码中,output_detached
和output
共享数据,但对output_detached
的任何操作都不会影响到output
的梯度,因为output_detached
已经从计算图中分离出来了。
总之,.detach()
是处理PyTorch张量时控制梯度流动的一个强大工具,可以帮助开发者更精确地管理内存使用和梯度计算。
实际上,虽然.detach()
创建的张量output_detached
与原始张量output
共享数据,但对output_detached
进行的操作不会直接影响output
的值。这是因为直接修改output_detached
的值(例如,通过索引赋值)通常是不允许的,因为它们是从计算图中分离出来的,旨在用于读取或作为不需要梯度计算的操作的输入。
共享数据的意思是,如果原始张量output
的值因为某些操作而改变,那么output_detached
的值也会随之改变,因为它们底层指向的是同一块内存区域。但是,通常我们不直接修改这些张量的值,而是通过进行计算或应用函数来生成新的张量。对output_detached
进行的任何计算都不会影响到output
,因为output_detached
已经从计算图中被分离出来了,它的操作不会回溯到原始张量。
这里有一个简单的例子来说明这个概念:
1 | import torch |
在这个例子中,y_detached
被用来生成了一个新的张量z
,但这对y
本身没有任何影响。这就是.detach()
方法的一个关键特性:它允许我们在保持数据共享的同时,避免对原始张量的梯度计算产生影响。
torch 根据概率抽样
1 | probs = self.actor(state) |
获得网络全部参数self.actor.parameters()
注意这里坑点,假设你有一个简单的神经网络,其中包括两个全连接层,每层的权重和偏置都需要计算梯度。例如:
第一层权重形状为 [100, 50],偏置形状为 [50]。
第二层权重形状为 [50, 10],偏置形状为 [10]。
计算这些参数的梯度后,你将得到四个梯度张量,它们的形状分别是 [100, 50]、[50]、[50, 10]、[10]。如果你想将这些梯度合并为一个向量进行进一步处理(比如更新参数),你需要先将每个张量展平。grad.view(-1) 正是用于这个目的:将任意形状的张量重塑为一维向量。
张量中提取指定索引元素
1 | log_probs = torch.log(actor(states).gather(1, actions)) |
.gather(dim, index) 是一个PyTorch操作,用于根据index指定的索引在指定的维度dim上选取元素。
在这个上下文中,actions 应该是一个形状为 [batch_size, 1] 的张量,包含每个状态对应的选择动作的索引。
gather(1, actions) 从 actor(states) 的输出中沿着第二维(dim=1,动作维度)收集动作概率。这意味着从每行(每个状态的动作概率分布)中提取由 actions 指定的动作的概率。
显式向量化处理参数
在 PyTorch 中,torch.nn.utils.convert_parameters.vector_to_parameters
函数用于将一个一维向量转换成一个与网络参数形状相匹配的参数集。这通常用在优化算法中,尤其是在那些需要显式地处理参数作为向量的场景,如某些高级优化技术或者参数更新策略中。
- 详细语法解析
函数 torch.nn.utils.convert_parameters.vector_to_parameters(vector, parameters)
接受两个参数:
-
vector: 这是一个一维向量,其中包含了应该被加载到模型中的所有参数值。这个向量通常是通过展平模型的所有参数获得的(例如使用
torch.nn.utils.parameters_to_vector
函数)。 -
parameters: 这是一个参数迭代器,通常可以通过模型的
.parameters()
方法获得。它应该包括所有将要被更新的参数。
- 函数作用
这个函数的作用是将一维向量中的参数值赋值回具有特定形状的模型参数中。这通常在以下情况中非常有用:
- 参数恢复:在进行一些操作(如优化步骤)后,可能需要将一维向量形式的参数恢复到模型的参数形状中。
- 优化算法:在某些优化算法中,尤其是那些需要计算参数的Hessian向量积的算法中,参数可能会被临时转换成向量形式进行计算,之后需要转换回原始形状以进行模型更新。
示例代码:
假设你有一个简单的神经网络模型,你需要将其参数展平后的向量恢复到模型的参数中:
1 | import torch |
在这个例子中,vector_to_parameters
函数用于将修改后的参数向量new_params_vector
重新赋值给模型的参数。这是在复杂的自定义优化流程中常见的一个步骤。
一个元素的张量到标准 Python 类型的转换
在你的 take_action 函数中,使用 [action.item()] 来返回动作是为了处理几个实际的问题,并且确保代码的通用性和效率。这里是具体原因的详细解释:
张量到标准 Python 类型的转换:
action.item(): 这个方法是用来从一个只有一个元素的张量中提取其值并转换成一个标准的Python数值(如float或int)。action_dist.sample() 会返回一个张量,哪怕这个张量只包含一个数值(这在大多数单一动作决策场景中都是这样)。使用 .item() 可以有效地从张量中提取出那个单一的动作值。
在分部下计算log_prob概率
1 | action_dists = torch.distributions.Normal(mu, std) |
功能说明:
- action_dists: 这是一个概率分布对象,通常是由策略网络(例如前面讨论的 PolicyNetContinuous)生成的。在连续动作空间中,这个分布通常是正态(高斯)分布,由动作的均值(mu)和标准差(std)参数化。
- log_prob(actions): 这是一个方法,用于计算给定动作 actions 在 action_dists 定义的概率分布下的对数概率。对数概率是很多强化学习算法中的关键组成部分,尤其是那些基于概率的策略梯度方法。