日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

LightGBM源码阅读+理论分析(处理特征类别,缺省值的实现细节)

發布時間:2023/12/14 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LightGBM源码阅读+理论分析(处理特征类别,缺省值的实现细节) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

關于LightGBM,網上已經介紹的很多了,筆者也零零散散的看了一些,有些寫的真的很好,但是最終總覺的還是不夠清晰,一些細節還是懵懵懂懂,大多數只是將原論文翻譯了一下,可是某些技術具體是怎么做的呢?即落實到代碼是怎么做的呢?網上資料基本沒有,所以總有一種似懂非懂的感覺,貌似懂了LightGBM,但是又很陌生,很不踏實,所以本篇的最大區別或者優勢是:源碼分析,看看其到底怎么實現的,同時會將源碼中的參數和官網給的API結合,這樣對一些超參數理解會更透徹(對于一些諸如學習率的參數都是以前GBDT同用的,很熟悉了這里就沒源碼介紹,感興趣的自行看源碼),下面理解僅代表個人觀點,若有錯還請大家指教,一起學習交流,同時這里最大的貢獻就是對源碼的大體框架進行了一個摸索,對其中很多細節也歡迎大家交流學習!!!!最后希望本篇能夠給大家在認識LightGBM方面帶來那么一點點幫助!!!共勉!!!

建議:大家在學習LightGBM的時候(學習其他算法一樣),不要僅僅是在網上隨便百度一下,支零破碎的了解,最好一開始還是通讀一遍原論文,其是最權威的,也是網上所有解析LightGBM文章的出處,包括最終LightGBM實際實現都是來源此論文,有部分博客包括本篇博客有可能因為個人理解有偏差,導致解析有誤,如果不看論文容易被誤導,所以還是建議看論文,加上自己的理解和博客介紹可能會更好!!!!!

本篇是按照相關技術來劃分模塊介紹的,以“ -------------”分隔開來,每個技術都以紫色斜體單獨標了出來!!!!

參考:

原論文:https://papers.nips.cc/paper/6907-lightgbm-a-highly-efficient-gradient-boosting-decision-tree.pdf

源碼:GitHub - microsoft/LightGBM: A fast, distributed, high performance gradient boosting (GBT, GBDT, GBRT, GBM or MART) framework based on decision tree algorithms, used for ranking, classification and many other machine learning tasks.

官方API:Parameters — LightGBM 3.2.1.99 documentation

關于其實踐,筆者這里用了一個kaggle上面正在比賽的賽題,有興趣的同學可以看下:

lightgbm實踐:Kaggle桑坦德銀行客戶交易預測比賽baseline_愛吃火鍋的博客-CSDN博客

-------------------------------------------------------------------------------------------------------------------------------------------------------------------

概述:

? ? ? ? ? ? ?GBDT一經提出,就得到了廣泛的關注,其演變的算法多多,包括Scikit-learn,gbm in R和pGBRT等等,尤其Xgboost更是被認為泛化能力最好的算法,其在各種問題中都表現出了優異的性能,但是在使用的過程中可以感受得出來Xgboost模型訓練需要較長的時間,也就是說效率不是很高,能不能在Xgboost的基礎上進一步改進,使得其訓練速度提高呢?有需求就有了努力的方向,正是在這一背景下LightGBM出現了。

? ? ? ? ? ? ?從名字也可以看出是Light+GBM,對于GBM很熟悉了其全稱應該是gradient boosting machine即梯度提升樹算法,其在該方面繼續延續了Xgboost那一套集成學習方法,這里不再重述,Light是輕量級的意思,其關注的是模型訓練速度,其也是LightGBM提出的初衷。所以本文就著眼盯住Light,看看其究竟是怎么做到Light的。

再次強調,LightGBM的著重點就是兩個字? ?“要快!!!!!!!!!”,所以我們在看其算法的時候不論其提出采用了什么新的技術記住其就是一個出發點!!!本著這個原則我們來學習LightGBM。(所介紹的技術都是和快有關的)

?LightGBM在實際代碼實現的時候采用了多種“快”的技術集合,但就原論文的而言主要提出了兩大技術:

? (1)GOSS(Gradient-based One-Side Sampling):減少樣本數

? (2)EFB (Exclusive Feature Bundling ):減少特征數

可以看到其想法的出發點很簡單也很容易想到,關鍵怎么落實,后面大家會看到。

除此之外其在真真實現的時候還采用了直方圖,支持分布式等等,通過下面的源碼分析會看到一下細節:

直方圖差加速

自動處理缺省值,包括是否將0視為缺省值。

處理類別特征

EFB、GOSS、Leaf-wise等細節

LightGBM正是由于本著快的思想,最終導致的一個優點就是能夠處理大數據!!!!!!!

? ?---------------------------------------------------------------------------------------------------------------------------------------------------------------------

直方圖 FeatureHistogram

在訓練樹的時候,需要找到最佳劃分節點,為此其中需要遍歷特征下的每一個value,這里通常有兩種做法:pre-sorted algorithm(預排序算法)和histogram-based algorithm(直方圖算法)。

