PyTorch-12 FGSM Attack 對(duì)抗樣本生成

要閱讀 帶有插圖的文章版本 請(qǐng)前往 http://studyai.com/pytorch-1.4/beginner/fgsm_tutorial.html

如果你正在閱讀這篇文章,希望你能體會(huì)到一些機(jī)器學(xué)習(xí)模型是多么的有效。研究不斷推動(dòng)ML模型變得更快、更準(zhǔn)確和更高效忧额。 然而循衰,設(shè)計(jì)和訓(xùn)練模型的一個(gè)經(jīng)常被忽視的方面是安全性和健壯性璃搜,特別是在面對(duì)希望欺騙模型的對(duì)手時(shí)寇荧。

本教程將提高您對(duì)ML模型的安全漏洞的認(rèn)識(shí),并將深入了解對(duì)抗性機(jī)器學(xué)習(xí)的熱門話題怒坯。 您可能會(huì)驚訝地發(fā)現(xiàn),在圖像中添加不可察覺(jué)的擾動(dòng)會(huì)導(dǎo)致截然不同的模型性能藻懒。 鑒于這是一個(gè)教程剔猿,我們將通過(guò)一個(gè)圖像分類器的例子來(lái)探討這個(gè)主題。 具體來(lái)說(shuō)嬉荆,我們將使用第一種也是最流行的攻擊方法-快速梯度符號(hào)攻擊(Fast Gradient Sign Attack ,FGSM)來(lái)欺騙MNIST分類器归敬。

威脅模型(Threat Model)

有很多種類的對(duì)抗性攻擊,每種攻擊都有不同的目標(biāo)和攻擊者的知識(shí)假設(shè)。但是汪茧,總體目標(biāo) 是在輸入數(shù)據(jù)中增加最少的擾動(dòng)量椅亚,以導(dǎo)致期望的錯(cuò)誤分類。攻擊者的知識(shí)有幾種假設(shè)舱污,其中兩種假設(shè)是: 白盒子(white-box) 和 黑盒子(black-box)呀舔。 白盒子 攻擊假定攻擊者擁有對(duì)模型的全部知識(shí)和訪問(wèn)權(quán)限,包括體系結(jié)構(gòu)扩灯、輸入媚赖、輸出和權(quán)重。 黑盒子 攻擊假設(shè)攻擊者只能訪問(wèn)模型的輸入和輸出珠插,而對(duì)底層架構(gòu)或權(quán)重一無(wú)所知惧磺。 還有幾種目標(biāo)類型,包括 錯(cuò)誤分類(misclassification) 和 源/目標(biāo)錯(cuò)誤分類(source/target misclassification) 捻撑。 錯(cuò)誤分類 的目標(biāo)意味著對(duì)手只希望輸出分類是錯(cuò)誤的磨隘,而不關(guān)心新的分類是什么。 源/目標(biāo)錯(cuò)誤分類 意味著對(duì)手希望更改最初屬于特定源類的圖像布讹,從而將其歸類為特定的目標(biāo)類琳拭。

在這種情況下,F(xiàn)GSM攻擊是以 錯(cuò)誤分類 為目標(biāo)的 白盒攻擊 描验。 有了這些背景信息白嘁,我們現(xiàn)在可以詳細(xì)討論攻擊(attack)了。
快速梯度符號(hào)攻擊(Fast Gradient Sign Attack)

迄今為止膘流,第一次也是最流行的對(duì)抗性攻擊(adversarial attacks)之一被稱為 快速梯度符號(hào)攻擊(FGSM) 絮缅, 古德費(fèi)爾特對(duì)此進(jìn)行了描述: Explaining and Harnessing Adversarial Examples。 攻擊是非常強(qiáng)大的呼股,但卻是直觀的耕魄。它是設(shè)計(jì)用來(lái)攻擊神經(jīng)網(wǎng)絡(luò),利用他們的學(xué)習(xí)方式彭谁,梯度 吸奴。其思想很簡(jiǎn)單, 不是通過(guò)調(diào)整基于反向傳播梯度的權(quán)重來(lái)最小化損失缠局,而是 基于相同的反向傳播梯度調(diào)整輸入數(shù)據(jù)则奥, 使損失最大化 。換句話說(shuō)狭园,攻擊使用損失W.r.t輸入數(shù)據(jù)的梯度读处,然后調(diào)整輸入數(shù)據(jù)以最大化損失。

