卷積深度置信網(wǎng)絡(luò)工具箱的使用---人臉識(shí)別

引言

本文主要以O(shè)RL_64x64人臉數(shù)據(jù)庫識(shí)別為例衔峰,介紹如何使用基于matlab的CDBN工具箱炕婶。至于卷積深度置信網(wǎng)絡(luò)(CDBN,Convolutional Deep Belief Network)的理論知識(shí),只給出筆者整理的一些學(xué)習(xí)資源申窘。

卷積深度置信網(wǎng)絡(luò)理論知識(shí)

參考以下學(xué)習(xí)資料

CDBN工具箱簡介

據(jù)筆者了解砸泛,目前十籍,比較流行的深度學(xué)習(xí)框架,如TensorFlow唇礁、DeepLearning4j等不支持CDBN勾栗。GitHub上有基于Matlab的CDBN工具箱:CDBN工具箱下載鏈接
下面簡要介紹該工具箱。
從GitHub上下載的壓縮包解壓后再打開盏筐,文件目錄如下:

CDBN工具箱的文件目錄

其中围俘,最為重要的肯定是toolbox。toolbox里面有三個(gè)lib,分別是CDBN琢融,DBN界牡,Softmax庫。本文將用到CDBN和Softmax兩個(gè)庫漾抬。

toolbox下的三個(gè)lib

需要注意的是宿亡,由于這個(gè)工具箱不是官方版的,因此可能存在某些bug纳令,后面會(huì)涉及到筆者使用工具箱過程中的一些經(jīng)驗(yàn)挽荠。

神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)

介紹一下本文搭建的進(jìn)行人臉識(shí)別的卷積深度置信網(wǎng)絡(luò)的結(jié)構(gòu)克胳。

  • 主體結(jié)構(gòu):兩個(gè)卷積受限玻爾茲曼機(jī)(CRBM,Convolutional Restricted Boltzmann Machine)堆疊(每個(gè)CRBM后都接有池化層)圈匆,頂層采用Softmax漠另,實(shí)現(xiàn)分類。

  • 第一個(gè)CRBM:

第一個(gè)CRBM參數(shù)
  • 第二個(gè)CRBM:
第二個(gè)CRBM參數(shù)
  • Softmax層
    神經(jīng)元個(gè)數(shù)40個(gè)臭脓,最大迭代次數(shù)maxIter=1000酗钞,代價(jià)函數(shù)為交叉熵代價(jià)函數(shù)(Cross-Entropy Error)

  • 其他參數(shù)
    其他諸如學(xué)習(xí)速率等的參數(shù)使用CDBN-master\toolbox\CDBNLIB\default_layer2D.m中的默認(rèn)值。

編程

以下講解編程步驟来累。

  • 步驟一:安裝工具箱
    只需運(yùn)行setup_toolbox.m即可砚作。
    安裝工具箱其實(shí)只是把用到的一些函數(shù)添加到matlab的搜索路徑,因此你完全可以把工具箱內(nèi)所有的文件都復(fù)制到你當(dāng)前的路徑下嘹锁,不過肯定麻煩啦葫录!

  • 步驟二:加載和矩陣化數(shù)據(jù)

%load data
dataFortrain=load('ORL_64x64\StTrainFile1.txt');%注意修改路徑
train_data=dataFortrain(:,1:end-1)';%訓(xùn)練樣本
train_data=reshape(train_data,[64,64,1,360]);%矩陣化訓(xùn)練樣本
trainL=dataFortrain(:,end);%訓(xùn)練樣本標(biāo)簽
dataFortest=load('ORL_64x64\StTestFile1.txt');%注意修改路徑
test_data=dataFortest(:,1:end-1)';%測(cè)試樣本
test_data=reshape(test_data,[64,64,1,40]);%注意修改路徑
testL=dataFortest(:,end);%測(cè)試樣本標(biāo)簽

重點(diǎn)講一下第四行。
StTrainFile1.txt中有360行领猾,4097列米同。每一行是一幅人臉圖像(像素為64X64=4096)的4096個(gè)灰度值,最后一列是該幅人臉圖像的標(biāo)簽(1-40)摔竿,表明其屬于哪個(gè)人的(共40人面粮,即分類數(shù)目為40)。由此可見继低,一幅二維圖像(矩陣)被拉成了向量進(jìn)行存儲(chǔ)熬苍,因此在數(shù)據(jù)輸入CDBN前,我們要對(duì)向量進(jìn)行矩陣化袁翁,調(diào)用matlab的reshape方法柴底,最終生成一個(gè)4維的矩陣,四個(gè)維度分別是64,64,1,360(樣本數(shù))粱胜。倒數(shù)第二行同理柄驻。

  • 步驟三:定義層參數(shù)
    工具箱把一層layer定義為一個(gè)struct對(duì)象。
