成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何從TensorFlow轉入PyTorch

開發 開發工具
在本文中,我會簡要解釋 PyTorch 的核心概念,為你轉入這個框架提供一些必要的動力。其中包含了一些基礎概念,以及先進的功能如學習速率調整、自定義層等等。

當我第一次嘗試學習 PyTorch 時,沒幾天就放棄了。和 TensorFlow 相比,我很難弄清 PyTorch 的核心要領。但是隨后不久,PyTorch 發布了一個新版本,我決定重新來過。在第二次的學習中,我開始了解這個框架的易用性。在本文中,我會簡要解釋 PyTorch 的核心概念,為你轉入這個框架提供一些必要的動力。其中包含了一些基礎概念,以及先進的功能如學習速率調整、自定義層等等。

PyTorch 的易用性如何?Andrej Karpathy 是這樣評價的

PyTorch 的易用性如何?Andrej Karpathy 是這樣評價的

資源

  • 首先要知道的是:PyTorch 的主目錄和教程是分開的。而且因為開發和版本更新的速度過快,有時候兩者之間并不匹配。所以你需要不時查看源代碼:http://pytorch.org/tutorials/。
  • 當然,目前網絡上已有了一些 PyTorch 論壇,你可以在其中詢問相關的問題,并很快得到回復:https://discuss.pytorch.org/。

把 PyTorch 當做 NumPy 用

讓我們先看看 PyTorch 本身,其主要構件是張量——這和 NumPy 看起來差不多。這種性質使得 PyTorch 可支持大量相同的 API,所以有時候你可以把它用作是 NumPy 的替代品。PyTorch 的開發者們這么做的原因是希望這種框架可以完全獲得 GPU 加速帶來的便利,以便你可以快速進行數據預處理,或其他任何機器學習任務。將張量從 NumPy 轉換至 PyTorch 非常容易,反之亦然。讓我們看看如下代碼:

  1. importtorch 
  2. importnumpy asnp 
  3. numpy_tensor =np.random.randn(10,20) 
  4. # convert numpy array to pytorch array 
  5. pytorch_tensor =torch.Tensor(numpy_tensor) 
  6. # or another way 
  7. pytorch_tensor =torch.from_numpy(numpy_tensor) 
  8. # convert torch tensor to numpy representation 
  9. pytorch_tensor.numpy() 
  10. # if we want to use tensor on GPU provide another type 
  11. dtype =torch.cuda.FloatTensor 
  12. gpu_tensor =torch.randn(10,20).type(dtype) 
  13. # or just call `cuda()` method 
  14. gpu_tensor =pytorch_tensor.cuda() 
  15. # call back to the CPU 
  16. cpu_tensor =gpu_tensor.cpu() 
  17. # define pytorch tensors 
  18. x =torch.randn(10,20) 
  19. y =torch.ones(20,5) 
  20. # `@` mean matrix multiplication from python3.5, PEP-0465 
  21. res =x @y 
  22. # get the shape 
  23. res.shape # torch.Size([10, 5]) 

從張量到變量

