Caffe2 的基本數(shù)據(jù)結(jié)構(gòu)(Basics of Caffe2 - Workspaces, Operators, and Nets)[4]

這篇文章主要介紹Caffe2的基本數(shù)據(jù)結(jié)構(gòu):

  • Workspaces
  • Operators
  • Nets

在開始之前最好先閱讀以下Intro Turorial
首先盗似,導(dǎo)入caffe2钧嘶。其中coreworksapce模塊采转,這是必須的兩個(gè)模塊涵叮。如果你要使用Caffe2生成的protocol buffers讨便,那么你也需要從caffe2_pb2中導(dǎo)入caffe2_pb2模塊垢油。

# We'll also import a few standard python libraries
from matplotlib import pyplot
import numpy as np
import time

# These are the droids you are looking for.
from caffe2.python import core, workspace
from caffe2.proto import caffe2_pb2

如果你看到一些警告:Caffe2不支持GPU。這說明,你正在跑的Caffe2僅僅編譯了CPU模式。不用擔(dān)心晃跺,Caffe2在CPU上也是可以運(yùn)行的。

Workspaces

讓我們先來介紹Workspace毫玖,它包含了所有數(shù)據(jù)掀虎。如果你熟悉Matlabworksapce包含了所有你創(chuàng)建的blob并保存在內(nèi)存中≡斜現(xiàn)在涩盾,讓我們考慮一個(gè)N維的blob,blob和numpy的矩陣很像励背,但是它是連續(xù)的春霍。接下來,我們將展示blob實(shí)際上是一種能指向任何C++類型對(duì)象的指針叶眉。下面址儒,我們來看看接口是什么樣的的芹枷。

Blobs()函數(shù)可以打印workspace里面所有的blobs。HasBlob則用于查詢worksapce里面是否存在某個(gè)blob莲趣。不過鸳慈,目前為止,我們的workspace里面沒有任何東西喧伞。

print("Current blobs in the workspace: {}".format(workspace.Blobs()))
print("Workspace has blob 'X'? {}".format(workspace.HasBlob("X")))

FeedBlob()函數(shù)用于向worksapce里面?zhèn)鬟fblob走芋。

X = np.random.randn(2, 3).astype(np.float32)
print("Generated X from numpy:\n{}".format(X))
workspace.FeedBlob("X", X)

打印出來的X如下:

Generated X from numpy:
[[-0.56927377 -1.28052795 -0.95808828]
 [-0.44225693 -0.0620895  -0.50509363]]

讓我們看一下workspace里面的blob是什么樣的。

print("Current blobs in the workspace: {}".format(workspace.Blobs()))
print("Workspace has blob 'X'? {}".format(workspace.HasBlob("X")))
print("Fetched X:\n{}".format(workspace.FetchBlob("X")))

輸出如下:

Current blobs in the workspace: [u'X']
Workspace has blob 'X'? True
Fetched X:
[[-0.56927377 -1.28052795 -0.95808828]
 [-0.44225693 -0.0620895  -0.50509363]]

接著驗(yàn)證兩個(gè)矩陣是否相等:

np.testing.assert_array_equal(X, workspace.FetchBlob("X"))

注意潘鲫,如果你訪問一個(gè)不存在的blob翁逞,將會(huì)引發(fā)一個(gè)錯(cuò)誤:

try:
    workspace.FetchBlob("invincible_pink_unicorn")
except RuntimeError as err:
    print(err)

錯(cuò)誤輸出如下:

[enforce fail at pybind_state.cc:441] gWorkspace->HasBlob(name).

另外,有一個(gè)你目前可能還用不上的東西:你可以定義兩個(gè)不同名字的workspace溉仑,并且在他們之間切換挖函。不同workspace的bolb是相互分離的。你可以通過CurrentWorkspace()函數(shù)來訪問當(dāng)前的workspace浊竟。下面演示了如何切換不同的workspace和創(chuàng)建新的workspace怨喘。

print("Current workspace: {}".format(workspace.CurrentWorkspace()))
print("Current blobs in the workspace: {}".format(workspace.Blobs()))

# 切換到`gutentag` workspace,第二個(gè)參數(shù)`True`表示振定,如果`gutentag`不存在必怜,則創(chuàng)建一個(gè)。
workspace.SwitchWorkspace("gutentag", True)