%INITIALIZE THE PARAMETERS OF THE NETWORK 
%first layer setting
layer{1} = default_layer2D();
layer{1}.inputdata=train_data;%輸入訓(xùn)練樣本
layer{1}.n_map_v=1;
layer{1}.n_map_h=9;
layer{1}.s_filter=[7 7];
layer{1}.stride=[1 1];
layer{1}.s_pool=[2 2];
 layer{1}.batchsize=90;
layer{1}.n_epoch=1;

%second layer setting
layer{2} = default_layer2D();
layer{2}.n_map_v=9;
layer{2}.n_map_h=16;
layer{2}.s_filter=[5 5];
layer{2}.stride=[1 1];
layer{2}.s_pool=[2 2];
layer{2}.batchsize=10;
layer{2}.n_epoch=1;

需要注意的是焙压,layer{i}=default_layer2D()這條語句是必須的鸿脓,且必須位于所有層參數(shù)定義語句的最前面。原因:如果layer{i}=default_layer2D()這條語句不位于最前面的話涯曲,在這條語句前面的參數(shù)賦值語句實(shí)質(zhì)不起作用答憔,這些參數(shù)還是取默認(rèn)值。特別是對(duì)于第一層掀抹,因?yàn)閐efault_layer2D()方法中是沒有定義inputdata字段的虐拓,如果layer{1}.inputdata=train_data這條語句位于layer{1}=default_layer2D()前面,則會(huì)出現(xiàn)“使用未定義字段”的錯(cuò)誤傲武。
補(bǔ)充:要注意根據(jù)自己使用的數(shù)據(jù)集的情況設(shè)定層的輸入類型蓉驹,對(duì)[0,1]之間的數(shù)據(jù)集城榛,應(yīng)該使用二值神經(jīng)網(wǎng)絡(luò),設(shè)定 layer{i}.type_input = 'Binary'(程序默認(rèn));其他數(shù)據(jù)集态兴,應(yīng)該設(shè)為layer{i}.type_input = 'Gaussian';至于二者的區(qū)別狠持,自行百度,這里不展開了瞻润。

  • 步驟四:訓(xùn)練CDBN網(wǎng)絡(luò)
    這個(gè)過程是無監(jiān)督學(xué)習(xí)喘垂,只需調(diào)用cdbn2D方法即可。

在調(diào)用cdbn2D方法之前绍撞,CDBN-master\toolbox\CDBNLIB\mex中的crbm_forward2D_batch_mex.c要先用mex命令編譯生成crbm_forward2D_batch_mex.mexw64文件才能供matlab調(diào)用

mex crbm_forward2D_batch_mex.c

在編譯前正勒,crbm_forward2D_batch_mex.c要先修改:128行的out_id要改成在最開始的位置定義,否則編譯時(shí)會(huì)出現(xiàn)“缺少:在類型前面’”的報(bào)錯(cuò)信息(PS:第一次遇到這么奇葩的報(bào)錯(cuò)傻铣,當(dāng)時(shí)懷疑C語言是不是白學(xué)了)章贞,原因:VS2010的C編譯器只支持C89標(biāo)準(zhǔn),對(duì)C99標(biāo)準(zhǔn)支持不完全非洲,而在C89標(biāo)準(zhǔn)中鸭限,變量需要放到函數(shù)體的前面聲明,先聲明再使用两踏。

%% ----------- GO TO 2D CONVOLUTIONAL DEEP BELIEF NETWORKS ------------------%% 
tic;
[model,layer] = cdbn2D(layer);
save('model_parameter','model','layer');
toc;
trainD  = model{1}.output;%訓(xùn)練樣本的第一個(gè)CRBM的輸出败京,是一個(gè)4維矩陣
trainD1 = model{2}.output;%訓(xùn)練樣本的第二個(gè)CRBM的輸出,是一個(gè)4維矩陣

我們來比較一下train_data梦染、trainD喧枷、trainD1的大小

