Keras FAQ: 常見問題解答


Keras FAQ: 常見問題解答


如何引用 Keras?

如果 Keras 有助于您的研究,請在你的出版物中引用它。以下是 BibTeX 條目引用的示例:

@misc{chollet2015keras,
  title={Keras},
  author={Chollet, Fran\c{c}ois and others},
  year={2015},
  publisher={GitHub},
  howpublished={\url{https://github.com/keras-team/keras}},
}


如何在 GPU 上運行 Keras?

如果你以 TensorFlow 或 CNTK 后端運行,只要檢測到任何可用的 GPU,那么代碼將自動在 GPU 上運行。

如果你以 Theano 后端運行,則可以使用以下方法之一:

方法 1: 使用 Theano flags。

THEANO_FLAGS=device=gpu,floatX=float32 python my_keras_script.py

"gpu" 可能需要根據(jù)你的設(shè)備標(biāo)識符(例如gpu0,gpu1等)進(jìn)行更改。

方法 2: 創(chuàng)建 .theanorc: 指導(dǎo)教程

方法 3: 在代碼的開頭手動設(shè)置 theano.config.device, theano.config.floatX

import theano
theano.config.device = 'gpu'
theano.config.floatX = 'float32'


如何在多 GPU 上運行 Keras 模型?

我們建議使用 TensorFlow 后端來執(zhí)行這項任務(wù)。有兩種方法可在多個 GPU 上運行單個模型:數(shù)據(jù)并行設(shè)備并行。

在大多數(shù)情況下,你最需要的是數(shù)據(jù)并行。

數(shù)據(jù)并行

數(shù)據(jù)并行包括在每個設(shè)備上復(fù)制一次目標(biāo)模型,并使用每個模型副本處理不同部分的輸入數(shù)據(jù)。Keras 有一個內(nèi)置的實用函數(shù) keras.utils.multi_gpu_model,它可以生成任何模型的數(shù)據(jù)并行版本,在多達(dá) 8 個 GPU 上實現(xiàn)準(zhǔn)線性加速。

有關(guān)更多信息,請參閱 multi_gpu_model 的文檔。這里是一個快速的例子:

from keras.utils import multi_gpu_model

# 將 `model` 復(fù)制到 8 個 GPU 上。
# 假定你的機(jī)器有 8 個可用的 GPU。
parallel_model = multi_gpu_model(model, gpus=8)
parallel_model.compile(loss='categorical_crossentropy',
                       optimizer='rmsprop')

# 這個 `fit` 調(diào)用將分布在 8 個 GPU 上。
# 由于 batch size 為 256,每個 GPU 將處理 32 個樣本。
parallel_model.fit(x, y, epochs=20, batch_size=256)

設(shè)備并行

設(shè)備并行性包括在不同設(shè)備上運行同一模型的不同部分。對于具有并行體系結(jié)構(gòu)的模型,例如有兩個分支的模型,這種方式很合適。

這種并行可以通過使用 TensorFlow device scopes 來實現(xiàn)。這里是一個簡單的例子:

# 模型中共享的 LSTM 用于并行編碼兩個不同的序列
input_a = keras.Input(shape=(140, 256))
input_b = keras.Input(shape=(140, 256))

shared_lstm = keras.layers.LSTM(64)

# 在一個 GPU 上處理第一個序列
with tf.device_scope('/gpu:0'):
    encoded_a = shared_lstm(tweet_a)
# 在另一個 GPU上 處理下一個序列
with tf.device_scope('/gpu:1'):
    encoded_b = shared_lstm(tweet_b)

# 在 CPU 上連接結(jié)果
with tf.device_scope('/cpu:0'):
    merged_vector = keras.layers.concatenate([encoded_a, encoded_b],
                                             axis=-1)


"sample", "batch", "epoch" 分別是什么?

為了正確地使用 Keras,以下是必須了解和理解的一些常見定義:

  • Sample: 樣本,數(shù)據(jù)集中的一個元素,一條數(shù)據(jù)。
    • 例1: 在卷積神經(jīng)網(wǎng)絡(luò)中,一張圖像是一個樣本。
    • 例2: 在語音識別模型中,一段音頻是一個樣本。
  • Batch: 批,含有 N 個樣本的集合。每一個 batch 的樣本都是獨立并行處理的。在訓(xùn)練時,一個 batch 的結(jié)果只會用來更新一次模型。
    • 一個 batch 的樣本通常比單個輸入更接近于總體輸入數(shù)據(jù)的分布,batch 越大就越近似。然而,每個 batch 將花費更長的時間來處理,并且仍然只更新模型一次。在推理(評估/預(yù)測)時,建議條件允許的情況下選擇一個盡可能大的 batch,(因為較大的 batch 通常評估/預(yù)測的速度會更快)。
  • Epoch: 輪次,通常被定義為 「在整個數(shù)據(jù)集上的一輪迭代」,用于訓(xùn)練的不同的階段,這有利于記錄和定期評估。
    • 當(dāng)在 Keras 模型的 fit 方法中使用 validation_datavalidation_split 時,評估將在每個 epoch 結(jié)束時運行。
    • 在 Keras 中,可以添加專門的用于在 epoch 結(jié)束時運行的 callbacks 回調(diào)。例如學(xué)習(xí)率變化和模型檢查點(保存)。

如何保存 Keras 模型?

保存/加載整個模型(結(jié)構(gòu) + 權(quán)重 + 優(yōu)化器狀態(tài))

不建議使用 pickle 或 cPickle 來保存 Keras 模型。

你可以使用 model.save(filepath) 將 Keras 模型保存到單個 HDF5 文件中,該文件將包含:

  • 模型的結(jié)構(gòu),允許重新創(chuàng)建模型
  • 模型的權(quán)重
  • 訓(xùn)練配置項(損失函數(shù),優(yōu)化器)
  • 優(yōu)化器狀態(tài),允許準(zhǔn)確地從你上次結(jié)束的地方繼續(xù)訓(xùn)練。

你可以使用 keras.models.load_model(filepath) 重新實例化模型。load_model 還將負(fù)責(zé)使用保存的訓(xùn)練配置項來編譯模型(除非模型從未編譯過)。

例子:

from keras.models import load_model

model.save('my_model.h5')  # 創(chuàng)建 HDF5 文件 'my_model.h5'
del model  # 刪除現(xiàn)有模型

# 返回一個編譯好的模型
# 與之前那個相同
model = load_model('my_model.h5')

另請參閱如何安裝 HDF5 或 h5py 以在 Keras 中保存我的模型?,查看有關(guān)如何安裝 h5py 的說明。

只保存/加載模型的結(jié)構(gòu)

如果您只需要保存模型的結(jié)構(gòu),而非其權(quán)重或訓(xùn)練配置項,則可以執(zhí)行以下操作:

# 保存為 JSON
json_string = model.to_json()

# 保存為 YAML
yaml_string = model.to_yaml()

生成的 JSON/YAML 文件是人類可讀的,如果需要還可以手動編輯。

你可以從這些數(shù)據(jù)建立一個新的模型:

# 從 JSON 重建模型:
from keras.models import model_from_json
model = model_from_json(json_string)

# 從 YAML 重建模型:
from keras.models import model_from_yaml
model = model_from_yaml(yaml_string)

只保存/加載模型的權(quán)重

如果您只需要 模型的權(quán)重,可以使用下面的代碼以 HDF5 格式進(jìn)行保存。

請注意,我們首先需要安裝 HDF5 和 Python 庫 h5py,它們不包含在 Keras 中。

model.save_weights('my_model_weights.h5')

假設(shè)你有用于實例化模型的代碼,則可以將保存的權(quán)重加載到具有相同結(jié)構(gòu)的模型中:

model.load_weights('my_model_weights.h5')

如果你需要將權(quán)重加載到不同的結(jié)構(gòu)(有一些共同層)的模型中,例如微調(diào)或遷移學(xué)習(xí),則可以按層的名字來加載權(quán)重:

model.load_weights('my_model_weights.h5', by_name=True)

例子:

"""
假設(shè)原始模型如下所示:
    model = Sequential()
    model.add(Dense(2, input_dim=3, name='dense_1'))
    model.add(Dense(3, name='dense_2'))
    ...
    model.save_weights(fname)
"""

# 新模型
model = Sequential()
model.add(Dense(2, input_dim=3, name='dense_1'))  # 將被加載
model.add(Dense(10, name='new_dense'))  # 將不被加載

# 從第一個模型加載權(quán)重;只會影響第一層,dense_1
model.load_weights(fname, by_name=True)

處理已保存模型中的自定義層(或其他自定義對象)

如果要加載的模型包含自定義層或其他自定義類或函數(shù),則可以通過 custom_objects 參數(shù)將它們傳遞給加載機(jī)制:

from keras.models import load_model
# 假設(shè)你的模型包含一個 AttentionLayer 類的實例
model = load_model('my_model.h5', custom_objects={'AttentionLayer': AttentionLayer})

或者,你可以使用 自定義對象作用域

from keras.utils import CustomObjectScope

with CustomObjectScope({'AttentionLayer': AttentionLayer}):
    model = load_model('my_model.h5')

自定義對象的處理與 load_model, model_from_json, model_from_yaml 的工作方式相同:

from keras.models import model_from_json
model = model_from_json(json_string, custom_objects={'AttentionLayer': AttentionLayer})


為什么訓(xùn)練誤差比測試誤差高很多?

Keras 模型有兩種模式:訓(xùn)練和測試。正則化機(jī)制,如 Dropout 和 L1/L2 權(quán)重正則化,在測試時是關(guān)閉的。

此外,訓(xùn)練誤差是每批訓(xùn)練數(shù)據(jù)的平均誤差。由于你的模型是隨著時間而變化的,一個 epoch 中的第一批數(shù)據(jù)的誤差通常比最后一批的要高。另一方面,測試誤差是模型在一個 epoch 訓(xùn)練完后計算的,因而誤差較小。


如何獲取中間層的輸出?

一個簡單的方法是創(chuàng)建一個新的 Model 來輸出你所感興趣的層:

from keras.models import Model

model = ...  # 創(chuàng)建原始模型

layer_name = 'my_layer'
intermediate_layer_model = Model(inputs=model.input,
                                 outputs=model.get_layer(layer_name).output)
intermediate_output = intermediate_layer_model.predict(data)

或者,你也可以構(gòu)建一個 Keras 函數(shù),該函數(shù)將在給定輸入的情況下返回某個層的輸出,例如:

from keras import backend as K

# 以 Sequential 模型為例
get_3rd_layer_output = K.function([model.layers[0].input],
                                  [model.layers[3].output])
layer_output = get_3rd_layer_output([x])[0]

同樣,你可以直接建立一個 Theano 或 TensorFlow 函數(shù)。

注意,如果你的模型在訓(xùn)練和測試階段有不同的行為(例如,使用 Dropout, BatchNormalization 等),則需要將學(xué)習(xí)階段標(biāo)志傳遞給你的函數(shù):

get_3rd_layer_output = K.function([model.layers[0].input, K.learning_phase()],
                                  [model.layers[3].output])

# 測試模式 = 0 時的輸出
layer_output = get_3rd_layer_output([x, 0])[0]

# 測試模式 = 1 時的輸出
layer_output = get_3rd_layer_output([x, 1])[0]


如何用 Keras 處理超過內(nèi)存的數(shù)據(jù)集?

你可以使用 model.train_on_batch(x,y)model.test_on_batch(x,y) 進(jìn)行批量訓(xùn)練與測試。請參閱 模型文檔。

或者,你可以編寫一個生成批處理訓(xùn)練數(shù)據(jù)的生成器,然后使用 model.fit_generator(data_generator,steps_per_epoch,epochs) 方法。

你可以在 CIFAR10 example 中找到實踐代碼。


在驗證集的誤差不再下降時,如何中斷訓(xùn)練?

你可以使用 EarlyStopping 回調(diào):

from keras.callbacks import EarlyStopping
early_stopping = EarlyStopping(monitor='val_loss', patience=2)
model.fit(x, y, validation_split=0.2, callbacks=[early_stopping])

更多信息請查看 callbacks 文檔。


驗證集劃分是如何計算的?

如果您將 model.fit 中的 validation_split 參數(shù)設(shè)置為 0.1,那么使用的驗證數(shù)據(jù)將是最后 10% 的數(shù)據(jù)。如果設(shè)置為 0.25,就是最后 25% 的數(shù)據(jù)。注意,在提取分割驗證集之前,數(shù)據(jù)不會被混洗,因此驗證集僅僅是傳遞的輸入中最后一個 x% 的樣本。

所有 epoch 都使用相同的驗證集(在同一個 fit 中調(diào)用)。


在訓(xùn)練過程中數(shù)據(jù)是否會混洗?

是的,如果 model.fit中的 shuffle參數(shù)設(shè)置為 True(默認(rèn)值),則訓(xùn)練數(shù)據(jù)將在每個 epoch 混洗。

驗證集永遠(yuǎn)不會混洗。


如何在每個 epoch 后記錄訓(xùn)練集和驗證集的誤差和準(zhǔn)確率?

model.fit 方法返回一個 History 回調(diào),它具有包含連續(xù)誤差的列表和其他度量的 history 屬性。

hist = model.fit(x, y, validation_split=0.2)
print(hist.history)


如何「凍結(jié)」網(wǎng)絡(luò)層?

「凍結(jié)」一個層意味著將其排除在訓(xùn)練之外,即其權(quán)重將永遠(yuǎn)不會更新。這在微調(diào)模型或使用固定的詞向量進(jìn)行文本輸入中很有用。

您可以將 trainable 參數(shù)(布爾值)傳遞給一個層的構(gòu)造器,以將該層設(shè)置為不可訓(xùn)練的:

frozen_layer = Dense(32, trainable=False)

另外,可以在實例化之后將網(wǎng)絡(luò)層的 trainable 屬性設(shè)置為 True 或 False。為了使之生效,在修改 trainable 屬性之后,需要在模型上調(diào)用 compile()。這是一個例子:

x = Input(shape=(32,))
layer = Dense(32)
layer.trainable = False
y = layer(x)

frozen_model = Model(x, y)
# 在下面的模型中,訓(xùn)練期間不會更新層的權(quán)重
frozen_model.compile(optimizer='rmsprop', loss='mse')

layer.trainable = True
trainable_model = Model(x, y)
# 使用這個模型,訓(xùn)練期間 `layer` 的權(quán)重將被更新
# (這也會影響上面的模型,因為它使用了同一個網(wǎng)絡(luò)層實例)
trainable_model.compile(optimizer='rmsprop', loss='mse')

frozen_model.fit(data, labels)  # 這不會更新 `layer` 的權(quán)重
trainable_model.fit(data, labels)  # 這會更新 `layer` 的權(quán)重


如何使用有狀態(tài) RNN (stateful RNNs)?

使 RNN 具有狀態(tài)意味著每批樣品的狀態(tài)將被重新用作下一批樣品的初始狀態(tài)。

當(dāng)使用有狀態(tài) RNN 時,假定:

  • 所有的批次都有相同數(shù)量的樣本
  • 如果 x1x2 是連續(xù)批次的樣本,則 x2[i]x1[i] 的后續(xù)序列,對于每個 i。

要在 RNN 中使用狀態(tài),你需要:

  • 通過將 batch_size 參數(shù)傳遞給模型的第一層來顯式指定你正在使用的批大小。例如,對于 10 個時間步長的 32 樣本的 batch,每個時間步長具有 16 個特征,batch_size = 32。
  • 在 RNN 層中設(shè)置 stateful = True。
  • 在調(diào)用 fit() 時指定 shuffle = False。

重置累積狀態(tài):

  • 使用 model.reset_states() 來重置模型中所有層的狀態(tài)
  • 使用 layer.reset_states() 來重置指定有狀態(tài) RNN 層的狀態(tài)

例子:

x  # 輸入數(shù)據(jù),尺寸為 (32, 21, 16)
# 將步長為 10 的序列輸送到模型中

model = Sequential()
model.add(LSTM(32, input_shape=(10, 16), batch_size=32, stateful=True))
model.add(Dense(16, activation='softmax'))

model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

# 訓(xùn)練網(wǎng)絡(luò),根據(jù)給定的前 10 個時間步,來預(yù)測第 11 個時間步:
model.train_on_batch(x[:, :10, :], np.reshape(x[:, 10, :], (32, 16)))

# 網(wǎng)絡(luò)的狀態(tài)已經(jīng)改變。我們可以提供后續(xù)序列:
model.train_on_batch(x[:, 10:20, :], np.reshape(x[:, 20, :], (32, 16)))

# 重置 LSTM 層的狀態(tài):
model.reset_states()

# 另一種重置方法:
model.layers[0].reset_states()

請注意,predict, fit, train_on_batch, predict_classes 等方法全部都會更新模型中有狀態(tài)層的狀態(tài)。這使你不僅可以進(jìn)行有狀態(tài)的訓(xùn)練,還可以進(jìn)行有狀態(tài)的預(yù)測。


如何從 Sequential 模型中移除一個層?

你可以通過調(diào)用 .pop() 來刪除 Sequential 模型中最后添加的層:

model = Sequential()
model.add(Dense(32, activation='relu', input_dim=784))
model.add(Dense(32, activation='relu'))

print(len(model.layers))  # "2"

model.pop()
print(len(model.layers))  # "1"


如何在 Keras 中使用預(yù)訓(xùn)練的模型?

我們提供了以下圖像分類模型的代碼和預(yù)訓(xùn)練的權(quán)重:

  • Xception
  • VGG16
  • VGG19
  • ResNet50
  • Inception v3
  • Inception-ResNet v2
  • MobileNet v1
  • DenseNet
  • NASNet
  • MobileNet v2

它們可以使用 keras.applications 模塊進(jìn)行導(dǎo)入:

from keras.applications.xception import Xception
from keras.applications.vgg16 import VGG16
from keras.applications.vgg19 import VGG19
from keras.applications.resnet50 import ResNet50
from keras.applications.inception_v3 import InceptionV3
from keras.applications.inception_resnet_v2 import InceptionResNetV2
from keras.applications.mobilenet import MobileNet
from keras.applications.densenet import DenseNet121
from keras.applications.densenet import DenseNet169
from keras.applications.densenet import DenseNet201
from keras.applications.nasnet import NASNetLarge
from keras.applications.nasnet import NASNetMobile
from keras.applications.mobilenet_v2 import MobileNetV2

model = VGG16(weights='imagenet', include_top=True)

有關(guān)一些簡單的用法示例,請參閱 Applications 模塊的文檔

有關(guān)如何使用此類預(yù)訓(xùn)練的模型進(jìn)行特征提取或微調(diào)的詳細(xì)示例,請參閱 此博客文章。

VGG16 模型也是以下幾個 Keras 示例腳本的基礎(chǔ):


如何在 Keras 中使用 HDF5 輸入?

你可以使用 keras.utils.io_utils 中的 HDF5Matrix 類。有關(guān)詳細(xì)信息,請參閱 HDF5Matrix文檔

你也可以直接使用 HDF5 數(shù)據(jù)集:

import h5py
with h5py.File('input/file.hdf5', 'r') as f:
    x_data = f['x_data']
    model.predict(x_data)


Keras 配置文件保存在哪里?

所有 Keras 數(shù)據(jù)存儲的默認(rèn)目錄是:

$HOME/.keras/

注意,Windows 用戶應(yīng)該將 $HOME 替換為 %USERPROFILE%。如果 Keras 無法創(chuàng)建上述目錄(例如,由于權(quán)限問題),則使用 /tmp/.keras/ 作為備份。

Keras配置文件是存儲在 $HOME/.keras/keras.json 中的 JSON 文件。默認(rèn)的配置文件如下所示:

{
    "image_data_format": "channels_last",
    "epsilon": 1e-07,
    "floatx": "float32",
    "backend": "tensorflow"
}

它包含以下字段:

  • 圖像處理層和實用程序所使用的默認(rèn)值圖像數(shù)據(jù)格式(channels_lastchannels_first)。
  • 用于防止在某些操作中被零除的 epsilon 模糊因子。
  • 默認(rèn)浮點數(shù)據(jù)類型。
  • 默認(rèn)后端。詳見 backend 文檔。

同樣,緩存的數(shù)據(jù)集文件(如使用 get_file() 下載的文件)默認(rèn)存儲在 $HOME/.keras/datasets/ 中。


如何在 Keras 開發(fā)過程中獲取可復(fù)現(xiàn)的結(jié)果?

在模型的開發(fā)過程中,能夠在一次次的運行中獲得可復(fù)現(xiàn)的結(jié)果,以確定性能的變化是來自模型還是數(shù)據(jù)集的變化,或者僅僅是一些新的隨機(jī)樣本點帶來的結(jié)果,有時候是很有用處的。

首先,你需要在程序啟動之前將 PYTHONHASHSEED 環(huán)境變量設(shè)置為 0(不在程序本身內(nèi))。對于 Python 3.2.3 以上版本,它對于某些基于散列的操作具有可重現(xiàn)的行為是必要的(例如,集合和字典的 item 順序,請參閱 Python 文檔issue #2280 獲取更多詳細(xì)信息)。設(shè)置環(huán)境變量的一種方法是,在這樣啟動 python 時:

$ cat test_hash.py
print(hash("keras"))
$ python3 test_hash.py                  # 無法復(fù)現(xiàn)的 hash (Python 3.2.3+)
-8127205062320133199
$ python3 test_hash.py                  # 無法復(fù)現(xiàn)的 hash (Python 3.2.3+)
3204480642156461591
$ PYTHONHASHSEED=0 python3 test_hash.py # 可復(fù)現(xiàn)的 hash
4883664951434749476
$ PYTHONHASHSEED=0 python3 test_hash.py # 可復(fù)現(xiàn)的 hash
4883664951434749476

此外,當(dāng)使用 TensorFlow 后端并在 GPU 上運行時,某些操作具有非確定性輸出,特別是 tf.reduce_sum()。這是因為 GPU 并行運行許多操作,因此并不總能保證執(zhí)行順序。由于浮點數(shù)的精度有限,即使添加幾個數(shù)字,也可能會產(chǎn)生略有不同的結(jié)果,具體取決于添加它們的順序。你可以嘗試避免某些非確定性操作,但有些操作可能是由 TensorFlow 在計算梯度時自動創(chuàng)建的,因此在 CPU 上運行代碼要簡單得多。為此,你可以將 CUDA_VISIBLE_DEVICES 環(huán)境變量設(shè)置為空字符串,例如:

$ CUDA_VISIBLE_DEVICES="" PYTHONHASHSEED=0 python your_program.py

下面的代碼片段提供了一個如何獲得可復(fù)現(xiàn)結(jié)果的例子 - 針對 Python 3 環(huán)境的 TensorFlow 后端。

import numpy as np
import tensorflow as tf
import random as rn

# 以下是 Numpy 在一個明確的初始狀態(tài)生成固定隨機(jī)數(shù)字所必需的。

np.random.seed(42)

# 以下是 Python 在一個明確的初始狀態(tài)生成固定隨機(jī)數(shù)字所必需的。

rn.seed(12345)

# 強(qiáng)制 TensorFlow 使用單線程。
# 多線程是結(jié)果不可復(fù)現(xiàn)的一個潛在因素。
# 更多詳情,見: https://stackoverflow.com/questions/42022950/

session_conf = tf.ConfigProto(intra_op_parallelism_threads=1,
                              inter_op_parallelism_threads=1)

from keras import backend as K

# `tf.set_random_seed()` 將會以 TensorFlow 為后端,
# 在一個明確的初始狀態(tài)下生成固定隨機(jī)數(shù)字。
# 更多詳情,見: https://www.tensorflow.org/api_docs/python/tf/set_random_seed

tf.set_random_seed(1234)

sess = tf.Session(graph=tf.get_default_graph(), config=session_conf)
K.set_session(sess)

# 剩余代碼 ...


如何在 Keras 中安裝 HDF5 或 h5py 來保存我的模型?

為了將你的 Keras 模型保存為 HDF5 文件,例如通過 keras.callbacks.ModelCheckpoint,Keras 使用了 h5py Python 包。h5py 是 Keras 的依賴項,應(yīng)默認(rèn)被安裝。在基于 Debian 的發(fā)行版本上,你需要再額外安裝 libhdf5

sudo apt-get install libhdf5-serial-dev

如果你不確定是否安裝了 h5py,則可以打開 Python shell 并通過下面的命令加載模塊

import h5py

如果模塊導(dǎo)入沒有錯誤,那么說明模塊已經(jīng)安裝成功,否則你可以在 http://docs.h5py.org/en/latest/build.html 中找到詳細(xì)的安裝說明。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容