# 現(xiàn)在重新打印吩案,注意到當(dāng)前的workspace是`gutentag`棚赔,并且其中不包含任何東西帝簇。
print("Current workspace: {}".format(workspace.CurrentWorkspace()))
print("Current blobs in the workspace: {}".format(workspace.Blobs()))

程序輸出:

Current workspace: default
Current blobs in the workspace: ['X']
Current workspace: gutentag
Current blobs in the workspace: []

重新切換回到defaultworkspace

workspace.SwitchWorkspace("default")
print("Current workspace: {}".format(workspace.CurrentWorkspace()))
print("Current blobs in the workspace: {}".format(workspace.Blobs()))

并有如下輸出:

Current workspace: default
Current blobs in the workspace: ['X']

最后徘郭,調(diào)用ResetWorkspace()函數(shù)可以清空當(dāng)前的workspace的所有東西

workspace.ResetWorkspace()

Operators

Caffe2中,operator就像函數(shù)一樣丧肴。從C++的角度理解残揉,operator全部從一個(gè)通用的接口繼承而來,它們通過類型進(jìn)行注冊(cè)芋浮,所以抱环,我們可以在運(yùn)行時(shí)調(diào)用不同的操作。operator的接口定義在caffe2/proto/caffe2.proto文件中纸巷。Operator根據(jù)輸出產(chǎn)生相應(yīng)的輸出镇草。
記住,在Caffe2的Python接口中瘤旨,當(dāng)我們說“創(chuàng)建一個(gè)operator”時(shí)梯啤,程序并沒有跑起來,它只是創(chuàng)建了關(guān)于這個(gè)operator的protocol buffere存哲,也就是定義了這個(gè)operator因宇,但還沒執(zhí)行七婴。之后,這個(gè)operator才會(huì)傳遞給C++接口禁止執(zhí)行察滑。如果你不明白什么是protobuf打厘,那么你可以看下這個(gè)鏈接.
**1. **
下面看一個(gè)實(shí)際例子:

# Create an operator.
op = core.CreateOperator(
    "Relu", # The type of operator that we want to run
    ["X"], # 輸入 blobs 的名字的列表
    ["Y"], # A list of 輸出 blobs by their names
)
# and we are done!

我們之前說到,創(chuàng)建op(operator),事實(shí)上只是創(chuàng)建了一個(gè)protobuf對(duì)象贺辰。我們可以查看它的內(nèi)容户盯。

print("Type of the created op is: {}".format(type(op)))
print("Content:\n")
print(str(op))

輸出如下:

Type of the created op is: <class 'caffe2.proto.caffe2_pb2.OperatorDef'>
Content:
input: "X"
output: "Y"
name: ""
type: "Relu"

現(xiàn)在跑起這個(gè)operator,我們首先需要向workspace中傳入數(shù)據(jù)X饲化,然后簡(jiǎn)單的調(diào)用workspace.RunOperatorOnce(operator)函數(shù)就可以先舷。

workspace.FeedBlob("X", np.random.randn(2, 3).astype(np.float32))
workspace.RunOperatorOnce(op)

執(zhí)行完后,讓我們檢查下這個(gè)operator是否正確操作滓侍。在這個(gè)操作中我們使用的是Relu函數(shù)蒋川。Relu函數(shù)在輸入小于0時(shí),取0撩笆,在輸入大于0時(shí)捺球,保持不變。

print("Current blobs in the workspace: {}\n".format(workspace.Blobs()))
print("X:\n{}\n".format(workspace.FetchBlob("X")))
print("Y:\n{}\n".format(workspace.FetchBlob("Y")))
print("Expected:\n{}\n".format(np.maximum(workspace.FetchBlob("X"), 0)))

輸出如下,可以看到輸出Y和你期望值一樣夕冲,這個(gè)operator正確跑起來了:

Current blobs in the workspace: ['X', 'Y']
X:
[[ 1.03125858  1.0038228   0.0066975 ]
 [ 1.33142471  1.80271244 -0.54222912]]
Y:
[[ 1.03125858  1.0038228   0.0066975 ]
 [ 1.33142471  1.80271244  0.        ]]

Expected:
[[ 1.03125858  1.0038228   0.0066975 ]
 [ 1.33142471  1.80271244  0.        ]]

2.
當(dāng)然Operator也支持選項(xiàng)參數(shù)氮兵。選項(xiàng)參數(shù)通過key-value對(duì)確定。下面是一個(gè)簡(jiǎn)單的例子:創(chuàng)建一個(gè)tensor并且用高斯隨機(jī)值填充它歹鱼。

