<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實(shí)操教程 | 卷積神經(jīng)網(wǎng)絡(luò)CNN詳解

          共 12020字,需瀏覽 25分鐘

           ·

          2021-08-29 15:10

          作者 | 臺(tái)運(yùn)鵬

          來源 | AI有道


          導(dǎo)讀

           

          本文將通過Filter、池化、demo、冷知識(shí)等章節(jié)對(duì)卷積神經(jīng)網(wǎng)絡(luò)(CNN)進(jìn)行解析,內(nèi)含詳細(xì)教程。

          章節(jié)

          • Filter

          • 池化

          • Demo

          • 冷知識(shí)

          • 參考

          CNN 一共分為輸入,卷積,池化,拉直,softmax,輸出。
          卷積由互關(guān)運(yùn)算(用Filter完成)和激活函數(shù)。

          Filter

          CNN常用于圖像識(shí)別,在深度學(xué)習(xí)中我們不可能直接將圖片輸入進(jìn)去,向量是機(jī)器學(xué)習(xí)的通行證,我們將圖片轉(zhuǎn)換為像素矩陣再送進(jìn)去,對(duì)于黑白的圖片,每一個(gè)點(diǎn)只有一個(gè)像素值,若為彩色的,每一個(gè)點(diǎn)會(huì)有三個(gè)像素值(RGB)。

          互關(guān)運(yùn)算其實(shí)就是做矩陣點(diǎn)乘運(yùn)算,用下面的Toy Example說明:其實(shí)就是用kernel(filter)來與像素矩陣局部做乘積,如下圖,output的第一個(gè)陰影值其實(shí)是input和kernel的陰影部分進(jìn)行矩陣乘法所得:

          接下來引入一個(gè)參數(shù)(Stride),代表我們每一次濾波器在像素矩陣上移動(dòng)的步幅,步幅共分為水平步幅和垂直步幅,下圖為水平步幅為2,垂直步幅為3的設(shè)置。

          所以filter就不斷滑過圖片,所到之處做點(diǎn)積,那么,做完點(diǎn)積之后的shape是多少呢?假設(shè)input shape是32 * 32,stride 為1,filter shape 為4 * 4,那么結(jié)束后的shape為29 * 29,計(jì)算公式是((input shape - filter shape) / stride ) + 1,記住在深度學(xué)習(xí)中務(wù)必要掌握每一層的輸入輸出。

          那么,假如stride改為3,那么((32 - 4) / 3) + 1 不是整數(shù),所以這樣的設(shè)定是錯(cuò)誤的,那么,我們可以通過padding的方式填充input shape,用0去填充,這里padding設(shè)為1,如下圖,填充意味著輸入的寬和高都會(huì)進(jìn)行增加2 * 1,那么接下來的out shape 就是 ((32 + 2 * 1 - 4)/3) + 1,即為11 * 11。

          接下來引入通道(channel),或?yàn)樯疃龋╠epth)的介紹,一張彩色照片的深度為3,每一個(gè)像素點(diǎn)由3個(gè)值組成,我們的filter的輸入通道或者說是深度應(yīng)該和輸入的一致,舉例來說,一張照片32 * 32 * 3,filter可以設(shè)置為3 * 3 * 3,我們剛開始理解了一維的互關(guān)運(yùn)算,三維無非就是filter拿出每一層和輸入的每一層做運(yùn)算,最后再組成一個(gè)深度為3的輸出,這里stride設(shè)置為1,padding也為1,所以輸出的shape為30 * 30 * 3。

          卷積的時(shí)候是用多個(gè)filter完成的,一般經(jīng)過卷積之后的output shape的輸入通道(深度)為filter的數(shù)量,下圖為輸入深度為2的操作,會(huì)發(fā)現(xiàn)一個(gè)filter的輸出最終會(huì)相加,將它的深度壓為1,而不是一開始的輸入通道。這是一個(gè)filter,多個(gè)filter最后放在一起,最后的深度就是filter的數(shù)量了。

          Q & A

          1.卷積的意義是什么呢?

          其實(shí)如果用圖片處理上的專業(yè)術(shù)語,被叫做銳化,卷積其實(shí)強(qiáng)調(diào)某些特征,然后將特征強(qiáng)化后提取出來,不同的卷積核關(guān)注圖片上不同的特征,比如有的更關(guān)注邊緣而有的更關(guān)注中心地帶等等,如下圖:

          當(dāng)完成幾個(gè)卷積層后(卷積 + 激活函數(shù) + 池化):

          可以看出,一開始提取一些比較基礎(chǔ)簡單的特征,比如邊角,后面會(huì)越來越關(guān)注某個(gè)局部比如頭部甚至是整體。

          2.如何使得不同的卷積核關(guān)注不同的地方?

          設(shè)置filter矩陣的值,比如input shape是4 * 4的,filter是2 * 2,filter是以一個(gè)一個(gè)小區(qū)域?yàn)閱挝?,如果說我們想要關(guān)注每一個(gè)小區(qū)域的左上角,那么將filter矩陣的第一個(gè)值設(shè)為1,其他全為0即可。

          總結(jié)來說,就是通過不斷改變filter矩陣的值來關(guān)注不同的細(xì)節(jié),提取不同的特征

          3.filter矩陣?yán)锏臋?quán)重參數(shù)是怎么來的?

          首先會(huì)初始化權(quán)重參數(shù),然后通過梯度下降不斷降低loss來獲得最好的權(quán)重參數(shù)

          4.常見參數(shù)的默認(rèn)設(shè)置有哪些?

          一般filter的數(shù)量(output channels)通??梢栽O(shè)置為2的指數(shù)次,如32,64,128,512,這里提供一組比較穩(wěn)定的搭配(具體還得看任務(wù)而定),F(xiàn)(kernel_size/filter_size)= 3,stride = 1,padding = 1;F = 5,stride = 1,Padding = 2;F = 1,S = 1,P = 0

          5.參數(shù)數(shù)量?

          舉例來說,filter的shape為5 * 5 * 3 ,一共6個(gè),stride設(shè)置為1,padding設(shè)為2,卷積層為(32 * 32 * 6),注意卷積層這里是代表最后的輸出shape,輸入shape為 32 * 32 * 3,那么所需要的參數(shù)數(shù)量為 6 * (5 * 5 * 3 + 1),里面 +1 的原因是原因是做完點(diǎn)積運(yùn)算之后會(huì)加偏置(bias),當(dāng)然這個(gè)參數(shù)是可以設(shè)置為沒有的

          6. 1x1 卷積的意義是什么?

          filter的shape為1 x 1,stride = 1,padding = 0,假如input為32 * 32 * 3,那么output shape = (32 - 1) / 1 + 1 = 32,換言之,它并沒有改變?cè)瓉淼膕hape,但是filter的數(shù)量可以決定輸出通道,所以,1 x 1的卷積目的是改變輸出通道??梢詫?duì)輸出通道進(jìn)行升維或者降維,降維之后乘上的參數(shù)數(shù)量會(huì)減少,訓(xùn)練會(huì)更快,內(nèi)存占用會(huì)更少。升維或降維的技術(shù)在ResNet中同樣運(yùn)用到啦(右圖):

          另外,其實(shí)1 x 1的卷積不過是實(shí)現(xiàn)多通道之間的線性疊加,如果你還記得上面多通道的意思,1 x 1 卷積改變卷積核的數(shù)量,無非就是使得不同的feature map進(jìn)行線性疊加而已(feature map指的是最后輸出的每一層疊加出來的),因?yàn)橥ǖ赖臄?shù)量可以隨時(shí)改變,1 x 1卷積也可以有跨通道信息交流的內(nèi)涵。

          池化

          卷積好之后會(huì)用RELU進(jìn)行激活,當(dāng)然,這并不會(huì)改變?cè)瓉淼膕hape,這樣可以增加模型的非線性兼容性,如果模型是線性的,很容易出問題,如XOR問題,接下來進(jìn)行池化操作(Pooling),常見的是MaxPooling(最大池化),它基本上長得跟filter一樣,只不過功能是選出區(qū)域內(nèi)的最大值。假如我們的shape是4 * 4 ,池化矩陣的shape是2 * 2,那么池化后的shape是2 * 2(4 / 2)。

          那么,池化的意義是什么?池化又可以被成為向下取樣(DownSample),經(jīng)過池化之后shape會(huì)減小不少,如果說卷積的意義是提取出特征,那么,池化的意義是在這些特征中取出最有代表性的特征,這樣可以降低像素的重復(fù)性,使得后續(xù)的卷積更有意義,同時(shí)可以降低shape,使得計(jì)算更為方便。

          當(dāng)然,也還有平均池化(AveragePooling),這樣做試圖包含區(qū)域內(nèi)的所有的特征,那么,如果圖片相鄰色素重復(fù)很多,那么最大池化是不錯(cuò)的,如果說一張圖片很多不同的特征需要關(guān)注,那么可以考慮平均池化。

          補(bǔ)充一下,可以給上述池操作加一個(gè)Global,這就意味著全局,而不是一個(gè)一個(gè)的小區(qū)域。

          Demo

          我的PyTorch完整Demo在:https://colab.research.google.com/drive/1XMlSmiZ4FjHohptX-GSHsT_CFs4EoE6f?usp=sharing

          進(jìn)行卷積池化這樣一組操作多次之后再全部拉直送入全連接網(wǎng)絡(luò),最后輸出10個(gè)值,然后優(yōu)化它們與真實(shí)標(biāo)簽的交叉熵?fù)p失,接下來用PyTorch和TensorFlow實(shí)操一下。

          首先先搭建一個(gè)簡單的PyTorch網(wǎng)絡(luò),這里采用Sequential容器寫法,當(dāng)然也可以按照普遍的self.conv1 = ...,按照Sequential寫法更加簡潔明了,后面前向傳播函數(shù)也沒有采取x = ...不斷更新x,而是直接放進(jìn)layer,遍歷每一層即可,簡潔干凈。

          # 導(dǎo)入庫
          import torch
          from torch import nn
          import torchvision
          from torchvision import datasets,transforms
          import torch.nn.functional as F
          import matplotlib.pyplot as plt
          class Net(nn.Module):
          def __init__(self):
          super().__init__()
          self.layer = nn.Sequential(
          nn.Conv2d(in_channels=1,out_channels=32,kernel_size=3),nn.ReLU(),
          nn.MaxPool2d(kernel_size=2),
          nn.Conv2d(32,64,2),nn.ReLU(),
          nn.MaxPool2d(2,2),
          nn.Flatten(),
          nn.Linear(64 * 6 * 6,10),nn.Softmax(),
          )

          def forward(self,x):
          x = self.layer(x)
          return x

          PyTorch中輸入必須為(1,1,28,28),這里比tensorflow多了一個(gè)1,原因是Torch中有一個(gè)group參數(shù),默認(rèn)為1,所以可以不設(shè)置,如果為N,就會(huì)把輸入分為N個(gè)小部分,每一個(gè)部分進(jìn)行卷積,最后再將結(jié)果拼接起來
          搭建好網(wǎng)絡(luò)之后,建議先檢驗(yàn)一下網(wǎng)絡(luò)和優(yōu)化器參數(shù)

          # 如果GPU沒有就會(huì)調(diào)到CPU
          device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
          model = Net().to(device)
          print(model.parameters)
          # 訓(xùn)練時(shí)還需要優(yōu)化器(Optimizer)
          optimizer = torch.optim.Adam(model.parameters())
          print(optimizer)

          接下來定義訓(xùn)練和測試函數(shù),先介紹幾個(gè)小知識(shí)

          model.train() # 啟用BatchNormalization和Dropout
          model.eval() # 因?yàn)槭菧y試,所以取消兩者
          i = torch.tensor([
          [1,2,3],
          [4,5,6]
          ])
          # 輸出最大的值和它的索引
          print(i.max(1,keepdim=True))
          # torch.return_types.max(values=tensor([[3],[6]]), indices=tensor([[2],[2]]))
          # 一般只要索引的話:
          print(i.max(1,keepdim=True))[1]
          # tensor([[2],
          # [2]])
          a = torch.tensor([1,2,3,4])
          b = torch.tensor([[1],
          [-1],
          [-2],
          2])
          # 將a轉(zhuǎn)換為與b形狀相同
          a.view_as(b)
          print(a)
          # tensor([[1],
          # [2],
          # [3],
          # [4]])

          # 相對(duì)于numpy的equal函數(shù),判斷tensor里每一個(gè)值是否相等
          # 輸出為True 或者 False
          print(b.eq(a.view_as(b)))

          # tensor([[ True],
          # [False],
          # [False],
          # [False]])

          # 求和用來判斷損失和準(zhǔn)確率
          # True --> 1,F(xiàn)alse --> 0
          print(b.eq(a.view_as(b)).sum())

          # tensor(1)

          # 最后將PyTorch的tensor轉(zhuǎn)換為Python中標(biāo)準(zhǔn)值
          print(b.eq(a.view_as(b)).sum().item())
          # 1
          # 下載訓(xùn)練和測試數(shù)據(jù)集
          # transforms函數(shù)可以對(duì)下載的數(shù)據(jù)做一些預(yù)處理
          # Compose 指的是將多個(gè)transforms操作組合在一起
          # ToTensor 是將[0,255] 范圍 轉(zhuǎn)換為[0,1]
          # 灰度圖片(channel=1),所以每一個(gè)括號(hào)內(nèi)只有一個(gè)值,前者代表mean,后者std(標(biāo)準(zhǔn)差)
          # 彩色圖片(channel=3),所以每一個(gè)括號(hào)內(nèi)有三個(gè)值,如
          # transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
          transform = transforms.Compose([
          transforms.ToTensor(),
          transforms.Normalize((0.5,),(0.5,))
          ])

          data_train = datasets.MNIST(root="填自己的主路徑",
          transform=transform,
          train=True,
          download=True)

          data_test = datasets.MNIST(root="填自己的主路徑",
          transform=transform,
          train=False)
          # 加載數(shù)據(jù)集
          # Load Data
          train_loader = torch.utils.data.DataLoader(dataset=data_train,
          batch_size=64,
          shuffle=True)

          test_loader = torch.utils.data.DataLoader(dataset=data_test,
          batch_size=64,
          shuffle=True)

          每一次新的batch中都需要梯度清零,否則的話梯度就會(huì)跨batch。

          def train(model,device,train_loader,optimizer,epoch):
          model.train()
          for batch_idx,(data,target) in enumerate(train_loader):
          data,target = data.to(device),target.to(device)
          optimizer.zero_grad() # 梯度清零
          output = model(data)
          loss = F.nll_loss(output,target) # negative likelihood loss
          loss.backward() # 誤差反向傳播
          optimizer.step() # 參數(shù)更新
          if (batch_idx + 1) % 200 == 0:
          print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
          epoch, batch_idx * len(data), len(train_loader.dataset),
          100. * batch_idx / len(train_loader), loss.item())) # .item()轉(zhuǎn)換為python值
          return loss.item()

          因?yàn)闇y試的時(shí)候不需要更新參數(shù),所以with torch.no_grad()。

          # 定義測試函數(shù)
          def test(model, device, test_loader):
          model.eval()
          test_loss,correct = 0 , 0
          with torch.no_grad(): # 不track梯度
          for data, target in test_loader:
          data, target = data.to(device), target.to(device)
          output = model(data)
          test_loss += F.nll_loss(output, target, reduction = 'sum') # 將一批的損失相加
          pred = output.max(1, keepdim = True)[1] # 找到概率最大的下標(biāo)
          correct += pred.eq(target.view_as(pred)).sum().item() # equals
          test_loss /= len(test_loader.dataset)
          acc = correct / len(test_loader.dataset)
          print("\nTest set: Average loss: {:.4f}, Accuracy: {} ({:.0f}%) \n".format(
          test_loss, acc ,
          100.* correct / len(test_loader.dataset)
          ))

          return acc

          接下來定義可視化函數(shù)

          def visualize(lis,epoch,*label):
          plt.xlabel("epochs")
          plt.ylabel(label)
          plt.plot(epoch,lis)
          plt.show()

          最后進(jìn)行訓(xùn)練和測試

          BATCH_SIZE = 512 # 大概需要2G的顯存
          EPOCHS = 20 # 總共訓(xùn)練批次
          DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
          train_ = []
          test_acc = []
          for epoch in range(1,EPOCHS+1):
          train_loss = train(model,DEVICE,train_loader,optimizer,epoch)
          acc = test(model,DEVICE,test_loader)
          train_.append(train_loss)
          test_acc.append(acc)

          visualize(train_,[i for i in range(20)],"loss")
          visualize(test_acc,[i for i in range(20)],"accuracy")

          接下來使用tensorflow-gpu 1.14.0再實(shí)操一下

          from tensorflow import keras
          import numpy as np
          import matplotlib.pyplot as plt
          import tensorflow as tf
          import matplotlib as mpl
          import sys

          # solve could not create cudnn handle: CUDNN_STATUS_INTERNAL_ERROR
          config = tf.compat.v1.ConfigProto()
          config.gpu_options.allow_growth = True
          session = tf.compat.v1.InteractiveSession(config=config)

          # 不同庫版本,使用此代碼塊查看
          print(sys.version_info)
          for module in mpl,np,tf,keras:
          print(module.__name__,module.__version__)

          '''
          sys.version_info(major=3, minor=6, micro=9, releaselevel='final', serial=0)
          matplotlib 3.3.4
          numpy 1.16.0
          tensorflow 1.14.0
          tensorflow.python.keras.api._v1.keras 2.2.4-tf
          '''

          # If you get numpy futurewarning,then try numpy 1.16.0

          # load train and test
          (x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

          # Scale images to the [0, 1] range
          x_train = x_train.astype("float32") / 255
          x_test = x_test.astype("float32") / 255

          # 1 Byte = 8 Bits,2^8 -1 = 255。[0,255]代表圖上的像素,同時(shí)除以一個(gè)常數(shù)進(jìn)行歸一化。1 就代表全部涂黑。0 就代表沒涂

          # Make sure images have shape (28, 28, 1)
          x_train = np.expand_dims(x_train, -1)
          x_test = np.expand_dims(x_test, -1)

          # CNN 的輸入方式必須得帶上channel,這里擴(kuò)充一下維度

          # convert class vectors to binary class matrices
          y_train = keras.utils.to_categorical(y_train, 10)
          y_test = keras.utils.to_categorical(y_test, 10)

          # y 屬于 [0,9]代表手寫數(shù)字的標(biāo)簽,這里將它轉(zhuǎn)換為0-1表示,可以類比one-hot,舉個(gè)例子,如果是2

          # [[0,0,1,0,0,0,0,0,0,0]……]

          model = keras.Sequential(
          [
          keras.Input(shape=(28, 28, 1)),
          keras.layers.Conv2D(filters=32, kernel_size=(3, 3), activation="relu"),
          keras.layers.MaxPooling2D(pool_size=(2, 2)),
          keras.layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu"),
          keras.layers.MaxPooling2D(pool_size=(2, 2)),
          keras.layers.Flatten(),
          keras.layers.Dense(units=10, activation="softmax"),
          ]
          )

          # 注意,Conv2D里面有激活函數(shù)不代表在卷積和池化的時(shí)候進(jìn)行。而是在DNN里進(jìn)行,最后拉直后直接接softmax就行


          # kernel_size 代表濾波器的大小,pool_size 代表池化的濾波器的大小

          model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

          model.summary()

          history = model.fit(x_train, y_train, batch_size=128, epochs=15, validation_split=0.1) #10層交叉檢驗(yàn)
          score = model.evaluate(x_test, y_test)
          print("Test loss:", score[0])
          print("Test accuracy:", score[1])

          # Test loss: 0.03664601594209671
          # Test accuracy: 0.989300012588501

          # visualize accuracy and loss
          def plot_(history,label):
          plt.plot(history.history[label])
          plt.plot(history.history["val_" + label])
          plt.title("model " + label)
          plt.ylabel(label)
          plt.xlabel("epoch")
          plt.legend(["train","test"],loc = "upper left")
          plt.show()

          plot_(history,"acc")
          plot_(history,"loss")

          在機(jī)器學(xué)習(xí)中畫精確度和loss的圖很有必要,這樣可以發(fā)現(xiàn)自己的代碼中是否存在問題,并且將這個(gè)問題可視化。

          冷知識(shí)

          We don't minimize total loss to find the best function.

          我們采取將數(shù)據(jù)打亂并分組成一個(gè)一個(gè)的mini-batch,每個(gè)數(shù)據(jù)所含的數(shù)據(jù)個(gè)數(shù)也是可調(diào)的。關(guān)于epoch。

          將一個(gè)mini-batch中的loss全部加起來,就更新一次參數(shù)。一個(gè)epoch就等于將所有的mini-batch都遍歷一遍,并且經(jīng)過一個(gè)就更新一次參數(shù)。

          如果epoch設(shè)為20,就將上述過程重復(fù)20遍。

          這里再細(xì)談一下batch 和 epoch。

          由圖可知,當(dāng)batch數(shù)目越多,分的越開,每一個(gè)epoch的速度理所應(yīng)當(dāng)就會(huì)**上升**的,當(dāng)batch_size = 1的時(shí)候,1 epoch 就更新參數(shù)50000次 和 batch_size = 10的時(shí)候,1 epoch就更新5000次,那么如果更新次數(shù)相等的話,batch_size = 1會(huì)花**166s**;batch_size = 10每個(gè)epoch會(huì)花**17s**,總的時(shí)間就是**17 * 10 = 170s**。其實(shí)batch_size = 1不就是[SGD](../optimization/GD.md)。隨機(jī)化很不穩(wěn)定,相對(duì)而言,batch_size = 10,收斂的會(huì)更穩(wěn)定,時(shí)間和等于1的差不多。那么何樂而不為呢?

          肯定有人要問了?隨機(jī)速度快可以理解,看一眼就更新一次參數(shù).

          為什么batch_size = 10速度和它差不多呢?按照上面來想,應(yīng)該是一個(gè)mini-batch結(jié)束再來下一個(gè),這樣慢慢進(jìn)行下去,其實(shí)沒理由啊。接下來以batch_size = 2來介紹一下:

          學(xué)過線性代數(shù)應(yīng)該明白,可以將同維度的向量拼成矩陣,來進(jìn)行矩陣運(yùn)算,這樣每一個(gè)mini-batch都在同一時(shí)間計(jì)算出來,即為平行運(yùn)算

          所有平行運(yùn)算GPU都能進(jìn)行加速。

          那么,好奇的是到底計(jì)算機(jī)看到了什么?是一個(gè)一個(gè)的數(shù)字嗎?

          其實(shí)這件事情很反直覺,原以為計(jì)算機(jī)是看一張一張的圖片,可是這個(gè)很難看出是單個(gè)數(shù)字而是數(shù)字集,那么我們?cè)囋嚳醋畲蠡袼亍?/p>

          其實(shí)左下角的6其實(shí)蠻像的耶。

          參考

          https://zh-v2.d2l.ai/
          https://demo.leemeng.tw/
          http://cs231n.stanford.edu/
          https://www.youtube.com/watch?v=FrKWiRv254g&list=PLJV_el3uVTsPy9oCRY30oBPNLCo89yu49&index=19

          往期精彩:



           時(shí)隔一年!深度學(xué)習(xí)語義分割理論與代碼實(shí)踐指南.pdf第二版來了!

           我工作第五年的學(xué)習(xí)與讀書之法

          【原創(chuàng)首發(fā)】機(jī)器學(xué)習(xí)公式推導(dǎo)與代碼實(shí)現(xiàn)30講.pdf

          瀏覽 68
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲激情综合 | 91天天干| 超碰在线伊人 | 亚洲秘 无码一区二区三区妃光 | av天天av天天爽 |