论文名称:Symbolic Music Genre Transfer with CycleGAN
作者:Gino Brunner, Yuyi Wang, Roger Wattenhofer and Sumu Zhao
Code:https://github.com/sumuzhao/CycleGAN-Music-Style-Transfer
前言
本文使用Cycle Gan
实现了不同音乐类型的转换,在原有模型的基础上,引入了新的loss
提升生成的音乐质量.
网络结构
标识(嫌太麻烦可以先看2.1):
- 蓝线:表示从
源域A
到目标域B
再到源域A
的转换 - 红线:表示从
目标域B
到源域A
再到目标域B
的转换 - 黑线:指向损失函数
- G A → B , G B → A G_{A\rightarrow B},G_{B\rightarrow A} GA→B,GB→A:表示在两个域之间转换的生成器
- D A , D B D_A,D_B DA,DB:表示两个域的判别器
- D A , m , D B , m D_{A,m},D_{B,m} DA,m,DB,m表示两个额外的判别器,其迫使生成器学习更多的高级特征
-
x
A
,
x
B
x_A,x_B
xA,xB:表示来自
源域A
和目标域B
中的真实样本数据,同时也是网络的输入 -
x
^
B
\hat{x}_B
x^B:表示转换到
目标域B
的样本数据,记为 x ^ B = G A → B ( x A ) \hat x_B=G_{A\rightarrow B}(x_A) x^B=GA→B(xA) -
x
~
B
\tilde{x}_B
x~B:表示转换回
目标域B
的样本数据,记为 x ~ B = G A → B ( x ^ A ) = G A → B ( G B → A ( x B ) ) \tilde{x}_B=G_{A\rightarrow B}(\hat{x}_A)=G_{A\rightarrow B}(G_{B\rightarrow A}(x_B)) x~B=GA→B(x^A)=GA→B(GB→A(xB)) -
x
~
A
\tilde{x}_A
x~A:表示转换回
源域A
的样本数据,记为 x ~ A = G B → A ( x ^ B ) = G B → A ( G A → B ( x A ) ) \tilde{x}_A=G_{B\rightarrow A}(\hat{x}_B)=G_{B\rightarrow A}(G_{A\rightarrow B}(x_A)) x~A=GB→A(x^B)=GB→A(GA→B(xA)) -
x
^
A
\hat{x}_A
x^A:表示转换到
源域A
的样本数据,记为 x ^ A = G B → A ( x B ) \hat{x}_A=G_{B\rightarrow A}(x_B) x^A=GB→A(xB) M
:是一个包含多个域的音乐集,例如 M = A ∪ B M=A\cup B M=A∪B,但也可能 M ⊃ A ∪ B M\supset A\cup B M⊃A∪B- x M x_M xM:表示来自M的样本数据
对于各种样本数据,带有
^
\hat{}
^上标表示中间输出
,带有
~
\tilde{}
~上标表示最终输出
简化结构
让我们分以下几步来简化这个结构
- 忽略判别器,因为判别器就相当于一个损失函数
- 只提取出其中的 G A → B , G B → A G_{A\rightarrow B},G_{B\rightarrow A} GA→B,GB→A
接下来,让我们仅仅针对 x A x_A xA来看一下它的对抗网络之旅
- 首先
x
A
x_A
xA会输入
G
A
→
B
G_{A\rightarrow B}
GA→B得到
中间输出
x ^ B \hat{x}_B x^B -
x
^
B
\hat{x}_B
x^B会输入
G
B
→
A
G_{B\rightarrow A}
GB→A得到
最终输出
x ~ A \tilde{x}_A x~A
至此,我们称这是一个循环,我们会对中间输出
x
^
B
\hat{x}_B
x^B和
x
B
x_B
xB求损失
同理,对于
x
B
x_B
xB,会进行一个反向
的循环,即先输给
G
B
→
A
G_{B\rightarrow A}
GB→A再输入给
G
A
→
B
G_{A\rightarrow B}
GA→B,得到
x
^
A
\hat{x}_A
x^A和
x
~
B
\tilde{x}_B
x~B,同样会对
x
^
A
\hat{x}_A
x^A和
x
A
x_A
xA求损失
此时我们得到了两个最终输出 x ~ A , x ~ B \tilde{x}_A,\tilde{x}_B x~A,x~B,分别和 x A , x B x_A,x_B xA,xB求损失( 见下循环一致损失)
生成器结构
判别器结构
分类器结构
损失函数
生成器损失
总损失如下
L
G
=
l
G
A
→
B
+
l
G
B
→
A
+
λ
L
c
L_G=l_{G_{A\rightarrow B}}+l_{G_{B\rightarrow A}}+\lambda L_c
LG=lGA→B+lGB→A+λLc
其分为两个部分:对抗损失和循环一致损失
这里的 λ \lambda λ表示循环一致损失的权重,论文中是10
对抗损失
使用
L
2
l
o
s
s
L2\quad loss
L2loss来作为生成器的损失
L
G
A
→
B
=
∣
∣
D
B
(
x
^
B
)
−
1
∣
∣
2
L
G
B
→
A
=
∣
∣
D
A
(
x
^
A
)
−
1
∣
∣
2
L_{G_{A\rightarrow B}}=||D_B(\hat{x}_B)-1||_2\\ L_{G_{B\rightarrow A}}=||D_A(\hat{x}_A)-1||_2
LGA→B=∣∣DB(x^B)−1∣∣2LGB→A=∣∣DA(x^A)−1∣∣2
对于生成器,我们希望其生成的数据都被判定为真,从而欺骗判别器,因此这里减去1
,1即代表着标签
循环一致损失
在此前的工作中,为了加强前后一致性,进入了一个
L
1
n
o
r
m
L1\quad norm
L1norm作为损失项,被称为循环一致损失( cycle consistency loss)
L
c
=
∣
∣
x
~
A
−
x
A
∣
∣
1
+
∣
∣
x
~
B
−
x
B
∣
∣
1
L_c=||\tilde{x}_A-x_A||_1+||\tilde{x}_B-x_B||_1
Lc=∣∣x~A−xA∣∣1+∣∣x~B−xB∣∣1
循环一致损失保证了输入经过两个生成器之后,即完成了一次循环,最终能被映射回自身
如果取消循环一致损失,输入与输出之间的关系将大大减弱
同时,循环一致损失也可以看做一个正则化项,它保证了生成器不忽略输入的数据,而是保留更多必要信息,以完成反向转换
判别器损失
L D , a l l = L D + γ ( L D A , m + L D B , m ) L_{D,all}=L_D+\gamma(L_{D_{A,m}}+L_{D_{B,m}}) LD,all=LD+γ(LDA,m+LDB,m)
GAN的训练是高度不平衡的,通常在早期判别器会过度强大,从而导致网络收敛到一个很差的局部最优
此外,对于风格迁移任务还存在着另一个困难:生成器需要学习源域和目标域的两种特征来欺骗判别器,然而生成器可以通过生成某种音乐类型独有的模式来欺骗判别器,这样即使判别器被欺骗了,生成器的生成也不一定是真实的。因此添加了两个额外的判别器来迫使生成器学习到更优的高级特征,并且使用了约束损失(我自己这么叫的)
其中 γ \gamma γ用来加权鉴别器的额外损失,论文中是1
为了保持训练的稳定性,同时添加了高斯噪声到判别器的输入(这应该是为了缓解判别器前期过于强大的情况)
标准判别器损失
L D A = 1 2 ( ∣ ∣ D A ( x A ) − 1 ∣ ∣ 2 + ∣ ∣ D A ( x ^ A ) ) ∣ ∣ 2 ) L D B = 1 2 ( ∣ ∣ D B ( x B ) − 1 ∣ ∣ 2 + ∣ ∣ D B ( x ^ B ) ) ∣ ∣ 2 ) L_{D_A}=\frac{1}{2}(||D_A(x_A)-1||_2+||D_A(\hat{x}_A))||_2)\\ L_{D_B}=\frac{1}{2}(||D_B(x_B)-1||_2+||D_B(\hat{x}_B))||_2) LDA=21(∣∣DA(xA)−1∣∣2+∣∣DA(x^A))∣∣2)LDB=21(∣∣DB(xB)−1∣∣2+∣∣DB(x^B))∣∣2)
约束损失
L D A , m = 1 2 ( ∣ ∣ D A , m ( x M ) − 1 ∣ ∣ 2 + ∣ ∣ D A , m ( x ^ A ) ∣ ∣ 2 ) L D B , m = 1 2 ( ∣ ∣ D B , m ( x M ) − 1 ∣ ∣ 2 + ∣ ∣ D B , m ( x ^ B ) ∣ ∣ 2 ) L_{D_{A,m}}=\frac{1}{2}(||D_{A,m}(x_M)-1||_2+||D_{A,m}(\hat{x}_A)||_2)\\ L_{D_{B,m}}=\frac{1}{2}(||D_{B,m}(x_M)-1||_2+||D_{B,m}(\hat{x}_B)||_2) LDA,m=21(∣∣DA,m(xM)−1∣∣2+∣∣DA,m(x^A)∣∣2)LDB,m=21(∣∣DB,m(xM)−1∣∣2+∣∣DB,m(x^B)∣∣2)
D B , m , D A , m D_{B,m},D_{A,m} DB,m,DA,m主要使用生成的假数据和多个域中的数据 x M x_M xM进行训练
这有助于使生成器保持在“音乐流形”上,并生成逼真的音乐。更重要的是,它让生成器保留了许多输入结构,从而确保在转换后仍然能够识别出原始内容。
简单来说,我认为这两个判别器起到了一个约束
的作用,其目的是将生成的音乐约束限制在音乐域
M
M
M中,从而解决上述第二个问题
先将生成器生成约束在真实音乐
的范围,再由其他判别器和损失函数增强其相应音乐风格转换的能力
数据集和处理
MIDI格式
与波形文件不同,MIDI文件不对音乐进行采样,而是将每一个音符记录为一个数字
虽然midi缺乏重现真实自然声音的能力,但是这是一种很好的将音乐离散化的方法
MIDI文件拥有note on
和note off
两种信号,note on
消息指示一个音符开始被演奏,它还指定了该音符的速度(响度)。note off
消息表示一个注释的结束。
采样策略
我们以每小节16个时间步对MIDI文件进行采样,因为绝大部分部分小节当中一个拍子的音符时值都不会低于十六分音符
我们最终的数据维度是 ( t , p ) (t,p) (t,p), t , p t,p t,p分别表示采样时间步的大小和音高
同时将速度都设置为127,使他们的响度保持一致,这会使学习更简单,因为只有note on和note off两种状态
于是,数据维度也是表示为在每个时间步上包含一个 p p p维的 k − h o t k-hot k−hot向量, k k k为同时弹奏的音,此外,MIDI的音域大于钢琴的音域,我们只取了 p = 84 p=84 p=84,使用连续的4个小节作为而训练数据,因此数据维度大小为 ( 64 , 84 ) (64,84) (64,84)
音轨
一首乐曲通常有很多音轨,若是忽略了伴奏会丢失很多原有的信息,因此,做了一个简单的处理:将所有音轨合并为一个音轨。
这样可以保留大部分原有信息,然而所有的不同音轨通常由不同乐器演奏,这样一来,所有音轨都会变为一种乐器演奏,生成时便会显得十分凌乱
此外,还去除了鼓的音轨,因为使用其他乐器演奏他通常很奇怪
数据处理
首先,过滤掉第一拍不以0开始的MIDI文件
然后删除掉拍号不为4/4拍或者会在中途改变的曲目
最终得到12,341个爵士、16,545个古典和20,780个流行音乐采样,每个文件包含4个小节
为了避免不同流派音乐数据的不均衡,训练时会减少大数据集的样本数,与小数据集进行匹配,如当训练爵士和古典时,从古典音乐中随即抽取12,341个文件
在预处理阶段,将数据归一化
训练
GAN的训练总是很不稳定,生成器和判别器需要小心翼翼地平衡
引入了Instance Norm
和LeakyReLU
使用Adam优化器, α = 0.0002 \alpha = 0.0002 α=0.0002, β 1 = 0.5 \beta_1 = 0.5 β1=0.5, β 2 = 0.999 \beta_2 = 0.999 β2=0.999, B a t c h s i z e = 16 Batchsize=16 Batchsize=16, e p o c h = 30 epoch=30 epoch=30
代码复现
https://github.com/Asthestarsfalll/Symbolic-Music-Genre-Transfer-with-CycleGAN-for-pytorch
然而在11g显存的显卡上Batch_size只能设置为1,而论文中为16,应该某些地方有问题
并且测试中总会出现输出为极小的情况,导致最终保存的MIDI文件为空,作者的代码似乎也有类似的问题。