DBB重参数化

https://hub.fastgit.org/DingXiaoH/DiverseBranchBlock

https://arxiv.org/abs/2103.13425

CVPR 2021

https://blog.csdn.net/u010087277/article/details/115488455

我们提出了一种通用的卷积神经网络构造块(ConvNet),以提高性能而不需要任何推理时间开销。该块被称为多样分支块(DBB),它通过组合不同规模和复杂度的多样分支来丰富特征空间,包括卷积序列、多尺度卷积和平均池,从而增强单个卷积的表示能力。经过训练后,DBB可以等效地转换为单个conv层进行部署。与新型ConvNet架构的进步不同,DBB在维护宏架构的同时使训练时微观结构复杂化,因此它可以作为任何架构的常规conv层的替代品。通过这种方式,可以训练模型达到更高的性能水平,然后将其转换为原始的推理时间结构进行推理。DBB在图像分类(比ImageNet的top-1准确率高出1.9%)、对象检测和语义分割方面改进了CONVNET。

1.介绍

提高卷积神经网络(ConvNet)的性能一直是一个热门的研究课题。一方面,架构设计的进步,例如初始模型[27,28,26,15],揭示了多分支拓扑和不同规模和复杂度的各种路径的组合可以丰富特征空间并提高性能。然而,复杂的结构通常会减慢推理速度,因为小型运算符的组合(例如,1×1 conv的串联和池)对具有强大并行计算能力的设备(如GPU)不友好[21]。

另一方面,更多的参数和连接通常会带来更高的性能,但由于业务需求和硬件限制,我们部署的ConvNet的大小不能任意增加。考虑到这一点,我们通常通过性能和推理时间成本(如延迟、内存占用和参数数量)之间的权衡来判断ConvNet的质量。在一般情况下,我们在强大的GPU工作站上对模型进行训练,并将其部署到效率敏感的设备上,因此我们认为可以接受更多的训练资源的成本来提高性能,只要部署的模型保持相同的大小。

在本文中,我们试图在众多ConvNet架构中插入复杂的结构,以提高性能,同时保持原始的推理时间开销。为此,我们将训练时间和推理时间网络结构解耦,只在训练期间使模型复杂化,并将其转换回原始推理结构。当然,我们要求这些额外的训练时间结构1)有效地提高训练时间模型的性能,2)能够转换为原始的推理时间结构。

为了可用性和通用性,我们将基本的ConvNet组件K×K conv升级为一个功能强大的块,名为多样化分支块(DBB)(图1)。作为一个构建块,DBB是对改进ConvNet的其他努力的补充,例如架构设计[12,24,13,21,23,35]、神经架构搜索[2,39,22,20,19]、数据扩充和训练方法[25,5,33],并通过以下两个特性满足上述两个要求:

•    DBB采用具有多尺度卷积、串接1×1-K×K卷积、平均池和分支相加的多分支拓扑。与Inception架构一样,具有不同复杂度的各种感受野和路径的操作可以丰富功能空间。

•    DBB可以等效地转换为单个conv进行推理。给定一个架构,我们可以用DBB替换一些常规的conv层,以构建更复杂的用于训练的子架构,并将其转换回原始结构,这样就不会有额外的推理时间开销。

更准确地说,我们不会在每次forwarding之前导出用于推断的参数。相反,我们在训练一次后转换模型,然后我们只保存并使用结果模型,并且训练后的模型可以被丢弃。将DBB转换为conv的想法可归类为结构重新参数化,这意味着使用从另一个结构转换的参数以及并行工作对结构进行参数化[9]。虽然DBB和常规conv层具有相同的推理时间结构,但前者具有更高的表征能力。通过一系列烧蚀实验,我们将这种有效性归因于集成Inception单元的差异化的连接(具有不同规模和复杂性的路径),以及批量归一化带来的训练期间非线性[15]。与一些具有重复路径或纯线性分支的对应项相比(图6),DBB显示出更好的性能(表4)。

我们将我们的贡献总结如下。

•    我们建议将丰富的微结构整合到各种ConvNet架构中,以提高性能,同时保持原有的宏架构。

•    我们提出了DBB,一种通用构建块,并总结了六种转换(图2),将DBB转换为单个卷积,因此对用户来说是无代价的。

•    我们展示了一个具有代表性的类似于Inception的DBB实例,并表明它提高了ImageNet[7](例如,top-1精度提高了1.9%)、COCO检测[18]和城市景观[4]的性能。

