ResNet之mxnet、keras、pytorch實現

目錄

1、ResNet50的mxnet實現

2、ResNet的keras實現

3、ResNet的pytorch實現

 

1、ResNet50的mxnet實現

具體網絡結構可參見https://blog.csdn.net/qq_21046135/article/details/81674605

和 https://blog.csdn.net/seven_year_promise/article/details/69360488

from mxnet.gluon import nn
from mxnet import nd, init, gluon, autograd
import mxnet as mx
import gluonbook as gb
from mxnet.gluon.data.vision import transforms


lr = 0.1
num_epochs = 100
batch_size = 128
ctx = mx.gpu()

tansformer = transforms.Compose([
    transforms.ToTensor()
])

# 加載數據
train_data = gluon.data.vision.ImageFolderDataset("/home/user/cf/st/train")
test_data = gluon.data.vision.ImageFolderDataset("/home/user/cf/st/test")

train_iter = gluon.data.DataLoader(train_data.transform_first(tansformer), shuffle=True, batch_size=batch_size)
test_iter = gluon.data.DataLoader(test_data.transform_first(tansformer),shuffle=True, batch_size=batch_size)

class Residual(nn.Block):
    def __init__(self, num_channels, use_1x1conv=False, strides=1, **kwargs):
        super(Residual, self).__init__(**kwargs)
        self.conv1 = nn.Conv2D(num_channels, kernel_size=1, strides=strides)
        self.conv2 = nn.Conv2D(num_channels, kernel_size=3, strides=1, padding=1)
        self.conv3 = nn.Conv2D(num_channels*4, kernel_size=1, strides=1)
        if use_1x1conv:
            self.conv4 = nn.Conv2D(num_channels*4, kernel_size=1, strides=strides)
            self.bn4 = nn.BatchNorm()
        else:
            self.conv4 = None
            self.bn4 = None
        self.bn1 = nn.BatchNorm()
        self.bn2 = nn.BatchNorm()
        self.bn3 = nn.BatchNorm()

    def forward(self, x):
        y = nd.relu(self.bn1(self.conv1(x)))
        y = nd.relu(self.bn2(self.conv2(y)))
        y = self.bn3(self.conv3(y))
        if self.conv4:
            x = self.bn4(self.conv4(x))
        return nd.relu(x+y)

def resnet_block(num_channels, num_residuals, stride=1):
    blk = nn.Sequential()
    for i in range(num_residuals):
        if i==0 :
            blk.add(Residual(num_channels, use_1x1conv=True, strides=stride))   
        else:
            blk.add(Residual(num_channels))
    return blk

net = nn.Sequential()
net.add(
    nn.Conv2D(64, kernel_size=7, strides=2, padding=3),
    nn.BatchNorm(),
    nn.Activation('relu'),
    nn.MaxPool2D(pool_size=3, strides=2)
)
net.add(
    resnet_block(64, 3, stride=1),
    resnet_block(128, 4, stride=2),
    resnet_block(256, 6, stride=2),
    resnet_block(512, 3, stride=2)
)
# 平均池化
net.add(nn.GlobalAvgPool2D(),
        nn.Dense(2))

net.initialize(init=init.Xavier(), force_reinit=True, ctx=ctx)
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate':lr})
loss = gluon.loss.SoftmaxCrossEntropyLoss()
gb.train_ch5(net, train_iter, test_iter, batch_size, trainer, ctx, num_epochs)

當self.bn4定義在self.bn3的後面時,會出現錯誤:mxnet.gluon.parameter.DeferredInitializationError: Parameter 'batchnorm8_gamma' has not been initialized yet because initialization was deferred. Actual initialization happens during the first forward pass. Please pass one batch of data through the network before accessing Parameters. You can also avoid deferred initialization by specifying in_units, num_features, etc., for network layers.

這是因爲定義在後面的bn4,當條件判斷爲false的時候,不會使用到self.bn4,會出現DeferredInitializationError的錯誤,所以需要同時定義兩種情況下的self.bn4

 

