Simon Shi的小站

人工智能,机器学习, 强化学习,大模型,自动驾驶

0%

Env 创建

1
2
3
conda create -n XXX python=3.8
conda create -n XXX --clone oldXXX
conda remove -n oldXXX --all

修改环境名字

1
2
3
4
5
6
直接修改conda环境下的目录名即可
# conda environments:
#
base /root/anaconda3
torch_1.4.0_cu100 /root/anaconda3/envs/torch_1.4.0_cu100
torch_1.4.1 /root/anaconda3/envs/torch_1.4.1

一、镜像

1.镜像源添加方法

首先是一些常用命令,帮你诊断目前你的conda源的情况,如果是新装的conda,可以不用管。
第一步:查看并还原默认镜像源

1.1 首先,看一下目前conda源都有哪些内容

conda info

1.2 然后,删除并恢复默认的conda源

1
conda config --remove-key channels 

下面是几个常用命令:后面会用到,这里不用管

1.3 添加指定源

1
conda config --add channels *(*指代你要添加的源)

1.4 设置安装包时,显示镜像来源,建议显示

1
conda config --set show_channel_urls yes 

1.5 删除指定源

1
conda config --remove channels *(*代表你要删除的源)

2.找到你要用的源

首先,我们进入国内几家镜像源的网站(这里以清华园为例,基本上可以涵盖所有镜像)
清华镜像源 一般够用 :https://mirrors.tuna.tsinghua.edu.cn/

中科大源https://mirrors.ustc.edu.cn/

常用的其他源汇总

1
2
3
4
5
6
#为了方便大家快速添加,我们把conda自身默认的镜像源的替换代码,写好放在这里,方便大家直接替换使用,大家逐条复制添加即可,下述链接更新时间为2022.3.10,可以通过前面的方法先判断一下镜像源对不对:
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
conda config --set show_channel_urls yes

二、Conda Env环境(linux)

环境备份

1
2
3
4
5
6
7
8
9
10
路径:<user home>/.conda/envs/

导出环境备份:
conda env export --name your_env_name > environment.yml

导出环境备份(批量):
for env in $(conda env list | awk '{print $1}' | tail -n +3); do conda env export --name $env > ~/.conda/envs/$env.yml; done

重建环境
conda env create -f environment.yml

2.2 在Linux环境下修改Anaconda的默认虚拟环境安装位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1 check
conda info

package cache : /home/sxw/anaconda3/pkgs
/home/sxw/.conda/pkgs
envs directories : /home/sxw/anaconda3/envs
/home/sxw/.conda/envs
# 2 change
conda config --add envs_dirs /home/username/Software/anaconda3/
or
sudo vim ~/.condarc
pkgs_dirs:
- /dataraid/apps/appdata/pkgs
envs_dirs:
- /dataraid/apps/appdata/envs

# 4 check

Windows Env 配置

1
2
3
4
5
6
7
8
9
10
11
12
channels:
- https://mirrors.aliyun.com/anaconda/miniconda/
- defaults
- https://mirrors.ustc.edu.cn/anaconda/pkgs/main/
- https://mirrors.ustc.edu.cn/anaconda/pkgs/free/
- https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/
- https://mirrors.ustc.edu.cn/anaconda/cloud/pytorch/
ssl_verify: true
envs_dirs:
- D:\AppData\Anaconda\envs
pkgs_dirs:
- D:\AppData\Anaconda\pkgs

三、PIP镜像

1
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

1、永久使用pip源 此时,我们可以把源改为清华源,具体操作如下:

1
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

如果set多次,似乎只能保存最后一次set的镜像源。

2、临时更换pip源 如果仅是临时更换可以使用以下命令

1
2
3
4
5
pip install -r requirement.txt
pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install torch -i http://mirrors.aliyun.com/pypi/simple/

此时,我们再用pip安装软件就会比较快了

3、查看pip源

pip config list

4、删除pip源

pip config unset global.index-url

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[global]
no-cache-dir = true
# index-url = https://pypi.org/simple
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
            
extra-index-url =
https://pypi.ngc.nvidia.com
trusted-host = mirrors.aliyun.com
pypi.ngc.nvidia.com


[global]
#index-url = http://mirrors.aliyun.com/pypi/simple/
trusted-host = mirrors.aliyun.com
pypi.ngc.nvidia.com
no-cache-dir = true
extra-index-url =
https://pypi.ngc.nvidia.com


root@ubuntu8125:~# pip config list
global.extra-index-url='\nhttps://pypi.ngc.nvidia.com'
global.index-url='https://pypi.org/simple'
global.no-cache-dir='true'
global.trusted-host='\npypi.ngc.nvidia.com'



/etc/pip.conf

[global]
no-cache-dir = true
# index-url = https://pypi.org/simple
index-url = http://mirrors.aliyun.com/pypi/simple/
extra-index-url = https://pypi.ngc.nvidia.com
trusted-host = mirrors.aliyun.com
pypi.ngc.nvidia.com

/root/.pip/pip.conf

/root/anaconda3/pip.conf

[global]
#index-url = http://mirrors.aliyun.com/pypi/simple/
trusted-host = mirrors.aliyun.com
pypi.ngc.nvidia.com
no-cache-dir = true
extra-index-url =
https://pypi.ngc.nvidia.com

Pip多个源头:

1
2
3
4
5
6
7
8
no-cache-dir = true
extra-index-url =
https://pypi.ngc.nvidia.com
        https://mirrors.aliyun.com/pypi/simple
trusted-host =
pypi.ngc.nvidia.com mirrors.aliyun.com
index-url =
        https://pypi.org/simple

book 深度强化学习:学术前沿 华章书院电子书

zhihu-Torch在倒立摆(CartPole)游戏中实现强化学习

  • 算法:DDQN、Duelling Network以及优先经验回放

CSDN tensorflow2.0 实现 DQN

CSND 深度强化学习-DQN算法原理与代码

DDQN与DQN算法用tensorflow2.0实现

github DQN Pytorch

一些讨论:

  • GAN是RL理论的一种实现。

  • RL是一个理论框架,GAN是在RL理论上开发的一种模型训练方法,但GAN更高明的一点是,生成式的网络和判别网络互为Environment,而RL则定义了机器根据环境的奖励后改变模型,然后来决定下一步action,再得到奖励来改变模型。所以GAN可以看成是一种双向强化学习。

  • 正强化学习有环境学策略,逆强化学习就是反过来学环境

三维可视化助你直观理解DQN算法[DQN理论篇] zhihu

[TOC]

install

https://pytorch.org/get-started/previous-versions/

https://pytorch.org/tutorials/beginner/saving_loading_models.html#save-load-entire-model

python库 https://download.pytorch.org/whl/cu101/torch_stable.html

example-app.cpp

1
2
3
4
5
6
7
#include <torch/torch.h>
#include <iostream>

int main() {
torch::Tensor tensor = torch::rand({2, 3});
std::cout << tensor << endl;
}

Cmakelists.xml

1
2
3
4
5
6
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(example-app)
find_package(Torch REQUIRED)
add_executable(example-app example-app.cpp)
target_link_libraries(example-app "${TORCH_LIBRARIES}")
set_property(TARGET example-app PROPERTY CXX_STANDARD 11)

1| pth-2-pt

官方jit文档

https://pytorch.org/docs/1.5.1/jit.html

https://pytorch.org/docs/stable/generated/torch.jit.trace_module.html

  • 模型里面要严格遵守函数在init初始化,forward来使用

    看下面这个例子:

  • 对比torch.jit.trace功能,此处foward函数里面有依赖于输入数据的控制流,故必须使用torch.jit.script功能。

1
2
3
torch.jit.script(obj)
torch.jit.trace(obj, (args...))
torch.jit.trace_module()

torch.jit.trace:

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

# 载⼊预训练的型
model = models.squeezenet1_1(pretrained=False)
model.classifier[1] = nn.Conv2d(in_channels=512, out_channels=2, kernel_size=(1, 1), stride=(1, 1))
model.num_classes = 2
model.load_state_dict(torch.load('./model/model_squeezenet_utk_face_20.pth', map_location='cpu'))
model.eval()
print(model)
# model = models.resnet18(pretrained=True)
# print(model)

example = torch.rand(1, 3, 224, 224)
traced_script_module = torch.jit.trace(model, example)

output = traced_script_module(torch.ones(1, 3, 224, 224))
print(output)
# ----------------------------------
traced_script_module.save("./model/model_squeezenet_utkface.pt")

2| c++ load pt

由于涉及到图像的加载与处理,本⼈使⽤opencv进⾏读取和处理。
Tips:
训练过程中,采⽤PIL.Iamge加载图像(3通道 RGB),然后Resize到224 x 224⼤⼩, 之后再进⾏ToTensor。因此使⽤C++ libTorch时候也需要按照上述过程对图像进⾏预处理。

  1. cv::imread()默认读取为三通道BGR,需要进⾏B/R通道交换,这⾥采⽤cv::cvtColor() 实现。
  2. 缩放cv::resize() 实现。
  3. opencv读取的图像矩阵存储形式:H x W x C, 但是pytorch中 Tensor的存储为:N x C x H x W, 因此需要进⾏变换,就是np.transpose()操作,这⾥使⽤tensor.permut({2,0,1})实现,效果是⼀样的。
  4. 数据归⼀化,采⽤tensor.div(255)实现。
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <torch/script.h> // One-stop header.
#include <opencv2/opencv.hpp>
#include <iostream>
#include <memory>
//https://pytorch.org/tutorials/advanced/cpp_export.html
string image_path = "/home/weipenghui/Project-dev/Cpp/testLibTorch2/image";

