完整地理解道家思想和达尔文主义

看到标题,有的人可能会奇怪,为什么要把道家思想和达尔文主义并列。我这里简单引用几句话大家就明白了:“圣人处无为之事,行不言之教”,“天地不仁,以万物为刍狗;圣人不仁,以百姓为刍狗”,“道常无为而无不为,侯王若能守之,万物将自化”,“其政闷闷,其民淳淳;其政察察,其民缺缺”,“圣人之道,为而不争”,这些都是道德经里面的话,道家是崇尚无为的,认为万物可以自化。再看看达尔文的进化论:“物竞天择,适者生存”,是不是同一个道理,我们都知道进化论不是基督教的信仰,而当时的欧洲的有识之士普遍对中国文化有所研究,达尔文的思想是不是受道家启发呢?暂时不得而知,但是逻辑是一样的。除了达尔文,还有重农学派提倡的放任自由的经济制度,亚当斯密的自由市场理论,都是和道家思想类似的。还有一句话中国人比较熟悉“不管白猫和黑猫,能抓到老鼠就是好猫”,这句话是为了证明市场经济合理性的。显然,以上思想都是可以归为一类的。

四十年前,有人提出要全面的理解伟人的思想,并在伟人著作的选集中增加一篇《反对本本主义》,并强调“实践是检验真理的唯一标准”,当然“实践论”也依然是伟人的名篇,但说法却不是这个说法,《实践论》强调的是“理论要结合实际,经验是理论的前提”是有很深的哲学方法论的,但是“实践是检验真理的唯一标准”这句话则有着强烈的成王败寇色彩。但必须承认这些话都是非常雄辩的,观点是非常有力的。于是受此启发,也就写了这篇“完整的理解道家思想和达尔文主义”。我认为,道家思想和达尔文主义是右派的最根本的理论基础,这是他们雄辩的部分,而他们错误的部分则是未能完整的理解道家思想和达尔文主义。

儒生和道家的争论。。。。汉景帝也是很没出息,他应该说如果朕做出违背人民的事情,也会受到一样的下场。这个道生的意思,就是君王再坏也是君王,臣下也不能弑君。这个感觉就像,有钱就是厉害,贪污也是人家的本事,口气是不是很像?就很明显没有真正理解道德经,汤武革命正是天地不仁以万物为刍狗的合理性结果。而圣人不仁所以百姓为刍狗的百姓,显然是包含贵族在内的,那时如果说下等人那叫做黎民,不叫百姓,有姓的都是贵族。所谓以百姓为刍狗,就是不管你是王公贵族还是黎民百姓,在圣人眼中都是刍狗,都是平等的。我这么解释也许有人会认为我是附会老子的意思,那么我们看看庄子的思想是什么。庄子是道家仅次于老子的重要人物。庄子在齐物论充分表达了什么叫做以万物为刍狗,那就是万物都是平等的,没有高低贵贱之别,这样怎么会存在一个不可以被推翻的君主呢?

社会达尔文主义的普遍错误。

进化论的翻译有问题,应该翻译为演化论。

演化论的意思是,把你丢到大草原上,你要么成为狮子,要么成为鬣狗,或者成为斑马,把你丢到猪圈里面,你必须变成一只猪才是适应环境的。把你丢到猪圈里面,你还想做人可以吗?按照演化论的规律来说,你在猪圈里面做人,就会饿死。演化论没有错,但把在猪圈里面做猪当作理想社会,那就是你的错了。

庄子在达生篇有一个小寓言,祭祀官穿着黑色礼服,一本正经地来到猪圈,劝说猪:“你干嘛厌恶死亡呢?我要饲养你三个月,然后戒十天,斋三天,用白茅作垫席,准备一张雕绘精美的案几,把你摆在上面去祭祀神灵,那么你愿意做吗?”替猪着想的话,一定会说,这样还不如拿糟糠喂养它,把它关在猪圈里呢;为人自身考虑的话,假如活着能享有高官厚禄的尊贵,死了能用华丽的装载灵柩的车子拉着,下葬时享受豪华的棺椁,那么就会去做。替猪着想的话,那就抛弃白茅彫俎;为自己打算就拼命追求轩冕腞楯聚偻。人与猪的不同之处是什么呢?庄子的意思很明显,追求自由,不是让你们在那里自我摧残的,连猪都知道不该追求那些虚头巴脑的东西,人干嘛要整那些没用的呢?

道家思想从来都是讲为而不争,而不是自由竞争。什么叫为而不争,我觉得和孔子说的君子和而不同,是一个意思。孔子说,君子和而不同,小人同而不和。小人都是一个样的,整体就知道争。君子呢,努力做事,但是从来不争。

丛林法则的有效的基础是绝对公平的游戏规则。基因的进化是不可以父传子的,更是不可以窃取公器的。达尔文主义是个体的竞争,田径比赛,高考,并且要禁止开小灶,才能达到优选优育的目的。公平是进化论的基础。关于生育部分也就不多说了,达尔文主义也要求一部分人应该多生育。基督教的一夫一妻制根本就不符合进化论的思想。在一夫一妻制下谈社会达尔文主义是没有意义的,因为你再怎么竞争也没有扩大你的遗传优势,没有遗传优势也就没有所谓演化。

优胜劣汰是市场经济的常用话语,但究竟是优胜劣汰还是劣币驱逐良币,我觉得要一分为二的看。大量的事实已经证明在没有强力监管的情况下是劣币驱逐良币而不是优胜劣汰。比如说,环境污染,那肯定是不治理污染的成本低,驱逐主动治理污染的,抄袭盗版的成本低驱逐自主研发的,无视劳工人权的成本低驱逐福利优厚的,官商勾结的成本低驱逐合法经营的。

人与动物最大的区别之一就是人可以制定规则,也可以改变规则,但动物无法改变人类给他们制定的规则。既然人有了动物所不具有的制定规则的能力,那么人类就应该制定一个属于人类的规则,而不是给人类制定一个动物的规则。

老子强调,为而不有,功成而弗居,老子是反对私有财产的,老子也是反对物质享受的。那么消费主义就不符合道家思想。

完整的理解猫论。不管白猫黑猫抓到老鼠就是好猫。这里的猫和老鼠究竟指什么?白猫黑猫是计划与市场,老鼠就是目标,这个目标是什么?这个目标是社会主义。让一部分人先富起来,这个目标就是最终实现共同富裕。如果你认为老鼠是一部分人先富起来,那么就大错特错了。