op = core.CreateOperator(
    "GaussianFill",
    [], # GaussianFill does not need any parameters.
    ["Z"],
    shape=[100, 100], # shape argument as a list of ints.
    mean=1.0,  # mean as a single float
    std=1.0, # std as a single float
)
print("Content of op:\n")
print(str(op))

看看輸出:

Content of op:
output: "Z"
name: ""
type: "GaussianFill"
arg {
  name: "std"
  f: 1.0
}
arg {
  name: "shape"
  ints: 100
  ints: 100
}
arg {
  name: "mean"
  f: 1.0
}

然后我們跑起這個(gè)op,看看事情是否如期泣栈。

workspace.RunOperatorOnce(op)
temp = workspace.FetchBlob("Z")
pyplot.hist(temp.flatten(), bins=50)
pyplot.title("Distribution of Z")
image.png

沒錯(cuò),就是這樣弥姻。

Nets

Net其實(shí)是多個(gè)operator的集合南片,就像寫程序時(shí)一行一行的命令。

讓我們創(chuàng)建一個(gè)等價(jià)于下面Python代碼的網(wǎng)絡(luò)庭敦。

X = np.random.randn(2, 3)
W = np.random.randn(5, 3)
b = np.ones(5)
Y = X * W^T + b

Caffe2中的core.net是對(duì)NetDef protocol buffer的一個(gè)封裝類疼进。當(dāng)創(chuàng)建一個(gè)網(wǎng)絡(luò)時(shí),這個(gè)對(duì)象完全是空的秧廉,除了擁有它的名字信息外伞广。

net = core.Net("my_first_net")
print("Current network proto:\n\n{}".format(net.Proto()))
Current network proto:
name: "my_first_net"

接著創(chuàng)建一個(gè)blob,命名為“X”,使用高斯函數(shù)進(jìn)行填充疼电。

X = net.GaussianFill([], ["X"], mean=0.0, std=1.0, shape=[2, 3], run_once=0)
print("New network proto:\n\n{}".format(net.Proto()))

這時(shí)網(wǎng)絡(luò)的結(jié)構(gòu)如下

New network proto:
name: "my_first_net"
op {
  output: "X"
  name: ""
  type: "GaussianFill"
  arg {
    name: "std"
    f: 1.0
  }
  arg {
    name: "run_once"
    i: 0
  }
  arg {
    name: "shape"
    ints: 2
    ints: 3
  }
  arg {
    name: "mean"
    f: 0.0
  }
}

聰明的讀者肯定想起了我們之前提到的core.CreateOperator()嚼锄。事實(shí)上,當(dāng)我們有了一個(gè)net蔽豺,我們可以直接創(chuàng)建一個(gè)operator然后通過Python接口加到net中去区丑。比如,你調(diào)用了net.SomeOp茫虽,這里的SomeOp是一個(gè)注冊(cè)了的operator的字符串刊苍,因此上面的操作和下面等效既们。

op = core.CreateOperator("SomeOp", ...)
net.Proto().op.append(op)

譯者注:
比如在我用op = core.CreateOperator("GaussianFill",[], ["Z"],shape=[100, 100],mean=1.0, std=1.0)創(chuàng)建了一個(gè)op,op的type為“GaussianFill”正什,這是一個(gè)注冊(cè)了的類型啥纸。然后再調(diào)用net.Proto().op.append(op)把這個(gè)op添加到網(wǎng)絡(luò)中去。
以上的操作可以同過net來調(diào)用直接實(shí)現(xiàn)婴氮。直接使用op的type string---“GaussianFill”作為函數(shù)名字斯棒,net.GaussianFill([], ["X"], mean=0.0, std=1.0, shape=[2, 3], run_once=0)。

當(dāng)然主经,讀者可能感到困惑荣暮,X是什么?X是一個(gè) BlobReference罩驻,這個(gè)引用包含兩樣?xùn)|西:
- 名字穗酥,可以通過str(X)來訪問得到
- 它是哪個(gè)net創(chuàng)建的,記錄在其中的變量_from_net
現(xiàn)在讓我們驗(yàn)證它惠遏。同樣記住砾跃,我們還沒有跑任何東西,所以X只是個(gè)符號(hào)节吮,里面什么也沒有抽高。別只望它會(huì)輸出什么值。

