主頁 >  其他 > 深度Q網路:DQN專案實戰CartPole-v0

深度Q網路:DQN專案實戰CartPole-v0

2023-06-26 08:39:50 其他

摘要:相比于Q learning,DQN本質上是為了適應更為復雜的環境,并且經過不斷的改良迭代,到了Nature DQN(即Volodymyr Mnih發表的Nature論文)這里才算是基本完善,

本文分享自華為云社區《強化學習從基礎到進階-案例與實踐[4.1]:深度Q網路-DQN專案實戰CartPole-v0》,作者: 汀丶 ,

1、定義演算法

相比于Q learning,DQN本質上是為了適應更為復雜的環境,并且經過不斷的改良迭代,到了Nature DQN(即Volodymyr Mnih發表的Nature論文)這里才算是基本完善,DQN主要改動的點有三個:

  • 使用深度神經網路替代原來的Q表:這個很容易理解原因
  • 使用了經驗回放(Replay Buffer):這個好處有很多,一個是使用一堆歷史資料去訓練,比之前用一次就扔掉好多了,大大提高樣本效率,另外一個是面試常提到的,減少樣本之間的相關性,原則上獲取經驗跟學習階段是分開的,原來時序的訓練資料有可能是不穩定的,打亂之后再學習有助于提高訓練的穩定性,跟深度學習中劃分訓練測驗集時打亂樣本是一個道理,
  • 使用了兩個網路:即策略網路和目標網路,每隔若干步才把每步更新的策略網路引數復制給目標網路,這樣做也是為了訓練的穩定,避免Q值的估計發散,想象一下,如果當前有個transition(這個Q learning中提過的,一定要記住!!!)樣本導致對Q值進行了較差的過估計,如果接下來從經驗回放中提取到的樣本正好連續幾個都這樣的,很有可能導致Q值的發散(它的青春小鳥一去不回來了),再打個比方,我們玩RPG或者闖關類游戲,有些人為了破紀錄經常Save和Load,只要我出了錯,我不滿意我就加載之前的存檔,假設不允許加載呢,就像DQN演算法一樣訓練程序中會退不了,這時候是不是搞兩個檔,一個檔每幀都存一下,另外一個檔打了不錯的結果再存,也就是若干個間隔再存一下,到最后用間隔若干步數再存的檔一般都比每幀都存的檔好些呢,當然你也可以再搞更多個檔,也就是DQN增加多個目標網路,但是對于DQN則沒有多大必要,多幾個網路效果不見得會好很多,

1.1 定義模型

import paddle
import paddle.nn as nn
import paddle.nn.functional as F
!pip uninstall -y parl
!pip install parl
import parl
from parl.algorithms import DQN
class MLP(parl.Model):
 """ Linear network to solve Cartpole problem.
 Args:
 input_dim (int): Dimension of observation space.
 output_dim (int): Dimension of action space.
    """
 def __init__(self, input_dim, output_dim):
 super(MLP, self).__init__()
        hidden_dim1 = 256
        hidden_dim2 = 256
        self.fc1 = nn.Linear(input_dim, hidden_dim1)
        self.fc2 = nn.Linear(hidden_dim1, hidden_dim2)
        self.fc3 = nn.Linear(hidden_dim2, output_dim)
 def forward(self, state):
        x = F.relu(self.fc1(state))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
 return x

1.2 定義經驗回放

from collections import deque
class ReplayBuffer:
 def __init__(self, capacity: int) -> None:
 self.capacity = capacity
 self.buffer = deque(maxlen=self.capacity)
 def push(self,transitions):
 '''_summary_
 Args:
 trainsitions (tuple): _description_
        '''
 self.buffer.append(transitions)
 def sample(self, batch_size: int, sequential: bool = False):
 if batch_size > len(self.buffer):
 batch_size = len(self.buffer)
 if sequential: # sequential sampling
            rand = random.randint(0, len(self.buffer) - batch_size)
            batch = [self.buffer[i] for i in range(rand, rand + batch_size)]
 return zip(*batch)
 else:
            batch = random.sample(self.buffer, batch_size)
 return zip(*batch)
 def clear(self):
 self.buffer.clear()
 def __len__(self):
 return len(self.buffer)