为什么闲的没事写这些玩意儿呢?因为市场竞争挺无聊的,以我们这些智商和那些蝇营狗苟的人竞争,一群野狗争抢食物,对外杀敌的时候,没多少输出,抢起食物来却非常起劲,我们的力量是用来对外的,不是用来对内的。还是老子那句话,为而不争。搞内部倾轧的竞争,厚黑学,对有修养的人来说,是很丢脸的一件事。

孔孟为什么伟大,因为孔孟肯定了革命的合法性。为什么要批孔呢?因为儒家已经被后生所篡改,将忠君变成了儒家教条的一部分,其实儒家一直认为民为贵、社稷次之、君为轻,而且肯定汤武革命,既然肯定汤武革命就不可能把忠君作为教条,即便董仲舒的天人感应,五德终始,也是劝喻君王,不好好干就要下台。但是后来儒家被偷换概念,成为了精英的代名词,这个转换与科举制有一定关系。也就是通过垄断知识来垄断权力,皇权与知识分子结成了牢固的联盟,这个在宋朝发展到了顶点。所以文革批孔,批的其实是精英意识,而不是批的革命意识。

汤武当然可以革命!

今天的新儒学复兴,一定要注意甄别,复兴的是精英意识还是革命意识,如果是精英意识,那么就要毫不留情的打倒他,如果是革命意识,那么说明就领悟到了儒家的精髓,就可以拥护他。

明朝时,儒生们积极的评议朝政,只可惜明朝仍然有很多制度上的缺陷,还是覆亡了。明朝覆亡后,孔孟的精神没有消失,西移到了法国。

强化学习简介(六):策略梯度实例

和第四节DQN的实例一样,我们依然使用CartPole-v1来作为训练环境。策略梯度的网络和DQN的网络结构是类似的,只是在输出层需要做Softmax处理,因为策略梯度的输出本质上是一个分类问题——将某一个状态分类到某一个动作的概率。而DQN网络则是一个回归问题——某一个网络在各个动作的Q值是多少。

1
2
3
4
5
6
7
8
9
10
11
class PolicyNet(nn.Module):
def __init__(self, input_size, hidden, outputs):
super(PolicyNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden)
self.fc2 = nn.Linear(hidden, outputs)

def forward(self, x):
x = F.relu(F.dropout(self.fc1(x), 0.1))
x = self.fc2(x)
# 输出层需要使用softmax
return F.softmax(x, dim=1)

不要忘了输出层的SoftMax。

初始化参数

相对于DQN,我们也不需要额外的目标网络和参数复制操作,只需要一个策略网络即可。

1
2
3
4
5
6
7
8
9
10
11
12
BATCH_SIZE = 256
GAMMA = 0.99
HIDDEN_SIZE = 15
LR = 0.005

n_actions = env.action_space.n
input_size = env.observation_space.shape[0]

policy_net = PolicyNet(input_size, HIDDEN_SIZE, n_actions)
optimizer = optim.Adam(policy_net.parameters(), lr=LR)

steps_done = 0

选择动作

在选择动作时,我们不再需要特地设置探索概率,因为输出结果就是各个动作的概率分布。我们使用torch.distributions.categorical.Categorical 来进行取样。在每次选择动作时,我们同时记录对应的概率,以便后续使用。这个概率就是 `ln pi_theta(S_t,A_t)`

1
2
3
4
5
6
7
8
9
10
11
12
log_probs = []
rewards = []

def select_action(state):
x = torch.unsqueeze(torch.FloatTensor(state),0)
probs = policy_net(x)
c = Categorical(probs)
action = c.sample()
# log action probs to plt
prob = c.log_prob(action)
log_probs.append(prob)
return action

优化模型

为了更新参数,我们首先需要计算`v_t`,这在后续参数迭代中需要用到。

  • ` v_t = r_(t+1) + gamma * v_(t+1) `

在模拟执行的时候,我们记录了每一步的reward,我们需要计算每一步的`v_t`,其顺序与执行顺序一致。根据公式我们需要倒序的计算`v_t`,然后将计算好的结果倒序排列,就形成了`v_1,v_2…v_t`的序列。最后我们需要将数据标准化。(TODO: 这里可能存在一个序列对应的问题,其中每一个状态的累计收益,是后续状态收益之和,不包含本轮收益)

1
2
3
4
5
6
7
8
9
values = []
v = 0
for reward in reversed(rewards):
v = v * GAMMA + reward
values.insert(0, v)
mean = np.mean(values)
std = np.std(values)
for i in range(size):
values[i] = (values[i] - mean) / std

接下来我们需要更新参数,参数更新的公式为:

  • ` theta larr theta + alpha v_t ln pi_theta (A_t|S_t) `

我们将其转换为损失函数形式:

  • ` L(theta) = - v_t ln pi_theta(A_t|S_t) `

这个损失函数的形式可以帮助我们更好的理解策略梯度的原理。如果一个动作价值为负值,但是其选择概率为正,则损失较大。

1
2
3
4
5
6
7
8
9
10
loss = []
for i in np.random.choice(size, n):
loss.append(- values[i] * log_probs[i])
loss = torch.cat(loss).sum()

optimizer.zero_grad()
loss.backward()
for param in policy_net.parameters():
param.grad.data.clamp_(-1, 1)
optimizer.step()

训练循环

训练循环需要在一局结束之后进行。并清除rewards、log_probs缓存。对于cartpole-v1环境,要注意他的每一步奖励都是1,很显然在最后一步代表着游戏失败,我们需要施加一定的惩罚,我们将最后一步的奖励设为-100。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
num_episodes = 5000
for i_episode in range(num_episodes):
state = env.reset()
for t in count():
action = select_action(state)
if i_episode % 2000 == 0:
env.render()
next_state, reward, done,_ = env.step(action.item())
if done:
reward = -100
rewards.append(reward)
state = next_state
if done or t >= 2500:
optimize_model()
print('EP', i_episode)
episode_durations.append(t+1)
plot_durations()
rewards = []
log_probs = []
break

Clamp

完整代码

强化学习简介(五):策略梯度Policy Gradient

DQN证明了深度学习在增强学习中的可行性。深度学习可以将复杂的概念隐含在网络之中。但是DQN并没有将所有的概念都隐含在网络之中,只是把Q值的计算放在了网络之中,比如`epsilon-greedy`动作选择策略。因为如何选择动作和如何通过Q值计算出目标值,都是DQN所面临的问题,而Q值的目的也是为了选择动作。我们可以将增加学习的问题简化为选择动作的问题。那么我们可否使用深度学习直接做出动作选择呢?显然,我们可以定义一个网络`pi_theta`,其中输入为状态`s`,输出为每个动作`a`的概率。