train_data、trainD弓坞、trainD1

現(xiàn)在再看看卷積神經(jīng)網(wǎng)絡(luò)的圖示,是不是很好理解了呢车荔?

卷積神經(jīng)網(wǎng)絡(luò)圖示
  • 步驟五:將測(cè)試樣本輸入訓(xùn)練好的CDBN網(wǎng)絡(luò)渡冻,提取高維特征

這段代碼可以直接copy,修改好變量名即可忧便!

%% ------------ TESTDATA FORWARD MODEL WITH THE PARAMETERS ------------------ %%
% FORWARD MODEL OF NETWORKS
H = length(layer);
layer{1}.inputdata = test_data;
fprintf('output the testdata features:>>...\n');
tic;
if H >= 2
% PREPROCESSS INPUTDATA TO BE SUITABLE FOR TRAIN 
layer{1} = preprocess_train_data2D(layer{1});
model{1}.output = crbm_forward2D_batch_mex(model{1},layer{1},layer{1}.inputdata);

for i = 2:H
    layer{i}.inputdata = model{i-1}.output;
    layer{i} = preprocess_train_data2D(layer{i});
    model{i}.output = crbm_forward2D_batch_mex(model{i},layer{i},layer{i}.inputdata);
end

else

layer{1} = preprocess_train_data2D(layer{1});
model{1}.output = crbm_forward2D_batch_mex(model{1},layer{1},layer{1}.inputdata);
end

testD  = model{1}.output;%訓(xùn)練樣本的第一個(gè)CRBM的輸出族吻,是一個(gè)4維矩陣
testD1 = model{2}.output;%訓(xùn)練樣本的第二個(gè)CRBM的輸出,是一個(gè)4維矩陣
toc;

同樣的寇窑,我們來看一下test_data钠糊、testD补胚、testD1的大小:

test_data巍举、testD、testD1的大小比較
  • 步驟六:訓(xùn)練Softmax分類器凝垛,同時(shí)進(jìn)行識(shí)別
    這里我們用到 softmaxExercise(inputData,labels,inputData_t,labels_t)這個(gè)函數(shù)
    參數(shù)說明:
    - inputdata:訓(xùn)練樣本的CDBN輸出懊悯,要求是二維矩陣
    -labels:訓(xùn)練樣本的標(biāo)簽
    -inputData_t:測(cè)試樣本的CDBN輸出蜓谋,要求是二維矩陣
    -labels_t:測(cè)試樣本的標(biāo)簽
    由于CDBN的輸出是4維矩陣,因此在訓(xùn)練Softmax分類器前炭分,需要把矩陣?yán)上蛄浚ê椭暗倪^程相反)桃焕。代碼如下,可直接copy捧毛,修改變量名即可观堂!
%% ------------------------------- Softmax ---------------------------------- %%

fprintf('train the softmax:>>...\n');

tic;

% TRANSLATE THE OUTPUT TO ONE VECTOR
trainDa = [];
trainLa=trainL;
for i= 1:size(trainD,4)
a1 = [];
a2 = [];
a3 = [];
for j = 1:size(trainD,3)
    a1 = [a1;reshape(trainD(:,:,j,i),size(trainD,2)*size(trainD,1),1)];
end

for j = 1:size(trainD1,3)
    a2 = [a2;reshape(trainD1(:,:,j,i),size(trainD1,2)*size(trainD1,1),1)];
end
a3 = [a3;a1;a2];
trainDa = [trainDa,a3];
end

testDa = [];
testLa=testL;
for i= 1:size(testD,4)
b1 = [];
b2 = [];
b3 = [];
for j = 1:size(testD,3)
    b1 = [b1;reshape(testD(:,:,j,i),size(testD,2)*size(testD,1),1)];
end

for j =1:size(testD1,3)
    b2 = [b2;reshape(testD1(:,:,j,i),size(testD1,2)*size(testD1,1),1)];
end
b3 = [b3;b1;b2];
testDa = [testDa,b3];
end

我們來看一下拉成向量后的trainDa以及testDa的大小

拉成向量后的trainDa以及testDa的大小

對(duì)比一下,train_data和test_data在矩陣化之前的大醒接恰:

train_data和test_data在矩陣化之前的大小

可見师痕,CDBN作為特征提取器,將4096維特征映射到了9873維特征荐虐,提高了Softmax的分類能力七兜!