預排序算法就是傳統的要遍歷當前特征下的每一個value,其通常是在開始對該特征下的每個value進行排序,后面就是遍歷選取最佳劃分點,直方圖算法其實就是將value離散化了,生成一個個bin,常稱為分桶,離散化后的bin數其實是小于原始的value數的,于是復雜度從(#feature*#data)降為了(#feature*#bin)。

需要注意:直方圖算法并不是LightGBM所特有的或是閃亮點(其閃亮點還是論文所說的兩大技術GOSS和EFB),GBDT的相關演變算法有很多,有部分計算法就用了直方圖,類如Scikit-learn和gbm in R演變算法使用了pre-sorted algorithm算法,pGBRT算法是使用了histogram-based algorithm,而XGBoost兩者都支持,這里多說一句,Xgboost的近似算法其實就用了histogram-based algorithm如下:

(上面所說的幾種演變算法,在論文的參考文獻都可以找到,有興趣的可以拜讀一下)

對于特征k這里找到先找到一組候選分割點就是上圖中的Sk1,Sk2,Sk2,這里和LightGBM中所提到的bin其實思想是一樣的

不同于pre-sorted algorithm的窮舉方法,這里有兩種算法一種是全局算法:即在初始化tree的時候劃分好候選分割點,并且在樹的每一層都使用這些候選分割點;另一種是局部算法,即每一次劃分的時候都重新計算候選分割點。這兩者各有利弊,全局算法不需要多次計算候選節點,但需要一次獲取較多的候選節點供后續樹生長使用,而局部算法一次獲取的候選節點較少,可以在分支過程中不斷改善,即適用于生長更深的樹,兩者在effect和accuracy做trade off。

關于怎么找這些候選集合,其使用了Weighted Quantile Sketch算法,有興趣的同學可以看一下:Xgboost近似分位數算法_anshuai_aw1的博客-CSDN博客

總之這里想表達一句話就是:因為在以前GBDT的眾多演變算法中,Xgbosst性能應該是最好的一個啦(Xgboost簡單介紹https://blog.csdn.net/weixin_42001089/article/details/84965333),而LightGBM也算是演變家族中的一員,所以為了凸顯其優越性,都是直接和Xgboost對比的,論文第五部分給出了對比結果大家可以直接去看,但是須知單從和Xgboost對比角度來看并不是直方圖帶來的這種差異可觀的效果或者說很大方面不是,其是下面介紹的多種其他方面技術結合的結果,尤其是其論文提到的兩大技術,當然了單單看直方圖算法,這不是也是一種優化,所以LightGBM本著快的原則也采用了histogram-based algorithm

看到Xgboost,其近似算法和LightGBM算法的速度對比

好了說了這么多理論下面看一下其源碼吧:

位于LightGBM/src/treelearner/feature_histogram.hpp

其下一共有三個類:

FeatureMetainfo

FeatureHistogram

HistogramPool

其中?FeatureMetainfo類是直方圖分裂算法的一些基本配置,其只有屬性沒有方法。

FeatureHistogram可以說是直方圖分裂算法的核心部分,我們主要來看看該類和分裂相關的幾個主要方法,其屬性很簡單就是data_即直方圖中的儲存數據,包括一階導數總和以及二階導數總和等等。

HistogramPool類的作用就是構建data_的過程。

明白了大致的作用,接下來我們就深入其中一探究竟:

?FeatureMetainfo屬性:

類的屬性如下:

public:int num_bin;MissingType missing_type;int8_t bias = 0;uint32_t default_bin;int8_t monotone_type;double penalty;/*! \brief pointer of tree config */const Config* config;BinType bin_type; };

其中config中包含了很多與樹有關的屬性,在下面會看到,這里還有一個比較重要的屬性是missing_type,其是lightGBM的一個超參數,用于指明缺省值類型后面會看到解釋

接下來就是FeatureHistogram這個重點啦,

其屬性較為簡單:

FeatureHistogram() {data_ = nullptr;}

接下來我們重點看看該類定義的方法

(1)ThresholdL1:L1門限

static double ThresholdL1(double s, double l1) {const double reg_s = std::max(0.0, std::fabs(s) - l1);return Common::Sign(s) * reg_s;}

其中s就是一階導數和,不難看出其返回的值是:、

(2)CalculateSplittedLeafOutput:計算分裂節點的輸出

static double CalculateSplittedLeafOutput(double sum_gradients, double sum_hessians, double l1, double l2, double max_delta_step) {double ret = -ThresholdL1(sum_gradients, l1) / (sum_hessians + l2);if (max_delta_step <= 0.0f || std::fabs(ret) <= max_delta_step) {return ret;} else {return Common::Sign(ret) * max_delta_step;}}

不難看出其返回的值核心是:

注意其還有一個重載函數其實就是將結果進一步規范在了一個范圍內(min_constraint,max_contraint)

static double CalculateSplittedLeafOutput(double sum_gradients, double sum_hessians, double l1, double l2, double max_delta_step,double min_constraint, double max_constraint) {double ret = CalculateSplittedLeafOutput(sum_gradients, sum_hessians, l1, l2, max_delta_step);if (ret < min_constraint) {ret = min_constraint;} else if (ret > max_constraint) {ret = max_constraint;}return ret;}

(3)GetLeafSplitGainGivenOutput:計算當前節點的熵

static double GetLeafSplitGainGivenOutput(double sum_gradients, double sum_hessians, double l1, double l2, double output) {const double sg_l1 = ThresholdL1(sum_gradients, l1);return -(2.0 * sg_l1 * output + (sum_hessians + l2) * output * output);}

這里的output就是經過(2)函數的結果,從這里就可以清晰的看到在lightGBM真實實踐中計算當前節點熵的具體表達式,還記得在Xgboost推導的結算當前節點對應的熵的結果嗎?其大概形式是:(sum_gradients)*(sum_gradients)/(sum_hessians+L)即一階導數的平方除以二階導數加正則項的和,其和(2)的結果即這里的output比較像,只不過(2)的結果還給sum_gradients加了正則項L1(分母的正則項是+,分子的正則項是-,即帶來的效果保持了一致性),而且一階導數不是完全的平方,而是其中一個用到了激活函數(深度學習中通常這樣稱呼)sign,然后lgb最后的得到熵公式可以從上面看到。

對應的官方超參數有:

lambda_l1?:?default =?0.0, type = double, aliases:?reg_alpha, constraints:?lambda_l1?>=?0.0

  • L1 regularization

lambda_l2?:?default =?0.0, type = double, aliases:?reg_lambda,?lambda, constraints:?lambda_l2?>=?0.0

  • L2 regularization

(4)GetSplitGains:計算分裂后左右子樹的熵的和:

概括一下就是:其值=GetLeafSplitGainGivenOutput(left)+GetLeafSplitGainGivenOutput(right)

(5)GetLeafSplitGain:計算分裂前的熵

概括一下就是:其值等于:GetLeafSplitGainGivenOutput(left+right)

同時從這里可以看到:GetSplitGains-GetLeafSplitGain就是分裂后的增益

以上就是定義的基本操作函數,下面介紹的函數就是功能函數(直方圖尋找最佳切分點):

首先其可以看成是兩大類:

一:特征下的值是非連續的即所謂的類別特征。

二:特征下的值是連續的

下面先來看處理類別特征的相關函數,再來看處理連續特征的相關函數

(6)FindBestThresholdCategorical:處理處理類別特征。

其分為兩種情況:one-hot形式和非one-hot形式,one-hot形式其實是一種one VS many的情況,而非one-hot是一種many VS many形式

那么不言而喻第二種在大數據的情況下,好處多多,起碼考慮的情況更多,而且不至于樹深度太大,還有一個直觀的好處就是單一的分裂其實帶來的增益通常較少,因為每次僅僅從一大推信息中區分出那么一丁點信息,帶來的信息增益自然不會高,第二種則是不一樣。

加下來我們具體看看源碼:

當bin的數目小于meta_->config->max_cat_to_onehot時即類別數目較少時例如開關狀態只有兩種這時候就采用one-hot形式,否則不采用one-hot形式。

當采用one-hot形式時:遍歷每一個bin(類別),丟棄那些樣本少的類別以及總二階導數和少的樣本:

if (use_onehot) {for (int t = 0; t < used_bin; ++t) {// if data not enough, or sum hessian too smallif (data_[t].cnt < meta_->config->min_data_in_leaf|| data_[t].sum_hessians < meta_->config->min_sum_hessian_in_leaf) continue;data_size_t other_count = num_data - data_[t].cnt;// if data not enoughif (other_count < meta_->config->min_data_in_leaf) continue;double sum_other_hessian = sum_hessian - data_[t].sum_hessians - kEpsilon;// if sum hessian too smallif (sum_other_hessian < meta_->config->min_sum_hessian_in_leaf) continue;

注意看:這里是綜合考慮左右子樹的即只要按當前的bin劃分出來的左右子樹有一個滿足拋棄條件即拋棄。同時這里也很好的體現了所謂的直方圖差加速概念,說白了就是我們只要得到比如左子樹,那么右子樹就直接可以使用總的減去左子樹得到,是不是很快!是不是很巧妙!這就是差加速的概念。

從這里可以看到ont-hot的one VS many形式:因為 不論是other_count還是sum_other_hessian等等都是除了當前這一類別之外其他所有類別的和。

最少樣本數和最少二階導數和樹均是配置參數:meta_->config->min_data_in_leaf,config->min_sum_hessian_in_leaf

然后在剩下的bin中使用GetSplitGains計算得到分裂后的左右子樹的熵的和current_gain,其先和min_gain_shift比較,

if (current_gain <= min_gain_shift) continue;

min_gain_shift是最小熵其定義如下:

min_gain_shift = gain_shift + meta_->config->min_gain_to_split

其中gain_shift是通過GetLeafSplitGain計算得到的未分裂前的熵,min_gain_to_split是一個配置參數,其含義就是當前分裂最小需要的增益。話句話說比如沒有分裂前熵是5,我們要求分裂后熵最少的增加2,所以當current_gain小于等于7時,說明利用該Bin分裂得到的增益不大,就不選用該bin作為分裂節點了,直接跳過。更直接點說就是分裂了還沒有沒分裂前熵大,那還分什么,對吧。

要是滿足了current_gain 大于?min_gain_shift,那么我們就判斷并更新:

if (current_gain > best_gain) {best_threshold = t;best_sum_left_gradient = data_[t].sum_gradients;best_sum_left_hessian = data_[t].sum_hessians + kEpsilon;best_left_count = data_[t].cnt;best_gain = current_gain;}

這里的best_gain初始化值為kMinScore即最小得分,后續就是不斷隨著更新一直保持當前最大啦

當不采用one-hot形式時:

其首先遍歷bin,得到樣本多的bin,這一結果保存在sorted_idx中:

for (int i = 0; i < used_bin; ++i) {if (data_[i].cnt >= meta_->config->cat_smooth) {sorted_idx.push_back(i);}}

當然了,這個多的衡量同樣是一個配置參數控制的即meta_->config->cat_smooth

然后對sorted_idx根據(一階導數/(二階導數+meta_->config->cat_smooth))的大小進行排序:

auto ctr_fun = [this](double sum_grad, double sum_hess) {return (sum_grad) / (sum_hess + meta_->config->cat_smooth);};

接下來是兩個for循環,外面for循環代表的是方向即從左到右和從右到左兩種遍歷方式,為了便于理解這里舉一個簡單的例子,假設當前這一特征有4種類別:A,B,C,D,數學化后為0,1,2,3

那么我們先按照從左到右的順序遍歷,從0開始那么左樹類別就是0,右樹就是1,2,3,4計算增益比較更新,接著到1,那么左樹就是0和1,右樹就是2,3,4計算增益比較更新,接著到2,那么左樹就是0,1,2右樹就是3

其次我們按照從右到左的順序遍歷,從3開始,那么左樹就是3,右樹就是0,1,2計算增益比較更新,依次類推,,,,

代碼中find_direction是方向其是一個數組里面有(1,-1),start_position代表起始點其值是(0,used_bin - 1)

左到右即:從0開始每次加1;右到左即:從used_bin - 1開始每次加-1;

for (size_t out_i = 0; out_i < find_direction.size(); ++out_i) {auto dir = find_direction[out_i];auto start_pos = start_position[out_i];data_size_t min_data_per_group = meta_->config->min_data_per_group;data_size_t cnt_cur_group = 0;double sum_left_gradient = 0.0f;double sum_left_hessian = kEpsilon;data_size_t left_count = 0;for (int i = 0; i < used_bin && i < max_num_cat; ++i) {auto t = sorted_idx[start_pos];start_pos += dir;sum_left_gradient += data_[t].sum_gradients;sum_left_hessian += data_[t].sum_hessians;left_count += data_[t].cnt;cnt_cur_group += data_[t].cnt;

從外層for可以看到,其是先從左到右的,?sum_left_gradient等累加的過程其實就是含義就是說將當前Bin的左面所有bin都歸為左子樹,毫無疑問當前Bin的右面所有bin就都歸為左子樹,就是此處出現了many VS many的身影!!!!!!!

當然了在遍歷的過程中,在源代碼中也可以看到同樣會考慮分裂后左右子樹的最少樣本數和最少二階導數和樹即用到meta_->config->min_data_in_leaf,config->min_sum_hessian_in_leaf。

同時在代碼中還有一點需要注意的就是:

if (cnt_cur_group < min_data_per_group) continue;

這里的cnt_cur_group就是當前單個bin中的樣本數,min_data_per_group是一個配置選項,意思就是說當單個bin的data小于min_data_per_group就忽略掉,注意和min_data_in_leaf區分,min_data_in_leaf指的是當前被劃分到一邊(左或右)的所有bin的data數目。那么cat_smooth和min_data_per_group又是什么區別呢?看一下源碼的邏輯是這樣的:首先使用cat_smooth淘汰掉那些data小的bin,然后在剩下的bin中按照上述所說的排序,然后左右遍歷,遍歷的過程中又會根據min_data_per_group淘汰掉一部分小的data。

這里需要兩點說明:

<1>可以看到非one-hot 是一種many VS many的形式即有左子樹是0和1,右子樹是2,3這種情況,而在one-hot中是不會出現這種情況的,其只可能是左子樹是0,右子樹是1,2,3或左子樹是1,右子樹是0,2,3這種one VS many的形式。

<2>左右兩次遍歷的意義何在?其意義就在于缺省值到底是在哪里?其實這類問題叫做Sparsity-aware Split Finding稀疏感知算法,

當從左到右,對于缺省值就規劃到了右面,當方向相反時,缺省值都規劃到了左面,大家可以這樣想這個問題:

當從左到右時,我們記錄不論是當前一階導數和也好二階導數也罷,都是針對有值的(缺省值就沒有一階導數和二階導數),那么我們用差加速得到右子樹,既然左子樹沒有包括缺省值,那么總的減去左子樹自然就將缺省值歸到右子樹了,假如沒有缺省值,其實這里進行兩次方向的遍歷并沒有什么意義,為什么呢?假如最好的劃分是樣本1和樣本3在一邊,樣本2和樣本4在一邊,那么兩次方向遍歷無非就是對應下圖兩種情況:

有區別嗎?其實并沒有,因為下一次根據Leaf-wise原則無非就是選取左面和右面一個進行下去即可所以說1,3到底在左面還是右面并沒有關系,可是當有缺省值時就完全不一樣了,比如這里有一個缺省值5.于是上圖就變為:

看出不同了吧,其實兩次方向的遍歷說白了就是將缺省值分別放到左右看看到底哪邊好!!!!!!

<3>最后不論是one-hot還是非one-hot最后都會得到最佳分裂的Bin索引,記錄在了best_threshold中,當然了對應非one-hot還得記錄一個參數那就是方向?best_dir(1或者-1)

通過上面我們看到了一些參數(就是上文說的配置),下面我們結合官方給出的API,將其來還原到源碼中就會更加清楚其作用:

min_data_per_group?:default =?100, type = int, constraints:?min_data_per_group?>?0

  • minimal number of data per categorical group

cat_smooth?:default =?10.0, type = double, constraints:?cat_smooth?>=?0.0

  • used for the categorical features
  • this can reduce the effect of noises in categorical features, especially for categories with few data

min_data_in_leaf?:default =?20, type = int, aliases:?min_data_per_leaf,?min_data,?min_child_samples, constraints:?min_data_in_leaf?>=?0

  • minimal number of data in one leaf. Can be used to deal with over-fitting

min_sum_hessian_in_leaf?:default =?1e-3, type = double, aliases:?min_sum_hessian_per_leaf,?min_sum_hessian,?min_hessian,?min_child_weight, constraints:?min_sum_hessian_in_leaf?>=?0.0

  • minimal sum hessian in one leaf. Like?min_data_in_leaf, it can be used to deal with over-fitting

max_cat_to_onehot?:default =?4, type = int, constraints:?max_cat_to_onehot?>?0

  • when number of categories of one feature smaller than or equal to?max_cat_to_onehot, one-vs-other split algorithm will be used

注意這里所說的one-vs-other就是我們上述的one-vs-many,含義一樣。

min_gain_to_split?:default =?0.0, type = double, aliases:?min_split_gain, constraints:?min_gain_to_split?>=?0.0

  • the minimal gain to perform split

看!是不是感覺一下明朗起來了,其實這些超參數大部分都是為了防止過擬合。

以上由于是第一次介紹相關的參數所以篇幅較長,盡量將所有的細節都介紹了,下面對相同的內容就不再重述啦!比如下面同樣使用了min_data_in_leaf?以及左右遍歷等伎倆,大家明白其目的就好了。

(7)FindBestThresholdNumerical:處理連續特征

該函數只是一個表象,其真真分裂算法核心在于(8),那么這里主要是做了一個判斷,即是否將0看為缺省值,為此進行了不同的處理:

if (meta_->missing_type == MissingType::Zero) {FindBestThresholdSequence(sum_gradient, sum_hessian, num_data, min_constraint, max_constraint, min_gain_shift, output, -1, true, false);FindBestThresholdSequence(sum_gradient, sum_hessian, num_data, min_constraint, max_constraint, min_gain_shift, output, 1, true, false);} else {FindBestThresholdSequence(sum_gradient, sum_hessian, num_data, min_constraint, max_constraint, min_gain_shift, output, -1, false, true);FindBestThresholdSequence(sum_gradient, sum_hessian, num_data, min_constraint, max_constraint, min_gain_shift, output, 1, false, true);}

通過對比大家可以看到兩者的不同之處在于調用(8)函數時,最后兩個參數不同,這兩個參數是Bool類型含義如下:

bool skip_default_bin, bool use_na_as_missing

第一個代表是否跳過默認bin,第二個含義是是否使用NaN作為缺省值。同時可以看道不論那種情況,都是調用了兩遍(8)函數,兩次的不同在于1或-1,該字段的意思是方向,1代表從左到右,-1代表是從右到左。所以我們還是將重點放在(8)函數吧!

(8)FindBestThresholdSequence:處理連續特征的分裂算法核心

這里核心的東西:

double current_gain = GetSplitGains(sum_left_gradient, sum_left_hessian, sum_right_gradient, sum_right_hessian,meta_->config->lambda_l1, meta_->config->lambda_l2, meta_->config->max_delta_step,min_constraint, max_constraint, meta_->monotone_type);// gain with split is worse than without splitif (current_gain <= min_gain_shift) continue;// mark to is splittableis_splittable_ = true;// better split pointif (current_gain > best_gain) {best_left_count = left_count;best_sum_left_gradient = sum_left_gradient;best_sum_left_hessian = sum_left_hessian;// left is <= threshold, right is > threshold. so this is t-1best_threshold = static_cast<uint32_t>(t - 1 + bias);best_gain = current_gain;}

可以看到和處理類別特征非one-hot形式一樣,方向的話這里就簡單判斷了一下:是-1時從右遍歷,1是從左:

-1時:

const int t_end = 1 - bias;// from right to left, and we don't need data in bin0for (; t >= t_end; --t) {............

1時:

t = -1;for (; t <= t_end; ++t) {.............

有兩點需要注意:

<1>當遇見默認的Bin時需要跳過:

if (skip_default_bin && (t + bias) == static_cast<int>(meta_->default_bin)) { continue; }

當將0也視為缺省值時是需要跳過默認的bin 的,而將只將NaN視為缺省值時是不需要跳過默認的bin 的

<2>在沒將0也視為缺省值時需要進行的特殊處理是:

注意這一過程只在從左到右這一方向做:

if (use_na_as_missing && bias == 1) {sum_left_gradient = sum_gradient;sum_left_hessian = sum_hessian - kEpsilon;left_count = num_data;for (int i = 0; i < meta_->num_bin - bias; ++i) {sum_left_gradient -= data_[i].sum_gradients;sum_left_hessian -= data_[i].sum_hessians;left_count -= data_[i].cnt;}t = -1;}

結合上面缺省值的分析,假設特征值下是0,那么是不是也相當于沒計數,所以代碼中并沒有進行什么處理就是左右遍歷兩次,相當于將0放到左右看看哪個好?但是當不將其視為缺省值,即這里的use_na_as_missing為真時,我們就要將bin最右邊偏離bias為止的所有bin默認為了左子樹。注意在從右到左的這一過程和use_na_as_missing并沒有什么關系,也就是說將0劃分到了左面,但在從左到右的時候按以前的話應該將其劃分到右面了,但這里采用了默認還是左子樹的做法(看似道理正確,但是還是有一點小糾結,還望大佬指正,筆者也再想想,好了接著往下寫吧)

對應官方超參數的API:

use_missing? :default =?true, type = bool

  • set this to?false?to disable the special handle of missing value

zero_as_missing?:?default =?false, type = bool

  • set this to?true?to treat all zero as missing values (including the unshown values in libsvm/sparse matrices)
  • set this to?false?to use?na?for representing missing values

接著說一下HistogramPool這個類:

說白了該類主要就是構建data_的信息,其中包括bin等等,深入到代碼中你會發現其主要是使用了Dataset這個數據集,例如下面的train_data:

void DynamicChangeSize(const Dataset* train_data, const Config* config, int cache_size, int total_size) {if (feature_metas_.empty()) {int num_feature = train_data->num_features();feature_metas_.resize(num_feature);

而這個Dataset類是在通過頭文件中導入的:

#include <LightGBM/dataset.h>

于是可以找到對應的源代碼,仔細看其細節:

該類具體在LightGBM/include/LightGBM/dataset.h,大約在282行就可以看到其定義,這是頭文件,其只是定義了一些接口,其主要實現細節即.cpp是在LightGBM/src/io/dataset.cpp位置。

該類有很多屬性和方法,其中比較重要的方法就是:ConstructHistograms方法即構造直方圖方法

從該方法中可以看到很多細節,例如使不使用二階導數,不使用時會將其視為一個常數:

if (!is_constant_hessian) {#pragma omp parallel for schedule(static)for (data_size_t i = 0; i < num_data; ++i) {ordered_gradients[i] = gradients[data_indices[i]];ordered_hessians[i] = hessians[data_indices[i]];}} else {#pragma omp parallel for schedule(static)for (data_size_t i = 0; i < num_data; ++i) {ordered_gradients[i] = gradients[data_indices[i]];}}

這里的data_indices[i]就是具體到每一個樣本,可以看到,當不使用二階導數時即else部分就沒有記錄具體每個樣本的二階導數。但一階導數都是始終記錄的。

說到這里我們有必要另外起一個分界線了:因為下面涉及到LightGBM論文中提到的兩大技術之一:EFB

----------------------------------------------------------------------------------------------------------------------------------------------------------------

EFB

上述我們已經簡單介紹過了,在原論文中給出特征捆綁算法:

首先說一下其原理:(本人理解有誤或大家有疑惑的可以去看原論文4.1部分)

大部分高緯度的數據集都是稀疏的,這就為我們捆綁特征帶來了可能性,特征的稀疏就說明很多特征是相互排斥的,例如它們不總是同時取非0值,所以我們可以很放心的將多個特征捆綁為一個特征,所以復雜度就從(#data*#feature)降為(#data*#bundle),其中bundle就是經過捆綁后的特征數,通常bundle遠小于feature。

需要點(論文2.2第三段):這和以往減少特征有著很大的區別,以往采用的都是例如PCA這種,但是這種算法有一個大前提那就是

特征有冗余性比如:動物類別,是否是狗(是筆者自己舉的例子),很明顯兩個特征其實有冗余性,但是這種情況并不是出現,當特征沒有這種冗余性的時候,這種算法就遜色很多了,于是LightGBM在特征降維這個問題上,提出了EFB來解決這一棘手問題。

到此就面臨到兩個待解決的問題:(1)到底那些特征需要合并到一起(2)怎么合并到一起

其中(1)是采用圖涂色算法,同時注意到有些特征并不是100%的互相排斥,但是呢?其也很少同時取非0值,如果我們允許一部分沖突,那么這部分特征就可以進一步進行合并,使得bundle進一步減少。

說了這么多可能大家還是一頭霧水,相互排斥到底是個什么東西?下面就一種理想情況畫一張圖直觀的看一下其原理:

假設現在有13個樣本,每個樣本有四個特征A,B,C,D,可以看到這很稀疏了吧(左圖),那么怎么合并呢?很簡單將ABCD捆綁為一個特征M就是右圖

是不是感覺很眼熟,是的其逆過程即從右圖到左圖就是有種ont-hot的味道。

知道了何謂排斥那么第一個問題就解決了,再來看第二個問題,具體怎么合并,上面是一種比較極端的情況,一般的情況是這樣:

假如A特征的范圍是[0,10),B特征的范圍是[0,20),那么就給B特征加一個偏值,比如10,那么B的范圍就變為[10,30),所以捆綁為一個特征后范圍就是[0,30]。算法對應右圖

所以結合兩個問題來看其完成的任務不但是簡簡單單的捆綁,而且要達到捆綁后還能從取值上區分出特征之間的關系。

上了上面的理論再看左邊的算法就很簡單了,大概就是先計算當前特征和當前bundle沖突,沖突小就將當前特征捆綁到當前bundle中,否則就再重新建一個bundle。需要注意的是該過程只需要在最開始做一次就好了,后面就都用捆綁好的bundle,其算法復雜度顯而易見是#feature*#feature,但是當特征緯度過高,這顯然也是不好的,于是乎對算法進行了改進,其不再建立圖了,而是統計非零的個數,非零個數越多就說明沖突越大,互相排斥越小,越不能捆綁到一起。

有了上面理論,我們就看看你源碼中的部分吧:

(1)?GetConfilctCount:這里就是計算沖突樹的地方

大體可以其就是在統計非零個數。

int GetConfilctCount(const std::vector<bool>& mark, const int* indices, int num_indices, int max_cnt) {int ret = 0;for (int i = 0; i < num_indices; ++i) {if (mark[indices[i]]) {++ret;if (ret > max_cnt) {return -1;}}}return ret; }

(2)FindGroups:解決上述問題一即哪些特征需要合并

這里就看一下最關鍵的部分大約在105行

for (auto gid : search_groups) {const int rest_max_cnt = max_error_cnt - group_conflict_cnt[gid];int cnt = GetConfilctCount(conflict_marks[gid], sample_indices[fidx], num_per_col[fidx], rest_max_cnt);if (cnt >= 0 && cnt <= rest_max_cnt) {data_size_t rest_non_zero_data = static_cast<data_size_t>(static_cast<double>(cur_non_zero_cnt - cnt) * num_data / total_sample_cnt);if (rest_non_zero_data < filter_cnt) { continue; }need_new_group = false;features_in_group[gid].push_back(fidx);group_conflict_cnt[gid] += cnt;group_non_zero_cnt[gid] += cur_non_zero_cnt - cnt;MarkUsed(conflict_marks[gid], sample_indices[fidx], num_per_col[fidx]);if (is_use_gpu) {group_num_bin[gid] += bin_mappers[fidx]->num_bin() + (bin_mappers[fidx]->GetDefaultBin() == 0 ? -1 : 0);}break;}}if (need_new_group) {features_in_group.emplace_back();features_in_group.back().push_back(fidx);group_conflict_cnt.push_back(0);conflict_marks.emplace_back(total_sample_cnt, false);MarkUsed(conflict_marks.back(), sample_indices[fidx], num_per_col[fidx]);group_non_zero_cnt.emplace_back(cur_non_zero_cnt);if (is_use_gpu) {group_num_bin.push_back(1 + bin_mappers[fidx]->num_bin() + (bin_mappers[fidx]->GetDefaultBin() == 0 ? -1 : 0));}}

其首先通過?GetConfilctCount計算沖突數,如果符合要求就將其捆綁當前的bundle即源碼中features_in_group,并且將need_new_group設置為false意思是不用新建bundle啦,否則就新建,并且將當前feature的id捆綁當其中即代碼中的:

features_in_group.emplace_back(); features_in_group.back().push_back(fidx);

最后改函數返回的就是features_in_group即分好的bundle。

return features_in_group;

(3)FastFeatureBundling:進一步捆綁

捆綁過程還沒有結束,該函數對捆綁做了進一步處理:

該函數遍歷經過(2)的初步捆綁的bundle,保留了哪些只有一個特征和5個以上的bundle,對于其他的bundle做了如下處理:

對哪些稀疏度低的bundle,將其進行了拆分,又一次還原到了初始的正式特征,換句話說就是解散了稀疏度低,排斥性小,沖突大的bundle。

這里的稀疏度計算方法很簡單就是:1-非零值數目/總數目

double sparse_rate = 1.0f - static_cast<double>(cnt_non_zero) / (num_data);

然后和一個門限值?sparse_threshold比較,低就拆分:

if (sparse_rate >= sparse_threshold && is_enable_sparse) {for (size_t j = 0; j < features_in_group[i].size(); ++j) {const int fidx = features_in_group[i][j];ret.emplace_back();ret.back().push_back(fidx);}} else {ret.push_back(features_in_group[i]);}

該函數結尾還將這些分好的bundle進行了打亂:

int num_group = static_cast<int>(ret.size());Random tmp_rand(12);for (int i = 0; i < num_group - 1; ++i) {int j = tmp_rand.NextShort(i + 1, num_group);std::swap(ret[i], ret[j]);}

最后結果就保存在了ret中,該函數最后就返回了ret

------------------------------------------------------------------------------------------------------------------------------------------------------------------

GOSS:

GOSS是論文提出兩大技術的另一個,其算法如下:

對于稀疏數據集來說:

首先GBDT如果采用pre-sorted方式進行分裂可以通過忽略掉大部分值為0特征來減少復雜度(具體怎么做有興趣的可以看一下原論文參考文獻13),但是我們說了使用histogram-based的好處多多,但是GBDT如果使用了histogram-based形式,則沒有了相關的稀疏優化方法,因為histogram-based需要遍歷所有的數據的bin值,而不會管其值是不是0,同時呢?我們知道傳統的Adaboost其實數據集都是有一個權值的,用來衡量其重要程度,沒有被好好訓練的樣本其權值就大,以便下一個基學習器對其多加訓練,于是就可以依據該權值對其采用,這樣就做到采用利用部分數據集,但是呢?我們知道在GBDT中是沒有權值這一說的,其每次利用的都是整個數據集,其這些數據集的權重是一樣的,所以怎么辦呢?

于是乎lightGBM提出了GOSS,其是這樣想的:

抽樣肯定還是要抽的,畢竟減少了樣本減少了復雜度嘛!沒有權值我們根據什么抽呢?其發現可以將一階導數看做權重,一階導數大的說明離最優解還遠,這部分樣本帶來的增益大,或者說這部分樣本還沒有被好好訓練,下一步我們應該重點訓練他們。

對應的是右圖的算法,a代表對大梯度樣本的采樣率,b代表對小梯度樣本的采樣率,首先對梯度排序得到sorted,前后取前topN作為大梯度樣本集合topSet(topN的個數是通過a確定的),然后在剩下的里面隨機抽取(RandomPick為隨機抽取算法)randN個作為小梯度樣本集合randSet,最后將兩者合并作為采用后的樣本usedSet,我們就拿這個樣本取訓練,同時呢為了盡可能不改變數據集的概率分布(因為這樣抽的結果就是小梯度的樣本被不斷的減少再減少),所以還有給小樣本一個補償,那就是乘以一個常數即(1-a)/b,可以看到當a=0時就變成了隨機采用啦,這樣抽的結果還是能保持準確率的,這里有詳細的數學證明,請看論文的3.2部分。

下面來看一下源碼:

其.h文件位于LightGBM/src/boosting/goss.hpp

該類下主要有如下方法:

Init

ResetTrainingData

ResetGoss

Bagging

BaggingHelper

(1)Init、ResetTrainingData、ResetGoss

前三個方法都可以簡單將其看為GOSS的一些初始化。都很簡單,看一下源碼就大概明白了,這里順便簡單說一下ResetGoss中的幾個注意點:

首先看如下代碼:

CHECK(config_->top_rate + config_->other_rate <= 1.0f); CHECK(config_->top_rate > 0.0f && config_->other_rate > 0.0f);if (config_->bagging_freq > 0 && config_->bagging_fraction != 1.0f) {Log::Fatal("Cannot use bagging in GOSS");}Log::Info("Using GOSS");

這里的的top_rate和other_rate就是我們上面理論部分說的a,b,正如代碼看到的兩則都必須大于0且和小于1,否則就不能用GOSS,同時還會發現,當bagging_freq大于0且bagging_fraction不等于1時也是不能用GOSS的,對應到官方API如下

top_rate?:default =?0.2, type = double, constraints:?0.0?<=?top_rate?<=?1.0

  • used only in?goss
  • the retain ratio of large gradient data

other_rate?:default =?0.1, type = double, constraints:?0.0?<=?other_rate?<=?1.0

  • used only in?goss
  • the retain ratio of small gradient data

bagging_fraction?:default =?1.0, type = double, aliases:?sub_row,?subsample,?bagging, constraints:?0.0?<?bagging_fraction?<=?1.0

  • like?feature_fraction, but this will randomly select part of data without resampling
  • can be used to speed up training
  • can be used to deal with over-fitting
  • Note: to enable bagging,?bagging_freq?should be set to a non zero value as well

bagging_freq?:default =?0, type = int, aliases:?subsample_freq

  • frequency for bagging
  • 0?means disable bagging;?k?means perform bagging at every?k?iteration
  • Note: to enable bagging,?bagging_fraction?should be set to value smaller than?1.0?as well

其次:

if (config_->top_rate + config_->other_rate <= 0.5) {auto bag_data_cnt = static_cast<data_size_t>((config_->top_rate + config_->other_rate) * num_data_);bag_data_cnt = std::max(1, bag_data_cnt);tmp_subset_.reset(new Dataset(bag_data_cnt));tmp_subset_->CopyFeatureMapperFrom(train_data_);is_use_subset_ = true;}// flag to not bagging firstbag_data_cnt_ = num_data_;

可以看到當a+b小于等于0.5,就可以用GOSS,其中bag_data_cnt就是抽樣后的樣本數,num_data_是總樣本數,當大于0.5時,就暫時先不進行GOSS了。

(2)Bagging

這部分就是表象,主要就是處理了一些線程的東西,而真正的GOSS算法是在(3)BaggingHelper,所以下面會重點說一下(3)的函數。本函數也注意幾點:

Random cur_rand(config_->bagging_seed + iter * num_threads_ + i);data_size_t cur_left_count = BaggingHelper(cur_rand, cur_start, cur_cnt,tmp_indices_.data() + cur_start, tmp_indice_right_.data() + cur_start);

這里的cur_rand就是上面所說的隨機抽取小梯度時用的seed數,可以看到其是下面(3)函數的一個參數,其對應的官方API:

bagging_seed?:default =?3, type = int, aliases:?bagging_fraction_seed

  • random seed for bagging
tree_learner_->ResetTrainingData(tmp_subset_.get());

最后看到將抽樣后的數據設置為了訓練樹的訓練數據集,那么我們具體看一下tmp_subset_是怎么來的:

tmp_subset_->ReSize(bag_data_cnt_); tmp_subset_->CopySubset(train_data_, bag_data_indices_.data(), bag_data_cnt_, false);

這里是先設置了bag_data_cnt(上面我們已經說過了)大小的空間,然后將bag_data_indices_? 復制了過來,那么再看一下bag_data_indices_

std::memcpy(bag_data_indices_.data() + left_write_pos_buf_[i],tmp_indices_.data() + offsets_buf_[i], left_cnts_buf_[i] * sizeof(data_size_t));

它又是通過tmp_indices_復制來的,再看一下tmp_indices_:

data_size_t cur_left_count = BaggingHelper(cur_rand, cur_start, cur_cnt,tmp_indices_.data() + cur_start, tmp_indice_right_.data() + cur_start);

會發現最終還是定位到了(3)函數,所以我們就來看看(3)函數吧!!!!!!!!!!

(3)BaggingHelper

data_size_t top_k = static_cast<data_size_t>(cnt * config_->top_rate);data_size_t other_k = static_cast<data_size_t>(cnt * config_->other_rate);

top_k和?other_k 就是我們要抽取的大梯度數據集和小梯度數據集的樣本數

ArrayArgs<score_t>::ArgMaxAtK(&tmp_gradients, 0, static_cast<int>(tmp_gradients.size()), top_k - 1); score_t threshold = tmp_gradients[top_k - 1];

這里的threshold就是門限,梯度大于該門限的我們都抽取,因為這里先對tmp_gradients進行了排序,然后選取了索引為top_k - 1作為門限值,可想而知,大于該門限值的一共就是top_k,那么是根據什么對tmp_gradients排序的呢?

mp_gradients[i] += std::fabs(gradients_[idx] * hessians_[idx]);

會發現其和理論部分還有有點不一樣,理論部分只是高度概括使用導數(一階導數),實際上這里是是使用了一階導數和二階導數的成績進行排序的。

score_t multiply = static_cast<score_t>(cnt - top_k) / other_k;

multiply就是理論部分所說的對小梯度集合的補償常數(1-a)/b,注意這里直接使用了樣本數,結果是一樣的:

樣本總數*(1-a)/b=(樣本總數-大梯度樣本數)/小梯度樣本數=(cnt - top_k) / other_k

下面部分算是最核心的部分了吧。

首先結果是保存在了buffer中的,也就是對應(2)函數的tmp_indices_

if (grad >= threshold) {buffer[cur_left_cnt++] = start + i;++big_weight_cnt;}

可以看到對大于threshold的樣本那就是大梯度樣本直接保存到buffer中,如果是小梯度,除了保存之外,還需要補償工作:

double prob = (rest_need) / static_cast<double>(rest_all);if (cur_rand.NextFloat() < prob) {

可以看到這里的prob=還需要抽取小梯度數/總共需要抽取小梯度數,如果小于其值進行補償:

gradients_[idx] *= multiply;hessians_[idx] *= multiply;

當然了別忘了還要將該樣本保存到buffer中作為小梯度中抽取的樣本:

buffer[cur_left_cnt++] = start + i;

如果大于該值,就直接進行:

buffer[cur_left_cnt++] = start + i;

無需補償。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------

Leaf-wise

Level-wise是將同一層所有的葉子節點進行分裂,這也是以前GBDT各種演變算法所采用的策略,而LightGBM采用的Leaf-wise是每次選取最大增益的那個葉子節點進行分類,可以看到生長相同的leaf時,leaf-wise 算法可以比 level-wise 算法減少更多的損失即得到更大的增益,但是其缺點就是可能會長出比較深的決策樹,產生過擬合。因此LightGBM在Leaf-wise之上增加了一個最大深度限制,在保證高效率的同時防止過擬合。

下面來看一下源碼:

(1)首先看SerialTreeLearner::BeforeFindBestSplit這個方法:

該方法就是在尋找最佳劃分點之前需要做哪些工作,最終返回的是一個bool類型,false代表不要再往下分裂了,true代表接著分。

其中一點就包括我們上面所說的檢查當前樹的深度,如果已經超過了配置選項就不在分裂了:

if (tree->leaf_depth(left_leaf) >= config_->max_depth) {best_split_per_leaf_[left_leaf].gain = kMinScore;if (right_leaf >= 0) {best_split_per_leaf_[right_leaf].gain = kMinScore;}return false;

需注意這里僅僅檢查了左樹的深度就夠了,因為leaf-wise這種方法從上面圖中也可以看出,左右深度是一樣的,檢查一邊就好了。這里的max_depath對應的官方API:

max_depth?:default =?-1, type = int

  • limit the max depth for tree model. This is used to deal with over-fitting when?#data?is small. Tree still grows leaf-wise
  • <?0?means no limit

當然了,該方法中還有其他檢查選項類如,當數據不夠多時也是不會往下分裂的:

if (num_data_in_right_child < static_cast<data_size_t>(config_->min_data_in_leaf * 2)&& num_data_in_left_child < static_cast<data_size_t>(config_->min_data_in_leaf * 2)) {best_split_per_leaf_[left_leaf].gain = kMinScore;if (right_leaf >= 0) {best_split_per_leaf_[right_leaf].gain = kMinScore;}return false;}

這里的num_data_in_right_child和num_data_in_left_child分別代表左右子樹的數據量,可以看到多兩邊的數據量同時小于門限值的2倍時就不分裂了,其中門限值min_data_in_leaf是配置選項,對應官網API:

min_data_in_leaf?:default =?20, type = int, aliases:?min_data_per_leaf,?min_data,?min_child_samples, constraints:?min_data_in_leaf?>=?0

  • minimal number of data in one leaf. Can be used to deal with over-fitting

其他這里不講了,因為我們重點要看Leaf-wise相關內容,總之該方法的作用可以簡單看做是為了防止過擬合采取的一系列手段。

(2)SerialTreeLearner::Train

for (int split = init_splits; split < config_->num_leaves - 1; ++split) {

可以看到其先遍歷當前層的所有的葉子結點(上述理論部分是2個,實際上不僅僅是2個),其中num_leaves是葉子總數,配置選項,官網API:

num_leaves?:default =?31, type = int, aliases:?num_leaf,?max_leaves,?max_leaf, constraints:?num_leaves?>?1

  • max number of leaves in one tree
int best_leaf = static_cast<int>(ArrayArgs<SplitInfo>::ArgMax(best_split_per_leaf_)); Split(tree.get(), best_leaf, &left_leaf, &right_leaf);

得到best_leaf,然后進行劃分Split。

其他部分都是樹的訓練了,和以前GBDT本質上沒有什么不同,例如這里的Spilt具體的劃分函數等等,這小節我們只看和Leaf-wise技術有關的代碼,其他部分感興趣可以深入研究。

------------------------------------------------------------------------------------------------------------------------------------------------------------------

其他部分:

比較重點的還有GBDT部分,即集成學習boosting部分,這里和以前的提升算法沒有什么大的不同,即LightGBM重點提到的技術就是我們上面所說的,如果對其感興趣可以進一步研究,這部分代碼位置位于LightGBM/src/boosting/

---------------------------------------------------------------------------------------------------------------------------------------------------------------------

結束:

每天進步一點點!!!!!

看到很多小伙伴私信和關注,為了不迷路,歡迎大家關注筆者的微信公眾號,會定期發一些關于NLP的干活總結和實踐心得,當然別的方向也會發,一起學習:


???????

?

總結

以上是生活随笔為你收集整理的LightGBM源码阅读+理论分析(处理特征类别,缺省值的实现细节)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

www.狠狠操 | 91免费在线视频 | 精品久久久久国产 | 在线性视频日韩欧美 | 中文亚洲欧美日韩 | 欧美日韩精品在线一区二区 | 欧美一级久久 | 久久久久久久亚洲精品 | 久热国产视频 | 国产成人精品av在线观 | 天天操操 | 97超碰香蕉| 精品国产久 | 伊人电影在线观看 | 久久久久免费电影 | 在线看小早川怜子av | 精品一区二区久久久久久久网站 | 97精品国自产拍在线观看 | 国产成人久久精品 | 久热免费 | 99久精品| 久久久久在线观看 | 欧洲一区二区三区精品 | 欧美ⅹxxxxxx | 91插插插网站 | 色婷婷亚洲综合 | 日本中文一级片 | 美女网站免费福利视频 | 特黄色大片| 国产中文字幕一区二区三区 | 中文国产成人精品久久一 | 国产精品一区二区久久久久 | 黄色看片 | 韩国一区视频 | 麻豆久久久 | 999电影免费在线观看2020 | 国产精品久久综合 | 2024国产精品视频 | 国产精品a久久久久 | 不卡精品| 在线观看视频97 | 日日干精品 | 日本韩国精品一区二区在线观看 | 日日综合 | 日韩欧美视频一区二区三区 | 成年人在线看片 | 日韩字幕 | 99色在线视频| 区一区二在线 | 99久精品 | 国产一区免费视频 | 久久这里只有精品久久 | 欧美日韩国产精品一区二区三区 | 国产精品色在线 | 欧美午夜精品久久久久 | 免费在线观看国产精品 | 外国av网| 亚洲精品视频偷拍 | 亚洲区另类春色综合小说校园片 | 国产成人福利 | www.五月婷婷 | 欧美精品在线一区二区 | 在线视频 日韩 | 最新真实国产在线视频 | 2021av在线 | 欧美精品三级在线观看 | bbw av| 黄色成人小视频 | 成年人免费观看在线视频 | 欧美午夜寂寞影院 | 蜜臀av性久久久久av蜜臀妖精 | 玖玖在线视频观看 | 欧美久草在线 | 四虎影视成人永久免费观看亚洲欧美 | 国产成人免费av电影 | 夜夜夜夜爽 | 欧美日韩一区二区在线观看 | 国产精品久久9 | 亚洲国产日韩精品 | 欧美黑人猛交 | 国内精品小视频 | 成人亚洲综合 | 婷婷色伊人 | 毛片一区二区 | 亚洲最新av | 久久精视频 | 欧美日韩在线观看一区 | 天天色天| 狠狠操导航 | 2023av在线| 99riav1国产精品视频 | 一区二区电影在线观看 | 国产韩国精品一区二区三区 | 蜜桃久久久 | 国产伦精品一区二区三区四区视频 | 婷婷激情小说网 | 最新av在线免费观看 | 深爱婷婷网| 91夜夜夜| 999久久久欧美日韩黑人 | 久久免费高清视频 | 免费在线观看毛片网站 | 免费成人在线网站 | 国产精品手机在线观看 | 午夜黄色大片 | 久草在线最新视频 | 欧美影院久久 | 96av在线视频| 国产高清视频在线 | 亚洲黄色网络 | 欧美午夜精品久久久久 | 国产精品一区在线观看你懂的 | 99久久精品无码一区二区毛片 | 日韩在线观看视频免费 | 日韩在线高清免费视频 | 久久精品国产久精国产 | 一级黄色片毛片 | 国产福利一区二区在线 | 日韩av视屏在线观看 | 四虎国产精品成人免费4hu | 视频成人 | 草久热| 特级毛片在线 | 成人久久久精品国产乱码一区二区 | 久久久久免费精品国产 | av电影 一区二区 | 国产精品 日本 | 99激情网| 久久亚洲二区 | 九九热视频在线播放 | 97日日| 色网站在线免费观看 | 色婷婷激婷婷情综天天 | 亚洲三级国产 | 国产色网| av电影不卡 | 美女视频又黄又免费 | 国内外成人免费在线视频 | 欧美在线aaa | 日韩中文久久 | 日韩精品不卡在线观看 | 免费高清在线观看成人 | 国产精品免费av | 精品一区在线看 | 波多野结衣在线观看一区二区三区 | 99在线视频观看 | 九九九在线观看 | 国产精品久久久久久久午夜 | 久久久久久久久久久网站 | 中文字幕在线观看一区二区 | 午夜久久久久久久久 | 久久69精品| 久久久久国产a免费观看rela | 久久综合婷婷国产二区高清 | 香蕉久久久久久av成人 | www日韩精品 | 在线视频 你懂得 | 国产免费又爽又刺激在线观看 | a黄色片在线观看 | 韩国av一区 | 久久免费在线观看视频 | 成人小视频在线播放 | 久久精品在线 | 精品久久中文 | 日韩最新在线视频 | 激情av在线资源 | 亚洲日韩中文字幕 | 美女网站免费福利视频 | 国产99黄 | 99久久er热在这里只有精品66 | 欧美精品久久久久久 | 亚洲午夜不卡 | 亚洲综合色丁香婷婷六月图片 | 九九综合久久 | 日韩av影视 | 欧美成年网站 | 久久久久一区二区三区四区 | 精品国产乱码久久久久久浪潮 | 国产日产精品一区二区三区四区 | 亚洲自拍偷拍色图 | 在线a视频 | 亚洲欧洲视频 | 免费在线观看中文字幕 | 在线免费亚洲 | 国产污视频在线观看 | 99精彩视频在线观看免费 | 一区二区视频电影在线观看 | 国产伦理久久精品久久久久_ | 久久www免费人成看片高清 | 三级黄色欧美 | 樱空桃av | 激情中文在线 | 午夜av一区二区三区 | 超碰在线国产 | 天天插日日操 | 色干综合 | 狠狠操精品 | 福利视频一区二区 | 国产原创av片| 国产精品一区二区果冻传媒 | 色多视频在线观看 | 欧美片一区二区三区 | 91亚洲精品久久久蜜桃 | 日韩视频免费在线观看 | 国产成人精品一区二区在线 | 日本公妇色中文字幕 | 亚洲黄色在线播放 | 毛片网站观看 | 国产成人免费在线观看 | 国产精品视频app | 夜夜躁日日躁狠狠久久88av | 精品视频www| 九九热中文字幕 | 免费av在线网 | 国产成人一区二区三区影院在线 | 色激情五月 | 免费黄色av. | 99热这里只有精品免费 | 久久精品免费 | 久精品在线 | 欧美va日韩va | 婷婷六月丁 | 免费观看成人网 | 日韩中文在线观看 | 日韩一二三 | 黄色官网在线观看 | 视频国产在线观看18 | 天天天天天天天操 | 国产日产欧美在线观看 | 久久电影网站中文字幕 | 日本精品一区二区三区在线播放视频 | 久久在线免费观看 | 一本一本久久a久久精品综合妖精 | 国产日本在线观看 | 超碰在线天天 | 久久精品成人 | av电影在线观看完整版一区二区 | 91九色蝌蚪视频在线 | 蜜臀一区二区三区精品免费视频 | 在线直播av | 在线探花| 日韩欧美一区视频 | 中文字幕第 | 97超视频| 激情网五月天 | 欧美在线18| 四虎亚洲精品 | 亚洲女欲精品久久久久久久18 | av片免费播放 | 综合久久网站 | 国产黄色视 | 国产中文字幕三区 | 国产精品九九视频 | 午夜精品av | 久久久久免费精品视频 | 国产精品日韩欧美一区二区 | 亚洲美女精品区人人人人 | 一级免费看 | 久久黄色影院 | 亚洲国产中文字幕 | 久日精品 | 日韩av女优视频 | 久久久久亚洲精品国产 | 在线观看日本高清mv视频 | www.久草视频 | 最新影院 | 手机在线视频福利 | 91免费版成人 | 国产一级久久久 | 五月香婷 | 中文字幕不卡在线88 | 91九色国产 | 国产精品网红直播 | 夜夜躁日日躁 | 国产精品自在线 | 亚洲经典在线 | 在线看中文字幕 | 最新av在线播放 | 91精品色| 欧美激情视频一区二区三区免费 | 久久久国产精品一区二区三区 | 日韩首页 | 麻豆果冻剧传媒在线播放 | 国产成人一二三 | 亚洲日本激情 | 综合激情婷婷 | 免费亚洲精品视频 | 色综合天天在线 | 亚洲久在线 | 91精选在线观看 | 色婷婷中文| 91爱爱中文字幕 | 午夜视频在线观看网站 | 国产精品免费一区二区三区在线观看 | 日本视频高清 | 干干操操| 在线日本看片免费人成视久网 | 成人精品一区二区三区中文字幕 | 又爽又黄又刺激的视频 | 天天躁日日躁狠狠躁av中文 | 男女免费视频观看 | 国产一区二区网址 | 成人av影视 | 五月综合色婷婷 | 91免费视频黄 | 九色91福利 | 国产一区免费在线观看 | 欧美久久久久久久久久 | 久久人人精 | 在线亚洲欧美视频 | 国产免费观看久久黄 | 免费进去里的视频 | 久久视频在线观看免费 | 91男人影院 | 黄色1级大片 | 亚洲va欧美va | 青青河边草观看完整版高清 | 天天射日| 久久在现 | 欧美日韩国产精品一区 | 在线国产片 | 91九色在线视频 | 黄色一级性片 | 激情丁香在线 | 久草www| 99re6热在线精品视频 | 国产一区观看 | 国产成人一区二区三区免费看 | 波多野结衣资源 | 日韩理论在线视频 | 亚洲综合一区二区精品导航 | 午夜精品一区二区三区可下载 | 叶爱av在线| 天堂网在线视频 | 亚洲精品在线观 | 久久久亚洲电影 | 日本在线观看中文字幕无线观看 | 国产高清在线精品 | 久久综合精品一区 | 久久国产精品系列 | 国产黄色片免费看 | 四虎精品成人免费网站 | 久草爱视频 | 五月婷婷伊人网 | 在线观看视频99 | 成人精品福利 | 国产视频精品免费播放 | 色婷婷综合久久久久中文字幕1 | 色狠狠综合 | aaa毛片视频| 中文一二区 | 伊人激情网| 日本精品视频免费观看 | 亚洲欧美婷婷六月色综合 | 天天色天天操天天爽 | 国产精品麻豆视频 | 亚洲精品自在在线观看 | 伊人久久电影网 | 91超级碰 | 国产色综合天天综合网 | 亚洲网站在线看 | 超碰97久久 | 欧美日韩视频在线播放 | 九九久久久 | 激情喷水 | 婷婷国产在线 | 在线黄网站 | 91黄色视屏| 亚洲视频高清 | 久久视频免费在线 | 国产精品专区在线观看 | 亚洲精品免费在线观看视频 | 一级精品视频在线观看宜春院 | 成年人免费在线观看网站 | 在线视频麻豆 | 亚洲激情视频在线观看 | 超碰人人舔 | 日韩一区二区在线免费观看 | 成人久久综合 | 国产中文在线视频 | 国产在线观看99 | 久久免费视频在线观看30 | 精品美女在线观看 | 亚洲精品在线观 | 免费看片黄色 | 亚洲一区精品二人人爽久久 | 日韩视频免费观看高清完整版在线 | 亚洲国产人午在线一二区 | 久久久亚洲网站 | 人人插人人草 | 人人射人人爱 | 又黄又爽又色无遮挡免费 | 久久99久久精品国产 | 亚洲精品美女久久 | 国产伦理一区 | 天天激情天天干 | 日韩av免费观看网站 | 国产精品综合久久久久 | 一二三区在线 | 色婷婷影视 | 国产精品麻豆99久久久久久 | 日韩成人看片 | 久久久久久久久免费 | 国产成人精品av久久 | 婷婷五月在线视频 | 色狠狠操 | 国产在线色站 | 免费观看国产精品视频 | 成人免费视频网站 | 日韩精品一区电影 | 国产一区二区三精品久久久无广告 | 亚洲一区二区三区精品在线观看 | 狠狠激情中文字幕 | 国产一区在线观看视频 | 国产成人精品一区一区一区 | 亚洲精品国产精品国自产 | 国产一级大片在线观看 | 91久久久久久久一区二区 | 九九久 | 丁香花在线视频观看免费 | 日韩电影在线看 | 97视频免费| 激情综合网五月激情 | 久久婷亚洲五月一区天天躁 | aaa日本高清在线播放免费观看 | 国产视频精品网 | 午夜黄色大片 | 欧美久久久久久 | 国产精品久久亚洲 | 五月天综合网站 | 天天干天天想 | 亚洲电影久久 | 99精品久久久久久久 | 国产在线专区 | 粉嫩av一区二区三区免费 | 激情网第四色 | 精品亚洲视频在线观看 | 欧美一区二区在线刺激视频 | 久久精品欧美一区 | 欧美在线观看视频一区二区 | 四虎影院在线观看av | 午夜视频免费在线观看 | 国产黄色精品在线观看 | 天天射天天爱天天干 | 97在线免费视频观看 | 视频一区二区精品 | 日韩欧美一区视频 | 免费观看成人 | 欧美一区二区三区在线播放 | 日韩欧美一区二区在线播放 | 久久国产精品一二三区 | 91麻豆精品国产91久久久更新时间 | 天堂中文在线播放 | 四虎在线免费视频 | 在线看岛国av | 日韩综合在线观看 | 又色又爽的网站 | 激情影音| 蜜臀av麻豆 | 国产精品入口麻豆 | 亚洲国产无 | 精品福利在线观看 | 亚洲精品视频一二三 | 亚洲精品国产精品国产 | 97精品国产 | 91高清免费观看 | 2019中文在线观看 | 色婷婷成人网 | 国产午夜免费视频 | 91免费日韩| 精品亚洲网 | 狠狠色狠狠综合久久 | 欧美精品亚洲精品 | 一本一本久久a久久精品综合 | 久久97视频 | 999国内精品永久免费视频 | 免费观看www小视频的软件 | 人人澡人人添人人爽一区二区 | 在线91网| 国产午夜麻豆影院在线观看 | 爱射综合 | 天天爱天天干天天爽 | 免费国产在线视频 | 97电影在线观看 | 久久精品一区二区三区视频 | 久久一区二区三区超碰国产精品 | 欧美成人999 | 日本特黄特色aaa大片免费 | 国产区在线看 | 午夜99| 久久久影视 | 国产99在线播放 | 日本最新中文字幕 | 国产国语在线 | 视频在线观看91 | 精品视频在线视频 | 美腿丝袜一区二区三区 | 亚洲精品国产第一综合99久久 | 一本一本久久a久久精品综合妖精 | 国产一区二区三区免费观看视频 | 欧美精品久久 | 午夜在线看片 | 久久久久久黄色 | 免费福利片2019潦草影视午夜 | 精品在线观看一区二区 | 成人在线免费看视频 | 欧美在线free| 国产精品入口66mio女同 | 日韩美女久久 | 欧美激情综合色综合啪啪五月 | 日韩有码中文字幕在线 | 丁香视频在线观看 | 日b视频在线观看网址 | 国产香蕉视频在线播放 | 91av电影在线观看 | 国产亚洲一区二区在线观看 | 18性欧美xxxⅹ性满足 | 亚洲精品18p| 国产小视频在线免费观看视频 | 西西44人体做爰大胆视频 | www.99av| 天天摸天天舔 | 四虎8848免费高清在线观看 | 三级a毛片| 欧亚日韩精品一区二区在线 | 久久五月天综合 | 制服丝袜一区二区 | 国产午夜精品理论片在线 | 久久婷婷一区二区三区 | 国产一区在线看 | 国产国语在线 | 午夜精品一区二区三区在线视频 | 国产午夜在线观看 | 天天做夜夜做 | 99爱在线 | 天天干天天干天天干 | 国产视频精选在线 | 久人人| 国产精品久久99精品毛片三a | 午夜精品福利一区二区三区蜜桃 | 精品久久影院 | 高清不卡一区二区在线 | 国产精品久久久一区二区三区网站 | 免费av小说 | 欧美日韩国产在线观看 | 日韩成人黄色av | 丁香5月婷婷久久 | 在线看成人片 | 国产精品福利视频 | 一区二区三区福利 | 亚洲精品国产第一综合99久久 | 欧美巨大 | 国产精品久久伊人 | av在线之家电影网站 | 美女视频国产 | 国产视频日韩视频欧美视频 | 亚洲成人资源在线观看 | 国产午夜影院 | 国产精品久久久久久超碰 | 黄色a一级视频 | 久久久久欠精品国产毛片国产毛生 | 久久综合偷偷噜噜噜色 | 久久久国产精品人人片99精片欧美一 | 黄色av电影网 | 精品一二三四五区 | 亚洲伊人网在线观看 | 91亚洲永久精品 | 国产又粗又硬又爽的视频 | 亚洲国产成人在线 | 在线观看中文字幕视频 | 国产99免费 | 久久毛片高清国产 | 国产精品乱码在线 | 成人黄大片视频在线观看 | 黄色av免费电影 | 欧美最爽乱淫视频播放 | 亚洲伦理精品 | 九九免费在线观看视频 | 精品99视频| 亚洲免费精品一区二区 | 嫩草av在线 | 91麻豆国产 | 亚洲成人网在线 | 亚洲精品视频在线播放 | 波多野结衣电影一区 | 国产极品尤物在线 | 97超碰超碰久久福利超碰 | 少妇视频一区 | 久草资源免费 | 天天激情综合 | 国产精品亚州 | 欧美日韩大片在线观看 | 91经典在线 | 久久久国产一区二区三区四区小说 | 精品国产一区二区三区噜噜噜 | 免费中文字幕视频 | 久久免费一级片 | 中文av字幕在线观看 | 99精品热| 在线国产一区二区 | 色欧美成人精品a∨在线观看 | 美女视频a美女大全免费下载蜜臀 | 天天激情在线 | 91手机在线看片 | 九九九九色 | 精品不卡av | 日本深夜福利视频 | 91亚洲精品久久久蜜桃借种 | 在线免费看黄网站 | 黄色大全视频 | 亚洲精品视频在线观看免费视频 | 人人射人人射 | 精品久久久久久久久久国产 | 日韩欧美电影 | 18久久久久久 | 天天操夜夜爱 | 在线观看中文字幕第一页 | 国产精品永久 | 四虎影视成人永久免费观看亚洲欧美 | 日韩在线观看视频在线 | 香蕉手机在线 | 99在线观看视频 | 91精品久久久久久久99蜜桃 | 五月婷社区 | 五月婷婷国产 | 欧美日韩中文视频 | 国产欧美精品一区aⅴ影院 99视频国产精品免费观看 | 久久久91精品国产 | 91精品视频一区二区三区 | 国产精品手机播放 | 久久久91精品国产 | 91精品国产91久久久久福利 | 欧美性护士 | 色综合天天色 | 国产精品观看视频 | 天堂av色婷婷一区二区三区 | 91视频在线观看大全 | 日批视频在线观看免费 | 韩国一区二区三区在线观看 | 18国产精品白浆在线观看免费 | 久草视频视频在线播放 | 国产中文字幕视频在线 | 欧洲激情在线 | 日本中文字幕网站 | 91精品国产高清自在线观看 | 国产在线最新 | 日本公妇在线观看高清 | 日韩国产高清在线 | 在线观看视频国产一区 | 深夜国产福利 | 99精品国产亚洲 | 日韩精品一区二区免费 | 天天插天天干天天操 | 国产精品完整版 | 国产黄色精品视频 | 亚洲五月婷 | 国产精品乱码一区二三区 | 91在线免费观看网站 | 婷婷www | 91综合久久一区二区 | 国精产品一二三线999 | 五月天六月婷婷 | 草久在线观看视频 | 国产黄网在线 | 久久久久久久久久久久久国产精品 | 91在线影院 | 亚洲丁香日韩 | 精品毛片一区二区免费看 | 国产在线第三页 | 国产精品精品国产婷婷这里av | 久久国产精品久久w女人spa | 国产成人高清av | 欧美日韩另类在线 | 天堂在线视频中文网 | 狠狠躁日日躁狂躁夜夜躁 | 黄色日本免费 | 成人免费在线播放视频 | 国产精品久久久久久久久久久久午夜 | 91九色成人蝌蚪首页 | h网站免费在线观看 | 麻豆视频免费入口 | 在线观看完整版免费 | 国产一区精品在线 | 中文字幕一区二区三区在线观看 | 久久精品综合 | av中文字幕av | 中文亚洲欧美日韩 | 精品免费视频. | 五月婷婷激情综合 | 麻豆传媒视频在线播放 | 成 人 黄 色 视频播放1 | 国产香蕉视频在线播放 | 久久久午夜精品理论片中文字幕 | 最新av网址在线观看 | 日韩电影一区二区三区 | 99久久婷婷国产精品综合 | 欧美日韩性视频在线 | 国产精美视频 | 波多野结衣电影一区 | 精品久久国产一区 | 在线视频观看91 | 亚洲va欧洲va国产va不卡 | 日本中文一级片 | 日韩在线视频二区 | 国产精品午夜av | av免费黄色 | 国产人成看黄久久久久久久久 | 91人人网 | 成人av网站在线播放 | 国产精品美 | av专区在线| 久久精品国产一区 | 亚洲精品日韩在线观看 | 国产99亚洲 | 一区免费在线 | 久久视频在线观看免费 | 婷婷丁香花 | 99热99| 免费黄色在线网址 | 色偷偷男人的天堂av | 97视频在线 | 久久久久久久18 | 国产麻豆果冻传媒在线观看 | 久久亚洲精品电影 | 毛片.com| 91秒拍国产福利一区 | 激情久久久久久久久久久久久久久久 | av电影 一区二区 | 国产中文字幕视频在线 | 日本中文字幕免费观看 | 狠狠操.com| 日本韩国在线不卡 | 香蕉视频18 | 久久99偷拍视频 | 区一区二区三在线观看 | 天堂v中文 | 中文字幕在 | 国产不卡在线看 | 精品九九九九 | 日日日天天天 | 性色av香蕉一区二区 | 91在线免费观看国产 | 欧美另类z0zx | www黄com | 丁香色综合 | 国产精品久久久久久麻豆一区 | 菠萝菠萝在线精品视频 | 伊人成人精品 | 国产精品日韩久久久久 | 国产成人在线免费观看 | 久国产在线播放 | 黄毛片在线观看 | 久久国产一二区 | 国产成人av网站 | 欧美成人黄色片 | 久久综合狠狠综合久久狠狠色综合 | 日韩黄视频 | 18女毛片| 日韩羞羞 | 激情小说网站亚洲综合网 | 久久99热这里只有精品 | 国内久久看 | 在线三级播放 | 午夜av片| 97视频资源 | 在线观看的a站 | 成人影视片 | 色www免费视频 | 国产又黄又猛又粗 | 久久99热这里只有精品 | 国产视频999| 国产一区二区网址 | 中文字幕乱码视频 | 日韩电影在线观看一区 | 九九免费在线看完整版 | 日韩免费在线网站 | 女人18精品一区二区三区 | 人人舔人人舔 | 97韩国电影| 欧美日韩三级 | 国产精久久久久久妇女av | 国产精品中文字幕在线 | 久久伊人色综合 | 国产一区二区三区视频在线 | 香蕉视频啪啪 | 亚洲成人高清在线 | 日韩视频免费在线 | 欧美午夜寂寞影院 | 中文字幕在线观看网站 | 日韩系列 | 免费网站看av片 | 中文字幕乱在线伦视频中文字幕乱码在线 | 91九色精品 | 国产精品 日韩精品 | 人人插人人费 | 日本久久不卡视频 | 伊人国产在线播放 | 激情在线网址 | 国产精品毛片久久蜜 | 久久精品久久久久 | 久久免费视频国产 | 日韩免费在线观看网站 | 中日韩欧美精彩视频 | 日韩久久影院 | 日韩视频免费在线观看 | 日韩欧美高清在线观看 | 91精品国产乱码久久 | 日韩精品一区在线观看 | 日韩一级精品 | 五月天久久婷婷 | 国产a级精品 | 欧美在线91 | 日韩欧美v | 国产精品成人av电影 | 日本中文字幕网 | 十八岁免进欧美 | 五月天婷婷免费视频 | 麻花豆传媒一二三产区 | 在线视频麻豆 | 国产黄色在线 | 探花视频免费观看 | 99色国产| 精品国产一区二区三区久久久蜜臀 | 中文字幕视频播放 | 中文字幕免费 | 天天曰 | 91黄色视屏| 日韩理论在线播放 | 亚洲精品黄 | 91精品毛片 | 国偷自产中文字幕亚洲手机在线 | 久久久久久高潮国产精品视 | 丁香六月天 | 人人射 | 91九色国产在线 | 一区二区三区免费在线观看视频 | 国产精品高潮呻吟久久久久 | 久久免费国产精品1 | 国产高清视频网 | www.玖玖玖| 狠狠做深爱婷婷综合一区 | 久久夜av | 97av超碰| 久操综合| 久久一级电影 | 黄色三级免费观看 | 国产伦理久久精品久久久久_ | 国产精品毛片一区二区三区 | 日本中文字幕一二区观 | 91色一区二区三区 | 久久99亚洲精品久久久久 | 日韩欧美精品免费 | 成人资源在线观看 | 免费在线观看av网站 | 亚洲人毛片 | 欧美成年黄网站色视频 | 国产字幕在线播放 | 国产成人精品女人久久久 | 激情偷乱人伦小说视频在线观看 | 五月视频 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | 久草久草久草久草 | 久久草在线视频国产 | 国内精品国产三级国产aⅴ久 | 视频国产 | 黄色视屏av | 日本久久中文字幕 | 久久久国产在线视频 | 天堂网一区二区三区 | 国产在线观看午夜 | 国产精品一区二区麻豆 | 欧洲精品一区二区 | 亚洲精品黄| 91人人干| 99久在线精品99re8热视频 | 狠狠的操狠狠的干 | 国产精品免费久久 | 日本精品在线视频 | 国产精品96久久久久久吹潮 | 狠狠干2018 | 国产精品免费久久久 | 二区三区在线观看 | 日韩18p| 国产成人久 | 色婷婷av一区二 | 亚洲成人欧美 | 激情视频免费观看 | 97视频在线播放 | 国产精品久久久久久五月尺 | 久久人视频 | av电影 一区二区 | 国产欧美在线一区二区三区 | 成人黄色av网站 | 精品亚洲男同gayvideo网站 | 色婷婷天天干 | 国产视频午夜 | 久久在线精品视频 | 日本三级在线观看中文字 | 中文字幕国产精品一区二区 | 亚洲日本色 | 97在线超碰| 亚洲免费一级电影 | 久久色亚洲 | 久久99欧美 | 伊在线视频 | 亚洲综合色视频在线观看 | 欧美一级高清片 | 久久久久免费精品 | 欧美成人xxx | 91精品国产成人 | 成人三级网站在线观看 | 亚洲在线视频观看 | 欧美亚洲国产一卡 | 国产欧美最新羞羞视频在线观看 | 黄色毛片电影 | 日韩欧美精品在线视频 | 在线免费av播放 | 911国产精品 | 亚洲伦理一区 | 91热爆在线观看 | 久久久久久久久久久免费视频 | 亚洲综合网站在线观看 | 久草精品视频 | www.色的| 婷婷5月激情5月 | 亚洲无吗av | 91九色在线观看视频 | 国产免费大片 | www黄色av| 日韩小视频网站 | 天天干天天操av | 国产免费三级在线观看 | 麻豆国产精品永久免费视频 | 一区二区不卡视频在线观看 | 欧美日韩伦理一区 | 天天曰天天射 | 国产日韩一区在线 | www.狠狠操.com | 最新极品jizzhd欧美 | 亚洲高清视频在线播放 | www黄色av| 亚洲欧美日韩一二三区 | 成人精品99 | 日韩3区| 夜夜躁狠狠躁日日躁 | 香蕉视频久久久 | 久久久久久国产一区二区三区 | 在线午夜av| 91污在线观看 | 中文字幕在线观看一区二区三区 | 成人在线免费小视频 | 亚洲第五色综合网 | caobi视频 | 午夜视频在线观看网站 | 激情婷婷欧美 | 亚洲干视频在线观看 | 激情视频免费在线观看 | 免费97视频 | 日韩成人精品一区二区三区 | 97国产大学生情侣白嫩酒店 | 日韩在线播放欧美字幕 | 91久久在线观看 | 亚洲激情在线观看 | 欧美成人区 | 99久热在线精品 | 综合av在线 | 欧美极品裸体 | 欧美在线观看视频 | 欧美一级爽 | 国产精品1区2区3区在线观看 | 天天色天天搞 | 在线观看av免费 | 天堂av色婷婷一区二区三区 | 日韩欧美一区二区三区免费观看 | 91成人网在线播放 | 天天干天天拍 | 色综合久久久网 | 天天操天天操天天 | 92中文资源在线 | 麻豆视频免费播放 | 久久久夜色 | 国产精品美女免费 | 午夜骚影| 手机av片| 日本久久中文字幕 | 日韩在线视频二区 | 日韩精品一区二区三区免费观看 | 成人免费观看电影 | 精品亚洲国产视频 | 中文字幕黄色网 | 伊人夜夜 | 婷婷丁香在线视频 | 欧美一区二区三区免费观看 | 欧美日韩中文视频 | 国产精品一区二区久久久久 | 在线观看 国产 | 97在线看 | 狠狠成人 |