策略梯度

因为这个网络与策略函数的定义一样,所以被称为策略网络。`pi_theta(a|s)`,表示在`s`状态下选择动作`a`的概率。只要这个网络能够收敛,我们就可以直接得到最佳策略。这个网络的奖励函数也就是最终游戏的总奖励。

`J(theta) = sum_(s in S)d^pi(s)V^pi(s) = sum_(s in S)d^pi(s)sum_(a in A)pi_theta(a|s)Q^pi(s, a)`

`d^pi(s)`指状态`s`在马尔科夫链上的稳定分布,`d^pi(s) = lim_(t->oo)P(s_t=s|s_0,pi_theta)`。

但是这个表达式看上去是不可能计算的,因为状态的分布和Q值都是随着策略的更新而不断变化的。但是我们并不需要计算`J(theta)`,在梯度下降法中我们只需要计算梯度`grad_(theta)J(theta)`即可

`grad_(theta)V^pi(s)`
`= grad_(theta)(sum_(a in A)pi_theta(a|s)Q^pi(s, a))`
根据导数乘法规则
`= sum_(a in A)(grad_(theta)pi_(theta)Q^pi(s, a)+pi_(theta)(a|s)grad_thetaQ^pi(s, a))`
展开`Q^pi(s,a)`为各各种可能的下一状态奖励之和
`= sum_(a in A)(grad_(theta)pi_(theta)Q^pi(s, a)+pi_(theta)(a|s)grad_(theta)sum_(s’,r)P(s’,r|s,a)(r+V^pi(s’)))`
而其中状态转移函数`P(s’,r|s,a)`、奖励`r`由环境决定,与`grad_theta`无关,所以
`= sum_(a in A)(grad_(theta)pi_(theta)Q^pi(s, a)+pi_(theta)(a|s)sum_(s’,r)P(s’,r|s,a)grad_(theta)V^pi(s’))`
`= sum_(a in A)(grad_(theta)pi_(theta)Q^pi(s, a)+pi_(theta)(a|s)sum_(s’)P(s’|s,a)grad_(theta)V^pi(s’))`

现在我们有了一个形式非常好的递归表达式:
`grad_(theta)V^pi(s) = sum_(a in A)(grad_(theta)pi_(theta)Q^pi(s, a)+pi_(theta)(a|s)sum_(s’)P(s’|s,a)grad_(theta)V^pi(s’))`

设 `rho^pi(s->x, k)` 表示在策略`pi^theta`下,`k`步以后状态`s`转移到状态`x`的概率。有:

  • `rho^pi(s->s, k=0)=1`
  • `rho^pi(s->s’, k=1)=sum_(a)pi_(theta)(a|s)P(s’|s,a)`
  • `rho^pi(s->x, k+1) = sum_(s’)rho^pi(s->s’, k)rho^pi(s’->x, 1)`

为了简化计算,令 `phi(s)=sum_(a in A)grad_(theta)pi_theta(a|s)Q^pi(s,a)`

`grad_(theta)V^pi(s)`
`= phi(s) + sum_(a in A)pi_(theta)(a|s)sum_(s’)P(s’|s,a)grad_(theta)V^pi(s’) `
`= phi(s) + sum_(s’)sum_(a in A)pi_(theta)(a|s)P(s’|s,a)grad_(theta)V^pi(s’) `
`= phi(s) + sum_(s’)rho^pi(s->s’,1)grad_(theta)V^pi(s’) `
`= phi(s) + sum_(s’)rho^pi(s->s’,1)(phi(s’) + sum_(s’’)rho^pi(s’->s’’,1)grad_(theta)V^pi(s’’)) `
`= phi(s) + sum_(s’)rho^pi(s->s’,1)phi(s’) + sum_(s’’)rho^pi(s->s’’,2)grad_(theta)V^pi(s’’) `
`= phi(s) + sum_(s’)rho^pi(s->s’,1)phi(s’) + sum_(s’’)rho^pi(s->s’’,2)phi(s’’) + sum_(s’’’)rho^pi(s->s’’’,3)grad_(theta)V^pi(s’’’) `
`= …`
`= sum_(x in S)sum_(k=0)^(oo)rho^pi(s->x, k)phi(x)`

令 `eta(s)=sum_(k=0)^(oo)rho^pi(s_0->s, k)`

`grad_(theta)J(theta)=grad_(theta)V^pi(s_0)`
`= sum_(s)sum_(k=0)^(oo)rho^pi(s_0->s,k)phi(s)`
`= sum_(s)eta(s)phi(s)`
`= (sum_(s)eta(s))sum_(s)((eta(s))/(sum_(s)eta(s)))phi(s)`
因 `sum_(s)eta(s)` 属于常数,对于求梯度而言常数可以忽略。
`prop sum_(s)((eta(s))/(sum_(s)eta(s)))phi(s)`
因 `eta(s)/(sum_(s)eta(s))`表示`s`的稳定分布
`= sum_(s)d^pi(s)sum_a grad_(theta)pi_(theta)(a|s)Q^pi(s,a)`
`= sum_(s)d^pi(s)sum_a pi_(theta)(a|s)Q^pi(s,a)(grad_(theta)pi_(theta)(a|s))/(pi_(theta)(a|s))`
因 ` (ln x)’ = 1/x `
`= Err_pi[Q^pi(s,a)grad_theta ln pi_theta(a|s)]`

所以得出策略梯度最重要的定理:

` grad_(theta)J(theta)=Err_pi[Q^pi(s,a)grad_theta ln pi_theta(a|s)] `

其中的`Q^pi(s,a)`也就是状态s的累计收益,可以在一次完整的动作轨迹中累计计算得出。

算法描述

该算法被称为 REINFORCE

  • 随机初始化`theta`
  • 生成一个完整的策略`pi_theta`的轨迹: `S1,A1,R2,S2,A2,…,ST`。
  • For t=1, 2, … , T-1:
    • ` v_t = sum_(i=0)^(oo) gamma^i R_(t+i+1) `
    • ` theta larr theta + alpha v_t ln pi_theta (A_t|S_t) `

参考:
Lilian Weng:Policy Gradient Algorithms

