Cartpole

#!/usr/bin/env python3
import gym
from collections import namedtuple
import numpy as np
from tensorboardX import SummaryWriter

import torch
import torch.nn as nn
import torch.optim as optim
# tensorboard --logdir=

HIDDEN_SIZE = 128
BATCH_SIZE = 16
PERCENTILE = 70


class Net(nn.Module):
    def __init__(self, obs_size, hidden_size, n_actions):
        # 總結:所有放在構造函數(shù)__init__里面的層的都是這個模型的“固有屬性”.
        super(Net, self).__init__()  # 第一句話乖酬,super調用父類的構造函數(shù)  =super().__init__()  # 第一句話尝苇,super調用父類的構造函數(shù)
        '''
        通過Sequential來包裝層
即將幾個層包裝在一起作為一個大的層(塊)猎唁,前面的一篇文章詳細介紹了Sequential類的使用瞳秽,包括常見的三種方式,以及每一種方式的優(yōu)缺點力九,
參見:https://blog.csdn.net/qq_27825451/article/details/90551513
        '''
        self.net = nn.Sequential(
            nn.Linear(obs_size, hidden_size),  # PyTorch的nn.Linear()是用于設置網絡中的全連接層的嗜傅,需要注意在二維圖像處理的任務中畦戒,全連接層的輸入與輸出一般都設置為二維張量,形狀通常為[batch_size, size]闪盔,不同于卷積層要求輸入輸出是四維張量弯院。
            nn.ReLU(),
            nn.Linear(hidden_size, n_actions)
        )
        # 此處定義為函數(shù)中的新定義,不是繼承的
        # view()的作用相當于numpy中的reshape泪掀,重新定義矩陣的形狀
        '''
        class Fruit():
            def __init__(self, color, shape):
                self.color = color
                self.shape = shape

        class Apple(Fruit):
            def __init__(self, color, shape, taste):
                Fruit.__init__(self, color, shape) # 等價于super().__init__(color, shape)
                self.taste = taste
    
            def feature(self):
                print("Apple's color is {}, shape is {} and taste {}".format(
                    self.color, self.shape, self.taste))
原文鏈接:https://blog.csdn.net/w1301100424/article/details/93858890
        '''

    def forward(self, x):
        return self.net(x)
# 3)forward方法是必須要重寫的抽兆,它是實現(xiàn)模型的功能,實現(xiàn)各個層之間的連接關系的核心族淮。

'''
因為元組的局限性:不能為元組內部的數(shù)據(jù)進行命名辫红,所以往往我們并不知道一個元組所要表達的意義,
所以在這里引入了 collections.namedtuple 這個工廠函數(shù)祝辣,來構造一個帶字段名的元組贴妻。
'''

Episode = namedtuple('Episode', field_names=['reward', 'steps'])
EpisodeStep = namedtuple('EpisodeStep', field_names=['observation', 'action'])
'''
# 兩種方法來給 namedtuple 定義方法名
#User = collections.namedtuple('User', ['name', 'age', 'id'])
User = collections.namedtuple('User', 'name age id')
user = User('tester', '22', '464643123')
'''