1.3 定義智能體

from random import random
import parl
import paddle
import math
import numpy as np
class DQNAgent(parl.Agent):
 """Agent of DQN.
    """
 def __init__(self, algorithm, memory,cfg):
 super(DQNAgent, self).__init__(algorithm)
 self.n_actions = cfg['n_actions']
 self.epsilon = cfg['epsilon_start']
 self.sample_count = 0 
 self.epsilon_start = cfg['epsilon_start']
 self.epsilon_end = cfg['epsilon_end']
 self.epsilon_decay = cfg['epsilon_decay']
 self.batch_size = cfg['batch_size']
 self.global_step = 0
 self.update_target_steps = 600
 self.memory = memory # replay buffer
 def sample_action(self, state):
 self.sample_count += 1
 # epsilon must decay(linear,exponential and etc.) for balancing exploration and exploitation
 self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \
 math.exp(-1. * self.sample_count / self.epsilon_decay) 
 if random.random() < self.epsilon:
            action = np.random.randint(self.n_actions)
 else:
            action = self.predict_action(state)
 return action
 def predict_action(self, state):
        state = paddle.to_tensor(state , dtype='float32')
 q_values = self.alg.predict(state) # self.alg 是自帶的演算法
        action = q_values.argmax().numpy()[0]
 return action
 def update(self):
 """Update model with an episode data
 Args:
 obs(np.float32): shape of (batch_size, obs_dim)
            act(np.int32): shape of (batch_size)
            reward(np.float32): shape of (batch_size)
 next_obs(np.float32): shape of (batch_size, obs_dim)
            terminal(np.float32): shape of (batch_size)
        Returns:
            loss(float)
        """
 if len(self.memory) < self.batch_size: # when transitions in memory donot meet a batch, not update
 return
 if self.global_step % self.update_target_steps == 0:
 self.alg.sync_target()
 self.global_step += 1
 state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.memory.sample(
 self.batch_size)
 action_batch = np.expand_dims(action_batch, axis=-1)
 reward_batch = np.expand_dims(reward_batch, axis=-1)
 done_batch = np.expand_dims(done_batch, axis=-1)
 state_batch = paddle.to_tensor(state_batch, dtype='float32')
 action_batch = paddle.to_tensor(action_batch, dtype='int32')
 reward_batch = paddle.to_tensor(reward_batch, dtype='float32')
 next_state_batch = paddle.to_tensor(next_state_batch, dtype='float32')
 done_batch = paddle.to_tensor(done_batch, dtype='float32')
        loss = self.alg.learn(state_batch, action_batch, reward_batch, next_state_batch, done_batch) 

2、定義訓練

def train(cfg, env, agent):
 ''' 訓練
    '''
 print(f"開始訓練!")
 print(f"環境:{cfg['env_name']},演算法:{cfg['algo_name']},設備:{cfg['device']}")
    rewards = [] # record rewards for all episodes
    steps = []
 for i_ep in range(cfg["train_eps"]):
 ep_reward = 0 # reward per episode
 ep_step = 0
        state = env.reset() # reset and obtain initial state
 for _ in range(cfg['ep_max_steps']):
 ep_step += 1
            action = agent.sample_action(state) # sample action
 next_state, reward, done, _ = env.step(action) # update env and return transitions
 agent.memory.push((state, action, reward,next_state, done)) # save transitions
            state = next_state # update next state for env
 agent.update() # update agent
 ep_reward += reward  #
 if done:
 break
 steps.append(ep_step)
 rewards.append(ep_reward)
 if (i_ep + 1) % 10 == 0:
 print(f"回合:{i_ep+1}/{cfg['train_eps']},獎勵:{ep_reward:.2f},Epislon: {agent.epsilon:.3f}")
 print("完成訓練!")
 env.close()
 res_dic = {'episodes':range(len(rewards)),'rewards':rewards,'steps':steps}
 return res_dic