softmaxExercise.m中有這樣一段注釋:

softmaxExercise.m中的注釋

因此在調(diào)用softmaxExercise方法前,要做以下4個(gè)工作:

  • 修改softmaxExercise.m第22行的numClasses福扬,如本文改為40
  • 修改softmaxExercise.m第96行的maxIter腕铸,本文取1000

PS:個(gè)人覺得softmaxExercise方法應(yīng)該增加兩個(gè)入口參數(shù),即numClasses和maxIter铛碑,如此才能更好體現(xiàn)封裝的思想狠裹。

  • softmaxCost.m中定義需要的損失函數(shù),只需要改第90行
cost = -(1. / numCases) * sum(sum(groundTruth .* log(p))) + (lambda / 2.) * sum(sum(theta.^2));

這條語句即可汽烦,原文件使用的是交叉熵代價(jià)函數(shù)涛菠。

  • 有必要的話可以修改 softmaxPredict.m中內(nèi)容,個(gè)人覺得完全沒必要撇吞,保留即可俗冻。

最后調(diào)用softmaxExercise方法

softmaxExercise(trainDa,trainLa,testDa,testLa);
toc;

完整代碼

FaceRecognitionDemo.m

clear;
%load data
dataFortrain=load('ORL_64x64\StTrainFile1.txt');
train_data=dataFortrain(:,1:end-1)';
train_data=reshape(train_data,[64,64,1,360]);
trainL=dataFortrain(:,end);
dataFortest=load('ORL_64x64\StTestFile1.txt');
test_data=dataFortest(:,1:end-1)';
test_data=reshape(test_data,[64,64,1,40]);
testL=dataFortest(:,end);
%INITIALIZE THE PARAMETERS OF THE NETWORK 
%first layer setting
layer{1} = default_layer2D();
layer{1}.inputdata=train_data;
layer{1}.n_map_v=1;
layer{1}.n_map_h=9;
layer{1}.s_filter=[7 7];
layer{1}.stride=[1 1];
layer{1}.s_pool=[2 2];
layer{1}.batchsize=90;
layer{1}.n_epoch=1;
%second layer setting
layer{2} = default_layer2D();
layer{2}.n_map_v=9;
layer{2}.n_map_h=16;
 layer{2}.s_filter=[5 5];
layer{2}.stride=[1 1];
layer{2}.s_pool=[2 2];
layer{2}.batchsize=10;
layer{2}.n_epoch=1;
%% ----------- GO TO 2D CONVOLUTIONAL DEEP BELIEF NETWORKS ------------------     %% 
tic;

[model,layer] = cdbn2D(layer);
save('model_parameter','model','layer');

toc;

trainD  = model{1}.output;
trainD1 = model{2}.output;
%% ------------ TESTDATA FORWARD MODEL WITH THE PARAMETERS ------------------ %%
% FORWARD MODEL OF NETWORKS
H = length(layer);
layer{1}.inputdata = test_data;
fprintf('output the testdata features:>>...\n');

tic;
if H >= 2

 % PREPROCESSS INPUTDATA TO BE SUITABLE FOR TRAIN 
layer{1} = preprocess_train_data2D(layer{1});
model{1}.output = crbm_forward2D_batch_mex(model{1},layer{1},layer{1}.inputdata);

for i = 2:H
    layer{i}.inputdata = model{i-1}.output;
    layer{i} = preprocess_train_data2D(layer{i});
    model{i}.output = crbm_forward2D_batch_mex(model{i},layer{i},layer{i}.inputdata);
end

else

layer{1} = preprocess_train_data2D(layer{1});
model{1}.output = crbm_forward2D_batch_mex(model{1},layer{1},layer{1}.inputdata);
end

testD  = model{1}.output;
testD1 = model{2}.output;
toc;
%% ------------------------------- Softmax ---------------------------------- %%

fprintf('train the softmax:>>...\n');

tic;

% TRANSLATE THE OUTPUT TO ONE VECTOR
trainDa = [];
trainLa=trainL;
for i= 1:size(trainD,4)
a1 = [];
a2 = [];
a3 = [];
for j = 1:size(trainD,3)
    a1 = [a1;reshape(trainD(:,:,j,i),size(trainD,2)*size(trainD,1),1)];
end

for j = 1:size(trainD1,3)
    a2 = [a2;reshape(trainD1(:,:,j,i),size(trainD1,2)*size(trainD1,1),1)];