2.    相关工作

2.1. 多分支架构

Inception[15,26,27,28]架构采用多分支结构来丰富特征空间,这证明了不同连接、不同感受野和多分支组合的重要性。DBB借用了使用多分支拓扑的思想,但区别在于1)DBB是一个可用于多种架构的构建块,2)DBB的每个分支都可以转换为一个conv,因此这些分支的组合可以合并为一个conv,这比实际的Inception单元快得多。我们将展示不同分支相对于重复分支的优势(表4),最有趣的发现是,将两个具有不同表征能力的分支(例如,1×1 conv和3×3 conv)组合起来比两个大容量分支(例如,两个3×3卷积)更好,这反过来可能会为ConvNet架构设计带来启示。

2.2. ConvNet组件以获得更好的性能

有一些新的组件可以改进convnet。例如,挤压和激励(SE)块[14]和Efficient Convolutional Attention(ECA)块[29]利用注意机制重新校准特征,Octave卷积[3]减少规则卷积的空间冗余,可变形卷积[6]增加具有可学习偏移的空间采样位置,扩张卷积扩展了感受野[32],模糊池[34]恢复了移位不变性等。DBB是这些组件的补充,因为它只升级了一个基本构建块:conv层。

2.3. 结构再参数化

本文和并行工作RepVGG[9]是第一个使用结构重新参数化来定义使用从另一个结构转换的参数对结构进行参数化的方法。ExpandNet[10]、DO Conv[1]和ACNet[8]也可以归类为结构重新参数化,因为它们将块转换为Conv。例如,ACNet使用非对称卷积块(ACB,如图6d所示)来加强Conv内核的骨架(即交叉部分)。与DBB相比,它还被设计为在不增加额外推理时间成本的情况下改进ConvNet。然而,不同之处在于,ACNet的动机是观察到skeleton的参数在大小上更大,因此试图使其更大,而我们关注的是不同的角度。我们发现平均池、1×1 conv和1×1-K×K顺序conv更有效,因为它们提供了不同复杂度的路径,并且允许使用更多的训练期间非线性。此外,ACB可以被视为DBB的特例,因为1×K和K×1 conv层可以通过变换VI(图2)扩充到K×K,并通过变换II合并到方形核中。

2.4. 其他ConvNet重新参数化方法

有些工作可以称为重新参数化,但不能称为结构重新参数化。例如,最近的NAS[20,39]方法[2]使用元内核对内核进行重新参数化,并将这些元内核的宽度和高度补充到搜索空间中。软条件计算(SCC)[31]或CondConv[30]可被视为与数据相关的内核重新参数化,因为它为相同形状的多个内核生成权重,然后导出一个内核作为所有此类内核的加权和,以参与卷积。请注意,SCC在部署的模型中引入了大量参数。这些重新参数化方法不同于ACB和DBB,前者的“重新参数化”意味着使用一些元参数(例如,元核[2])导出一组新参数,然后将新参数用于其他计算,而后者意味着将训练模型的参数转换为另一个参数化。

3.    多分支块

3.1. 卷积的线性

具有C个输入通道、D个输出通道和核大小K×K的conv层的参数在conv核中,它是一个四阶张量F∈ R^{D×C×K×K}和可选偏置b\in R^D。它输入一个C通道的特征图I∈ R^{C×H×W},输出一个D通道的特征图O∈ R^{D×H’×W’},其中H’和W’取决于K、填充和步幅。我们使用⊛表示卷积操作,并将偏置b复制为REP(b)\in R^{D×H’×W’},并将其加到卷积操作的结果上。Formally,

O=I⊛F+REP(b)    (1)

输出的第j个通道在(h,w)处的值为:

其中X(c,h,w)\in R^{K×K}是在输入I的第c个通道上、对应于输出O(h,w)位置 的滑动窗口。

这种对应关系由填充和步幅决定。conv的线性可以很容易地从等式2中推导出来,其中包括齐次性和可加性:

请注意,只有当两个卷积具有相同的配置(例如通道数、核大小、步长、填充等)时,相加性才成立,以便它们共享相同的滑动窗口对应X。

3.2. 不同分支的卷积

在这一小节中,我们总结了六种转换(图2),以使用批处理归一化(BN)、分支相加、深度串联、多尺度操作、平均池和卷积序列来转换DBB。

转换 I:将conv-BN转换为conv