在我們進(jìn)入代碼之前唱矛,讓我們看一下著名的 FGSM 熊貓示例罚舱,并提取一些記號(hào)(notation)井辜。
fgsm_panda_image

從圖片中, x
是被正確分類為“panda”的原始圖像, y 是 x 的真正的類標(biāo)簽管闷。 θ 表示模型參數(shù)粥脚,并且 J(θ,x,y) 用來(lái) 訓(xùn)練網(wǎng)絡(luò)的損失。 攻擊將梯度反向傳播回輸入數(shù)據(jù)以進(jìn)行計(jì)算 ?xJ(θ,x,y) 渐北。 然后阿逃,它沿著使損失最大化的方向(i.e. sign(?xJ(θ,x,y))) 上 調(diào)整輸入數(shù)據(jù)一小步(? 或 0.007 在圖片中)。 由此產(chǎn)生的擾動(dòng)圖像(perturbed image), x′

, 就會(huì)被目標(biāo)網(wǎng)絡(luò) 誤分類(misclassified) 為 “gibbon”赃蛛, 但事實(shí)上 被擾動(dòng)的圖像依然是個(gè) “panda” 恃锉。

希望現(xiàn)在你已明了本教程的動(dòng)機(jī)了,所以讓我們跳到它的具體實(shí)現(xiàn)吧呕臂。

from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import numpy as np
import matplotlib.pyplot as plt

實(shí)現(xiàn)

在這一小節(jié)中, 我們將討論輸入?yún)?shù)破托,定義在攻擊之下的模型,然后編寫攻擊代碼然后將一些測(cè)試跑起來(lái)歧蒋。
輸入

本教程只有三個(gè)輸入土砂,定義如下:

epsilons - 要用于運(yùn)行的epsilon值列表。在列表中保持0很重要谜洽,因?yàn)樗砹嗽紲y(cè)試集上的模型性能萝映。而且,從直覺(jué)上說(shuō)阐虚, 我們認(rèn)為epsilon越大序臂,擾動(dòng)越明顯,但攻擊越有效实束,降低了模型的準(zhǔn)確性奥秆。由于 數(shù)據(jù)的范圍是 [0,1]

,任何epsilon值都不應(yīng)超過(guò)1咸灿。
pretrained_model - 通向預(yù)先訓(xùn)練過(guò)的MNIST模型的路徑构订,該模型是用 pytorch/examples/mnist 。 為了簡(jiǎn)單起見(jiàn)避矢,請(qǐng)?jiān)?這里 下載經(jīng)過(guò)預(yù)先訓(xùn)練的模型悼瘾。
use_cuda - 布爾標(biāo)志使用CUDA(如果需要和可用的話)。注意审胸,帶有CUDA的GPU對(duì)于本教程來(lái)說(shuō)并不重要分尸,因?yàn)镃PU不會(huì)花費(fèi)太多時(shí)間。
epsilons = [0, .05, .1, .15, .2, .25, .3]
pretrained_model = "data/lenet_mnist_model.pth"
use_cuda=True

受攻擊模型(Model Under Attack)

如前所述歹嘹,受攻擊的模型是與 pytorch/examples/mnist 相同的MNIST模型。您可以訓(xùn)練和保存自己的MNIST模型孔庭,也可以下載和使用所提供的模型尺上。 這里的網(wǎng)絡(luò)定義和測(cè)試dataloader是從MNIST示例中復(fù)制的材蛛。本節(jié)的目的是定義model和dataloader, 然后初始化模型并加載預(yù)先訓(xùn)練的權(quán)重怎抛。

# LeNet Model definition
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        self.conv2_drop = nn.Dropout2d()
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10)

    def forward(self, x):
        x = F.relu(F.max_pool2d(self.conv1(x), 2))
        x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
        x = x.view(-1, 320)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

