从零开始强化学习(三)——表格型方法

三. 表格型方法(Tabular Methods)

强化学习的三个重要的要素:状态、动作和奖励。强化学习智能体跟环境是一步一步交互的,就是先观察一下状态,然后再输入动作。再观察一下状态,再输出动作,拿到这些reward。它是一个跟时间相关的序列决策的问题

这样的一个状态转移概率是具有马尔可夫性质的(系统下一时刻的状态仅由当前时刻的状态决定,不依赖于以往任何状态)。因为这个状态转移概率,它是下一时刻的状态是取决于当前的状态,它和之前的s_{t-1}s_{t-2}都没有什么关系。然后再加上这个过程也取决于智能体跟环境交互的这个a_t,所以有一个决策的一个过程在里面,就称这样的一个过程为马尔可夫决策过程(Markov Decision Process,MDP)

MDP就是序列决策这样一个经典的表达方式。MDP也是强化学习里面一个非常基本的学习框架。状态、动作、状态转移概率和奖励(S,A,P,R),这四个合集就构成了强化学习MDP的四元组,后面也可能会再加个衰减因子构成五元组

3.1 无环境交互(model-base)

状态转移概率函数+奖励函数=>已知的马尔可夫决策过程(环境已知)=>策略迭代+价值迭代=>最佳策略

把可能的动作和可能的状态转移的关系画成一个树状图。它们之间的关系就是从s_ta_t,再到s_{t+1} ,再到a_{t+1},再到s_{t+2}这样子的一个过程

跟环境交互,只能走完整的一条通路。这里面产生了一系列的一个决策的过程,就是跟环境交互产生了一个经验。使用概率函数(probability function)奖励函数(reward function)来去描述环境。概率函数就是状态转移的概率,概率函数实际上反映的是环境的一个随机性。当知道概率函数和奖励函数时,这个MDP就是已知的,可以通过策略迭代(policy iteration)价值迭代(value iteration)来找最佳的策略。如果知道这些状态转移概率和奖励函数的话,就说这个环境是已知的,因为我们是用这两个函数去描述环境的,如果是已知的话,其实可以用动态规划去计算说,很多强化学习的经典算法都是model-free的,就是环境是未知的

3.2 有环境交互(model-free)

价值函数+Q函数=>评价=>选取最大奖励的动作

在一个未知的环境里的,也就是这一系列的决策的概率函数和奖励函数是未知的,这就是model-basedmodel-free的一个最大的区别。强化学习就是可以用来解决用完全未知的和随机的环境。强化学习要像人类一样去学习,人类学习的话就是一条路一条路地去尝试一下,先走一条路,看看结果到底是什么。多试几次,只要能活命的。可以慢慢地了解哪个状态会更好:

  • 用价值函数V(s)来代表这个状态是好的还是坏的
  • Q函数来判断说在什么状态下做什么动作能够拿到最大奖励,用Q函数来表示这个状态-动作值

3.3 model-base与model-free的本质区别

当使用概率转移函数p[s_{t+1},r_t|s_t,a_t]和奖励函数r[s_t,a_t]来描述环境

  • model-base

    • 当我们知道概率函数和奖励函数时,马尔可夫决策过程已知,可以采用策略迭代或价值迭代获得智能体的最优策略,这个过程Agent没有与环境进行交互
    • 在很多实际的问题中,MDP的模型有可能是未知的,也有可能模型太大了,不能进行迭代的计算。比如Atari游戏、围棋、控制直升飞机、股票交易等问题,这些问题的状态转移太复杂了
  • model-free

    • 当概率函数和奖励函数时,处于未知的环境中,让智能体与环境进行交互,采集很多轨迹数据,Agent从轨迹中获取信息改进策略,从而获得更多的奖励,目前大部分领域的AI应用都是model-free模式

3.4 Q表格(Q-table)

如果Q表格是一张已经训练好的表格的话,那这一张表格就像是一本生活手册

这张表格里面Q函数的意义就是选择了这个动作之后,最后面能不能成功,就是需要去计算在这个状态下,选择了这个动作,后续能够一共拿到多少总收益。如果可以预估未来的总收益的大小,就知道在当前的这个状态下选择哪个动作,价值更高,选择某个动作是因为未来可以拿到的那个价值会更高一点。所以强化学习的目标导向性很强,环境给出的奖励是一个非常重要的反馈,它就是根据环境的奖励来去做选择

