particle filter

# --------------
# USER INSTRUCTIONS
#
# Now you will put everything together.
#
# First make sure that your sense and move functions
# work as expected for the test cases provided at the
# bottom of the previous two programming assignments.
# Once you are satisfied, copy your sense and move
# definitions into the robot class on this page, BUT
# now include noise.
#
# A good way to include noise in the sense step is to
# add Gaussian noise, centered at zero with variance
# of self.bearing_noise to each bearing. You can do this
# with the command random.gauss(0, self.bearing_noise)
#
# In the move step, you should make sure that your
# actual steering angle is chosen from a Gaussian
# distribution of steering angles. This distribution
# should be centered at the intended steering angle
# with variance of self.steering_noise.
#
# Feel free to use the included set_noise function.
#
# Please do not modify anything except where indicated
# below.

from math import *
import random

# --------
# 
# some top level parameters
#

max_steering_angle = pi / 4.0 # You do not need to use this value, but keep in mind the limitations of a real car.
bearing_noise = 0.1 # Noise parameter: should be included in sense function.
steering_noise = 0.1 # Noise parameter: should be included in move function.
distance_noise = 5.0 # Noise parameter: should be included in move function.

tolerance_xy = 15.0 # Tolerance for localization in the x and y directions.
tolerance_orientation = 0.25 # Tolerance for orientation.


# --------
# 
# the "world" has 4 landmarks.
# the robot's initial coordinates are somewhere in the square
# represented by the landmarks.
#
# NOTE: Landmark coordinates are given in (y, x) form and NOT
# in the traditional (x, y) format!

landmarks  = [[0.0, 100.0], [0.0, 0.0], [100.0, 0.0], [100.0, 100.0]] # position of 4 landmarks in (y, x) format.
world_size = 100.0 # world is NOT cyclic. Robot is allowed to travel "out of bounds"

# ------------------------------------------------
# 
# this is the robot class
#

class robot:

    # --------
    # init: 
    #    creates robot and initializes location/orientation 
    #

    def __init__(self, length = 20.0):
        self.x = random.random() * world_size # initial x position
        self.y = random.random() * world_size # initial y position
        self.orientation = random.random() * 2.0 * pi # initial orientation
        self.length = length # length of robot
        self.bearing_noise  = 0.0 # initialize bearing noise to zero
        self.steering_noise = 0.0 # initialize steering noise to zero
        self.distance_noise = 0.0 # initialize distance noise to zero

    # --------
    # set: 
    #    sets a robot coordinate
    #

    def set(self, new_x, new_y, new_orientation):

        if new_orientation < 0 or new_orientation >= 2 * pi:
            raise ValueError('Orientation must be in [0..2pi]')
        self.x = float(new_x)
        self.y = float(new_y)
        self.orientation = float(new_orientation)

    # --------
    # set_noise: 
    #    sets the noise parameters
    #
    def set_noise(self, new_b_noise, new_s_noise, new_d_noise):
        # makes it possible to change the noise parameters
        # this is often useful in particle filters
        self.bearing_noise  = float(new_b_noise)
        self.steering_noise = float(new_s_noise)
        self.distance_noise = float(new_d_noise)

    # --------
    # measurement_prob
    #    computes the probability of a measurement
    #  

    def measurement_prob(self, measurements):

        # calculate the correct measurement
        predicted_measurements = self.sense(0) # Our sense function took 0 as an argument to switch off noise.


        # compute errors
        error = 1.0
        for i in range(len(measurements)):
            error_bearing = abs(measurements[i] - predicted_measurements[i])
            error_bearing = (error_bearing + pi) % (2.0 * pi) - pi # truncate
            

            # update Gaussian
            error *= (exp(- (error_bearing ** 2) / (self.bearing_noise ** 2) / 2.0) /  
                      sqrt(2.0 * pi * (self.bearing_noise ** 2)))

        return error
    
    def __repr__(self): #allows us to print robot attributes.
        return '[x=%.6s y=%.6s orient=%.6s]' % (str(self.x), str(self.y), 
                                                str(self.orientation))
    
    ############# ONLY ADD/MODIFY CODE BELOW HERE ###################
       
    # --------
    # move: 
    #   
    
    # copy your code from the previous exercise
    # and modify it so that it simulates motion noise
    # according to the noise parameters
    #           self.steering_noise
    #           self.distance_noise
    # --------
    # move:
    #   move along a section of a circular path according to motion
    #
    
    def move(self, motion): # Do not change the name of this function

        # ADD CODE HERE
        theta = self.orientation
        alpha = motion[0]
        d = motion[1]
        beta = d*tan(alpha)/self.length
        if beta > 1e-4:
            R = d/beta
            new_orientation = (theta + beta)%(2*pi)
            new_x = self.x + R*(sin(theta+beta)-sin(theta))
            new_y = self.y + R*(cos(theta)-cos(theta+beta))
        else:
            new_orientation = (theta + beta)%(2*pi)
            new_x = self.x + d*cos(new_orientation)
            new_y = self.y + d*sin(new_orientation)
        result = robot(self.length)
        result.set_noise(bearing_noise, steering_noise, distance_noise)
        result.set(new_x,new_y,new_orientation)
        
        return result # make sure your move function returns an instance
                      # of the robot class with the correct coordinates.
                    

    # --------
    # sense: 
    #    

    # copy your code from the previous exercise
    # and modify it so that it simulates bearing noise
    # according to
    #           self.bearing_noise
    # --------
    # sense:
    #   obtains bearings from positions
    #
    
    def sense(self, add_noise=1): #do not change the name of this function
        Z = []

        # ENTER CODE HERE
        # HINT: You will probably need to use the function atan2()
        for i in range(len(landmarks)):
            dy = landmarks[i][0] - self.y
            dx = landmarks[i][1] - self.x
            bearing = atan2(dy,dx) - self.orientation
            if add_noise:
                bearing += random.gauss(0.0, self.bearing_noise)
            Z.append(bearing%(2*pi))
        return Z #Leave this line here. Return vector Z of 4 bearings.

    ############## ONLY ADD/MODIFY CODE ABOVE HERE ####################