def iterate_batches(env, net, batch_size):  # 接受環(huán)境(來自Gym庫的Env實例)、神經網絡蝙斜、以及每次迭代時應該生成的episode數(shù)量
    batch = []  # batch變量用于累積batch(一個Episode實例列表)
    episode_reward = 0.0  # 獎勵計數(shù)器
    episode_steps = []  #
    obs = env.reset()  # 重新設定環(huán)境名惩,獲得第一個觀察并創(chuàng)建softmax層,用于將網絡輸出裝換成動作的概率分布
    sm = nn.Softmax(dim=1)
    '''
    def softmax(x):
        exp_x = np.exp(x)
        sum_exp_x = np.sum(exp_x) 
        y = exp_x/sum_exp_x
        return y
    改進:解決溢出問題
    def softmax(a):
        c = np.max(a)
        exp_a = np.exp(a - c) # 溢出對策
        sum_exp_a = np.sum(exp_a)
        y = exp_a / sum_exp_a
        return y
    softmax函數(shù)的輸出是0.0到1.0之間的實數(shù)孕荠。并且娩鹉,softmax函數(shù)的輸出值的總和是1攻谁。
    輸出總和為1是softmax函數(shù)的一個重要性質。正因為有了這個性質弯予,我們才可以把softmax函數(shù)的輸出解釋為“概率”戚宦。
    
    所以,當nn.Softmax的輸入是一個二維張量時锈嫩,其參數(shù)dim = 0受楼,是讓列之和為1;dim = 1呼寸,是讓行之和為1艳汽。
    '''
    while True:  # 進行環(huán)境循環(huán)
        obs_v = torch.FloatTensor([obs])  # 將觀察值(在CartPole中是一個四個數(shù)字的向量,即cart_pos,cart_v,pole_angle,pole_v))轉換成1*4的張量对雪,這里用單一元素列表傳遞觀察實現(xiàn)
        act_probs_v = sm(net(obs_v))  # 這里沒有在網絡中使用非線性特性河狐,他將輸出原始動作分值,此分值需要softmax函數(shù)提供 ,net = Net(obs_size, HIDDEN_SIZE, n_actions)瑟捣,此處obs_v相當于網絡輸入的x
        act_probs = act_probs_v.data.numpy()[0]   # 這里的網絡和softmax層都返回能夠跟蹤梯度變化的張量馋艺,因此需要通過訪問tensor.data字段,然后將張量轉換為Numpy數(shù)組將其解包蝶柿。
        # 這個數(shù)組具有和輸入相同的二維結構丈钙,batch維度在0軸上,因此需要得到第一個batch元素交汤,獲得動作概率的一個一維向量

        action = np.random.choice(len(act_probs), p=act_probs)  # 根據(jù)已有的動作的概率分布雏赦,獲得當前步驟采取的動作,通過使用Numpy.choice()函數(shù)對該分布進行采樣實現(xiàn),得到0~len(act_probs)-1整數(shù)列表
        next_obs, reward, is_done, _ = env.step(action)   # 之后芙扎,把這個動作傳遞到環(huán)境中星岗,獲得下一個觀察、獎勵以及episode是否結束的提示戒洼,step()是執(zhí)行動作的方法
        episode_reward += reward  # 更新
        episode_steps.append(EpisodeStep(observation=obs, action=action))  # episode列表擴展了一個(用于選擇動作的觀察俏橘,動作)對
        if is_done:
            batch.append(Episode(reward=episode_reward, steps=episode_steps))  # 將最終的episode附加到batch中,保存總獎勵和采取的步驟,Episode是具名元組
            episode_reward = 0.0  # 重置總獎勵累加器并清理步驟列表
            episode_steps = []
            next_obs = env.reset()  # 充值環(huán)境重新開始
            if len(batch) == batch_size:
                yield batch  # 如果batch已經達到所需的episode數(shù)量圈浇,使用yield函數(shù)將其返回給調用者進行處理寥掐,返回具有不同的稍好一些(所期望)的行為
                batch = []  # 清理batch
        obs = next_obs   # 非常重要的一步是將從環(huán)境中獲得的觀察分配給當前的觀察變量
