前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用人工智能探索音乐生成的世界

用人工智能探索音乐生成的世界

作者头像
磐创AI
发布2024-01-10 14:34:35
2970
发布2024-01-10 14:34:35
举报
文章被收录于专栏:磐创AI技术团队的专栏

介绍

利用人工智能生成音乐已经成为一个重要领域,这改变了音乐的创作和欣赏方式。本项目介绍了在音乐创作中应用人工智能的概念和目的。我们旨在探索使用人工智能算法生成音乐的过程以及其潜力。

我们的项目着重于理解和实施促进音乐创作的人工智能技术。人工智能可以通过从大量音乐作品中学习,使用特殊的数学规则来理解音乐中的模式、节奏和结构,然后根据所学内容创作新的音乐。通过在音乐数据上训练模型,我们使人工智能系统能够学习和产生新的原创作品。我们还将研究人工智能生成音乐的最新发展,特别是Meta的MusicGen。

通过探索人工智能在音乐生成中的应用范围,本项目的目标是激发音乐家、研究人员和音乐爱好者探索这项创新技术的可能性。让我们一起踏上这个音乐之旅,揭示人工智能可以创造出的旋律。

学习目标

通过参与这个项目,我们将获得新的技术技能,以及如何实施AI算法来构建创新应用的理解。在项目结束时,我们将会::

  1. 了解如何利用人工智能来创作音乐。我们将学习用于训练音乐创作AI模型的基本概念和技术。
  2. 了解如何收集和准备用于 AI 模型训练的相关音乐数据。我们将了解如何利用 Spotify 的 Basic Pitch 等工具收集 .mp3 文件并将其转换为 MIDI 文件。
  3. 我们还将了解构建音乐生成人工智能模型所涉及的步骤。此外,我们将了解适合此任务的模型架构及其相关性,并获得训练模型的实践经验,包括确定时期数和批量大小。
  4. 我们将花时间探索评估训练模型性能的方法。然后,我们将学习如何分析指标,并评估生成的音乐作品的质量,以衡量模型的效果,并确定改进的方向。
  5. 最后,我们将探索使用训练过的AI模型来生成新的音乐作品的过程

项目介绍

本项目的目的是探索利用人工智能生成音乐的有趣领域。我们旨在调查人工智能技术如何创作独特的音乐作品。通过利用机器学习算法,我们的目标是训练一个能够跨足多种音乐流派产生旋律和和声的AI模型。

该项目的重点是收集多样化的音乐数据,特别是.mp3文件,这些文件将作为训练AI模型的基础。这些文件将经过预处理,使用专门的工具如Spotify的Basic Pitch将其转换为MIDI格式。这种转换是必要的,因为MIDI文件提供了音乐元素的结构化表示,AI模型可以轻松解释。

随后的阶段涉及构建专为音乐生成量身定制的AI模型。使用准备好的MIDI数据训练模型,旨在捕捉音乐中存在的潜在模式和结构。

进行性能评估以评估模型的熟练程度。这将涉及生成音乐样本并评估其质量,以优化过程,增强模型生成创意音乐的能力。

本项目的最终结果将是使用经过训练的AI模型生成原创作品的能力。这些作品可以通过后处理技术进一步完善,以丰富其音乐性和连贯性。

问题陈述

本项目旨在解决音乐创作工具的有限可访问性问题。传统的音乐创作方法可能费时费力,需要专业知识。此外,生成新颖且独特的音乐概念可能具有挑战性。本项目的目标是通过使用人工智能来克服这些障碍,并为音乐创作提供无缝的解决方案,即使对于非音乐家也是如此。

通过开发能够创作旋律和和声的AI模型,项目旨在使音乐创作过程民主化,赋予音乐家、爱好者和新手释放创造潜力、轻松创作独特作品的能力。

使用人工智能生成音乐的简史

人工智能创作音乐的故事可以追溯到 20 世纪 50 年代,最早的计算机辅助创作的作品是《Illiac Suite for String Quartet》。然而,直到最近几年,人工智能在这个领域才真正开始发光。