# --------
#
# extract position from a particle set
# 

def get_position(p):
    x = 0.0
    y = 0.0
    orientation = 0.0
    for i in range(len(p)):
        x += p[i].x
        y += p[i].y
        # orientation is tricky because it is cyclic. By normalizing
        # around the first particle we are somewhat more robust to
        # the 0=2pi problem
        orientation += (((p[i].orientation - p[0].orientation + pi) % (2.0 * pi)) 
                        + p[0].orientation - pi)
    return [x / len(p), y / len(p), orientation / len(p)]

# --------
#
# The following code generates the measurements vector
# You can use it to develop your solution.
# 


def generate_ground_truth(motions):

    myrobot = robot()
    myrobot.set_noise(bearing_noise, steering_noise, distance_noise)

    Z = []
    T = len(motions)

    for t in range(T):
        myrobot = myrobot.move(motions[t])
        Z.append(myrobot.sense())
    #print 'Robot:    ', myrobot
    return [myrobot, Z]

# --------
#
# The following code prints the measurements associated
# with generate_ground_truth
#

def print_measurements(Z):

    T = len(Z)

    print('measurements = [[%.8s, %.8s, %.8s, %.8s],' % \
        (str(Z[0][0]), str(Z[0][1]), str(Z[0][2]), str(Z[0][3])))
    for t in range(1,T-1):
        print('                [%.8s, %.8s, %.8s, %.8s],' % \
            (str(Z[t][0]), str(Z[t][1]), str(Z[t][2]), str(Z[t][3])))
    print('                [%.8s, %.8s, %.8s, %.8s]]' % \
        (str(Z[T-1][0]), str(Z[T-1][1]), str(Z[T-1][2]), str(Z[T-1][3])))

# --------
#
# The following code checks to see if your particle filter
# localizes the robot to within the desired tolerances
# of the true position. The tolerances are defined at the top.
#