def test(cfg, env, agent):
 print("開始測驗!")
 print(f"環境:{cfg['env_name']},演算法:{cfg['algo_name']},設備:{cfg['device']}")
    rewards = [] # record rewards for all episodes
    steps = []
 for i_ep in range(cfg['test_eps']):
 ep_reward = 0 # reward per episode
 ep_step = 0
        state = env.reset() # reset and obtain initial state
 for _ in range(cfg['ep_max_steps']):
 ep_step+=1
            action = agent.predict_action(state) # predict action
 next_state, reward, done, _ = env.step(action) 
            state = next_state 
 ep_reward += reward 
 if done:
 break
 steps.append(ep_step)
 rewards.append(ep_reward)
 print(f"回合:{i_ep+1}/{cfg['test_eps']},獎勵:{ep_reward:.2f}")
 print("完成測驗!")
 env.close()
 return {'episodes':range(len(rewards)),'rewards':rewards,'steps':steps}

3、定義環境

OpenAI Gym中其實集成了很多強化學習環境,足夠大家學習了,但是在做強化學習的應用中免不了要自己創建環境,比如在本專案中其實不太好找到Qlearning能學出來的環境,Qlearning實在是太弱了,需要足夠簡單的環境才行,因此本專案寫了一個環境,大家感興趣的話可以看一下,一般環境介面最關鍵的部分即使reset和step,

import gym
import paddle
import numpy as np
import random
import os
from parl.algorithms import DQN
def all_seed(env,seed = 1):
 ''' omnipotent seed for RL, attention the position of seed function, you'd better put it just following the env create function
 Args:
        env (_type_): 
        seed (int, optional): _description_. Defaults to 1.
    '''
 print(f"seed = {seed}")
 env.seed(seed) # env config
 np.random.seed(seed)
 random.seed(seed)
 paddle.seed(seed)
def env_agent_config(cfg):
 ''' create env and agent
    '''
    env = gym.make(cfg['env_name']) 
 if cfg['seed'] !=0: # set random seed
 all_seed(env,seed=cfg["seed"]) 
 n_states = env.observation_space.shape[0] # print(hasattr(env.observation_space, 'n'))
 n_actions = env.action_space.n # action dimension
 print(f"n_states: {n_states}, n_actions: {n_actions}")
 cfg.update({"n_states":n_states,"n_actions":n_actions}) # update to cfg paramters
    model = MLP(n_states,n_actions)
 algo = DQN(model, gamma=cfg['gamma'], lr=cfg['lr'])
    memory = ReplayBuffer(cfg["memory_capacity"]) # replay buffer
    agent = DQNAgent(algo,memory,cfg) # create agent
 return env, agent

4、設定引數

到這里所有qlearning模塊就算完成了,下面需要設定一些引數,方便大家“煉丹”,其中默認的是筆者已經調好的~,另外為了定義了一個畫圖函式,用來描述獎勵的變化,

import argparse
import seaborn as sns
import matplotlib.pyplot as plt
def get_args():
 """ 
    """
    parser = argparse.ArgumentParser(description="hyperparameters") 
 parser.add_argument('--algo_name',default='DQN',type=str,help="name of algorithm")
 parser.add_argument('--env_name',default='CartPole-v0',type=str,help="name of environment")
 parser.add_argument('--train_eps',default=200,type=int,help="episodes of training") # 訓練的回合數
 parser.add_argument('--test_eps',default=20,type=int,help="episodes of testing") # 測驗的回合數
 parser.add_argument('--ep_max_steps',default = 100000,type=int,help="steps per episode, much larger value can simulate infinite steps")
 parser.add_argument('--gamma',default=0.99,type=float,help="discounted factor") # 折扣因子
 parser.add_argument('--epsilon_start',default=0.95,type=float,help="initial value of epsilon") #  e-greedy策略中初始epsilon
 parser.add_argument('--epsilon_end',default=0.01,type=float,help="final value of epsilon") # e-greedy策略中的終止epsilon
 parser.add_argument('--epsilon_decay',default=200,type=int,help="decay rate of epsilon") # e-greedy策略中epsilon的衰減率
 parser.add_argument('--memory_capacity',default=200000,type=int) # replay memory的容量
 parser.add_argument('--memory_warmup_size',default=200,type=int) # replay memory的預熱容量
 parser.add_argument('--batch_size',default=64,type=int,help="batch size of training") # 訓練時每次使用的樣本數
 parser.add_argument('--targe_update_fre',default=200,type=int,help="frequency of target network update") # target network更新頻率
 parser.add_argument('--seed',default=10,type=int,help="seed") 
 parser.add_argument('--lr',default=0.0001,type=float,help="learning rate")
 parser.add_argument('--device',default='cpu',type=str,help="cpu or gpu") 
 args = parser.parse_args([]) 
 args = {**vars(args)} # type(dict)         
 return args