如今,人工智能可以创作各种类型的音乐,从古典到流行,甚至可以模仿著名音乐家的风格。

当前,人工智能在音乐创作方面的状态非常先进。近期,Meta推出了一款名为MusicGen的新型AI音乐创作工具。MusicGen基于强大的Transformer模型,可以以类似于语言模型猜测下一个句子中的字母的方式猜测并生成音乐片段。它使用名为EnCodec的音频分词器将音频数据拆分为较小的部分,以便于处理。

MusicGen:https://huggingface.co/spaces/facebook/MusicGen

MusicGen的一个特殊功能是它能够同时处理文本描述和音乐提示,从而实现艺术表达的顺畅融合。使用大量的20000小时允许使用的音乐数据集,确保其具备创建与听众产生共鸣的曲调的能力。此外,像OpenAI这样的公司推出了像MuseNet和Jukin Media的Jukin Composer等AI模型,可以以多种风格和类型创作音乐。此外,现在AI可以生成与人类创作的音乐几乎相同的音乐,使其成为音乐世界中的强大工具。

伦理考虑

在探索这个领域时,讨论AI生成的音乐的伦理方面是至关重要的。一个相关的关注领域涉及潜在的版权和知识产权侵权问题。训练AI模型使用大量的音乐数据集,可能会导致生成的作品与现有作品相似。尊重版权法律并适当归属原创艺术家是维护公平实践的重要举措。

此外,AI生成的音乐的出现可能会扰乱音乐产业,对寻求在充斥着AI创作的环境中获得认可的音乐家构成挑战。在利用AI作为创作工具和保护人类音乐家的艺术个性之间取得平衡是一项重要的考虑。

数据收集和准备

为了完成这个项目,我们将尝试使用AI生成一些原创的器乐音乐。个人而言,我是YouTube上著名的器乐音乐频道(如Fluidified、MusicLabChill和FilFar)的忠实粉丝,这些频道有各种心情的优秀音轨。受到这些频道的启发,我们将尝试以类似的方式生成音乐,最终将其分享到YouTube上。

为了收集所需的数据,我们致力于获取与我们所期望的音乐风格相符的相关.mp3文件。通过广泛探索在线平台和网站,我们发现了合法且免费提供的器乐音乐曲目。这些曲目为我们的数据集提供了宝贵的资产,涵盖了各种各样的旋律和和声,以丰富我们模型的训练过程。

