使用tensorflow进行音乐类型的分类
音乐流媒体服务的兴起使得音乐无处不在。我们在上下班的时候听音乐,锻炼身体,工作或者只是放松一下。
这些服务的一个关键特性是播放列表,通常按流派分组。这些数据可能来自出版歌曲的人手工标注。但这并不是一个很好的划分,因为可能是一些艺人想利用一个特定流派的流行趋势。更好的选择是依靠自动音乐类型分类。与我的两位合作者张伟信(WilsonCheung)和顾长乐(JoyGu)一起,我们试图比较不同的音乐样本分类方法。特别是,我们评估了标准机器学习和深度学习方法的性能。我们发现特征工程是至关重要的,而领域知识可以真正提高性能。
在描述了所使用的数据源之后,我对我们使用的方法及其结果进行了简要概述。在本文的最后一部分,我将花更多的时间来解释googlecolab中的TensorFlow框架如何通过TFRecord格式在GPU或TPU运行时高效地执行这些任务。所有代码都在这里,我们很高兴与感兴趣的人分享我们更详细的报告。
数据源
预测一个音频样本的类型是一个监督学习问题。换句话说,我们需要包含标记示例的数据。FreeMusicArchive是一个包含相关标签和元数据的音频片段库,最初是在2017年的国际音乐信息检索会议(ISMIR)上为论文而收集的。
我们将分析重点放在所提供数据的一小部分上。它包含8000个音频片段,每段长度为30秒,分为八种不同类型之一:
- Hip-Hop
- Pop
- Folk
- Experimental
- Rock
- International
- Electronic
- Instrumental
每种类型都有1000个代表性的音频片段。采样率为44100hz,这意味着每个音频样本有超过100万个数据点,或者总共超过10个数据点。在分类器中使用所有这些数据是一个挑战,我们将在接下来的章节中详细讨论。
有关如何下载数据的说明,请参阅存储库中包含的自述文件。我们非常感谢MichaëlDefferrard、KirellBenzi、PierreVandergheynst、XavierBresson将这些数据整合在一起并免费提供,但我们只能想象Spotify或PandoraRadio拥有的数据规模所能提供的见解。有了这些数据,我们可以描述各种模型来执行手头的任务。
模型说明
我会尽量减少理论上的细节,但会尽可能地链接到相关资源。另外,我们的报告包含的信息比我在这里能包含的要多得多,尤其是关于功能工程的信息。
标准机器学习
我们使用了Logistic回归、k-近邻(kNN)、高斯朴素贝叶斯和支持向量机(SVM):
支持向量机(SVM)通过最大化训练数据的裕度来寻找最佳决策边界。核技巧通过将数据投影到高维空间来定义非线性边界
kNN根据k个最近的训练样本的多数票分配一个标签
naivebayes根据特征预测不同类的概率。条件独立性假设大大简化了计算
Logistic回归还利用Logistic函数,通过对概率的直接建模来预测不同类别的概率
深度学习
对于深入学习,我们利用TensorFlow框架。我们根据输入的类型建立了不同的模型。
对于原始音频,每个示例是一个30秒的音频样本,或者大约130万个数据点。这些浮点值(正或负)表示在某一时刻的波位移。为了管理计算资源,只能使用不到1%的数据。有了这些特征和相关的标签(一个热点编码),我们可以建立一个卷积神经网络。总体架构如下:
一维卷积层,其中过滤器结合来自偶然数据的信息MaxPooling层,它结合了来自卷积层的信息全连接层,创建提取的卷积特征的线性组合,并执行最终的分类Dropout层,它帮助模型泛化到不可见的数据
另一方面,光谱图作为音频样本的视觉表示。这启发了将训练数据视为图像,并通过迁移学习利用预先训练的模型。对于每个例子,我们可以形成一个矩阵的Mel谱图。如果我们正确计算尺寸,这个矩阵可以表示为224x224x3图像。这些都是利用MobileNetV2的正确维度,MobileNetV2在图像分类任务上有着出色的性能。转移学习的思想是使用预先训练的模型的基本层来提取特征,并用一个定制的分类器(在我们的例子中是稠密层)代替最后一层。这是因为基本层通常可以很好地泛化到所有图像,即使它们没有经过训练。
模型结果
我们使用20%的测试集来评估我们模型的性能。我们可以将结果汇总到下表中:
在谱图中应用迁移学习的卷积神经网络是性能最好的,尽管SVM和Gaussiannaivebayes在性能上相似(考虑到后者的简化假设,这本身就很有趣)。我们在报告中描述了最好的超参数和模型体系结构。
我们对训练和验证曲线的分析突出了过度拟合的问题,如下图所示(我们的大多数模型都有类似的图表)。目前的特征模式有助于我们确定这一问题。我们为此设计了一些解决方案,可以在本项目的未来迭代中实现:
- 降低数据的维数:PCA等技术可用于将提取的特征组合在一起,并限制每个示例的特征向量的大小
- 增加训练数据的大小:数据源提供更大的数据子集。我们将探索范围限制在整个数据集的10%以下。如果有更多的计算资源可用,或者成功地降低数据的维数,我们可以考虑使用完整的数据集。这很可能使我们的方法能够隔离更多的模式,并大大提高性能
- 在我们的搜索功能时请多加注意:FreeMusicChive包含一系列功能。当我们使用这些特性而不是我们自己的特性时,我们确实看到了性能的提高,这使我们相信我们可以希望通过领域知识和扩展的特征集获得更好的结果
TensorFlow实现
TensorFlow是一个非常强大的工具,可以在规模上构建神经网络,尤其是与googlecolab的免费GPU/TPU运行时结合使用。这个项目的主要观点是找出瓶颈:我最初的实现非常缓慢,甚至使用GPU。我发现问题出在I/O过程(从磁盘读取数据,这是非常慢的)而不是训练过程。使用TFrecord格式可以通过并行化来加快速度,这使得模型的训练和开发更快。
在我开始之前,有一个重要的注意事项:虽然数据集中的所有歌曲都是MP3格式,但我将它们转换成wav文件,因为TensorFlow有更好的内置支持。请参考GitHub上的库以查看与此项目相关的所有代码。代码还假设您有一个Google云存储桶,其中所有wav文件都可用,一个上载元数据的Google驱动器,并且您正在使用googlecolab。尽管如此,将所有代码调整到另一个系统(基于云的或本地的)应该相对简单。
初始设置
这个项目需要大量的库。这个requirements.txt存储库中的文件为您处理安装,但您也可以找到下面的详细列表。
#importlibraries importpandasaspd importtensorflowastf fromIPython.displayimportAudio importos importmatplotlib.pyplotasplt importnumpyasnp importmath importsys fromdatetimeimportdatetime importpickle importlibrosa importast importscipy importlibrosa.display fromsklearn.model_selectionimporttrain_test_split fromsklearn.preprocessingimportLabelEncoder fromtensorflowimportkeras fromgoogle.colabimportfiles keras.backend.clear_session() tf.random.set_seed(42) np.random.seed(42)
第一步是挂载驱动器(数据已上传的位置),并使用存储音频文件的GCS存储桶进行身份验证。从技术上讲,数据也可以上传到GCS,这样就不需要安装驱动器了,但我自己的项目就是这样构建的。
#mountthedrive #adaptedfromhttps://colab.sandbox.google.com/notebooks/io.ipynb#scrollTo=S7c8WYyQdh5i fromgoogle.colabimportdrive drive.mount('/content/drive') #loadthemetadatatoColabfromDrive,willgreatlyspeeduptheI/Oprocess zip_path_metadata="/content/drive/MyDrive/master_degree/machine_learning/Project/fma_metadata.zip" !cp"{zip_path_metadata}". !unzip-qfma_metadata.zip !rmfma_metadata.zip #authenticateforGCSaccess if'google.colab'insys.modules: fromgoogle.colabimportauth auth.authenticate_user()
我们还存储了一些变量以备将来使用,例如。
#setsomevariablesforcreatingthedataset AUTO=tf.data.experimental.AUTOTUNE#usedintf.data.DatasetAPI GCS_PATTERN='gs://music-genre-classification-project-isye6740/fma_small_wav/*/*.wav' GCS_OUTPUT_1D='gs://music-genre-classification-project-isye6740/tfrecords-wav-1D/songs'#prefixforoutputfilenames,firsttypeofmodel GCS_OUTPUT_2D='gs://music-genre-classification-project-isye6740/tfrecords-wav-2D/songs'#prefixforoutputfilenames,secondtypeofmodel GCS_OUTPUT_FEATURES='gs://music-genre-classification-project-isye6740/tfrecords-features/songs'#prefixforoutputfilenames,modelsbuiltwithextractedfeatures SHARDS=16 window_size=10000#numberofrawaudiosamples length_size_2d=50176#numberofdatapointstoformtheMelspectrogram feature_size=85210#sizeofthefeaturevector N_CLASSES=8 DATA_SIZE=(224,224,3)#requireddatasizefortransferlearning
创建TensorFlow数据集
下一步就是设置函数读入数据时所需的必要信息。我没有写这段代码,只是把它改编自FreeMusicArchive。这一部分很可能在您自己的项目中发生变化,这取决于您使用的数据集。
#functiontoloadmetadata #adaptedfromhttps://github.com/mdeff/fma/blob/master/utils.py defmetadata_load(filepath): filename=os.path.basename(filepath) if'features'infilename: returnpd.read_csv(filepath,index_col=0,header=[0,1,2]) if'echonest'infilename: returnpd.read_csv(filepath,index_col=0,header=[0,1,2]) if'genres'infilename: returnpd.read_csv(filepath,index_col=0) if'tracks'infilename: tracks=pd.read_csv(filepath,index_col=0,header=[0,1]) COLUMNS=[('track','tags'),('album','tags'),('artist','tags'), ('track','genres'),('track','genres_all')] forcolumninCOLUMNS: tracks[column]=tracks[column].map(ast.literal_eval) COLUMNS=[('track','date_created'),('track','date_recorded'), ('album','date_created'),('album','date_released'), ('artist','date_created'),('artist','active_year_begin'), ('artist','active_year_end')] forcolumninCOLUMNS: tracks[column]=pd.to_datetime(tracks[column]) SUBSETS=('small','medium','large') try: tracks['set','subset']=tracks['set','subset'].astype( pd.CategoricalDtype(categories=SUBSETS,ordered=True)) exceptValueError: #thecategoriesandorderedargumentswereremovedinpandas0.25 tracks['set','subset']=tracks['set','subset'].astype( pd.CategoricalDtype(categories=SUBSETS,ordered=True)) COLUMNS=[('track','genre_top'),('track','license'), ('album','type'),('album','information'), ('artist','bio')] forcolumninCOLUMNS: tracks[column]=tracks[column].astype('category') returntracks #functiontogetgenreinformationforeachtrackID deftrack_genre_information(GENRE_PATH,TRACKS_PATH,subset): """ GENRE_PATH(str):pathtothecsvwiththegenremetadata TRACKS_PATH(str):pathtothecsvwiththetrackmetadata FILE_PATHS(list):listofpathstothemp3files subset(str):thesubsetofthedatadesired """ #getthegenreinformation genres=pd.read_csv(GENRE_PATH) #loadmetadataonallthetracks tracks=metadata_load(TRACKS_PATH) #focusonthespecificsubsettracks subset_tracks=tracks[tracks['set','subset']<=subset] #extracttrackIDandgenreinformationforeachtrack subset_tracks_genre=np.array([np.array(subset_tracks.index), np.array(subset_tracks['track','genre_top'])]).T #combinetheinformationinadataframe tracks_genre_df=pd.DataFrame({'track_id':subset_tracks_genre[:,0],'genre':subset_tracks_genre[:,1]}) #labelclasseswithnumbers encoder=LabelEncoder() tracks_genre_df['genre_nb']=encoder.fit_transform(tracks_genre_df.genre) returntracks_genre_df #getgenreinformationforalltracksfromthesmallsubset GENRE_PATH="fma_metadata/genres.csv" TRACKS_PATH="fma_metadata/tracks.csv" subset='small' small_tracks_genre=track_genre_information(GENRE_PATH,TRACKS_PATH,subset)
然后我们需要函数来创建一个TensorFlow数据集。其思想是在文件名列表上循环,在管道中应用一系列操作,这些操作返回批处理数据集,其中包含一个特征张量和一个标签张量。我们使用TensorFlow内置函数和Python函数(与tf.py_函数,对于在数据管道中使用Python函数非常有用)。这里我只包含从原始音频数据创建数据集的函数,但过程与以频谱图作为特性创建数据集的过程极为相似。
#checkthenumberofsongswhicharestoredinGCS nb_songs=len(tf.io.gfile.glob(GCS_PATTERN)) shard_size=math.ceil(1.0*nb_songs/SHARDS) print("Patternmatches{}songswhichwillberewrittenas{}.tfrecfilescontaining{}songseach.".format(nb_songs,SHARDS,shard_size)) #functionstocreatethedatasetfromrawaudio #defineafunctiontogetthelabelassociatedwithafilepath defget_label(file_path,genre_df=small_tracks_genre): path=file_path.numpy() path=path.decode("utf-8") track_id=int(path.split('/')[-1].split('.')[0].lstrip('0')) label=genre_df.loc[genre_df.track_id==track_id,'genre_nb'].values[0] returntf.constant([label]) #defineafunctionthatextractsthedesiredfeaturesfromafilepath defget_audio(file_path,window_size=window_size): wav=tf.io.read_file(file_path) audio=tf.audio.decode_wav(wav,desired_channels=1).audio filtered_audio=audio[:window_size,:] returnfiltered_audio #processthepath defprocess_path(file_path,window_size=window_size): label=get_label(file_path) audio=get_audio(file_path,window_size) returnaudio,label #parser,wraparoundtheprocessingfunctionandspecifyoutputshape defparser(file_path,window_size=window_size): audio,label=tf.py_function(process_path,[file_path],(tf.float32,tf.int32)) audio.set_shape((window_size,1)) label.set_shape((1,)) returnaudio,label filenames=tf.data.Dataset.list_files(GCS_PATTERN,seed=35155)#Thisalsoshufflestheimages dataset_1d=filenames.map(parser,num_parallel_calls=AUTO) dataset_1d=dataset_1d.batch(shard_size)
在GCS上使用TFRecord格式
现在我们有了数据集,我们使用TFRecord格式将其存储在GCS上。这是GPU和TPU推荐使用的格式,因为并行化带来了快速的I/O。其主要思想是tf.Features和tf.Example.我们将数据集写入这些示例,存储在GCS上。这部分代码应该需要对其他项目进行最少的编辑,除了更改特性类型之外。如果数据已经上传到记录格式一次,则可以跳过此部分。本节中的大部分代码都改编自TensorFlow官方文档以及本教程中有关音频管道的内容。
#writetoTFRecord #needtoTFRecordtogreatlyspeeduptheI/Oprocess,previouslyabottleneck #functionstocreatevariousfeatures #adaptedfromhttps://codelabs.developers.google.com/codelabs/keras-flowers-data/#4 #andhttps://www.tensorflow.org/tutorials/load_data/tfrecord def_bytestring_feature(list_of_bytestrings): returntf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings)) def_int_feature(list_of_ints):#int64 returntf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints)) def_float_feature(list_of_floats):#float32 returntf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats)) #writerfunction defto_tfrecord(tfrec_filewriter,song,label): one_hot_class=np.eye(N_CLASSES)[label][0] feature={ "song":_float_feature(song.flatten().tolist()),#onesonginthelist "class":_int_feature([label]),#oneclassinthelist "one_hot_class":_float_feature(one_hot_class.tolist())#variablelengthlistoffloats,n=len(CLASSES) } returntf.train.Example(features=tf.train.Features(feature=feature)) defwrite_tfrecord(dataset,GCS_OUTPUT): print("WritingTFRecords") forshard,(song,label)inenumerate(dataset): #batchsizeusedasshardsizehere shard_size=song.numpy().shape[0] #goodpracticetohavethenumberofrecordsinthefilename filename=GCS_OUTPUT+"{:02d}-{}.tfrec".format(shard,shard_size) withtf.io.TFRecordWriter(filename)asout_file: foriinrange(shard_size): example=to_tfrecord(out_file, song.numpy()[i], label.numpy()[i]) out_file.write(example.SerializeToString()) print("Wrotefile{}containing{}records".format(filename,shard_size))s
一旦这些记录被存储,我们需要其他函数来读取它们。依次处理每个示例,从TFRecord中提取相关信息并重新构造tf.数据集.这看起来像是一个循环过程(创建一个tf.数据集→作为TFRecord上传到GCS→将TFRecord读入tf.数据集),但这实际上通过简化I/O过程提供了巨大的速度效率。如果I/O是瓶颈,使用GPU或TPU是没有帮助的,这种方法允许我们通过优化数据加载来充分利用它们在训练期间的速度增益。
#functiontoparseanexampleandreturnthesongfeatureandtheone-hotclass #adaptedfromhttps://codelabs.developers.google.com/codelabs/keras-flowers-data/#4 #andhttps://www.tensorflow.org/tutorials/load_data/tfrecord defread_tfrecord_1d(example): features={ "song":tf.io.FixedLenFeature([window_size],tf.float32),#tf.stringmeansbytestring "class":tf.io.FixedLenFeature([1],tf.int64),#shape[]meansscalar "one_hot_class":tf.io.VarLenFeature(tf.float32), } example=tf.io.parse_single_example(example,features) song=example['song'] #song=tf.audio.decode_wav(example['song'],desired_channels=1).audio song=tf.cast(example['song'],tf.float32) song=tf.reshape(song,[window_size,1]) label=tf.reshape(example['class'],[1]) one_hot_class=tf.sparse.to_dense(example['one_hot_class']) one_hot_class=tf.reshape(one_hot_class,[N_CLASSES]) returnsong,one_hot_class #functiontoloadthedatasetfromTFRecords defload_dataset_1d(filenames): #readfromTFRecords.Foroptimalperformance,readfrommultiple #TFRecordfilesatonceandsettheoptionexperimental_deterministic=False #toalloworder-alteringoptimizations. option_no_order=tf.data.Options() option_no_order.experimental_deterministic=False dataset=tf.data.TFRecordDataset(filenames,num_parallel_reads=AUTO) dataset=dataset.with_options(option_no_order) dataset=dataset.map(read_tfrecord_1d,num_parallel_calls=AUTO) #ignorepotentiallycorruptedrecords dataset=dataset.apply(tf.data.experimental.ignore_errors()) returndataset
准备训练、验证和测试集
重要的是,将数据适当地分割成训练验证测试集(64%-16%-20%),前两个测试集用于优化模型体系结构,后者用于评估模型性能。拆分发生在文件名级别。
#functiontocreatetraining,validationandtestingsets #adaptedfromhttps://colab.sandbox.google.com/notebooks/tpu.ipynb #andhttps://codelabs.developers.google.com/codelabs/keras-flowers-data/#4 defcreate_train_validation_testing_sets(TFREC_PATTERN, VALIDATION_SPLIT=0.2, TESTING_SPLIT=0.2): """ TFREC_PATTERN:stringpatternfortheTFRECbucketonGCS """ #seewhichacceleratorisavailable try:#detectTPUs tpu=None tpu=tf.distribute.cluster_resolver.TPUClusterResolver()#TPUdetection tf.config.experimental_connect_to_cluster(tpu) tf.tpu.experimental.initialize_tpu_system(tpu) strategy=tf.distribute.experimental.TPUStrategy(tpu) exceptValueError:#detectGPUs strategy=tf.distribute.MirroredStrategy()#forGPUormulti-GPUmachines print("Numberofaccelerators:",strategy.num_replicas_in_sync) #Configuration #adaptedfromhttps://codelabs.developers.google.com/codelabs/keras-flowers-data/#4 iftpu: BATCH_SIZE=16*strategy.num_replicas_in_sync#ATPUhas8coressothiswillbe128 else: BATCH_SIZE=32#OnColab/GPU,ahigherbatchsizedoesnothelpandsometimesdoesnotfitontheGPU(OOM) #splittingdatafilesbetweentrainingandvalidation filenames=tf.io.gfile.glob(TFREC_PATTERN) testing_split=int(len(filenames)*TESTING_SPLIT) training_filenames=filenames[testing_split:] testing_filenames=filenames[:testing_split] validation_split=int(len(filenames)*VALIDATION_SPLIT) validation_filenames=training_filenames[:validation_split] training_filenames=training_filenames[validation_split:] validation_steps=int(3670//len(filenames)*len(validation_filenames))//BATCH_SIZE steps_per_epoch=int(3670//len(filenames)*len(training_filenames))//BATCH_SIZE returntpu,BATCH_SIZE,strategy,training_filenames,validation_filenames,testing_filenames,steps_per_epoch #getthebatcheddataset,optimizingforI/Operformance #followbestpracticeforshufflingandrepeatingdata defget_batched_dataset(filenames,load_func,train=False): """ filenames:filenamestoload load_func:specificloadingfunctiontouse train:Boolean,whetherthisisatrainingset """ dataset=load_func(filenames) dataset=dataset.cache()#ThisdatasetfitsinRAM iftrain: #BestpracticesforKeras: #Trainingdataset:repeatthenbatch #Evaluationdataset:donotrepeat dataset=dataset.repeat() dataset=dataset.batch(BATCH_SIZE) dataset=dataset.prefetch(AUTO)#prefetchnextbatchwhiletraining(autotuneprefetchbuffersize) #shouldshuffletoobutthisdatasetwaswellshuffledondiskalready returndataset #source:Datasetperformanceguide:https://www.tensorflow.org/guide/performance/datasets #instantiatethedatasets training_dataset_1d=get_batched_dataset(training_filenames_1d,load_dataset_1d, train=True) validation_dataset_1d=get_batched_dataset(validation_filenames_1d,load_dataset_1d, train=False) testing_dataset_1d=get_batched_dataset(testing_filenames_1d,load_dataset_1d, train=False)
模型和训练
最后,我们可以使用kerasapi来构建和测试模型。网上有大量关于如何使用Keras构建模型的信息,所以我不会深入讨论细节,但是这里是使用1D卷积层与池层相结合来从原始音频中提取特征。
#createaCNNmodel withstrategy.scope(): #createthemodel model=tf.keras.Sequential([ tf.keras.layers.Conv1D(filters=128, kernel_size=3, activation='relu', input_shape=[window_size,1], name='conv1'), tf.keras.layers.MaxPooling1D(name='max1'), tf.keras.layers.Conv1D(filters=64, kernel_size=3, activation='relu', name='conv2'), tf.keras.layers.MaxPooling1D(name='max2'), tf.keras.layers.Flatten(name='flatten'), tf.keras.layers.Dense(100,activation='relu',name='dense1'), tf.keras.layers.Dropout(0.5,name='dropout2'), tf.keras.layers.Dense(20,activation='relu',name='dense2'), tf.keras.layers.Dropout(0.5,name='dropout3'), tf.keras.layers.Dense(8,name='dense3') ]) #compile model.compile(optimizer='adam', loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True), metrics=['accuracy']) model.summary() #trainthemodel logdir="logs/scalars/"+datetime.now().strftime("%Y%m%d-%H%M%S") tensorboard_callback=keras.callbacks.TensorBoard(log_dir=logdir) EPOCHS=100 raw_audio_history=model.fit(training_dataset_1d,steps_per_epoch=steps_per_epoch, validation_data=validation_dataset_1d,epochs=EPOCHS, callbacks=tensorboard_callback) #evaluateonthetestdata model.evaluate(testing_dataset_1d)
最后一点相关信息是关于使用TensorBoard绘制训练和验证曲线。
%load_exttensorboard %tensorboard--logdirlogs/scalars
总结
总之,对同一个机器学习任务进行不同机器学习方法的基准测试是很有启发性的。该项目强调了领域知识和特征工程的重要性,以及标准的、相对容易的机器学习技术(如naivebayes)的威力。过拟合是一个问题,因为与示例数量相比,特性的规模很大,但我相信未来的努力可以帮助缓解这个问题。
我很高兴地看到了在谱图上进行迁移学习的强大表现,并认为我们可以通过使用更多的音乐理论特征来做得更好。然而,如果有更多的数据可用于提取模式,原始音频的深度学习技术确实显示出希望。我们可以设想一个应用程序,其中分类可以直接发生在音频样本上,而不需要特征工程。
作者:CélestinHermez
本文代码https://github.com/celestinhermez/music-genre-classification
deephub翻译组
到此这篇关于使用tensorflow进行音乐类型的分类的文章就介绍到这了,更多相关tensorflow音乐类型的分类内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。