我们通常为conv配备BN层,该层执行通道归一化和线性缩放。假设j是通道索引,\mu _{j} σ_j是累积的通道平均值和标准偏置,γ_jβ_j分别是学习的比例因子和偏置项。第j个通道的输出变为:

卷积的齐次性使得在推理时将BN融合到前面的卷积中。在实践中,我们只需构建一个具有核F’和偏置b’的卷积,其参数取值来自于对原始的卷积-BN的参数做一些转换,然后保存模型以供推理。通过公式(1)和(5),对于每个输出通道j,我们构建F’b’为:

转换 II:将多分支相加转换为单个conv

可加性确保,如果具有相同配置的两个或多个conv层的输出相加,我们可以将它们合并为一个conv。对于conv BN,我们应该首先执行转换 I。显然,两个卷积的合并为:


通过上述公式合并两个卷积,只适用于具有相同配置的conv层。虽然合并这些分支可以在一定程度上增强模型(表4),但我们希望合并不同的分支以进一步提高性能。下面,我们将介绍一些可以等价转换为单个conv的分支形式。通过多次转换为每个分支构造K×K conv后,我们使用Transform II将所有此类分支合并为一个conv。

变革三:

序列卷积的conv


我们可以将1×1 conv-BN-K×K conv-BN序列合并为一个K×K conv。我们暂时假设conv是稠密的(即组数g=1)。g>1的分组情况将通过变换IV实现。我们假设1×1和K×K层的核形状分别为D×C×1×1和E×D×K×K,其中D可以是任意的。我们首先将两个BN层融合到两个conv层中以获得F(1)∈ rd×C×1×1,b(1)∈ 研发部,F(2)∈ re×D×K×K和b(2)∈ R.E.输出为

我们需要一个conv,f0和b0的核和偏置的表达式,它满足

将conv的可加性应用于等式8,我们得到

由于I~F(1)是1×1 conv,它只执行信道线性组合而不执行空间聚合,因此我们可以通过线性重组K×K核中的参数将其合并到K×K conv中。很容易验证这种转换可以通过转置conv来完成,

其中TRANS(F(1))∈ rc×D×1×1是F(1)的转置张量。等式10的第二项是常数矩阵上的卷积,因此输出也是常数矩阵。正式地说,让P∈ rh×W是一个常数矩阵,其中每个项等于p,∗ 作为2D conv算子,W是2D conv核,结果是一个常数矩阵,与p和所有核元素之和成正比,即。,

基于这一观察,我们将ˆb构造为

那么就很容易验证了

那么我们有

值得注意的是,对于零填充输入的K×K conv,等式8不成立,因为F(2)不在I~F(1)+REP(b(1))的结果上卷积(而是额外的零像素圆)。解决方案是A)配置第一个conv带填充,第二个不带填充,或者B)按B(1)填充。后者的一个有效实现是定制第一个BN,以1)像往常一样批量标准化输入,2)计算b(1)(等式6),3)用b(1)填充批量标准化结果,即,用b(1)j代替0填充每个通道j。

Transform IV:深度连接的conv

初始单元使用深度连接来合并分支。但是,当这些分支各自仅包含一个具有相同配置的conv时,深度连接相当于一个conv,其内核沿着区分输出通道的轴(例如,我们公式中的第一个轴)连接。给定F(1)∈ rD1×C×K×K,b(1)∈ R D1,F(2)∈ rd2×C×K×K,b(2)∈ rD2,我们把它们连接成f0∈ R(D1+D2)×C×K×K,b0∈ R D1+D2。明显地

变换IV对于将变换III推广到分组情况特别有用。直观地说,groupwise conv将输入拆分为g个并行组,分别进行卷积,然后连接输出。为了替换g组conv,我们构建了一个DBB,其中所有conv层都具有相同的g组。为了转换1×1-K×K序列,我们将其等效地分成g组,分别执行转换III,并连接输出(图3)。

Transform V:用于平均池的conv

应用于C通道的内核大小为K和步长s的平均池相当于具有相同K和s的conv。这样一个内核f0∈ rc×C×K×K由

与普通平均池一样,它在s>1时执行下采样,但在s=1时实际上是平滑的


变换VI:多尺度卷积的conv

考虑kh×kw(kh≤ K、 千瓦≤ K) 该核等价于一个具有零项的K×K核,我们可以通过零填充将一个kh×kw核转换为K×K。具体而言,1×1、1×K和K×1 conv特别实用,因为它们可以有效地实现。应填充输入以对齐滑动窗口(图4)。