# MNIST Test dataset 和 dataloader 聲明
test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data', train=False, download=True, transform=transforms.Compose([
            transforms.ToTensor(),
            ])),
        batch_size=1, shuffle=True)

# 定義我們要使用的設(shè)備
print("CUDA Available: ",torch.cuda.is_available())
device = torch.device("cuda" if (use_cuda and torch.cuda.is_available()) else "cpu")

# 初始化網(wǎng)絡(luò)
model = Net().to(device)

# 加載預(yù)訓(xùn)練模型
model.load_state_dict(torch.load(pretrained_model, map_location='cpu'))

# 將模型設(shè)置為評(píng)估模式. 這是為了 Dropout layers卑吭。
model.eval()

FGSM Attack

現(xiàn)在,我們可以通過(guò)擾動(dòng)原始輸入來(lái)定義創(chuàng)建對(duì)抗性樣例(adversarial examples)的函數(shù)马绝。 fgsm_attack 函數(shù)接收三個(gè)輸入: image 是原始的干凈圖像 (x
), epsilon 是 逐像素?cái)_動(dòng)量 (?), 而 data_grad 是損失相對(duì)于(w.r.t)輸入圖像的梯度: (?xJ(θ,x,y)

) 豆赏。 有了這三個(gè)輸入,該函數(shù)就會(huì)按下述方法 創(chuàng)建擾動(dòng)圖像(perturbed image):
perturbed_image=image+epsilon?sign(data_grad)=x+??sign(?xJ(θ,x,y))

最后, 為了保持?jǐn)?shù)據(jù)的原始范圍富稻,將擾動(dòng)圖像裁剪到 [0,1]

范圍內(nèi)掷邦。

# FGSM 攻擊代碼
def fgsm_attack(image, epsilon, data_grad):
    # Collect the element-wise sign of the data gradient
    sign_data_grad = data_grad.sign()
    # Create the perturbed image by adjusting each pixel of the input image
    perturbed_image = image + epsilon*sign_data_grad
    # Adding clipping to maintain [0,1] range
    perturbed_image = torch.clamp(perturbed_image, 0, 1)
    # Return the perturbed image
    return perturbed_image

測(cè)試函數(shù)

最后,本教程的中心結(jié)果來(lái)自于 test 函數(shù)椭赋。每次調(diào)用該測(cè)試函數(shù)都會(huì)在MNIST測(cè)試集上執(zhí)行完整的測(cè)試步驟抚岗, 并報(bào)告最終的準(zhǔn)確性。但是哪怔,請(qǐng)注意宣蔚,此函數(shù)也接受 epsilon 輸入。這是因?yàn)?test 函數(shù)報(bào)告了一個(gè)模型的準(zhǔn)確性认境, 該模型正受到來(lái)自實(shí)力 ?
的對(duì)手的攻擊胚委。更具體地說(shuō),對(duì)于測(cè)試集中的每個(gè)樣本叉信, 該函數(shù)計(jì)算loss w.r.t the input data (data_grad)亩冬,用 fgsm_attack (perturbed_data

) 創(chuàng)建一個(gè)受擾動(dòng)的圖像,然后檢查被擾動(dòng)的樣例是否是對(duì)抗性的茉盏。除了測(cè)試模型的準(zhǔn)確性外鉴未, 該函數(shù)還保存并返回了一些成功的對(duì)抗性樣例,以供以后可視化鸠姨。