int main(int argc, const char* argv[]) {
// Deserialize the ScriptModule from a file using torch::jit::load().
std::shared_ptr<torch::jit::script::Module> module = torch::jit::load("model/model_squeezenet_utkface.pt");
assert(module != nullptr);
std::cout << "ok\n";

//输⼊图像
auto image = cv::imread(image_path +"/"+ "7.jpg",cv::ImreadModes::IMREAD_COLOR);
cv::Mat image_transfomed;
cv::resize(image, image_transfomed, cv::Size(224, 224));
cv::cvtColor(image_transfomed, image_transfomed, cv::COLOR_BGR2RGB);

// 图像转换为Tensor
torch::Tensor tensor_image = torch::from_blob(image_transfomed.data, {image_transfomed.rows, image_transfomed.cols,3},torch::kByte);
tensor_image = tensor_image.permute({2,0,1});
tensor_image = tensor_image.toType(torch::kFloat);
tensor_image = tensor_image.div(255);
tensor_image = tensor_image.unsqueeze(0);

// ⽹络前向计算
// Execute the model and turn its output into a tensor.
at::Tensor output = module->forward({tensor_image}).toTensor();
auto max_result = output.max(1,true);
auto max_index = std::get<1>(max_result).item<float>();
if (max_index == 0){
cv::putText(image, "male", cv::Point(50, 50), 1, 1,cv::Scalar(0, 255, 255));
}else{
cv::putText(image, "female", cv::Point(50, 50), 1, 1,cv::Scalar(0, 255, 255));
}
cv::imwrite("./result7.jpg", image);

//cv::imshow("image", image);
//cv::waitKey(0);
// at::Tensor prob = torch::softmax(output,1);
// auto prediction = torch::argmax(output, 1);
//
// auto aa = prediction.slice(/*dim=*/0, /*start=*/0, /*end=*/2).item();
//
// std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/2) << '\n';
// std::cout << prob.slice(/*dim=*/1, /*start=*/0, /*end=*/2) << '\n';
//

3| CMakeList

1
2
3
4
5
6
7
8
9
10
11
12
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(custom_ops)
#include_directories(./opencv-3.4.3/build/install_cv/include)
# set(CMAKE_PREFIX_PATH "./opencv-3.4.3/build/install_cv")
find_package(OpenCV REQUIRED)
set(CMAKE_PREFIX_PATH
./libtorch
./opencv-3.4.3/build/install_cv)
find_package(Torch REQUIRED)
add_executable(example-app main.cpp)
target_link_libraries(example-app ${TORCH_LIBRARIES} ${OpenCV_LIBS})
set_property(TARGET example-app PROPERTY CXX_STANDARD 11)

参考资料:

1、环境配置Pytorch Offical libtorch make demo

2、 模型转换 model To torchScript

3、模型调用LOADING A TORCHSCRIPT MODEL IN C++

C++ 调用libtorch,推理模型,very全面

VS2017 C++调用torch模型——libtorch

libtroch部署之torch.jit.script Module踩坑之旅

Demos

行人重识别PCB

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <torch/torch.h>
#include <torch/script.h>

#include <opencv2/opencv.hpp>
#include <vector>

int main()
{

torch::jit::script::Module module = torch::jit::load("pt模型路径", torch::kCUDA); //模型加载到cuda上
std::cout << "模型加载成功" << std::endl;
torch::NoGradGuard no_grad_guard;

cv::Mat image = cv::imread("1.jpg");

//图片预处理,保证跟pytorch训练时的预处理一样的
cv::resize(image, image, cv::Size(128, 384));
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
//输入数据移到cuda上
torch::Tensor img_tensor = torch::from_blob(image.data, { 1, image.rows, image.cols, 3 }, torch::kByte).to(torch::kCUDA);
img_tensor = img_tensor.permute({ 0, 3, 1, 2 });
img_tensor = img_tensor.toType(torch::kFloat);
img_tensor = img_tensor.div(255);
img_tensor[0][0] = img_tensor[0][0].sub_(0.485).div_(0.229);
img_tensor[0][1] = img_tensor[0][1].sub_(0.456).div_(0.224);
img_tensor[0][2] = img_tensor[0][2].sub_(0.406).div_(0.225);

std::vector<torch::jit::IValue> inputs;
inputs.push_back(img_tensor);
torch::Tensor output = module.forward(inputs).toTensor();

torch::Tensor features = output.contiguous().view({ 1, -1 });

torch::Tensor fnorm = features.norm(2, 1, false);

torch::Tensor norm_feature = features.div(fnorm.unsqueeze(1)).to(torch::kCPU);

std::vector<float> vec_feature_ouput(norm_feature.data<float>(), norm_feature.data<float>() + norm_feature.numel());
std::cout << "\n" << std::endl;
}

Contents:

[TOC]

Naikai RL

1、基本原理,概念

依赖模型:

  • 基于模型的强化学习算法:用数据先学习系统模型,然后基于模型得到最优策略

  • 无模型的强化学习算法:直接通过交互数据得到最优策略

策略更新方法:

  • 基于值函数的强化学习,
  • 基于直接策略搜索的强化学习,
  • Actor-Critic的方法。

根据回报函数是否已知分为:正向强化学习和逆向强化学习

  • 正向强化学习:从回报(reward)学到最优策略

  • 逆向强化学习:从专家示例中学到回报函数

根据任务大小和多少分为:分层强化学习、元强化学习、多智能体强化学习、迁移学习等

发展趋势

\1. 贝叶斯强化学习

​ 融合推理能力,可解决POMDP问题

\2. 分层强化学习

​ 解决大规模学习问题

\3. 元强化学习

​ 解决对任务学习

\4. 多智能体强化学习

​ 博弈:合作,竞争,混合

路线图

强化学习课程的路线图

\1. 搞清楚马尔科夫决策过程的概念

\2. 抓住强化学习的基本迭代过程:策略评估和策略改善

\3. 掌握强化学习最常用的两种方法:基于值函数的方法和基于直接策略搜索的方法

\4. 强化学习的其他方法:AC框架,基于模型的强化学习,基于记忆的强化学习等等

置信度算法

多臂赌博机:UCB https://blog.csdn.net/chenxy_bwave/article/details/121715210

2、马尔可夫决策过程

有限马尔可夫决策问题的三种基本方法:

  • 动态规划,
  • 蒙特卡洛方法
  • 时序差分学习。

马尔科夫性

马尔科夫性: 系统的下一个状态只与当前状态有关,与以前状态无关。
定义:一个状态St是马尔科夫的,当且仅当:

$$
𝑃 [𝑆_{𝑡+1 }| 𝑆_𝑡] = 𝑃 [𝑆{𝑡+1}|𝑆_1, ⋯ , 𝑆_𝑡]
$$

  1. 当前状态蕴含所有相关的历史信息
  2. 一旦当前状态已知,历史信息将会被抛弃

马尔可夫决策过程(MDP)

MDP:带有决策和回报的马尔科夫过程:
定义:马尔科夫决策过程由元组: $(𝑆, 𝐴, 𝑃, 𝑅, \gamma)$ 描述S为有限的状态集,A为有限的行为集,P为状态转
移概率,R为回报函数,𝛾为折扣因子 ;

随机变量 s’,r的分布由条件概率定义:

$$
p(s’, r| s,a) = Pr{ S_t=s’, R=r|S_{t-1}=s, A_{t-1} = a }
$$

值函数与策略

状态s的好坏:值函数 V(s ) (状态的函数)。

值函数跟策略有关,不同的策略对应不同的值函数

策略的定义:

一个策略 𝜋是给定状态s时,动作集上的一个分布:

$$
\pi(𝑎|𝑠) = 𝑝[𝐴_𝑡 = 𝑎 | 𝑆_𝑡 = 𝑠]
$$

策略的定义

image-20220514220120535 image-20220514220139656

值函数的定义

image-20220514222618432

马尔科夫决策过程:贝尔曼方程

$$
v_{\pi}(s) = \sum_{a\in A} \pi(a|s)(R_s^a + r\sum_{s’\in S} P_{ss’}^a v_{\pi}(s’) )
$$

image-20220514230945730

最优价值函数和最优状态-动作值函数

image-20220514231308060

3、动态规划到强化学习

单阶段决策:数学规划

多阶段决策:动态规划

动态规划的本质:将多阶段决策问题通过贝尔曼方程转化为多个单阶段的决策问题

离散贝尔曼方程:

连续贝尔曼方程:

微分动态规划方法

DP -> RL

控制领域集中于连续高维问题

最优控制:模型已知,立即回报解析地给出,状态空间小。

近似动态规划:利用机器学习方法学习值函数,解决维数灾难

计算机领域:集中于离散大规模问题

以马尔科夫决策过程为基础,强化学习 无模型,回报函数不解,状态空间无穷

动态规划: 策略评估(估计值函数) ,策略改进

所有强化学习算法都可以看成是动态规划算法,只是用更少的计算,并且没有假设完美模型

策略评估(Prediction)

image-20220515011248768 image-20220515011317445

策略改进

image-20220515011413638 image-20220515011510658

值函数迭代

image-20220515011034529

4、MC & TD

image-20220515011639271

值函数:

$$
v_{\pi}(s) = E_{\pi} [G_t|S_t=s]= E_{\pi}[\sum_{k=0}^{\infty} r^kR_{t+k+1} | S_t=s]
$$

状态-行为值函数:

$$
q_{\pi} = \sum_{a\in A} \pi(a|s)\Big(R_s^a + r\sum_{s’ \in S} P_{ss’}^a v_{\pi}(s’)\Big)
$$

RL_Sutton

2、Multi-armed Bandits

3、Finite Markov Decision Processes

4、DP

5、MC

5.3 MC (ES)

  • On-Policy

5.4 On-Policy

On-policy methods attempt to evaluate or improve the policy that is used to make decisions;

off-policy methods evaluate or improve a policy different from that used to generate the data.

基于experience replay的方法基本上都是off-policy的。

5.5 Off-policy

target policy : to learned

behavior policy : to generate behavior

  • 行为策略(behavior policy):agent与environment交互使用的策略,用来产生数据来改进agent的行为。因为需要有探索性,这个策略是随机的。
  • 目标策略(target policy): 学成以后用于应用的策略。这个时候agent根据这个策略来采取行动。

on-policy 是Off-policy的一种特例(same)

5.6 Incremental Implementation

  • b : is the behavior policy

  • $\pi$ : target policy

  • state-transition概率p=$\frac{\pi}{b}$

6、TD

TD(0)

Sarsa(on-policy TD)

TD(0)学习的是状态价值函数V(s),这里因为(),所以需要学习动作价值函数Q(s,a);

五元组$(S_t, A_t, R_{t+1}, S_{t+1}, A_{t+1})$

Q-learning (off-policy TD)

Double Q-learning

7、n-step Bootstrapping

7.1 n-step TD

各个算法的更新目标(回报)

$$
\begin{align*}
& MC: G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + … + \gamma^{T-t-1} R_T \

& TD(0): G_{t:t+1} = R_{t+1} + \gamma V(S_{t+1}) \
& \gamma V(S_{t+1})= \gamma R_{t+2} + \gamma^2 R_{t+3} + … + \gamma^{T-t-1} R_T\

& TD(2): G_{t:t+1} = R_{t+1} + \gamma R_{t+2}+ \gamma^2 V(S_{t+2}) \

& TD(n): G_{t:t+n} = R_{t+1} + \gamma R_{t+2}+ …
+ \gamma^{n-1} R_{t+n} + \gamma^{n} V_{t+n-1}(S_{t+n}) \tag{7.1}
\end{align*}
$$

n-step Sarsa

n-step Sarsa(Off-policy)

n-step Tree Backup

8、 Planning and Learning with Tabular Methods

9 On-Policy Prediction

9.1 Value-function Approximation

$$
\begin{align*}
& MC: S_t \rightarrow G_t \
& TD(0): S_t \rightarrow R_{t+1} + \gamma\hat{v}(S_{t+1}, W_t) \
& TD: S_t \rightarrow G_{t:t+n} \
& DP: S_t \rightarrow E_\pi[R_{t+1} + \gamma\hat{v}(S_{t+1}, W_t) | S_t = s]
\end{align*}
% & 左对齐
$$

9.2 The Prediction Objective

预测目标: Mean Squared Value Error 均方差

9.3 Stochastic-gradient and Semi-gradient TD(0) Methods

随机梯度与半梯度方法

9.4 Linear Methods(n-step Semi-Graditent TD)

n-step Semi-Graditent TD

9.5 Feature Construction for Linear Methods

Polynomials 多项式

Radial Basis Functions 径向基函数

    与其说 ,特征0,1;不如说是区间[0,1]之间的任何东西,反应特征存在的不同程度;

RBF特征采用高斯表示,当前状态s和中心(原型)状态ci之间的举例表示;

9.7 ANN (非线性函数逼近)

9.8 LSTD

Least-Squares TD

9.11 Looking Deeper at On-Policy Learning : Interest and Emphasis

(9.15)引入强调值Mt

10 On-Policy Control

10.1 Episodic Semi-gradient Control

10.2 Semi-gradient n-step Sarsa

10.3 Average Reward: A New Problem Setting for Continuing Tasks

10.4 Deprecating the Discounted Setting

10.5 Differential Semi-gradient n-step Sarsa

11 Off-Policy Methods

12 Eligibility Traces 资格迹

12.1 $\gamma 回报$

参数化的函数逼近:

$$
G_{t:t+n} = R_{t+1} + \gamma R_{t+2}+ …
+ \gamma^{n-1} R_{t+n} + \gamma^{n} V_{t+n-1}(S_{t+n}, W_{t+n-1}) \tag{12.1}
$$

$\lambda$ 回报:

$$
G_t^{\lambda} = (1- \lambda) \sum_{n=1}^{\infty} \lambda^{n-1}G_{t:t+n} \tag{12.2}
$$

提取终止项:

$$
G_t^{\lambda} = (1- \lambda) \sum_{n=1}^{T-t-1} \lambda^{n-1}G_{t:t+n}
+ \lambda^{T-t-1}G_{t} \tag{12.3}
$$

更新目标:

$$
w_{t+1} = w_{t} + \alpha[ G_{t}^{\lambda} - \hat{v}(S_t, w_t)]
\nabla \hat{v}(S_t, w_t), t=0,…,T-1 \tag{12.4}
$$

12.2 TD($\lambda$)

  • 每一步都更新权重向量(原本是结束时更新)

  • 计算平均分布在整个时间轴,而不是结束时

  • 也适用于持续性问题,(episode更不用说了)

资格迹$z_t$,短期记忆;是一个和权值向量w_t同维度的向量;

  • 唯一作用:影响权值向量,进而权值向量影响估计值v;

权值向量$w_t$:长期的记忆

$$
\begin{align*}
& z_{-1} = 0,\
& z_t = \gamma \lambda z_{t-1} + \nabla \hat{v}(S_t, w_t), 0 \leqslant t \leqslant T
\tag{12.5}
\end{align*}
$$

$$
\begin{align*}
& \delta_t = R_{t+1} + \gamma \hat{v}(S_{t+1}, w_t) - \hat{v}(S_t, w_t) \tag{12.6} \

& w_{t+1} = w + \alpha \delta_t z_t \tag{12.7}
\end{align*}
$$

12.3 n-step Truncated $\lambda$-return Methods

12.7 Sarsa()

Sarsa: TD metod for action values;

extend eligibility-traces to action-value methods.

learn approximate action values, qˆ(s, a, w), rather than approximate state values, vˆ(s,w)

12.8 Variable $\lambda$ and $\gamma$

13 Policy Gradient Methods

参数化的策略方法;价值函数仍可用于学习策略的参数,但是对于动作选择而言,就不是必须了。

梯度上升:gradient ascent in J。

$$
\theta_{t+1} = \theta_t + \alpha \widehat{ \nabla J(\theta_t) } \tag{13.1}
$$

同时学习策略和值函数的近似值的方法通常称为 演员-评论家方法,其中“演员”是指所学策略,而“评论家”是指所学习的值函数,通常是状态价值函数。

1. Policy Approximation and its Advantages

动作偏好soft-max (soft-max in action preferences)

例如在扑克中诈(bluffing)。 动作值方法没有找到随机最优策略的自然方法,而策略逼近方法却可以,如示例13.1所示。

相比于动作值参数化,策略参数化可能具有的最简单的优势是策略可能是一种做近似更简单的函数。 问题的策略和行动价值函数的复杂性各不相同。

  • 对于某些来说,动作值函数更简单,因此更容易近似。

  • 对于其他,策略更简单。 在后一种情况下,基于策略的方法通常将学习得更快, 并产生更好的渐近策略 (如俄罗斯方块;请参见Şimşek, Algórta和Kothiyal,2016年)。

优势1:近似策略接近于一个确定策略;

优势2:可以以任意概率选择动作;

  • 例如,在信息不完美的纸牌游戏中,最佳玩法通常是用特定的概率做两件不同的事情,比如在扑克中虚张声势。

2. The Policy Gradient Theorem(定理)

策略参数化选择动作的概率会平滑的变化,这很大程度上导致了其更强的收敛保证。

特定(非随机)状态下开始:

$$
J(\theta) = v_{\pi_\theta}(s_0)
$$

3. REINFORCE: Monte Carlo Policy Gradient

回想我们前几节提到的梯度上升,我们只需要一种获取样本的获取方法,让采样样本的梯度近似或者等于策略梯度:

  • 样本梯度的期望 正比于 实际梯度(作为参数的函数的性能度量)

策略梯度定理公式:

这时我们可以应用梯度上升算法(13.1):

由于更新涉及了所有的动作,因此被称为全部动作算法,不过,我们更关注在时刻t被采样的动作,因此需要用策略对动作进行期望加权:

使用这个算法进行梯度上升,就可以得到我们想要的Reinforce算法:

(13.8)

综合起来,就可以写出算法伪代码:

$$
\nabla ln(x) = \frac{\nabla x}{x}
$$

4、带有baseline的Reinforce

可以在动作价值函数后加一个对比的baseline(v(s))以推广策略梯度定理,只要不随动作a变化就行:

为什么可以这么做呢?因为对策略求和的梯度为0:

比较自然的一个baseline就是状态价值函数了。这样做的好处在于,在不改变期望的前提下,大大降低了方差。

5. Actor–Critic Methods

尽管带baseline的强化学习方法学习了一个状态价值函数,我们也不能说它是演员评论家(actor critic)方法,因为状态价值函数仅仅当做baseline,而不是Critic。没有被用作自举(bootstrapping,根据后续状态的估计值更新状态的值估计)。

然而,和所有MC方法一样,这种方法收敛很慢,而且难以在线实现。

use actor–critic methods with a bootstrapping critic.

为了解决这个问题,我们引入TD(0)方法与演员评论家方法:

这是一个online、增量式的算法,所有收集到的数据只会在第一次使用,之后就弃用:

n-step (+资格迹)

6、持续性问题的策略梯度

Policy Gradient for Continuing Problems

7、Policy Parameterization for Continuous Actions

通过学习概率分布的统计量,而不是每一个动作的概率,我们能够解决连续动作空间的任务正态分布的概率密度函数:

为了得到参数化的策略函数,我们不如把策略定义为正态分布的概率密度:

我们分别用均值和标准差来近似策略参数向量,由于标准差为正数,所以得用指数形式:

参考资料

《南开大学–强化学习课程PPT》

《强化学习 Sutton》

★★★★十二、Policy Gradient Methods 策略梯度方法

强化学习 (Reinforcement Learning) | 莫烦Python

C++类(五)——重新审视auto、比较三种for循环的效率、

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
26
int ids[] = {1,2,3,4,5}

// auto 方法最快
for (auto v: ids){
cout << v;
}

//
for (int i=0; i< sizeof(ids)/sizeof(int); i++)
{
cout << ids[i];
}

// 每次都要求group.size(), 并还需要找group[i]的位置
vector<int> group;
for (size_t i=0; i< group.size(); i++){
cout << group[i];
}
// 每次求iter!=end(), 并且要提取相应位置的值
for (iter= group.begin(); iter != group.end(); iter++){
cout << *iter
}

for (auto v: group){
cout << v;
}

[TOC]

c++ STL

容器

  • 序列容器

    • vector

    • array

    • queue

    • deque

    • list

    • forward_list

  • 关联容器

    • map

    • set

    • unordered_map

    • multimap

    • unordered_multimap

    • multiset

    • unordered_multiset

1. vector

初始化

1
2
3
4
5
6
7
8
9
10
    vector<int> vec1;
vector<float> vec2(3);
vector<char> vec3(3,'a');
vector<char> vec4(vec3);
vector<vector<int>> vecNN(5);

//<1> 第一个是空的整形vector,我们没有给他添加任何元素。
//<2>第二个初始化了一个有3个元素的vector,由于并没有指定初始 值,将会使用编译器默认的初始值。
//<3>第三个初始化了含有3个a的字符vector,括号中第二个值代表着所有元素的指定值。
//<4>第四个vector通过拷贝vec3中的元素初始化vec4,它们的元素会一模一样。

push_back()

insert()

size()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <vector>
using namespace std;

vector<int> vect1,vect2;

//# push_back
vect1.push_back(1);
vect1.push_back(2);

vect2.push_back(8);
vect2.push_back(9);

//# insert
vect2.insert(vect2.end(),vect1.begin(),vect1.end());

//# size
vect2.size();

for (int i = 0; i < vect2.size(); i++)
{
cout<<vect2[i]<<endl;
}

Delete ~ ~Erase()

  1. erase返回一个迭代器,指向被删除元素之后的位置。
  2. 如果尝试删除超出向量范围的索引,erase将抛出std::out_of_range异常。
  3. 在删除元素后,所有后续元素的索引都会减少。例如,如果你删除了索引为2的元素,索引为3的元素会变成索引为2的元素。
  4. erase会释放被删除元素占用的内存。
  5. 使用erase后,向量的长度会减少。
1
2
3
4
5
6
7
8
9
10
11
12
    std::vector<int> vec = {1, 2, 3, 4, 5};  

// 删除索引为2的元素(即数字3)
vec.erase(vec.begin() + 2);

// 输出修改后的向量
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;

vect.erase(std::remove(vect.begin(), vect.end(), id), vect.end());

find

1
2
3
4
5
6
//# find
auto iter = find(vec2.begin(), vec2.end(), 9);
if (iter != vec2.end())
{
cout << "Find IT!";
}

replace

1
2
std::vector<int> vec = {1, 2, 3, 4, 5};  
vec[2] = 10; // 将索引为2的元素替换为10

find + delete (删除找到的第一个元素)

1
2
3
vector<int> vec = {3, 4, 5, 6, 7, 4};
auto iter = find(vec.begin(), vec.end(), 4);
vec.erase(iter);

find + replace

1
2
3
4
5
6
std::vector<int> vec = {1, 2, 3, 4, 5};  
for (auto& num : vec) {
if (num == 3) {
num = 10; // 将值为3的元素替换为10
}
}

max

1
2
3
4
vector<int> a = { 2,4,6,7,1,0,8,9,6,3,2 };
auto maxPosition = max_element(a.begin(), a.end());
cout << *maxPosition << " at the postion of " << maxPosition - a.begin() <<endl;
//cout << a[maxPosition - a.begin()] << " at the postion of " << distance(a.begin(), maxPosition) << endl;

vector TO 数组

1:memcpy
1
2
3
4
5
6
7
8
9
10
// 1 vector to arr
vector<char> vec(10, 'c'); // 10个c字符
char charray[10];
memcpy(charray, &vec[0], vec.size()*sizeof(vec[0])); // 注意数组越界--数据超长

// 2. arr to vector
// 2.1 直接赋值-init
vector<char> v(arr, arr+sizeof(arr))
// 2.2 copy
memcpy(&vec[0], arr, sizeof(arr));

ref baidu wenku

2: std::copy vector to arr
1
2
3
4
vector<int> input({1,2,3,4,5});
int arr[input.size()];
//Copies the range [first,last) into result.
std::copy(input.begin(), input.end(), arr);

ref:: https://www.techiedelight.com/convert-vector-to-array-cpp/

max

1
2
3
vector<int> a = { 2,4,6,7,1,0,8,9,6,3,2 };
auto maxPosition = max_element(a.begin(), a.end());
cout << *maxPosition << " at the postion of " << maxPosition - a.begin() <<endl;### map

vector 切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方法1
std::vector<int> vector{1,2,3,4,5,6,7,8,9};
// 截取[0,4]
std::vector<int>::const_iterator first1 = vector.begin();
std::vector<int>::const_iterator last1 = vector.begin() + 4;
std::vector<int> cut1_vector(first1, last1);


// 截取[-4,]
std::vector<int>::const_iterator first2 = vector.end() - 4;
std::vector<int>::const_iterator last2 = vector.end();
std::vector<int> cut2_vector(first2, last2);

// 方法2
vector<int> Arrs {1,2,3,4,5,6,7,8,9}; // 假设有这么个数组,要截取中间第二个元素到第四个元素:2,3,4
vector<int>::const_iterator Fist = Arrs.begin() + 1; // 找到第二个迭代器
vector<int>::const_iterator Second = Arrs.begin() + 2; // 找到第三个迭代器
vector<int> Arr2;
Arr2.assign(First,Second); //使用assign() 成员函数将Arrs对应位置的值存入Arrs2数组中

去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1
vector<int> vec = {1, 2, 3, 1, 1};
set<int> s(vec.begin(), vec.end());
vec.assign(s.begin(), s.end());

// 2
vector<int> vec = {1, 2, 3, 1, 1};
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
// unique函数将重复的元素放到 vector 的尾部,然后返回指向第一个重复元素的迭代器


// 3 remove 删除所有的7
auto ret = std::remove(vec.begin(), vec.end(), 7);
vec.erase(ret, vec.end());
1

2. Map

存放的元素都是K-V键值对,并且Key是不能重复的。

构造函数

1
map<int, string> mapStudent;  

插入数据

1
2
3
4
mapStudent.insert(pair<int, string>(1, "student_one"));             //pair<>()函数
mapStudent.insert(map<int, string>::value_type (1, "student_one")); //map<>::value_type
mapStudent.insert(make_pair(1, "student_one")); //make_pair()函数
mapStudent[1] = "student_one"; //数组方式

查找

1
2
3
4
5
iter = mapStudent.find(1); // find(key)
if (iter != mapStudent.end())
cout << "Find " << iter->second ;
else
cout << "Dont not find";

遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
map<int, CString>  maps;      // 或者map < int, 结构体名>   maps;
for (int i =0; i < maps.size(); i++)
{
CString s=maps[ i ];
}

map<CString, 结构体名> maps;
map<CString, 结构体名>::iterator iter; //迭代器遍历 如vector 也可使用
for( iter=maps.begin(); iter!=maps.end(); iter++)
{
CString a = iter - > first;
结构体名 p = iter - > second;
}

删除与清空元素

1
2
3
4
5
6
7
8
9
// 迭代器删除
iter = mapStudent.find("123");
mapStudent.erase(iter);

// 关键字删除
int n = mapStudent.erase("123");

// 清空map
mapStudent.erase(mapStudent.begin(), mapStudent.end())

大小

1
int mSize = mapStudent.size();

原文链接:https://blog.csdn.net/bandaoyu/article/details/87775533

3. unordered_map

存放的元素都是K-V键值对,并且Key是不能重复的。

1
2
3
4
5
#include <unordered_map>
// 如果C++的版本低于C++11,则会报错:[Error] ‘unordered_map’ was not declared in this scope
// 则需要将头文件改为
#include<tr1/unordered_map>
using namespace std::tr1;

建立Hashmap

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
26
27
28
29
30
31
unordered_map<int,int> Hashmap;


// **建立迭代器**
unordered_map<int,int>::iterator it;

//插入键值对
// insert函数
Hashmap.insert(make_pair<int,int>(1,3));
Hashmap.insert(make_pair(1,3));
// 通过键添加
Hashmap[3]=1;

//其他函数
it = Hashmap.begin() //指向哈希表的第一个容器
it = Hashmap.end() //指向哈希表的最后一个容器,实则超出了哈希表的范围,为空
Hashmap.size() //返回哈希表的大小
Hashmap.empty() //判断哈希表是否为空,返回值为true/false
Hashmap.clear() //清空哈希表

// 通过key值查找键值对
it = Hashmap.find(2) //查找key为2的键值对是否存在 ,若没找到则返回Hashmap.end()
if(Hashmap.find(2)!=Hashmap.end()) //判断找到了key为2的键值对

//通过key值查找该key值下的键值对对数
Hashmap.count(1) //返回 1


//swap交换两个Hashmap的键值对
Hashmap1.swap(Hashmap2);
swap(Hashmap1,Hashmap2);

遍历Map

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
//第一种
unordered_map<int, int> Hashmap;
for (auto p : Hashmap) {
int front = p.first; //key
int end = p.second; //value
}

//第二种
unordered_map<int, int> Hashmap;
for(auto it=Hashmap.begin();it!=Hashmap.end();it++)
{
int front = it->first; //key
int end = it->second; //value
}

//第三种
unordered_map<int,int> hash;
unordered_map<int,int>::iterator it;
it = hash.begin();
while(it != hash.end())
{
int front= it->first; //key
int end = it->second; //value
it++;
}

辨析.map VS unordered_map

内部实现机理不同
map: map内部实现了⼀个红⿊树(红⿊树是⾮严格平衡⼆叉搜索树,⽽AVL是严格平衡⼆叉搜索树),红⿊树具有⾃动排序的功能,因此map内部的所有元素都是有序的,红⿊树的每⼀个节点都代表着map的⼀个元素。因此,对于map进⾏的查找,删除,添加等⼀系列的操作都相当于是对红⿊树进⾏的操作。map中的元素是按照⼆叉搜索树(⼜名⼆叉查找树、⼆叉排序树,特点就是左⼦树上所有节点的键值都⼩于根节点的键值,右⼦树所有节点的键值都⼤于根节点的键值)存储的,使⽤中序遍历可将键值按照从⼩到⼤遍历出来。

unordered_map: 内部实现了⼀个哈希表(也叫散列表,通过把关键码值映射到Hash表中⼀个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着⼴泛应⽤)。因此,其元素的排列顺序是⽆序的。

map

特点
map 有序性,这是map结构最⼤的优点,其元素的有序性在很多应⽤中都会简化很多的操作
红⿊树,内部实现⼀个红⿊书使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率⾮常的⾼
缺点: 空间占⽤率⾼,因为map内部实现了红⿊树,虽然提⾼了运⾏效率,但是因为每⼀个节点都需要额外保存⽗节点、孩⼦节点和红/⿊
性质,使得每⼀个节点都占⽤⼤量的空间
适⽤处:对于那些有顺序要求的问题,⽤map会更⾼效⼀些
unorder_map 优点: 因为内部实现了哈希表,因此其查找速度⾮常的快
缺点: 哈希表的建⽴⽐较耗费时间
适⽤处:对于查找问题,unordered_map会更加⾼效⼀些,因此遇到查找问题,常会考虑⼀下⽤unordered_map

总结:

内存占有率的问题就转化成红⿊树 VS hash表 , 还是unorder_map占⽤的内存要⾼。

  • 但是unordered_map执⾏效率要⽐map⾼很多
  • 对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输⼊的顺序不⼀定相同,因为遍历是按照哈希表从前往后依次遍历的

map和unordered_map的使⽤

  • unordered_map的⽤法和map是⼀样的,提供了 insert,size,count等操作,并且⾥⾯的元素也是以pair类型来存贮的。
  • 其底层实现是完全不同的,上⽅已经解释了,但是就外部使⽤来说却是⼀致的。

4. set

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// init
std::set<int> myset{1, 2, 3, 4, 5};
// init 2
int arr[] = {1, 2, 3, 4, 5};
std::set<int> myset(arr, arr+5);

// insert
std::set<int> myset;
myset.insert(1);
myset.insert(2);


// delete
auto cnt = myset.erase(2); // 返回删除几个元素,0个,1个
// clear
myset.clear();

// for
for (std::set<int>::iterator it = myset.begin(); it != myset.end(); it++) {
// or
// for (auto it = myset.begin(); it != myset.end(); it++) {
std::cout << *it << " ";
}

// for 逆序
for (auto it = myset.rbegin(); it != myset.rend(); it++) {
std::cout << *it << " ";
}

// count 元素查询
std::set<int> myset;
myset.insert(2);
myset.insert(4);
myset.insert(6);
std::cout << myset.count(4) << "\n"; // 1
std::cout << myset.count(8) << "\n"; // 0

// find 元素查询
std::set<int> myset = {2, 4, 6};
auto search = myset.find(2);
if (search != myset.end()) {
std::cout << "Found " << *search << "\n"; // Found 2
} else {
std::cout << "Not found\n";
}

// empty
if (myset.empty())
cout << "empty set " << endl;

https://shengyu7697.github.io/std-set/

排列组合

算法-递归法实现排列组合C++

通用 排列/组合 函数(c++实现)

全排列(stl)

从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。如果这组数有n个,那么全排列数为n!个。

比如a,b,c的全排列一共有3!= 6 种 分别是{a, b, c}、{a, c, b}、{b, a, c}、{b, c, a}、{c, a, b}、{c, b, a}。

api:

prev_permutation

next_permutation

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
26
27
#include <iostream>
#include <algorithm>
using namespace std;
int main ()
{
int arr[] = {3,2,1};
cout<<"用prev_permutation对3 2 1的全排列"<<endl;
do
{
cout << arr[0] << ' ' << arr[1] << ' ' << arr[2]<<'\n';
}
while (prev_permutation(arr,arr+3) );
///获取上一个较大字典序排列,如果3改为2,只对前两个数全排列

int arr1[] = {1,2,3};
cout<<"用next_permutation对1 2 3的全排列"<<endl;
do
{
cout << arr1[0] << ' ' << arr1[1] << ' ' << arr1[2] <<'\n';
}
while (next_permutation(arr1,arr1+3) );
///获取下一个较大字典序排列,如果3改为2,只对前两个数全排列

///注意数组顺序,必要时要对数组先进行排序

return 0;
}
全排列递归思路

我们可以将这个排列问题画成图形表示,即排列枚举树,比如下图为{1,2,3}的排列枚举树,此树和我们这里介绍的算法完全一致;

image-20220507112024363

算法思路:

(1)n个元素的全排列=(n-1个元素的全排列)+(另一个元素作为前缀);

(2)出口:如果只有一个元素的全排列,则说明已经排完,则输出数组;

(3)不断将每个元素放作第一个元素,然后将这个元素作为前缀,并将其余元素继续全排列,等到出口,出口出去后还需要还原数组;

排列(递归)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/** permutaion: 排列。
* 从n个数中选出m个数的方式,若不考虑顺序Cn(m),若考虑顺序An(m)
*//* 问题:密码破解
* 假设有一个 4 位字母密码,每位密码是 a~e 之间的小写字。
* 编写代码暴力破解密码。
* 提示:根据可重复排列的规律,生成所有可能的 4 位密码。
*/

#include <iostream>
#include <vector>

using namespace std;
class Permutation {
private:
int resultCount_ = 0;
public:
/** Details: 根据输入字母列表,获得所有的排列方式。
* params: result- 当前排列形式, candidate- 未排列字母表。
* return: null
*/
void breakPassword(vector<string> result, vector<string> candidate) {
int len = candidate.size();
if (0 == len) {
// 无字母剩余,输出排列结果
outputResult(result);
resultCount_++;
return;
}
for (int i = 0; i < len; i++) {
vector<string> resultNew;
vector<string> candidateLeft;
// 读取排列字母
resultNew = result;
resultNew.push_back(candidate[i]);
// 获得剩余字母表
candidateLeft = candidate;
vector<string>::iterator it = candidateLeft.begin();
candidateLeft.erase(it + i);
// 递归
breakPassword(resultNew, candidateLeft);
}
}
// 输出结果
void outputResult(vector<string> result) {
for (unsigned int i = 0; i < result.size(); i++) {
cout << result[i];
}
cout << endl;
}
// 获得所有可能密码总数
int getResultCount() {
return resultCount_;
}
};
int main(void) {
vector<string> fourAlphabetString = {"a", "b", "c", "d", "e"};
vector<string> res;
Permutation test;
test.breakPassword(res, fourAlphabetString);
cout << "可能的密码形式:";
cout << test.getResultCount() << "种" << endl;
}

组合(数组)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <string>
#include <vector>

using namespace std;

vector<string> &combination(vector<string> &res, const size_t &choose, string &working, const size_t &pos)
{
if (choose > working.size() - pos)
return res;
for (size_t i = pos; i != working.size(); ++i)
{
working[i] = '0';
}
if (choose == 0 || pos == working.size())
{
res.push_back(working);
return res;
}
working[pos] = '1';
combination(res, choose - 1, working, pos + 1);
working[pos] = '0';
combination(res, choose, working, pos + 1);
return res;
}

int main()
{
vector<string> res;
const size_t choose = 3;
string working(5, '0');
combination(res, choose, working, 0);
for (size_t i = 0; i != res.size(); ++i)
{
cout << res[i] << '\t';
for (size_t j = 0; j != working.size(); ++j)
{
if (res[i][j] == '1')
{
cout << j + 1 << ' ';
}
}
cout << endl;
}
return 0;
}

组合(stl, vector)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <vector>
#include <algorithm>
#include <boost/assign.hpp>
#include <boost/function.hpp>

using namespace std;
using namespace boost;
using namespace boost::assign;

inline void print_(int t){cout<<t<<" ";}
inline void print(vector<int>& vec)
{
for_each(vec.begin(),vec.end(),print_);
cout<<endl;
}

//! 全排列测试
void test1()
{
vector<int> vec;
vec += 1,2,3,4,5,6,7,8;
sort(vec.begin(),vec.end());
int i = 0;
do
{
print(vec);
i++;
}
while(next_permutation(vec.begin(),vec.end()));
std::cout<<i<<std::endl;//40320
}

//! 组合测试
size_t test2(int n,int m,boost::function<void(std::vector<int>& vec)> fn)
{
vector<int> p,set;
p.insert(p.end(),m,1);
p.insert(p.end(),n-m,0);
for(int i = 0;i != p.size();++i)
set.push_back(i+1);
vector<int> vec;
size_t cnt = 0;
do{
for(int i = 0;i != p.size();++i)
if(p[i])
vec.push_back(set[i]);
fn(vec);
cnt ++;
vec.clear();
}while(prev_permutation(p.begin(), p.end()));
return cnt;
}

int main()
{
// test1();
std::cout<<test2(20,3,print)<<std::endl;
return 0;
}

组合(class,递归)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <cmath>
#include <iostream>
#include <vector>
using namespace std;
// 组合
// 思想:从一些对象n中选出一部分m(1<=m<=n),不考虑顺序,可能的形式有几种。
// 应用:队伍参赛排序。自然语言处理中,处理输入词。当输入词相同仅顺序不一致时,
// 可以采用组合的概念将其排序,并认定为意思相同。

template<typename T> class Combination {
private:
const int firstPrize;
const int secondPrize;
const int thirdPrize;
public:
Combination(int x, int y, int z)
: firstPrize(x), secondPrize(y), thirdPrize(z) {
}
/**
* @description:
* 从10个人中选出三等奖3人,二等奖2人,一等奖1人,不能重复获奖。
* @param {type} rewardNum- 指定赏金数, result- 奖赏方式结果。
* @return: null
*/
void rewardMethods(vector<T> result, vector<T> candidate){
unsigned int len = thirdPrize + secondPrize + firstPrize;
if (result.size() == len) {
// 输出结果
resultOutput(result);
return;
} else {
for (unsigned int i = 0; i < candidate.size(); i++) {
vector<int> resultNew = result;
resultNew.push_back(candidate[i]);
vector<int> candidateNew;
copyElem(candidate, candidateNew, i + 1);
rewardMethods(resultNew, candidateNew);
}
}
}
// 数据复制函数
void copyElem(vector<T>& input, vector<T>& output, int i){
vector<int>::iterator it = input.begin() + i;
for (; it < input.end(); it++) {
output.push_back(*it);
}
}
// 输出结果
void resultOutput(vector<T> res) {
for (unsigned int i = 0; i < res.size(); i++) {
if (i == thirdPrize) cout << '*';
if (i == thirdPrize + secondPrize)
cout << '*';
cout << res[i] << ' ';
}
cout << endl;
}
};
// test
int main(void) {
Combination <int> test(1, 2, 3);
vector<int> res; vector<int> candidate;
// 输入
for (unsigned int i = 0; i < 10; i++) {
candidate.push_back(i + 1);
}
test.rewardMethods(res, candidate);
}

可重复组合(运行Pass)

  • 允许元素重复的一类组合问题

  • 用P(n, k)表示从n个元素中选出k个元素(允许重复)的组合问题,那么此问题可以分解为两个子问题:P(n, k-1)和P(n-1, k),

  • 解释如下:

    • P(n, k)中n个元素的所有k元素组合可以分成两部分。

    • 第一部分的每个组合均以第一个元素开始,再连接上k-1个元素的组合,即再连接上P(n,k-1)的每一个组合;

    • 第二部分的每个组合不含有第一个元素,即P(n-1, k)中的每一个组合(此时元素为后n-1个元素)。

    • 因此,P(n, k)分解为P(n, k-1)和P(n-1, k)。

如果用f(n, k)表示P(n, k)中的组合数,那么有:
(1)当k = 1时,f(n, k) = n
(2)当n = 1时,f(n, k) = 1
(3)当k > 1且n > 1时,f(n, k) = f(n, k -1) + f(n-1, k)

此外,有公式f(n, k) = C(n + k -1, k)(表示n+k-1选k的组合数,没有重复元素),这可以用归纳法证明,这里就不啰嗦了。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
template <typename ElemType>
void CalcRepeatableCombination(const ElemType elements[], int num, int k, vector<ElemType> &pre, vector<vector<ElemType>> &result)
{
if (k == 1)
{
for (int i = 0; i < num; i++)
{
// copy(pre.begin(), pre.end(), ostream_iterator<ElemType>(cout));
// cout << elements[i];
// cout << endl;
vector<ElemType> tmp(pre.begin(), pre.end());
tmp.push_back(elements[i]);
result.push_back(tmp);
}
return;
}
if (num == 1)
{
// ostream_iterator<ElemType> outIter(cout);
// outIter = copy(pre.begin(), pre.end(), outIter);
// fill_n(outIter, k, elements[0]);
// cout << endl;
vector<ElemType> tmp(pre.begin(), pre.end());
for (int i = 0; i < k; i++)
{
tmp.push_back(elements[0]);
}
result.push_back(tmp);
return;
}

pre.push_back(elements[0]);
CalcRepeatableCombination(elements, num, k - 1, pre, result);
pre.pop_back();

CalcRepeatableCombination(elements + 1, num - 1, k, pre, result);
}

template <typename ElemType>
void CalcRepeatableCombination(const ElemType elements[], int num, int k, vector<vector<ElemType>> &result)
{
cout << " num=" << num << " k=" << k << endl;
// assert(num >= k && k >= 1);
assert(k >= 1);
vector<ElemType> one;
CalcRepeatableCombination(elements, num, k, one, result);
}

int main()
{
char elements[] = {'a', 'b', 'c', 'd'};
vector<char> com_r;
CalcRepeatableCombination(elements, 4, 3, com_r);
}

矩阵

[TOC]

官方文档

Offical Eigen Doc

安装

https://gitlab.com/libeigen/eigen/-/releases

1
2
3
4
5
# Ubuntu 系统默认版本安装
sudo apt-get install libeigen3-dev

安装路径
/usr/include/eigen3
1
2
3
4
5
6
7
8
9
mkdir build
cd build
cmake..
sudo make install

安装路径
/usr/local/include/eigen3
#
make uninstall

Map

Map的定义:

1
Map< Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime> >

在这种默认的定义中,Map只需要一个模板参数-Matrix。
为了构造Map变量,需要另两个信息:一个指针,该指针指向用于定义数组元素的内存区域;另一个是希望得到的matrix或vector的形状。

比如:定义一个float类型、动态尺寸大小的matrix:

1
Map<MatrixXf> mf(pf,rows,columns);

其中pf是一个float *类型的指向数组内存的指针。

定义一个固定大小的、整形的、只读vector:

1
Map<const Vector4i> mi(pi);

其中,pi是一个int *类型的指针。在这个例子中不需要传递尺寸大小给构造函数,因为尺寸大小已经由Matrix/Array类型确定了。

注意:Map没有默认的构造函数,你需要传递一个指针来初始化对象。

Map能够足够灵活地去容纳多种不同的数据表示,其他的两个模板参数:

1
2
3
Map<typename MatrixType,
int MapOptions,
typename StrideType>

MapOptions标识指针是否是对齐的(Aligned or Unaligned),默认是Unaligned。
StrideType表示内存数组的组织方式:行列的步长。

Map矩阵赋值(数组转mxtrix)

blog: Map class:连接Eigen与C++的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//matrix_map1.cpp
#include <iostream>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

int main()
{
int array[9];
for(int i = 0; i < 9; ++i) array[i] = i;

Map<VectorXi> vi(array,9);
Map<const Vector4i> fixed_v(array); // unknown type name 'Vector5i' -- 这种模式size不能大于4
cout<< "vector vi: "<< endl << vi << endl<< endl;
cout<< "fixed-vector : "<< endl << fixed_v << endl<< endl;

cout << "Column-major:\n" << Map<Matrix<int,2,4> >(array) << endl;
cout << "Row-major:\n" << Map<Matrix<int,2,4,RowMajor> >(array) << endl;
cout << "Row-major using stride:\n" <<
Map<Matrix<int,2,4>, Unaligned, Stride<1,4> >(array) << endl;
}

Matrix to c++数组

1
2
3
4
5
6
7
8
Matrix3d eigMat;

// 法1
double* eigMatptr = eigMat.data();

// 法2
double* eigMatptrnew = new double[eigMat.size()];
Map<MatrixXd>(eigMatptrnew, eigMat.rows(), eigMat.cols()) = eigMat;

使用Map变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef Matrix<float,1,Dynamic> MatrixType;
typedef Map<MatrixType> MapType;
typedef Map<const MatrixType> MapTypeConst; // a read-only map
const int n_dims = 5;

MatrixType m1(n_dims), m2(n_dims);
m1.setRandom();
m2.setRandom();
float *p = &m2(0); // get the address storing the data for m2
MapType m2map(p,m2.size()); // m2map shares data with m2
MapTypeConst m2mapconst(p,m2.size()); // a read-only accessor for m2
cout << "m1: " << m1 << endl;
cout << "m2: " << m2 << endl;
cout << "Squared euclidean distance: " << (m1-m2).squaredNorm() << endl;
cout << "Squared euclidean distance, using map: " << (m1-m2map).squaredNorm() << endl;
m2map(3) = 7; // this will change m2, since they share the same array
cout << "Updated m2: " << m2 << endl;
cout << "m2 coefficient 2, constant accessor: " << m2mapconst(2) << endl;
/* m2mapconst(2) = 5; */ // this yields a compile-time error

修改Map数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//matrix_map3.cpp
#include <iostream>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

int main()
{
int data[] = {1,2,3,4,5,6,7,8,9};
Map<RowVectorXi> ov(data,9);
cout << "Origin data: " << ov << endl;

Map<RowVectorXi> v(data,4);
cout << "The mapped vector v is: " << v << "\n";

// placement new
new (&v) Map<RowVectorXi>(data+4,5);
cout << "Changed, Now v is: " << v << "\n";
cout << "Again origin data: " << ov << endl;
}

矩阵拼接

1.cv::Mat矩阵拼接(注意在拼接方向上的维度应该相同)

1
2
cv::vconcat(C, A, B); //垂直拼接
cv::hconcat(C, A, B); //水平拼接

2.Eigen矩阵拼接(注意矩阵拼接之前必须要确定大小! 否则会报错)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MatrixXd A;
MatrixXd B;
MatrixXd C;
A.resize(3, 3); //注意矩阵拼接之前必须要确定大小,否则会报错
B.resize(3, 9); //注意矩阵拼接之前必须要确定大小,否则会报错
C.resize(9, 3); //注意矩阵拼接之前必须要确定大小,否则会报错
A << 1, 2, 3,
4, 5, 6,
7, 8, 9;
cout<<"A:"<<A<<endl;
B << A,A,A; //水平拼接
cout<<"B:"<<B<<endl;
//垂直拼接
C << A,
A,
A;
cout<<"C:"<<C<<endl;

模块介绍

向量运算(.T,Sum,Trace,Inverse…)

Eigen库的矩阵(包括向量)运算时,需要声明头文件<Eigen/Core>,矩阵执行常见的运算指令:

1
2
3
4
5
6
7
8
matrix_33 = Matrix3d::Random();                           //生成一个3*3的随机矩阵
cout << "random matrix: \n"<< matrix_33 << endl;
cout << "transpose : \n"<< matrix_33.transpose() << endl; //转置
cout << "sum :" << matrix_33.sum() << endl; //求和
cout << "trace : "<< matrix_33.trace() << endl; //求迹
cout << "time 10: \n"<< 10 * matrix_33 << endl; //数乘
cout << "inverse : \n"<< matrix_33.inverse() << endl; //求逆
cout << "det : \n"<< matrix_33.determinant() << endl; //求行列式

几何模块(四元数,欧拉角,旋转等)

使用Eigen库的几何模块时,需要声明头文件<Eigen/Geometry>,此模块支持进行四元数、欧拉角和旋转矩阵的运算。各种常见形式的表达方式如下所示:

1
2
3
4
5
6
7
Eigen::Matrix3d      //旋转矩阵(3*3)
Eigen::AngleAxisd //旋转向量(3*1)
Eigen::Vector3d //欧拉角(3*1)
Eigen::Quaterniond //四元数(4*1)
Eigen::Isometry3d //欧式变换矩阵(4*4)
Eigen::Affine3d //放射变换矩阵(4*4)
Eigen::Projective3d //射影变换矩阵(4*4)

eigenGeometry Demo

  • 旋转
  • 四元数
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <cmath>
#include <Eigen/Core>
#include <Eigen/Geometry>


int main(int argc, char **argv){

// Eigen/Geometry 模块提供了各种旋转和平移的表示
// 3D 旋转矩阵直接使用 Matrix3d 或 Matrix3f
//Identity() -> 单位矩阵
Eigen::Matrix3f rotation_matrix = Eigen::Matrix3f::Identity();
std::cout << "rotation matrix: \n" << rotation_matrix << std::endl;

// 旋转向量使用 AngleAxis, 它底层不直接是Matrix,但运算可以当作矩阵(因为重载了运算符)
//沿 Z 轴旋转 45 度
Eigen::AngleAxisf rotation_vector(M_PI/4,Eigen::Vector3f(0, 0, 1));
std::cout.precision(3);
std::cout << "rotation matrix: \n" << rotation_vector.matrix() << std::endl;

rotation_matrix = rotation_vector.toRotationMatrix();
std::cout << "rotation matrix: \n" << rotation_matrix << std::endl;


// 用 AngleAxis 可以进行坐标变换
Eigen::Vector3f v(1, 0, 0);
Eigen::Vector3f v_rotated = rotation_vector * v;
std::cout << "(1,0,0) after rotation (by angle axis) = " << v_rotated.transpose() << std::endl;

// 用旋转矩阵
v_rotated = rotation_matrix * v;
std::cout << "(1,0,0) after rotation (by matrix) = " << v_rotated.transpose() << std::endl;

//欧拉角: 可以将旋转矩阵直接转换成欧拉角
// ZYX顺序,即yaw-pitch-roll顺序
Eigen::Vector3f euler_angles = rotation_matrix.eulerAngles(2,1,0);
std::cout << "yaw-pitch-roll = " << euler_angles.transpose() << std::endl;

// 欧氏变换矩阵使用 Eigen::Isometry
// 虽然称为3d,实质上是4*4的矩阵
Eigen::Isometry3f T = Eigen::Isometry3f::Identity();
std::cout << "Transform matrix = \n" << T.matrix() << std::endl;
T.rotate(rotation_vector); // 按照rotation_vector进行旋转
T.pretranslate(Eigen::Vector3f(1, 2, 3)); // 把平移向量设成(1,3,4)
std::cout << "Transform matrix = \n" << T.matrix() << std::endl;

// 用变换矩阵进行坐标变换
Eigen::Vector3f v_transformed = T * v; // 相当于R*v+t
std::cout << "v tranformed = " << v_transformed.transpose() << std::endl;

// 对于仿射和射影变换,使用 Eigen::Affine3d 和 Eigen::Projective3d 即可,略

// 四元数
// 可以直接把AngleAxis赋值给四元数,反之亦然
Eigen::Quaternionf q = Eigen::Quaternionf(rotation_vector);
std::cout << "quaternion from rotation vector = " << q.coeffs().transpose()
<< std::endl; // 请注意coeffs的顺序是(x,y,z,w),w为实部,前三者为虚部

// 也可以把旋转矩阵赋给它
q = Eigen::Quaternionf(rotation_matrix);
std::cout << "quaternion from rotation matrix = " << q.coeffs().transpose()
<< std::endl;

// // 使用四元数旋转一个向量,使用重载的乘法即可
Eigen::Vector3f v_rotated1 = q * v ;// 注意数学上是qvq^{-1}
std::cout << "(1,0,0) after rotation (by Quaternion) = " << v_rotated1.transpose() << std::endl;

// 用常规向量乘法表示,则应该如下计算
std::cout << "should be equal to = " << (q * Eigen::Quaternionf(0, 1, 0, 0) * q.inverse()).coeffs().transpose()
<< std::endl;

return 0;
}

参考资料链接:

PDF资源网站

  • 高等教育出版社(多是试读版)

  • zLibrary(外文书籍多)

  • 鸠摩搜书(收费网站)

  • 全国图书馆参考咨询联盟网(基本没用)

CONTENTS:

[toc]

1 PNC(Panning and Control)Overview

image-20220426005824549

image-20220426010020157

难点:

  • 人和人之间的博弈–转换成–人和机器的博弈(路口会车情况)

2 Prediction Task

image-20220426010433330

两种方法

  • Model-based
  • Trajectories轨迹预测

image-20220426010827741

Recruit Requirement

image-20220426011146498

3 Vehicle Predict (车辆预测)

image-20220426011425730

道路建模

  • 连续空间–转换成–预测问题

image-20220426011657015

  • 非结构化数据(感知是结构化数据)

lane Feature

  • Lane S (前方)

  • Lane L (宽度)

  • reference lane

  • Curvature 曲率

  • Traffic law 交通信号

Vehicle State

  • Velocity (加速度)
  • Acc (速度)
  • Heading ()
  • Heading rete(角度)
  • Type(车的类型,救护车,交通车)
  • Size

image-20220426012003676

Lane Model

image-20220426012739621

  • Obstacle Statue 输入几秒的数据

image-20220426012846986

Squence Data Network

image-20220426013007183

Apollo Model

image-20220426013338102

Data Pipeline

image-20220426015202905

waygom 首席科学家

image-20220426015504496

Trajectory builder

  • Kalman Filter 卡尔曼滤波
  • Polynomial 多项式
  • Velocity 动力学方式

image-20220426015613613

STOA

  • 各家各的格式
Uber

—用图表示周围环境

  • 当成回归问题,解决

image-20220426015841513

waymo

预测+规划 结合

image-20220426020111762

image-20220426020145542

用图的思想去预测

image-20220426020530774

4 Pedestrian predict (行人预测)

image-20220426020549502

image-20220426020608203

image-20220426020710296

image-20220426020753609

思想:人+人的姿态信息 + 环境信息 + 【分割,识别】 ===形成一个感知预测的结合

输出: 要做的任务(而不是轨迹的预测)

image-20220426020842076

Summary

image-20220426021108625

HomeWork

image-20220426021148390

traffic Violation违规交通

博弈问题

  • 什么是规划

    • 规划的本质
    • 如何解决一个规划问题
  • 传统的规划方法

    • 机器人学基础
    • 经典算法
  • 无人车规划

    • Routing
    • Planning
    • Lattice Palnner
  • Apollo 如何求解规划问题

    • EM Planner
    • DP, QP求解

[toc]

What is motion planning?

  • planning
  • 本质是什么

$$
argmin_xf(x)
$$

  • 搜索问题
    • Google: Quary词,返回给最优结果。
    • 无人车:当前环境和当前状态,当前库路径上最优选择。
    • 什么是好规划?
  • “好”其实就是个目标函数:f(x)
    • f(x)的最优解

Motion Planning 的三个领域

  • Robotics Fileds:(机器人领域)
    • 生成轨迹实现目标
    • 常用方法: RRT,A*, D*, D*lite
  • Control Theory:(控制领域)
    • 方式:动态系统理论实现目标状态
    • 方法:MPC , LQR
  • AI:生成状态和Action的映射

如何解决一个Motion Panning 问题?

  • 找一个简单的突破口
    • 将问题转换成一个简单的问题:Path Finding Problem
      • 不关心速度,不关心走
      • 周围固定
  • 简言之就是,路径选择问题
    • A sample shortest path example
    • 什么样的path是最好的?这是重点
      • 路径最短
        • BFS,DFS
        • Dijkstra
    • 刚刚看到的Search属于non-information search 效率较低
    • A search*: 基于Dijkstra的改进算法【基础算法,很重要
    • 无人机中的规划和A* search相差多远?
      • 部分感知
      • 动态障碍物
      • 复杂环境: 交通规则,碰瓷
      • A*本身是一个Global Algorithm
        • Global Routing

Partial Observed situation

  • 贪心算法

    • incremental search增量搜索:目前状态求解道最优
  • p* star

    • 部分环境信息的一个Search
    • Apollo登月小车
    • 改进版:D* lite
  • 可以求解全局最优?

    • 有难度

    • 一定必要全局最优吗?

      • Stentz Anthony,“Optimal and Efficient Path Planing for Partially-Known Enviroments”, 1994

        • (统计学教授)通过部分最优,可以逼近全局最优
  • Informative & Non-informative Search

    • Global & Partial observed
  • 至此,我们已经有了如下几个方法:

    • 目标函数并且结合了平滑性和目标Cost
    • 使用通用的Search方法来最小化Cost从而找到一个最优解
    • 通过Partial observed information来做局部planning
  • 我们还缺什么?

    • 处理动态障碍物,动态环境
    • 处理交通规则
    • 实时计算
      • (100ms-150ms)
      • 人一般反应时间300-500ms
      • 有效时间内找到最优解
      • c++
  • 给无人车motion planning下一个定义:

    • Safely

    • Smoothly

    • Achieve to destination

    • X, Y, Time: 3D trajectory optimization problem

    • 无人车硬件系统

      • 定位
      • 感知
    • 无人车软件信息

      • 动态信息
      • 静态信息
        • HD Map
          • 实时性保证
    • 如何设计出一个合理轨迹?

      • 路径Path
      • 速度Speed

经典参考书籍

  • Steve Lavelle, Motion Planning Algorithms

  • Principles of Robot Motion: Theory, Algorithms and implementations

经典文献

  • A Review of Motion Planning for automated Vehivles

基本Planning方法

  • 经典基于环境建模的方法

    • RRT
    • Lattice
  • 现代无人车Planning方法

    • Darpa

    • Lattice in Frenet Frame

    • Spiral polymial

      A Review of Motion Planning Techniques for Automated Vehicles

  • 质点模型

    • 物体看车一个质点
    • 点与点不相碰
  • 刚体问题

    • BycicleModel
    • XY Heading
    • Collision
  • Planning限制条件

    • 避免膨胀
    • 边界阈值(跟车距离等)
  • 连续空间问题怎么解?

    • 离散化
    • 网格化

传统机器人基础

  • PRM (Probabilistic Roadmap Planning)

    • 非常常用三维一个方法
    • 连续空间离散化
      • 随机撒点
      • Obstacle上的点删除
    • 连续可行点,形成可行空间
    • A*
  • RRT(Incremental version of PRM)PRM的增量式的版本

    • 使用增加搜索的方式来进行
      • 找附近可行点的最优点
      • F(x) 最小,Cost最小
      • 走过车中也不能碰到障碍物
      • 撒点距离不能太远
        • 一步一步的移动
  • Lattice 方法

    • 改进了RRT的折线问题
    • 给出Path的平滑曲线
    • 网格化
      • 每个采样格中都是用曲线连接
    • 指数级别的一个搜索算法(NP-Hard)
  • DP(动态规划)

    • 减少搜索空间
      • 服用已有结果
    • Lattice DP的平滑度够吗?
      • 曲率连续
      • 曲率导数不一定连续【此是大问题,–方向盘突然就打大的角度变化】
  • QP(二次规划)

    • 凸优化问题最优化求解

    • 公式表达
      $$
      minimize \frac{1}{2} X^T QX+c^TX \
      subject: Ex = d , Fx \leqslant m
      $$

    • 性质:再凸优化中的图空间问题,用QP有最优解

    • QP如何找到平滑曲线

      • $ min|f’|^2$
      • $ min|f’’|^2$
      • $ min|f’’’|^2$
    • 其它的平滑曲线方法还有贝塞尔曲线,样条插值方法

  • 刚体模型

  • image-20220501021506769

    • 前轮转向和Heading的关系

      • 前轮是沿着切线的方向行驶
      • 前后轮是同一个旋转中心
      • 左右轮的结构相同
    • Bicycle Model

    • image-20220501021446166

      • 曲率公式
        $$
        \frac{1}{R} = kappa = (tan(\omega)) / L
        $$

无人车Planning

  • 定义

    从A点到B点,构建一个车辆运动归结,结合HD Map Localization 和Prediction

    • 输入
    • 输出:可行是归家,有一系列点组成
    • 两个层面,导航界面,运动轨迹层面
  • Routing

    • 导航一条A到B的全局路径
    • 输入:地图(路网信息,交通状态),当前位置,目的地(乘客定)
    • 输出:可行驶道路的连接线
    • 搜索:地图数据转化成图网络
      • 节点表示道路
      • 边代表道路连接

    image-20220501021924135

  • A*经典算法

    • 最经典的路径查找算法
    • F(n) = G(n) + H(n)