又看了一次DCGANs,比上次看的清楚了些,代码也大致看明白了。这次总结也是结合代码记录一下遇到的问题。
- 数据处理部分。数据处理包括数据预处理和数据格式转换。数据预处理使用的是
torchvision.datasets, torchvision.transforms
, 在datasets模块下面有一个datasets.ImageFolder
,可以获取文件夹下面的图片。
torchvision.datasets.ImageFolder(root, transform=None, target_transform=None, loader=<function default_loader>, is_valid_file=None)
[source]
A generic data loader where the images are arranged in this way:
root/dog/xxx.png
root/cat/123.png
对于ImageFolder里面有一个参数是路径,这里一个细节是我们写的路径下面还要有一个子目录,把数据放在子目录里面才可以正常被读取.
torchvision.transforms()
的常见操作是transforms.Compose([ ])
, transforms.Resize()
, transforms.Crop()
,transforms.ToTensor()
, transforms.Normalize(mean=(a,b,c), std=(a,b,c))
,这里面,Resize()和Crop()操作的对象是PIL格式的图片,也就是说,ToTensor()的操作要在这些操作之后才可以,如果顺序颠倒,就会报错.
- 接下来的就是
torch.utils.data.DataLoader()
操作,
class
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None, multiprocessing_context=None)
[source]
Data loader. Combines a dataset and a sampler, and provides an iterable over the given dataset.
The DataLoader supports both map-style and iterable-style datasets with single- or multi-process loading, customizing loading order and optional automatic batching (collation) and memory pinning.
之前我就很奇怪,数据预处理完了为什么还要进行这一步操作,感觉好麻烦,这次算是明白了一点原因。 我的理解是,DataLoader最大的作用就是将上面的数据集变成了一个可迭代的对象,我们通过设置batch_size
的大小来决定一次内存处理数据的多少;同时通过num_workers
来设置多进程处理。使用了dataLoader()之后,我们就可以使用iter()
+next()
来迭代数据,亦可使用 enumerate(DataLoader)
来遍历数据。
for i, data in enumerate(dataloader, 0):
############################
# (1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))
###########################
netD.zero_grad()
real_cpu = data[0].to(device)
b_size = real_cpu.size(0)
label = torch.full((b_size,), real_label, device=device)
对于这几行代码我进行了debug,来看看他们的数据类型。
data是列表类型,但是它里面存的两个Tensor, 这种类型嵌套我最近才搞明白。再来看一下data[0]的维度,这也是一个细节。最后一个细节是data[1]代表的label,还记得我们之前的ImageFolder吗,对于同一目录下的文件,全都赋予同样的标签。
-
matplotlib
画图
plt 画图要求数据格式为PIL而不是tensor,这里就会涉及到一个维度的转变,在Tensor中,图片的存储方式为(C,H,W),而PI的维度要求为(H,W,C),因此,我们需要np.transpose(Tensor,(1,2,0))
进行调整。 -
class torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros')
有仔细看了一下输入输出维度变化的计算公式,这里记录一下,顺便和nn.Conv2d()比较一下。
ConvTranspose2d
: W o u t = ( W i n − 1 ) ∗ s t r i d e − 2 ∗ p a d d i n g + k e r n e l s i z e W_{out} = (W_{in}-1)*stride - 2*padding + kernel_size Wout=(Win−1)∗stride−2∗padding+kernelsizeConv2d
: W o u t = ( W i n − k e r n e l s i z e ) + 2 ∗ p a d d i n g 2 ∗ s t r i d e + 1 W_{out} = \frac{(W_{in}-kernel_size) + 2*padding}{2*stride} + 1 Wout=2∗stride(Win−kernelsize)+2∗padding+1
这里就显示了Conv2d和ConvTranspose2其实就是两个相反的操作~ -
torch.full(size, fill_value, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor .
在上面代码最后一行,第一个参数代表张量大小,第二个代表要填充的值,最后返回的就是是一个张量。这里一个细节是label之前并没有定义哦 -
最后一个问题就是关于loss的。对BCELoss()算是有了个初步的了解了。先放公式
torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
-
l ( x , y ) = L = { l 1 , . . . , l N } T l(x,y)=L=\{ l_1,...,l_N\}^T l(x,y)=L={l1,...,lN}T , l n = − [ y n l o g x n + ( 1 − y ) l o g ( 1 − x n ) ] l_n=-[y_nlogx_n + (1-y)log(1-x_n)] ln=−[ynlogxn+(1−y)log(1−xn)]
首先来看鉴别器D,D的loss两部分都有,一方面它要尽可能的把真图识别为1;另一方面它要尽可能的把假图识别为0,所以当真正测试的时候,使用criterion(output, label)
,就要用两次,一次label=0,一次label=1.
接下来是生成器G,G的loss也是基于D,但是他只需要一项,那么问题来了,使用哪一项呢? 一方面我们可以使用label=1,那么G的目的就是使 l o g x n logx_n logxn尽可能趋于0,也就是D(G(z))尽可能趋近于1(根本目的还是要 降低loss)记住,此时 x n = D ( G ( z ) ) x_n=D(G(z)) xn=D(G(z));另一方面,我们还可以使用label=0,此时就是要使 l o g ( 1 − x n ) log(1-x_n) log(1−xn)尽可能趋于0,也就是1-D(G(z))尽可能趋近于0。
这两个方法,作者选择了第一个,说是这样好训练,我等也就这样吧。一开始以为会写很多,结果发现其实就这些内容,不过还是要记录下来,记性太差了.