首先需要python2.7的版本
导入gym库https://gym.openai.com/docs/
导入numpy库https://numpy.org/
""" Trains an agent with (stochastic) Policy Gradients on Pong. Uses OpenAI Gym. """
import numpy as np
import _pickle as pickle
import gym
# hyperparameters超参数
H = 200 # number of hidden layer neurons层数
batch_size = 10 # every how many episodes to do a param update?
learning_rate = 1e-4#学习率
gamma = 0.99 # discount factor for reward退火率
decay_rate = 0.99 # decay factor for RMSProp leaky sum of grad^2
resume = False # resume from previous checkpoint?
render = False
# model initialization
D = 80 * 80 # input dimensionality: 80x80 grid
if resume:
model = pickle.load(open('save.p', 'rb'))#rb是用二进制方法读取二进制文件(非人工书写的数据如。jepg这种)
# open('文件路径', '模式',编码方式),
# #pickle.load是从文件中读取数据。
else:
model = {}
model['W1'] = np.random.randn(H,D) / np.sqrt(D) # "Xavier" 初始化(除以矩阵平方根即为转化为标准分布矩阵)
#np.random.rand()函数作用;过本函数可以返回一个或一组服从“0~1”均匀分布的随机样本值。随机样本取值范围是[0,1),不包括1。
#例如np.random.rand(3,3)就可以返回一组3行3列的随机矩阵,矩阵中每个元素的值为0-1(不包含1)
#np.random.randn(d0,d1,d2……dn) ,当函数括号内没有参数时,则返回一个浮点数;当函数括号内有一个参数时,则返回秩为1的数组,不能表示向量和矩阵;
#当函数括号内有两个及以上参数时,则返回对应维度的数组,能表示向量或矩阵;通过本函数可以返回一个或一组服从标准正态分布的随机样本值。
model['W2'] = np.random.randn(H) / np.sqrt(H)#numpy.sqrt()为矩阵平方根,例如numpy.sqrt(【【1,4】,【9,16】】)输出为
#【1,2】,【3,4】
grad_buffer = { k : np.zeros_like(v) for k,v in model.items() } # update buffers that add up gradients over a batch
rmsprop_cache = { k : np.zeros_like(v) for k,v in model.items() } # rmsprop memory
#np.zeros_like(v)的作用是构造一个矩阵并使其初始化为0.其shape与v矩阵相同
#for循环可以遍历任何序列的项目,如一个列表或者一个字符串EX;for letter in 'Python':print '当前字母 :', letter结果是p,y,t,h,o,n
#return的作用就是把你需要的东西拿过来def worker(a, b, c): x = a + b y = x * c return y 你让工人把y拿来他就会把x*c的结果拿给你。
def sigmoid(x): #定义 sigmoid这个函数
return 1.0 / (1.0 + np.exp(-x)) # sigmoid "squashing" function to interval [0,1]
def prepro(I):
""" prepro 210x160x3 uint8 frame into 6400 (80x80) 1D float vector """
I = I[35:195] # crop裁剪这是numpy的切片操作,一般结构如num[a:b,c:d],分析时以逗号为分隔符,逗号之前为要取的num行的下标范围(a到b-1),
#逗号之后为要取的num列的下标范围(c到d-1);此处为切片I矩阵的35行的195列
I = I[::2,::2,0] # downsample by factor of 2按照步长2来采样
#b = a[i:j:s]这种格式呢,i,j与上面的一样,但s表示步进,缺省为1上面的意思是行和列都按照步进2来
#a[i:j:1]相当于a[i:j],当s<0时:i缺省时,默认为-1; j缺省时,默认为-len(a)-1
#所以a[::-1]相当于 a[-1:-len(a)-1:-1],也就是从最后一个元素到第一个元素复制一遍。
#len() 方法返回对象(字符、列表、元组等)长度或项目个数。
I[I == 144] = 0 # erase background (background type 1)擦除背景
#python中的‘==’ 是用来判断两个对象的值是否相等的,比如判断两个数字的值是否相等。判断的是内存块里存的值
#I中=144的元素全部设置为0
I[I == 109] = 0 # erase background (background type 2)擦除背景
#I中=109的元素全部设置为0
I[I != 0] = 1 # everything else (paddles, ball) just set to 1其他所有东西设置为1
#!=代表不等于的意思,上式的意思是不等于0的元素全部设置为1
return I.astype(np.float).ravel()
#astype(np.float)用来转换数据类型使其可以进行计算
#假设所取数据存在str型,那么取出的数据,全部转化为str型,也就是array阵列的元素全是str,不管数据库定义的是不是int型。
#那么问题来了,取出的数据代入公式进行计算的时候,就会类型不符,这是就用到astype(np.float)此时就可以转换为可计算的float类型
#ravel函数实现扁平化操作:一个矩阵ravel后会变成一行数组。ex:c = a.ravel() c为a的一种展示方式,虽然他们是不同的对象,但在修改c的时候,
#a中相应的数也改变了,所以扁平化时尽量用flatten函数。
def discount_rewards(r):#_单下划线开头:弱“内部使用”标识,如:”from M import *”,将不导入所有以下划线开头的对象,包括包、模块、成员
""" take 1D float array of rewards and compute discounted reward """#取一维浮点奖励数组并计算折扣奖励
discounted_r = np.zeros_like(r)
running_add = 0
for t in reversed(xrange(0, r.size)):#size返回矩阵元素的个数,axis = 0,返回该二维矩阵的行数,axis = 1,返回该二维矩阵的列数。
#xrange(stop)、xrange(start, stop[, step])。start: 计数从 start 开始。默认是从 0 开始。例如 xrange(5) 等价于 xrange(0, 5).
#stop: 计数到 stop 结束,但不包括 stop。例如:xrange(0, 5) 是 [0, 1, 2, 3, 4] 没有 5
#step:步长,默认为1。例如:xrange(0, 5) 等价于 xrange(0, 5, 1)
#EX;list(xrange(0,6,2)),[0, 2, 4]
#reversed()反转排序,可对列表、元组、区间等进行排序.a = [1,2,3,4,5,6,7,8,9,10],a_list = [x for x in reversed(a)]
#print(a_list),[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
if r[t] != 0: running_add = 0 # reset the sum, since this was a game boundary (pong specific!)
#重置总和,因为这是一个游戏边界(特定于乒乓球!):如果r中元素不等于0那么设置的参数等于0
#!=是不等于的意思
#if 判断条件:执行语句、else:执行语句
running_add = running_add * gamma + r[t]
discounted_r[t] = running_add
return discounted_r
def policy_forward(x):
h = np.dot(model['W1'], x)
#np.dot()为向量内积计算,计算过程就是将向量中对应元素相乘,再相加所得。即普通的向量乘法运算。
h[h<0] = 0 # ReLU nonlinearity ReLU 非线性
logp = np.dot(model['W2'], h)
p = sigmoid(logp)
return p, h # return probability of taking action 2, and hidden state 返回采取动作 2 的概率,以及隐藏状态
def policy_backward(eph, epdlogp):
""" backward pass. (eph is array of intermediate hidden states) """#向后传递。(eph 是中间隐藏状态的数组)
dW2 = np.dot(eph.T, epdlogp).ravel()#点乘后扁平化操作
dh = np.outer(epdlogp, model['W2'])#np.outer()对于多维向量,全部展开变为一维向量
dh[eph <= 0] = 0 # backpro prelu
dW1 = np.dot(dh.T, epx)
return {'W1':dW1, 'W2':dW2}
env = gym.make("Pong-v0")
observation = env.reset()
prev_x = None # used in computing the difference frame 用于计算差异
xs,hs,dlogps,drs = [],[],[],[]
running_reward = None
reward_sum = 0
episode_number = 0
while True:
if render: env.render()
# preprocess the observation, set input to network to be difference image 预处理,设置输入为不同的图像
cur_x = prepro(observation)
x = cur_x - prev_x if prev_x is not None else np.zeros(D)
prev_x = cur_x
# forward the policy network and sample an action from the returned probability 更新网络策略并且从返回的概率中采样一个动作
aprob, h = policy_forward(x)
action = 2 if np.random.uniform() < aprob else 3 # roll the dice!掷骰子
#np.random()创建一个随机矩阵,里面的元素取值范围是[0, 1)
#np.random.uniform()生成一个矩阵里面的元素在(x,y)范围内随机取值
# record various intermediates (needed later for backprop)记录各种中间层(稍后需要用于反向传播)
xs.append(x) # observation观察
#append()向列表尾部加一个新元素
hs.append(h) # hidden state隐藏层
y = 1 if action == 2 else 0 # a "fake label"“假标签
dlogps.append(y - aprob) # grad that encourages the action that was taken to be taken (see http://cs231n.github.io/neural-networks-2/#losses if confused)
#鼓励采取行动
# step the environment and get new measurements更新环境并获得新的测量值
observation, reward, done, info = env.step(action)#将环境动作赋值给左边的变量
reward_sum += reward#c+=a等同于c=c+a
drs.append(reward) # record reward (has to be done after we call step() to get reward for previous action)
#记录奖励(必须在我们调用 step() 以获得之前操作的奖励之后完成)
if done: # an episode finished 完成了一步
episode_number += 1
# stack together all inputs, hidden states, action gradients, and rewards for this episode
#将这一集的所有输入、隐藏状态、动作梯度和奖励叠加在一起
epx = np.vstack(xs)#np.vstack()把数组垂直(按照行顺序排列)堆叠起来EX:a=[[1],[2],[3]],b=[[1],[2],[3]]
#输出会变成
#[[1]
#[2]
#[3]
#[1]
#[2]
#[3]
eph = np.vstack(hs)
epdlogp = np.vstack(dlogps)
epr = np.vstack(drs)
xs,hs,dlogps,drs = [],[],[],[] # reset array memory重置矩阵内存
# compute the discounted reward backwards through time 向后计算折扣奖励
discounted_epr = discount_rewards(epr)
# standardize the rewards to be unit normal (helps control the gradient estimator variance)
#将奖励标准化为单位正态(有助于控制梯度估计方差)
discounted_epr -= np.mean(discounted_epr)#np.mean()对指定的行或者列或者所有元素求均值
discounted_epr /= np.std(discounted_epr)#np.std()计算标准差
epdlogp *= discounted_epr # modulate the gradient with advantage (PG magic happens right here.)
#利用优势调整梯度(PG 魔法就在这里)
grad = policy_backward(eph, epdlogp)
for k in model: grad_buffer[k] += grad[k] # accumulate grad over batch 按批次累加梯度
# perform rmsprop parameter update every batch_size episodes 每一次batch_size时就更新rmsprop参数
if episode_number % batch_size == 0:
#%是返回除法的余数EX:b % a 输出结果 1(a=10,b=21)
for k,v in model.items():
g = grad_buffer[k] # gradient梯度
rmsprop_cache[k] = decay_rate * rmsprop_cache[k] + (1 - decay_rate) * g**2
model[k] += learning_rate * g / (np.sqrt(rmsprop_cache[k]) + 1e-5)
grad_buffer[k] = np.zeros_like(v) # reset batch gradient buffer重新按批次设置梯度
# boring book-keeping 没什么用的一些记账
running_reward = reward_sum if running_reward is None else running_reward * 0.99 + reward_sum * 0.01
print ('resetting env. episode reward total was %f. running mean: %f') % (reward_sum, running_reward)
if episode_number % 100 == 0: pickle.dump(model, open('save.p', 'wb'))
#pickle.dump()将序列存储到文件中
#EX;pickle.dump(obj, file, protocol)、obj——序列化对象,将对象obj保存到文件file中去;
#file——file表示保存到的类文件对象,file必须有write()接口,file可以是一个以’w’打开的文件或者是一个StringIO对象,也可以是任何可以实现write()接口的对象;
#protocol——序列化模式,默认是 0(ASCII协议,表示以文本的形式进行序列化),protocol的值还可以是1和2(1和2表示以二进制的形式进行序列化。其中,1是老式的二进制协议;2是新二进制协议)。
reward_sum = 0
observation = env.reset() # reset env重置环境
prev_x = None
if reward != 0: # Pong has either +1 or -1 reward exactly when game ends.在游戏结束时有 +1 或 -1 的奖励。
print ('ep %d: game finished, reward: %f' % (episode_number, reward)) + ('' if reward == -1 else ' !!!!!!!!')