DQN算法详解 代码解释 算法详解

第七章 DQN算法

7.1 简介

在之前讲到的Q-learning算法中,我们以矩阵的方式建立了一张存储每个状态下所有动作Q值得表格。表格中的每一个动作价值 Q ( s , a ) Q(s,a) Q(s,a)表示在状态 s s s下选择动作 a a a然后继续遵循某一策略预期能够得到的期望回报。然而,这种表格存储动作价值的做法只在环境的状态和动作都是离散的,并且空间都比较小的情况下适用,我们之前进行代码实战的几个环境都是如此(如悬崖漫步)。当状态或者动作数量非常大的时候,这种做法就不适用了。例如,当状态或者动作数量比较大的时候,这种做法就不适用了。例如,当状态是一张RGB图像时,假设图像大小是210X160X3,此时共有 25 6 ( 210 × 60 × 3 ) 256^ {(210×60×3)} 256(210×60×3)种状态,在计算机中存储这个数量级的Q值表格是不现实的,更甚者,当动作或者动作是连续的时候,就有无限个状态动作对,我们更加无法使用这种表格形式来记录各个状态动作对的Q值。

对于这种情况,我们需要用函数拟合的方法来估计Q值,即将这个复杂的Q值表格视作数据,使用一个参数化的函数 Q θ Q_\theta Qθ来拟合这些数据。很显然,这种函数拟合的方法存在一定的精度损失,因此被称为近似方法。我们今天要介绍DQN算法便可以用来解决连续状态下离散动作的问题

7.2 C a r t P o l e CartPole CartPole 环境

以图 7-1 中所示的所示的车杆(CartPole)环境为例,它的状态值就是连续的,动作值是离散的。

在这里插入图片描述

在车杆环境中,有一辆小车,智能体的任务是通过左右移动保持车上的杆竖直,若杆的倾斜度数过大,或者车子离初始位置左右的偏离程度过大,或者坚持时间到达200帧,则游戏结束。智能体的状态是一个维数为4的向量,每一维都是连续的,其动作都是离散的,动作空间大小为2,详情参见表7-1和表7-2.在游戏中每坚持一帧,智能体能获得的分数为1的奖励,坚持时间越长,则最后的分数越高,坚持200帧即可获得最高的分数。

​ 表7-1 Cartpole环境的状态空间

维度意义最小值最大值
0车的位置-2.42.4
1车的速度-InfInf
2杆的角度~ − 41. 8 。 -41.8^。 41.8~ 41. 8 。 41.8^。 41.8
3杆尖端的速度2.-InfInf

​ 表7-2 Cartpole环境的动作空间

标号动作
0向左移动小车
1向右移动小车

7.3 DQN

现在我们想在类似车杆的环境中得到动作价值函数 Q ( s , a ) Q(s,a) Q(s,a),由于状态每一维的值都是连续的,无法使用表格记录,因此一个常见的解决方法便是使用函数拟合(function approximation)的思想。由于神经网络具有强大的表达能力,因此我们可以用一个神经网络来表示函数 Q Q Q。若动作是连续(无限)的,神经网络的输入是状态s和动作 a a a,然后输出一个标量,表示在状态s下采取动作a能获得的价值。若动作是离散(有限)的,除了可以采取动作连续情况下的做法,我们还可以只将状态s输入到神经网络中,使其同时输出每一个动作的Q值。通常DQN(以及Q-learning)只能处理动作离散的情况,因为在函数Q的更新过程中有 m a x a max_a maxa这一操作。假设神经网络用来拟合函数 ω \omega ω的参数,即每一状态s下所有可能动作a的Q值我们都能表示为 Q ω ( s , a ) Q_{\omega}(s,a) Qω(s,a)。我们将用于拟合函数Q函数的神经网络称为Q网络,如图7-2所示。

在这里插入图片描述

那么Q网络的损失函数是什么呢》我们先来回顾一下Q-learning的更新规则(参见5.5节):