3.3. 一个类似于Inception的DBB实例

我们给出了DBB的一个代表性实例(图1),而它的通用性和灵活性使许多可行的实例成为可能。与Inception一样,我们使用1×1,1×1-K×K,1×1-AVG来增强原始K×K层。对于1×1-K×K分支,我们将内部通道设置为等于输入,并将1×1内核初始化为单位矩阵。其他conv内核定期初始化[11]。BN跟随每个conv或AVG层,提供训练时间非线性。如果没有这种非线性,性能增益将是微乎其微的(表4)。值得注意的是,对于深度DBB,每个conv都应该有相同数量的组,我们删除了1×1路径和1×1-AVG路径中的1×1 conv,因为1×1深度conv只是一个线性缩放。

4.实验

我们在CIFAR[16]、ImageNet[7]、Cityscapes[4]和COCO detection[18]上使用了几种基准架构来评估DBB改善ConvNet性能的能力,然后研究不同连接和训练时间非线性的意义。

4.1. 数据集、架构和配置

我们首先总结了实验配置(表1)。在CIFAR-10/100上,我们采用了标准的数据扩充技术[12]:填充到40×40、随机裁剪和左右翻转。我们使用VGG-16[24]进行快速的健康检查。在ACNet[8]之后,我们用全局平均池替换了两个隐藏的完全连接(FC)层,然后是一个由512个神经元组成的FC。为了公平比较,我们在VGG的原始模型中为每个conv层配备了BN。然后我们使用ImageNet-1K,其中包括1.28M图像用于训练,50K用于验证。对于数据增强,我们采用标准管道,包括随机裁剪、AlexNet[17]和MobileNet[13]等小型模型的左右翻转,以及ResNet18/50[12]的额外颜色抖动和基于PCA的照明。具体来说,我们使用与ACNet相同的AlexNet[8],它由五个堆叠的conv层和三个FC层组成,没有局部响应归一化。我们在每个conv层之后也插入BN。为了简单起见,我们在CIFAR和ImageNet上使用初始值为0.1的余弦学习速率衰减。在COCO检测方面,我们对CenterNet[38]进行126k次迭代训练,学习率初始化为0.02,并分别在81k和108k次迭代中乘以0.1。在城市景观方面,我们只需采用PSPNet[37]的官方实现和默认配置[36],以获得更好的再现性:200个epoch的基础为0.01、幂次为0.9的多边形学习率。

对于每个架构,我们用DBB替换每个K×K(1<K<7)conv及其后续BN,以构建DBB网络。我们不使用较大的内核(例如,ResNet和AlexNet的前7×7和11×11 conv)进行实验,因为它们在模型架构中不太受欢迎。所有型号均采用相同的配置进行训练。训练后,DBB网络转换为与原始模型相同的结构并进行测试。所有的实验都是用PyTorch完成的。

4.2. DBB免费改进

桌子2显示DBB网络在CIFAR和ImageNet上表现出明显且一致的性能提升:

DBB分别将CIFAR-10和CIFAR-100上的VGG-16提高了0.67%和1.67%,ImageNet上的AlexNet提高了1.96%,MobileNet提高了0.99%,ResNet-18/50提高了1.45%/0.57%。尽管ACB[8](图6d)是DBB的一个特例,我们仍然选择它作为竞争对手进行比较。具体来说,我们添加K×1和1×K分支来构建acb,并使用相同的设置进行训练。DBB网络相对于ACNet的优越性表明,将具有类似于初始阶段的不同复杂性的路径组合起来,可能比聚合由多尺度卷积生成的特征更有利于该模型。值得注意的是,比较偏向于原始模型,因为我们采用了原始论文中报告的超参数(例如,10的重量衰减)−4),已在原始模型上进行了调整,但可能不太适合DBB网络。