强化学习简介(四):DQN实战

我们在上文简述了DQN算法。此文以PyTorch来实现一个DQN的例子。我们的环境选用CartPole-v1。我们的输入是一幅图片,动作是施加一个向左向右的力量,我们需要尽可能的保持木棍的平衡。

CartPole-v1

对于这个环境,尝试了很多次,总是不能达到很好的效果,一度怀疑自己的代码写的有问题。后来仔细看了这个环境的奖励,是每一帧返回奖励1,哪怕是最后一帧也是返回1 的奖励。这里很明显是不合理的俄。我们需要重新定义这个奖励函数,也就是在游戏结束的时候,给一个比较大的惩罚,r=-100。很快可以达到收敛。

Replay Memory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Transition = namedtuple('Transition', ('state', 'action', 'next_state', 'reward'))

class ReplayMemory:
def __init__(self, capacity):
self.cap = capacity
self.mem = []
self.pos = 0

def push(self, *args):
"""Save a transition."""
if len(self.mem) < self.cap:
self.mem.append(None)
self.mem[self.pos] = Transition(*args)
self.pos = (self.pos + 1) % self.cap

def sample(self, batch_size):
return random.sample(self.mem, batch_size)

def __len__(self):
return len(self.mem)

Q网络

1
2
3
4
5
6
7
8
9
10
class DQN(nn.Module):
def __init__(self, input_size, hidden, outputs):
super(DQN, self).__init__()
self.fc1 = nn.Linear(input_size, hidden)
self.fc2 = nn.Linear(hidden, outputs)

def forward(self, x):
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x

初始化参数和状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import gym

env = gym.make('CartPole-v0').unwrapped

BATCH_SIZE = 128
GAMMA = 0.9
EPS_START = 0.9
EPS_END = 0.05
EPS_DECAY = 200
TARGET_UPDATE = 10
LR = 0.01

n_actions = env.action_space.n
input_size = env.observation_space.shape[0]

# 策略网络
policy_net = DQN(input_size, 10, n_actions)
# 目标网络
target_net = DQN(input_size, 10, n_actions)
# 目标网络从策略网络复制参数
target_net.load_state_dict(policy_net.state_dict())
target_net.eval()

optimizer = optim.Adam(policy_net.parameters(), lr=LR)
memory = ReplayMemory(10000)

探索和选择最佳动作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
steps_done = 0

def select_action(state, no_explore=False):
x = torch.unsqueeze(torch.FloatTensor(state), 0)
global steps_done
sample = random.random()
eps_threshold = EPS_END + (EPS_START - EPS_END) * math.exp(-1. * steps_done / EPS_DECAY)
steps_done += 1
if sample > eps_threshold or no_explore:
with torch.no_grad():
# t.max(1) will return largest column value of each row.
# second column on max result is index of where max element was
# found, so we pick action with the larger expected reward.
return policy_net(x).max(1)[1].view(1, 1)
else:
return torch.tensor([[random.randrange(n_actions)]], dtype=torch.long)

优化模型(关键代码)

这里主要是抽样、目标值计算、损失计算的部分。损失计算采用Huber loss。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def optimize_model():
if len(memory) < BATCH_SIZE:
return
transitions = memory.sample(BATCH_SIZE)
batch = Transition(*zip(*transitions))
non_final_mask = torch.tensor(tuple(map(lambda s: s is not None,
batch.next_state)), dtype=torch.uint8)
non_final_next_states = torch.FloatTensor([s for s in batch.next_state if s is not None])
state_batch = torch.FloatTensor(batch.state)
action_batch = torch.cat(batch.action)
reward_batch = torch.FloatTensor(batch.reward)
state_action_values = policy_net(state_batch).gather(1, action_batch)
next_state_values = torch.zeros(BATCH_SIZE)
next_state_values[non_final_mask] = target_net(non_final_next_states).max(1)[0].detach()
expected_state_action_values = (next_state_values * GAMMA) + reward_batch

loss = F.mse_loss(state_action_values, expected_state_action_values.unsqueeze(1))

optimizer.zero_grad()
loss.backward()
# 限制网络更新的幅度,可以大幅提升训练的效果。
for param in policy_net.parameters():
param.grad.data.clamp_(-1, 1)
optimizer.step()

训练循环

这里主要有主循环、获取输入、记录回放、训练、复制参数等环节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
num_episodes = 1000
for i_episode in range(num_episodes):
state = env.reset()
for t in count():
action = select_action(state, i_episode%50==0)
if(i_episode%50==0):
env.render()
next_state, reward,done,_ = env.step(action.item())
# reward = torch.tensor([reward])
if done:
reward = -100
memory.push(state, action, next_state, reward)
state = next_state
if done or t > 2500:
for i in range(10):
optimize_model()
episode_durations.append(t+1)
plot_durations()
break
if i_episode % TARGET_UPDATE == 0:
target_net.load_state_dict(policy_net.state_dict())

Clamp

完整代码

强化学习简介(三):DQN

上一个例子中我们使用了冰面滑行的游戏环境,这是一个有限状态空间的环境。如果我们现在面对的是一个接近无限的状态空间呢?比如围棋,比如非离散型的问题。我们无法使用一个有限的Q表来存储所有Q值,即使有足够的存储空间,也没有足够的计算资源来遍历所有的状态空间。对于这一类问题,深度学习就有了施展的空间了。

Q表存储着状态s和动作a、奖励r的信息。我们知道深度神经网络,也具有存储信息的能力。DQN算法就是将Q-table存储结构替换为神经网络来存储信息。我们定义神经网络`f(s, w) ~~ Q(s)`,输出为一个向量`[Q(s, a_1), Q(s, a_2), Q(s, a_3), …, Q(s, a_n)]`。经过这样的改造,我们就可以用Q-learing的算法思路来解决更复杂的状态空间的问题了。我们可以通过下面两张图来对比Q-learning和DQN的异同。

Q-learning

Deep-Q-learning

网络结构要根据具体问题来设计。在神经网络训练的过程中,损失函数是关键。我们采用MSE来计算error。

`L(w) = (ubrace(r + argmax_aQ(s’, a’))_(目标值) - ubrace(Q(s, a))_(预测值))^2`

基本算法描述

1
2
3
4
5
6
7
8
9
10
11
使用随机参数初始化网络Q
while 未收敛:
action 按一定概率随机选取,其余使用argmax Q(s)选取
模拟执行 action 获取 状态 s_, 奖励 r, 是否完成 done
if done:
target = r
else:
target = r + gamma * argmax Q(s_)
loss = MSE(target, Q(s, a))
使用loss更新网络Q
s = s_