# 這個函數(shù)邏輯中需要理解的一個非常重要的事實是,這里的網絡訓練和episode的生成是同時進行的磷蜀。
'''
到這里你可能就明白yield和return的關系和區(qū)別了召耘,帶yield的函數(shù)是一個生成器,而不是一個函數(shù)了褐隆,這個生成器有一個函數(shù)就是next函數(shù)污它,next就相當于“下一步”生成哪個數(shù),
這一次的next開始的地方是接著上一次的next停止的地方執(zhí)行的,所以調用next的時候衫贬,生成器并不會從foo函數(shù)的開始執(zhí)行德澈,只是接著上一步停止的地方開始,然后遇到y(tǒng)ield后固惯,return出要生成的數(shù)梆造,此步就結束。
原文鏈接:https://blog.csdn.net/mieleizhi0522/article/details/82142856
'''
def filter_batch(batch, percentile):
    rewards = list(map(lambda s: s.reward, batch))
    '''map() 會根據(jù)提供的函數(shù)對指定序列做映射缝呕。map(function, iterable, ...) 澳窑,第一個參數(shù) function 以參數(shù)序列中的每一個元素調用 function 函數(shù)斧散,返回包含每次 function 函數(shù)返回值的新列表供常。
    lambda (匿名函數(shù)):示例:add = lambda x,y:x+y  print(add(3,4))-》7
    '''
    reward_bound = np.percentile(rewards, percentile)
    # np.percentile(a, q, axis=None, out=None, overwrite_input=False, interpolation='linear', keepdims=False)
    # 作用:找到一組數(shù)的分位數(shù)值,如四分位數(shù)等(具體什么位置根據(jù)自己定義)鸡捐,注意實際百分位數(shù)計算方式
    reward_mean = float(np.mean(rewards))

    # 這個函數(shù)是交叉熵方法的核心:他從給定batch中的episode和百分位數(shù)中計算出一個邊界獎勵栈暇,用于篩選“精華”episode進行訓練。為獲得邊界獎勵箍镜,
    # 使用Numpy的百分位數(shù)函數(shù)源祈,他從一組值列表和期望的百分位數(shù)中計算該百分位數(shù)對應的值。然后計算平均獎勵色迂,用于監(jiān)控香缺。

    train_obs = []
    train_act = []
    for example in batch:
        if example.reward < reward_bound:
            continue
        train_obs.extend(map(lambda step: step.observation, example.steps))  # 將example中的steps觀察值列表擴展到train_obs中
        # extend() 函數(shù)用于在列表末尾一次性追加另一個序列中的多個值(用新列表擴展原來的列表)。
        train_act.extend(map(lambda step: step.action, example.steps))
    # 然后篩選episode歇僧。對于batch中每個episode图张,這里將檢查該episode的總獎勵是否高于邊界,若是則填寫要觀察和行動的列表用于訓練诈悍。

    train_obs_v = torch.FloatTensor(train_obs)
    train_act_v = torch.LongTensor(train_act)
    return train_obs_v, train_act_v, reward_bound, reward_mean
    # 該函數(shù)最后一步祸轮,需要把“精華”episode中的觀察和動作轉換為張量,并返回一個四元組:觀察侥钳、動作适袜、獎勵邊界和平均獎勵。
    # 最后兩個值僅用于將他們寫入TensorBoard以檢查智能體性能舷夺。