一旦我们成功地获取了所需的.mp3文件,我们就会将它们转换成MIDI文件。MIDI文件以数字格式表示音乐作品,使我们的模型能够高效地进行分析和生成。为了进行这种转换,我们依赖于Spotify的Basic Pitch(https://basicpitch.spotify.com/)提供的实用和用户友好的功能。

在Spotify的Basic Pitch的帮助下,我们上传所获取的.mp3文件,启动转换过程。该工具利用先进的算法来解析音频内容,提取关键的音乐元素,如音符和结构,以生成相应的MIDI文件。这些MIDI文件是我们音乐生成模型的基石,使我们能够操纵并产生新颖的创意作品。

模型架构

为了开发我们的音乐生成模型,我们使用了专门为此目的设计的架构。所选择的架构包括两个LSTM(长短时记忆)层,每个层都由256个单元组成。LSTM是一种递归神经网络(RNN)类型,擅长处理序列数据,使其成为生成具有内在时间特性的音乐的绝佳选择。

第一个LSTM层处理长度为100的固定长度的输入序列,由sequence_length变量确定。通过返回序列,该层有效地保留了音乐数据中存在的时间关系。为了防止过拟合并提高模型对新数据的适应能力,加入了一个丢失率为0.3的丢失层。

第二个LSTM层不返回序列,接收来自前一层的输出,并进一步学习音乐中的复杂模式。最后,使用softmax激活函数的密集层生成下一个音符的输出概率。

构建模型

在建立了我们的模型架构之后,让我们直接开始构建模型。我们将把代码分解成几个部分,并为读者解释每个部分。

我们首先导入必要的库,这些库提供了项目中有用的功能。除了常规操作所需的常规库外,我们还将使用tensorflow进行深度学习,以及使用music21进行音乐处理。

代码语言:javascript
复制
import numpy as np
import os
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dropout, Dense
from tensorflow.keras.utils import to_categorical
from music21 import converter, instrument, stream, note, chord
from google.colab import files
加载和处理 MIDI 文件

接下来,我们定义 MIDI 文件所在的目录。然后,代码会遍历目录中的每个文件,提取音符和和弦,并将其存储以供进一步处理。music21 库中的“converter”模块用于解析 MIDI 文件并检索音乐元素。

作为实验,我们将首先仅使用一个 MIDI 文件来训练模型,然后使用五个 MIDI 文件进行训练来比较结果。

代码语言:javascript
复制
# Directory containing the MIDI files
midi_dir = "/content/Midi Files"

notes = []

# Process each MIDI file in the directory
for filename in os.listdir(midi_dir):
    if filename.endswith(".midi"):
        file = converter.parse(os.path.join(midi_dir, filename))

        # Find all the notes and chords in the MIDI file
        try:
            # If the MIDI file has instrument parts
            s2 = file.parts.stream()
            notes_to_parse = s2[0].recurse()
        except:
            # If the MIDI file only has notes (
            # no chords or instrument parts)
            notes_to_parse = file.flat.notes

        # Extract pitch and duration information from notes and chords
        for element in notes_to_parse:
            if isinstance(element, note.Note):
                notes.append(str(element.pitch))
            elif isinstance(element, chord.Chord):
                notes.append('.'.join(str(n) for n in 
                element.normalOrder))

# Print the number of notes and some example notes
print("Total notes:", len(notes))
print("Example notes:", notes[:10])
将音符映射到整数

为了将音符转换为我们的模型可以处理的数字序列,我们创建一个字典,将每个独特的音符或和弦映射到相应的整数。这一步骤使我们能够以数字格式表示音乐元素。

代码语言:javascript
复制
# Create a dictionary to map unique notes to integers
unique_notes = sorted(set(notes))
note_to_int = {note: i for i, note in 
enumerate(unique_notes)}
生成输入和输出序列

为了训练我们的模型,我们需要创建输入和输出序列。这通过在音符列表上滑动一个固定长度的窗口来实现。输入序列由前面的音符组成,输出序列是下一个音符。这些序列被存储在不同的列表中。

代码语言:javascript
复制
# Convert the notes to numerical sequences
sequence_length = 100  # Length of each input sequence
input_sequences = []
output_sequences = []

# Generate input/output sequences
for i in range(0, len(notes) - sequence_length, 1):
    # Extract the input sequence
    input_sequence = notes[i:i + sequence_length]
    input_sequences.append([note_to_int[note] for 
    note in input_sequence])

    # Extract the output sequence
    output_sequence = notes[i + sequence_length]
    output_sequences.append(note_to_int[output_sequence])
重塑和规范化输入序列

在将输入序列馈送到我们的模型之前,我们将它们重新塑造以匹配LSTM层的预期输入形状。此外,我们通过将它们除以唯一音符的总数来对序列进行标准化。这一步骤确保输入值落在模型有效学习的合适范围内。

代码语言:javascript
复制
# Reshape and normalize the input sequences
num_sequences = len(input_sequences)
num_unique_notes = len(unique_notes)

# Reshape the input sequences
X = np.reshape(input_sequences, (num_sequences, sequence_length, 1))
# Normalize the input sequences
X = X / float(num_unique_notes)
One-Hot 编码输出序列

表示下一个要预测的音符的输出序列将被转换为 One-Hot 编码格式。这种编码使得模型能够理解下一个音符在可用音符中的概率分布。

代码语言:javascript
复制
# One-hot encode the output sequences
y = to_categorical(output_sequences)
定义 RNN 模型

我们使用 tensorflow.keras.models 模块中的 Sequential 类来定义我们的RNN(循环神经网络)模型。该模型由两个LSTM(长短时记忆)层组成,后面跟着一个用于防止过拟合的dropout层。最后一层是一个带有softmax激活函数的Dense层,用于输出每个音符的概率。

代码语言:javascript
复制
# Define the RNN model
model = Sequential()
model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2]), 
return_sequences=True))
model.add(Dropout(0.3))
model.add(LSTM(256))
model.add(Dense(y.shape[1], activation='softmax'))
编译和训练模型