但是,通过实验我们会发现,训练过程非常的不稳定。稳定性是强化学习所面临的主要问题之一,为了达到稳定的训练我们需要运用一些优化的手段。

环境的稳定性

Agent生活在环境之中,并根据环境的反馈进行学习,但环境是否是稳定的呢?假设agent在学习出门穿衣的技能,它需要学会在冬天多穿,夏天少穿。但是这个agent只会根据当天的反馈来修正自己的行为,也就是说这个agent是没有记忆的。那么这个agent就会在多次失败后终于在冬天学会了多穿衣,但转眼之间到了夏天他又会陷入不断的失败,最终他在夏天学会了少穿衣之后,又会在冬天陷入失败,如此循环不断,永远不会收敛。如果要能够很好的训练,这个agent至少要有一整年的记忆空间,每一批都要从过去的记忆中抽取记忆来进行训练,就可以避免遗忘过去的教训。

在DeepMind的Atari 论文中提到

First, we used a biologically inspired mechanism termed experience replay that randomizes over the data, thereby removing correlations in the observation sequence and smoothing over changes in the data distribution.

意思是,受生物学启发,他们采用了一种叫做经验回放(experience replay)的机制,随机抽取数据来到达“移除观察序列的相关性,平滑数据分布的改变”的目的。
DQN with Atari

我们已经理解了要有经验回放的记忆,但是为什么一定要随机抽取呢?对此论文认为这个随机抽取可以移除序列相关性、平滑分布中的改变。当如何理解呢?简单的说就是在我们不清楚合理的周期的情况下,能够保证采样的合理性。我们仍然以四季穿衣举例,假设我们不使用随机采样,我们必须在每次训练中都采用365天左右的数据,才能使我们的数据样本分布合理。可是agent并不清楚一年365天这个规律,这恰恰是我们所要学习的内容。采用随机采用,就可以自然的做到数据的分布合理,而且只需要使用记忆中的部分数据,减少单次迭代的计算量。

在这个记忆里,我们并不记录当时的网络参数(分析过程),我们只记录(状态s,动作a,新状态s’, 单步奖励r)。显然,记忆的尺寸不可能无限大。当记忆体增大到一定程度之后,我们采用滚动的方式用最新的记忆替换掉最老的记忆。就像在学习围棋的过程中,有初学者阶段的对局记忆,也有高手阶段的对局记忆,在提升棋艺的角度来看,高手阶段的记忆显然比初学者阶段的记忆更有价值。

说句题外话,其实对于一个民族而言也是一样的。我们这个民族拥有一个非常好的传统,就是记述历史,也就是等于我们这个民族拥有足够大的记忆量,这是我们胜于其他民族的。但是这个历史记录中,掺杂了历史上不同阶段的评价,这些评价是根据当时的经验得出的。而根据DQN的算法描述来看,对我们最有价值的部分其实是原始信息,而不是那些附加在之上的评价,这些评价有正确的部分,也有错误的部分,我们不用去过多关心。我们只需要在今天的认知(也就是最新的训练结果)基础上,对历史原始信息(旧状态、动作、新状态、单步奖励)进行随机的抽样分析即可。

网络稳定性

DQN另一个稳定性问题与目标值计算有关。因为`target = r + gamma * argmax Q(s’)`,所以目标值与网络参数本身是相关,而参数在训练中是不断变化的,所以这会造成训练中的不稳定。一个神经网络可以自动收敛,取决于存在一个稳定的目标,如果目标本身在不断的游移变动,那么想要达到稳定就比较困难。这就像站在平地上的人很容易平衡,但如果让人站在一个不断晃动的木板上,就很难达到平衡。为了解决这个问题,我们需要构建一个稳定的目标函数。

解决的方法是采用两个网络代替一个网络。一个网络用于训练调整参数,称之为策略网络,另一个专门用于计算目标,称之为目标网络。目标网络与策略网络拥有完全一样的网络结构,在训练的过程中目标网络的参数是固定的。执行一小批训练之后,将策略网络最新的参数复制到目标网络中。

目标网络

经验回放和目标网络的效果见下表(引用自Nature 论文):
优化对比

其他DQN优化

关于DQN的优化,这篇文章描述的比较全面 https://zhuanlan.zhihu.com/p/21547911。在之后的实践中考虑是否进一步深入。主要介绍3个改进:

DQN优化

  • Double DQN:对目标值计算的优化,a’使用策略网络选择的动作来代替目标网络选择的动作。
  • Prioritised replay:使用优先队列(priority queue)来存储经验,避免丢弃早期的重要经验。使用error作为优先级,仿生学技巧,类似于生物对可怕往事的记忆。
  • Dueling Network:将Q网络分成两个通道,一个输出V,一个输出A,最后再合起来得到Q。如下图所示(引用自Dueling Network论文)。这个方法主要是idea很简单但是很难想到,然后效果一级棒,因此也成为了ICML的best paper。

Dueling Network

强化学习简介(二):Q-learning实战

我们现在通过一个例子来演练Q-learning算法。在练习强化学习之前,我们需要拥有一个模拟器环境。我们主要的目的是学习编写机器人算法,所以对模拟器环境部分不推荐自己写。直接使用gym来作为我们对实验环境。安装方法:

1
pip install gym

初识环境

我们的实验环境是一个冰湖滑行游戏,你将控制一个agent在冰面到达目标终点,前进方向并不总受你的控制,你还需要躲过冰窟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import gym
# 构造游戏环境
env = gym.make('FrozenLake-v0')
# 动作空间-> Discrete(4)
print(env.action_space)
# 状态空间-> Discrete(16)
print(env.observation_space)
# 初始化游戏环境,并得到状态s
s = env.reset()
for _ in range(10):
# 渲染游戏画面
env.render()
# 从动作空间中随机选择一个动作a
a = env.action_space.sample()
# 执行动作a,得到新状态s,奖励r,是否完成done
s, r, done, info = env.step(a) # take a random action
print(s, r, done, info)
# 关闭环境
env.close()

游戏画面示意如下:

1
2
3
4
SFFF       (S: 起点,安全)
FHFH (F: 冰面,安全)
FFFH (H: 冰窟,进入则失败)
HFFG (G: 终点,到达则成功)

Agent结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import random
import math
import gym