張量是 PyTorch 的一個完美組件,但是要想構建神經網絡這還遠遠不夠。反向傳播怎么辦?當然,我們可以手動實現它,但是真的需要這樣做嗎?幸好還有自動微分。為了支持這個功能,PyTorch 提供了變量,它是張量之上的封裝。如此,我們可以構建自己的計算圖,并自動計算梯度。每個變量實例都有兩個屬性:包含初始張量本身的.data,以及包含相應張量梯度的.grad

  1. importtorch 
  2. fromtorch.autograd importVariable 
  3. # define an inputs 
  4. x_tensor =torch.randn(10,20) 
  5. y_tensor =torch.randn(10,5) 
  6. x =Variable(x_tensor,requires_grad=False
  7. y =Variable(y_tensor,requires_grad=False
  8. # define some weights 
  9. w =Variable(torch.randn(20,5),requires_grad=True
  10. # get variable tensor 
  11. print(type(w.data))# torch.FloatTensor 
  12. # get variable gradient 
  13. print(w.grad)# None 
  14. loss =torch.mean((y -x @w)**2) 
  15. # calculate the gradients 
  16. loss.backward() 
  17. print(w.grad)# some gradients 
  18. # manually apply gradients 
  19. w.data -=0.01*w.grad.data 
  20. # manually zero gradients after update 
  21. w.grad.data.zero_() 

你也許注意到我們手動計算了自己的梯度,這樣看起來很麻煩,我們能使用優化器嗎?當然。

  1. importtorch 
  2. fromtorch.autograd importVariable 
  3. importtorch.nn.functional asF 
  4. x =Variable(torch.randn(10,20),requires_grad=False
  5. y =Variable(torch.randn(10,3),requires_grad=False
  6. # define some weights 
  7. w1 =Variable(torch.randn(20,5),requires_grad=True
  8. w2 =Variable(torch.randn(5,3),requires_grad=True
  9. learning_rate =0.1 
  10. loss_fn =torch.nn.MSELoss() 
  11. optimizer =torch.optim.SGD([w1,w2],lr=learning_rate
  12. forstep inrange(5): 
  13. pred =F.sigmoid(x @w1) 
  14. pred =F.sigmoid(pred @w2) 
  15. loss =loss_fn(pred,y) 
  16. # manually zero all previous gradients 
  17. optimizer.zero_grad() 
  18. # calculate new gradients 
  19. loss.backward() 
  20. # apply new gradients 
  21. optimizer.step() 

并不是所有的變量都可以自動更新。但是你應該可以從最后一段代碼中看到重點:我們仍然需要在計算新梯度之前將它手動歸零。這是 PyTorch 的核心理念之一。有時我們會不太明白為什么要這么做,但另一方面,這樣可以讓我們充分控制自己的梯度。

靜態圖 vs 動態圖

PyTorch 和 TensorFlow 的另一個主要區別在于其不同的計算圖表現形式。TensorFlow 使用靜態圖,這意味著我們是先定義,然后不斷使用它。在 PyTorch 中,每次正向傳播都會定義一個新計算圖。在開始階段,兩者之間或許差別不是很大,但動態圖會在你希望調試代碼,或定義一些條件語句時顯現出自己的優勢。就像你可以使用自己最喜歡的 debugger 一樣!

你可以比較一下 while 循環語句的下兩種定義——第一個是 TensorFlow 中,第二個是 PyTorch 中:

  1. importtensorflow astf 
  2. first_counter =tf.constant(0) 
  3. second_counter =tf.constant(10) 
  4. some_value =tf.Variable(15) 
  5. # condition should handle all args: 
  6. defcond(first_counter,second_counter,*args): 
  7. returnfirst_counter <second_counter 
  8. defbody(first_counter,second_counter,some_value): 
  9. first_counter =tf.add(first_counter,2) 
  10. second_counter =tf.add(second_counter,1) 
  11. returnfirst_counter,second_counter,some_value 
  12. c1,c2,val =tf.while_loop( 
  13. cond,body,[first_counter,second_counter,some_value]) 
  14. withtf.Session()assess: 
  15. sess.run(tf.global_variables_initializer()) 
  16. counter_1_res,counter_2_res =sess.run([c1,c2]) 
  17. importtorch 
  18. first_counter =torch.Tensor([0]) 
  19. second_counter =torch.Tensor([10]) 
  20. some_value =torch.Tensor(15) 
  21. while(first_counter <second_counter)[0]: 
  22. first_counter +=2 
  23. second_counter +=1 

看起來第二種方法比第一個簡單多了,你覺得呢?

模型定義

現在我們看到,想在 PyTorch 中創建 if/else/while 復雜語句非常容易。不過讓我們先回到常見模型中,PyTorch 提供了非常類似于 Keras 的、即開即用的層構造函數:

神經網絡包(nn)定義了一系列的模塊,它可以粗略地等價于神經網絡的層。模塊接收輸入變量并計算輸出變量,但也可以保存內部狀態,例如包含可學習參數的變量。nn 包還定義了一組在訓練神經網絡時常用的損失函數。

  1. fromcollections importOrderedDict 
  2. importtorch.nn asnn 
  3. # Example of using Sequential 
  4. model =nn.Sequential( 
  5. nn.Conv2d(1,20,5), 
  6. nn.ReLU(), 
  7. nn.Conv2d(20,64,5), 
  8. nn.ReLU() 
  9. # Example of using Sequential with OrderedDict 
  10. model =nn.Sequential(OrderedDict([ 
  11. ('conv1',nn.Conv2d(1,20,5)), 
  12. ('relu1',nn.ReLU()), 
  13. ('conv2',nn.Conv2d(20,64,5)), 
  14. ('relu2',nn.ReLU()) 
  15. ])) 
  16. output =model(some_input) 

如果你想要構建復雜的模型,我們可以將 nn.Module 類子類化。當然,這兩種方式也可以互相結合。

  1. fromtorch importnn 
  2. classModel(nn.Module): 
  3. def__init__(self): 
  4. super().__init__() 
  5. self.feature_extractor =nn.Sequential( 
  6. nn.Conv2d(3,12,kernel_size=3,padding=1,stride=1), 
  7. nn.Conv2d(12,24,kernel_size=3,padding=1,stride=1), 
  8. self.second_extractor =nn.Conv2d( 
  9. 24,36,kernel_size=3,padding=1,stride=1
  10. defforward(self,x): 
  11. x =self.feature_extractor(x) 
  12. x =self.second_extractor(x) 
  13. # note that we may call same layer twice or mode 
  14. x =self.second_extractor(x) 
  15. returnx 

在__init__方法中,我們需要定義之后需要使用的所有層。在正向方法中,我們需要提出如何使用已經定義的層的步驟。而在反向傳播上,和往常一樣,計算是自動進行的。

自定義層

如果我們想要定義一些非標準反向傳播模型要怎么辦?這里有一個例子——XNOR 網絡:

在這里我們不會深入細節,如果你對它感興趣,可以參考一下原始論文:

https://arxiv.org/abs/1603.05279

與我們問題相關的是反向傳播需要權重必須介于-1 到 1 之間。在 PyTorch 中,這可以很容易實現:

  1. importtorch 
  2. classMyFunction(torch.autograd.Function): 
  3. @staticmethod 
  4. defforward(ctx,input): 
  5. ctx.save_for_backward(input) 
  6. output =torch.sign(input) 
  7. returnoutput 
  8. @staticmethod 
  9. defbackward(ctx,grad_output): 
  10. # saved tensors - tuple of tensors, so we need get first 
  11. input,=ctx.saved_variables 
  12. grad_output[input.ge(1)]=0 
  13. grad_output[input.le(-1)]=0 
  14. returngrad_output 
  15. # usage 
  16. x =torch.randn(10,20) 
  17. y =MyFunction.apply(x) 
  18. # or 
  19. my_func =MyFunction.apply 
  20. y =my_func(x) 
  21. # and if we want to use inside nn.Module 
  22. classMyFunctionModule(torch.nn.Module): 
  23. defforward(self,x): 
  24. returnMyFunction.apply(x) 

正如你所見,我們應該只定義兩種方法:一個為正向傳播,一個為反向傳播。如果我們需要從正向通道訪問一些變量,我們可以將它們存儲在 ctx 變量中。注意:在此前的 API 正向/反向傳播不是靜態的,我們存儲變量需要以 self.save_for_backward(input) 的形式,并以 input, _ = self.saved_tensors 的方式接入。

在 CUDA 上訓練模型

我們曾經討論過傳遞一個張量到 CUDA 上。但如果希望傳遞整個模型,我們可以通過調用.cuda() 來完成,并將每個輸入變量傳遞到.cuda() 中。在所有計算后,我們需要用返回.cpu() 的方法來獲得結果。

同時,PyTorch 也支持在源代碼中直接分配設備:

  1. importtorch 
  2. ### tensor example 
  3. x_cpu =torch.randn(10,20) 
  4. w_cpu =torch.randn(20,10) 
  5. # direct transfer to the GPU 
  6. x_gpu =x_cpu.cuda() 
  7. w_gpu =w_cpu.cuda() 
  8. result_gpu =x_gpu @w_gpu 
  9. # get back from GPU to CPU 
  10. result_cpu =result_gpu.cpu() 
  11. ### model example 
  12. modelmodel =model.cuda() 
  13. # train step 
  14. inputs =Variable(inputs.cuda()) 
  15. outputs =model(inputs) 
  16. # get back from GPU to CPU 
  17. outputsoutputs =outputs.cpu() 

因為有些時候我們想在 CPU 和 GPU 中運行相同的模型,而無需改動代碼,我們會需要一種封裝:

  1. classTrainer: 
  2. def__init__(self,model,use_cuda=False,gpu_idx=0): 
  3. self.use_cuda =use_cuda 
  4. self.gpu_idx =gpu_idx 
  5. selfself.model =self.to_gpu(model) 
  6. defto_gpu(self,tensor): 
  7. ifself.use_cuda: 
  8. returntensor.cuda(self.gpu_idx) 
  9. else: 
  10. returntensor 
  11. deffrom_gpu(self,tensor): 
  12. ifself.use_cuda: 
  13. returntensor.cpu() 
  14. else: 
  15. returntensor 
  16. deftrain(self,inputs): 
  17. inputs =self.to_gpu(inputs) 
  18. outputs =self.model(inputs) 
  19. outputs =self.from_gpu(outputs) 

權重初始化

在 TesnorFlow 中權重初始化主要是在張量聲明中進行的。PyTorch 則提供了另一種方法:首先聲明張量,隨后在下一步里改變張量的權重。權重可以用調用 torch.nn.init 包中的多種方法初始化為直接訪問張量的屬性。這個決定或許并不直接了當,但當你希望初始化具有某些相同初始化類型的層時,它就會變得有用。

  1. importtorch 
  2. fromtorch.autograd importVariable 
  3. # new way with `init` module 
  4. w =torch.Tensor(3,5) 
  5. torch.nn.init.normal(w) 
  6. # work for Variables also 
  7. w2 =Variable(w) 
  8. torch.nn.init.normal(w2) 
  9. # old styled direct access to tensors data attribute 
  10. w2.data.normal_() 
  11. # example for some module 
  12. defweights_init(m): 
  13. classname =m.__class__.__name__ 
  14. ifclassname.find('Conv')!=-1: 
  15. m.weight.data.normal_(0.0,0.02) 
  16. elifclassname.find('BatchNorm')!=-1: 
  17. m.weight.data.normal_(1.0,0.02) 
  18. m.bias.data.fill_(0) 
  19. # for loop approach with direct access 
  20. classMyModel(nn.Module): 
  21. def__init__(self): 
  22. form inself.modules(): 
  23. ifisinstance(m,nn.Conv2d): 
  24. n =m.kernel_size[0]*m.kernel_size[1]*m.out_channels 
  25. m.weight.data.normal_(0,math.sqrt(2./n)) 
  26. elifisinstance(m,nn.BatchNorm2d): 
  27. m.weight.data.fill_(1) 
  28. m.bias.data.zero_() 
  29. elifisinstance(m,nn.Linear): 
  30. m.bias.data.zero_() 

反向排除子圖

有時,當你希望保留模型中的某些層或者為生產環境做準備的時候,禁用某些層的自動梯度機制非常有用。在這種思路下,PyTorch 設計了兩個 flag:requires_grad 和 volatile。第一個可以禁用當前層的梯度,但子節點仍然可以計算。第二個可以禁用自動梯度,同時效果沿用至所有子節點。

  1. importtorch 
  2. fromtorch.autograd importVariable 
  3. # requires grad 
  4. # If there’s a single input to an operation that requires gradient, 
  5. # its output will also require gradient. 
  6. x =Variable(torch.randn(5,5)) 
  7. y =Variable(torch.randn(5,5)) 
  8. z =Variable(torch.randn(5,5),requires_grad=True
  9. a =x +y 
  10. a.requires_grad # False 
  11. b =a +z 
  12. b.requires_grad # True 
  13. # Volatile differs from requires_grad in how the flag propagates. 
  14. # If there’s even a single volatile input to an operation, 
  15. # its output is also going to be volatile. 
  16. x =Variable(torch.randn(5,5),requires_grad=True
  17. y =Variable(torch.randn(5,5),volatile=True
  18. a =x +y 
  19. a.requires_grad # False 

訓練過程

當然,PyTorch 還有一些其他賣點。例如你可以設定學習速率,讓它以特定規則進行變化。或者你可以通過簡單的訓練標記允許/禁止批規范層和 dropout。如果你想要做的話,讓 CPU 和 GPU 的隨機算子不同也是可以的。

  1. # scheduler example 
  2. fromtorch.optim importlr_scheduler 
  3. optimizer =torch.optim.SGD(model.parameters(),lr=0.01) 
  4. scheduler =lr_scheduler.StepLR(optimizer,step_size=30,gamma=0.1) 
  5. forepoch inrange(100): 
  6. scheduler.step() 
  7. train() 
  8. validate() 
  9. # Train flag can be updated with boolean 
  10. # to disable dropout and batch norm learning 
  11. model.train(True) 
  12. # execute train step 
  13. model.train(False) 
  14. # run inference step 
  15. # CPU seed 
  16. torch.manual_seed(42) 
  17. # GPU seed 
  18. torch.cuda.manual_seed_all(42) 

同時,你也可以添加模型信息,或存儲/加載一小段代碼。如果你的模型是由 OrderedDict 或基于類的模型字符串,它的表示會包含層名。

  1. fromcollections importOrderedDict 
  2. importtorch.nn asnn 
  3. model =nn.Sequential(OrderedDict([ 
  4. ('conv1',nn.Conv2d(1,20,5)), 
  5. ('relu1',nn.ReLU()), 
  6. ('conv2',nn.Conv2d(20,64,5)), 
  7. ('relu2',nn.ReLU()) 
  8. ])) 
  9. print(model) 
  10. # Sequential ( 
  11. # (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1)) 
  12. # (relu1): ReLU () 
  13. # (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1)) 
  14. # (relu2): ReLU () 
  15. # ) 
  16. # save/load only the model parameters(prefered solution) 
  17. torch.save(model.state_dict(),save_path) 
  18. model.load_state_dict(torch.load(save_path)) 
  19. # save whole model 
  20. torch.save(model,save_path) 
  21. model =torch.load(save_path) 

根據 PyTorch 文檔,用 state_dict() 的方式存儲文檔更好。

記錄

訓練過程的記錄是一個非常重要的部分。不幸的是,PyTorch 目前還沒有像 Tensorboard 這樣的東西。所以你只能使用普通文本記錄 Python 了,你也可以試試一些第三方庫:

  • logger:https://github.com/oval-group/logger
  • Crayon:https://github.com/torrvision/crayon
  • tensorboard_logger:https://github.com/TeamHG-Memex/tensorboard_logger
  • tensorboard-pytorch:https://github.com/lanpa/tensorboard-pytorch
  • Visdom:https://github.com/facebookresearch/visdom

掌控數據

你可能會記得 TensorFlow 中的數據加載器,甚至想要實現它的一些功能。對于我來說,我花了四個小時來掌握其中所有管道的執行原理。

首先,我想在這里添加一些代碼,但我認為上圖足以解釋它的基礎理念了。

PyTorch 開發者不希望重新發明輪子,他們只是想要借鑒多重處理。為了構建自己的數據加載器,你可以從 torch.utils.data.Dataset 繼承類,并更改一些方法:

  1. importtorch 
  2. importtorchvision astv 
  3. classImagesDataset(torch.utils.data.Dataset): 
  4. def__init__(self,df,transform=None
  5. loader=tv.datasets.folder.default_loader): 
  6. self.df =df 
  7. self.transform =transform 
  8. self.loader =loader 
  9. def__getitem__(self,index): 
  10. row =self.df.iloc[index] 
  11. target =row['class_'] 
  12. path =row['path'] 
  13. img =self.loader(path) 
  14. ifself.transform isnotNone: 
  15. img =self.transform(img) 
  16. returnimg,target 
  17. def__len__(self): 
  18. n,_ =self.df.shape 
  19. returnn 
  20. # what transformations should be done with our images 
  21. data_transforms =tv.transforms.Compose([ 
  22. tv.transforms.RandomCrop((64,64),padding=4), 
  23. tv.transforms.RandomHorizontalFlip(), 
  24. tv.transforms.ToTensor(), 
  25. ]) 
  26. train_df =pd.read_csv('path/to/some.csv') 
  27. # initialize our dataset at first 
  28. train_dataset =ImagesDataset
  29. df=train_df
  30. transform=data_transforms 
  31. # initialize data loader with required number of workers and other params 
  32. train_loader =torch.utils.data.DataLoader(train_dataset, 
  33. batch_size=10
  34. shuffle=True
  35. num_workers=16
  36. # fetch the batch(call to `__getitem__` method) 
  37. forimg,target intrain_loader: 
  38. pass 

有兩件事你需要事先知道:

1. PyTorch 的圖維度和 TensorFlow 的不同。前者的是 [Batch_size × channels × height × width] 的形式。但如果你沒有通過預處理步驟 torchvision.transforms.ToTensor() 進行交互,則可以進行轉換。在 transforms 包中還有很多有用小工具。

2. 你很可能會使用固定內存的 GPU,對此,你只需要對 cuda() 調用額外的標志 async = True,并從標記為 pin_memory = True 的 DataLoader 中獲取固定批次。

最終架構

現在我們了解了模型、優化器和很多其他細節。是時候來個總結了:

這里有一段用于解讀的偽代碼:

  1. classImagesDataset(torch.utils.data.Dataset): 
  2. pass 
  3. classNet(nn.Module): 
  4. pass 
  5. model =Net() 
  6. optimizer =torch.optim.SGD(model.parameters(),lr=0.01) 
  7. scheduler =lr_scheduler.StepLR(optimizer,step_size=30,gamma=0.1) 
  8. criterion =torch.nn.MSELoss() 
  9. dataset =ImagesDataset(path_to_images) 
  10. data_loader =torch.utils.data.DataLoader(dataset,batch_size=10
  11. train =True 
  12. forepoch inrange(epochs): 
  13. iftrain: 
  14. lr_scheduler.step() 
  15. forinputs,labels indata_loader: 
  16. inputs =Variable(to_gpu(inputs)) 
  17. labels =Variable(to_gpu(labels)) 
  18. outputs =model(inputs) 
  19. loss =criterion(outputs,labels) 
  20. iftrain: 
  21. optimizer.zero_grad() 
  22. loss.backward() 
  23. optimizer.step() 
  24. ifnottrain: 
  25. save_best_model(epoch_validation_accuracy) 

結論

希望本文可以讓你了解 PyTorch 的如下特點:

  • 它可以用來代替 Numpy
  • 它的原型設計非常快
  • 調試和使用條件流非常簡單
  • 有很多方便且開箱即用的工具

PyTorch 是一個正在快速發展的框架,背靠一個富有活力的社區。現在是嘗試 PyTorch 的好時機。

【本文是51CTO專欄機構“機器之心”的原創譯文,微信公眾號“機器之心( id: almosthuman2014)”】

戳這里,看該作者更多好文

責任編輯:趙寧寧 來源: 51CTO專欄
相關推薦

2010-05-11 18:40:46

Unix系統

2020-10-27 09:37:43

PyTorchTensorFlow機器學習

2017-03-02 14:52:46

2022-09-15 14:40:38

PyTorchFAIRTorch

2021-07-01 16:45:17

PyTorchTensorflow 機器學習

2022-06-15 14:48:39

谷歌TensorFlowMeta

2021-01-15 13:28:53

RNNPyTorch神經網絡

2021-12-19 22:51:24

PyTorchTensorFlow框架

2020-02-03 09:20:43

深度學習編程人工智能

2021-03-18 08:59:14

框架pytorchtensorflow

2009-11-02 12:46:15

Winform

2020-10-15 11:22:34

PyTorchTensorFlow機器學習

2017-06-06 10:14:55

KerasTensorFlow深度學習

2018-09-10 14:38:16

編程語言TensorFlow.機器學習

2020-05-15 08:18:51

TFPyTorch深度學習

2019-09-01 19:19:04

TensorFlowPyTorch深度學習

2018-04-11 17:50:14

深度學習PyTorchTensorFlow

2020-06-28 10:16:53

PyTorchTensorFlow機器學習

2017-09-05 10:20:30

PyTorchTensorPython

2021-06-16 15:37:50

深度學習編程人工智能
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91精品久久久 | 久久久免费电影 | 中文在线观看视频 | 精产嫩模国品一二三区 | 在线成人免费观看 | 成人一区二区在线 | 日韩国产一区二区三区 | 六月色婷| 成人精品一区亚洲午夜久久久 | 免费看片在线播放 | 伊人狼人影院 | 一区二区三区四区av | 九九热最新地址 | 99久久免费观看 | 国产精品毛片无码 | 免费视频一区二区 | www国产亚洲精品久久网站 | 国产精品1区2区3区 国产在线观看一区 | 国产激情一区二区三区 | 在线免费激情视频 | 久久麻豆精品 | 国产精品久久久久久一区二区三区 | 四虎免费视频 | 亚洲成人av | 亚洲天堂中文字幕 | 一区二区三区国产好 | 伊人激情综合网 | 日韩精品久久久 | 久久亚洲一区二区三区四区 | 国产性生活一级片 | 中文字幕在线观看一区 | 日韩欧美亚洲一区 | 久久免费精品 | 久久精品视频网站 | 国产精品永久免费 | 玖玖精品 | 91美女在线观看 | 国产精品视频一二三区 | 精品视频国产 | 日韩中文在线观看 | 亚洲精品国产第一综合99久久 |