2、ResNet的keras實現

from keras.layers import Conv2D, BatchNormalization, MaxPooling2D, Activation, ZeroPadding2D, Input, Add,AveragePooling2D, Flatten, Dense
from keras.models import Model
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import LearningRateScheduler
from keras.callbacks import ModelCheckpoint
from keras.utils import multi_gpu_model
from keras.optimizers import Adam
import keras.backend as K

def Bottleneck(x, filters, stage, block, s, short_cut):
    conv_name_base = 'res'+str(stage)+block+'_branch'
    bn_name_base = 'bn'+str(stage)+block+'_branch'
    
    residual = x    #shortcut
    x = Conv2D(filters=filters, kernel_size=(1,1), strides=(1,1), padding='valid', name=conv_name_base+'2a')(x)
    x = BatchNormalization(axis=3, name=bn_name_base+'2a')(x)
    x = Activation('relu')(x)
    
    x = Conv2D(filters=filters, kernel_size=(3,3), strides=(s,s), padding='same', name=conv_name_base+'2b')(x)
    x = BatchNormalization(axis=3, name=bn_name_base+'2b')(x)
    x = Activation('relu')(x)
    
    x = Conv2D(filters=filters*4, kernel_size=(1,1), strides=(1,1), padding='valid', name=conv_name_base+'2c')(x)
    x = BatchNormalization(axis=3, name=bn_name_base+'2c')(x)
    
    if short_cut:
        residual = Conv2D(filters=filters*4, kernel_size=(1,1), strides=(s,s), name=conv_name_base+'1')(residual)
        residual = BatchNormalization(axis=3, name=bn_name_base+'1')(residual)
    
    out = Add()([x, residual])
    out = Activation('relu')(out)
    return out

def ResNet50(input_shape, classes):
    x_input = Input(input_shape)
    x = ZeroPadding2D((3,3))(x_input)
    
    x = Conv2D(64, kernel_size=(7,7), strides=(2,2), name='conv1', use_bias=True, kernel_initializer='glorot_uniform')(x)
    x = BatchNormalization(axis=3, name='bn_conv1')(x)
    x = Activation('relu')(x)
    x = ZeroPadding2D((1,1))(x)   
    x = MaxPooling2D(pool_size=(3,3), strides=(2,2), padding='same')(x)
    
    # layer1 *3
    x = Bottleneck(x, 64, stage=2, block='a', s=1, short_cut=True)
    x = Bottleneck(x, 64, stage=2, block='b', s=1, short_cut=False)
    x = Bottleneck(x, 64, stage=2, block='c', s=1, short_cut=False)
    
    # layer2 *4
    x = Bottleneck(x, 128, stage=3, block='a', s=2, short_cut=True)
    x = Bottleneck(x, 128, stage=3, block='b', s=1, short_cut=False)
    x = Bottleneck(x, 128, stage=3, block='c', s=1, short_cut=False)
    x = Bottleneck(x, 128, stage=3, block='d', s=1, short_cut=False)
    
    # layer3 *6
    x = Bottleneck(x, 256, stage=4, block='a', s=2, short_cut=True)
    x = Bottleneck(x, 256, stage=4, block='b', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='c', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='d', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='e', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='f', s=1, short_cut=False)
    
    # layer4 *3
    x = Bottleneck(x, 512, stage=5, block='a', s=2, short_cut=True)
    x = Bottleneck(x, 512, stage=5, block='b', s=1, short_cut=False)
    x = Bottleneck(x, 512, stage=5, block='c', s=1, short_cut=False)
    
    x = AveragePooling2D((2,2), name='avg_pool')(x)
    x = Flatten()(x)
    x = Dense(classes, activation='softmax', name='fc'+str(classes))(x)
    
    model = Model(inputs=x_input, outputs=x, name='ResNet50')
    
    return model