# 最后一部分代碼將所有函數(shù)結合一起苦酱,訓練循環(huán)組成如下:
if __name__ == "__main__":
    env = gym.make("CartPole-v0")
    # env = gym.wrappers.Monitor(env, directory="mon", force=True)
    obs_size = env.observation_space.shape[0]
    # env.observation_space是Box屬性,box(可能是無界的)在n維空間中给猾。一個box代表n維封閉區(qū)間的笛卡爾積疫萤。
    # 假設集合A={a,b},集合B={0,1,2}耙册,則兩個集合的笛卡爾積為{(a,0),(a,1),(a,2),(b,0),(b,1),(b,2)}给僵。
    n_actions = env.action_space.n

    net = Net(obs_size, HIDDEN_SIZE, n_actions)  # HIDDEN_SIZE = 128,返回一個net,可輸入?yún)?shù)為x
    objective = nn.CrossEntropyLoss()
    optimizer = optim.Adam(params=net.parameters(), lr=0.01)
    # 深度學習的優(yōu)化算法Adam
    # torch.optim is a package implementing various optimization algorithms. Most commonly used methods are already supported,
    # and the interface is general enough, so that more sophisticated ones can be also easily integrated in the future.
    writer = SummaryWriter(comment="-cartpole")
    # 首先帝际,創(chuàng)建所有必須的對象:環(huán)境蔓同、神經網絡、目標函數(shù)蹲诀、優(yōu)化器斑粱、TensorBoard的摘要編寫器。注釋行創(chuàng)建一個監(jiān)視器以寫入智能體程序性能的視頻脯爪。

    for iter_no, batch in enumerate(iterate_batches(env, net, BATCH_SIZE)):  # BATCH_SIZE = 16则北,enumerate返回索引和值
        '''
        for循環(huán)遍歷的原理就是迭代,in后面必須是可迭代對象. iterate_batches()函數(shù)里面有yield()函數(shù)痕慢,自動變成可迭代對象
        '''
        obs_v, acts_v, reward_b, reward_m = filter_batch(batch, PERCENTILE)  # PERCENTILE = 70
        optimizer.zero_grad()
        action_scores_v = net(obs_v)
        loss_v = objective(action_scores_v, acts_v)
        loss_v.backward()
        optimizer.step()
    # 在訓練循環(huán)中尚揣,迭代batch(一個episode對象的列表),然后使用filter_batch函數(shù)篩選“精華”episode掖举。其結果就是觀察和采取行動的變量快骗,用于篩選的獎勵邊界和平均獎勵。
    # 之后塔次,將網絡的梯度歸零方篮,并將觀察傳遞給網絡,獲得其動作分值励负。這些分值被傳遞給目標函數(shù)藕溅,目標函數(shù)計算網絡輸出和智能體所采取的動作之間的交叉熵。這樣做可以增強網絡继榆,
    # 以執(zhí)行哪些可以帶來良好獎勵的“精華:動作巾表。然后,計算損失梯度裕照,并要求優(yōu)化器調整網絡攒发。


        print("%d: loss=%.3f, reward_mean=%.1f, reward_bound=%.1f" % (
            iter_no, loss_v.item(), reward_m, reward_b))
        writer.add_scalar("loss", loss_v.item(), iter_no)
        writer.add_scalar("reward_bound", reward_b, iter_no)
        writer.add_scalar("reward_mean", reward_m, iter_no)
        # 循環(huán)其余部分是監(jiān)控進度,在控制臺上晋南,顯示迭代次數(shù)惠猿、損失、batch的平均獎勵和獎勵邊界负间。這里還將相同的值寫入TensorBoard偶妖,以獲得一個漂亮的智能體學習性能圖。
        if reward_m > 199:
            print("Solved!")
            break
    writer.close()
    # 訓練最后一次檢查是比較該batch中episode的平均獎勵政溃。若該平均獎勵數(shù)值超過199時趾访,就停止訓練。為什么是199董虱?在Gym中扼鞋,當最后100個episode的平均獎勵大于195時申鱼,
    # 此Cart Pole環(huán)境可以考慮為被解決完了,交叉熵方法收斂的非吃仆罚快捐友,以至于通常只需要100個episode。經過適當訓練的智能體可以無限長時間的保持棍子平衡(獲得任何數(shù)量的
    # 分數(shù))溃槐,但Cart Pole中的episode長度限制為200步(Cart Pole環(huán)境中匣砖,Time limit包裝器,它會停止200步后的episode)考慮到此問題昏滴,這將在batch中的平均獎勵大于199
    # 之后停止訓練猴鲫,者可以很好的表明智能體已經知道如何像一個專業(yè)者一樣平衡棍子。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末谣殊,一起剝皮案震驚了整個濱河市拂共,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蟹倾,老刑警劉巖匣缘,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猖闪,死亡現(xiàn)場離奇詭異鲜棠,居然都是意外死亡,警方通過查閱死者的電腦和手機培慌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門豁陆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吵护,你說我怎么就攤上這事盒音。” “怎么了馅而?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵祥诽,是天一觀的道長。 經常有香客問我瓮恭,道長雄坪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任屯蹦,我火速辦了婚禮维哈,結果婚禮上,老公的妹妹穿的比我還像新娘登澜。我一直安慰自己阔挠,他們只是感情好,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布脑蠕。 她就那樣靜靜地躺著购撼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上迂求,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天切揭,我揣著相機與錄音,去河邊找鬼锁摔。 笑死廓旬,一個胖子當著我的面吹牛,可吹牛的內容都是我干的谐腰。 我是一名探鬼主播孕豹,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼十气!你這毒婦竟也來了励背?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤砸西,失蹤者是張志新(化名)和其女友劉穎叶眉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芹枷,經...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡衅疙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸳慈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饱溢。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖走芋,靈堂內的尸體忽然破棺而出绩郎,到底是詐尸還是另有隱情,我是刑警寧澤翁逞,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布肋杖,位于F島的核電站,受9級特大地震影響挖函,放射性物質發(fā)生泄漏状植。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一挪圾、第九天 我趴在偏房一處隱蔽的房頂上張望浅萧。 院中可真熱鬧,春花似錦哲思、人聲如沸洼畅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帝簇。三九已至徘郭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丧肴,已是汗流浹背残揉。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芋浮,地道東北人抱环。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像纸巷,于是被迫代替她去往敵國和親镇草。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內容