def test( model, device, test_loader, epsilon ):

    # Accuracy counter
    correct = 0
    adv_examples = []

    # Loop over all examples in test set
    for data, target in test_loader:

        # Send the data and label to the device
        data, target = data.to(device), target.to(device)

        # Set requires_grad attribute of tensor. Important for Attack
        data.requires_grad = True

        # Forward pass the data through the model
        output = model(data)
        init_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability

        # If the initial prediction is wrong, dont bother attacking, just move on
        if init_pred.item() != target.item():
            continue

        # Calculate the loss
        loss = F.nll_loss(output, target)

        # Zero all existing gradients
        model.zero_grad()

        # Calculate gradients of model in backward pass
        loss.backward()

        # Collect datagrad
        data_grad = data.grad.data

        # Call FGSM Attack
        perturbed_data = fgsm_attack(data, epsilon, data_grad)

        # Re-classify the perturbed image
        output = model(perturbed_data)

        # Check for success
        final_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability
        if final_pred.item() == target.item():
            correct += 1
            # Special case for saving 0 epsilon examples
            if (epsilon == 0) and (len(adv_examples) < 5):
                adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
                adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )
        else:
            # Save some adv examples for visualization later
            if len(adv_examples) < 5:
                adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
                adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )

    # Calculate final accuracy for this epsilon
    final_acc = correct/float(len(test_loader))
    print("Epsilon: {}\tTest Accuracy = {} / {} = {}".format(epsilon, correct, len(test_loader), final_acc))

    # Return the accuracy and an adversarial example
    return final_acc, adv_examples

運(yùn)行 Attack

實(shí)現(xiàn)的最后一部分是實(shí)際運(yùn)行攻擊铜秆。在這里,我們對(duì) epsilons 輸入中的每個(gè)epsilon值運(yùn)行一個(gè)完整的測(cè)試步驟讶迁。 對(duì)于每個(gè)epsilon连茧,我們還保存了最終的準(zhǔn)確性和一些成功的對(duì)抗性樣例,將在接下來(lái)繪制出來(lái)巍糯。 注意打印精度是如何隨著epsilon值的增加而降低的啸驯。另外,請(qǐng)注意 ?=0

表示原始測(cè)試的準(zhǔn)確性祟峦,沒(méi)有任何攻擊罚斗。

accuracies = []
examples = []

# Run test for each epsilon
for eps in epsilons:
    acc, ex = test(model, device, test_loader, eps)
    accuracies.append(acc)
    examples.append(ex)

結(jié)果

Accuracy vs Epsilon

第一個(gè)結(jié)果是accuracy vs epsilon的圖。正如前面提到的宅楞,隨著epsilon的增加针姿,我們預(yù)計(jì)測(cè)試的準(zhǔn)確性會(huì)下降袱吆。 這是因?yàn)楦蟮膃psilon意味著我們朝著最大化損失的方向邁出了更大的一步。注意距淫,即使epsilon值是線性的绞绒, 曲線中的趨勢(shì)也不是線性的。例如榕暇,在 ?=0.05
處的準(zhǔn)確度僅比 ?=0.15 低4%蓬衡, 而 ?=0.2 的準(zhǔn)確度比 ?=0.15 低25%。 另外彤枢,注意模型的精度對(duì)10類分類器的隨機(jī)精度影響在 ?=0.25 和 ?=0.3

之間狰晚。

plt.figure(figsize=(5,5))
plt.plot(epsilons, accuracies, "*-")
plt.yticks(np.arange(0, 1.1, step=0.1))
plt.xticks(np.arange(0, .35, step=0.05))
plt.title("Accuracy vs Epsilon")
plt.xlabel("Epsilon")
plt.ylabel("Accuracy")
plt.show()

一些對(duì)抗性樣本

還記得沒(méi)有免費(fèi)午餐的思想嗎?在這種情況下堂污,隨著epsilon的增加家肯,測(cè)試精度降低,但擾動(dòng)變得更容易察覺(jué)盟猖。 實(shí)際上讨衣,攻擊者必須考慮的是準(zhǔn)確性、程度和可感知性之間的權(quán)衡式镐。在這里反镇,我們展示了在每個(gè)epsilon值下 一些成功的對(duì)抗性樣例。圖中的每一行都顯示不同的epsilon值娘汞。第一行是 ?=0
示例歹茶, 它表示原始的“干凈”圖像,沒(méi)有任何擾動(dòng)你弦。每幅圖像的標(biāo)題顯示“原始分類->對(duì)抗性分類”惊豺。 注意,當(dāng) ?=0.15 時(shí)禽作,擾動(dòng)開(kāi)始變得明顯尸昧,在 ?=0.3