我们通过指定损失函数和优化器来编译模型。然后,我们继续在输入序列 (X) 和输出序列 (y) 上针对特定数量的 epoch 和给定的批量大小训练模型。

代码语言:javascript
复制
# Compile the model
model.compile(loss='categorical_crossentropy', optimizer='adam')

# Step 4: Train the model
model.fit(X, y, batch_size=64, epochs=100)

音乐生成

一旦我们训练好了模型,就可以生成新的音乐序列。我们定义一个名为generate_music的函数,该函数接受三个输入:训练好的模型、种子序列(seed_sequence)和长度(length)。它使用模型根据先前的音符预测下一个音符,并重复这个过程来生成所需长度的音乐。

首先,我们创建种子序列的副本,以防止对原始序列进行任何修改。这个种子序列作为生成音乐的起始点。

然后,我们进入一个循环,循环运行length次。在每次迭代中,执行以下步骤:

  1. 将generated_sequence转换为numpy数组。
  2. 通过添加一个额外的维度来reshape输入序列,以匹配模型预期的输入形状。
  3. 通过将输入序列除以唯一音符的总数来对输入序列进行标准化。这确保了值在模型有效工作的合适范围内。

在对输入序列进行标准化之后,使用模型预测下一个音符的概率。model.predict方法以输入序列为输入,并返回预测的概率。

为了选择下一个音符,使用np.random.choice函数,该函数基于获得的概率随机选择一个索引。这种随机性将多样性和不可预测性引入生成的音乐中。

所选的索引代表新的音符,将其附加到generated_sequence中。然后,通过删除第一个元素来更新generated_sequence,以保持所需的长度。循环完成后,返回generated_sequence,表示新生成的音乐。

需要设置seed_sequence和desired generated_length来生成音乐。seed_sequence应该是模型已经训练过的有效输入序列,而generated_length决定了生成的音乐应该包含的音符数量。

代码语言:javascript
复制
# Generate new music
def generate_music(model, seed_sequence, length):
    generated_sequence = seed_sequence.copy()

    for _ in range(length):
        input_sequence = np.array(generated_sequence)
        input_sequence = np.reshape(input_sequence, (1, len(input_sequence), 1))
        input_sequence = input_sequence / float(num_unique_notes)  # Normalize input sequence

        predictions = model.predict(input_sequence)[0]
        new_note = np.random.choice(range(len(predictions)), p=predictions)
        generated_sequence.append(new_note)
        generated_sequence = generated_sequence[1:]

    return generated_sequence

# Set the seed sequence and length of the generated music
seed_sequence = input_sequences[0]   # Replace with your own seed sequence
generated_length = 100  # Replace with the desired length of the generated music

generated_music = generate_music(model, seed_sequence, generated_length)
generated_music
# Output of the above code
[1928,
 1916,
 1959,
 1964,
 1948,
 1928,
 1190,
 873,
 1965,
 1946,
 1928,
 1970,
 1947,
 1946,
 1964,
 1948,
 1022,
 1945,
 1916,
 1653,
 873,
 873,
 1960,
 1946,
 1959,
 1942,
 1348,
 1960,
 1961,
 1971,
 1966,
 1927,
 705,
 1054,
 150,
 1935,
 864,
 1932,
 1936,
 1763,
 1978,
 1949,
 1946,
 351,
 1926,
 357,
 363,
 864,
 1965,
 357,
 1928,
 1949,
 351,
 1928,
 1949,
 1662,
 1352,
 1034,
 1021,
 977,
 150,
 325,
 1916,
 1960,
 363,
 943,
 1949,
 553,
 1917,
 1962,
 1917,
 1916,
 1947,
 1021,
 1021,
 1051,
 1648,
 873,
 977,
 1959,
 1927,
 1959,
 1947,
 434,
 1949,
 553,
 360,
 1916,
 1190,
 1022,
 1348,
 1051,
 325,
 1965,
 1051,
 1917,
 1917,
 407,
 1948,
 1051]

