Introduction
파이토치를 사용하는 코드를 디버그 하다 보면 텐서 정보를 출력해 봐야 할 때가 있다.
인풋 / 아웃풋 텐서 이미지라던지, 중간 activation 결과라던지, 아니면 하다못해 모델 웨이트라던지 다양한 텐서 데이터에 대해 지금 어떤 값을 가지고 있고, 비주얼라이즈 하면 어떤 모습일지 확인해 봐야 하는 상황이 종종 있을 것이다.
하지만 일반적으로 텐서를 출력하면 보이는 모습은 아래와 크게 다르지 않다.
>>> tensor
tensor([[[[ 0.4419, 0.8523, -0.5494, ..., -0.8664, -0.5186, 0.1264],
[ 0.2430, -0.7320, -0.7026, ..., 0.4742, -0.7101, 0.6129],
[-0.2232, -0.9372, 0.8252, ..., -0.3732, -0.1305, -0.4546],
...,
[ 0.8121, 0.0090, 0.4795, ..., -0.9208, 0.5308, 0.2632],
[ 0.7351, -0.0374, 0.6936, ..., 0.2749, 0.1641, -0.7762],
[-0.4228, -0.3985, 0.0833, ..., -0.4752, -0.9426, -0.2397]],
[[ 0.9001, 0.3858, 0.4689, ..., -0.4774, 0.8334, -0.9562],
[-0.8670, -0.9348, -0.0565, ..., -0.9697, 0.0295, -0.8752],
[ 0.5359, -0.5235, -0.8968, ..., 0.8222, 0.7782, -0.4593],
...,
[ 0.0876, 0.8467, 0.7426, ..., 0.2200, -0.9015, 0.3496],
[-0.0573, -0.8845, 0.2549, ..., 0.1418, 0.1694, 0.0156],
[-0.0082, -0.0013, -0.6327, ..., 0.0129, -0.5517, -0.6331]],
[[-0.1571, 0.0897, -0.1166, ..., -0.0531, -0.0486, -0.1829],
[-0.2304, -0.2668, -0.3745, ..., 0.8702, 0.7485, 0.2444],
[-0.9946, -0.9933, 0.7351, ..., -0.3398, -0.3613, 0.3562],
...,
[-0.7182, 0.0887, -0.6671, ..., -0.6816, -0.3352, -0.4100],
[-0.9584, -0.5193, -0.8190, ..., 0.6039, -0.3632, -0.8856],
[-0.2748, -0.1126, 0.6288, ..., -0.9729, -0.2787, -0.6302]]]])
여기서 대체 어떤 정보를 알 수 있을까? 각 디멘션마다 세 번째까지의 값을 보여주고 있지만 데이터 타입 이외에는 크게 얻어갈 만한 정보가 없다.
결국 디버거 안에서 여러 명령어를 실행해 가며 각종 통계값을 계산하거나, torchvision의 save_image 등으로 일일이 내용을 확인하게 되는데 (디버거 사용법 참고!), 여간 귀찮은 일이 아니다.
>>> torch.max(tensor)
tensor(1.0000)
>>> torch.min(tensor)
tensor(-1.0000)
>>> torch.mean(tensor)
tensor(0.0006)
>>> tensor.shape
torch.Size([1, 3, 512, 512])
...
이런 걸 귀찮아 하는 사람들을 위해 lovely-tensors 라는 라이브러리가 나왔다! 살짝 맛을 보자면 아래와 같다.
>>> tensor
tensor[1, 3, 512, 512] n=786432 x∈[-1.000, 1.000] μ=7.375e-05 σ=0.577
느낌이 오는가? 단순히 프린트만 시켜도 shape, element 갯수, min/max, mean/var 값을 자동으로 표현해 준다! 이제부터 사용법을 간단하게 소개하고자 한다.
Installation
$ pip install lovely-tensors
Activation
import lovely_tensors as lt
lt.monkey_patch()
멍키 패치.. 를 통해 기능을 활성화 해 줄 수 있다.
Usage
Big sized tensor 출력
>>> tensor
tensor[1, 3, 512, 512] n=786432 x∈[-1.000, 1.000] μ=7.375e-05 σ=0.577
아까처럼 단순히 텐서를 프린트하는 것만으로도 유용한 값들을 볼 수 있다.
Small sized tensor 출력
>>> tensor = torch.Tensor(np.random.uniform(-1, 1, [1, 3, 1, 1]))
>>> tensor
tensor[1, 3, 1, 1] x∈[-0.149, 0.127] μ=-0.019 σ=0.139 [[[[-0.034]], [[-0.149]], [[0.127]]]]
만약 텐서의 크기가 크지 않아 출력을 통해 볼 만하다면, 직접적인 값을 함께 보여주기도 한다.
+inf, -inf, nan 표시
>>> tensor = torch.Tensor(np.random.uniform(-1, 1, [5]))
>>> tensor[0] = float('inf')
>>> tensor[1] = float('nan')
>>> tensor
tensor[5] x∈[-0.871, 0.100] μ=-0.513 σ=0.534 +Inf! NaN! [inf, nan, 0.100, -0.871, -0.769]
만약 inf, nan 등 anomalous한 값이 섞여 있으면 함께 표시해 준다. 콘솔에서는 시뻘건 경고로 보여진다... 오히려 좋아
all_zeros 표시
>>> torch.zeros([1, 3, 3])
tensor[1, 3, 3] all_zeros [[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]]
모든 값이 0이면 all_zeros 라는 태그가 붙어 나온다.
verbose - 기존 방식 출력과 함께 표현
>>> tensor = torch.Tensor(np.random.uniform(-1, 1, [1, 3, 512, 512]))
>>> tensor.v
tensor[1, 3, 512, 512] n=786432 x∈[-1.000, 1.000] μ=6.057e-05 σ=0.578
tensor([[[[ 0.4629, -0.3336, 0.2669, ..., -0.4750, -0.9840, -0.3920],
[ 0.6647, 0.9869, 0.1955, ..., 0.7707, -0.6304, 0.1928],
[-0.5483, -0.5828, -0.2459, ..., -0.2821, 0.5236, -0.2374],
...,
[-0.2321, -0.1395, -0.6522, ..., 0.8366, -0.2286, 0.5231],
[-0.7868, 0.1926, 0.2032, ..., -0.9411, -0.1955, 0.6832],
[ 0.9232, 0.3619, 0.0273, ..., 0.9276, 0.9776, 0.5790]]]])
Verbose 모드에서는 기존 출력 방식을 같이 보여주고,
plain - 기존 방식 출력만 표현
>>> tensor.p
tensor([[[[ 0.4629, -0.3336, 0.2669, ..., -0.4750, -0.9840, -0.3920],
[ 0.6647, 0.9869, 0.1955, ..., 0.7707, -0.6304, 0.1928],
[-0.5483, -0.5828, -0.2459, ..., -0.2821, 0.5236, -0.2374],
...,
[-0.2321, -0.1395, -0.6522, ..., 0.8366, -0.2286, 0.5231],
[-0.7868, 0.1926, 0.2032, ..., -0.9411, -0.1955, 0.6832],
[ 0.9232, 0.3619, 0.0273, ..., 0.9276, 0.9776, 0.5790]]]])
Plain 모드에서는 기존 출력 방식만을 보여준다. 줄이 길어 여기선 임의로 출력을 잘라냈다.
.deeper - 하위 차원 통계 표시
>>> tensor = torch.Tensor(np.random.uniform(-1, 1, [3, 512, 512]))
>>> tensor.deeper
tensor[3, 512, 512] n=786432 x∈[-1.000, 1.000] μ=-0.001 σ=0.577
tensor[512, 512] n=262144 x∈[-1.000, 1.000] μ=-2.813e-05 σ=0.577
tensor[512, 512] n=262144 x∈[-1.000, 1.000] μ=-0.001 σ=0.577
tensor[512, 512] n=262144 x∈[-1.000, 1.000] μ=-0.001 σ=0.577
,deeper 메서드를 이용하면 한 단계 아래의 차원에 대해서 추가로 통계를 표시해 준다.
>>> tensor.deeper(2)
tensor[3, 512, 512] n=786432 x∈[-1.000, 1.000] μ=-0.001 σ=0.577
tensor[512, 512] n=262144 x∈[-1.000, 1.000] μ=-2.813e-05 σ=0.577
tensor[512] x∈[-0.999, 1.000] μ=-0.009 σ=0.553
tensor[512] x∈[-0.999, 0.999] μ=0.014 σ=0.572
tensor[512] x∈[-0.998, 1.000] μ=-0.015 σ=0.573
tensor[512] x∈[-0.998, 0.997] μ=0.010 σ=0.583
tensor[512] x∈[-0.995, 0.999] μ=0.034 σ=0.578
tensor[512] x∈[-0.999, 0.998] μ=-0.008 σ=0.580
tensor[512] x∈[-1.000, 0.999] μ=-0.002 σ=0.579
tensor[512] x∈[-0.974, 0.998] μ=0.059 σ=0.564
tensor[512] x∈[-0.996, 0.993] μ=0.061 σ=0.579
...
메서드 뒤에 숫자를 붙이면 해당 숫자만큼 내려가 해당 차원까지의 통계값을 표시해 준다.
.rgb - 3 채널 이미지 시각화
>>> tensor = torch.rand([1, 3, 256, 256])
>>> tensor.rgb.fig.savefig("test.png")
텐서를 곧바로 figure로 만드는 것도 가능하다. torch.Tensor.rgb 를 호출하면 RGB 이미지 오브젝트가 생성되며, 주피터와 같은 인터랙티브 UI에서는 matplotlib figure처럼 곧바로 출력해 보는 것도 가능하다.
>>> tensor.rgb
<lovely_tensors.repr_rgb.RGBProxy object at 0x000002202FBA36A0>
>>> tensor.rgb.fig
<Figure size 264x264 with 1 Axes>
여기에 다시 .fig를 호출하면 matplotlib figure가 반환된다.
>>> tensor = torch.rand([1, 3, 256, 256]).cuda()
>>> tensor.rgb
<lovely_tensors.repr_rgb.RGBProxy object at 0x000002202FBA36A0>
GPU에 올라가 백워드 그래프가 생성된 텐서도 따로 처리 없이 figure화 시키는 것이 가능하다.
>>> tensor = torch.rand([6, 3, 256, 256]).cuda()
>>> tensor.rgb.fig.savefig("test.png")
1 이상의 배치로 이루어진 텐서의 경우 알아서 나누어 그려준다.
.plt - 통계 플롯 시각화
>>> tensor = torch.rand([1, 3, 256, 256]).cuda()
>>> tensor.plt.fig.savefig("test.png")
.plt는 텐서에 대한 통계를 시각화해 보여준다. 가우시안 분포, 히스토그램, 평균과 +-1 시그마 지점, min/max 등을 그려주고 있다. .rgb와 똑같이 matplotlib figure로 변환해 볼 수 있다.
>>> tensor.plt(center="mean").fig.savefig("test.png")
>>> tensor.plt(center="range").fig.savefig("test.png")
여러 옵션이 있어 필요한대로 플롯 조건을 수정할 수 있다.
.chans - 채널별 시각화
>>> tensor.chans.fig.savefig("test.png")
.chans 는 텐서의 각 채널별로 Grayscale 이미지의 형태로 시각화를 해 준다. 3 이상의 채널에 대해서도 문제없이 실행시킬 수 있다.
>>> tensor = torch.rand([1, 4, 256, 256]).cuda()
>>> tensor.chans.fig.savefig("test.png")
네트워크의 중간 activation 결과에서 어떤 부분이 주로 activate 되었는지 확인할 때 사용할 수도 있다.
Without activation
모듈 활성화 없이 일회성으로만 기능을 사용하는 것도 가능하다.
import lovely_tensors as lt
lt.lovely(tensor)
lt.lovely(tensor, verbose=True)
lt.lovely(tensor, depth=1)
lt.plot(tensor, center="mean")
lt.chans(tensor)
사용방법은 크게 다르지 않다. 텐서에서 직접 호출하던 것을 lt 메서드를 텐서를 받아 호출하는 식으로 바꾸면 된다.
처음엔 설치하고 임포트하는 과정 때문에 이걸 많이 쓰려나 싶었는데, 생각보다 디버그 과정에서 해야 하던 번거로운 작업들을 명령어 하나로 뚝딱뚝딱 수행해 주니 정말 편리하다고 느꼈다. 특히 주피터 노트북과 같은 인터랙티브 환경에서 더욱 빛을 발할 듯 하다. 본인은 콘솔에서 pdb를 이용한 디버그를 많이 이용하는데 그럼에도 불구하고 꽤 유용했다.
자매품으로 Lovely Numpy 와 Lovely JAX도 존재하니 참고하길 바란다!
아래는 참고할 만한 페이지들이다.
References
Lovely Tensors
https://xl0.github.io/lovely-tensors/
Lovely Numpy
https://xl0.github.io/lovely-numpy/
Lovely JAX
https://xl0.github.io/lovely-jax/