時(shí)非常明顯。 然而旷偿,在所有情況下烹俗,人類仍然能夠識(shí)別正確的類別,盡管增加了噪音萍程。

# Plot several examples of adversarial samples at each epsilon
cnt = 0
plt.figure(figsize=(8,10))
for i in range(len(epsilons)):
    for j in range(len(examples[i])):
        cnt += 1
        plt.subplot(len(epsilons),len(examples[0]),cnt)
        plt.xticks([], [])
        plt.yticks([], [])
        if j == 0:
            plt.ylabel("Eps: {}".format(epsilons[i]), fontsize=14)
        orig,adv,ex = examples[i][j]
        plt.title("{} -> {}".format(orig, adv))
        plt.imshow(ex, cmap="gray")
plt.tight_layout()
plt.show()

下一步去哪里?

希望本教程能提供一些關(guān)于對(duì)抗性機(jī)器學(xué)習(xí)主題的見(jiàn)解幢妄。這里有許多潛在的方向可走。 這種攻擊代表了對(duì)抗性攻擊研究的開(kāi)始茫负,并且由于有許多關(guān)于如何攻擊和保護(hù)ML模型不受對(duì)手攻擊的想法蕉鸳。 實(shí)際上,在NIPS 2017的比賽中忍法,存在著一種對(duì)抗性的攻防競(jìng)爭(zhēng)潮尝, 本文 介紹了在這場(chǎng)比賽中所采用的許多方法:對(duì)抗攻擊和防御競(jìng)爭(zhēng)无虚。 防御方面的工作也帶來(lái)了使機(jī)器學(xué)習(xí)模型在一般情況下更加健壯的想法, 使機(jī)器學(xué)習(xí)模型既具有自然的擾動(dòng)性衍锚,又具有對(duì)抗性的輸入。

另一個(gè)方向是不同領(lǐng)域的對(duì)抗攻擊和防御嗤堰。對(duì)抗性研究并不局限于圖像領(lǐng)域戴质,請(qǐng)看 這個(gè) 對(duì)語(yǔ)音到文本模型的攻擊。 但是也許了解更多對(duì)抗性機(jī)器學(xué)習(xí)的最好方法是弄臟你的手(意思是讓你動(dòng)手嘗試)踢匣。 嘗試實(shí)現(xiàn)來(lái)自NIPS 2017 競(jìng)賽的不同的攻擊策略告匠,看看它與FGSM有何不同。然后离唬,試著保護(hù)模型不受你自己的攻擊后专。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市输莺,隨后出現(xiàn)的幾起案子戚哎,更是在濱河造成了極大的恐慌,老刑警劉巖嫂用,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件型凳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嘱函,警方通過(guò)查閱死者的電腦和手機(jī)甘畅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)往弓,“玉大人疏唾,你說(shuō)我怎么就攤上這事『疲” “怎么了槐脏?”我有些...
    開(kāi)封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)缴淋。 經(jīng)常有香客問(wèn)我准给,道長(zhǎng),這世上最難降的妖魔是什么重抖? 我笑而不...
    開(kāi)封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任露氮,我火速辦了婚禮,結(jié)果婚禮上钟沛,老公的妹妹穿的比我還像新娘畔规。我一直安慰自己,他們只是感情好恨统,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布叁扫。 她就那樣靜靜地躺著三妈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莫绣。 梳的紋絲不亂的頭發(fā)上畴蒲,一...
    開(kāi)封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音对室,去河邊找鬼模燥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛掩宜,可吹牛的內(nèi)容都是我干的蔫骂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼牺汤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼辽旋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起檐迟,我...
    開(kāi)封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤补胚,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后锅减,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糖儡,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年怔匣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了握联。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡每瞒,死狀恐怖金闽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剿骨,我是刑警寧澤代芜,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站浓利,受9級(jí)特大地震影響挤庇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贷掖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一嫡秕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苹威,春花似錦昆咽、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)调违。三九已至,卻和暖如春泻轰,著一層夾襖步出監(jiān)牢的瞬間技肩,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工浮声, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亩鬼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓阿蝶,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親黄绩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子羡洁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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