后期处理

如上所示,生成的输出是一个表示生成音乐中音符或和弦的整数序列。为了听到生成的输出,我们需要通过反向映射将其转换回音乐,以获得原始的音符/和弦。为此,首先我们将创建一个名为int_to_note的字典,其中整数是键,对应的音符是值。

接下来,我们创建一个名为output_stream的流,用于存储生成的音符和和弦。这个流充当一个容器,用于保存将构成生成音乐的音乐元素。

然后,我们遍历生成的音乐序列中的每个元素。每个元素都是一个表示音符或和弦的数字。我们使用int_to_note字典将数字转换回其原始的音符或和弦字符串表示。

如果模式是一个和弦,可以通过存在点号或是数字来识别,我们将模式字符串拆分成单个音符。对于每个音符,我们创建一个note.Note对象,为其分配一个钢琴乐器,并将其添加到音符列表中。最后,我们从音符列表中创建一个chord.Chord对象,表示和弦,并将其附加到output_stream中。

如果模式是单个音符,我们为该音符创建一个note.Note对象,为其分配一个钢琴乐器,并将其直接添加到output_stream中。

一旦生成音乐序列中的所有模式都被处理完毕,我们将output_stream写入名为'generated_music.mid'的MIDI文件中。最后,我们使用files.download函数从Colab下载生成的音乐文件。

代码语言:javascript
复制
# Reverse the mapping from notes to integers
int_to_note = {i: note for note, i in note_to_int.items()}

# Create a stream to hold the generated notes/chords
output_stream = stream.Stream()

# Convert the output from the model into notes/chords
for pattern in generated_music:
    # pattern is a number, so we convert it back to a note/chord string
    pattern = int_to_note[pattern]

    # If the pattern is a chord
    if ('.' in pattern) or pattern.isdigit():
        notes_in_chord = pattern.split('.')
        notes = []
        for current_note in notes_in_chord:
            new_note = note.Note(int(current_note))
            new_note.storedInstrument = instrument.Piano()
            notes.append(new_note)
        new_chord = chord.Chord(notes)
        output_stream.append(new_chord)
    # If the pattern is a note
    else:
        new_note = note.Note(pattern)
        new_note.storedInstrument = instrument.Piano()
        output_stream.append(new_note)

# Write the stream to a MIDI file
output_stream.write('midi', fp='generated_music.mid')

# Download the generated music file from Colab
files.download('generated_music.mid')

最终输出

现在,是时候聆听人工智能生成的音乐的结果了。你可以在下面找到聆听音乐的链接。

https://youtu.be/FIbXnoK-OOs

老实说,最初的结果听起来可能像是一个演奏乐器经验有限的人。这主要是因为我们仅使用单个 MIDI 文件来训练模型。然而,我们可以通过重复这个过程并在更大的数据集上训练我们的模型来提高音乐的质量。在本例中,我们将使用五个 MIDI 文件来训练我们的模型,所有这些文件都是风格相似的器乐。

https://youtu.be/lVVbPAkfVSY

通过扩展数据集生成的音乐质量差异非常显著。这清楚地表明,将模型训练在更多样化的MIDI文件范围上会显著改善生成的音乐。这强调了增加训练数据集的规模和多样性对于实现更好的音乐结果的重要性。

局限性

尽管我们成功使用复杂的模型生成了音乐,但是扩展这样的系统存在一定的限制。

  1. 有限数据集:生成音乐的质量和多样性取决于用于训练的数据集的种类和大小。有限的数据集可能会限制我们的模型可以学习的音乐思想和风格的范围。
  2. 创造力差距:尽管AI生成的音乐可以产生令人印象深刻的结果,但它缺乏人类作曲家在作品中展现的内在创造力和情感深度。由AI生成的音乐可能听起来呆板,或者会错过使音乐真正引人入胜的微妙细微差别。
  3. 数据依赖性:生成的音乐受到用于训练的输入MIDI文件的影响。如果训练数据集存在偏见或特定模式,生成的音乐可能会呈现类似的偏见或模式,从而限制其独创性。
  4. 计算要求:使用人工智能模型训练和生成音乐可能在计算上既昂贵又耗时。它需要强大的硬件和高效的算法来训练复杂的模型并在合理的时间范围内生成音乐。
  5. 主观评估:评估人工智能生成的音乐的质量和艺术价值可能是主观的。不同的人对音乐的美学和情感影响可能有不同的看法,这使得建立普遍的评价标准具有挑战性。