但有的时候把目光放得太长远不好,因为如果事情很快就结束的话,考虑到最后一步的收益无可厚非。如果是一个持续的没有尽头的任务,即持续式任务(Continuing Task),把未来的收益全部相加,作为当前的状态价值就很不合理

在这个环境当中,去计算状态动作价值(未来的总收益)可使用γ折扣因子:考虑累积收益,并且对于未来的收益也做考虑,但是越后面的收益对当前价值影响越小,因此γ∈[0,1]

State Aciton 1 Aciton 2 Aciton 3 Aciton 4
(1,1) 0 0 0 0
(1,2) 0 0 0 0
(2,1) 0 0 0 0
(2,2) 0 0 0 0

类似于上表,最后我们要求解的就是一张Q表格

  • 它的行数是所有的状态数量,一般可以用坐标来表示格子的状态,也可以用1、2、3、4、5、6、7来表示不同的位置
  • Q表格的列表示四个动作Action space

最开始这张Q表格会全部初始化为零,然后agent会不断地去和环境交互得到不同的轨迹,当交互的次数足够多的时候,就可以估算出每一个状态下,每个行动的平均总收益去更新这个Q表格。怎么去更新Q表格就是接下来要引入的强化概念。

强化就是可以用下一个状态的价值来更新当前状态的价值,其实就是强化学习里面bootstrapping的概念。在强化学习里面,可以每走一步更新一下Q表格,然后用下一个状态的Q值来更新这个状态的Q值,这种单步更新的方法叫做时序差分

3.5 无模型交互预测(model-free Prediction)

在没法获取MDP的模型情况下,可以通过以下两种方法来估计某个给定策略的价值:

  • (蒙特卡罗策略评估)Monte Carlo policy evaluation
  • (时序差分策略评估)Temporal Difference(TD) learning
3.5.1 蒙特卡罗(Monte-Carlo,MC)

思想:基于采样的方法,通过让agent跟环境进行交互,就会得到很多轨迹。每个轨迹都有对应的 return:
G_{t}=R_{t+1}+\gamma R_{t+2}+\gamma^{2} R_{t+3}+\ldots

蒙特卡洛是用经验平均回报(emmpirical mean return)的方法来估计的,即把每个轨迹的return进行平均,就可以知道某一个策略下面对应状态的价值

算法步骤:


  1. 在时间步t,状态s被访问:
  • 状态s的访问数增加1:N(s)=N(s)+1
  • 状态s的总的回报S(s)增加G_tS(s)←S(s)+G_t
  1. 通过回报的平均估计状态s的价值:v(s)=S(s)/N(s)

假设现在有样本x_1,x_2,\cdots,可以把经验均值(empirical mean)转换成增量均值(incremental mean)的形式,如下式所示:
\begin{aligned} \mu_{t} &=\frac{1}{t} \sum_{j=1}^{t} x_{j} \\ &=\frac{1}{t}\left(x_{t}+\sum_{j=1}^{t-1} x_{j}\right) \\ &=\frac{1}{t}\left(x_{t}+(t-1) \mu_{t-1}\right) \\ &=\frac{1}{t}\left(x_{t}+t \mu_{t-1}-\mu_{t-1}\right) \\ &=\mu_{t-1}+\frac{1}{t}\left(x_{t}-\mu_{t-1}\right) \end{aligned}
通过这种转换,就可以把上一时刻的平均值跟现在时刻的平均值建立联系,即:
\mu_t = \mu_{t-1}+\frac{1}{t}(x_t-\mu_{t-1})
其中:

  • x_t- \mu_{t-1}是残差
  • \frac{1}{t}类似于学习率(learning rate)

当得到x_t,就可以用上一时刻的值来更新现在的值:
v(s_t)\leftarrow v(s_{t-1})+\alpha(G_t-v(s_{t-1}))
比较动态规划法和蒙特卡洛方法的差异