通过显示添加前四个BN层的标度因子γ,我们继续验证每个分支的重要性。具体而言,对于ResNet-18的16个3×3 DBB中的每一个(因为它最初有16个3×3 conv层),我们计算四个缩放向量绝对值的平均值。桌子5a表明K×K、1×1和1×1−K×K分支具有可比的标度因子大小,表明这三个分支很重要。一个有趣的发现是,1×1-AVG分支对于stride-2 DBB更为重要,这表明平均池作为下采样比平滑更有用。图5b示出了第9个块的比例因子的绝对值,其中四个γ向量分别被排序以获得更好的可读性。结果表明,K×K分支的最小尺度较大,其余三个分支的尺度变化范围较大。对于步幅为1的第10个区块(图5c),这一现象大不相同:超过160个通道的1×1-AVG分支的刻度接近于零,但其他通道的刻度相对较大,1×1分支的刻度大于第9个区块,这表明步幅为1时,1×1 conv更有用。这种比例因子分布的多样性表明,DBB网络为每个区块学习了不同分支的不同组合,这些发现可能会对建筑设计等其他研究领域有所启发。

4.3. 目标检测与语义分割

我们使用ImageNet预训练的ResNet-18模型来验证其在目标检测和语义分割方面的泛化性能。具体而言,我们构建了两个CenterNet/PSPNets,其中唯一的区别是主干(原始的ResNet-18或DBB-ResNet-18),加载ImageNet预训练(尚未转换)权重,在COCO/Cityscapes上训练,执行转换和测试。

4.4. 烧蚀研究

我们对ResNet18进行了一系列消融研究,以验证不同连接和训练时间非线性的重要性。具体来说,我们首先从DBB中切除一些分支并观察性能的变化,然后将DBB与具有重复分支或纯线性分支组合的一些对应分支进行比较,如图6所示。对于纯线性对应项,我们在分支加法之前不使用BN,但总和通过BN。同样,所有模型都是使用与以前相同的设置从头开始训练的,并转换为相同的原始结构进行测试。我们坐在桌子上。4最终精度和训练成本。

桌子4显示删除任何分支都会降低性能,这表明每个分支都很重要。还观察到,使用这三个分支中的任何一个都可以将精度提高到70%以上。从训练时间参数与精度的对比来看,如果训练资源有限,可以使用仅具有1×1和1×1-AVG分支的轻型DBB进行较低精度但更高效的训练。双重/三重重复块也提高了精度,但不如不同分支那样多。在将DBB与具有相同分支数目的重复块进行比较时,我们有两个特别有趣的发现:

•1×1 conv可被视为具有许多零条目的降级3×3 conv,其代表能力比后者弱,但(K×K+1×1)和双K×K的准确率分别为70.15%和69.81%。换句话说,弱容量分量加上强容量分量比两个强分量好。

•类似地,具有(K×K+1×1+(1×1-AVG))的DBB优于三重K×K(70.40%>70.29%),尽管后者与前者具有2.3倍的训练时间参数,这表明ConvNet的表征能力不仅取决于参数数量,还取决于连接的多样性。

为了验证改进是否是由于不同的初始化引起的,我们通过在随机初始化之后立即转换全功能DBB网络,使用生成的权重初始化常规ResNet-18,然后使用相同的设置对其进行训练,来构造基线(由“基线+初始”表示)。最终准确率为69.67%,几乎不高于常规初始化的基线,表明初始化不是关键。

我们继续验证分支中BN带来的训练时间非线性。在上述讨论中,我们注意到,即使是具有BN的重复分支也可以提高性能,因为这样的训练时间非线性使块比单个conv更强大。当BN层从添加前移动到添加后,块(从输入到添加分支)在训练期间变得纯线性。在这种情况下,双复制块几乎无法提高性能(69.54%)→ 69.59%),且(K×K+1×1)的DBB改善不如BN的可比DBB(69.83%<70.15%),表明即使没有训练时间非线性,不同的连接也可以改善模型。

我们还在表中给出了训练时间模型的训练速度和推理速度。4,这表明增加训练时间参数不会显著降低训练速度。值得注意的是,实际训练速度受数据预处理、跨GPU通信、反向传播实现等的影响,因此这些数据仅供参考。在工业中,研究人员和工程师通常拥有丰富的训练资源,但对推理时间成本有严格的限制,因此他们可能打算额外训练模型数十天,以获得非常微小的性能改进。在这些应用场景中,人们可能会发现DBB对于构建功能强大的CONVNET特别有用,只需要合理的额外训练成本。

5.结论

我们提出了一个名为DBB的ConvNet构建块,它通过单个卷积实现不同分支的组合。DBB使我们能够提高现有ConvNet架构的性能,而无需额外的推理时间成本。通过控制实验,我们证明了不同连接和训练时间非线性的重要性,这使得DBB比常规conv层更强大,尽管它们最终具有相同的推理时间结构。

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

推荐阅读更多精彩内容