结论

在这个项目中,我们踏上了使用人工智能生成音乐的迷人之旅。我们的目标是探索人工智能在音乐创作中的能力,并释放其在创造独特音乐作品方面的潜力。通过实施人工智能模型和深度学习技术,我们成功地生成了与输入MIDI文件风格密切相似的音乐。该项目展示了人工智能在音乐创作的创造过程中辅助和启发的能力。

要点

以下是该项目的一些关键要点:

  1. 我们了解到人工智能可以在创作过程中充当有价值的助手,为音乐家和作曲家提供新的视角和创意。
  2. 训练数据集的质量和多样性极大地影响AI生成的音乐输出。策划一个多样化和全面的数据集对于实现更原创和多样化的作品至关重要。
  3. 尽管AI生成的音乐显示出潜力,但它无法取代人类作曲家带来的艺术和情感深度。最佳方法是将AI作为一种协作工具,来补充人类的创造力。
  4. 探索AI生成的音乐引发了重要的道德考虑,如版权和知识产权。尊重这些权利,并为AI和人类艺术家营造健康支持的环境是至关重要的。
  5. 这个项目强化了在AI生成的音乐领域持续学习的重要性。随着技术的进步和新技术的应用,我们能够推动音乐表达和创新的界限。

经常问的问题

Q1. AI如何创作音乐?

答:AI通过理解大量音乐数据中的模式和结构来创作音乐。它学习音符、和弦和节奏之间的关系,并将这种理解应用于生成新的旋律、和声和节奏。

Q2. AI能否创作出多种风格的音乐?

答:是的,AI可以创作广泛的音乐风格。通过训练AI模型使用不同风格的音乐,它可以学习每种风格的独特特点和元素。这使其能够生成捕捉古典、爵士、摇滚或电子等各种风格精髓的音乐。

Q3. 版权是否保护人工智能生成的音乐?

答:AI生成的音乐可能涉及版权复杂性。虽然AI算法创作音乐,但输入数据通常包含受版权保护的材料。对于AI生成的音乐的法律保护和所有权取决于司法管辖区和具体情况。在使用或共享AI生成的音乐时,正确的归属和版权法律知识非常重要。

Q4. 可以在商业项目中使用人工智能音乐吗?

答:是的,可以在商业项目中使用AI创作的音乐,但需要考虑版权方面的问题。某些AI模型是基于受版权保护的音乐进行训练的,这可能需要获取适当的商业使用许可或权限。建议咨询法律专家或版权专家,以确保遵守版权法律。

Q5. 人工智能创造的音乐可以替代人类音乐家吗?

答:AI创作的音乐不能完全替代人类音乐家。尽管AI可以创作出令人印象深刻的音乐,但它缺乏人类音乐家所具备的情感深度、创造力和解释技巧。AI作为一种有价值的灵感和协作工具,但无法复制人类音乐家独特的艺术和表达。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 磐创AI 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 学习目标
  • 项目介绍
  • 问题陈述
  • 使用人工智能生成音乐的简史
  • 伦理考虑
  • 数据收集和准备
  • 模型架构
  • 构建模型
    • 加载和处理 MIDI 文件
      • 将音符映射到整数
        • 生成输入和输出序列
          • 重塑和规范化输入序列
            • One-Hot 编码输出序列
              • 定义 RNN 模型
                • 编译和训练模型
                • 音乐生成
                • 后期处理
                • 最终输出
                • 局限性
                • 结论
                  • 要点
                  • 经常问的问题
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档