def ResNet101(input_shape, classes):
    x_input = Input(input_shape)
    x = ZeroPadding2D((3,3))(x_input)
    
    x = Conv2D(64, kernel_size=(7,7), strides=(2,2), name='conv1', use_bias=True, kernel_initializer='glorot_uniform')(x)
    x = BatchNormalization(axis=3, name='bn_conv1')(x)
    x = Activation('relu')(x)
    x = ZeroPadding2D((1,1))(x)    
    x = MaxPooling2D(pool_size=(3,3), strides=(2,2), padding='same')(x)
    
    # layer1 *3
    x = Bottleneck(x, 64, stage=2, block='a', s=1, short_cut=True)
    x = Bottleneck(x, 64, stage=2, block='b', s=1, short_cut=False)
    x = Bottleneck(x, 64, stage=2, block='c', s=1, short_cut=False)
    
    # layer2 *4
    x = Bottleneck(x, 128, stage=3, block='a', s=2, short_cut=True)
    x = Bottleneck(x, 128, stage=3, block='b', s=1, short_cut=False)
    x = Bottleneck(x, 128, stage=3, block='c', s=1, short_cut=False)
    x = Bottleneck(x, 128, stage=3, block='d', s=1, short_cut=False)
    
    # layer3 *23
    x = Bottleneck(x, 256, stage=4, block='a', s=2, short_cut=True)
    x = Bottleneck(x, 256, stage=4, block='b', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='c', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='d', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='e', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='f', s=1, short_cut=False)
    
    x = Bottleneck(x, 256, stage=4, block='a_2', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='b_2', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='c_2', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='d_2', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='e_2', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='f_2', s=1, short_cut=False)
    
    x = Bottleneck(x, 256, stage=4, block='a_3', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='b_3', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='c_3', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='d_3', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='e_3', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='f_3', s=1, short_cut=False)
    
    x = Bottleneck(x, 256, stage=4, block='a_4', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='b_4', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='c_4', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='d_4', s=1, short_cut=False)
    x = Bottleneck(x, 256, stage=4, block='e_4', s=1, short_cut=False)
    
    # layer4 *3
    x = Bottleneck(x, 512, stage=5, block='a', s=2, short_cut=True)
    x = Bottleneck(x, 512, stage=5, block='b', s=1, short_cut=False)
    x = Bottleneck(x, 512, stage=5, block='c', s=1, short_cut=False)
    
    x = AveragePooling2D((2,2), name='avg_pool')(x)
    x = Flatten()(x)
    x = Dense(classes, activation='softmax', name='fc'+str(classes))(x)
    
    model = Model(inputs=x_input, outputs=x, name='ResNet101')
    
    return model