Q ( s , a ) < − − Q ( s , a ) + α [ r + γ m a x a ′ ∈ A Q ( s ′ , a ′ ) − Q ( s , a ) ] Q(s,a)<--Q(s,a)+\alpha[r+\gamma \underset{a'\in A} {max} Q(s',a')-Q(s,a)] Q(s,a)<Q(s,a)+α[r+γaAmaxQ(s,a)Q(s,a)]

上述公式用到时序差分(temporal difference,TD)学习目标 r + γ m a x a ′ ∈ A Q ( s ′ , a ′ ) r+\gamma \underset{a'\in A} {max} Q(s',a') r+γaAmaxQ(s,a)来增量式更新 Q ( s , a ) Q(s,a) Q(s,a),也就是说要使 Q ( s , a ) Q(s,a) Q(s,a)和TD目标 r + γ m a x a ′ ∈ A Q ( s ′ , a ′ ) r+\gamma \underset{a'\in A} {max} Q(s',a') r+γaAmaxQ(s,a)靠近。于是,对于一组数据{ ( s i , a i , r i , s i ′ ) {(s_i,a_i,r_i,s'_i)} (si,ai,ri,si)},我们可以很自然的将Q网络的损失函数构造为均方误差的形式:

ω ∗ = a r g   min ⁡ 1 2 N Σ N i = 1 [ Q ω ( s i , a i ) − ( r i + γ   max ⁡ a ′ Q w ( s i , a ) ) ] 2 \omega ^*=arg\ \min \frac{1}{2N}\underset{i=1}{\overset{N}{\varSigma}}\left[ Q_{\omega}\left( s_i,a_i \right) -\left( r_i+\gamma \ \underset{a'}{\max}Q_w\left( s_i,a \right) \right) \right] ^2 ω=arg min2N1i=1ΣN[Qω(si,ai)(ri+γ amaxQw(si,a))]2

至此,我们就可以将Q-learning扩展到神经网络形式——深度Q网络(deep Q network,DQN)算法。由于DQN是离线策略算法,因此我们在收集数据的时候可以使用一个 ϵ − \epsilon- ϵ贪婪策略来平衡探索与利用,将收集到的数据存储起来,在后续的训练中使用。DQN中还有两个非常重要的模块——经验回放目标网络,它们能够帮助DQN取得稳定、出色的性能。

7.3.1 经验回放

在一般的有监督学习中,假设训练数据是独立同分布的,我们每次训练神经网络的时候从训练数据中随机采样一个或若干个数据来进行梯度下降,随着学习的不断进行,每一个训练数据都会被使用多次。在原来的Q-learning算法中,每一个数据只会用来更新一次Q值。为了更好地将Q-learning和深度神经网络结合,DQN算法采用了经验回放方法,具体做法为维护一个回放缓冲区,将每次环境中采样得到的四元组数据(状态、动作、奖励、下一状态)存储到回放缓冲区中,训练Q网络的时候再从回放缓冲区中随机采样若干数据来进行训练。这么做可以起到以下两个作用。

  1. 使样本满足独立假设。在MDP中交互采样得到的数据本身不满足独立假设,因为这一时刻的状态和上一时刻的状态有关。非独立同分布的数据对训练神经网络有很大的影响,会使神经网路拟合到最近训练的数据上。采用经验回放可以打破样本之间的相关性,让其满足独立假设。
  2. 提高样本效率。每一个样本可以被使用多次,十分适合深度神将网络的梯度学习。

7.3.2

DQN算法最终更新的目标是让 Q ω ( s , a ) Q_{\omega}(s,a) Qω(s,a)逼近 r + γ max ⁡ a ′ Q w ( s ′ , a ′ ) r+\gamma {\max}_{a' }Q_w( s',a' ) r+γmaxaQw(s,a),由于TD误差目标本身就包含神经网络的输出,因此在更新网络参数的同时目标也在不断地改变,这非常容易造成神经网络训练的不稳定性。为了解决这一问题,DQN便使用了目标网络的思想:既然训练过程中Q网络的不断更新会导致目标不断发生改变,不如暂时先将TD目标中的Q网络固定住。为了实现这一思想,我们需要利用两套Q网络。

  1. 原来的训练网络 Q ω ( s , a ) Q_{\omega}(s,a) Qω(s,a),用于计算原来的损失函数 1 2 [ Q ω ( s , a ) − ( r + γ max ⁡ a ′ Q ω − ( s ′ , a ′ ) ) ] 2 \frac{1}{2}\left[ Q_{\omega}\left( s,a \right) -\left( r+\gamma \max _{a'}Q_{\omega -}\left( s',a' \right) \right) \right] ^2 21[Qω(s,a)(r+γamaxQω(s,a))]2中的 Q ω ( s , a ) Q_{\omega}(s,a) Qω(s,a)向,并且使用正常梯度下降方法来进行更新。
  2. 目标网络 Q ω − ( s , a ) Q_{\omega^-}(s,a) Qω(s,a),用于计算原先损失函数 1 2 [ Q ω ( s , a ) − ( r + γ max ⁡ a ′ Q ω − ( s ′ , a ′ ) ) ] 2 \frac{1}{2}\left[ Q_{\omega}\left( s,a \right) -\left( r+\gamma \max _{a'}Q_{\omega -}\left( s',a' \right) \right) \right] ^2 21[Qω(s,a)(r+γamaxQω(s,a))]2中的 ( r + γ max ⁡ a ′ Q ω − ( s ′ , a ′ ) ) \left( r+\gamma \max _{a'}Q_{\omega -}\left( s',a' \right) \right) (r+γamaxQω(s,a))项,其中 ω − \omega^- ω表示目标网络中的参数。如果两套网络的参数随时保持一致,则仍为原先不够稳定的算法。为了让更新目标更稳定,目标网络并不会每一步都更新。具体而言,目标网络使用训练网络的一套较旧的参数,训练网络 Q ω ( s , a ) Q_{\omega}(s,a) Qω(s,a)在训练中的每一步都会更新,而目标网络的参数每隔C步才会与训练网络同步一次,即 ω − < − − ω \omega^-<--\omega ω<ω这样做使得目标网络相对于训练网络更加稳定。

综上所述,DQN算法的具体流程如下:

  • 用随机的网络参数 ω \omega ω初始化网络 Q ω ( s , a ) Q_{\omega}(s,a) Qω(s,a)
  • 复制相同的参数 ω − < − − ω \omega^-<--\omega ω<ω来初始化目标网络 Q ω ′ Q_{\omega'} Qω
  • 初始化经验回放池R
  • for序列 e = 1 − − > E e=1-->E e=1>Edo
  • ​ 获得环境初始状态 s 1 s_1 s1
  • for时间步 t = 1 − − > T t=1-->T t=1>Tdo
  • ​ 根据当前网络 Q ω ( s , a ) Q_\omega(s,a) Qω(s,a) ϵ − \epsilon- ϵ贪婪策略选择动作 a t a_t at
  • ​ 执行动作 a t a_t at,获得回报 r t r_t rt,环境状态变为 s t + 1 s_{t+1} st+1
  • ​ 将 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1)存放进回放池R中
  • ​ 若R中的数据足够,从R中采样N个数据 { ( s i , a i , r i , s i + 1 ) } i = 1 , . . . . . . , N \left\{(s_i,a_i,r_i,s_{i+1})\right\}_{i=1,......,N} {(si,ai,ri,si+1)}i=1,......,N
  • ​ 对每个数据,用目标网络计算 y i = r i + γ max ⁡ a Q ω − ( s i + 1 , a ) y_i=r_i+\gamma \max _{a}Q_{\omega -}\left( s_{i+1},a \right) yi=ri+γamaxQω(si+1,a)
  • ​ 最小化目标损失 L = 1 N Σ i ( y i − Q ω ( s i , a i ) ) 2 L=\frac{1}{N}\varSigma _i\left( y_i-Q_{\omega}\left( s_i,a_i \right) \right) ^2 L=N1Σi(yiQω(si,ai))2,以此更新当前网络 Q ω Q_{\omega} Qω
  • ​ 更新目标网络
  • end for
  • end for

7.4 DQN 代码实践

我们采用的测试环境是 CartPole-v0,其状态空间相对简单,只有 4 个变量,因此网络结构的设计也相对简单:采用一层 128 个神经元的全连接并以 ReLU 作为激活函数。当遇到更复杂的诸如以图像作为输入的环境时,我们可以考虑采用深度卷积神经网络。

从 DQN 算法开始,我们将会用到rl_utils库,它包含一些专门为本书准备的函数,如绘制移动平均曲线、计算优势函数等,不同的算法可以一起使用这些函数。为了能够调用rl_utils库,请从本书的GitHub 仓库下载rl_utils.py文件。

import random
import gym
import numpy as np
import collections
from tqdm import tqdm
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
import rl_utils

class ReplayBuffer:
    ''' 经验回放池 '''
    def __init__(self, capacity):
        self.buffer = collections.deque(maxlen=capacity)  # 队列,先进先出

    def add(self, state, action, reward, next_state, done):  # 将数据加入buffer
        self.buffer.append((state, action, reward, next_state, done))

    def sample(self, batch_size):  # 从buffer中采样数据,数量为batch_size
        transitions = random.sample(self.buffer, batch_size)  # 从经验回放池中抽取batch_size个数据
        state, action, reward, next_state, done = zip(*transitions)  # 将经验回放池中的数据组成一个字典  或者列表  再分别赋值给状态  动作 奖励 下一个状态
        return np.array(state), action, reward, np.array(next_state), done

    def size(self):  # 目前buffer中数据的数量
        return len(self.buffer)

class Qnet(torch.nn.Module):
    ''' 只有一层隐藏层的Q网络 '''
    def __init__(self, state_dim, hidden_dim, action_dim):
        super(Qnet, self).__init__()
        self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
        self.fc2 = torch.nn.Linear(hidden_dim, action_dim)

    def forward(self, x):
        x = F.relu(self.fc1(x))  # 隐藏层使用ReLU激活函数
        return self.fc2(x)


class DQN:
    ''' DQN算法 '''
    def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
                 epsilon, target_update, device):
        self.action_dim = action_dim
        self.q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)  # Q网络
        # 目标网络
        self.target_q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)
        # 使用Adam优化器
        self.optimizer = torch.optim.Adam(self.q_net.parameters(), lr=learning_rate) # 降低训练误差
        self.gamma = gamma  # 折扣因子
        self.epsilon = epsilon  # epsilon-贪婪策略
        self.target_update = target_update  # 目标网络更新频率
        self.count = 0  # 计数器,记录更新次数
        self.device = device

    def take_action(self, state):  # epsilon-贪婪策略采取动作

        if np.random.random() < self.epsilon:
            action = np.random.randint(self.action_dim)
        else:
            state = torch.tensor([state], dtype=torch.float).to(self.device)
            action = self.q_net.forward(state).argmax().item()   # item用来增加动作的精度
        return action

    def update(self, transition_dict):
        states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)
        actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)
        rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device)
        next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device)
        dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device)

        q_values = self.q_net(states).gather(1, actions)  # Q值  省略了前向传播的forward函数  输出的是状态对应的每个动作的Q值
        # 下个状态的最大Q值
        max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)
        q_targets = rewards + self.gamma * max_next_q_values * (1 - dones)  # TD误差目标
        dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))  # 均方误差损失函数
        self.optimizer.zero_grad()  # PyTorch中默认梯度会累积,这里需要显式将梯度置为0
        dqn_loss.backward()  # 反向传播更新参数
        self.optimizer.step()

        if self.count % self.target_update == 0:
            self.target_q_net.load_state_dict(self.q_net.state_dict())  # 更新目标网络
        self.count += 1



lr = 2e-3
num_episodes = 500
hidden_dim = 128
gamma = 0.98
epsilon = 0.01
target_update = 10
buffer_size = 10000
minimal_size = 500
batch_size = 64
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")

env_name = 'CartPole-v0'
env = gym.make(env_name)
random.seed(0)
np.random.seed(0)
env.seed(0)
torch.manual_seed(0)
replay_buffer = ReplayBuffer(buffer_size)
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon,
            target_update, device)

return_list = []
for i in range(10):
    with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:
        for i_episode in range(int(num_episodes / 10)):
            episode_return = 0
            state = env.reset()
            done = False
            while not done:
                action = agent.take_action(state)
                next_state, reward, done, _ = env.step(action)
                replay_buffer.add(state, action, reward, next_state, done)
                state = next_state
                episode_return += reward
                # 当buffer数据的数量超过一定值后,才进行Q网络训练
                if replay_buffer.size() > minimal_size:
                    b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)  # 经验池中的状态 动作 奖励  下一个状态
                    transition_dict = {
                        'states': b_s,
                        'actions': b_a,
                        'next_states': b_ns,
                        'rewards': b_r,
                        'dones': b_d
                    }
                    agent.update(transition_dict)
            return_list.append(episode_return)
            if (i_episode + 1) % 10 == 0:
                pbar.set_postfix({
                    'episode':
                    '%d' % (num_episodes / 10 * i + i_episode + 1),
                    'return':
                    '%.3f' % np.mean(return_list[-10:])
                })
            pbar.update(1)




episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('DQN on {}'.format(env_name))
plt.show()

mv_return = rl_utils.moving_average(return_list, 9)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('DQN on {}'.format(env_name))
plt.show()

7.5 以图象为输入的DQN算法

在本书前面章节所述的强化学习环境中,我们都使用非图像的状态作为输入(例如车杆环境中车的坐标、速度),但是在一些视频游戏中,智能体并不能直接获取这些状态信息,而只能直接获取屏幕中的截图。要让智能体和人一样玩游戏,我们需要让智能体学会以图像作为状态时的决策。我们可以利用7.4节的DQN算法,将卷积层加入其网络结构以提取图像特征,最终实现以图象为输入的强化学习。以图象为输入的DQN算法的代码与7.4节的代码的不同之处主要在于Q网络的结构和数据输入。

class ConvolutionalQnet(torch.nn.Module):
    ''' 加入卷积层的Q网络 '''
    def __init__(self, action_dim, in_channels=4):
        super(ConvolutionalQnet, self).__init__()
        self.conv1 = torch.nn.Conv2d(in_channels, 32, kernel_size=8, stride=4)
        self.conv2 = torch.nn.Conv2d(32, 64, kernel_size=4, stride=2)
        self.conv3 = torch.nn.Conv2d(64, 64, kernel_size=3, stride=1)
        self.fc4 = torch.nn.Linear(7 * 7 * 64, 512)
        self.head = torch.nn.Linear(512, action_dim)

    def forward(self, x):
        x = x / 255
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = F.relu(self.fc4(x))
        return self.head(x)

