diff --git a/Lab 1/README-ko.md b/Lab 1/README-ko.md new file mode 100644 index 0000000..85b53b4 --- /dev/null +++ b/Lab 1/README-ko.md @@ -0,0 +1,79 @@ +# Lab 1 - DeepComposer 모델로 음악 작곡하기 + +## 전제 조건: (계속 진행하기 전에 숙지하세요) +1. 본 Lab에서는 Chrome 브라우저를 사용합니다. +2. AWS DeepComposer 키보드를 연결합니다. + +## AWS DeepComposer 콘솔 접근: + +시작하려면 Deepcomposer 링크를 클릭하세요: https://us-east-1.console.aws.amazon.com/deepcomposer + +![access-console](images/lab1-access-console.png) + +AWS account ID, IAM Username와 Password를 입력하고 Sign In 버튼을 클릭해 주세요. + +**참고 - N.Virginia(us-east-1) AWS 리전의 콘솔에 액세스해야 합니다.** 드롭 다운을 사용하여 올바른 리전을 선택할 수 있습니다. +![aws-region](images/aws-region.jpg) + +## 시작하기: +![models](images/models.jpg) + +왼쪽 네비게이션 메뉴에서 **Models**을 선택하세요. + +![create-models](images/create-models.jpg) + +여러분의 계정에서 모델을 생성하려면 ***Create a model**을 클릭하세요. + +AWS DeepComposer는 몇 가지 사전 학습된 모델들을 제공합니다. 이를 사용하려면 이러한 모델들을 여러분의 계정에 복제해야 합니다. + +![clone-models](images/clone-models.jpg) + +**Clone a pre-trained model**를 선택하여 모델을 계정에 복제합니다. + +목록에서 원하는 모델을 선택하세요. + +모델의 이름을 입력해 주세오. 모델 리스트에 모델이 여러분이 입력하신 이름으로 나타날 것입니다. + +![create-model-button](images/create-model-button.jpg) + +Click **Create model** to create the model in your account + +![music-studio](images/music-studio.jpg) + +왼쪽 네비게이션 메뉴에서 **Music Studio**을 클릭하세요. + +![play-default](images/lab1-play-default.PNG) + +기본 입력 멜로디를 연주하려면 *play*를 클릭하세요. + +![generate-composition](images/lab1-generate-composition.PNG) + +컴포지션(composition)을 생성하려면 **Generate composition**을 클릭하세요. AI 생성 컴포지션이 생성됩니다. + +새로운 AI 생성 음악 작곡을 재생하려면 *play*를 클릭하세요. + +## 입력 멜로디: + +![record-composition](images/lab1-record.PNG) + +녹음을 시작하려면 *record*를 클릭하세요. + +![custom-input](images/lab1-custom-music.png) + +실제 키보드에서 음표를 연주해 봅니다. + +*record* 버튼을 다시 클릭하여 녹음을 중지하세요. + +녹음된 음악을 재생하여 입력을 확인하십시오. 녹음된 음악이 마음에 들지 않으면, *record*를 클릭하여 녹음을 다시 시작할 수 있습니다. + +![model-jazz](images/lab1-model-default.PNG) + + *Model*에서 **Jazz** 모델을 선택하세요. + +여러분이 제공한 입력 멜로디를 기반으로 컴포지션을 생성하려면 **Generate Composition**을 클릭하세요. *참고: 이 단계는 여러분이 선택한 장르의 음악 작품을 생성하는 데 몇 분이 소요됩니다.* + +컴포지션을을 재생하고 AI기 생성한 음악을 즐기려면 *play*를 클릭하세요. + +다른 장르 또는 샘플 입력 멜로디로 실험해 보세요. + +**축하합니다! 사전 학습된 모델을 사용하여 새로운 음악을 생성하는 방법을 배웠습니다.** diff --git a/Lab 2/GAN-ko.ipynb b/Lab 2/GAN-ko.ipynb new file mode 100644 index 0000000..6b65d70 --- /dev/null +++ b/Lab 2/GAN-ko.ipynb @@ -0,0 +1,1183 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "이 튜토리얼은 **Generative Adversarial Networks**(**GAN**)을 사용한 음악 생성에 대한 간략한 소개입니다.\n", + "\n", + "이 튜토리얼의 목표는 모델이 싱글 트랙 입력 멜로디에 반주를 추가하는 방법을 배우도록 바흐(Bach) 컴포지션의 데이터셋을 사용하여 머신 러닝 모델을 학습하는 것입니다. 다시 말해, 사용자가 \"twinkle twinkle little star\"와 같은 노래의 싱글 피아노 트랙을 입력하는 경우, GAN 모델은 3개의 다른 피아노 트랙을 추가하여 음악 사운드를 보다 바흐 스타일과 가깝게 연주합니다.\n", + "\n", + "제안된 알고리즘은 두 개의 경쟁 네트워크인 generator와 critic(discriminator)로 구성됩니다. Generator는 학습된 데이터셋의 분포와 유사한 새로운 합성 데이터를 생성하는 방법을 배우는 딥 뉴럴 네트워크입니다. Critic은 실제 데이터와 합성 데이터를 구별하도록 학습된 또 다른 딥 뉴럴 네트워크입니다. Generator와 critic은 교대 주기로 학습되어 generator는 점점 더 현실적인 데이터(이 사용 사례에서는 바흐와 같은 음악)를 생성하는 법을 배우고, critic은 실제 데이터(바흐 음악)와 합성 데이터를 구별하는 법을 더 잘 배우게 됩니다.\n", + "\n", + "결과적으로, generator가 생성한 음악의 품질은 시간이 지날수록 더욱 현실감 있게 됩니다." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![High level WGAN-GP architecture](images/dgan.png \"WGAN-GP architecture\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dependencies\n", + "\n", + "먼저, 본 튜토리얼에서 사용할 모든 파이썬 패키지들을 가져 오겠습니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n", + "\n", + "# Permission is hereby granted, free of charge, to any person obtaining a copy of\n", + "# this software and associated documentation files (the \"Software\"), to deal in\n", + "# the Software without restriction, including without limitation the rights to\n", + "# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n", + "# the Software, and to permit persons to whom the Software is furnished to do so.\n", + "\n", + "# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n", + "# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n", + "# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n", + "# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n", + "# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n", + "\n", + "\n", + "# Create the environment\n", + "!conda update --all --y \n", + "!pip install tensorflow-gpu==1.14.0\n", + "!pip install numpy==1.16.4\n", + "!pip install pretty_midi\n", + "!pip install pypianoroll\n", + "!pip install music21\n", + "!pip install seaborn\n", + "!pip install --ignore-installed moviepy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# IMPORTS\n", + "import os \n", + "import numpy as np\n", + "from PIL import Image\n", + "import logging\n", + "import pypianoroll\n", + "import scipy.stats\n", + "import pickle\n", + "import music21\n", + "from IPython import display\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Configure Tensorflow\n", + "import tensorflow as tf\n", + "print(tf.__version__)\n", + "tf.logging.set_verbosity(tf.logging.ERROR)\n", + "tf.enable_eager_execution()\n", + "\n", + "# Use this command to make a subset of GPUS visible to the jupyter notebook.\n", + "os.environ['CUDA_VISIBLE_DEVICES'] = '0'\n", + "os.environ[\"CUDA_DEVICE_ORDER\"]=\"PCI_BUS_ID\"\n", + "\n", + "# Utils library for plotting, loading and saving midi among other functions\n", + "from utils import display_utils, metrics_utils, path_utils, inference_utils, midi_utils\n", + "\n", + "LOGGER = logging.getLogger(\"gan.train\")\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configuration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "데이터셋을 검색하고 실험 결과를 저장하기 위한 경로를 설정합니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "root_dir = './Experiments'\n", + "\n", + "# Directory to save checkpoints\n", + "model_dir = os.path.join(root_dir,'2Bar') # JSP: 229, Bach: 19199\n", + "\n", + "# Directory to save pianorolls during training\n", + "train_dir = os.path.join(model_dir, 'train')\n", + "\n", + "# Directory to save checkpoint generated during training\n", + "check_dir = os.path.join(model_dir, 'preload')\n", + "\n", + "# Directory to save midi during training\n", + "sample_dir = os.path.join(model_dir, 'sample')\n", + "\n", + "# Directory to save samples generated during inference\n", + "eval_dir = os.path.join(model_dir, 'eval')\n", + "\n", + "os.makedirs(train_dir, exist_ok=True)\n", + "os.makedirs(eval_dir, exist_ok=True)\n", + "os.makedirs(sample_dir, exist_ok=True)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data Preparation\n", + "\n", + "### 데이터셋 요약\n", + "\n", + "이 튜토리얼에서는 229개의 코랄(chorale) snippet들로 구성된 [`JSB-Chorales-dataset`](http://www-etud.iro.umontreal.ca/~boulanni/icml2012)을 사용합니다. 코랄은 찬송가로 보통 싱글 멜로디를 연주하는 싱글 음색(voice)과 하모니를 제공하는 3개의 하위 음색으로 노래됩니다. 이 데이터셋에서 이 음색은 4개의 피아노 트랙으로 표시됩니다.\n", + "\n", + "이 데이터셋의 노래를 한 번 들어 보겠습니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "display_utils.playmidi('./original_midi/MIDI-0.mid')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 데이터 포맷 - piano roll" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "본 튜토리얼의 실습을 위해, JSB-Choales 데이터셋의 음악을 piano roll 포맷으로 표현해 보겠습니다.\n", + "\n", + "**Piano roll**은 음악을 머신 러닝 알고리즘들의 입력에 적용할 수 있게 변환한 이산적인(discrete) 표현이며, 가로 축에 \"시간(Time)\", 세로 축에 \"피치(Pitch)\"가 있는 2차원 격자입니다. 이 그리드의 특정 셀에서 1 또는 0은 해당 피치에 대해 음이 연주되었는지 여부를 나타냅니다.\n", + "\n", + "데이터셋에서 piano roll 몇 개를 살펴 보겠습니다. 이 예시에서, 싱글 piano roll 트랙은 32개의 이산적인 타임스텝(time step)과 128개의 피치를 갖습니다. 여기에는 4개의 piano roll들이 있는데, 각 piano roll은 노래에서 별도의 피아노 트랙을 나타냅니다." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Dataset\n", + "\n", + "이 표현이 이미지(image)와 비슷하게 보일 수 있습니다. 일련의 음표가 사람들이 음악을 보는 자연스러운 방법인 경우가 많지만, 많은 현대 머신 러닝 모델들은 음악을 이미지로 취급하고 컴퓨터 비전 영역 내의 기존 기술을 활용합니다. 여러분은 이 튜토리얼의 뒷부분에서 Deepcomposer 아키텍처에 사용된 이러한 기술들을 보게 될 것입니다." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**왜 32개의 타임 스텝인가요?**\n", + "\n", + "이 튜토리얼에서는 JSB-Chorales 데이터셋의 각 노래에서 2개의 non-empty bar (https://en.wikipedia.org/wiki/Bar_(music)) 를 샘플링합니다. **Bar** (또는 **measure**)는 구성 단위이며 특정 데이터셋의 노래에 대한 4개의 비트(beat)를 포함합니다 (우리의 노래는 모두 4/4 time입니다).\n", + "\n", + "비트(beat) 당 4개의 타임 스텝 해상도를 사용하면 이 데이터셋의 음악적 세부 사항을 충분히 포착할 수 있으며, 이를 수식으로 표현하면 아래와 같습니다.\n", + "\n", + "$$ \\frac{4\\;timesteps}{1\\;beat} * \\frac{4\\;beats}{1\\;bar} * \\frac{2\\;bars}{1} = 32\\;timesteps $$\n", + "\n", + "이제 데이터셋을 numpy 배열로 로드하겠습니다. 튜토리얼에서 사용할 데이터셋은 4개 트랙의 229개 샘플들로 구성됩니다(모든 트랙은 피아노입니다). 각 샘플은 노래의 32개 타임 스텝 snippet이므로 데이터셋의 크기는 다음과 같습니다.
\n", + "(num_samples, time_steps, pitch_range, tracks) = (229, 32, 128, 4)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "training_data = np.load('./dataset/train.npy')\n", + "print(training_data.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "모델에 공급(feed into)할 데이터 샘플을 보도록 하겠습니다. 4개의 그래프는 4개의 트랙을 나타냅니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "display_utils.show_pianoroll(training_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 데이터 로드" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "이제, numpy 배열에서 Tensorflow dataset 객체를 만들어 모델에 공급(feed into)해 보겠습니다. Dataset 객체는 모델에 데이터 배치를 공급하는 데 도움이 됩니다. 배치는 가중치가 업데이트되기 전에 딥 러닝 네트워크를 통해 전달되는 데이터의 하위 집합입니다. 학습 환경에서 전체 데이터셋을 한 번에 메모리에 로드하지 못할 수 있으므로, 대부분의 학습 시나리오에서 데이터 배치가 필요합니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Number of input data samples in a batch\n", + "BATCH_SIZE = 64\n", + "\n", + "#Shuffle buffer size for shuffling data\n", + "SHUFFLE_BUFFER_SIZE = 1000\n", + "\n", + "#Preloads PREFETCH_SIZE batches so that there is no idle time between batches\n", + "PREFETCH_SIZE = 4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def prepare_dataset(filename):\n", + " \n", + " \"\"\"Load the samples used for training.\"\"\"\n", + " \n", + " data = np.load(filename)\n", + " data = np.asarray(data, dtype=np.float32) # {-1, 1}\n", + "\n", + " print('data shape = {}'.format(data.shape))\n", + "\n", + " dataset = tf.data.Dataset.from_tensor_slices(data)\n", + " dataset = dataset.shuffle(SHUFFLE_BUFFER_SIZE).repeat()\n", + " dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)\n", + " dataset = dataset.prefetch(PREFETCH_SIZE)\n", + "\n", + " return dataset \n", + "\n", + "dataset = prepare_dataset('./dataset/train.npy')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model architecture\n", + "이 섹션에서는 제안된 GAN의 아키텍처를 살펴 보겠습니다.\n", + "\n", + "이 모델은 generator와 critic의 두 가지 네트워크로 구성됩니다. 이 두 네트워크는 다음과 같이 긴밀한(tight) 루프로 작동합니다.\n", + "\n", + "* Generator :\n", + " 1. Generator는 싱글 트랙 piano roll(멜로디) 배치를 입력으로 취하고, 각 입력 음악 트랙에 반주를 추가하여 출력으로 멀티 트랙 piano roll 배치를 생성합니다.\n", + " 2. 그러면 critic은 생성된 음악 트랙을 가져 와서 학습 데이터셋에 존재하는 실제 데이터와 얼마나 멀리 떨어져 있는지 예측합니다.\n", + " 3. Critic으로부터의 이 피드백은 가중치를 업데이트하기 위해 generator에서 사용됩니다.\n", + "- Critic : Generator가 critic의 피드백을 사용하여 더 좋은 음악 반주를 만드는 데 익숙해 짐에 따라, critic도 재학습이 필요합니다.\n", + " 1. Generator에서 방금 생성한 음악 트랙을 가짜 입력으로, 원래 데이터셋과 동일한 수의 노래들을 실제 입력으로 하여 비평가를 학습시킵니다.\n", + "* 첫 에포크(epoch)에 대한 critic부터 시작하여, 모델이 수렴하고 보다 사실적인 음악을 생성할 때까지 이 두 네트워크를 교대로 전환합니다.\n", + "\n", + "음악을 생성하기 위해, **Gradient Penalty가 있는 Wasserstein GAN** (또는 **WGAN-GP**)이라는 특별한 유형의 GAN을 사용합니다. WGAN-GP의 기본 아키텍처는 GAN의 vanilla 변형과 매우 유사하지만 WGAN-GP는 vanishing gradient 문제 및 mode collapse와 같이 GAN에서 일반적으로 보이는 결함들을 극복하는 데 도움이 됩니다 (자세한 내용은 appendix을 참조하세요).\n", + "\n", + "참고로, \"critic\" 네트워크는 vanilla GAN의 더 일반적인 맥락에서, 일반적으로 \"discriminator\" 네트워크라고 불립니다." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "이 generator는 U-Net 아키텍처(컴퓨터 비전 도메인에서 널리 사용되는 유명한 CNN)를 채택한 것으로, 싱글 트랙 음악 데이터(piano roll롤 이미지로 표현)를 비교적 저차원 \"latent 공간\"에 매핑하는 \"인코더(encoder)\"와 latent 공간을 다시 멀티 트랙 음악 데이터에 매핑하는 \"디코더(decoder)\"로 구성되어 있습니다.\n", + "\n", + "Generator의 입력 데이터는 다음과 같습니다.\n", + "\n", + "**싱글 트랙 piano roll 입력**: \n", + "(32, 128, 1) => (TimeStep, NumPitches, NumTracks) 크기의 싱글 멜로디 트랙이 generator에 대한 입력으로 전달됩니다.\n", + "\n", + "**Latent noise 벡터**: 차원 (2, 8, 512)의 latent noise 벡터 $z$도 입력으로 전달되며, 이는 동일한 입력이 전달되는 경우라도 generator에 의해 생성된 각 출력이 고유한 특징이 있는지 확인하는 역할을 합니다.\n", + "\n", + "참고로, 아래 그림에서 왼쪽에 있는 generator의 인코더 레이어와 오른쪽에 있는 디코더 레이어가 연결되어 U자 모양을 만들기에, 이 아키텍처에 U-Net이라는 이름이 붙여졌습니다." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Generator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "이 구현에서는 `_conv2d`와 `_deconv2d`를 결합하여, 간단한 4계층 U-Net 아키텍처를 따라 generator를 빌드합니다. 여기서 `_conv2d`는 contracting path를 구성하고 `_deconv2d`는 expansive path를 형성합니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def _conv2d(layer_input, filters, f_size=4, bn=True):\n", + " \"\"\"Generator Basic Downsampling Block\"\"\"\n", + " d = tf.keras.layers.Conv2D(filters, kernel_size=f_size, strides=2,\n", + " padding='same')(layer_input)\n", + " d = tf.keras.layers.LeakyReLU(alpha=0.2)(d)\n", + " if bn:\n", + " d = tf.keras.layers.BatchNormalization(momentum=0.8)(d)\n", + " return d\n", + "\n", + "\n", + "def _deconv2d(layer_input, pre_input, filters, f_size=4, dropout_rate=0):\n", + " \"\"\"Generator Basic Upsampling Block\"\"\"\n", + " u = tf.keras.layers.UpSampling2D(size=2)(layer_input)\n", + " u = tf.keras.layers.Conv2D(filters, kernel_size=f_size, strides=1,\n", + " padding='same')(u)\n", + " u = tf.keras.layers.BatchNormalization(momentum=0.8)(u)\n", + " u = tf.keras.layers.ReLU()(u)\n", + "\n", + " if dropout_rate:\n", + " u = tf.keras.layers.Dropout(dropout_rate)(u)\n", + " \n", + " u = tf.keras.layers.Concatenate()([u, pre_input])\n", + " return u\n", + "\n", + " \n", + "def build_generator(condition_input_shape=(32, 128, 1), filters=64,\n", + " instruments=4, latent_shape=(2, 8, 512)):\n", + " \"\"\"Buld Generator\"\"\"\n", + " c_input = tf.keras.layers.Input(shape=condition_input_shape)\n", + " z_input = tf.keras.layers.Input(shape=latent_shape)\n", + "\n", + " d1 = _conv2d(c_input, filters, bn=False)\n", + " d2 = _conv2d(d1, filters * 2)\n", + " d3 = _conv2d(d2, filters * 4)\n", + " d4 = _conv2d(d3, filters * 8)\n", + "\n", + " d4 = tf.keras.layers.Concatenate(axis=-1)([d4, z_input])\n", + "\n", + " u4 = _deconv2d(d4, d3, filters * 4)\n", + " u5 = _deconv2d(u4, d2, filters * 2)\n", + " u6 = _deconv2d(u5, d1, filters)\n", + "\n", + " u7 = tf.keras.layers.UpSampling2D(size=2)(u6)\n", + " output = tf.keras.layers.Conv2D(instruments, kernel_size=4, strides=1,\n", + " padding='same', activation='tanh')(u7) # 32, 128, 4\n", + "\n", + " generator = tf.keras.models.Model([c_input, z_input], output, name='Generator')\n", + "\n", + " return generator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "이제 각 레이어(layer)의 입력/출력을 보기 위해, generator의 각 레이어를 살펴 보도록 하겠습니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Models\n", + "generator = build_generator()\n", + "generator.summary()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Critic (Discriminator)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Critic의 목표는 생성된 piano roll이 얼마나 현실적인지 생성기에 피드백을 제공하여, Generator가 보다 현실적인 데이터를 생성하는 방법을 배울 수 있도록 하는 것입니다. Critic은 piano roll이 얼마나 \"실제\" 또는 \"가짜\"인지를 나타내는 스칼라(scalar)를 출력하여 이 피드백을 제공합니다.\n", + "\n", + "Critic은 데이터를 \"실제\" 또는 \"가짜\"로 분류하려고 시도하기 때문에, 일반적으로 사용되는 이진 분류기(binary classifier)와 크게 다르지 않습니다. \n", + "Critic은 4개의 convolution 레이어와 마지막 계층에 dense 레이어로 구성된 간단한 아키텍처를 사용합니다." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Discriminator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "def _build_critic_layer(layer_input, filters, f_size=4):\n", + " \"\"\"\n", + " This layer decreases the spatial resolution by 2:\n", + "\n", + " input: [batch_size, in_channels, H, W]\n", + " output: [batch_size, out_channels, H/2, W/2]\n", + " \"\"\"\n", + " d = tf.keras.layers.Conv2D(filters, kernel_size=f_size, strides=2,\n", + " padding='same')(layer_input)\n", + " # Critic does not use batch-norm\n", + " d = tf.keras.layers.LeakyReLU(alpha=0.2)(d) \n", + " return d\n", + "\n", + "\n", + "def build_critic(pianoroll_shape=(32, 128, 4), filters=64):\n", + " \"\"\"WGAN critic.\"\"\"\n", + " \n", + " condition_input_shape = (32,128,1)\n", + " groundtruth_pianoroll = tf.keras.layers.Input(shape=pianoroll_shape)\n", + " condition_input = tf.keras.layers.Input(shape=condition_input_shape)\n", + " combined_imgs = tf.keras.layers.Concatenate(axis=-1)([groundtruth_pianoroll, condition_input])\n", + "\n", + "\n", + " \n", + " d1 = _build_critic_layer(combined_imgs, filters)\n", + " d2 = _build_critic_layer(d1, filters * 2)\n", + " d3 = _build_critic_layer(d2, filters * 4)\n", + " d4 = _build_critic_layer(d3, filters * 8)\n", + "\n", + " x = tf.keras.layers.Flatten()(d4)\n", + " logit = tf.keras.layers.Dense(1)(x)\n", + "\n", + " critic = tf.keras.models.Model([groundtruth_pianoroll,condition_input], logit,\n", + " name='Critic')\n", + " \n", + "\n", + " return critic" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create the Discriminator\n", + "\n", + "critic = build_critic()\n", + "critic.summary() # View discriminator architecture." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Training\n", + "\n", + "목적 함수(objective function)를 최적화하는 모델 파라메터를 검색하여 모델을 학습합니다. WGAN-GP에는 generator와 critic 네트워크를 번갈아 가며 교대 학습할 때, 목적 함수를 최소화하는 특수 loss function을 사용합니다.\n", + "\n", + "*Generator Loss:*\n", + "* Critic Loss 함수의 음수인 Wasserstein (Generator) loss function을 사용합니다. Generator는 생성된 piano roll을 가능한 한 실제 피아노 롤에 최대한 가깝게 하도록 학습됩니다.\n", + " * $\\frac{1}{m} \\sum_{i=1}^{m} -D_w(G(z^{i}|c^{i})|c^{i})$\n", + "\n", + "*Critic Loss:*\n", + "\n", + "* 먼저 실제 piano roll 확률 분포와 생성된(가짜) piano roll 확률 분포 사이의 거리를 최대화하도록 설계된 Wasserstein (Critic) loss function을 적용합니다.\n", + "\n", + " * $\\frac{1}{m} \\sum_{i=1}^{m} [D_w(G(z^{i}|c^{i})|c^{i}) - D_w(x^{i}|c^{i})]$\n", + "\n", + "* 입력 데이터에 대한 critic의 gradient가 어떻게 행동하는지 제어하기 위해 고안된 gradient penalty loss function 항을 추가합니다. 이것은 Generator의 최적화를 더 쉽게 합니다.\n", + " * $\\frac{1}{m} \\sum_{i=1}^{m}(\\lVert \\nabla_{\\hat{x}^i}D_w(\\hat{x}^i|c^{i}) \\rVert_2 - 1)^2 $" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Define the different loss functions\n", + "\n", + "def generator_loss(critic_fake_output):\n", + " \"\"\" Wasserstein GAN loss\n", + " (Generator) -D(G(z|c))\n", + " \"\"\"\n", + " return -tf.reduce_mean(critic_fake_output)\n", + "\n", + "\n", + "def wasserstein_loss(critic_real_output, critic_fake_output):\n", + " \"\"\" Wasserstein GAN loss\n", + " (Critic) D(G(z|c)) - D(x|c)\n", + " \"\"\"\n", + " return tf.reduce_mean(critic_fake_output) - tf.reduce_mean(\n", + " critic_real_output)\n", + "\n", + "\n", + "def compute_gradient_penalty(critic, x, fake_x):\n", + " \n", + " c = tf.expand_dims(x[..., 0], -1)\n", + " batch_size = x.get_shape().as_list()[0]\n", + " eps_x = tf.random.uniform(\n", + " [batch_size] + [1] * (len(x.get_shape()) - 1)) # B, 1, 1, 1, 1\n", + " inter = eps_x * x + (1.0 - eps_x) * fake_x\n", + "\n", + " with tf.GradientTape() as g:\n", + " g.watch(inter)\n", + " disc_inter_output = critic((inter,c), training=True)\n", + " grads = g.gradient(disc_inter_output, inter)\n", + " slopes = tf.sqrt(1e-8 + tf.reduce_sum(\n", + " tf.square(grads),\n", + " reduction_indices=tf.range(1, grads.get_shape().ndims)))\n", + " gradient_penalty = tf.reduce_mean(tf.square(slopes - 1.0))\n", + " \n", + " return gradient_penalty\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Loss function이 정의된 상태에서 적절한 모델 파라메터 셋을 검색하는 방법을 정의하기 위해 Tensorflow optimizers 클래스를 사용합니다. 본 튜토리얼에서는 일반적으로 사용되는 범용 최적화 기법인 *Adam* 알고리즘을 사용합니다. 또한 학습 시 진행 상황을 저장하기 위한 체크포인트(Checkpoint)도 설정합니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Setup Adam optimizers for both G and D\n", + "generator_optimizer = tf.keras.optimizers.Adam(1e-3, beta_1=0.5, beta_2=0.9)\n", + "critic_optimizer = tf.keras.optimizers.Adam(1e-3, beta_1=0.5, beta_2=0.9)\n", + "\n", + "# We define our checkpoint directory and where to save trained checkpoints\n", + "ckpt = tf.train.Checkpoint(generator=generator,\n", + " generator_optimizer=generator_optimizer,\n", + " critic=critic,\n", + " critic_optimizer=critic_optimizer)\n", + "ckpt_manager = tf.train.CheckpointManager(ckpt, check_dir, max_to_keep=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "이제 `generator_train_step` 및 `critic_train_step` 함수를 정의합니다. 각 함수는 배치에서 단일 forward pass를 수행하고 해당 loss를 리턴합니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@tf.function\n", + "def generator_train_step(x, condition_track_idx=0):\n", + "\n", + " ############################################\n", + " #(1) Update G network: maximize D(G(z|c))\n", + " ############################################\n", + "\n", + " # Extract condition track to make real batches pianoroll\n", + " c = tf.expand_dims(x[..., condition_track_idx], -1)\n", + "\n", + " # Generate batch of latent vectors\n", + " z = tf.random.truncated_normal([BATCH_SIZE, 2, 8, 512])\n", + "\n", + " with tf.GradientTape() as tape:\n", + " fake_x = generator((c, z), training=True)\n", + " fake_output = critic((fake_x,c), training=False)\n", + "\n", + " # Calculate Generator's loss based on this generated output\n", + " gen_loss = generator_loss(fake_output)\n", + "\n", + " # Calculate gradients for Generator\n", + " gradients_of_generator = tape.gradient(gen_loss,\n", + " generator.trainable_variables)\n", + " # Update Generator\n", + " generator_optimizer.apply_gradients(\n", + " zip(gradients_of_generator, generator.trainable_variables))\n", + "\n", + " return gen_loss\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@tf.function\n", + "def critic_train_step(x, condition_track_idx=0):\n", + "\n", + " ############################################################################\n", + " #(2) Update D network: maximize (D(x|c)) + (1 - D(G(z|c))|c) + GradientPenality() \n", + " ############################################################################\n", + "\n", + " # Extract condition track to make real batches pianoroll\n", + " c = tf.expand_dims(x[..., condition_track_idx], -1)\n", + "\n", + " # Generate batch of latent vectors\n", + " z = tf.random.truncated_normal([BATCH_SIZE, 2, 8, 512])\n", + "\n", + " # Generated fake pianoroll\n", + " fake_x = generator((c, z), training=False)\n", + "\n", + "\n", + " # Update critic parameters\n", + " with tf.GradientTape() as tape:\n", + " real_output = critic((x,c), training=True)\n", + " fake_output = critic((fake_x,c), training=True)\n", + " critic_loss = wasserstein_loss(real_output, fake_output)\n", + "\n", + " # Caculate the gradients from the real and fake batches\n", + " grads_of_critic = tape.gradient(critic_loss,\n", + " critic.trainable_variables)\n", + "\n", + " with tf.GradientTape() as tape:\n", + " gp_loss = compute_gradient_penalty(critic, x, fake_x)\n", + " gp_loss *= 10.0\n", + "\n", + " # Calculate the gradients penalty from the real and fake batches\n", + " grads_gp = tape.gradient(gp_loss, critic.trainable_variables)\n", + " gradients_of_critic = [g + ggp for g, ggp in\n", + " zip(grads_of_critic, grads_gp)\n", + " if ggp is not None]\n", + "\n", + " # Update Critic\n", + " critic_optimizer.apply_gradients(\n", + " zip(gradients_of_critic, critic.trainable_variables))\n", + "\n", + " return critic_loss + gp_loss\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "학습을 시작하기 전에 몇 가지 학습 설정 파라메터들를 정의하고 주요 정량적 지표들을 모니터링할 준비를 하겠습니다. 여기에서 학습 중단시기를 결정하는 데 사용할 수 있는 loss 및 metric을 기록합니다. 이 파라메터들을 조정하고 모델이 어떻게 반응하는지 알아 보려면 이 코드 셀(Code shell)로 돌아와 주세요." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# We use load_melody_samples() to load 10 input data samples from our dataset into sample_x \n", + "# and 10 random noise latent vectors into sample_z\n", + "sample_x, sample_z = inference_utils.load_melody_samples(n_sample=10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Number of iterations to train for\n", + "iterations = 1000\n", + "\n", + "# Update critic n times per generator update \n", + "n_dis_updates_per_gen_update = 5\n", + "\n", + "# Determine input track in sample_x that we condition on\n", + "condition_track_idx = 0 \n", + "sample_c = tf.expand_dims(sample_x[..., condition_track_idx], -1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "이제 모델을 학습해 보겠습니다!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Clear out any old metrics we've collected\n", + "metrics_utils.metrics_manager.initialize()\n", + "\n", + "# Keep a running list of various quantities:\n", + "c_losses = []\n", + "g_losses = []\n", + "\n", + "# Data iterator to iterate over our dataset\n", + "it = iter(dataset)\n", + "\n", + "for iteration in range(iterations):\n", + "\n", + " # Train critic\n", + " for _ in range(n_dis_updates_per_gen_update):\n", + " c_loss = critic_train_step(next(it))\n", + "\n", + " # Train generator\n", + " g_loss = generator_train_step(next(it))\n", + "\n", + " # Save Losses for plotting later\n", + " c_losses.append(c_loss)\n", + " g_losses.append(g_loss)\n", + "\n", + " display.clear_output(wait=True)\n", + " fig = plt.figure(figsize=(15, 5))\n", + " line1, = plt.plot(range(iteration+1), c_losses, 'r')\n", + " line2, = plt.plot(range(iteration+1), g_losses, 'k')\n", + " plt.xlabel('Iterations')\n", + " plt.ylabel('Losses')\n", + " plt.legend((line1, line2), ('C-loss', 'G-loss'))\n", + " display.display(fig)\n", + " plt.close(fig)\n", + " \n", + " # Output training stats\n", + " print('Iteration {}, c_loss={:.2f}, g_loss={:.2f}'.format(iteration, c_loss, g_loss))\n", + " \n", + " # Save checkpoints, music metrics, generated output\n", + " if iteration < 100 or iteration % 50 == 0 :\n", + " # Check how the generator is doing by saving G's samples on fixed_noise\n", + " fake_sample_x = generator((sample_c, sample_z), training=False)\n", + " metrics_utils.metrics_manager.append_metrics_for_iteration(fake_sample_x.numpy(), iteration)\n", + "\n", + " if iteration % 50 == 0:\n", + " # Save the checkpoint to disk.\n", + " ckpt_manager.save(checkpoint_number=iteration) \n", + " \n", + " fake_sample_x = fake_sample_x.numpy()\n", + " \n", + " # plot the pianoroll\n", + " display_utils.plot_pianoroll(iteration, sample_x[:4], fake_sample_x[:4], save_dir=train_dir)\n", + "\n", + " # generate the midi\n", + " destination_path = path_utils.generated_midi_path_for_iteration(iteration, saveto_dir=sample_dir)\n", + " midi_utils.save_pianoroll_as_midi(fake_sample_x[:4], destination_path=destination_path)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 이제 학습을 시작했습니다!\n", + "\n", + "Wasserstein loss function을 사용할 때, 여러분은 critic을 수렴하도록(generator 업데이트의 gradient가 정확한 지의 여부를 확인) 학습시켜야 합니다. 이는 vanishing gradient를 피하기 위해 critic이 너무 강해지지 않도록 하는 것이 중요한 표준 GAN과는 대조적입니다.\n", + "\n", + "따라서 Wasserstein loss를 사용하면 GAN 학습의 주요 어려움들 중 하나인 discriminator와 generator 학습의 균형을 맞추는 방법이 제거됩니다. WGAN을 사용하면 generator 업데이트 간에 여러 번 critic을 학습시켜 수렴에 가깝게 만들 수 있습니다. 일반적으로 사용되는 비율은 1번 generator 업데이트 시, 5번의 critic 업데이트를 수행하는 것입니다.\n", + "\n", + "\n", + "### 학습 과정 모니터링\n", + "\n", + "이러한 모델을 학습 시 시간과 리소스가 많이 소요되므로, 예외가 발생하는 경우 이상점들(anomalies)을 포착하고 해결하기 위해 지속적으로 학습 과정을 모니터링해야 합니다. 주의해야 할 사항들은 다음과 같습니다.\n", + "\n", + "**Loss는 어떻게 보이나요?**\n", + "\n", + "Adversarial 학습 과정은 매우 역동적이고 고주파 진동 현상이 매우 일반적입니다. 그러나 Loss(critic 또는 generator)가 큰 값으로 급등하거나 0으로 급락하거나 단일 값에 정체되어 있으면, 어딘가에 문제가 있을 수 있습니다.\n", + "\n", + "**모델이 정상적으로 학습 중인가요?**\n", + "\n", + "- 가능한 경우, Critic loss 및 기타 음악 품질 지표들을 모니터링합니다. 예상 궤도를 따르고 있나요?\n", + "- 생성된 샘플들(piano rolls)을 모니터링합니다. 이들이 시간이 지남에 따라 개선되고 있나요? mode collapse 현상이 보이나요? 샘플들을 직접 들어 보셨나요? \n", + "\n", + "**언제 중지해야 하는지 어떻게 알 수 있나요?**\n", + "\n", + "- 샘플이 기대치를 충족시키는 경우\n", + "- Critic loss가 더 이상 개선되지 않을 경우\n", + "- 음악 품질 지표의 기대값(expected value)이 학습 데이터에 대한 동일한 지표의 해당 기대값에 수렴 시" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 학습 중 샘플 품질을 측정하는 방법\n", + "\n", + "일반적으로 모든 종류의 신경망을 학습할 때는 학습 기간 동안 loss function 값을 모니터링하는 것이 표준 관행입니다. WGAN의 critic loss은 샘플 품질과 밀접한 관련이 있는 것으로 밝혀졌습니다.\n", + "\n", + "분류 또는 회귀와 같은 보다 전통적인(traditional) 모델의 정확도를 평가하기 위한 표준 메커니즘이 존재하지만, generative 모델을 평가하는 것은 활발한 연구 분야이며 음악 생성 도메인 내에서는 더 어렵습니다.\n", + "\n", + "이 문제를 해결하기 위해, 데이터에 대한 높은 수준의 측정들을 수행하고 모델이 이러한 측정치들에 맞는 음악을 얼마나 잘 생성하는지 확인해 보겠습니다. 만약 어려분의 모델이 학습 데이터셋에 대해 이러한 측정의 평균값(mean value)에 가까운 음악을 생성한다면, 음악은 일반적인 “모양(shape)” 과 일치해야 합니다.\n", + "\n", + "다음과 같은 세 가지 측정들을 살펴 보겠습니다.\n", + "\n", + "- **Empty bar rate:** 총 bar 대비 empty bar의 비율입니다.\n", + "- **Pitch histogram distance:** 피치(pitch)의 분포와 위치를 캡처하는 지표입니다.\n", + "- **In Scale Ratio:** 음악에서 발견되는 공통 키인 C 메이저 키에 있는 노트 수와 총 노트 수의 비율입니다." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Evaluate results\n", + "\n", + "학습이 완료되었으니 모델을 평가해 봅시다. 여러분은 다양한 방법들로 모델을 분석할 수 있습니다.\n", + "\n", + "1. 학습하는 동안 generator와 critic 손실이 어떻게 변했는지 조사\n", + "2. 학습하는 동안 특정 음악 지표가 어떻게 변경되었는지 분석\n", + "3. 매 iteration마다 고정 입력값을 위해 생성된 piano roll 출력 시각화 및 비디오 생성" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "마지막으로 저장된 체크포인트를 먼저 복원하겠습니다. 학습을 완료하지 않았지만 사전 학습된(pre-trained) 버전으로 계속하려면 `TRAIN = False`로 설정하세요." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ckpt = tf.train.Checkpoint(generator=generator)\n", + "ckpt_manager = tf.train.CheckpointManager(ckpt, check_dir, max_to_keep=5)\n", + "\n", + "ckpt.restore(ckpt_manager.latest_checkpoint).expect_partial()\n", + "print('Latest checkpoint {} restored.'.format(ckpt_manager.latest_checkpoint))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot losses" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "display_utils.plot_loss_logs(g_losses, c_losses, figsize=(15, 5), smoothing=0.01)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "학습하면서 critic loss(그래프의 C_loss)가 어떻게 0으로 감소하는지 관찰해 보세요. WGAN-GP에서는 학습할 때 critic loss가 거의 단조(monotonically) 감소합니다." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot metrics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "metrics_utils.metrics_manager.set_reference_metrics(training_data)\n", + "metrics_utils.metrics_manager.plot_metrics()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "여기의 각 행은 서로 다른 음악 품질 지표에 해당하며 각 열은 악기 트랙(instrument track)을 나타냅니다.\n", + "\n", + "Iteration 횟수가 증가함에 따라 서로 다른 지표의 기대값(파란색 scatter)이 해당 학습셋의 기대값(빨간색)에 어떻게 가까워지는지 관찰해 보세요. 모델이 수렴함에 따라 리턴값이 감소함을 기대할 수 있습니다." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 학습 중 생성된 샘플\n", + "아래 Code cell은 학습 과정에서 생성된 중간 샘플들을 조사하는 데 도움이 됩니다. 여기서 조건부 입력(conditioned input)은 학습 데이터에서 샘플링됩니다. Iteration 0과 iteration 100에서 샘플을 듣고 관찰하는 것으로 시작하겠습니다. 차이점을 주목해 주세요!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Enter an iteration number (can be divided by 50) and listen to the midi at that iteration\n", + "iteration = 50\n", + "midi_file = os.path.join(sample_dir, 'iteration-{}.mid'.format(iteration))\n", + "display_utils.playmidi(midi_file) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Enter an iteration number (can be divided by 50) and look at the generated pianorolls at that iteration\n", + "iteration = 50\n", + "pianoroll_png = os.path.join(train_dir, 'sample_iteration_%05d.png' % iteration)\n", + "display.Image(filename=pianoroll_png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "생성된 piano roll이 iteration 횟수에 따라 어떻게 변하는 지 확인해 봅니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Video\n", + "\n", + "display_utils.make_training_video(train_dir)\n", + "video_path = \"movie.mp4\"\n", + "Video(video_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Inference " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generating accompaniment for custom input\n", + "\n", + "축하합니다! 음악을 생성하도록 사용자 정의 WGAN-GP를 학습했습니다. Generator가 사용자 정의 입력(custom input)에서 어떻게 수행되는지 확인해 보세요.\n", + "아래 Code cell은 \"Twinkle Twinkle Little Star\"를 기반으로 새 노래를 생성합니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "latest_midi = inference_utils.generate_midi(generator, eval_dir, input_midi_file='./input_twinkle_twinkle.mid')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "display_utils.playmidi(latest_midi)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "또한 특정 샘플에 대해 생성된 piano roll들을 보고 얼마나 다양한 지 확인할 수 있습니다!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inference_utils.show_generated_pianorolls(generator, eval_dir, input_midi_file='./input_twinkle_twinkle.mid')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# What's next?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### (Optional) 사용자 정의 데이터 사용" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "사용자 정의 데이터셋을 만들려면 MIDI 데이터에서 piano roll을 추출할 수 있습니다. MIDI 파일에서 pinao roll을 만드는 예는 다음과 같습니다." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from pypianoroll import Multitrack\n", + "\n", + "midi_data = Multitrack('./input_twinkle_twinkle.mid')\n", + "tracks = [track.pianoroll for track in midi_data.tracks]\n", + "sample = np.stack(tracks, axis=-1)\n", + "\n", + "print(sample.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Appendix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 오픈 소스 구현\n", + "음악에 대한 generative 모델의 오픈 소스 구현에 대해서는 아래 링크들을 확인하세요.\n", + "\n", + "- [MuseGAN](https://github.com/salu133445/musegan): GAN을 사용하여 멀티 트랙 폴리포닉(polyphonic) 음악을 생성하는 공식 TensorFlow 구현\n", + "- [GANSynth](https://github.com/tensorflow/magenta/tree/master/magenta/models/gansynth): 프로그레시브 GAN 아키텍처를 사용하여 단일 벡터에서 전체 오디오 스펙트로그램(spectrogram)으로 컨볼루션을 사용하여 업샘플링\n", + "- [Music Transformer](https://github.com/tensorflow/magenta/tree/master/magenta/models/score2perf): 트랜스포머(Transformer)를 사용하여 음악 생성\n", + "\n", + "GAN은 또한 도메인 간 이미지 이동, 유명인사 얼굴 생성, 이미지에 대한 초고해상도 텍스트, 이미지 inpainting을 포함한 여러 도메인에서 성과를 보이고 있습니다.\n", + "\n", + "- [Keras-GAN](https://github.com/eriklindernoren/Keras-GAN): 이미지 생성을 위한 Keras의 참조 구현 라이브러리(교육 목적에 적합).\n", + "\n", + "여러 분야들에 대한 확률 분포를 모델링하기 위해 GAN을 사용하는 문헌들이 많이 있습니다! 관심이 있으시면 [Gan Zoo](https://github.com/hindupuravinash/the-gan-zoo)에서 시작해 보세요." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### References\n", + "\n", + "1. [Dong, H.W., Hsiao, W.Y., Yang, L.C. and Yang, Y.H., 2018, April. MuseGAN: Multi-track sequential generative adversarial networks for symbolic music generation and accompaniment. In Thirty-Second AAAI Conference on Artificial Intelligence.](https://arxiv.org/abs/1709.06298)\n", + "2. [Ishaan, G., Faruk, A., Martin, A., Vincent, D. and Aaron, C., 2017. Improved training of wasserstein gans. In Advances in Neural Information Processing Systems.](https://arxiv.org/abs/1704.00028)\n", + "3. [Arjovsky, M., Chintala, S. and Bottou, L., 2017. Wasserstein gan. arXiv preprint arXiv:1701.07875.](https://arxiv.org/abs/1701.07875)\n", + "4. [Foster, D., 2019. Generative Deep Learning: Teaching Machines to Paint, Write, Compose, and Play. O'Reilly Media.](https://www.amazon.com/Generative-Deep-Learning-Teaching-Machines/dp/1492041947)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### (Optional) Wassertein GAN with Gradient Penalty에 대한 보충 내용\n", + "\n", + "GAN은 generative 모델링을 위한 주요 혁신이지만, 일반(plain) GAN도 학습하기가 어렵습니다. 몇 가지 일반적인 문제들은 다음과 같습니다.\n", + "\n", + "* **Oscillating loss:** Discriminator와 generator의 loss는 장기적인 안정성을 나타내지 않고 진동하기 시작할 수 있습니다.\n", + "* **Mode collapse:** Generator는 항상 discriminator를 속이는 작은 샘플셋에 갇힐 수 있습니다. 이것은 새로운 샘플을 생성하는 네트워크의 능력을 감소시킵니다.\n", + "* **Uninformative loss:** Generator loss와 생성된 출력 데이터의 품질 사이에 상관 관계가 없기 때문에, 일반 GAN 학습을 해석하기가 어렵습니다.\n", + "\n", + "[Wasserstein GAN](#references)은 GAN의 주요 발전으로 이러한 문제들 중 일부를 완화하는 데 도움이 되었습니다. 그 기능들 중 일부는 다음과 같습니다.\n", + "\n", + "1. Loss function의 해석성을 크게 개선하고 보다 명확한 학습 중단 기준(stopping criteria)을 제공합니다.\n", + "2. WGAN은 일반적으로 더 고품질의 결과를 생성하며, 이는 이미지 생성 도메인에서 실험으로 확인되었습니다.\n", + "\n", + "**Wasserstein GAN with Gradient Penalty의 수학적 내용**\n", + "\n", + "실제 분포 $P_r$와 생성된 piano roll 분포 $P_g$ 사이의 [Wasserstein distance](https://en.wikipedia.org/wiki/Wasserstein_metric)는 다음과 같이 정의됩니다.\n", + "\n", + "$$\\mathbb{W}(P_{r},P_{g})=\\sup_{\\lVert{f} \\rVert_{L} \\le 1} \\mathbb{E}_{x \\sim \\mathbb{P}_r}(f(x)) - \\mathbb{E}_{x \\sim \\mathbb{P}_g}(f(x)) $$\n", + "\n", + "이 수식에서 우리는 실제 분포의 기대값과 생성 분포의 기대값 사이의 거리를 최소화하려고 합니다. 이 때, $f$는 [1-Lipschitz](https://en.wikipedia.org/wiki/Lipschitz_continuity) 여야 한다는 기술적 제약이 따릅니다.\n", + "\n", + "Gradient가 너무 빠르게 변하는 것을 기본적으로 제한하는 1-Lipschitz 조건을 강제하기 위해 gradient penalty를 사용합니다.\n", + "\n", + "**Gradient penalty** : 우리는 critic의 gradient에 핸디캡을 부여하고 싶습니다. 데이터 분포 $P_r$와 생성자 분포 $P_g$에서 샘플링된 점 쌍(pairs of points) 사이의 직선을 따라 균일하게 샘플링하여 $P_{\\hat{x}}$를 암시적으로(implicitly) 정의합니다. 이것은 최적의 critic이 $P_r$와 $P_g$의 결합 점을 연결하는 gradient norm 1을 가진 직선을 포함한다는 사실에 의해 모티베이션을 받은 것입니다. 본 구현에서는 원래 논문에서 권장한 penalty coefficient $\\lambda = 10$을 사용합니다.\n", + "\n", + "Gradient penalty로 인한 loss는 다음과 같습니다.\n", + "\n", + "$$\\mathbb{L}(P_{r},P_{g},P_{\\hat{x}} )= \\mathbb{W}(P_{r},P_{g}) + \\lambda \\mathbb{E}_{\\hat{x} \\sim \\mathbb{P}_\\hat{x}}[(\\lVert \\nabla_{\\hat{x}}D(\\hat{x}) \\rVert_2 - 1)^2]$$\n", + "\n", + "이 loss는 $w$ 및 $\\theta$로 매개변수화할 수 있습니다. 그런 다음, 신경망을 사용하여 $f_w$ (discriminator) 와 $g_\\theta$ (generator) functrion을 학습합니다.\n", + "\n", + "$$\\mathbb{W}(P_{r},P_{\\theta})=\\max_{w \\in \\mathbb{W}} \\mathbb{E}_{x \\sim \\mathbb{P}_r}(D_w(x)) - \\mathbb{E}_{z \\sim p(z)}(D_w(G_{\\theta}(z)) $$\n", + "$$\\mathbb{L}(P_{r},P_{\\theta},P_{\\hat{x}})=\\max_{w \\in \\mathbb{W}} \\mathbb{E}_{x \\sim \\mathbb{P}_r}(D_w(x)) - \\mathbb{E}_{z \\sim p(z)}(D_w(G_{\\theta}(z)) + \\lambda \\mathbb{E}_{\\hat{x} \\sim \\mathbb{P}_\\hat{x}}[(\\lVert \\nabla_{\\hat{x}}D_w(\\hat{x}) \\rVert_2 - 1)^2]$$\n", + "\n", + "이 때, $\\hat{x}$과 $\\epsilon$은 아래와 같습니다. $$ \\hat{x} = \\epsilon x + (1- \\epsilon) G(z), \\;\\; \\epsilon \\sim Unif(0,1)$$\n", + "\n", + "\n", + "학습 기본 절차는 다음과 같습니다.\n", + "1. 실제 분포 $P_r$에서 real_x를 추출하고 생성된 분포 $G_{\\theta}(z)$에서 fake_x를 추출합니다. ($z \\sim p(z)$)\n", + "2. z에서 latent vector들을 샘플링한 다음, $G_{\\theta}$ generator로 변환하여 가짜 샘플 fake_x를 얻습니다. 변환된 샘플들은 Critic function $D_w$에 의해 평가됩니다.\n", + "3. 두 분포 사이의 Wasserstein distance를 최소화합니다.\n", + "\n", + "generator와 critic은 모두 입력 piano roll 멜로디에서 조절됩니다." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_python3", + "language": "python", + "name": "conda_python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/Lab 2/README-ko.md b/Lab 2/README-ko.md new file mode 100644 index 0000000..c2cc30b --- /dev/null +++ b/Lab 2/README-ko.md @@ -0,0 +1,80 @@ +# Lab 2 - Amazon SageMaker로 사용자 정의(custom) GAN 모델 학습하기 + +## 목표 + +여러분은 이 랩에서 사용자 지정(custom) GAN 아키텍처를 구축하고 Amazon SageMaker를 사용하여 모델을 학습하는 방법을 배웁니다. + +## 전제 조건(Prerequisites) + +* Amazon SageMaker 접근 권한 + +## 비용 + +`ml.c5.4xlarge` 인스턴스를 사용하면 전체 학습을 실행하는 데 3-4 시간이 걸립니다. 자세한 내용은 [Amazon SageMaker 요금](https://aws.amazon.com/sagemaker/pricing/)을 참조하세요. + +## 셋업 + +먼저 Amazon SageMaker 노트북 인스턴스를 생성합니다. + +https://console.aws.amazon.com/sagemaker/home?region=us-east-1#/dashboard 링크를 사용하여 Amazon SageMaker로 이동하세요. + +![notebook-instance](images/notebook-instance.PNG) + +왼쪽 네비게이션 바(navigation bar)에서 **Notebook instances**를 클릭하세요. + +**Create notebook instance**을 선택하세요. + +![create-notebook](images/create_notebook.png) + +노트북 인스턴스 생성 양식에서 **Notebook instance type**으로 "c5.4xlarge"를 선택하세요. + +![notebook-instance-settings](images/notebook_instance_settings.png) + +**Permissions and encryption**에 대해 아래 사항들을 설정해 주세요: +* IAM role: Use an existing role or create a new role +* Root access: Enable +* Encryption key: No Custom Encryption + +![notebook-instance-settings](images/permissions_and_encryption.png) + +**Git repositories**에 대해 아래 사항들을 설정해 주세요: +* Repository: Clone a public Git repository to this notebook instance only +* Git repository URL: https://github.com/aws-samples/aws-deepcomposer-samples + +![notebook-instance-settings](images/notebook_git_settings.png) + +**Open Jupyter**를 클릭하세요. + +![open-notebook](images/open_jupyter.png) + +**Lab 2** 폴더를 클릭하고, **GAN-ko.ipynb** 파일을 클릭해 주세요. + +![GAN-notebook](images/gan_notebook.png) + +*커널(kernel)을 선택하라는 메시지가 표시될 수 있습니다. 드롭 다운(Drop down)을 선택하고 커널로 **conda_python3**을 선택하세요.* + +![set-kernel](images/set-kernel.PNG) + +이 jupyter 노트북에는 사용자 정의 GAN 모델을 밑바닥부터(from scratch) 작성하는 지침과 코드가 포함되어 있습니다. 노트북 내용을 따라 모든 코드 셀(code cell)을 끝까지 실행하십시오. + +![run-notebook](images/run-notebook.PNG) + +코드 셀을 실행하려면 실행할 코드 셀을 선택하고 **Run**을 클릭하세요. (Shift+Enter 단축키로도 동일 작업을 수행하실 수 있습니다.) + +![kernel-empty](images/kernel-empty.png) + +커널에 빈 원이 있다면, 그것은 코드를 실행할 준비가 되어 있음을 의미합니다. + +![kernel-busy](images/kernel-busy.png) + +커널에 채워진 원이 있으면 사용 중임을 의미합니다. 다음 코드 셀을 실행하기 전에 이가 해제될 때까지 기다려 주세요. + +## 다음 단계 + +**밑바닥부터(from stratch) 사용자 정의 GAN 모델을 구축하신 것을 축하합니다!** + +이제 모델을 사용하여 사용자 정의 MIDI 입력 데이터를 기반으로 작곡을 해 보세요. + +**중요: 추가 과금을 피하기 위해 Lab 완료 후 Amazon SageMaker 인스턴스를 중지해야 합니다.** + +![notebook-stop](images/notebook-stop.png) \ No newline at end of file diff --git a/Loss-functions-ko.md b/Loss-functions-ko.md new file mode 100644 index 0000000..ab2cf88 --- /dev/null +++ b/Loss-functions-ko.md @@ -0,0 +1,42 @@ +## Loss Function이란 무엇인가요? ## + +Loss function은 모델이 얼마나 정확하게 예측하는지에 대한 피드백을 네트워크에 제공함으로써, 딥러닝 알고리즘을 학습할 때 가장 중요한 구성 요소 중 하나입니다. "Loss function"은 일반적으로 ground truth를 포함한 하나의 학습 배치(batch)에 대해 네트워크에서 출력된 모든 값들을 loss(또는 예측의 부정확함)로 표현하는 단일 값에 매핑하는 수학 함수입니다. +Loss function의 값이 높을수록 일반적으로 알고리즘의 출력 예측이 실제와 크게 다르다는 것을 의미합니다. +Cross Entropy loss, Mean Squared Error 및 그 변형들이 가장 일반적으로 사용되는 loss function입니다. +CNN, RNN 또는 LSTM과 같은 일반적인 딥러닝 네트워크에서는 학습이 진행됨에 따라 loss 값이 감소할 것으로 예상되며, 이는 모델이 올바르게 학습하고 있음을 의미합니다. + +## GAN의 loss function은 어떻게 다른가요? ## + +GAN의 loss function은 위에서 설명한 일반적인 네트워크의 loss function과 다르지 않습니다. 그러나, GAN의 가장 중요한 속성은 2개의 네트워크가 별도로(또는 교대로) generator와 critic(discriminator)로 학습해야 한다는 것입니다. 결론적으로, 두 네트워크들은 각자 학습하는 동안 자신의 loss function을 가지게 됩니다. + +*__Critic loss function:__* Critic 학습은 고양이와 개 클래스가 "실제"와 "가짜" 클래스로 대체된다는 점을 제외하고 고양이-개 감지기와 같은 모든 표준 알고리즘을 학습하는 것과 매우 유사합니다. Critic loss function은 실제 데이터가 "실제"로 분류되고 generator의 가짜 데이터가 한 번의 critic 학습에 대해 "가짜"로 얼마나 정확하게 분류되는지 평가합니다. Critic loss function은 generator의 "가짜" 데이터가 종종 critic에 의해 "실제"로 분류되어 generator에 의해 쉽게 속이는 것을 암시하는 경우 큰 값을 출력합니다. + +*__Generator loss function:__* Generator 학습 중에 생성된 데이터가 이를 속일 만큼 현실적인지 확인하기 위해, 하나의 배치에 대한 generator의 출력이 critic에게 공급(feed into)됩니다. 잘 학습된 generator는 일반적으로 critic에게 생성된 데이터가 실제라고 믿게 하여 loss function score가 더 작아질 것이라고 속이는 것입니다. + +## GAN loss function들의 문제점은 무엇인가요?? ## + +Cross entropy와 같은 표준 loss function을 사용하는 경우, GAN loss function에서 많은 문제점들이 발생합니다. + +*__(1) Oscillating losses:__* 이전 섹션에서 언급했듯이, 표준 고양이-개 감지기 유형 문제에서 학습이 진행됨에 따라 loss function 값이 감소할 것으로 예상합니다. 그러나, 이는 GAN에는 해당되지 않습니다. Generator와 critic이 교대로 학습을 수행하기 때문에, 그들은 서로에 대해 "더 강하고" 더 "약하게" loss를 얻음으로써 loss값들의 진동이 일어납니다(oscillating). 이 현상은 학습이 전반적으로 잘 진행되었는지 여부와 generator에 의해 생성된 데이터가 충분한지 이해하기 어렵게 만듭니다. + +*__(2) Mode collapse:__* 종종, generator는 critic이 항상 속는 1개의 특정 출력에 대해 critic을 속이는 것을 학습합니다. 이렇게 되면, generator는 새로운 종류의 데이터를 학습하고 생성하려는 동기가 없어져 최적의 generator 되지 못합니다. + +## Wasserstein loss function과 WGAN은 무엇인가요? ## + +위에서 논의된 문제들은 Wasserstein loss라는 새로운 유형의 loss function으로 극복되었으며, 이를 사용하는 GAN을 WGAN(Wasserstein GAN) 이라고 합니다. 실무에 적용 시에는 수학적으로 깊은 이해 없이, +Wasserstein loss가 주로 위에서 논의한 문제를 해결한다는 것을 아는 것으로 충분합니다. + +(1) Loss 값과 모델 학습 간에 상관 관계가 있도록 Loss function을 정의합니다. + +(2) GAN이 시간이 지남에 따라 안정적이 되도록 합니다. + +## Loss function은 DeepComposer에서 어떻게 작동하나요? ## + +GAN loss function 대해 언급한 모든 내용은 DeepComposer에도 적용되므로, DeepComposer 아키텍처에서도 Wasserstein loss function을 사용합니다. +그러나 Wasserstein loss function은 GAN에 잘 맞는 또 다른 유형의 loss function에 불과하기 때문에, DeepComposer에서 사용되는 loss function에 대해 명확히 이해하는 것이 중요합니다. +이를 염두에 두고, loss function이 DeepComposer 아키텍처가 어떻게 관련되어 있는지 확인해 봅시다. + +*__(1) Critic loss function:__* Critic 학습 중에는 실제 데이터(멜로디가 있는 멀티 트랙 곡 + 학습 데이터셋의 반주)와 가짜 데이터(generator에 의해 생성된 악기 반주들에서 생성된 노래)의 2가지 타입 데이터가 필요합니다. +Loss function은 critic이 실제 노래를 "실제"로 식별하고 가짜 노래를 "가짜"로 얼마나 잘 식별할 수 있는지를 나타내는 값을 출력합니다. 값이 높으면 critic이 약하고 generator에 의해 쉽게 속이는 것을 나타내므로, 이 값은 critic의 학습을 개선하기 위한 피드백을 제공하는 데 사용됩니다. + +*__(2) Generator loss function:__* Generator 학습 중에 generator는 단일 멜로디 트랙을 입력으로 받아 멀티 트랙 곡을 출력합니다. 이 결과는 이제 critic(weight들은 이때 동결됩니다)에게 전달되어 critic이 실제 곡들의 분포 대비 얼마나 가깝게 생각하는지 알 수 있게 됩니다. 높은 값을 산출하는 loss function은 critic이 올바르게 속지 않았음을 의미하므로, generator가 더 잘 학습하도록 페널티를 부여합니다. \ No newline at end of file diff --git a/Music-terminologies-ko.md b/Music-terminologies-ko.md new file mode 100644 index 0000000..2d5728c --- /dev/null +++ b/Music-terminologies-ko.md @@ -0,0 +1,19 @@ +## 음악 용어(Music Terminologies) + +*__Pitch(피치):__* 우리 뇌가 음을 인식하는 정도를 나타냅니다. + +*__Duration(지속 시간):__* 사운드 길이를 나타냅니다. + +*__Loudness(음량):__* 소리가 얼마나 큰지(loud) 또는 조용한지(quiet)를 나타냅니다. + +*__Timbre(음색):__* 같은 크기, 같은 높이의 음을 구별할 수 있게 해주는 소리의 특성입니다. (예: 같은 크기, 같은 높이의 음이라도 기타로 들을 때와 피아노로 들을 때의 소리가 다름) + +![piano](Lab%202/images/Piano.png) + +*__Note:__* : 음악 소리를 나타내는 기호로, 각 note는 음악의 피치와 지속 시간 등의 정보를 포함하고 있습니다. + +서양 음악에서 음표는 첫 7글자(A, B, C, D, E, F 및 G)로 표시되고 유럽에서는 La, [t/s]i, Do, Re, Mi, Fa, Sol로 표시됩니다. + +8번째 음 또는 옥타브는 1번째와 같은 이름을 갖지만 두 배의 주파수를 가집니다. + +구별하기 위해 일반적으로 문자 뒤에 숫자를 넣습니다. (예: C3 (피아노의 중간 C)). C4는 1 옥타브 위 (C3의 2배 주파수) \ No newline at end of file diff --git a/README-ko.md b/README-ko.md new file mode 100644 index 0000000..737439f --- /dev/null +++ b/README-ko.md @@ -0,0 +1,28 @@ +# 들어가며 + +이 워크샵은 AWS DeepComposer Workshop([영문 원본](https://github.com/aws-samples/aws-deepcomposer-samples))의 한국어 번역으로, 한국 고객들을 위해 원저자들의 공식적인 허락을 받아 한글화하였습니다. +일부 단어 및 용어들은 번역 없이 원문을 그대로 사용하였으며, 번역으로 인해 의미가 모호할 수 있는 단어 및 용어들은 번역된 내용에 괄호를 추가하여 괄호 안에 원문을 같이 표기하였습니다. +아래 예시들을 참조해 주세요. +- 원문 사용: generative, discriminative +- 괄호 추가: 전통적인(traditional), 사용자 정의 입력(custom input) + +# AWS DeepComposer + +AWS DeepComposer 워크샵에 오신 것을 환영합니다. 워크샵은 2개의 Lab으로 구성되어 있으며, +Lab 진행 중 특정 task나 code cell의 수행 시간이 오래 소요될 때 Optional Reading의 내용을 읽어 보시는 것을 추천드립니다. + +## [Lab 1: DeepComposer 모델로 음악 작곡하기](./Lab%201/README-ko.md) + +## [Lab 2: Amazon SageMaker로 사용자 정의(custom) GAN 모델 학습하기](./Lab%202/README-ko.md) + +# Optional Reading + +## [GAN 아키텍처의 loss function](Loss-functions-ko.md) + +## [음악 용어(Music Terminologies)](Music-terminologies-ko.md) + +## [re:Invent DeepComposer 워크숍 장표(deck)](reinvent-workshop-deck) + +# License + +이 라이브러리는 MIT-0 라이센스에 따라 제공됩니다. LICENSE 파일을 참조하십시오.