线性回归-原理详解+代码示例

一元一次线性回归

抽象问题

现有一组数据,共有两列,分别为x和y,如下所示

原数据

现将数据用python作散点图,观察其变化趋势

import pandas as pd 
import numpy as np
import matplotlib.pyplot as plt

# 读取数据并转为np.array的格式
train = pd.read_csv('click.csv').values 
# 读取所有行的第0列作为训练数据的x
train_x = train[:, 0] 
# 读取所有行的第1列作为训练数据的y
train_y = train[:, 1]
# 画散点图
plt.plot(train_x, train_y, 'o')
plt.show()
散点图

我们的问题是要找到x和y的对应关系,根据此对应关系,我们就可以在给定任意x的情况下,计算得到y的值。

定义模型

观察上面的散点图可以看出来,随着x的增大,y值也在近似的不断增大。据此,我们推测,这组数据可能符合一次函数的关系,即:

y\;=\;kx\;+\;b

此式可改写为
f_\theta(x)\;=\;\theta_0\;+\;\theta_1\cdot x

其中f_\theta(x)表示含有参数 θ ,与变量 x 相关的函数。该函数即为:一元一次线性回归模型。

:统计学中,常用 θ 来表示未知数和推测值,采用 θ 加数字下标的形式,是为了防止当未知数的数量增加时,表达式中大量出现a、b、c、d...这样的符号。这样不但不易理解,还容易出现符号本身不够用的情况。

只要求得了上述公式中的未知参数θ_0\theta_1,即知道了x和y的关系。

现在问题转化为求未知参数θ_0\theta_1

求解方法

已知1个自变量x^{(i)},根据模型公式f_\theta(x)\;=\;\theta_0\;+\;\theta_1\cdot x,可以求得与其对应的因变量f_\theta(x^{(i)})

那么y^{(i)} - f_\theta(x^{(i)})表示实际值与模型预测值之间的误差。

我们要求解的模型函数最理想的情况就是使得f_\theta(x^{(i)}) = y^{(i)},即令误差值y^{(i)} - f_\theta(x^{(i)})= 0

实际中,很难做到让所有点的误差都等于0,因此,我们要做的就是让所有点的误差之和尽可能的小。

我们一般使用误差的平方来表征误差,一方面是去除误差正负抵消的影响,一方面是为了使之后求解微分更加方便。

所有点的误差的平方和可以用如下公式来表示:
E(\theta)\;=\;\frac12\sum_{i=1}^n{(y^{(i)}-f_\theta(x^{(i)}))}^2

这个表达式称为目标函数E是Error的首字母。

现在,我们的问题转化为:求出使得目标函数E_{\theta}取最小值的参数\theta_0\theta_1

梯度下降法(最速下降法)

现有函数g(x)根据导数的意义:

  1. \frac{d}{dx}g(x)>0,即导数为正g(x)随x的增大而增大,因此,要使得g(x)减小,需要x负移

  2. \frac{d}{dx}g(x)<0,即导数为负g(x)随x的增大而减小,因此,要使得g(x)减小,需要x正移

据此可知,要使得g(x)不断减小,只需要将x向与导数符号相反的方向移动即可。用数学表达式表述为:
x\;:=\;x-\eta\frac d{dx}g(x)

  1. A:=B,表示通过B来定义A

  2. η,表示学习率

    η越小,更新次数越多,速度越慢

    η越大,更新次数越少,速度越快,但有可能导致不收敛。

梯度下降法的核心:根据误差函数的导数的符号来决定x移动的方向。

根据梯度下降法的原理,我们知道了怎样更新参数\theta_0\theta_1,就能使目标函数E(θ)的值达到最小。