代码报错

env.seed(0)

报错信息:

AttributeError: 'CartPoleEnv' object has no attribute 'seed'

 action = agent.take_action(state)
 
 state = torch.tensor([state], dtype=torch.float).to(self.device)

报错信息为:

ValueError: expected sequence of length 4 at dim 2 (got 0)

在这里插入图片描述

https://github.com/boyu-ai/Hands-on-RL/blob/main/README.md

进不去环境

在我进行pip uninstall gym时显示我没有gym环境,并且我执行完pip install gym==0.18.3时,运行还是报错 我觉得应该是环境的问题,我一观察terminal所在的环境 确实不是我想要进的环境 甚至也不是base环境 是在c盘的什么 环境,之前我也经常进错环境,但是我也不清楚是为什么 ,但是我可以改正呀

之后在右下角在这里插入图片描述
更改了环境,代码才执行成功

在terminal进行库的卸载安装时

一定要观察是否在你选择的环境中

在这里插入图片描述

标黄的地方和右下角应该是一致的 如果不一致就要更改环境 或者重新进如terminal

之后在重新安装gym库

python

deque

collections作为python的内建集合模块,实现了许多十分高效的特殊容器数据类型,即除了python通用的内置容器:dict、list、set和tuple等的替代方案。在IDLE输入help(collections)可查看帮助文档,其中常见的类/函数如下:

名称功能
namedtuple用于创建具有命名字段的tuple子类的factory函数(具名元组)
deque类似 list 的容器,两端都能实现快速 append 和 pop (双端队列)
ChainMap类似 dict 的类,用于创建多个映射的单视图
Counter用于计算 hashable 对象的 dict 子类 (可哈希对象计数)
OrderedDict记住元素添加顺序的 dict 子类 (有序字典)
defaultdictdict 子类调用 factory 函数来提供缺失值
UserDict包装 dict 对象以便于 dict 的子类化
UserList包装 list 对象以便于 list 的子类化
UserString包装 string 对象以便于 string 的子类化

deque是**“double-ended-queue”的缩写(发音是“deck”),意为双端队列**。输入可迭代对象iterable并返回一个新的双端队列对象。若不指定可迭代对象iterable,则返回的双端队列对象为空。

deque可以从队列两端添加(append)和弹出(pop)元素,且两个方向的时间复杂度均为O(1)。虽然list对象也支持类似操作,但list在开头插入insert(0,iterm)或弹出(pop(0))元素的时间复杂度均为O(n)。

若可选参数 maxlen 未指定或为 None,deque 将成为 任意长度 的双端队列。否则, *maxlen* 就是 *限定最大长度*,将构造一个固定大小的双端队列。当 deque 元素已满且有新元素从一端“入队”时,数量相同的旧元素将从另一端“出队” (被移除)。

形式说明
append(x)从deque最右端加入元素x
appendleft()从deque最左端加入元素
extend(iterable)使用可迭代对象iterable中的元素扩展deque右端
extendleft(iterable)使用可迭代对象iterable中的元素扩展deque左端
insert(i, x)在 index=i 的位置插入元素 x (若导致 deque 长度超过 maxlen,引发 IndexError)
pop()弹出 deque 最右端的一个元素 (若无元素引发 IndexError)
popleft()弹出 deque 最左端的一个元素 (若无元素引发 IndexError)
remove(x)移除从左到右找到的第一个 x (若无 x 引发 ValueError)
clear()清空 deque 中的所有元素,使之为空 deque (长度归0)
copy()创建一份当前 deque 的浅拷贝
count(x)计算 deque 中 x 的个数
index(x[, start[, stop]])返回在 [start, stop] 之间从左到右找到的第一个 x 的 index (未找到引发 ValueError)
reverse()将当前 deque 逆序排列,返回 None
rotate(n=1)n 为正数向右循环移动 n 步,n 为负数向左循环移动 n 步,若 deque 非空,向右循环移动 1 步等价于 d.appendleft(d.pop()),向左循环移动 1 步等价于 d.append(d.popleft())