动态规划法:通过上一时刻的值的值。不断迭代,直到达到收敛:v_t(s) \leftarrow \sum\limits_{a \in A} \pi(a|s)(R(s,a)+\gamma \sum\limits_{s' \in S}P(s'|s,a)v_{i-1}(s')),动态规划也是常用的估计价值函数的方法。在动态规划里面,我们使用了bootstrapping的思想。bootstrapping的意思就是基于之前估计的量来估计一个

蒙特卡洛方法:通过一个回合的经验平均回报进行更新:v(s_t)\leftarrow v(s_{t-1})+\alpha(G_t-v(s_{t-1}))MC是通过empirical mean return(实际得到的收益)来更新它,对应树上面蓝色的轨迹,得到是一个实际的轨迹,实际的轨迹上的状态已经是决定的,采取的行为都是决定的。MC得到的是一条轨迹,这条轨迹表现出来就是这个蓝色的从起始到最后终止状态的轨迹。现在只是更新这个轨迹上的所有状态,跟这个轨迹没有关系的状态都没有更新

结论

  • 蒙特卡洛可以在未知环境中使用,而动态规划是model-based
  • 蒙特卡洛只需要更新一条轨迹的状态,动态规划需要更新所有的状态。状态数量很多的时候,DP这样去迭代的话,速度是非常慢的。这也是sample-based的方法MC相对于DP的优势
3.5.2 时序差分学习(Temporal-Difference Learning,TD)

思想:时序差分(TD)学习是蒙特卡洛思想和动态规划思想的完美结合。一方面,像蒙特卡洛方法一样,时序差分不需要知道环境的动力学模型,可以从经历的回合中直接学习;另一方面,像动态规划一样,时序差分更新估计值基于部分已知的估计值,不需要等到最后的结果(完整的回合),这就是引导bootstrapping的思想。因此,TD是无模型的,通过引导,从不完整的回合中学习,用一个估计来更新另一个估计。

目的:对于给定策略\pi,在线地算出它的价值函数v^\pi,即对于某个给定的策略,在线(online)地算出它的价值函数:
v(s_t)←v(s_t)+α(G_t^n−v(s_t))
最简单的算法是TD(0),每往前走一步,就做一步bootstrapping,用得到的估计回报(estimated return)来更新上一时刻的值。估计回报R_{t+1}+\gamma v(S_{t+1})被称为 TD目标(TD target)TD目标是带衰减的未来收益的总和。TD目标由两部分组成:

  • 走了某一步后得到的实际奖励:R_{t+1}
  • 利用bootstrapping的方法,通过之前的估计来估计v(S_{t+1}),然后加了一个折扣系数,即\gamma v(S_{t+1}),具体过程如下式所示:

\begin{aligned} v(s)&=\mathbb{E}\left[G_{t} \mid s_{t}=s\right] \\ &=\mathbb{E}\left[R_{t+1}+\gamma R_{t+2}+\gamma^{2} R_{t+3}+\ldots \mid s_{t}=s\right] \\ &=\mathbb{E}\left[R_{t+1}|s_t=s\right] +\gamma \mathbb{E}\left[R_{t+2}+\gamma R_{t+3}+\gamma^{2} R_{t+4}+\ldots \mid s_{t}=s\right]\\ &=R(s)+\gamma \mathbb{E}[G_{t+1}|s_t=s] \\ &=R(s)+\gamma \mathbb{E}[v(s_{t+1})|s_t=s]\\ \end{aligned}

TD目标是估计有两个原因:它对期望值进行采样,并且使用当前估计v而不是真实v_{\pi}

TD误差(TD error)\delta=R_{t+1}+\gamma v(S_{t+1})-v(S_t)

可以类比于Incremental Monte-Carlo的方法,写出如下的更新方法:
v\left(S_{t}\right) \leftarrow v\left(S_{t}\right)+\alpha\left(R_{t+1}+\gamma v\left(S_{t+1}\right)-v\left(S_{t}\right)\right)
时序差分目标是带衰减的未来收益的总和。对于n步时序差分:
G_t^1=R_{t+1}+γv(S_{t+1})\\ G_t^2=R_{t+1}+γR_{t+2}+γv(S_{t+2})\\ ⋮\\ G_t^n=R_{t+1}+γR_{t+2}+⋯+γ^{n−1}R_{t+n}+γ^nv(S_{t+n})
即当n\rightarrow \infty时,时序差分变成了蒙特卡罗

3.5.3 比较蒙特卡洛和时序差分法
  • 蒙特卡洛增量式:
    v(s_t) \leftarrow v(s_t)+\alpha(G_t-v(s_t))\\ G_t = R_{t+1}+\gamma R_{t+2}+\cdots+\gamma^k R_{t+1+k}

  • 时序差分增量式:
    v(s_t) \leftarrow v(s_t)+\alpha(G_t^n-v(s_t)) \\ TD(0): G_t^0=R_{t+1}+\gamma v(s_{t+1}) \\ TD(n):G_t^n = R_{t+1}+\gamma R_{t+2}+\cdots+\gamma^{n-1} R_{t+n}+\gamma^n v(s_{t+n})

时序差分可以在不完整的序列上学习,蒙特卡洛只能在完整的序列上学习

时序差分可以在连续的环境下(无终止)学习,蒙特卡洛只能在有终止的情况下学习

时序差分可以在线学习,每走一步就可以更新。蒙特卡洛必须等到序列结束才能学习

3.6 无模型交互控制(model-free comtrol)

policy iteration进行一个广义的推广,使它能够兼容MCTD的方法,即Generalized Policy Iteration(GPI) with MC and TD

策略迭代:

Policy iteration由两个步骤组成:

  • 根据给定的当前的policy \pi来估计价值函数
  • 得到估计的价值函数后,通过greedy的方法来改进它的算法

这两个步骤是一个互相迭代的过程,由于不知道奖励函数和状态转移,无法使用策略迭代进行优化。因此我们引入广义策略迭代的方法

广义策略迭代(蒙特卡洛估计Q函数):

  • 用蒙特卡洛方法代替动态规划估计Q函数Q(s_t,a_t) \leftarrow Q(s_t,a_t)-\frac{1}{N(s_t,a_t)}(G(t)-Q(s_t,a_t))
  • 假设每一个回合都有一个探索性开始\ \epsilon,保证所有的状态和动作都在无限步的执行后能被采样到

蒙特卡洛求Q表的过程

在每一个策略迭代回合中

  • 采用蒙特卡洛策略估计
    • 首先采集数据,获得一条新的轨迹,计算轨迹上各个状态的真实回报G(t)=R_{t+1}+\gamma R_{t+2}+\cdots+\gamma^k R_{t+1+k}
    • 采用增量的方法更新Q:Q(s_t,a_t) \leftarrow Q(s_t,a_t)-\frac{1}{N(s_t,a_t)}(G(t)-Q(s_t,a_t))
  • 基于贪心思想进行策略更新:
    • \pi(s)=\arg \max\limits_a q(s,a)
  • 一个策略迭代回合结束,采样数据进入新的一回合。当Q函数趋于收敛后,迭代过程结束,即获得了每个状态的Q函数
3.6.1 基于ε-贪心探索的蒙特卡罗算法(ε-greedy)

ε-贪心(ε-greedy)搜索:有1-ε的概率会按照Q-function来决定action,通常ε就设一个很小的值,1-ε可能是90%,也就是90%的概率会按照Q-function来决定action,但是有10%的机率是随机的。通常在实现上ε会随着时间递减。在最开始的时候。因为还不知道那个action是比较好的,所以会花比较大的力气在做exploration。接下来随着训练的次数越来越多。已经比较确定说哪一个Q是比较好的。就会减少exploration,把ε的值变小,主要根据Q-function来决定action,比较少做random,这是ε-greedy

算法流程如下:

因为时序差分相比于蒙特卡洛方法有如下优势:低方差,能够在线学习,能够从不完整序列中学习。因此可以把时序差分放到控制循环中估计Q表格,再采用ε-greedy改进探索

3.6.2 同策略时序差分控制(Sarsa)
  • 特点

    将时序差分更新V的过程,变成了更新Q:

Q(s_t,a_t) \leftarrow Q(s_t,a_t)+\alpha[R_{t+1}+\gamma Q(s_{t+1},a_{t+1})-Q(s_t,a_t)]

每次更新值函数都需要知道当前状态、当前动作、奖励、下一步状态、下一步动作A’,即(s_t,a_t,R_{t+1},s_{t+1},a_{t+1})之后,就可以做一次更新。A’是下一步骤一定会执行的动作

  • 更新公式
    单步更新法:
    每执行一个动作,就会更新以此价值和策略。单步Q收获为:
    q_t=R_{t+1}+\gamma Q(s_{t+1},a_{t+1})
    其中a_{t+1}为下一步骤一定会执行的动作。单步Sarsa更新公式为:
    Q(s,a) \leftarrow Q(s,a)+\alpha[R_{t+1}+\gamma Q(s',a')-Q(s,a)]

n步Sarsa(n-step Sarsa):
在执行n步之后再来更新Q函数和策略。n步Q收获为:
q_t^n=R_{t+1}+\gamma R_{t+2}+\cdots+\gamma^{n-1}R_{t+n}+\gamma^n
如果给q_t^{(n)}加上折扣因子\gamma并进行求和,即可得到Sarsa(λ)Q收获
q_t^\lambda=(1-\lambda)\sum_{n=1}^\infty \lambda^{n-1}q_t^{(n)}
综上所述,将Q收获带入增量公式可得,n步Sarsa(λ)更新公式为:
Q(s_t,a_t) \leftarrow Q(s_t,a_t)+\alpha(q_t^\lambda-Q(s_t,a_t))

  • Sarsa所作出的改变很简单,就是将原本TD更新V的过程,变成了更新Q,就是说可以拿下一步的Q值Q(S_{t+_1},A_{t+1})更新这一步的Q值Q(S_t,A_t)。Sarsa是直接估计Q-table,得到Q-table后,就可以更新策略。该算法由于每次更新值函数需要知道当前的状态(state)、当前的动作(action)、奖励(reward)、下一步的状态(state)、下一步的动作(action),(S_{t}, A_{t}, R_{t+1}, S_{t+1}, A_{t+1})这几个值 ,由此得名Sarsa算法。它走了一步之后,拿到了(S_{t}, A_{t}, R_{t+1}, S_{t+1}, A_{t+1})之后,就可以做一次更新

代码演示:

class Sarsa(object):
    def __init__(self, n_actions,cfg,):
        self.n_actions = n_actions  
        self.lr = cfg.lr  
        self.gamma = cfg.gamma  
        self.sample_count = 0 
        self.epsilon_start = cfg.epsilon_start
        self.epsilon_end = cfg.epsilon_end
        self.epsilon_decay = cfg.epsilon_decay 
        self.Q  = defaultdict(lambda: np.zeros(n_actions)) # Q table
    def choose_action(self, state):
        self.sample_count += 1
        self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * math.exp(-1. * self.sample_count / self.epsilon_decay) # The probability to select a random action, is is log decayed
        best_action = np.argmax(self.Q[state])
        action_probs = np.ones(self.n_actions, dtype=float) * self.epsilon / self.n_actions
        action_probs[best_action] += (1.0 - self.epsilon)
        action = np.random.choice(np.arange(len(action_probs)), p=action_probs) 
        return action
    def predict_action(self,state):
        return np.argmax(self.Q[state])
    def update(self, state, action, reward, next_state, next_action,done):
        Q_predict = self.Q[state][action]
        if done:
            Q_target = reward  # terminal state
        else:
            Q_target = reward + self.gamma * self.Q[next_state][next_action] 
        self.Q[state][action] += self.lr * (Q_target - Q_predict) 
3.6.3 Q学习(Q-Learing)

Sarsa是一种同策略on-policy策略。Sarsa优化的是它实际执行的策略,它直接拿下一步会执行的action来去优化Q表格,所以on-policy在学习的过程中,只存在一种策略,它用一种策略去做action的选取,也用一种策略去做优化。所以Sarsa知道下一步的动作有可能会跑到悬崖那边去,所以它就会在优化它自己的策略的时候,会尽可能的离悬崖远一点。这样子就会保证说,它下一步哪怕是有随机动作,它也还是在安全区域内

异策略off-policy在学习的过程中,有两种不同的策略:

  • 第一个策略是需要去学习的策略,即target policy(目标策略),一般用\pi来表示,target policy就像根据自己的经验来学习最优的策略,不需要去和环境交互
  • 另外一个策略是探索环境的策略,即behavior policy(行为策略),一般用\mu来表示。\mu可以大胆地去探索到所有可能的轨迹,然后把采集到的数据喂给target policy去学习。而且喂给目标策略的数据中并不需要A_{t+1},而Sarsa是要有A_{t+1}的。Behavior policy可以在环境里面探索所有的动作、轨迹和经验,然后把这些经验交给目标策略去学习。比如目标策略优化的时候,Q-learning不会管你下一步去往哪里探索,它就只选收益最大的策略

Off-policy learning有很多好处:

  • 可以利用exploratory policy来学到一个最佳的策略,学习效率高
  • 可以学习其他agent的行为,学习人或者其他agent产生的轨迹
  • 重用老的策略产生的轨迹。探索过程需要很多计算资源,这样的话,可以节省资源。

Q-learning有两种policy:behavior policytarget policy

  • Target policy \pi直接在Q-table上取greedy,就取它下一步能得到的所有状态,如下式所示:

\pi\left(S_{t+1}\right)=\underset{a^{\prime}}{\arg \max}~ Q\left(S_{t+1}, a^{\prime}\right)

  • Behavior policy \mu可以是一个随机的policy,但采取 ε-greedy,让behavior policy不至于是完全随机的,它是基于Q-table逐渐改进的

于是可以构造Q-learning target,Q-learning的next action都是通过argmax操作来选出来的,于是可以代入argmax操作,可以得到下式:
\begin{aligned} R_{t+1}+\gamma Q\left(S_{t+1}, A^{\prime}\right) &=R_{t+1}+\gamma Q\left(S_{t+1},\arg \max ~Q\left(S_{t+1}, a^{\prime}\right)\right) \\ &=R_{t+1}+\gamma \max _{a^{\prime}} Q\left(S_{t+1}, a^{\prime}\right) \end{aligned}
接着可以把Q-learning更新写成增量学习的形式,TD target就变成max的值,即
Q\left(S_{t}, A_{t}\right) \leftarrow Q\left(S_{t}, A_{t}\right)+\alpha\left[R_{t+1}+\gamma \max _{a} Q\left(S_{t+1}, a\right)-Q\left(S_{t}, A_{t}\right)\right]

代码演示:

class QLearning(object):
    def __init__(self,n_states,
                 n_actions,cfg):
        self.n_actions = n_actions 
        self.lr = cfg.lr  # 学习率
        self.gamma = cfg.gamma  
        self.epsilon = 0 
        self.sample_count = 0  
        self.epsilon_start = cfg.epsilon_start
        self.epsilon_end = cfg.epsilon_end
        self.epsilon_decay = cfg.epsilon_decay
        self.Q_table  = defaultdict(lambda: np.zeros(n_actions)) # 用嵌套字典存放状态->动作->状态-动作值(Q值)的映射,即Q表
    def choose_action(self, state):
        self.sample_count += 1
        self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \
            math.exp(-1. * self.sample_count / self.epsilon_decay) # epsilon是会递减的,这里选择指数递减
        # e-greedy 策略
        if np.random.uniform(0, 1) > self.epsilon:
            action = np.argmax(self.Q_table[str(state)]) # 选择Q(s,a)最大对应的动作
        else:
            action = np.random.choice(self.n_actions) # 随机选择动作
        return action
    def predict(self,state):
        action = np.argmax(self.Q_table[str(state)])
        return action
    def update(self, state, action, reward, next_state, done):
        Q_predict = self.Q_table[str(state)][action] 
        if done: # 终止状态
            Q_target = reward  
        else:
            Q_target = reward + self.gamma * np.max(self.Q_table[str(next_state)]) 
        self.Q_table[str(state)][action] += self.lr * (Q_target - Q_predict)
3.6.4 同策略on-policy和异策略off-policy的区别
  • 同策略on-policy
    • 使用同一策略\pi学习和与环境进行交互
    • 兼顾探索和利用
    • 胆小的,策略不稳定
  • 异策略off-policy
    • 分离了目标策略\pi和行为策略\mu
    • 不需要兼顾探索和利用
    • 激进的

总结:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容