参数更新方法如下
\left\{ \begin{aligned} \theta_0\;:=\;\theta_0\;-\;\eta\frac\partial{\partial\theta_0}E(\theta)\\ \theta_1\;:=\;\theta_1\;-\;\eta\frac\partial{\partial\theta_1}E(\theta) \end{aligned} \right.

现在只要计算出\frac\partial{\partial\theta_0}E(\theta)\frac\partial{\partial\theta_1}E(\theta),代入上式,就可不断更新参数,求得最佳的\theta_0\theta_1

计算上式中的两个偏导数过程如下:

\begin{aligned} \frac\partial{\partial\theta_0}E(\theta)\;&=\;\frac12\overset n{\underset{i=1}{\sum\;}}2\cdot(y^{(i)}-f_\theta(x^{(i)}))\cdot\frac{\partial(y^{(i)}-f_\theta(x^{(i)}))}{\partial\theta_0}\\ &=\overset n{\underset{i=1}{\sum\;}}(y^{(i)}-f_\theta(x^{(i)}))\cdot(-1)\cdot\frac{\partial(f_\theta(x^{(i)}))}{\partial\theta_0}\\ &=\overset n{\underset{i=1}{\sum\;}}(y^{(i)}-f_\theta(x^{(i)}))\cdot(-1)\cdot\frac{\partial(\theta_0\;+\;\theta_1\cdot x^{(i)})}{\partial\theta_0}\\ &=\overset n{\underset{i=1}{\sum\;}}(y^{(i)}-f_\theta(x^{(i)}))\cdot(-1)\cdot1\\ &=\overset n{\underset{i=1}{\sum\;}}(f_\theta(x^{(i)})-y^{(i)}) \end{aligned}

同理:

\begin{aligned} \frac\partial{\partial\theta_1}E(\theta)\;&=\;\frac12\overset n{\underset{i=1}{\sum\;}}2\cdot(y^{(i)}-f_\theta(x^{(i)}))\cdot\frac{\partial(y^{(i)}-f_\theta(x^{(i)}))}{\partial\theta_1}\\ &=\overset n{\underset{i=1}{\sum\;}}(y^{(i)}-f_\theta(x^{(i)}))\cdot(-1)\cdot\frac{\partial(f_\theta(x^{(i)}))}{\partial\theta_1}\\ &=\overset n{\underset{i=1}{\sum\;}}(y^{(i)}-f_\theta(x^{(i)}))\cdot(-1)\cdot\frac{\partial(\theta_0\;+\;\theta_1\cdot x^{(i)})}{\partial\theta_1}\\ &=\overset n{\underset{i=1}{\sum\;}}(y^{(i)}-f_\theta(x^{(i)}))\cdot(-1)\cdot x^{(i)}\\ &=\overset n{\underset{i=1}{\sum\;}}(f_\theta(x^{(i)})-y^{(i)})\cdot x^{(i)} \end{aligned}

因此,参数更新表达式如下:
\left\{ \begin{aligned} \theta_0\;&:=\theta_0-\eta\cdot \sum_{i=1}^n(f_\theta(x^{(i)}-y^{(i)}))\\ \theta_1\;&:=\theta_1-\eta\cdot \sum_{i=1}^n(f_\theta(x^{(i)}-y^{(i)}))\cdot x^{(i)} \end{aligned} \right.

根据该式,不断迭代,直到求得最佳的参数\theta_0\theta_1,即确定了模型表达式f_\theta(x)\;=\;\theta_0\;+\;\theta_1\cdot x

python实现

根据上述的理论及推导,我们知道:

  1. 模型表达式为
    f_\theta(x)\;=\;\theta_0\;+\;\theta_1\cdot x
  2. 目标函数为
    E(\theta)\;=\;\frac12\sum_{i=1}^n{(y^{(i)}-f_\theta(x^{(i)}))}^2
  3. 参数更新表达式为
    \left\{ \begin{aligned} \theta_0\;&:=\theta_0-\eta\cdot \sum_{i=1}^n(f_\theta(x^{(i)}-y^{(i)}))\\ \theta_1\;&:=\theta_1-\eta\cdot \sum_{i=1}^n(f_\theta(x^{(i)}-y^{(i)}))\cdot x^{(i)} \end{aligned} \right.

在进行参数更新之前,应该将数据标准化(也称z-score规范化)。这个步骤非必须,但是做了之后,参数的收敛会更快
z^{(i)}=\frac{x^{(i)}-\mu}\sigma

# 读取数据并转为np.array的格式
train = pd.read_csv('click.csv').values 
train_x = train[:, 0] 
train_y = train[:, 1]

# 对变量train_x进行标准化
mu = train_x.mean()
sigma = train_x.std()
def standardize(x):
    return (x-mu) / sigma
train_x_std = standardize(train_x)

python逐步实现

# 1,随机生成初始参数theta0 和 theta1
theta0, theta1 = np.random.rand(2)

# 2,定义模型函数f(x)
def f(x):
    y = theta0 + theta1 * x
    return y

# 3,定义目标函数E(x, y)
def E(x, y):
    e = 0.5 * np.sum((y-f(x)) ** 2)
    return e 

# 4,初始化学习率η
ETA = 0.001

# 5,初始化误差变化量diff
diff = 1

# 6,初始化更新次数count
count = 0 

# 7,迭代学习
error = E(train_x_std, train_y)    # 计算初始误差
# 开始迭代更新参数,直到误差的变化小于0.01
while diff > 0.01:
    # 更新参数theta0和theta1
    theta0 = theta0 - ETA * np.sum((f(train_x_std) - train_y))
    theta1 = theta1 - ETA * np.sum((f(train_x_std) - train_y) * train_x_std)
    # 计算更新参数后的当前误差
    error_current = E(train_x_std, train_y)
    # 计算原误差与当前误差的差值
    diff = error - error_current
    # 更新误差值
    error = error_current
    # 输出日志
    count += 1
    print(f'第{count}次:theta0 = {theta0:.3f}, theta1 = {theta1:.3f}, 差值 = {diff:.4f}')

部分输出日志如下

注:若执行多次,会发现迭代次数和误差的差值在每次执行时都不一样,这是因为随机初始化的参数\theta_0\theta_1的不同而导致的。

此时,最终参数为:

print(f'theta0 = {theta0:.3f}')
print(f'theta1 = {theta1:.3f}')

作图查看拟合结果如下

plt.plot(train_x_std, train_y, 'o')     # 标准化后散点图
x = np.linspace(-3, 3, 100)
plt.plot(x, f(x))                       # 拟合直线
plt.show()
线性拟合结果

sklearn方法

在实际使用中,机器学习库sklearn为我们提供了更模块化的方式来进行线性回归,如下所示

from sklearn.linear_model import LinearRegression
# 1, 定义线性回归模型
lr = LinearRegression()
# 2, 训练模型
lr.fit(train_x_std.reshape(-1,1), train_y)

# 查看tehta0 和 theta1
print(f'theta0 = {lr.intercept_}')
print(f'theta1 = {lr.coef_}')

结果如下所示

和使用python逐步计算得到的参数一致。

np.polyfit(X, y, n)方法

Numpy库也提供了线性拟合方法:Numpy.polyfit(X, y, n),使用方法如下所示:

# 一次线性拟合
z1 = np.polyfit(train_x_std, train_y, 1)
# 线性表示
p1 = np.poly1d(z1)
print(z1)
print(p1)

结果如下所示

和使用前两种方法得到的结果一致。

一元多次线性回归-多项式回归

观察前面做出的图像,发现一元一次线性模型f_\theta(x)\;=\;\theta_0\;+\;\theta_1\cdot x的拟合效果并不好。

实际上,对于给定的数据,曲线比直线拟合地更好。因此,我们重新定义模型函数为
f_\theta(x)\;=\;\theta_0+\;\theta_1\cdot x+\theta_2\cdot x^2该式为一元多次线性回归,即多项式拟合模型表达式

如果使用更高次数的表达式,如下所示,就能表示更复杂的曲线了f_\theta(x)\;=\;\theta_0+\;\theta_1\cdot x+\theta_2\cdot x^2+\theta_3\cdot x^3+\dots+\theta_n\cdot x^n次数越高,拟合的越好,但可能会出现过拟合问题

对于要解决的问题,在找出合适的表达式之前,需要不断地去尝试。

这里以二次拟合函数为例,我们增加了参数θ_2,此时:

  1. 模型表达式为
    f_\theta(x)\;=\;\theta_0+\;\theta_1\cdot x+\theta_2\cdot x^2
  2. 目标函数为
    E(\theta)\;=\;\frac12\sum_{i=1}^n{(y^{(i)}-f_\theta(x^{(i)}))}^2

使用同样的方式,对E(θ)进行微分,得到参数更新表达式如下:
\left\{ \begin{aligned} \theta_0\;&:=\;\theta_0\;-\;\eta\cdot\sum_{i=1}^n(f_\theta(x^{(i)})-y^{(i)})\\ \theta_1\;&:=\;\theta_1\;-\;\eta\cdot\sum_{i=1}^n(f_\theta(x^{(i)})-y^{(i)})\cdot x^{(i)}\\ \theta_2\;&:=\;\theta_2\;-\;\eta\cdot\sum_{i=1}^n(f_\theta(x^{(i)})-y^{(i)})\cdot x^{(i)^2} \end{aligned} \right.

即使增加参数(增加次数),比如有θ_3θ_4等,依然可以用同样的方法来求出参数更新表达式。

像这样增加函数中多项式的次数,然后再使用函数的分析方法被称为多项式回归

多元线性回归

前述的线性回归都是一元回归,即只有一个变量。

但是,实际中要解决的很多问题是变量超过2个的复杂问题。

例如:有三个变量,分别为x_1x_2x_3。此时,模型表达式为:
f_\theta(x_1,x_2,x_3)=\;\theta_0+\;\theta_1\cdot x_1+\theta_2\cdot x_2+\theta_3\cdot x_3

将此式推广到n个变量的情况,此时

模型表达式为:
f_\theta(x_1,\dots,x_n)=\;\theta_0+\;\theta_1\cdot x_1+\theta_2\cdot x_2+\dots+\theta_n\cdot x_n

我们可以把参数θ和变量x看作向量,用黑体表示
\boldsymbol{\theta}=\begin{bmatrix}\theta_0\\\theta_1\\\theta_2\\\vdots\\\theta_n\end{bmatrix},\quad \boldsymbol{x}=\begin{bmatrix}\begin{array}{c}x_0\\x_1\\x_2\end{array}\\\vdots\\x_n\end{bmatrix}

其中 x_0=1,则:
\theta^Tx\;=\;\theta_0\cdot x_0+\;\theta_1\cdot x_1+\theta_2\cdot x_2+\dots+\theta_n\cdot x_n

因此:f_\theta(x)=\theta^Tx

对目标函数E(\theta)=\frac{1}{2}\sum_{i=1}^n{(y^{(i)}-f_\theta(x^{(i)}))}^2θ_j的偏导如下:
\begin{aligned} \frac\partial{\partial\theta_j}E(\theta)\;&=\;\frac12\overset n{\underset{i=1}{\sum\;}}2\cdot(y^{(i)}-f_\theta(x^{(i)}))\cdot\frac{\partial(y^{(i)}-f_\theta(x^{(i)}))}{\partial\theta_j}\\&=\overset n{\underset{i=1}{\sum\;}}(y^{(i)}-f_\theta(x^{(i)}))\cdot(-1)\cdot\frac{\partial(f_\theta(x^{(i)}))}{\partial\theta_j}\\ &=\overset n{\underset{i=1}{\sum\;}}(y^{(i)}-f_\theta(x^{(i)}))\cdot(-1)\cdot\frac{\partial(\theta_0\cdot x_0^{(i)}+\theta_1\cdot x_1^{(i)}+\theta_2\cdot x_2^{(i)}+\dots+\theta_n\cdot x_n^{(i)})}{\partial\theta_j}\\&=\overset n{\underset{i=1}{\sum\;}}(y^{(i)}-f_\theta(x^{(i)}))\cdot(-1)\cdot x_j^{(i)}\\ &=\overset n{\underset{i=1}{\sum\;}}(f_\theta(x^{(i)})-y^{(i)})\cdot x_j^{(i)}\\ \end{aligned}

综上:

  1. 模型表达式为:
    f_\theta(x)=\;\theta_0\cdot x_0+\;\theta_1\cdot x_1+\theta_2\cdot x_2+\dots+\theta_n\cdot x_n\;=\;\boldsymbol\theta^{\mathbf T}\boldsymbol x\;

  2. 目标函数为:
    E(\theta)\;=\;\frac12\sum_{i=1}^n{(y^{(i)}-f_\theta(x^{(i)}))}^2

  3. 参数更新表达式为:
    \theta_j:=\theta_j-\eta\cdot\sum_{i=1}^n(f_\theta(x^{(i)})-y^{(i)})\cdot x_j^{(i)}

像这样包含了多个变量的回归称为多重回归。

采用矩阵求解参数

由于训练集数据有很多,所以我们把1行数据当作一个训练数据,以矩阵的形式来处理更方便。

n个变量(x_0,x_1,x_2,...,x_n),n个参数(θ_0,θ_1,θ_2,...,θ_n)n个训练数据的模型表达式如下:
\begin{aligned} f_\theta(x)=X\cdot\theta=\begin{bmatrix}{\mathbf x}_{\mathbf0}&{\mathbf x}_{\mathbf1}\;\;{\mathbf x}_{\mathbf2}\;\cdots\;\;{\mathbf x}_{\mathbf n}\end{bmatrix}\boldsymbol\cdot\begin{bmatrix}\theta_0\\\theta_1\\\vdots\\\theta_n\end{bmatrix}&=\begin{bmatrix}x_0^{(1)}&x_1^{(1)}&\cdots&x_n^{(1)}\\x_0^{(2)}&x_1^{(2)}&\cdots&x_n^{(2)}\\x_0^{(3)}&x_1^{(3)}&\cdots&x_n^{(3)}\\&&\vdots&\\x_0^{(n)}&x_1^{(n)}&\cdots&x_n^{(n)}\end{bmatrix}\cdot\begin{bmatrix}\theta_0\\\theta_1\\\vdots\\\theta_n\end{bmatrix}\\ &=\begin{bmatrix}\theta_0\cdot x_0^{(1)}+\theta_1\cdot x_1^{(1)}+\cdots+\theta_n\cdot x_n^{(1)}\\\theta_0\cdot x_0^{(2)}+\theta_1\cdot x_1^{(2)}+\cdots+\theta_n\cdot x_n^{(2)}\\\theta_0\cdot x_0^{(3)}+\theta_1\cdot x_1^{(3)}+\cdots+\theta_n\cdot x_n^{(3)}\\\vdots\\\theta_0\cdot x_0^{(n)}+\theta_1\cdot x_1^{(n)}+\cdots+\theta_n\cdot x_n^{(n)}\end{bmatrix} \end{aligned}

对于参数更新表达式:
\theta_j:=\theta_j-\eta\cdot\sum_{i=1}^n(f_\theta(x^{(i)})-y^{(i)})\cdot x_j^{(i)}
j=0时,求和项可展开为:
\sum_{i=1}^n(f_\theta(x^{(i)})-y^{(i)})\cdot x_0^{(i)}=(f_\theta(x^{(1)})-y^{(1)})\cdot x_0^{(1)}+(f_\theta(x^{(2)})-y^{(2)})\cdot x_0^{(2)}+\cdots+(f_\theta(x^{(n)})-y^{(n)})\cdot x_0^{(n)}

令:
\boldsymbol f=\begin{bmatrix}f_\theta(x^{(1)})-y^{(1)}\\f_\theta(x^{(2)})-y^{(2)}\\\vdots\\f_\theta(x^{(n)})-y^{(n)}\end{bmatrix},{\boldsymbol x}_{\mathbf0}=\begin{bmatrix}x_0^{(1)}\\x_0^{(2)}\\\vdots\\x_0^{(n)}\end{bmatrix}

则:

\sum_{i=1}^n(f_\theta(x^{(i)})-y^{(i)})\cdot x_0^{(i)}=\boldsymbol f^T\cdot{\boldsymbol x}_{\mathbf0}

Python逐步实现

对于多项式回归:

x_0=\begin{bmatrix}1\\1\\\vdots\\1\end{bmatrix},\;x_1=\begin{bmatrix}x^{(i)}\\x^{(i)}\\\vdots\\x^{(i)}\end{bmatrix},\;x_2=\begin{bmatrix}x^{(i)^2}\\x^{(i)^2}\\\vdots\\x^{(i)^2}\end{bmatrix},\;X=\lbrack{\boldsymbol x}_{\mathbf0}\;{x}_{\mathbf1}\;{\boldsymbol x}_{\mathbf2}\rbrack=\begin{bmatrix}1&x^{(i)}&x^{(i)^2}\\1&x^{(i)}&x^{(i)^2}\\&\vdots&\\1&x^{(i)}&x^{(i)^2}\end{bmatrix}

# 1,创建训练数据的矩阵X
def to_matrix(x):
    return np.vstack([np.ones(x.shape[0]), x, x**2]).T
X = to_matrix(train_x_std)

# 2,随机初始化参数Theta
theta = np.random.rand(3)

# 3,定义预测函数
def f(x):
    return np.dot(x, theta)

# 4, 初始化参数
diff = 1
ETA = 0.001

# 5, 迭代更新参数
error = E(X, train_y)
while diff > 0.01:
    theta = theta - ETA * np.dot(f(X)-train_y, X)
    current_error = E(X, train_y)
    diff = error - current_error
    error = current_error

运行结束后,得到参数如下

将结果绘图展示如下:

x = np.linspace(-3, 3, 100)
plt.plot(train_x_std, train_y, 'o')   # 原数据散点图
plt.plot(x, f(to_matrix(x)))             # 多项式拟合曲线
plt.show()

np.polyfit(X, y, n)方法

z2 = np.polyfit(train_x_std, train_y, 2)
p2 = np.poly1d(z2)
print(z2)
print(p2)

结果如下所示

与python逐步实现结果一致

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

推荐阅读更多精彩内容