def smooth(data, weight=0.9): 
 '''用于平滑曲線,類似于Tensorboard中的smooth
 Args:
        data (List):輸入資料
        weight (Float): 平滑權重,處于0-1之間,數值越高說明越平滑,一般取0.9
    Returns:
        smoothed (List): 平滑后的資料
    '''
    last = data[0] # First value in the plot (first timestep)
    smoothed = list()
 for point in data:
 smoothed_val = last * weight + (1 - weight) * point  # 計算平滑值
 smoothed.append(smoothed_val) 
        last = smoothed_val 
 return smoothed
def plot_rewards(rewards,cfg,path=None,tag='train'):
 sns.set()
 plt.figure() # 創建一個圖形實體,方便同時多畫幾個圖
 plt.title(f"{tag}ing curve on {cfg['device']} of {cfg['algo_name']} for {cfg['env_name']}")
 plt.xlabel('epsiodes')
 plt.plot(rewards, label='rewards')
 plt.plot(smooth(rewards), label='smoothed')
 plt.legend()

5、訓練

# 獲取引數
cfg = get_args() 
# 訓練
env, agent = env_agent_config(cfg)
res_dic = train(cfg, env, agent)
plot_rewards(res_dic['rewards'], cfg, tag="train") 
# 測驗
res_dic = test(cfg, env, agent)
plot_rewards(res_dic['rewards'], cfg, tag="test") # 畫出結果

 

seed = 10
n_states: 4, n_actions: 2
開始訓練!
環境:CartPole-v0,演算法:DQN,設備:cpu
回合:10/200,獎勵:10.00,Epislon: 0.062
回合:20/200,獎勵:85.00,Epislon: 0.014
回合:30/200,獎勵:41.00,Epislon: 0.011
回合:40/200,獎勵:31.00,Epislon: 0.010
回合:50/200,獎勵:22.00,Epislon: 0.010
回合:60/200,獎勵:10.00,Epislon: 0.010
回合:70/200,獎勵:10.00,Epislon: 0.010
回合:80/200,獎勵:22.00,Epislon: 0.010
回合:90/200,獎勵:30.00,Epislon: 0.010
回合:100/200,獎勵:20.00,Epislon: 0.010
回合:110/200,獎勵:15.00,Epislon: 0.010
回合:120/200,獎勵:45.00,Epislon: 0.010
回合:130/200,獎勵:73.00,Epislon: 0.010
回合:140/200,獎勵:180.00,Epislon: 0.010
回合:150/200,獎勵:167.00,Epislon: 0.010
回合:160/200,獎勵:200.00,Epislon: 0.010
回合:170/200,獎勵:165.00,Epislon: 0.010
回合:180/200,獎勵:200.00,Epislon: 0.010
回合:190/200,獎勵:200.00,Epislon: 0.010

 

點擊關注,第一時間了解華為云新鮮技術~

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/555938.html

標籤:其他

上一篇:Midjouney限時免費體驗

下一篇:返回列表