class QLAgent():
q = None
action_space = None
epsilon = 0.1 # 探索率
gamma = 0.99 # 衰减率
lr = 0.1 # 学习率
def __init__(self, action_space, state_count, epsilon=0.1, lr=0.1, gamma=0.99):
self.q = [[0. for a in range(action_space.n)] for s in range(state_count)]
self.action_space = action_space
self.epsilon = epsilon
self.lr = lr
self.gamma = gamma

# 根据状态s,选择动作a
def choose_action(self, s):
pass

# 更新状态变化并学习,状态s执行了a动作,得到了奖励r,状态转移到了s_
def update_transition(self, s, a, r, s_):
pass

这是一个Agent的一般结构,主要由初始化、选择动作、更新状态变化,三个方法构成。后续的其他算法将依然采用该结构。q表数据使用一个二维数组表示,其大小为 state_count action_count,对于这个项目而言是一个 `16*4` 的大小。

添加Q-table的辅助方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 返回状态s的最佳动作a、及其r值。
def argmax(self, s):
max_r = -math.inf
max_a = None
for a in range(self.action_space.n):
r = self.q_get(s, a)
if r > max_r:
max_a = a
max_r = r
return max_a, max_r
# 获得 状态s,动作a 对应的r值
def q_get(self, s, a):
return self.q[s][a]
# 更新 状态s 动作a 对应的r值
def q_put(self, s, a, v):
self.q[s][a] = v

Q-learning的关键步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def choose_action(self, s):
if random.random() < self.epsilon:
# 按一定概率进行随机探索
return self.action_space.sample()
# 返回最佳动作
a, _ = self.argmax(s)
return a

def update_transition(self, s, a, r, s_):
q = self.q_get(s, a)
_, r_ = self.argmax(s_)
# Q <- Q + a(Q' - Q)
# <=> Q <- (1-a)Q + a(Q')
q = (1-self.lr) * q + self.lr * (r + self.gamma * r_)
self.q_put(s, a, q)

训练主循环

我们进行10000局游戏的训练,每局游戏执行直到完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
env = gym.make('FrozenLake-v0')
agent = QLAgent(env.action_space, env.observation_space.n)
for epoch in range(10000):
s = env.reset()
done = False
while not done:
# env.render() # 训练过程不需要渲染
a = agent.choose_action(s) # 选择动作
s_, r, done, info = env.step(a) # 执行动作
agent.update_transition(s, a, r, s_) # 更新状态变化
s = s_
# 显示训练后的Q表
print(agent.q)

测试效果

在测试中,我们只选择最佳策略,不再探索,也不再更新Q表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 获胜次数
total_win = 0
for i in range(10000):
s = env.reset()
done = False
while not done:
# 选择最佳策略
a, _ = agent.argmax(s)
# 执行动作 a
s_, r, done, info = env.step(a)
if done and r == 1:
total_win += 1
s = s_
print('Total win=', total_win)
env.close()

最终测试的效果是在1万局中获胜了7284次,说明达到了不错的实验效果。

强化学习简介(一):Q-learning

完成图像分类之类的监督学习任务,已经没有特别大的难度。我们希望AI能够帮助我们处理一些更加复杂的任务,比如:下棋、玩游戏、自动驾驶、行走等。这一类的学习任务被称为强化学习。

强化学习图示

K摇臂赌博机

我们可以考虑一个最简单的环境:一个动作可立刻获得奖励,目标是使每一个动作的奖励最大化。对这种单步强化学习任务,可以设计一个理论模型——“K-摇臂赌博机”。这个赌博机有K个摇臂,赌徒在投入一个硬币后可一选择按下一个摇臂,每个摇臂以一定概率吐出硬币,但这个概率赌徒并不知道。赌徒的目标是通过一定的策略最大化自己的奖赏,即获得最多的硬币。最终所获得的总奖励被称为累计奖励。

K摇臂赌博机

对于这个简单模型,若要知道每个摇臂的概率,我们只需要进行足够多的尝试即可,这是“仅探索”策略;若要奖励最大化,则需要执行奖赏概率最大的动作即可,这是“仅利用”策略。但在更复杂的环境中,我们不可能对每一个状态的每个动作都进行足够多的探索。比如围棋,我们后续的探索是需要依赖于之前的探索的。因此我们需要在探索和利用之间进行平衡。我们在学习的过程中,必须要保持一定的概率`epsilon`进行探索,其余时候则执行学习到的策略。

基本概念术语

为了便于分析讨论,我们定义一些术语。

  • 机器agent处于环境`E`中。

  • 状态空间为`S`,每个状态`s in S`是机器感知到的环境描述。

  • 机器能够采取的可采取的动作a的集合即动作空间`A`,`a in A`。

  • 转移函数`P`表示:当机器执行了一个动作`a`后,环境有一定概率从状态`s`改变为新的状态`s’`。即:`s’=P(s, a)`

  • 奖赏函数`R`则表示了执行动作可能获得的奖赏`r`。即:`r=R(s,a)`。

  • 环境可以描述为`E=<<S, A, P, R>>`。

  • 强化学习的任务是习得一个策略(policy)`pi`,使机器在状态`s`下选择到最佳的`a`。策略有两种描述方法:

    1. `a=pi(s)` 表示状态`s`下将执行动作`a`,是一种确定性的表示法。
    2. `pi(s, a)` 表示状态`s`下执行动作`a`的概率。这里有 `sum_a pi(s,a)=1`
  • 累计奖励指连续的执行一串动作之后的奖励总和。

  • `Q^(pi)(s, a)`表示在状态`s`下,执行动作`a`,再策略`pi`的累计奖励。为方便讨论后续直接写为`Q(s,a)`。

  • `V^(pi)(s)` 表示在状态`s`下,使用策略`pi`的累计奖励。为方便讨论后续直接写为`V(s)`。

强化学习往往不会立刻得到奖励,而是在很多步之后才能得到一个诸如成功/失败的奖励,这是我们的算法需要反思之前所有的动作来学习。所以强化学习可以视作一种延迟标记的监督学习。

Q-learning

对于我们要学习的`Q(s,a)`函数,我们可以使用一个Q-table来表示。Q-table是一个二维表,记录每个状态`s in S, a in A`的`Q`值。Q表被初始化为0的状态。在状态`s`执行了动作`a`之后,得到状态`s’`,奖励`r`。我们将潜在的`Q`函数记为`Q_(real)`,其值为当前奖励r与后续状态`s’`的最佳累计奖励之和。则有:

` Q_(real)(s, a) = r + gamma * argmax_aQ(s’, a) `
` err = Q_(real)(s, a) - Q(s, a) `

其中`gamma`为`Q`函数的偏差,`err`为误差,`alpha`为学习率。 可得出更新公式为:

` Q(s, a) leftarrow Q(s, a) + alpha*err `
即:
` Q(s,a) leftarrow (1-alpha)Q(s,a) + alpha(r + gamma * argmax_aQ(s’, a)) `

以上公式即为Q-learning的关键

算法描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 初始化Q表
for s in S:
for a in A:
Q(s, a) = 0
# 训练过程
for i in range(N):
if rand() < epsilon:
a = 从 A 中随机选取一个动作
else:
a = 从 A 中选取使 Q(a) 最大的a
# 从环境中获得反馈
r = R(s, a)
s1 = P(s, a)
# 更新Q表
Q(s,a) = (1-alpha)*Q(s,a) + alpha * (r + gamma * argmax Q(s1, a) - Q(s, a))
s = s1

下图是一个Q表内存结构,在经过一定的学习后,Q表的内容将能够不断逼近每个状态的每个动作的累计收益。
Q-table

2019年度预测:新思潮的前夜

我们正站在世界巨变的前夜,这个巨变将以我们意向不到的方式出现。

美国会倒下,但我们不知道美国将会如何倒下。中美最根本的战争在金融领域,人民币国际化就是在争夺全球铸币权份额。美元可能会崩溃,但中国拥有的大量美元资产需要安全置换。如果美元霸权能够缓缓的衰落,对中国的影响最小,如果美元霸权骤然终结,也将对中国带来冲击。

在现在的国际环境下,依靠出口拉动经济的方式是不可持续的,最重要的是提振内需,但目前老百姓手里没钱,内需也拉不动。所以现在必须要平衡贫富差距。缩小贫富差距就要有人掏钱,因此有钱的人都想着抽逃资金。严打不仅稳定社会治安,也会收回大量不法收入。

房价不会大涨也不会大跌,上涨会让还没买房的人不满意,下跌会让已经买了房的人不满意,所以就锁住,不涨不跌。房子已经进入了计划时代,就像火车票必须凭身份证购买来抑制黄牛是一样的。信息化使计划经济具有了更大的可行性。即使通货膨胀了,房价也不会涨,因为已经不再是市场化环境了。所以本质上,房价在缓缓下跌。

需要优化财富分配,医疗养老之类的福利会加强,教育科研投入会加大,贫困人口持续扶贫。

大量高科技依然掌握在欧美手中。中国的普及教育很好,但是高等教育与欧美差距较大,教育改革也是困难重重。仅从本人专业来看,编程语言、操作系统、深度学习框架,都是美国人的。

生产过剩、人工智能,会影响就业率。新增人口下滑,老龄化加速。

创业会越来越难,基本上属于解决就业的公益事业。体制内、大平台的职工相当于新的铁饭碗,同时临时工也会越来越多。

创业的最佳阶段是GDP高速增长的阶段,只要入局基本都有得赚。如果宏观上的财富没有增加,那么创业相当于是在存量市场与其他人竞争。除非有压倒性的武器,找到可以战胜的敌人,否则不要创业。海外市场机会较大。

创新是有闲阶级特权,有闲不一定是非常富有,但需要有一份保证基本生活的、时间投入少的收入,即“睡后收入”。如果创新可立刻获得回报,则可以进入增强回路循环。

随着机器人取代越来越多的低端就业,UBI即无条件基本收入的概念会被越来越多的人接受。今年参加美国总统竞选的杨安泽使用了UBI的口号,别人问他钱从哪来,他说向富人收税。桑德斯再次参加大选,美国民主社会主义近年快速崛起。

传统发达国家因为后发国家的追赶,导致本国产业受到影响,就业率下滑。从而产生越来越多的社会运动,希望获得更高的社会福利,有的地方甚至为了几毛钱的地铁涨价就闹了起来。但这些国家由于债务压力已经很大,经济环境恶化,无法给到更多的社会福利。富人们则利用国际避税手段,来躲避社会责任。一场全球性的萧条和左派革命正在酝酿,那些小政府的发达地区问题最严重(比如香港、韩国是小政府、新加坡是大政府)。

中美的竞争不会转化为热战,因为中国不想打,美国打不赢。中美握手后,将联手打击国际避税,共治天下。

资本因其可以转移,在国际共运中,虽然一部分资产被没收,但更多的则逃到了避风港中。苏联解体、改革开放也使大量的公有资产再次被私有化。美国衰落将使资本最大的避风港消失,所有的资本都将处于监管之下。

中国国内思想分歧增大。近几十年的高速增长,右派认为前人有罪,自己有功,而问题则留给后人处理,大力宣传,所以右派思想居主流。近十年文革一代领导人上台,左派思想逐渐兴起,加之贫富差距、环保、贪腐等问题的存在,助推了左派思潮的壮大。我也是因为近几年的反腐、扶贫、强军才开始重新认识这个国家。

如果说这一代领导人的思想是在文革中形成的话,那么下一届领导人的思想又是在何时形成的呢?文革给中国续了命,修正主义则给苏联送了命。戈尔巴乔夫说“我们是苏共二十大的孩子,苏联六十年代的历史对我们影响很大”,六十年代的苏联就相当于八十年代的中国,赫鲁晓夫教出了戈尔巴乔夫,戈尔巴乔夫一手送走了苏联。下一届领导人是左还是右?改革开放中形成的利益集团是否已经可以左右政治格局,形成类似日韩的财阀统治?这是未来最大的不确定因素。

正因为这是未来最大的不确定因素,所以伟人在晚年才要发动文革。不断受人非议的文革,完成了一代人的政治教育,奠定了一代人的思想基础,顶住了世界范围的社会主义颠覆潮。伟人看得太远了,我们无法企及。但是我们虽然顶住了颠覆,却也经历了改革,大量国有资产被私有化,形成了利益集团。几十年过去了,人心不古。既得利益者一定会想方设法垄断政治权力,不加控制结局与明朝无异。解决方案伟人都说过了,而我们首先要做的就是正确评价文革。反对文革的人常拿文革中的极端案例来举例,犯罪案件每个时代都有,我们要注意到文革时期的犯罪率是远低于改革之后的,即便把群众运动中极端案件算上,也比改革之后的犯罪率低。就连当时“受迫害”的人都没有反对文革(比如现任领导人),那些不了解文革的人又凭什么反对呢?妖魔化文革的本质就是妖魔化群众运动,从而顺理成章的剥夺了民众的政治权力。