def check_output(final_robot, estimated_position):

    error_x = abs(final_robot.x - estimated_position[0])
    error_y = abs(final_robot.y - estimated_position[1])
    error_orientation = abs(final_robot.orientation - estimated_position[2])
    error_orientation = (error_orientation + pi) % (2.0 * pi) - pi
    correct = error_x < tolerance_xy and error_y < tolerance_xy \
              and error_orientation < tolerance_orientation
    return correct



def particle_filter(motions, measurements, N=500): # I know it's tempting, but don't change N!
    # --------
    #
    # Make particles
    # 

    p = []
    for i in range(N):
        r = robot()
        r.set_noise(bearing_noise, steering_noise, distance_noise)
        p.append(r)

    # --------
    #
    # Update particles
    #     

    for t in range(len(motions)):
    
        # motion update (prediction)
        p2 = []
        for i in range(N):
            p2.append(p[i].move(motions[t]))
        p = p2

        # measurement update
        w = []
        for i in range(N):
            w.append(p[i].measurement_prob(measurements[t]))

        # resampling
        p3 = []
        index = int(random.random() * N)
        beta = 0.0
        mw = max(w)
        for i in range(N):
            beta += random.random() * 2.0 * mw
            while beta > w[index]:
                beta -= w[index]
                index = (index + 1) % N
            p3.append(p[index])
        p = p3
    
    return get_position(p)

## IMPORTANT: You may uncomment the test cases below to test your code.
## But when you submit this code, your test cases MUST be commented
## out.
##
## You can test whether your particle filter works using the
## function check_output (see test case 2). We will be using a similar
## function. Note: Even for a well-implemented particle filter this
## function occasionally returns False. This is because a particle
## filter is a randomized algorithm. We will be testing your code
## multiple times. Make sure check_output returns True at least 80%
## of the time.


 
## --------
## TEST CASES:
## 
##1) Calling the particle_filter function with the following
##    motions and measurements should return a [x,y,orientation]
##    vector near [x=93.476 y=75.186 orient=5.2664], that is, the
##    robot's true location.
##
motions = [[2. * pi / 10, 20.] for row in range(8)]
measurements = [[4.746936, 3.859782, 3.045217, 2.045506],
               [3.510067, 2.916300, 2.146394, 1.598332],
               [2.972469, 2.407489, 1.588474, 1.611094],
               [1.906178, 1.193329, 0.619356, 0.807930],
               [1.352825, 0.662233, 0.144927, 0.799090],
               [0.856150, 0.214590, 5.651497, 1.062401],
               [0.194460, 5.660382, 4.761072, 2.471682],
               [5.717342, 4.736780, 3.909599, 2.342536]]

print(particle_filter(motions, measurements))

## 2) You can generate your own test cases by generating
##    measurements using the generate_ground_truth function.
##    It will print the robot's last location when calling it.
##
##
number_of_iterations = 6
motions = [[2. * pi / 20, 12.] for row in range(number_of_iterations)]

x = generate_ground_truth(motions)
final_robot = x[0]
measurements = x[1]
estimated_position = particle_filter(motions, measurements)
print_measurements(measurements)
print('Ground truth:    ', final_robot)
print('Particle filter: ', estimated_position)
print('Code check:      ', check_output(final_robot, estimated_position))





