<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      大模型基礎補全計劃(三)---RNN實例與測試

      PS:要轉載請注明出處,本人版權所有。

      PS: 這個只是基于《我自己》的理解,

      如果和你的原則及想法相沖突,請諒解,勿噴。

      環境說明

      ??無

      前言


      ???本文是這個系列第三篇,它們是:

      ???在CV世界里,卷積神經網絡一直是主流。在以前,NLP的世界里,循環神經網絡是主流,站在今天大模型時代,Transformer 及相關變體,是當今的NLP的絕對主流。但是我們要了解Transformer提出的原因,還需要回到循環神經網絡,了解其歷史變遷。當然,在循環神經網絡中,一些主流的概念當前也還在使用,例如:token、詞表等等。

      ??因此,如本文題目所示,本文主要簡單介紹一下RNN,并嘗試用RNN訓練一個簡單的文本續寫模型。





      RNN (Recurrent Neural Network)


      ??



      RNN的意義

      ??在提到rnn之前,我們還是有必要先提一下cnn,cnn的應用目標是指定一個輸入,獲得一個模型輸出,多次輸入之間是沒有必然聯系。然而,在日常生活中,我們還有許多其他的任務是多個輸入之間是有前后關系的。例如:機翻、對話模型等等,這些任務都有明顯的特征,那就是輸入數據是一個序列,前面輸入的數據會對后面的輸出產生了影響,因此有了rnn模型結構。



      RNN的結構

      ??如圖(注意,此圖找不到來源出處,看到網絡大部分文章都引用了此圖,若有侵權,聯系刪除)rnn的基礎結構就三層:輸入層、隱藏層、輸出層,:

      rep_img

      ??從圖中可以知道,W是一個隱藏參數,是作為來至于上一次模型計算值\(S_{t-1}\)的參數。V是輸出的參數,U是輸入的參數。那么我們就可以簡單定義模型結構是:\(S_t = U*X_t + W*S_{t-1} + b_i\)\(O_t = V*S_t + b_o\)

      ??對于輸入層來說,其是一個輸入序列,我們輸出的內容也是一個序列。

      ??注意,這里的核心就是\(S_t\),前面的輸入\(X_t\)對應一個\(S_t\),那么在計算\(O_{t+1}\)的時候,會用到\(S_t\)。這樣對于這個模型來說,\(X_t\)\(O_{t+1}\)是有影響的,也就意味著,模型可能可以學習到\(X_t\)\(X_{t+1}\)的關系。





      基于RNN訓練一個簡單的文字序列輸出模型


      ??



      文本預處理
      import collections
      # [
      #     [line0],
      #     [line1],
      #     .....
      # ]
      def read_data_from_txt():
          with open('誅仙 (蕭鼎).txt', 'r', encoding='utf-8') as f:
              lines = f.readlines()
          
          return [line.strip() for line in lines]
      
      # 下面的tokenize函數將文本行列表(lines)作為輸入, 列表中的每個元素是一個文本序列(如一條文本行)。 
      # 每個文本序列又被拆分成一個詞元列表,詞元(token)是文本的基本單位。 最后,返回一個由詞元列表組成的列表,
      # 其中的每個詞元都是一個字符串(string)。
      # [
      #     [line0-char0, line0-char1, line0-char2, ....],
      #     [line1-char0, line1-char1, line1-char2, ....],
      #     .....
      # ]
      def tokenize(lines, token='char'):  #@save
          """將文本行拆分為單詞或字符詞元"""
          if token == 'word':
              return [line.split() for line in lines]
          elif token == 'char':
              return [list(line) for line in lines]
          else:
              print('錯誤:未知詞元類型:' + token)
      
      
      # 詞元的類型是字符串,而模型需要的輸入是數字,因此這種類型不方便模型使用。 現在,讓我們構建一個字典,
      # 通常也叫做詞表(vocabulary), 用來將字符串類型的詞元映射到從開始的數字索引中。
      def count_corpus(tokens):  #@save
          """統計詞元的頻率"""
          # 這里的tokens是1D列表或2D列表
          if len(tokens) == 0 or isinstance(tokens[0], list):
              # 將詞元列表展平成一個列表
              tokens = [token for line in tokens for token in line]
          return collections.Counter(tokens)
      
      # 返回類似{'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1}的一個字典
      class Vocab:
          """文本詞表"""
          def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
              if tokens is None:
                  tokens = []
              if reserved_tokens is None:
                  reserved_tokens = []
              # 按出現頻率排序
              # 對于Counter("hello world"),結果如下
              # Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
              counter = count_corpus(tokens)
              self._token_freqs = sorted(counter.items(), key=lambda x: x[1],
                                         reverse=True)
              # 未知詞元的索引為0
              self.idx_to_token = ['<unk>'] + reserved_tokens
              self.token_to_idx = {token: idx
                                   for idx, token in enumerate(self.idx_to_token)}
              for token, freq in self._token_freqs:
                  if freq < min_freq:
                      break
                  if token not in self.token_to_idx:
                      self.idx_to_token.append(token)
                      self.token_to_idx[token] = len(self.idx_to_token) - 1
      
          def __len__(self):
              return len(self.idx_to_token)
      
          def __getitem__(self, tokens):
              if not isinstance(tokens, (list, tuple)):
                  return self.token_to_idx.get(tokens, self.unk)
              return [self.__getitem__(token) for token in tokens]
      
          def to_tokens(self, indices):
              if not isinstance(indices, (list, tuple)):
                  return self.idx_to_token[indices]
              return [self.idx_to_token[index] for index in indices]
      
          @property
          def unk(self):  # 未知詞元的索引為0
              return 0
      
          @property
          def token_freqs(self):
              return self._token_freqs    
      
      # 將傳入的數據集映射為一個索引表
      # 返回傳入文本的索引、詞表
      def load_dataset(max_tokens=-1):
      
          lines = read_data_from_txt()
          print(f'# 文本總行數: {len(lines)}')
          # print(lines[0])
          # print(lines[10])
      
          tokens = tokenize(lines)
          # for i in range(11):
          #     print(tokens[i])
      
          vocab = Vocab(tokens, reserved_tokens=['<pad>', '<bos>', '<eos>'])
      
          # print(list(vocab.token_to_idx.items())[:10])
      
          # for i in [0, 10]:
          #     print('文本:', tokens[i])
          #     print('索引:', vocab[tokens[i]])
      
      
          corpus = [vocab[token] for line in tokens for token in line]
          if max_tokens > 0:
              corpus = corpus[:max_tokens]
          return corpus, vocab
      

      ??上面代碼做了如下事情:

      • 首先我們隨便找了一部中文小說,然后讀取其所有的行,然后得到一個包含所有行的二維列表。
      • 然后我們對每一行進行文字切割,得到了一個二維列表,列表中的每一行又被分割為一個個中文文字,也就得到了一個個token。(特別注意,站在當前的時刻,這里的token和現在主流的大語言模型的token概念是一樣的,但是不是一樣的實現。)
      • 由于模型不能直接處理文字,我們需要將文字轉換為數字,那么直接的做法就是將一個個token編號即可,這個時候我們得到了詞表(vocabulary)。
      • 然后我們根據我們得到的詞表,對原始數據集進行數字化,得到一個列表,列表中每個元素就是一個個token對應的索引。


      構造數據集及加載器
      # 以num_steps為步長,從隨機的起始位置開始,返回
      # x1=[ [random_offset1:random_offset1 + num_steps], ... , [random_offset_batchsize:random_offset_batchsize + num_steps] ]
      # y1=[ [random_offset1 + 1:random_offset1 + num_steps + 1], ... , [random_offset_batchsize + 1:random_offset_batchsize + num_steps + 1] ]
      def seq_data_iter_random(corpus, batch_size, num_steps):  #@save
          """使用隨機抽樣生成一個小批量子序列"""
          # 從隨機偏移量開始對序列進行分區,隨機范圍包括num_steps-1
          corpus = corpus[random.randint(0, num_steps - 1):]
          # 減去1,是因為我們需要考慮標簽
          num_subseqs = (len(corpus) - 1) // num_steps
          # 長度為num_steps的子序列的起始索引
          # [0, num_steps*1, num_steps*2, num_steps*3, ...]
          initial_indices = list(range(0, num_subseqs * num_steps, num_steps))
          # 在隨機抽樣的迭代過程中,
          # 來自兩個相鄰的、隨機的、小批量中的子序列不一定在原始序列上相鄰
          random.shuffle(initial_indices)
      
          def data(pos):
              # 返回從pos位置開始的長度為num_steps的序列
              return corpus[pos: pos + num_steps]
      
          num_batches = num_subseqs // batch_size
          for i in range(0, batch_size * num_batches, batch_size):
              # 在這里,initial_indices包含子序列的隨機起始索引
              initial_indices_per_batch = initial_indices[i: i + batch_size]
              X = [data(j) for j in initial_indices_per_batch]
              Y = [data(j + 1) for j in initial_indices_per_batch]
              yield torch.tensor(X), torch.tensor(Y)
      
      # 以num_steps為步長,從隨機的起始位置開始,返回
      # x1=[:, random_offset1:random_offset1 + num_steps]
      # y1=[:, random_offset1 + 1:random_offset1 + num_steps + 1]
      
      def seq_data_iter_sequential(corpus, batch_size, num_steps):  #@save
          """使用順序分區生成一個小批量子序列"""
          # 從隨機偏移量開始劃分序列
          offset = random.randint(0, num_steps)
          num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_size
          # 重新根據corpus建立X_corpus, Y_corpus,兩者之間差一位。注意X_corpus, Y_corpus的長度是batch_size的整數倍
          Xs = torch.tensor(corpus[offset: offset + num_tokens])
          Ys = torch.tensor(corpus[offset + 1: offset + 1 + num_tokens])
      
          # 直接根據batchsize劃分X_corpus, Y_corpus
          Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)
          # 計算出需要多少次才能取完數據
          num_batches = Xs.shape[1] // num_steps
          for i in range(0, num_steps * num_batches, num_steps):
              X = Xs[:, i: i + num_steps]
              Y = Ys[:, i: i + num_steps]
              yield X, Y
      
      
      class SeqDataLoader:  #@save
          """加載序列數據的迭代器"""
          def __init__(self, batch_size, num_steps, use_random_iter, max_tokens):
              if use_random_iter:
                  self.data_iter_fn = seq_data_iter_random
              else:
                  self.data_iter_fn = seq_data_iter_sequential
              self.corpus, self.vocab = dateset.load_dataset(max_tokens)
              self.batch_size, self.num_steps = batch_size, num_steps
      
          def __iter__(self):
              return self.data_iter_fn(self.corpus, self.batch_size, self.num_steps)
          
      def load_data_epoch(batch_size, num_steps,  #@save
                                 use_random_iter=False, max_tokens=10000):
          """返回時光機器數據集的迭代器和詞表"""
          data_iter = SeqDataLoader(
              batch_size, num_steps, use_random_iter, max_tokens)
          return data_iter, data_iter.vocab
      

      ??上面的代碼主要作用是:在訓練的時候,從我們在文本預處理數據中,以隨機順序或者相鄰順序抽取其中的部分數據作為隨機批量數據。每次抽取的數據維度是:(batch_size, num_steps)



      搭建RNN訓練框架

      ??按照原來的經驗,我們要設計一個訓練框架,第一步就要搭建網絡,此網絡用于接收一個輸入,輸出一個輸出。

      def rnn(inputs, state, params):
          # inputs的形狀:(時間步數量,批量大小,詞表大小)
          # inputs的形狀:(num_steps,batch_size,詞表大小)
          # W_xh的形狀: (詞表大小, num_hiddens)
          # W_hh的形狀:(num_hiddens, num_hiddens)
          # b_h 的形狀:(num_hiddens)
          # W_hq的形狀:(num_hiddens, 詞表大小)
          # b_q 的形狀:(詞表大小)
          W_xh, W_hh, b_h, W_hq, b_q = params
          # H的形狀:(batch_size, num_hiddens)
          H, = state
          outputs = []
          # X的形狀:(批量大小,詞表大小)
          # X的形狀:(batch_size,詞表大小)
          for X in inputs:
              # H是上一次預測的一個參數,每次計算隱藏層值后,更新H的值
              # H = tanh(X*W_xh + H*W_hh + b_h) 
              H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)
              # Y是輸出值,每次rnn輸出的時候,都會輸出從開始到當前的所有值,因此我們需要保存所有的輸出值
              # Y = H * W_hq + b_q
              # Y的形狀:(batch_size,詞表大小)
              Y = torch.mm(H, W_hq) + b_q
              outputs.append(Y)
          return torch.cat(outputs, dim=0), (H,)
      
      class RNNModelScratch: #@save
          """從零開始實現的循環神經網絡模型"""
          def __init__(self, vocab_size, num_hiddens, device,
                       get_params, init_state, forward_fn):
              self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
              # 初始化了隱藏參數 W_xh, W_hh, b_h,  W_hq, b_q
              self.params = get_params(vocab_size, num_hiddens, device)
              self.init_state, self.forward_fn = init_state, forward_fn
      
          def __call__(self, X, state):
              # X的形狀:(batch_size, num_steps)
              # X one_hot之后的形狀:(num_steps,batch_size,詞表大小)
              X = F.one_hot(X.T, self.vocab_size).type(torch.float32)
              return self.forward_fn(X, state, self.params)
      
          def begin_state(self, batch_size, device):
              return self.init_state(batch_size, self.num_hiddens, device)
      
      # 用框架
      #@save
      class RNNModel(nn.Module):
          """循環神經網絡模型"""
          def __init__(self, rnn_layer, vocab_size, device, **kwargs):
              super(RNNModel, self).__init__(**kwargs)
              self.rnn = rnn_layer
              self.vocab_size = vocab_size
              self.num_hiddens = self.rnn.hidden_size
              # 如果RNN是雙向的(之后將介紹),num_directions應該是2,否則應該是1
              if not self.rnn.bidirectional:
                  self.num_directions = 1
                  self.linear = nn.Linear(self.num_hiddens, self.vocab_size, device=device)
              else:
                  self.num_directions = 2
                  self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size, device=device)
      
          def forward(self, inputs, state):
              X = F.one_hot(inputs.T.long(), self.vocab_size)
              X = X.to(torch.float32)
              Y, state = self.rnn(X, state)
              # 全連接層首先將Y的形狀改為(時間步數*批量大小,隱藏單元數)
              # 它的輸出形狀是(時間步數*批量大小,詞表大小)。
              output = self.linear(Y.reshape((-1, Y.shape[-1])))
              return output, state
      
          def begin_state(self, device, batch_size=1):
              if not isinstance(self.rnn, nn.LSTM):
                  # nn.GRU以張量作為隱狀態
                  return  torch.zeros((self.num_directions * self.rnn.num_layers,
                                       batch_size, self.num_hiddens),
                                      device=device)
              else:
                  # nn.LSTM以元組作為隱狀態
                  return (torch.zeros((
                      self.num_directions * self.rnn.num_layers,
                      batch_size, self.num_hiddens), device=device),
                          torch.zeros((
                              self.num_directions * self.rnn.num_layers,
                              batch_size, self.num_hiddens), device=device))
      
      

      ??上面主要是設計了兩個網絡類:RNNModelScratch、RNNModel。前者是手搓rnn實現。后者是借用torch框架來實現一個簡單的rnn網絡。他們的主要做了如下幾個事情:

      • 接收(batch_size, num_steps)的輸入,并將輸入轉換為one_hot向量模式,其shape是(num_steps,batch_size,詞表大小)
      • 通過rnn的計算,然后通過變換,將最終輸出映射到(batch_size * num_steps, 詞表大小)

      ??其實我們觀察輸入和輸出,就可以理解一個事情:輸入的內容就是輸入序列所有的字符對應的one_hot向量。輸出的內容就是batch_size * num_steps個向量,代表輸出的文字序列信息,每個向量里面的最大值就代表了網絡預測的文字id。

      ??有了網絡,對于部署角度來說,我們只需要實現預測過程即可:

      def predict_ch8(prefix, num_preds, net, vocab, device):  #@save
          """在prefix后面生成新字符"""
          state = net.begin_state(batch_size=1, device=device)
          outputs = [vocab[prefix[0]]]
          get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1))
          for y in prefix[1:]:  # 預熱期
              _, state = net(get_input(), state)
              outputs.append(vocab[y])
          for _ in range(num_preds):  # 預測num_preds步
              # y 包含從開始到現在的所有輸出
              # state是當前計算出來的隱藏參數
              y, state = net(get_input(), state)
              outputs.append(int(y.argmax(dim=1).reshape(1)))
          return ''.join([vocab.idx_to_token[i] for i in outputs])
      

      ??由于輸出的信息就是batch_size * num_steps個向量,那么只需要計算每一個向量的最大值id就得到了網絡輸出的tokenid,然后通過詞表反向映射回詞表,完成了預測文字輸出的功能。

      ??有了網絡、預測過程,然后就可以搭建訓練過程,訓練過程最重要的一步就是通過網絡得到輸入對應的輸出,然后根據輸出計算loss信息,然后根據loss信息進行梯度下降(這就是通用流程)

      def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):
          """訓練網絡一個迭代周期(定義見第8章)"""
          state, timer = None, Timer()
          metric = Accumulator(2)  # 訓練損失之和,詞元數量
          # X的形狀:(batch_size, num_steps)
          # Y的形狀:(batch_size, num_steps)
          for X, Y in train_iter:
              if state is None or use_random_iter:
                  # 在第一次迭代或使用隨機抽樣時初始化state
                  state = net.begin_state(batch_size=X.shape[0], device=device)
              else:
                  if isinstance(net, nn.Module) and not isinstance(state, tuple):
                      # state對于nn.GRU是個張量
                      state.detach_()
                  else:
                      # state對于nn.LSTM或對于我們從零開始實現的模型是個張量
                      for s in state:
                          s.detach_()
              y = Y.T.reshape(-1)
              X, y = X.to(device), y.to(device)
              # y_hat 包含從開始到現在的所有輸出
              # y_hat的形狀:(batch_size * num_steps, 詞表大小)
              # state是當前計算出來的隱藏參數
              y_hat, state = net(X, state)
              # 交叉熵損失函數,傳入預測值和標簽值,并求平均值
              l = loss(y_hat, y.long()).mean()
              if isinstance(updater, torch.optim.Optimizer):
                  updater.zero_grad()
                  l.backward()
                  grad_clipping(net, 1)
                  updater.step()
              else:
                  l.backward()
                  grad_clipping(net, 1)
                  # 因為已經調用了mean函數
                  updater(batch_size=1)
              # 這里記錄交叉熵損失的值的和,以及記錄對應交叉熵損失值的樣本個數
              metric.add(l * y.numel(), y.numel())
          # 求交叉熵損失的平均值,再求exp,即可得到困惑度
          return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()
      
      
      def sgd(params, lr, batch_size):
          """小批量隨機梯度下降
      
          Defined in :numref:`sec_linear_scratch`"""
          with torch.no_grad():
              for param in params:
                  param -= lr * param.grad / batch_size
                  param.grad.zero_()
      
      #@save
      def train_ch8(net, train_iter, vocab, lr, num_epochs, device,
                    use_random_iter=False):
          """訓練模型(定義見第8章)"""
          loss = nn.CrossEntropyLoss()
          # 新建一個連接客戶端
          # 指定 env=u'test1',默認端口為 8097,host 是 'localhost'
          vis = visdom.Visdom(env=u'test1', server="http://127.0.0.1", port=8097)
          animator = vis
          # 初始化
          if isinstance(net, nn.Module):
              updater = torch.optim.SGD(net.parameters(), lr)
          else:
              updater = lambda batch_size: sgd(net.params, lr, batch_size)
          predict = lambda prefix: predict_ch8(prefix, 30, net, vocab, device)
          # 訓練和預測
          for epoch in range(num_epochs):
              ppl, speed = train_epoch_ch8(
                  net, train_iter, loss, updater, device, use_random_iter)
              
      
      
              if (epoch + 1) % 10 == 0:
                  # print(predict('你是?'))
                  # print(epoch)
                  # animator.add(epoch + 1, )
      
                  if epoch == 9:
                      # 清空圖表:使用空數組來替換現有內容
                      vis.line(X=np.array([0]), Y=np.array([0]), win='train_ch8', update='replace')
      
                  vis.line(
                      X=np.array([epoch + 1]),
                      Y=[ppl],
                      win='train_ch8',
                      update='append',
                      opts={
                          'title': 'train_ch8',
                          'xlabel': 'epoch',
                          'ylabel': 'ppl',
                          'linecolor': np.array([[0, 0, 255]]),  # 藍色線條
                      }
                  )
          print(f'困惑度 {ppl:.1f}, {speed:.1f} 詞元/秒 {str(device)}')
          print(predict('你是'))
          print(predict('我有一劍'))
      

      ??其實從上面的代碼就可以看到,我們傳入數據,得到輸出,計算了交叉熵loss,然后使用sgd最小化loss,最終我們計算困惑度,得到了模型的質量。注意,這里面有關于梯度截斷的計算,這個我們只需要它是避免梯度爆炸的一個方法即可。

      ??然后我們使用如下的代碼就可以開始訓練,注意使用net就是自定義rnn,net1就是使用框架的rnn。

      def try_gpu(i=0):
          """如果存在,則返回gpu(i),否則返回cpu()
      
          Defined in :numref:`sec_use_gpu`"""
          if torch.cuda.device_count() >= i + 1:
              return torch.device(f'cuda:{i}')
          return torch.device('cpu')
      
      if __name__ == '__main__':
          num_epochs, lr = 1000, 0.5
          batch_size, num_steps = 32, 35
          data_iter, vocab  = load_data_epoch(batch_size, num_steps)
          num_hiddens = 512
          device = try_gpu()
          net = RNNModelScratch(len(vocab), num_hiddens, device, get_params,
                              init_rnn_state, rnn)
          
          rnn_layer = nn.RNN(len(vocab), num_hiddens, device=device)
          net1 = RNNModel(rnn_layer, vocab_size=len(vocab),  device=device)
          
          print(predict_ch8('你是', 30, net, vocab, device))
      
          train_ch8(net, data_iter, vocab, lr, num_epochs, device)
      

      ??我們分別使用手動構建的rnn和框架構建的rnn進行訓練和測試,結果如下:

      rep_img
      rep_img
      rep_img
      rep_img

      ??我們可以看到,模型未訓練和訓練后的對比,明顯訓練后能說兩句人話,雖然感覺還是胡說八道,但是感覺還是有點效果。





      后記


      ??總的來說,未訓練的模型和已訓練的模型的文字續寫效果完全不一樣,明顯感覺訓練之后的模型,文字續寫給人一種可以讀感覺。

      參考文獻




      打賞、訂閱、收藏、丟香蕉、硬幣,請關注公眾號(攻城獅的搬磚之路)
      qrc_img

      PS: 請尊重原創,不喜勿噴。

      PS: 要轉載請注明出處,本人版權所有。

      PS: 有問題請留言,看到后我會第一時間回復。

      posted on 2025-07-05 17:47  SkyOnSky  閱讀(169)  評論(0)    收藏  舉報

      導航

      主站蜘蛛池模板: 高清偷拍一区二区三区| 亚洲精品综合网在线8050影院| 久久人妻国产精品| 两个人免费完整高清视频| 777奇米四色成人影视色区| 在线看片免费人成视久网| 亚洲午夜理论片在线观看| 国产精品中文字幕av| 国产福利视频区一区二区| 亚洲日韩精品一区二区三区无码 | 99精品热在线在线观看视| 日日爽日日操| 日韩无套无码精品| 亚洲av日韩av综合在线观看| 欧美三级欧美成人高清| 久久SE精品一区精品二区| 亚洲爆乳WWW无码专区| 久久99精品久久久久久青青| 久久久久亚洲A√无码| 日本一区二区三区专线| 日韩在线视频一区二区三| 激情自拍校园春色中文| 福利一区二区1000| 成人午夜无人区一区二区| 色综合久久久久综合体桃花网| 免费现黄频在线观看国产| 国产高清视频一区二区乱| 国产精品美女一区二三区| 国产亚洲精品成人aa片新蒲金| jizz视频在线观看| 亚洲人成色7777在线观看不卡 | 在线观看无码av五月花| 亚洲欧美中文字幕5发布| 亚洲岛国成人免费av| 内地自拍三级在线观看| 精品一区二区亚洲国产| 在线精品另类自拍视频| 被灌满精子的少妇视频| 国内精品久久人妻无码妲| 午夜高清福利在线观看| 国产亚洲精品aaaa片app|