扯远了,看来可以写的主题有很多。

Torch的损失函数和优化器

深度神经网络输出的结果与标注结果进行对比,计算出损失,根据损失进行优化。那么输出结果、损失函数、优化方法就需要进行正确的选择。

常用损失函数

pytorch 损失函数的基本用法

1
2
criterion = LossCriterion(参数)
loss = criterion(x, y)

Mean Absolute Error
torch.nn.L1Loss
Measures the mean absolute error.

Mean Absolute Error/ L1Loss

nn.L1Loss

很少使用

Mean Square Error Loss

nn.MSELoss

针对数值不大的回归问题。

Smooth L1 Loss

nn.SmoothL1Loss

它在绝对差值大于1时不求平方,可以避免梯度爆炸。大部分回归问题都可以适用,尤其是数值比较大的时候。

Negative Log-Likelihood Loss

torch.nn.NLLLoss,一般与 LogSoftmax 成对使用。使用时 loss(softmaxTarget, target)。用于处理多分类问题。

1
2
3
4
5
6
7
8
m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
# input is of size N x C = 3 x 5, C为分类数
input = torch.randn(3, 5, requires_grad=True)
# each element in target has to have 0 <= value < C
target = torch.tensor([1, 0, 4])
output = loss(m(input), target)
output.backward()

Cross Entropy Loss

nn.CrossEntropyLoss 将 LogSoftmax 和 NLLLoss 绑定到了一起。所以无需再对结果使用Softmax

1
2
3
4
5
loss = nn.CrossEntropyLoss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.empty(3, dtype=torch.long).random_(5)
output = loss(input, target)
output.backward()

BCELoss

二分类问题的CrossEntropyLoss。输入、目标结构是一样的。

1
2
3
4
5
6
m = nn.Sigmoid()
loss = nn.BCELoss()
input = torch.randn(3, requires_grad=True)
target = torch.empty(3).random_(2)
output = loss(m(input), target)
output.backward()

Margin Ranking Loss

常用户增强学习、对抗生成网络、排序任务。给定输入x1,x2,y的值是1或-1,如果y==1表示x1应该比x2的排名更高,y==-1则相反。如果y值与x1、x2顺序一致,那么loss为0,否则错误为 y*(x1-x2)

Hinge Embedding Loss

y的值是1或-1,用于衡量两个输入是否相似或不相似。

Cosine Embedding Loss

给定两个输入x1,x2,y的值是1或-1,用于衡量x1和x2是否相似。

其中cos(x1, x2)表示相似度

各种优化器

大多数情况Adam能够取得比较好的效果。SGD 是最普通的优化器, 也可以说没有加速效果, 而 Momentum 是 SGD 的改良版, 它加入了动量原则. 后面的 RMSprop 又是 Momentum 的升级版. 而 Adam 又是 RMSprop 的升级版. 不过从这个结果中我们看到, Adam 的效果似乎比 RMSprop 要差一点. 所以说并不是越先进的优化器, 结果越佳.

1
2
3
4
5
6
7
8
# SGD 就是随机梯度下降
opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
# momentum 动量加速,在SGD函数里指定momentum的值即可
opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.8)
# RMSprop 指定参数alpha
opt_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)
# Adam 参数betas=(0.9, 0.99)
opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))

理解CNN参数及PyTorch实例

本文假设读者已经了解了CNN的基本原理。在实际的项目中,会发现CNN有多个参数需要调整,本文主要目的在于理清各个参数的作用。

卷积核 kernel

Kernel,卷积核,有时也称为filter。在迭代过程中,学习的结果就保存在kernel里面。深度学习,学习的就是一个权重。kernel的尺寸越小,计算量越小,一般选择3x3,更小就没有意义了。

结果是对卷积核与一小块输入数据的点积。

层数 Channels

所有位置的点积构成一个激活层。

如果我们有6个卷积核,我们就会有6个激活层。

步长 Stride


上图是每次向右移动一格,一行结束向下移动一行,所以stride是1x1,如果是移动2格2行则是2x2。

填充 Padding

Padding的作用是为了获取图片上下左右边缘的特征。

池化 Pooling

卷积层为了提取特征,但是卷积层提取完特征后特征图层依然很大。为了减少计算量,我们可以用padding的方式来减小特征图层。Pooling的方法有MaxPooling核AveragePooling。

推荐看一下李飞飞的这篇slide

PyTorch 中的相关方法

  • torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode=’zeros’)

  • torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

    • stride 默认与kernel_size相等
  • torch.nn.AvgPool2d(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None)

  • Tensor.view(*shape) -> Tensor

    • 用于将卷积层展开为全连接层
      1
      2
      3
      4
      5
      6
      7
      8
      9
      >>> x = torch.randn(4, 4)
      >>> x.size()
      torch.Size([4, 4])
      >>> y = x.view(16)
      >>> y.size()
      torch.Size([16])
      >>> z = x.view(-1, 8) # the size -1 is inferred from other dimensions
      >>> z.size()
      torch.Size([2, 8])

MNIST例子

MNIST 数据集的输入是 1x28x28 的数据集。在实际开发中必须要清楚每一次的输出结构。

  • 我们第一层使用 5x5的卷积核,步长为1,padding为0,28-5+1 = 24,那么输出就是 24x24。计算方法是 (input_size - kernel_size)/ stride + 1。
  • 我们第二层使用 2x2的MaxPool,那么输出为 12x12.
  • 第三层再使用5x5,卷积核,输出则为 12-5+1,即 8x8。
  • 再使用 2x2 MaxPool,输出则为 4x4。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
"""ConvNet -> Max_Pool -> RELU -> ConvNet -> Max_Pool -> RELU -> FC -> RELU -> FC -> SOFTMAX"""
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 10, 5, 1)
self.conv2 = nn.Conv2d(10, 20, 5, 1)
self.fc1 = nn.Linear(4*4*20, 50)
self.fc2 = nn.Linear(50, 10)

def forward(self, x):
x = F.relu(self.conv1(x))
x = F.max_pool2d(x, 2, 2)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2, 2)
x = x.view(-1, 4*4*20)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x, dim=1)

以上代码摘自 https://github.com/floydhub/mnist