print("Type of X is: {}".format(type(X)))
print("The blob name is: {}".format(str(X)))
Type of X is: <class 'caffe2.python.core.BlobReference'>
The blob name is: X

讓我們繼續(xù)創(chuàng)建W和b.

W = net.GaussianFill([], ["W"], mean=0.0, std=1.0, shape=[5, 3], run_once=0)
b = net.ConstantFill([], ["b"], shape=[5,], value=1.0, run_once=0)

現(xiàn)在一個(gè)簡(jiǎn)單的代碼:Note由于BlonReference對(duì)象知道它由什么網(wǎng)絡(luò)創(chuàng)建的透绩,所以除了從net中創(chuàng)建op翘骂,你還可以通過BlobReference創(chuàng)建op。因此帚豪,我們可以通過如下方式創(chuàng)建FC操作碳竟。

Y = X.FC([W, b], ["Y"])

事實(shí)上,在底下志鞍,X.FC(...)只是簡(jiǎn)單的委托net.FC來實(shí)現(xiàn)瞭亮,X.FC()會(huì)將X作為op的第一個(gè)輸入。所以上面的操作其實(shí)等價(jià)于下面的:

Y = net.FC([X, W, b], ["Y"])

現(xiàn)在讓我們看下當(dāng)前這個(gè)網(wǎng)絡(luò)固棚。

print("Current network proto:\n\n{}".format(net.Proto()))
Current network proto:
name: "my_first_net"
op {
  output: "X"
  name: ""
  type: "GaussianFill"
  arg {
    name: "std"
    f: 1.0
  }
  arg {
    name: "run_once"
    i: 0
  }
  arg {
    name: "shape"
    ints: 2
    ints: 3
  }
  arg {
    name: "mean"
    f: 0.0
  }
}
op {
  output: "W"
  name: ""
  type: "GaussianFill"
  arg {
    name: "std"
    f: 1.0
  }
  arg {
    name: "run_once"
    i: 0
  }
  arg {
    name: "shape"
    ints: 5
    ints: 3
  }
  arg {
    name: "mean"
    f: 0.0
  }
}
op {
  output: "b"
  name: ""
  type: "ConstantFill"
  arg {
    name: "run_once"
    i: 0
  }
  arg {
    name: "shape"
    ints: 5
  }
  arg {
    name: "value"
    f: 1.0
  }
}
op {
  input: "X"
  input: "W"
  input: "b"
  output: "Y"
  name: ""
  type: "FC"
}

是不是覺得太過冗長(zhǎng)?GOOD~讓我們嘗試下把它變成一個(gè)圖仙蚜。用ipython顯示此洲。

from caffe2.python import net_drawer
from IPython import display
graph = net_drawer.GetPydotGraph(net, rankdir="LR")
display.Image(graph.create_png(), width=800)

image.png

目前為止,我們已經(jīng)定義了一個(gè)Net委粉,但是并沒有執(zhí)行任何東西呜师。記住,上面的net只是一個(gè)protobuf贾节,僅僅定義了網(wǎng)路的結(jié)構(gòu)汁汗。當(dāng)我們真正跑起這個(gè)網(wǎng)絡(luò)時(shí)衷畦,底層發(fā)生的事件如下。
- 實(shí)例化protobuf中定義的C++net 對(duì)象
- 調(diào)用實(shí)例化后的net的Run()函數(shù)
在我們進(jìn)行任何操作前知牌,我們應(yīng)該先使用ResetWorkspace()清空workspace里的東
西祈争。
NOTE有兩種方式通過python來跑一個(gè)網(wǎng)絡(luò)。我們選擇第一種來展示角寸。

  1. 使用 workspace.RunNetOnce()
  2. 第二種更復(fù)雜點(diǎn):需要兩步菩混,a) 調(diào)用workspace.CreateNet()創(chuàng)建C++net對(duì)象,b)使用workspace.RunNet(),這步需要傳遞網(wǎng)絡(luò)的名字作為參數(shù)扁藕。

第一種

workspace.ResetWorkspace()
print("Current blobs in the workspace: {}".format(workspace.Blobs()))
workspace.RunNetOnce(net)
print("Blobs in the workspace after execution: {}".format(workspace.Blobs()))
# Let's dump the contents of the blobs
for name in workspace.Blobs():
    print("{}:\n{}".format(name, workspace.FetchBlob(name)))

輸出如下:

Current blobs in the workspace: []
Blobs in the workspace after execution: ['W', 'X', 'Y', 'b']
W:
[[-0.96537346  0.42591459  0.66788739]
 [-0.47695673  2.25724339 -0.10370601]
 [-0.20327474 -3.07469416  0.47715324]
 [-1.62159526  0.73711687 -1.42365313]
 [ 0.60718107 -0.50448036 -1.17132831]]
X:
[[-0.99601173 -0.61438894  0.10042733]
 [ 0.23359862  0.15135486  0.77555442]]
Y:
[[ 1.76692021  0.07781416  3.13944149  2.01927781  0.58755434]
 [ 1.35693741  1.14979863  0.85720366 -0.37135673  0.15705228]]
b:
[ 1.  1.  1.  1.  1.]

第二種
現(xiàn)在嘗試第二種方法去創(chuàng)建這個(gè)網(wǎng)絡(luò)沮峡,并跑起它。

workspace.ResetWorkspace()
print("Current blobs in the workspace: {}".format(workspace.Blobs()))
workspace.CreateNet(net)
workspace.RunNet(net.Proto().name)#傳入名字
print("Blobs in the workspace after execution: {}".format(workspace.Blobs()))
for name in workspace.Blobs():
    print("{}:\n{}".format(name, workspace.FetchBlob(name)))

輸出

Current blobs in the workspace: []
Blobs in the workspace after execution: ['W', 'X', 'Y', 'b']
W:
[[-0.29295802  0.02897477 -1.25667715]
 [-1.82299471  0.92877913  0.33613944]
 [-0.64382178 -0.68545657 -0.44015241]
 [ 1.10232282  1.38060772 -2.29121733]
 [-0.55766547  1.97437167  0.39324901]]
X:
[[-0.47522315 -0.40166432  0.7179445 ]
 [-0.8363331  -0.82451206  1.54286408]]
Y:
[[ 0.22535783  1.73460138  1.2652775  -1.72335696  0.7543118 ]
 [-0.71776152  2.27745867  1.42452145 -4.59527397  0.4452306 ]]
b:
[ 1.  1.  1.  1.  1.]

RunNetOnce()RunNet()之間有不少差異亿柑,其中最大的差異就是計(jì)算耗時(shí)邢疙。因?yàn)?code>RunNetOnce()涉及到protobuf的序列化,和實(shí)例化網(wǎng)絡(luò)望薄。這可能會(huì)使用很長(zhǎng)時(shí)間秘症。讓我們來看下開銷。

# It seems that %timeit magic does not work well with
# C++ extensions so we'll basically do for loops
start = time.time()
for i in range(1000):
    workspace.RunNetOnce(net)
end = time.time()
print('Run time per RunNetOnce: {}'.format((end - start) / 1000))

start = time.time()
for i in range(1000):
    workspace.RunNet(net.Proto().name)
end = time.time()
print('Run time per RunNet: {}'.format((end - start) / 1000))

輸出如下:

Run time per RunNetOnce: 0.000364284992218
Run time per RunNet: 4.42600250244e-06

可以看到RunNet()更快式矫。

結(jié)語(yǔ):以上就是Caffe2的Python接口的一些主要部件乡摹。裝載請(qǐng)注明出處:
http://www.reibang.com/c/cf07b31bb5f2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市采转,隨后出現(xiàn)的幾起案子聪廉,更是在濱河造成了極大的恐慌,老刑警劉巖故慈,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件板熊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡察绷,警方通過查閱死者的電腦和手機(jī)干签,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拆撼,“玉大人容劳,你說我怎么就攤上這事≌⒍龋” “怎么了竭贩?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)莺禁。 經(jīng)常有香客問我留量,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任楼熄,我火速辦了婚禮忆绰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘可岂。我一直安慰自己错敢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布青柄。 她就那樣靜靜地躺著伐债,像睡著了一般。 火紅的嫁衣襯著肌膚如雪致开。 梳的紋絲不亂的頭發(fā)上峰锁,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音双戳,去河邊找鬼虹蒋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛飒货,可吹牛的內(nèi)容都是我干的魄衅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼塘辅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼晃虫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扣墩,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤哲银,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后呻惕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荆责,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年亚脆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了做院。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡濒持,死狀恐怖键耕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弥喉,我是刑警寧澤郁竟,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站由境,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜虏杰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一讥蟆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纺阔,春花似錦瘸彤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至玻靡,卻和暖如春结榄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背囤捻。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工臼朗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蝎土。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓视哑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親誊涯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挡毅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容