標籤雲
其他(161587) Python(38248) JavaScript(25513) Java(18259) C(15238) 區塊鏈(8272) C#(7972) AI(7469) 爪哇(7425) MySQL(7266) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4606) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2437) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) HtmlCss(1971) 功能(1967) Web開發(1951) C++(1942) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 深度Q網路:DQN專案實戰CartPole-v0

    摘要:相比于Q learning,DQN本質上是為了適應更為復雜的環境,并且經過不斷的改良迭代,到了Nature DQN(即Volodymyr Mnih發表的Nature論文)這里才算是基本完善。 本文分享自華為云社區《強化學習從基礎到進階-案例與實踐[4.1]:深度Q網路-DQN專案實戰CartP ......

    uj5u.com 2023-06-26 08:39:50 more
  • Midjouney限時免費體驗

    免費體驗Midjourney:https://www.topgpt.one;常見的繪畫風格:室內設計、兒童插畫、表情包制作相關風格都有介紹如何制作,midjourney的強大,只有在使用的時候才能充分體驗。若您想要獲得Midjourney中英對照辭典,請在公眾號回復“mj辭典”。AI已經滲透到各行各... ......

    uj5u.com 2023-06-26 08:34:21 more
  • Terraform 系列-使用 for-each 對本地 json 進行迭代

    ## 系列文章 * [Terraform 系列文章](https://ewhisper.cn/tags/Terraform/) * [Grafana 系列文章](https://ewhisper.cn/tags/Grafana/) ## 概述 前文 [Grafana 系列 - Grafana Ter ......

    uj5u.com 2023-06-26 08:29:07 more
  • 密碼學概念科普(加密演算法、數字簽名、散列函式、HMAC)

    ## 密碼散列函式 密碼散列函式 (Cryptographic hash function),是一個單向函式,輸入訊息,輸出摘要。主要特點是: - 只能根據訊息計算摘要,很難根據摘要反推訊息 - 改變訊息,摘要一定會跟著改變 - 對于不同的訊息,計算出的摘要幾乎不可能相同 根據散列函式的上述特點,可 ......

    uj5u.com 2023-06-26 08:29:02 more
  • 為醫生打造專屬數字分身!華為云聯合萬木健康打造醫療醫學科普和患

    摘要:如今,醫生出鏡的視頻已經成為喜聞樂見的醫學科普和患者教育手段,但醫生難以抽出時間拍攝、拍攝時間較長、成本較高等制作痛點也日益凸顯。對此,國內首個醫生AI數字人運營服務商——成都萬木健康科技有限公司找到了破局之法。 本文分享自華為云社區《為醫生打造專屬數字分身!華為云聯合萬木健康打造醫療醫學科普 ......

    uj5u.com 2023-06-26 08:28:31 more
  • 深度Q網路:DQN專案實戰CartPole-v0

    摘要:相比于Q learning,DQN本質上是為了適應更為復雜的環境,并且經過不斷的改良迭代,到了Nature DQN(即Volodymyr Mnih發表的Nature論文)這里才算是基本完善。 本文分享自華為云社區《強化學習從基礎到進階-案例與實踐[4.1]:深度Q網路-DQN專案實戰CartP ......

    uj5u.com 2023-06-26 08:28:22 more
  • KubeSphere 社區雙周報 | OpenFunction 發布 v1.1.1 | 2023.6.9-

    KubeSphere 社區雙周報主要整理展示新增的貢獻者名單和證書、新增的講師證書以及兩周內提交過 commit 的貢獻者,并對近期重要的 PR 進行決議,同時還包含了線上/線下活動和布道推廣等一系列社區動態。 本次雙周報涵蓋時間為:2023.6.9-6.22。 ## 貢獻者名單 ![](https ......

    uj5u.com 2023-06-26 08:27:47 more
  • 如何重繪 DNS 快取 (macOS, Linux, Windows)

    如何重繪 DNS 快取 (macOS, Linux, Windows) Unix Linux Windows 如何重繪 DNS 快取 (macOS, FreeBSD, RHEL, CentOS, Debian, Ubuntu, Windows) 請訪問原文鏈接:,查看最新版。原創作品,轉載請保留出處 ......

    uj5u.com 2023-06-26 08:27:01 more
  • C++ 核心指南之資源管理(中)

    > C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup、Herb Sutter 等頂尖 C++ 專家創建的一份 C++ 指南、規則及最佳實踐。旨在幫助大家正確、高效地使用“現代 C++”。 > > 這份指南側重于介面、資源管理、記憶體管理、并發等 Hig ......

    uj5u.com 2023-06-26 08:26:21 more
  • Note of Introduction to Bioorganic Chemistry and Chemical Bi

    ## Chapter 1: The Fundamentals of Chemical Biology (第 1 章 化學生物學基礎) ### 1.0 INTRODUCTION (引子) #### 1.0.1 Why organize a book on chemical biology around ......

    uj5u.com 2023-06-26 08:20:37 more