<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>

          廣告行業(yè)中那些趣事系列8:詳解BERT中分類器源碼

          共 12883字,需瀏覽 26分鐘

           ·

          2021-09-30 02:16


          摘要:BERT是近幾年NLP領(lǐng)域中具有里程碑意義的存在。因?yàn)樾Ч煤蛻?yīng)用范圍廣所以被廣泛應(yīng)用于科學(xué)研究和工程項(xiàng)目中。廣告系列中前幾篇文章有從理論的方面講過BERT的原理,也有從實(shí)戰(zhàn)的方面講過使用BERT構(gòu)建分類模型。本篇從源碼的角度從整體到局部分析BERT模型中分類器部分的源碼。

           

          目錄

          01 整體模塊劃分
          02 數(shù)據(jù)處理模塊
          03 特征處理模塊
          04 模型構(gòu)建模塊
          05 模型運(yùn)行模塊
          06 其他模塊
          總結(jié)



          整體模塊劃分


          對于機(jī)器學(xué)習(xí)工程師來說,會(huì)調(diào)包跑程序應(yīng)該是萬里長征的第一步。這一步主要是幫助我們迅速將模型應(yīng)用到實(shí)際業(yè)務(wù)中,并且提升自信心,但這還遠(yuǎn)遠(yuǎn)不夠。要想根據(jù)不同的業(yè)務(wù)場景更好的使用模型,我們需要深層次的理解模型,讀點(diǎn)源碼才能走的更遠(yuǎn)。

           

          本篇解讀的是BERT開源項(xiàng)目中分類器部分的源碼,從最開始的數(shù)據(jù)輸入到模型運(yùn)行整個(gè)流程主要可以分成數(shù)據(jù)處理模塊、特征處理模塊、模型構(gòu)建模塊和模型運(yùn)行模塊。具體如下圖所示:

          圖1 BERT分類器整體模塊劃分

           

          因?yàn)樵鷳B(tài)BERT預(yù)訓(xùn)練模型動(dòng)輒幾百甚至上千兆的大小,模型訓(xùn)練速度非常慢,對于BERT模型線上化非常不友好,所以使用目前比較火的BERT最新派生產(chǎn)品ALBERT來完成BERT線上化服務(wù)。ALBERT使用參數(shù)減少技術(shù)來降低內(nèi)存消耗從而最終達(dá)到提高BERT的訓(xùn)練速度,并且在主要基準(zhǔn)測試中均名列前茅,可謂跑的快,還跑的好。本篇解讀的BERT源碼也是基于ALBERT開源項(xiàng)目。


          項(xiàng)目開源的github工程:https://github.com/wilsonlsm006/albert_zh

          主要解讀分類器部分的源碼,代碼及注釋在run_classifier.py文件,歡迎小伙伴們fork。

          數(shù)據(jù)處理模塊


          數(shù)據(jù)處理模塊主要負(fù)責(zé)數(shù)據(jù)讀入和預(yù)處理功能。

           

          數(shù)據(jù)處理主要由數(shù)據(jù)處理器DataProcessor來完成。根據(jù)不同的任務(wù)會(huì)有不同的數(shù)據(jù)處理器子類,這里的不同表現(xiàn)在數(shù)據(jù)讀入方式和數(shù)據(jù)預(yù)處理方面。

           

          1. 數(shù)據(jù)讀入方式

          實(shí)際項(xiàng)目中數(shù)據(jù)讀入的方式多種多樣,比如csv、tsv、txt等。比如有的項(xiàng)目是需要讀取csv文件,而有的則需要tsv或者txt格式。我們可以構(gòu)建自定義的數(shù)據(jù)處理器來完成不同的項(xiàng)目需求。

           

          2. 數(shù)據(jù)預(yù)處理

          數(shù)據(jù)預(yù)處理是根據(jù)不同的NLP任務(wù)來完成不同的操作,比如單句分類任務(wù)我們需要的是text_alabel格式。而句子相似關(guān)系判斷任務(wù)需要的是text_a,text_b,label格式。其他任務(wù)也是類似的,根據(jù)不同的NLP任務(wù)來完成數(shù)據(jù)預(yù)處理操作。


          通過一個(gè)類圖來講解源碼中的數(shù)據(jù)處理器:

          圖2 數(shù)據(jù)處理器類圖


          對應(yīng)到項(xiàng)目源碼中,我們有一個(gè)DataProcessor父類。父類中有五個(gè)方法,分別是讀取tsv文件、獲得訓(xùn)練集、獲得驗(yàn)證集、獲得測試集和獲得標(biāo)簽。這里可根據(jù)業(yè)務(wù)需求增刪改獲取文件類型的函數(shù),比如讀取csv可以添加get_csv(input_file)等等。

          class DataProcessor(object):    """Base class for data converters for sequence classification data sets."""    def get_train_examples(self, data_dir):        """Gets a collection of `InputExample`s for the train set."""        raise NotImplementedError()    def get_dev_examples(self, data_dir):        """Gets a collection of `InputExample`s for the dev set."""        raise NotImplementedError()    def get_test_examples(self, data_dir):        """Gets a collection of `InputExample`s for prediction."""        raise NotImplementedError()    def get_labels(self):        """Gets the list of labels for this data set."""        raise NotImplementedError()    @classmethod    def _read_tsv(cls, input_file, quotechar=None):        """Reads a tab separated value file."""        with tf.gfile.Open(input_file, "r") as f:            reader = csv.reader(f, delimiter="\t", quotechar=quotechar)            lines = []            for line in reader:                lines.append(line)            return lines


          下面兩個(gè)子類,分別是處理句子關(guān)系判斷任務(wù)的SentencePairClassificationProcessor數(shù)據(jù)處理器和LCQMCPairClassificationProcessor分類的數(shù)據(jù)處理器。前面文章有講過如果需要做單句分類的任務(wù)我們可以在這里添加一個(gè)SentenceClassifierProcess進(jìn)行定制化開發(fā)。

           

          對應(yīng)到項(xiàng)目源碼中,因?yàn)槲覀兪蔷渥雨P(guān)系判斷任務(wù),其實(shí)就是判斷兩句話是不是有關(guān)系,這里我們得到的最終數(shù)據(jù)格式是列表類型,具體數(shù)據(jù)格式如下:

          [(guid,text_a,text_b,label),(guid,text_a,text_b,label),....]


          其中guid作為唯一識(shí)別text_atext_b句子對的標(biāo)志,可以理解為該條樣例的唯一id;


          text_atext_b是需要判斷的兩個(gè)句子;


          label字段就是標(biāo)簽,如果兩句話相似則置為1,否則為0

           

          上面四個(gè)字段guidtext_a是必須的。text_b是可選的,如果為空則變成單句分類任務(wù),不為空則是句子關(guān)系判斷任務(wù)。label在訓(xùn)練集和驗(yàn)證集是必須的,在測試集中可以不提供。


          具體代碼在SentencePairClassificationProcessor子類的_create_examples函數(shù):

          def _create_examples(self, lines, set_type):    """Creates examples for the training and dev sets."""    examples = []    print("length of lines:", len(lines))    for (i, line) in enumerate(lines):        # print('#i:',i,line)        if i == 0:            continue        guid = "%s-%s" % (set_type, i)        try:            label = tokenization.convert_to_unicode(line[2])            text_a = tokenization.convert_to_unicode(line[0])            text_b = tokenization.convert_to_unicode(line[1])            examples.append(                InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))        except Exception:            print('###error.i:', i, line)    return examples

           

          特征處理模塊


          特征處理模塊主要的功能是將數(shù)據(jù)處理模塊得到的數(shù)據(jù)轉(zhuǎn)化成特征并持久化到TFRecord文件中,由file_based_convert_examples_to_features函數(shù)完成。

          """將數(shù)據(jù)處理模塊得到的數(shù)據(jù)轉(zhuǎn)化成TFRecord文件input:    examples:數(shù)據(jù)格式為[(guid,text_a,text_b,label),(guid,text_a,text_b,label),....]    label_list:標(biāo)簽列表    max_seq_length:允許的句子最大長度     tokenizer:分詞器    output_file:TFRecord文件存儲(chǔ)路徑output:持久化到TFRecord格式文件"""def file_based_convert_examples_to_features(        examples,         label_list,         max_seq_length,         tokenizer, output_file):


          1. 預(yù)處理數(shù)據(jù)轉(zhuǎn)化成特征

          數(shù)據(jù)轉(zhuǎn)化成特征的操作主要由函數(shù)convert_single_example完成。傳統(tǒng)的機(jī)器學(xué)習(xí)需要從數(shù)據(jù)中抽取特征,NLP任務(wù)是對文本進(jìn)行分詞等操作獲取特征。BERT模型中默認(rèn)每個(gè)字字就是一個(gè)詞。

          """將預(yù)處理數(shù)據(jù)加工成模型需要的特征input:    ex_index:數(shù)據(jù)條數(shù)索引    example:數(shù)據(jù)格式為[(guid,text_a,text_b,label),(guid,text_a,text_b,label),....]    label_list:標(biāo)簽列表    max_seq_length:允許的句子最大長度,這里如果輸入句子長度不足則補(bǔ)0    tokenizer:分詞器output:  feature = InputFeatures(  input_ids=input_ids:token embedding:表示詞向量,第一個(gè)詞是CLS,分隔詞有SEP,是單詞本身  input_mask=input_mask:position embedding:為了令transformer感知詞與詞之間的位置關(guān)系  segment_ids=segment_ids:segment embedding:text_a與text_b的句子關(guān)系  label_id=label_id:標(biāo)簽  is_real_example=True)"""def convert_single_example(ex_index, example,                 label_list, max_seq_length,tokenizer):    ....    feature = InputFeatures(        input_ids=input_ids,        input_mask=input_mask,        segment_ids=segment_ids,        label_id=label_id,        is_real_example=True)    return feature


          論文中BERT模型的輸入轉(zhuǎn)化成特征如下圖所示:


          圖3 句子輸入轉(zhuǎn)化成三層Embedding


          這里需要注意下對text_atext_b的預(yù)處理操作。首先會(huì)進(jìn)行標(biāo)記化將text_atext_b轉(zhuǎn)化成tokens_atokens_b。如果tokens_b存在,那么tokens_atokens_b的長度就不能超過max_seq_length-3,因?yàn)樾枰尤?/span>cls,sep,seq三個(gè)符號(hào);如果tokens_b不存在,那么tokens_a的長度不能超過 max_seq_length -2 ,因?yàn)樾枰尤?/span> cls sep符號(hào)。


          這里通過一條具體的數(shù)據(jù)轉(zhuǎn)化成特征說明上述流程?,F(xiàn)在我們的example中有一條數(shù)據(jù),分別有三個(gè)字段:

          text_a: 這種圖片是用什么軟件制作的?

          text_b: 這種圖片制作是用什么軟件呢?

          label: 1

           

          經(jīng)過分詞之后,我們會(huì)得到:

          tokens: [CLS] [SEP] ? [SEP]


          其中[CLS]是模型額外增加的開始標(biāo)志,說明這是句首位置。[SEP]代表分隔符,我們會(huì)將兩句話拼接成一句話,通過分隔符來識(shí)別。第二句話拼接完成后也會(huì)加上一個(gè)分隔符。這里需要注意的是BERT對于中文分詞是以每個(gè)字進(jìn)行切分,并不是我們通常理解的按照中文實(shí)際的詞進(jìn)行切分。

           

          經(jīng)過特征提取之后變成了:

          input_ids101 6821 4905 1745 4275 3221 4500 784 720 6763 816 1169 868 46388043 102 6821 4905 1745 4275 1169 868 3221 4500 784 720 6763 816 1450 8043 1020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0


          input_mask1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0


          segment_ids0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0


          label_id1


          這里詳細(xì)說下我們真正給模型輸入的特征是什么。

          input_ids代表詞向量編碼。NLP任務(wù)中我們會(huì)將文本轉(zhuǎn)化成詞向量的表征形式提供給模型。通過BERT源碼中的tokenizer將句子拆分成字,并且將字映射成id。比如上面例子中第一句話有14個(gè)字,第二句話也有14個(gè)字,再加上一個(gè)開始標(biāo)志和兩個(gè)分隔符,一種有31個(gè)字。而上面例子中的input_ids列表中前31個(gè)位置都有每個(gè)字映射的id,并且相同字的映射的id也是一樣的。其他則通過添加0進(jìn)行填充;


          input_mask代表位置編碼。為了transformer感知詞與詞之間的位置關(guān)系,源碼中會(huì)將當(dāng)前位置有字的設(shè)置為1,其他用0進(jìn)行填充;


          segment_ids代表句子關(guān)系編碼。如果是句子關(guān)系判斷任務(wù)則會(huì)將text_b位置對應(yīng)的句子關(guān)系編碼置為1。這里需要注意,只要是句子關(guān)系判斷任務(wù),不管兩句話到底有沒有關(guān)系,即標(biāo)簽是否為1都會(huì)將text_b位置對應(yīng)的句子關(guān)系編碼置為1;


          label_id就代表兩句話是不是有關(guān)系。如果有關(guān)系則標(biāo)簽置為1,否則為0。

           

          2. 特征存儲(chǔ)在TFRecord格式文件

          當(dāng)我們進(jìn)行模型訓(xùn)練的時(shí)候,會(huì)將全部訓(xùn)練數(shù)據(jù)加載到內(nèi)存中。對于小規(guī)模數(shù)據(jù)集來說沒有問題,但是遇到大規(guī)模數(shù)據(jù)集時(shí)我們的內(nèi)存并不能加載全部的數(shù)據(jù),所以涉及到分批加載數(shù)據(jù)。Tensorflow給開發(fā)者提供了TFRecord格式文件。TFRecord內(nèi)部采用二進(jìn)制編碼,加載快,對大型數(shù)據(jù)轉(zhuǎn)換友好。

           

          小結(jié)下,特征處理模塊主要將預(yù)處理得到的數(shù)據(jù)轉(zhuǎn)化成特征并存儲(chǔ)到TFRecord格式文件。BERT會(huì)將句子輸入轉(zhuǎn)化成三層Embedding編碼,第一層是詞編碼,主要表示詞本身;第二層編碼是位置編碼,主要為了transformer感知詞與詞之間的位置關(guān)系;第三層編碼則表示句與句之間關(guān)系。通過這三層編碼我們就得到了模型的特征輸入。為了方便大數(shù)據(jù)集下模型訓(xùn)練加載數(shù)據(jù),我們將特征持久化到TFRecord格式文件。


          模型構(gòu)建模塊


          模型構(gòu)建模塊主要分成模型構(gòu)建和模型標(biāo)準(zhǔn)輸入。


          1. 模型構(gòu)建

          通過函數(shù)model_fn_builder來構(gòu)建自定義模型估計(jì)器。

          """自定義模型估計(jì)器(model_fn_builder)input:bert_config:bert相關(guān)的配置      num_labels:標(biāo)簽的數(shù)量      init_checkpoint:預(yù)訓(xùn)練模型      learning_rate:學(xué)習(xí)率      num_train_steps:模型訓(xùn)練輪數(shù) = (訓(xùn)練集總數(shù)/batch_size)*epochs      num_warmup_steps:線性地增加學(xué)習(xí)率,num_warmup_steps = num_train_steps * warmup_proportion       use_tpu:是否使用TPUoutput:構(gòu)建好的模型"""def model_fn_builder(bert_config, num_labels, init_checkpoint, learning_rate,                     num_train_steps, num_warmup_steps, use_tpu,                     use_one_hot_embeddings):    """Returns `model_fn` closure for TPUEstimator."""    ......    return model_fn


          這里模型構(gòu)建主要有create_model函數(shù)完成,主要完成兩件事:第一是調(diào)用modeling.py中的BertModel類創(chuàng)建模型;第二是計(jì)算交叉熵?fù)p失loss。交叉熵的值越小,兩個(gè)概率分布就越接近。

          """創(chuàng)建模型,主要完成兩件事:第一件事是調(diào)用modeling.py中國的BertModel類創(chuàng)建模型;第二件事事計(jì)算交叉熵?fù)p失loss。交叉熵的值越小,兩個(gè)概率分布就越接近。"""def create_model(bert_config, is_training, input_ids, input_mask, segment_ids,                 labels, num_labels, use_one_hot_embeddings):    """Creates a classification model."""    # 建立一個(gè)BERT分類模型(create_model)    model = modeling.BertModel(        config=bert_config,        is_training=is_training,        input_ids=input_ids,        input_mask=input_mask,        token_type_ids=segment_ids,        use_one_hot_embeddings=use_one_hot_embeddings)    ......    return (loss, per_example_loss, logits, probabilities)


          2. 模型標(biāo)準(zhǔn)輸入

          因?yàn)樵错?xiàng)目是基于Tensorflow框架開發(fā),所以需要將前面得到的特征轉(zhuǎn)化成標(biāo)準(zhǔn)的Tensorflow模型輸入格式。這塊主要由函數(shù)file_based_input_fn_builder來完成。通過輸入文件的不同可以完成訓(xùn)練集、驗(yàn)證集和測試集的輸入。

          """模型標(biāo)準(zhǔn)輸入從TFRecord格式文件中讀取特征并轉(zhuǎn)化成TensorFlow標(biāo)準(zhǔn)的數(shù)據(jù)輸入格式input:input_file:    input_file=train_file:輸入文件,可以是訓(xùn)練集、驗(yàn)證集和預(yù)測集    seq_length=FLAGS.max_seq_length:句子最大長度    is_training=True:是否訓(xùn)練標(biāo)志    drop_remainder=True:表示在少于batch_size元素的情況下是否應(yīng)刪除最后一批 ; 默認(rèn)是不刪除。output:TensorFlow標(biāo)準(zhǔn)的格式輸入"""def file_based_input_fn_builder(input_file, seq_length, is_training,                                drop_remainder):  name_to_features = {        "input_ids": tf.FixedLenFeature([seq_length], tf.int64),        "input_mask": tf.FixedLenFeature([seq_length], tf.int64),        "segment_ids": tf.FixedLenFeature([seq_length], tf.int64),        "label_ids": tf.FixedLenFeature([], tf.int64),        "is_real_example": tf.FixedLenFeature([], tf.int64),    }  ......  return input_fn


          這里需要注意的是is_training字段,對于訓(xùn)練數(shù)據(jù),需要大量的并行讀寫和打亂順序;而對于驗(yàn)證數(shù)據(jù),我們不希望打亂數(shù)據(jù),是否并行也不關(guān)心。


          小結(jié)下,模型構(gòu)建模塊主要由模型構(gòu)建和模型標(biāo)準(zhǔn)輸入兩部分。模型構(gòu)建負(fù)責(zé)創(chuàng)建和配置BERT模型。模型標(biāo)準(zhǔn)輸入則讀取TFRecord格式文件并轉(zhuǎn)化成標(biāo)準(zhǔn)的模型輸入,根據(jù)輸入文件的不同完成訓(xùn)練集、驗(yàn)證集和測試集的標(biāo)準(zhǔn)輸入。


          模型運(yùn)行模塊


          上面模型構(gòu)建好了之后即可運(yùn)行模型。Tensorflow中模型運(yùn)行需要構(gòu)建一個(gè)Estimator對象。主要通過源碼中tf.contrib.tpu.TPUEstimator()來構(gòu)建。

          """Estimator對象包裝由model_fn指定的模型input:給定輸入和其他一些參數(shù)    use_tpu:是否使用TPU    model_fn:前面構(gòu)建好的模型    config:模型運(yùn)行相關(guān)的配置    train_batch_size:訓(xùn)練batch大小    eval_batch_size:驗(yàn)證batch大小    predict_batch_size:預(yù)測batch大小output:需要進(jìn)行訓(xùn)練、計(jì)算,或預(yù)測的操作"""estimator = tf.contrib.tpu.TPUEstimator(    use_tpu=FLAGS.use_tpu,    model_fn=model_fn,    config=run_config,    train_batch_size=FLAGS.train_batch_size,    eval_batch_size=FLAGS.eval_batch_size,    predict_batch_size=FLAGS.predict_batch_size)


          1. 模型訓(xùn)練

          模型訓(xùn)練通過estimator.train即可完成:

          if FLAGS.do_train:    train_input_fn = file_based_input_fn_builder(        input_file=train_file,        seq_length=FLAGS.max_seq_length,        is_training=True,        drop_remainder=True)    ....    estimator.train(input_fn=train_input_fn, max_steps=num_train_steps)


          2. 模型驗(yàn)證

          模型驗(yàn)證通過estimator.evaluate即可完成:

          if FLAGS.do_eval:    eval_input_fn = file_based_input_fn_builder(            input_file=eval_file,            seq_length=FLAGS.max_seq_length,            is_training=False,            drop_remainder=eval_drop_remainder)    ....    result = estimator.evaluate(input_fn=eval_input_fn, steps=eval_steps, checkpoint_path=filename)


          3. 模型預(yù)測

          模型預(yù)測通過estimator.predict即可完成:

          if FLAGS.do_predict:    predict_input_fn = file_based_input_fn_builder(            input_file=predict_file,            seq_length=FLAGS.max_seq_length,            is_training=False,            drop_remainder=predict_drop_remainder)    ....    result = estimator.predict(input_fn=predict_input_fn)


          其他模塊


          1. tf日志模塊

          import tensorflow as tf# 日志的顯示等級(jí)tf.logging.set_verbosity(tf.logging.INFO) # 打印提示日志tf.logging.info("***** Runningtraining *****")# 打印傳參日志tf.logging.info("  Num examples = %d", len(train_examples))

          2. 外部傳參模塊

          import tensorflow as tfflags = tf.flagsFLAGS = flags.FLAGSflags.DEFINE_string(   "data_dir", None,   "The input data dir. Should contain the .tsv files (or other datafiles) ""for thetask.")# 設(shè)置哪些參數(shù)是必須要傳入的flags.mark_flag_as_required("data_dir")

           

          總結(jié)


          本篇主要講解BERT中分類器部分的源碼。整體來看主要分成數(shù)據(jù)處理模塊、特征處理模塊、模型構(gòu)建模塊和模型運(yùn)行模塊。數(shù)據(jù)處理模塊主要負(fù)責(zé)數(shù)據(jù)讀入和預(yù)處理工作;特征處理模塊負(fù)責(zé)將預(yù)處理后的數(shù)據(jù)轉(zhuǎn)化成特征并持久化到TFRecord格式文件中;模型構(gòu)建模塊主要負(fù)責(zé)構(gòu)建BERT模型和模型標(biāo)準(zhǔn)輸入數(shù)據(jù)準(zhǔn)備;模型運(yùn)行模塊主要負(fù)責(zé)模型訓(xùn)練、驗(yàn)證和預(yù)測。通過整體到局部的方式我們可以對BERT中的分類器源碼有深入的了解。后面可以根據(jù)實(shí)際的業(yè)務(wù)需求對分類器進(jìn)行二次開發(fā)。

          瀏覽 24
          點(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>
                  玉米地一级婬片A片 | 中文天堂视频在线观看 | 五月天婷婷av | 日韩AV电影在线观看 | 最爽亂倫刺激對白1 |