此外,deque 还支持索引、迭代、清洗 以及 len(d)、reversed(d)、copy.copy(d)、copy.deepcopy(d) 等内建函数通用操作,支持用 in 成员测试操作符。deque 两端的索引增、删、改复杂度是 O(1),在中间的复杂度则比 O(n) 略低。从 Python 3.5 开始 deque 还支持 add()、mul()、imul() 等。

python random.sample()

sample()是Python中随机模块的内置函数,可返回从序列中选择的项目的特定长度列表,即列表,元组,字符串或集合。用于随机抽样而无需更换

语法random.sample(sequence, k)

参数
sequence:可以是列表,元组,字符串或集合。
k:一个整数值,它指定样本的长度。

返回:从序列中选择的k长度新元素列表

from random import sample 
  
# Prints list of random items of given length 
list1 = [1, 2, 3, 4, 5]  
  
print(sample(list1,3))

输出: [2, 3, 5]

torch.nn.Linear()

1. 基本定义:

torch.nn.Linear(in_features, # 输入的神经元个数
           out_features, # 输出神经元个数
           bias=True # 是否包含偏置
           )

2. 参数解释

1.in_fetaure:该参数代表输入的最后一维的通道个数,通俗来说就是X矩阵的列数,因为X是输入的参数,而X维度为2,其最后一维就是列,所以in_feature在二维中就是矩阵X的列数

2.out_feature:该参数代表输出的最后一维的通道个数,通俗来说就是Y矩阵的列数,因为Y是输出的参数,而Y维度为2,其最后一维就是列,所以out_feature在二维中就是矩阵Y的列数

3.bias:bias就是整个线性回归方程的偏置量,使用时有True和False两种选择

3.作用说明

1.作用概述

从名称就可以看出来,nn.Linear表示的是线性变换,原型就是初级数学里学到的线性函数:y=kx+b 在矩阵中的线性变换就是

y = x A T + b y=xA^T+b y=xAT+b不过在深度学习中,变量都是多维张量,乘法就是矩阵乘法,加法就是矩阵加法,因此nn.Linear()运行的真正的计算就是: A T A^T AT的大小为(in_feature,out_feature) 但是,nn.linear(x,y)其权重的shape为(y,x),所以输入的矩阵与其相乘时,用torch.t求了nn.linear的转置,torch.mm是数学上的两个矩阵相乘

output = weight @ input + bias
@:在python中代表矩阵乘法
input:表述输入的Tensor,可以有多个维度
weights:表示可学习的权重,shape=(output_feature,in_feature)
bias:表示可学习的偏置,shape=(output_feature)
in_feature:nn.Linear初始化的第一个参数,即输入Tensor最后一维的通道数
out_feature:nn.Linear初始化第二个参数,即输出Teansor最后一维的通道数
output:表示输入的Tensor,可以有多个维度
2.nn.Linear()的使用

常见的头文件:import torch.nn as nn

nn.Linear()的初始化:

nn.Linear(in_feature,out_feature,bias)

in_feature :int型,在forward中输入Tensor最后一维的通道数

out_feature:int型,在forward中输出Tensor最后一维的通道数

bias:bool型,Linear线性变换中是否添加bias偏置

nn.Linear()的执行(即执行forward函数)

out=nn.Linear(input)

input:表示输入的Tensor,可以有多个维度

output:表示输出的Tensor,可以有多个维度

举例:

2维的Tensor

m = nn.Linear(20, 40)
input = torch.randn(128, 20)
output = m(input)
print(output.size())  # [(128,40])

4维的Tensor

m = nn.Linear(128, 64)
input = torch.randn(512, 3,128,128)
output = m(input)
print(output.size())  # [(512, 3,128,64))

Linear()函数通常用于设置网络中的全连接层

import torch

x = torch.randn(8, 3)  # 输入样本
fc = torch.nn.Linear(3, 5)  # 20为输入样本大小,30为输出样本大小
output = fc(x)
print('fc.weight.shape:\n ', fc.weight.shape, fc.weight)
print('fc.bias.shape:\n', fc.bias.shape)
print('output.shape:\n', output.shape)

ans = torch.mm(x, torch.t(fc.weight)) + fc.bias  # 计算结果与fc(x)相同
print('ans.shape:\n', ans.shape)

print(torch.equal(ans, output))

输出结果为:

fc.weight.shape:
  torch.Size([5, 3]) Parameter containing:
tensor([[-0.1878, -0.2082,  0.4506],
        [ 0.3230,  0.3543,  0.3187],
        [-0.0993, -0.0028, -0.1001],
        [-0.0479,  0.3248, -0.4867],
        [ 0.0574,  0.0451,  0.1525]], requires_grad=True)
fc.bias.shape:
 torch.Size([5])
output.shape:
 torch.Size([8, 5])
ans.shape:
 torch.Size([8, 5])
True

Process finished with exit code 0

torch.tensor()

用来构建矩阵

torch.tensor 是一个多维矩阵,其中包含同种类型的元素(如整数或浮点数)。它是 PyTorch 库的核心类,几乎所有的计算操作都可以在 tensor 上进行。

torch.gather()

定义:从原tensor中获取指定dim和指定index的数据

看到这个核心定义,我们很容易想到gather()基本想法其实就类似从完整数据中按索引取值般简单,比如下面从列表中按索引取值

lst = [1, 2, 3, 4, 5]
value = lst[2]  # value = 3
value = lst[2:4]  # value = [3, 4]

上面的取值例子是取单个值或具有逻辑顺序序列的例子,而对于深度学习常用的批量tensor数据来说,我们的需求可能是选取其中多个且乱序的值,此时gather()就是一个很好的tool,它可以帮助我们从批量tensor中取出指定乱序索引下的数据,因此其用途如下

用途:方便从批量tensor中获取指定索引下的数据,该索引是 高度自定义化的,可乱序的

官方文档

在这里插入图片描述

根据官方文档的显示是根据给出的index的索引坐标来确定要寻找的坐标,然后根据dim来确定将哪个位置坐标换位index中的数字,剩余维度的位置坐标保持不变。 并且input的shape应该是和index的shape保持一致。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

24024159.png?origin_url=D%3A%2Ftypora1%2Fimage-20231121113008861.png&pos_id=img-zO1WpNSg-1700621534798)

到这里再回去看官方文档是不是就能看懂了!!!

【PyTorch】Torch.gather()用法详细图文解释

实战

import torch
import torch.nn.functional as F

a = torch.arange(0,16).view(4,4)
print(a)
index = torch.tensor([[0,1,2,3]]) 
print(index)
print(a.gather(0, index))
print(a.gather(1, index))
index = torch.tensor([[3, 2, 1, 0]])
print(index)
tensor_1 = a.gather(0, index)
print(tensor_1)

tesnor_2 = a.gather(1,index)
print(tesnor_2)

输出:

tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11],
        [12, 13, 14, 15]])
tensor([[0, 1, 2, 3]])
tensor([[ 0,  5, 10, 15]])
tensor([[0, 1, 2, 3]])
tensor([[3, 2, 1, 0]])
tensor([[12,  9,  6,  3]])
tensor([[3, 2, 1, 0]])

三维实例:

import torch
random_seed = 200
torch.manual_seed(random_seed)
input = torch.randint(0, 100, (2, 3, 4))
print("input:")
print(input)

index = torch.randint(0, 2, (2, 1, 2))
print("index:")
print(index)

output = input.gather(0, index)
print("output:")
print(output)

# 控制台输出
input:
tensor([[[62, 29, 76, 60],
         [82, 27, 88, 11],
         [57, 50, 71,  9]],

        [[33, 71, 66, 34],
         [20, 81,  3, 39],
         [15, 33, 19, 89]]])
index:
tensor([[[0, 1]],

        [[1, 0]]])
output:
tensor([[[62, 71]],

        [[33, 29]]])

random.sample()

sample()是Python中随机模块的内置函数,可返回从序列中选择的项目的特定长度列表,即列表,元组,字符串或集合。用于随机抽样而无需更换。

语法random.sample(sequence, k)

参数
sequence:可以是列表,元组,字符串或集合。
k:一个整数值,它指定样本的长度。

返回:从序列中选择的k长度新元素列表。

zip 函数

  • zip() 函数来可以把 2 个或多个列表合并,并创建一个元组对的列表,元组对的数量以合并列表的最短长度为准
  • python 3中 zip 方法合并列表后生成的是 zip 对象,使用 list 方法可以将其变成列表,使用 dict 方法可以将其变成字典:
>>> l1 = [ 1, 2, 3 ]
>>> l2 = [ 'x', 'y', 'z']
>>> l3 = [ 'x', 'y' ]
>>> zip(l1, l2)
<zip object at 0x031D6828>
>>> print(list(zip(l1, l2)))
[(1, 'x'), (2, 'y'), (3, 'z')]
>>> print(list(zip(l1, l3)))
[(1, 'x'), (2, 'y')]
>>> print(dict(zip(l1,l3)))
{1: 'x', 2: 'y'}
  • 实际上 zip 方法支持所有可迭代对象(字符串、列表、元祖、字典),而不仅仅是列表。利用这个特性,可以很容易创建各种字典,包括很复杂的字典。

  • 来看 2 个经典例子,如下所示,注意 zip 对象支持直接遍历,不需要先转成 list 或 dict:

  • >> > l1 = [1, 2, 3]
    >> > str1 = "abc"
    >> > print(dict(zip(l1, str1)))
    {1: 'a', 2: 'b', 3: 'c'}
    >> > name = ["John", "Jim", "Lucy"]
    >> > year = [1983, 1985, 1995]
    >> > birth_year = dict(zip(name, year))
    >> > print(birth_year)
    {'John': 1983, 'Jim': 1985, 'Lucy': 1995}
    >> > for name, year in zip(name, year):
        print("{} - {}".format(name, year))
    
    John - 1983
    Jim - 1985
    Lucy - 1995
    
    

    利用 zip(*some_list) 方法可以实现元组列表的反向解压,如下所示:

    • >>> l1 = [("John", 1995), ("Lucy", 2000), ("Max", 1985)]
      >>> name, year = zip(*l1)
      >>> print(name)
      ('John', 'Lucy', 'Max')
      >>> print(year)
      (1995, 2000, 1985)
      >>> l2 = dict(l1)
      >>> print(l2)
      {'John': 1995, 'Lucy': 2000, 'Max': 1985}
      >>> name1, year1 = zip(*l2)
      Traceback (most recent call last):
        File "<pyshell#6>", line 1, in <module>
          name1, year1 = zip(*l2)
      ValueError: too many values to unpack (expected 2)
      
      
      • 注意 unzip 只支持元组列表,不支持 dict 直接解压。

torch.argmax()

这个函数是用来选择最大值的索引序号的,并不是选择最大值的 那它是怎么选取最大值序号呢

两个维度的张量使用torch.argmax()函数

import torch
x = torch.rand(2, 3)
print(x)
y0 = torch.argmax(x, dim=0)
print(y0)
y1 = torch.argmax(x, dim=1)
print(y1)

运行结果“

在这里插入图片描述

1)dim=0时,返回每一列最大值的索引

2)dim=1时,返回每一行最大值的索引

三维张量中使用torch.argmax()函数

1)dim=0时,返回每个元素在各个通道最大值的索引号

2)dim=1时,返回n排每一列最大值的索引(n是通道数)

3)dim=2时,返回n排每一排最大值的索引(n是通道数)

Adam优化算法

自适应矩估计是一种计算每个参数或权重的自适应学习率的方法

Adam是梯度下降优化算法的扩展,当处理涉及大量数据或大量参数时,该算法非常有效。直观地说,它是带动量的梯度下降算法和RMSProp算法的结合。

Adam旨在加速优化过程,例如减少达到最优值所需的迭代次数,或提高优化算法的能力,例如获得更好的最终结果。

有时Adam算法可能由于⽅差控制不良⽽发散

1、优化算法对于深度学习的意义

​ 深度学习中的优化问题通常指的是:寻找模型上的一组参数θ,它能显著地降低(最小化)代价函数J(θ),通常也有采取最大化问题转化为最小化问题再优化的方法。

​ 优化算法直接影响模型的训练效率。

2、优化算法与深度学习的关系

优化算法的目的:降低训练误差;

深度学习的目的:降低泛化误差;

因此深度学习需要优化算法+应对过拟合。

的索引(n是通道数)

3)dim=2时,返回n排每一排最大值的索引(n是通道数)

Adam优化算法

自适应矩估计是一种计算每个参数或权重的自适应学习率的方法

Adam是梯度下降优化算法的扩展,当处理涉及大量数据或大量参数时,该算法非常有效。直观地说,它是带动量的梯度下降算法和RMSProp算法的结合。

Adam旨在加速优化过程,例如减少达到最优值所需的迭代次数,或提高优化算法的能力,例如获得更好的最终结果。

有时Adam算法可能由于⽅差控制不良⽽发散

1、优化算法对于深度学习的意义

​ 深度学习中的优化问题通常指的是:寻找模型上的一组参数θ,它能显著地降低(最小化)代价函数J(θ),通常也有采取最大化问题转化为最小化问题再优化的方法。

​ 优化算法直接影响模型的训练效率。

2、优化算法与深度学习的关系

优化算法的目的:降低训练误差;

深度学习的目的:降低泛化误差;

因此深度学习需要优化算法+应对过拟合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值