<think> 我们之前讨论了递归状态空间滤波器,特别是卡尔曼滤波器(Kalman Filter)。现在用户询问粒子滤波器(Particle Filter)以及它与递归状态空间滤波器的关系。 粒子滤波器也是一种递归状态空间滤波器,但它采用不同的方法处理非线性非高斯系统。 粒子滤波器的核心思想: 1. 使用一组随机样本(粒子)来表示状态的后验概率分布。 2. 通过递归地预测、更新和重采样来估计状态。 与卡尔曼滤波器的比较: - 卡尔曼滤波器假设线性高斯模型,可以得到解析解(均值和协方差)。 - 粒子滤波器适用于非线性非高斯模型,通过蒙特卡洛方法近似后验分布。 粒子滤波器的基本步骤: 1. **初始化**:从先验分布$p(x_0)$中抽取$N$个粒子$\{x_0^{(i)}\}_{i=1}^N$,并赋予相等的权重$w_0^{(i)}=1/N$。 2. **递归滤波**(对于每个时刻$k$): a. **预测步(采样)**:根据状态转移模型$p(x_k|x_{k-1})$,从每个粒子$x_{k-1}^{(i)}$生成预测粒子$\tilde{x}_k^{(i)}$。 b. **更新步(权重更新)**:根据观测$z_k$,更新每个粒子的权重:$w_k^{(i)} \propto w_{k-1}^{(i)} \cdot p(z_k|\tilde{x}_k^{(i)})$,然后归一化权重。 c. **重采样**:根据权重重新采样$N$个新粒子$\{x_k^{(i)}\}_{i=1}^N$(权重重置为$1/N$),以避免粒子退化问题。 粒子滤波器的关键点: - 粒子数$N$:需要足够多的粒子来准确表示分布(通常至少1000个)[^3]。 - 重采样策略:如系统重采样、残差重采样等。 - 退化问题:经过几次迭代后,少数粒子权重很大,多数粒子权重接近零。重采样可以缓解,但可能引起样本贫化。 与递归状态空间滤波器的关联: - 两者都是贝叶斯滤波的实现方式,都遵循预测-更新框架。 - 卡尔曼滤波器是线性高斯下的最优滤波器,而粒子滤波器是非线性非高斯情况下的近似解法。 应用场景: - 机器人定位(蒙特卡洛定位) - 视觉目标跟踪 - 金融时间序列分析(波动率估计) 代码示例(简化粒子滤波器): ```python import numpy as np class ParticleFilter: def __init__(self, num_particles, transition_func, obs_likelihood): self.num_particles = num_particles self.transition = transition_func # 状态转移函数: x_{k} = f(x_{k-1}, noise) self.obs_likelihood = obs_likelihood # 观测似然函数: p(z_k | x_k) self.particles = None # 粒子集 self.weights = np.ones(num_particles) / num_particles def init_particles(self, prior_sample): self.particles = prior_sample def predict(self, process_noise): # 预测步:每个粒子根据状态转移模型传播 for i in range(self.num_particles): self.particles[i] = self.transition(self.particles[i], process_noise[i]) def update(self, obs): # 更新步:根据观测更新权重 for i in range(self.num_particles): self.weights[i] *= self.obs_likelihood(obs, self.particles[i]) self.weights /= np.sum(self.weights) # 归一化 def resample(self): # 重采样(系统重采样) indices = np.random.choice(self.num_particles, size=self.num_particles, p=self.weights) self.particles = self.particles[indices] self.weights = np.ones(self.num_particles) / self.num_particles def estimate(self): # 计算状态估计(加权平均) return np.average(self.particles, weights=self.weights, axis=0) ``` 优缺点: ✅ 优点: - 处理任意非线性/非高斯系统 - 实现相对简单 ❌ 缺点: - 计算成本高(粒子数需足够多) - 维数灾难(高维状态需要粒子数指数增长) 未来方向:自适应粒子数、优化重采样策略、与深度学习结合[^2]。 根据用户提供的引用[1],我们注意到在非线性变换下,粒子滤波器是一种常用的方法,而引用[3]则强调了粒子数设置的重要性。 因此,粒子滤波器是递归状态空间滤波器家族中处理非线性非高斯问题的重要成员。 §§相关问题§§ 1. 粒子滤波器中重采样方法有哪些改进策略? 2. 如何评估粒子滤波器的性能? 3. 粒子滤波器在机器人定位中的具体应用案例? 4. 粒子滤波器与扩展卡尔曼滤波器(EKF)在计算效率上有何差异? </think> ### 粒子滤波器(Particle Filter)详解 粒子滤波器(Particle Filter, PF)是一种基于蒙特卡洛方法的**非线性非高斯递归状态空间滤波器**,通过随机采样粒子群近似状态后验概率分布。它突破了卡尔曼滤波器对线性高斯系统的限制,适用于复杂动态系统估计[^1]。 #### 核心原理(对比卡尔曼滤波器) | 特性 | 粒子滤波器 | 卡尔曼滤波器 | |---------------------|-----------------------------------------------|----------------------------------| | **系统模型** | 非线性/非高斯系统 | 线性/高斯系统 | | **状态表示** | $N$个带权粒子$\{x_k^{(i)}, w_k^{(i)}\}_{i=1}^N$ | 高斯分布$(\hat{x}_k, P_k)$ | | **计算方式** | 蒙特卡洛采样 | 解析解 | | **内存需求** | $O(N \cdot \dim(x))$ | $O(\dim(x)^2)$ | #### 算法流程(递归三步法) 1. **预测步(粒子传播)** 根据状态转移模型扩散粒子: $$x_k^{(i)} = f(x_{k-1}^{(i)}, u_k, v_k^{(i)}), \quad v_k^{(i)} \sim p_v(\cdot)$$ 其中$f(\cdot)$为非线性状态方程,$v_k$为过程噪声。 2. **更新步(权重调整)** 根据观测$z_k$更新粒子权重: $$w_k^{(i)} = w_{k-1}^{(i)} \cdot p(z_k \mid x_k^{(i)})$$ 后归一化:$\tilde{w}_k^{(i)} = w_k^{(i)} / \sum_j w_k^{(j)}$ 观测似然$p(z_k \mid x_k^{(i)})$通常取高斯形式。 3. **重采样(Resampling)** 解决粒子退化问题(少数粒子占据大部分权重): ```mermaid graph LR A[高权重粒子] -- 复制 --> B[新粒子群] C[低权重粒子] -- 丢弃 --> D[淘汰] ``` 重采样后粒子权重重置为$1/N$,保持粒子总数不变[^3]。 #### 关键参数与优化 - **粒子数$N$**: 默认1000以上,需权衡精度与计算成本($N \uparrow$ → 精度$\uparrow$,速度$\downarrow$)[^3] - **重采样策略**: - 系统重采样(Systematic Resampling) - 残差重采样(Residual Resampling) - 避免样本贫化(Sample Impoverishment) #### 应用场景 1. **机器人定位**(蒙特卡洛定位) 在未知环境中通过粒子群表示位姿置信度 2. **视觉目标跟踪** 处理遮挡和非线性运动(如人体姿态估计) 3. **金融预测** 估计随机波动率模型(如Heston模型) 4. **神经科学** 解码神经元群动态编码信息[^2] ```python import numpy as np class ParticleFilter: def __init__(self, num_particles, transition_fn, obs_likelihood_fn): self.num_particles = num_particles self.transition = transition_fn # 状态转移函数 x_k = f(x_{k-1}, noise) self.obs_likelihood = obs_likelihood_fn # 观测似然函数 p(z|x) self.particles = None self.weights = np.ones(num_particles) / num_particles def init_particles(self, prior_samples): self.particles = prior_samples # 从先验分布采样初始化 def predict(self, process_noise): # 粒子通过状态方程传播 for i in range(self.num_particles): self.particles[i] = self.transition( self.particles[i], process_noise[i] ) def update(self, z_obs): # 根据观测更新权重 for i in range(self.num_particles): self.weights[i] *= self.obs_likelihood(z_obs, self.particles[i]) self.weights /= np.sum(self.weights) # 归一化 def resample(self): # 系统重采样 indices = np.random.choice( self.num_particles, size=self.num_particles, p=self.weights ) self.particles = self.particles[indices] self.weights = np.ones(self.num_particles) / self.num_particles def estimate(self): # 状态估计(加权平均) return np.average(self.particles, weights=self.weights, axis=0) ``` #### 优势与局限 ✅ **优势** - 处理**强非线性/非高斯**系统 - 实现简单,**并行化潜力大** - 可表示**多模态分布**(如同时跟踪多个假设) ❌ **局限** - **计算成本高**(尤其高维状态) - **粒子退化**问题(重采样引入样本贫化) - **参数敏感**(粒子数、建议分布选择) #### 改进方向 1. **自适应粒子数**:动态调整$N$平衡精度与效率 2. **优化建议分布**:采用UPF(Unscented Particle Filter)提升采样效率 3. **混合架构**:结合深度学习学习状态转移模型[^2] 粒子滤波器作为递归状态空间滤波器的重要扩展,为复杂动态系统估计提供了**概率框架下的通用解决方案**。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

颹蕭蕭

白嫖?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值