def ResNet152(input_shape, classes):
    x_input = Input(input_shape)
    x = ZeroPadding2D((3,3))(x_input)
    
    x = Conv2D(64, kernel_size=(7,7), strides=(2,2), name='conv1', use_bias=True, kernel_initializer='glorot_uniform')(x)
    x = BatchNormalization(axis=3, name='bn_conv1')(x)
    x = Activation('relu')(x)
    x = ZeroPadding2D((1,1))(x)    
    x = MaxPooling2D(pool_size=(3,3), strides=(2,2), padding='same')(x)
    
    # layer1   * 3
    x = Bottleneck(x, filters=64, stage=2, block='a', s=1, short_cut=True)
    x = Bottleneck(x, filters=64, stage=2, block='b', s=1, short_cut=False)
    x = Bottleneck(x, filters=64, stage=2, block='c', s=1, short_cut=False)
    
    
    # layer2  *8
    x = Bottleneck(x, filters=128, stage=3, block='a', s=2, short_cut=True)
    x = Bottleneck(x, filters=128, stage=3, block='b', s=1, short_cut=False)
    x = Bottleneck(x, filters=128, stage=3, block='c', s=1, short_cut=False)
    x = Bottleneck(x, filters=128, stage=3, block='d', s=1, short_cut=False)
    x = Bottleneck(x, filters=128, stage=3, block='e', s=1, short_cut=False)
    x = Bottleneck(x, filters=128, stage=3, block='f', s=1, short_cut=False)
    x = Bottleneck(x, filters=128, stage=3, block='g', s=1, short_cut=False)
    x = Bottleneck(x, filters=128, stage=3, block='h', s=1, short_cut=False)
    
    # layer3  * 36
    x = Bottleneck(x, filters=256, stage=4, block='a', s=2, short_cut=True)
    x = Bottleneck(x, filters=256, stage=4, block='b', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='c', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='d', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='e', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='f', s=1, short_cut=False)
    
    x = Bottleneck(x, filters=256, stage=4, block='a_2', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='b_2', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='c_2', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='d_2', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='e_2', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='f_2', s=1, short_cut=False)
    
    x = Bottleneck(x, filters=256, stage=4, block='a_3', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='b_3', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='c_3', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='d_3', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='e_3', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='f_3', s=1, short_cut=False)
    
    x = Bottleneck(x, filters=256, stage=4, block='a_4', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='b_4', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='c_4', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='d_4', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='e_4', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='f_4', s=1, short_cut=False)
    
    x = Bottleneck(x, filters=256, stage=4, block='a_5', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='b_5', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='c_5', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='d_5', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='e_5', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='f_5', s=1, short_cut=False)
    
    x = Bottleneck(x, filters=256, stage=4, block='a_6', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='b_6', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='c_6', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='d_6', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='e_6', s=1, short_cut=False)
    x = Bottleneck(x, filters=256, stage=4, block='f_6', s=1, short_cut=False)
    
    # layer4 * 3
    x = Bottleneck(x, filters=512, stage=5, block='a', s=2, short_cut=True)
    x = Bottleneck(x, filters=512, stage=5, block='b', s=1, short_cut=False)
    x = Bottleneck(x, filters=512, stage=5, block='c', s=1, short_cut=False)
    
    x = AveragePooling2D((2,2), name='avg_pool')(x)
    x = Flatten()(x)
    x = Dense(classes, activation='softmax', name='fc'+str(classes))(x)
    
    model = Model(inputs=x_input, outputs=x, name='ResNet152')
    return model

查看網絡結構可以通過model = ResNet152(input_shape=(224, 224, 3), classes=1000)  print(model)

ResNet網絡很有規律性,conv2_x、conv3_x、conv4_x、conv5_x每個模塊無論重複多少遍,模塊內第一次調用Bottleneck,shortcut都是要通過downsample後再add,並且步長是2

 

3、ResNet的pytorch實現

import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo

__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101', 'resnet152']


model_urls = {
    'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
    'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
    'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
    'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
    'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
}

def conv3x3(in_planes, out_planes, stride=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=1, bias=False)


class BasicBlock(nn.Module):
    # Figure5(左) Block
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


class Bottleneck(nn.Module):
        # Figure5(右) Block
    expansion = 4

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
                               padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(planes * self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


class ResNet(nn.Module):

    def __init__(self, block, layers, num_classes=1000):
        self.inplanes = 64
        super(ResNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.fc = nn.Linear(512 * block.expansion, num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def _make_layer(self, block, planes, blocks, stride=1):
        downsample = None
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes * block.expansion,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(planes * block.expansion),
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * block.expansion
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes))

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)

        return x


def resnet18(pretrained=False, **kwargs):
    """Constructs a ResNet-18 model.

    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
    """
    model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
    return model


def resnet34(pretrained=False, **kwargs):
    """Constructs a ResNet-34 model.

    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
    """
    model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet34']))
    return model


def resnet50(pretrained=False, **kwargs):
    """Constructs a ResNet-50 model.

    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
    """
    model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet50']))
    return model


def resnet101(pretrained=False, **kwargs):
    """Constructs a ResNet-101 model.

    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
    """
    model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet101']))
    return model


def resnet152(pretrained=False, **kwargs):
    """Constructs a ResNet-152 model.

    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
    """
    model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet152']))
    return model

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章