end
a3 = [a3;a1;a2];
trainDa = [trainDa,a3];
end

testDa = [];
testLa=testL;
for i= 1:size(testD,4)
b1 = [];
b2 = [];
b3 = [];
for j = 1:size(testD,3)
    b1 = [b1;reshape(testD(:,:,j,i),size(testD,2)*size(testD,1),1)];
end

for j =1:size(testD1,3)
    b2 = [b2;reshape(testD1(:,:,j,i),size(testD1,2)*size(testD1,1),1)];
end
b3 = [b3;b1;b2];
testDa = [testDa,b3];
end
softmaxExercise(trainDa,trainLa,testDa,testLa);
toc;

運(yùn)行截圖及準(zhǔn)確率

運(yùn)行截圖1
運(yùn)行截圖2
運(yùn)行截圖3

97.5%的識(shí)別率,還是可以接受的牍颈,一方面是數(shù)據(jù)集好迄薄,另一方面是搭建得網(wǎng)絡(luò)好。
讀者可以試一試調(diào)整CDBN網(wǎng)絡(luò)的參數(shù)煮岁,比如增大epoch(本文取1)讥蔽,看能否獲得更高的識(shí)別率。
為了方便讀者研究画机,附上所有文件冶伞。

本Demo文件匯總下載鏈接(原鏈接失效,此為新版連接),提取碼:7f6i

以下是使用此工具箱的幾點(diǎn)提示:

  • 原始工具箱只在LINUX系統(tǒng)測(cè)試過步氏,由于LINUX系統(tǒng)和WINDOWS系統(tǒng)的文件分隔符不同响禽,
    因此DemoCDBN_Binary_2D.m的第83行、
    cdbn2D.m的第15、24行金抡、 setup_toolbox.m的文件分隔符要修改瀑焦。
  • 源程序存在bug,即若樣本個(gè)數(shù)不是batchsize的整數(shù)倍的話梗肝,會(huì)出錯(cuò)榛瓮,因此在此bug排除前,應(yīng)將batchsize設(shè)置為樣本數(shù)目的因數(shù)
  • 類別標(biāo)簽不要用負(fù)數(shù)或0巫击,比如進(jìn)行二分類禀晓,標(biāo)簽不要設(shè)為-1和1,可以設(shè)為1和2坝锰,這是因?yàn)閟oftmaxCost.m文件中的第18行建立稀疏矩陣時(shí)會(huì)以標(biāo)簽作為矩陣的索引粹懒,如果設(shè)為0或負(fù)數(shù),肯定會(huì)報(bào)錯(cuò):矩陣索引必須為正數(shù)

over顷级,接觸機(jī)器學(xué)習(xí)時(shí)間不是很長凫乖,文章有什么錯(cuò)誤,歡迎留言指正弓颈,謝謝帽芽!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市翔冀,隨后出現(xiàn)的幾起案子导街,更是在濱河造成了極大的恐慌,老刑警劉巖纤子,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搬瑰,死亡現(xiàn)場離奇詭異,居然都是意外死亡控硼,警方通過查閱死者的電腦和手機(jī)泽论,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卡乾,“玉大人翼悴,你說我怎么就攤上這事∷刀” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵潮瓶,是天一觀的道長陶冷。 經(jīng)常有香客問我,道長毯辅,這世上最難降的妖魔是什么埂伦? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮思恐,結(jié)果婚禮上沾谜,老公的妹妹穿的比我還像新娘膊毁。我一直安慰自己,他們只是感情好基跑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布婚温。 她就那樣靜靜地躺著,像睡著了一般媳否。 火紅的嫁衣襯著肌膚如雪栅螟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天篱竭,我揣著相機(jī)與錄音力图,去河邊找鬼。 笑死掺逼,一個(gè)胖子當(dāng)著我的面吹牛吃媒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吕喘,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼赘那,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了兽泄?” 一聲冷哼從身側(cè)響起漓概,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎病梢,沒想到半個(gè)月后胃珍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜓陌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年觅彰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钮热。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡填抬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隧期,到底是詐尸還是另有隱情飒责,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布仆潮,位于F島的核電站宏蛉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏性置。R本人自食惡果不足惜拾并,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗅义,春花似錦屏歹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至继控,卻和暖如春械馆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背武通。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工霹崎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冶忱。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓尾菇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親囚枪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子派诬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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