【摘要】人生苦短,我用Python!现代深度强化学习,包括多智能体强化学习,研究者大多使用Python进行试验验证。主要的原因就是Python语言较为贴近自然语言,入门容易,具有各种方便好用的功能包。研究者可以快速开发出各种应用环境验证强化学习算法,实现研究论证与论文发表。本文包含以下内容:
- Python语言介绍、数据类型、语法、数据结构,类。
- Numpy的使用:基于Python的数学运算包,是Tenorflow和Pytorch的基础。
文章目录
1 Python介绍
Python语言的优点:
- 类似伪代码的书写方式,让你能够集中精力去解决问题,而不是花费大量的时间在开发和debug上
- 得益于Numpy/Scipy这样的科学计算库,使得其有非常高效和简易的科学计算能力。
- 活跃的社区提供的各种可视化的库,也使得 机器学习/数据挖掘 的全过程(数据采集,数据清洗,数据处理,建模,可视化)可以非常流畅地完成。
- 近年来极其热门的深度学习开源框架,基本都有python接口,Tensorflow和PyTorch更是python主导。
2 基本python语法
2.1 基本数据类型
2.1.1 数值型
整型和浮点型以及基本的运算大家应该都知道:
x = 4
print(x, type(x)) # 将变量的值和类型打印出来
4 <class 'int'>
print(x + 1) # 加;
print(x - 1) # 减;
print(x * 2) # 乘;
print(x ** 2) # 指数;
5
3
8
16
x += 1
print(x) # 自加
x *= 2
print(x) # 自乘
5
10
2.1.2 布尔型
用于指定真假的类型,布尔型,包含真(True)和假(False):
t, f = True, False
print(type(t))
print(t and f) # 逻辑"与";
print(t or f) # 逻辑"或";
print(not t) # 逻辑"非";
print(t != f) # 判断;
<class 'bool'>
False
True
False
True
看看逻辑与或非这些操作
2.1.3 字符串型
hello = 'hello' # 实在想不出的时候就用hello world
world = "world"
print(hello, len(hello)) # 字符串长度
hello 5
hw = hello + ' ' + world # 字符串拼接
print(hw)
hello world
hw12 = '%s %s %d' % (hello, world, 12.11) # 类似sprintf的格式化输出
hw13 = "{} {} {}".format(hello, world, 12.111111)
print(hw12)
print(hw13)
hello world 12
hello world 12.111111
其他你想要的字符串操作看这里.
2.2 容器
内置的容器用得非常非常多,包括: lists, dictionaries, sets, and tuples.
2.2.1 Lists/列表
创建、访问、修改、添加、去除
x = [3, 1, 2] # 建一个列表
print(x, x[2])
print(x[-1]) # 用-1表示最后一个元素,输出来
x[2] = 'I Love' # 有意思的是,Python的list居然可以有不同类型的元素
print(x)
x.append('Pthon')
print(x)
x_last = x.pop() # 也可以把最后一个元素“弹射”出来
print(x,x_last)
[3, 1, 2] 2
2
[3, 1, 'I Love']
[3, 1, 'I Love', 'Pthon']
[3, 1, 'I Love'] Pthon
切片
nums = [0, 1, 2, 3, 4] # 0-4
print(nums) # 输出 "[0, 1, 2, 3, 4]"
print(nums[2:4]) # 下标2到4(不包括)的元素,注意下标从0开始
print(nums[2:]) # 下标2到结尾的元素; prints "[2, 3, 4]"
print(nums[:2]) # 直到下标2的元素; prints "[0, 1]"
print(nums[:]) # Get a slice of the whole list; prints ["0, 1, 2, 3, 4]"
print(nums[:-1]) # 直到倒数第一个元素; prints ["0, 1, 2, 3]"
nums[2:4] = [8, 9] # 也可以直接这么赋值
print(nums) # Prints "[0, 1, 8, 8, 4]"
[0, 1, 2, 3, 4]
[2, 3]
[2, 3, 4]
[0, 1]
[0, 1, 2, 3, 4]
[0, 1, 2, 3]
[0, 1, 8, 9, 4]
循环
可以对list立面的元素做一个循环:
slogan = ['强化', '学习', '学起来!']
for word in slogan:
print(word)
强化
学习
学起来!
又要输出元素,又要输出下标怎么办,用 enumerate
函数:
slogan = ['强化', '学习', '学起来!']
for idx, word in enumerate(slogan):
print('#%d: %s' % (idx + 1, word))
#1: 强化
#2: 学习
#3: 学起来!
List comprehensions:
如果对list里面的元素都做一样的操作,然后生成一个list,很方便,甚至可以说是Python语言的标志之一。
# 求一个list里面的元素的平方,然后输出,很out的for循环写法
x = [0, 1, 2, 3, 4]
squares = []
for x_i in nums:
squares.append(x_i ** 2)
print(squares)
# 对每个x完成一个操作以后返回来,组成新的list
squares1 = [x ** 2 for x in nums]
print(squares1)
[0, 1, 4, 9, 16]
[0, 1, 4, 9, 16]
还可以加条件,去筛出你想要的元素,去做你想要的操作。
x = [0, 1, 2, 3, 4]
# 把所有的偶数取出来,平方后返回
odd_squares = [x ** 2 for x in nums if x % 2 == 1]
print(odd_squares)
[1, 9]
2.2.2 字典
存储键值对(key => value)的数据结构, 使用频度相当高。
d = {'猫': '吃鱼', '狗': '玩球'} # 建立字典
print(d['猫']) # 根据key取value
print('猫' in d) # 查一个元素是否在字典中
吃鱼
True
d['猫'] = '英文名是cat' # 设定键值对
print(d['猫']) # 这时候肯定是输出修改后的内容
英文名是cat
print(d['猴子']) # 不是d的键,运行报错
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-57-be49a3464a5a> in <module>()
----> 1 print(d['猴子']) # 不是d的键,运行报错
KeyError: '猴子'
想了解的字典相关的操作和内容可以看 这里.
你可以这样循环python字典取出你想要的内容:
d = {'person': 2, 'cat': 4, 'spider': 8}
for animal in d:
legs = d[animal]
print('A %s has %d legs' % (animal, legs))
A person has 2 legs
A cat has 4 legs
A spider has 8 legs
用iteritems函数可以同时取出键值对:
Dictionary comprehensions: 和list comprehension有点像啦,但是生成的是字典。
nums = [0, 1, 2, 3, 4]
even_num_to_square = {x: x ** 2 for x in nums if x % 2 == 0}
print(even_num_to_square)
{0: 0, 2: 4, 4: 16}
2.2.3 Sets/集合
你可以理解成没有相同元素的列表(当然,显然和list是不同的):
animals = {'cat', 'dog'}
print('cat' in animals)
print('fish' in animals)
True
False
animals.add('fish') # 添加元素
print('fish' in animals)
print(len(animals)) # 元素个数
True
3
循环的方式和list很像啦:
animals = {'cat', 'dog', 'fish'}
for idx, animal in enumerate(animals):
print('#%d: %s' % (idx + 1, animal))
# Prints "#1: fish", "#2: dog", "#3: cat"
#1: cat
#2: fish
#3: dog
Set comprehensions: 熟悉的感觉:
from math import sqrt
print( {int(sqrt(x)) for x in range(30)})
{0, 1, 2, 3, 4, 5}
2.2.4 元组
和list很像,但是可以作为字典的key或者set的元素出现,但是一整个list不可以作为字典的key或者set的元素的。
d = {(x, x + 1): x for x in range(10)} # 将tuple作为字典的key
t = (5, 6) # 创建一个key
print(type(t))
print(d[t])
print(d[(1, 2)])
<class 'tuple'>
5
1
2.3 函数
用 def
就可以定义一个函数,就像下面这样:
def sign(x):
if x > 0:
return 'positive'
elif x < 0:
return 'negative'
else:
return 'zero'
for x in [-1, 0, 1]:
print(sign(x))
negative
zero
positive
函数名字后面接的括号里,可以有多个参数,你自己可以试试。
def hello(name, loud=False):
if loud:
print('你好, %s!' % name.upper())
else:
print('你好, %s!' % name)
hello('Bob')
hello('Alice', loud=True)
你好, Bob!
你好, ALICE!
2.4 类
Python的类,怎么说呢,比较简单粗暴:
class Greeter:
# 构造函数
def __init__(self, name):
self.name = name # Create an instance variable
# 类的成员函数
def greet(self, loud=False):
if loud:
print('HELLO, %s!' % self.name.upper())
else:
print('Hello, %s' % self.name)
g = Greeter('Bob') # 构造一个类
g.greet() # 调用函数; prints "Hello, Fred"
g.greet(loud=True) # 调用函数; prints "HELLO, FRED!"
Hello, Bob
HELLO, BOB!
3 Numpy
我们要开始接触高效计算库Numpy了,你要是之前在实验室用MATLAB之类的语法,你会发现Numpy和它们长得不要太像,爱MATLAB的同学,参考文档可以看这里
python里面调用一个包,用import对吧, 所以我们import numpy
包:
import numpy as np
3.1 Arrays/数组
看你数组的维度啦,我自己的话比较简单粗暴,一般直接把1维数组就看做向量/vector,2维数组看做2维矩阵,3维数组看做3维矩阵…
可以调用np.array去从list初始化一个数组:
a = np.array([1, 2, 3]) # 1维数组
print(type(a), a.shape, a[0], a[1], a[2])
a[0] = 5 # 重新赋值
print(a)
<class 'numpy.ndarray'> (3,) 1 2 3
[5 2 3]
b = np.array([[1,2,3],[4,5,6]]) # 2维数组
print(b)
[[1 2 3]
[4 5 6]]
print(b.shape) #可以看形状的(非常常用!!!)
print(b[0, 0], b[0, 1], b[1, 0])
(2, 3)
1 2 4
有一些内置的创建数组的函数:
a = np.zeros((2,2)) # 创建2x2的全0数组
b = np.ones((1,2)) # 创建1x2的全0数组
c = np.full((2,2), 7) # 定值数组
d = np.eye(2) # 对角矩阵(对角元素为1)
e = np.random.random((2,2)) # 2x2的随机数组(矩阵)
print(a)
print(b)
print(c)
print(d)
print(e)
[[0. 0.]
[0. 0.]]
[[1. 1.]]
[[7 7]
[7 7]]
[[1. 0.]
[0. 1.]]
[[0.96067457 0.14478034]
[0.789771 0.95025755]]
3.2 Array indexing/数组取值
Numpy提供了蛮多种取值的方式的.
可以像list一样切片(多维数组可以从各个维度同时切片):
import numpy as np
# 创建一个如下格式的3x4数组
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
# 在两个维度上分别按照[:2]和[1:3]进行切片,取需要的部分
# [[2 3]
# [6 7]]
b = a[:2, 1:3]
print(b)
[[2 3]
[6 7]]
虽然,怎么说呢,不建议你这样去赋值,但是你确实可以修改切片出来的对象,然后完成对原数组的赋值.
print(a[0, 1])
b[0, 0] = 77 # b[0, 0]改了,很遗憾a[0, 1]也被修改了
print(a[0, 1])
2
77
# 创建3x4的2维数组/矩阵
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print(a)
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
你就放心大胆地去取你想要的数咯:
row_r1 = a[1, :] # 第2行,但是得到的是1维输出(列向量)
row_r2 = a[1:2, :] # 1x2的2维输出
row_r3 = a[[1], :] # 同上
print(row_r1, row_r1.shape)
print(row_r2, row_r2.shape)
print(row_r3, row_r3.shape)
[5 6 7 8] (4,)
[[5 6 7 8]] (1, 4)
[[5 6 7 8]] (1, 4)
# 试试在第2个维度上切片也一样的:
col_r1 = a[:, 1]
col_r2 = a[:, 1:2]
print(col_r1, col_r1.shape)
print(col_r2, col_r2.shape)
[ 2 6 10] (3,)
[[ 2]
[ 6]
[10]] (3, 1)
下面这个高级了,更自由地取值和组合,但是要看清楚一点:
a = np.array([[1,2], [3, 4], [5, 6]])
# 其实意思就是取(0,0),(1,1),(2,0)的元素组起来
print(a[[0, 1, 2], [0, 1, 0]])
# 下面这个比较直白啦
print(np.array([a[0, 0], a[1, 1], a[2, 0]]))
[1 4 5]
[1 4 5]
# 再来试试
print(a[[0, 0], [1, 1]])
# 还是一样
print(np.array([a[0, 1], a[0, 1]]))
[2 2]
[2 2]
# 再来熟悉一下
# 先创建一个2维数组
a = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
print(a)
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
# 用下标生成一个向量
b = np.array([0, 2, 0, 1])
# 你能看明白下面做的事情吗?
print(a[np.arange(4), b]) # Prints "[ 1 6 7 11]"
[ 1 6 7 11]
# 既然可以取出来,我们当然可以对这些元素操作咯
a[np.arange(4), b] += 10
print a
[[21 2 3]
[ 4 5 26]
[27 8 9]
[10 31 12]]
比较fashion的取法之一,用条件判定去取(但是很好用):
import numpy as np
a = np.array([[1,2], [3, 4], [5, 6]])
bool_idx = (a > 2) # 就是判定一下是否大于2
print(bool_idx) # 返回一个布尔型的3x2数组
[[False False]
[ True True]
[ True True]]
# 用刚才的布尔型数组作为下标就可以去除符合条件的元素啦
print(a[bool_idx])
# 其实一句话也可以完成是不是?
print(a[a > 2])
[3 4 5 6]
[3 4 5 6]
那个,真的,其实还有很多细节,其他的方式去取值,你可以看看官方文档。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c7jea0hR-1651452947270)(http://old.sebug.net/paper/books/scipydoc/_images/numpy_intro_02.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uoVhgJFk-1651452947272)(http://old.sebug.net/paper/books/scipydoc/_images/numpy_intro_03.png)]
3.3 Numpy数据类型
我们可以用dtype来看numpy数组中元素的类型:
x = np.array([1, 2]) # numpy构建数组的时候自己会确定类型
y = np.array([1.0, 2.0])
z = np.array([1, 2], dtype=np.int64)# 指定用int64构建
print(x.dtype, y.dtype, z.dtype)
int32 float64 int64
更多的内容可以读读文档.
3.4 数学运算
下面这些运算才是你在科学运算中经常经常会用到的,比如逐个元素的运算如下:
x = np.array([[1,2],[3,4]], dtype=np.float64)
y = np.array([[5,6],[7,8]], dtype=np.float64)
# 逐元素求和有下面2种方式
print(x + y)
print(np.add(x, y))
# 逐元素作差
print(x - y)
print(np.subtract(x, y))
# 逐元素相乘
print(x * y)
print(np.multiply(x, y))
# 逐元素相除
# [[ 0.2 0.33333333]
# [ 0.42857143 0.5 ]]
print(x / y)
print(np.divide(x, y))
# 逐元素求平方根!!!
# [[ 1. 1.41421356]
# [ 1.73205081 2. ]]
print(np.sqrt(x))
[[ 6. 8.]
[10. 12.]]
[[ 6. 8.]
[10. 12.]]
[[-4. -4.]
[-4. -4.]]
[[-4. -4.]
[-4. -4.]]
[[ 5. 12.]
[21. 32.]]
[[ 5. 12.]
[21. 32.]]
[[0.2 0.33333333]
[0.42857143 0.5 ]]
[[0.2 0.33333333]
[0.42857143 0.5 ]]
[[1. 1.41421356]
[1.73205081 2. ]]
那如果我要做矩阵的乘法运算怎么办!!!别着急,照着下面写就可以了:
x = np.array([[1,2],[3,4]])
y = np.array([[5,6],[7,8]])
v = np.array([9,10])
w = np.array([11, 12])
# 求向量内积
print(v.dot(w))
print(np.dot(v, w))
# 矩阵的乘法
print(x.dot(v))
print(np.dot(x, v))
# 矩阵的乘法
# [[19 22]
# [43 50]]
print(x.dot(y))
print(np.dot(x, y))
219
219
[29 67]
[29 67]
[[19 22]
[43 50]]
[[19 22]
[43 50]]
你猜你做科学运算会最常用到的矩阵内元素的运算是什么?对啦,是求和,用 sum
可以完成:
x = np.array([[1,2],[3,4]])
print(np.sum(x)) # 数组/矩阵中所有元素求和; prints "10"
print(np.sum(x, axis=0)) # 按行去求和; prints "[4 6]"
print(np.sum(x, axis=1)) # 按列去求和; prints "[3 7]"
10
[4 6]
[3 7]
我想说最基本的运算就是上面这个样子,更多的运算可能得查查文档.
其实除掉基本运算,我们经常还需要做一些操作,比如矩阵的变形,转置和重排等等:
# 转置和数学公式一直,简单粗暴
print(x)
print(x.T)
[[1 2]
[3 4]]
[[1 3]
[2 4]]
# 需要说明一下,1维的vector转置还是自己
v = np.array([1,2,3])
print(v)
print(v.T)
# 2维的就不一样了
w = np.array([[1,2,3]])
print(w )
print(w.T)
[1 2 3]
[1 2 3]
[[1 2 3]]
[[1]
[2]
[3]]
3.5 Broadcasting
这个没想好哪个中文词最贴切,我们暂且叫它“传播吧”:
作用是什么呢,我们设想一个场景,如果要用小的矩阵去和大的矩阵做一些操作,但是希望小矩阵能循环和大矩阵的那些块做一样的操作,那急需要Broadcasting啦
# 我们要做一件事情,给x的每一行都逐元素加上一个向量,然后生成y
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = np.empty_like(x) # 生成一个和x维度一致的空数组/矩阵
# 比较粗暴的方式是,用for循环逐个相加
for i in range(4):
y[i, :] = x[i, :] + v
print(y)
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
这种方法当然可以啦,问题是不高效嘛,如果你的x矩阵行数非常多,那就很慢的咯:
咱们调整一下,先生成好要加的内容
vv = np.tile(v, (4, 1)) # 重复4遍v,叠起来
print(vv) # Prints "[[1 0 1]
# [1 0 1]
# [1 0 1]
# [1 0 1]]"
y = x + vv # 这样求和大家都能看明白对吧
print(y)
[[1 0 1]
[1 0 1]
[1 0 1]
[1 0 1]]
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
import numpy as np
# 因为broadcasting的存在,你上面的操作可以简单地汇总成一个求和操作
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = x + v # 对应列相加
print(y)
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
当操作两个array时,numpy会逐个比较它们的shape,在下述情况下,两arrays会兼容和输出broadcasting结果:
- 相等
- 其中一个为1,(进而可进行拷贝拓展已至,shape匹配)
比如求和的时候有:
Image (3d array): 256 x 256 x 3
Scale (1d array): 3
Result (3d array): 256 x 256 x 3
A (4d array): 8 x 1 x 6 x 1
B (3d array): 7 x 1 x 5
Result (4d array): 8 x 7 x 6 x 5
A (2d array): 5 x 4
B (1d array): 1
Result (2d array): 5 x 4
A (2d array): 15 x 3 x 5
B (1d array): 15 x 1 x 5
Result (2d array): 15 x 3 x 5
下面是一些 broadcasting 的例子:
# 我们来理解一下broadcasting的这种用法
v = np.array([1,2,3]) # v 形状是 (3,)
w = np.array([4,5]) # w 形状是 (2,)
# 先把v变形成3x1的数组/矩阵,然后就可以broadcasting加在w上了:
print(np.reshape(v, (3, 1)) * w)
[[ 4 5]
[ 8 10]
[12 15]]
# 那如果要把一个矩阵的每一行都加上一个向量呢
x = np.array([[1,2,3], [4,5,6]])
v = np.array([1,2,3])
# 恩,其实是一样的啦
print(x + v)
[[2 4 6]
[5 7 9]]
x = np.array([[1,2,3], [4,5,6]]) # 2x3的
w = np.array([4,5]) # w 形状是 (2,)
# 自己算算看?
print((x.T + w).T)
# 上面那个操作太复杂了,其实我们可以直接这么做嘛
print(x + np.reshape(w, (2, 1)))
# broadcasting当然可以逐元素运算了
print(x * 2)
[[ 5 6 7]
[ 9 10 11]]
总结一下broadcasting,可以看看下面的图:
更多的numpy细节和用法可以查看一下官网numpy指南