[파이토치 딥러닝] GAN으로 사람 얼굴 만들기

이 글은    [Must Have] 텐초의 파이토치 딥러닝 특강   에서 발췌했습니다.

골든래빗 출판
이종민(텐초) 지음

시작하며

GAN을 이용해 세상에 존재하지 않는 사람 얼굴을 만들어봅시다.

 

데이터셋 파일명

CelebA : https://mmlab.ie.cuhk.edu.hk/projects/CelebA.html

 

예제 코드

위치 : http://t2m.kr/H3ern

 

 

학습 순서

 

 

미리보기

  1. GAN은 진짜와 가짜를 구별할 수 없을 정도로 정교한 가짜를 만드는 생성자를 학습하는 알고리 즘입니다.
  2. 감별자는 진짜와 가짜를 구별하도록 학습됩니다.
  3. 생성자는 감별자를 속이도록 학습됩니다.
  4. 특징 공간 상에서 두 특징의 평균값은 두 특징의 중간 정도로 표현됩니다.
  5. 가중치 초기화는 신경망의 가중치를 초기화하는 방법을 말하며, 일반적으로 특정한 확률 분포를 따르도록 합니다.

 

GAN 이해하기

만약 진짜와 너무나도 똑같아서 구별이 가지 않는 가짜 그림이 있다고 하면, 그 그림은 진짜일까요, 가짜일까요? GAN은 이런 생각에서 출발한 모델입니다. ❶ 가짜 이미지를 만들어내도록 학습되는 생성자와 ❷ 가짜와 진짜 이미지를 구별하는 감별자를 경쟁시켜 학습하면, 생성자가 점점 진짜와 같은 이미지를 만들게 되는 원리입니다.

 

▼ GAN 구조

 

생성자는 특징 공간에 있는 임의의 점을 입력으로 받아 이미지를 출력합니다. 특징 공간은 이미지 의 특징들로 표현되는 공간입니다. 예를 들어 이미지의 노이즈 제거에 사용했던 오토인코더는, 인코더에서 특징을 추출하고 추출된 특징을 바탕으로 디코더에서 노이즈가 없는 이미지를 생성했습니다. 이때 인코더의 출력을 좌표 형식으로 나타낼 수 있습니다. 그런 좌표가 존재하는 공간이 바로 특징 공간이 되는 겁니다.

❶ 생성자가 출력한 이미지는 세상에는 존재하지 않는 컴퓨터가 만들어낸 이미지입니다. 감별자 는 데이터로 갖고 있는 실제 이미지와 생성자가 만든 가짜 이미지를 구별하도록 학습됩니다. 따라 서 ❷ 감별자는 이미지를 입력받으면 해당 이미지가 컴퓨터가 만들어낸 이미지인지, 아니면 실제로 존재하는 이미지인지를 분류하는 이진분류기입니다. 생성자가 진짜와 너무 똑같은 이미지를 생성하면, 생성자는 더는 진짜와 가짜를 구별할 수 없을 겁니다. 이렇게 되면 GAN 학습이 잘된 겁니다.

 

▼ GAN 장단점

 

▼ GAN 장단점

 

특징 공간 이해하기

그렇다면 왜 생성자는 특징 공간 상의 점을 입력으로 받아야 할까요? 그 이유를 알려면 특징 공간을 조금 자세히 들여다봐야 합니다. 다음과 같은 나선형의 데이터 분포가 있다고 가정합시다.

이 데이터 분포는 ❶ 3차원 공간에 표현되어 있습니다. 나선형의 데이터 분포의 특징을 추출해 ❷ 2차원 공간에 표현해봅시다.

 

▼ 좌표가 존재하는 공간에 따른 차이

 

A와 B의 평균값을 구하고자 할 때 2차원 특징 공간 상의 평균값과 원래의 3차원 공간에서의 평균값이 크게 차이가 납니다. 이게 어떤 문제를 야기하는지 다음 그림을 보면서 살펴봅시다.

 

▼ 특징 공간에서의 중간과 픽셀의 평균값의 차이

 

이미지 A와 B는 각각 서 있는 네모와 누워 있는 네모입니다. 이때 이미지 A와 B의 각 픽셀값을 더한 평균값을 그림으로 나타내면 색이 살짝 흐려진 ❶처럼 십자가 모양이 될 겁니다. 이제 이미지 A와 B로부터 적당히 특징을 추출한 후 특징 공간 상의 평균값을 다시 이미지로 복원합니다. 이번에는 ❷처럼 비스듬히 누워 있는 네모를 얻을 수 있습니다. 만약 사람에게 90도로 서 있는 것과 0도로 누워 있는 것의 중간이 무엇이냐고 물어본다면 많은 사람들이 45도로 기울어진 것이라고 대답할 겁니다. 즉, 특징 공간이라는 것은 사람이 인식하는 사물의 특징을 표현하는 공간이라고 생각할 수 있습니다.

다음은 손글씨 이미지 0부터 9까지의 특징 공간(2차원)으로 표현한 그림입니다. 하나의 숫자가 다른 숫자로 변모해가는 과정을 확인할 수 있습니다. 이 그림을 보면 특징 공간을 이용하는 이유를 알 수 있습니다. 비슷한 숫자끼리 서로 모여 있습니다. 예를 들면 1에서 9로 변해가는 중간에는 7이 있고, 3에서 6으로 변해 갈 때는 2를 거칩니다. 이렇게 특징 공간은 인공지능이 학습한 특징을 표현해놓은 공간입니다. 잘 활용한다면 두 사람의 얼굴을 적당히 합치거나, 안경을 쓴 사람의 안경을 지우는 다양한 기능을 구현할 수 있습니다.

 

▼ 손글씨 이미지의 특징 공간을 2차원으로 나타낸 그림

데이터 살펴보기

이번에는 Celeba 데이터셋(https://mmlab.ie.cuhk.edu.hk/projects/CelebA.html)을 사용합니다. 연예인 얼굴 이미지를 모아놓은 데이터셋으로 GAN 실습용으로 많이 사용합니다. 하지만 Celeba 데이터셋은 이미지 약 20만 장을 포함하는 대규모 데이터셋입니다. 구글 드라이브에서 이미지 20만 장을 불러오면 런타임에서 시간 초과 에러를 일으킵니다. 따라서 이번에는 압축을 풀지 않은 데이터셋을 구글 드라이브에 올린 다음, 코랩에서 압축을 해제해줄 겁니다. 다음은 구글 드라이브로부터 압축 파일을 읽어온 다음, 코랩 저장소에 압축을 푸는 코드입니다. 먼저 드라이브를 마운드한 후에 다음 명령을 실행해 주세요.

 

▼ 데이터 불러오기

!cp "/content/drive/MyDrive/Colab Notebooks/data/img_align_celeba.zip" "."
!unzip "./img_align_celeba.zip" -d "./GAN/"

예제코드 바로가기 ➡️

 

그러면 다음과 같은 위치에 GAN 폴더가 생성됩니다.

 

코드를 통해 어떤 이미지가 데이터셋에 들어 있는지 확인하겠습니다.

▼ 데이터 살펴보기

import glob
import matplotlib.pyplot as plt
import os

from PIL import Image

# 이미지까지의 경로
pth_to_imgs = "./GAN/img_align_celeba"
imgs = glob.glob(os.path.join(pth_to_imgs, "*"))


# 9개의 이미지를 보여줌
for i in range(9):
   plt.subplot(3, 3, i+1)
   img = Image.open(imgs[i])
   plt.imshow(img)

plt.show()

예제코드 바로가기 ➡️

 

이미지 크기가 모두 동일하지만 가로 세로의 비율이 동일하지 않습니다. 학습을 원활하게 학습하려면 가로 세로 길이를 동일하게 변환하는 과정이 필요합니다.

 

학습용 데이터셋 만들기

이번에는 직접 데이터셋을 만들지 않고 토치비전(torchvision)의 ImageFolder 객체를 이용하겠습니다. ImageFolder는 경로를 넣어주면 자동으로 이미지를 읽어와주는 편리한 토치비전의 객체입니다.

 

▼ ImageFolder 객체가 이미지를 읽어오는 과정

 

❶ 최상위 폴더 안에 ❷ 클래스별로 폴더를 만들고 그 안에 해당 클래스의 ❸ 이미지 파일을 모아둡니다. ImageFolder( )에 최상위 경로를 입력해주면 자동으로 클래스별 이미지를 읽어 파이토치의 Dataset 객체로 반환해줍니다. 이번에는 클래스(분류)가 없는 GAN을 학습하고 있으므로 클래스명을 데이터 폴더명으로 지정해줍시다. 정리하면 다음과 같습니다.

  1. GAN이라는 폴더에 데이터 압축을 풉니다.(데이터 살펴보기에 있는 코랩 명령어 참고)
  2. ImageFolder의 최상위 경로에 GAN 폴더를 지정합니다.

이제 코드로 ImageFolder를 이용해 데이터셋 객체를 만들어봅시다.

 

▼ 이미지 전처리 정의

import torch
import torchvision.transforms as tf

from torchvision.datasets import ImageFolder
from torch.utils.data.dataloader import DataLoader


# ❶ 이미지의 전처리 과정
transforms = tf.Compose([
   tf.Resize(64),
   tf.CenterCrop(64),
   tf.ToTensor(),
   tf.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])


# ❷ ImageFolder()를 이용해 데이터셋을 작성
# root는 최상위 경로를, transform은 전처리를 의미합니다.
dataset = ImageFolder(
   root="./GAN",
   transform=transforms
)
loader = DataLoader(dataset, batch_size=128, shuffle=True)

예제코드 바로가기 ➡️

 

❶ 먼저 이미지의 전처리를 살펴봅시다. 입력 이미지를 64×64 크기로 변환한 뒤, 가운데를 오려낸 다음 다시 64×64 크기로 업스케일링합니다.

 

▼ CenterCrop 과정

 

그 후 이미지 정규화를 거치는 것으로 전처리가 종료됩니다.

❷ ImageFolder 객체를 이용해 데이터셋을 만들어줍니다. root 안에는 학습에 필요한 이미지를 클래스 이름으로 나눠놓은 최상위 폴더까지의 경로를 넣어줍니다. transforms 안에는 이미지의 전처리를 넣어줍니다. 마지막으로 데이터로더까지 정의해주면 됩니다.

 

 

GAN 생성자 정의하기

GAN의 생성자는 업샘플링층과 배치 정규화층을 쌓아서 만들 수 있습니다. 입력은 100차원의 벡터로 하겠습니다. 그림으로 나타내면 다음과 같습니다. 여기서 차원 수는 특징 수입니다. 적은 개수의 특징을 이용하면 학습은 쉬워지지만 성능이 낮아집니다. 반대로 너무 많은 특징을 이용하면 학습이 어려워지므로 적당한 개수를 선택하는 것이 중요합니다. 이번에는 가로 세로 64픽셀 이미지를 100개 정도를 사용하겠습니다. 이미지 크기가 줄어들면 특징 수를 줄이고, 반대로 이미지 크기가 커지면 특징 수를 늘려주세요.

 

▼ GAN 생성자의 기본 블록 구성

 

이제 생성자의 코드를 구현합니다.

 

▼ 생성자 정의

import torch.nn as nn


class Generator(nn.Module):
   def __init__(self):
       super(Generator, self).__init__()
      
       # 생성자를 구성하는 층 정의
       self.gen = nn.Sequential(
           nn.ConvTranspose2d(100, 512, kernel_size=4, bias=False),
           nn.BatchNorm2d(512),
           nn.ReLU(),

           nn.ConvTranspose2d(512, 256, kernel_size=4, 
                              stride=2, padding=1, bias=False),
           nn.BatchNorm2d(256),
           nn.ReLU(),

           nn.ConvTranspose2d(256, 128, kernel_size=4, 
                              stride=2, padding=1, bias=False),
           nn.BatchNorm2d(128),
           nn.ReLU(),

           nn.ConvTranspose2d(128, 64, kernel_size=4, 
                              stride=2, padding=1, bias=False),
           nn.BatchNorm2d(64),
           nn.ReLU(),

           nn.ConvTranspose2d(64, 3, kernel_size=4, 
                              stride=2, padding=1, bias=False),
           nn.Tanh()
       )

   def forward(self, x):
       return self.gen(x)

 

예제코드 바로가기 ➡️

 

GAN의 생성자는 특징 공간 상의 벡터를 입력으로 받고 업샘플링층을 거쳐 이미지를 만들게 됩니다. 업샘플링층과 배치 정규화층을 적절히 섞어서 생성자를 구성해줍시다. 구조는 인코더 디코더의 디코더와 비슷합니다. 업샘플링을 거친 특징 맵을 배치 정규화하고 활성화하는 것이 기본 골자입니다.

생성자의 마지막 층은 각 픽셀의 값을 결정하는 층이기 때문에 배치 정규화층을 사용하지 않습니다. 또한, 마지막 층의 활성화 함수로 Tanh() 함수를 사용한 이유는 Tanh() 함수가 원점에 대해 대칭적이기 때문입니다. 생성자로 하여금 어두운 색과 밝은 색을 대칭적으로 학습할 수 있도록 Tanh( ) 함수를 사용합니다.

 

 

GAN 감별자 정의하기

감별자는 기존의 CNN과 유사하게 구성합니다. 합성곱층과 배치 정규화층을 반복해서 쌓아가면 됩니다. 그림으로 나타내면 다음과 같습니다.

 

▼ GAN 감별자의 기본 블록 구성

 

???? LeakyReLU() 함수 : LeakyReLU( )는 ReLU( )와 비슷한 함수입니다. ReLU( )는 0이 하의 값이 전부 0인 반면 LeakyReLU( ) 함수는 0 이하의 값에서 매우 작은 기울기로 값이 있습니다.

 

▼ LeakyReLU() 함수

 

이제 GAN 감별자를 직접 만들어봅시다.

 

▼ 감별자 정의

class Discriminator(nn.Module):
   def __init__(self):
       super(Discriminator, self).__init__()
      
       # 감별자를 구성하는 층의 정의
       self.disc = nn.Sequential(
           nn.Conv2d(3, 64, kernel_size=4, 
                     stride=2, padding=1, bias=False),
           nn.BatchNorm2d(64),
           nn.LeakyReLU(0.2),

           nn.Conv2d(64, 128, kernel_size=4, 
                     stride=2, padding=1, bias=False),
           nn.BatchNorm2d(128),
           nn.LeakyReLU(0.2),

           nn.Conv2d(128, 256, kernel_size=4, 
                     stride=2, padding=1, bias=False),
           nn.BatchNorm2d(256),
           nn.LeakyReLU(0.2),

           nn.Conv2d(256, 512, kernel_size=4, 
                     stride=2, padding=1, bias=False),
           nn.BatchNorm2d(512),
           nn.LeakyReLU(0.2),

           nn.Conv2d(512, 1, kernel_size=4),
           nn.Sigmoid()
       )

   def forward(self, x):
       return self.disc(x)

예제코드 바로가기 ➡️

 

감별자는 생성자가 만들어낸 이미지의 진위 여부를 판별합니다. 합성곱층과 배치 정규화층을 적절히 섞어서 감별자를 구성해줍시다. 감별자도 마찬가지로 인코더 디코더에서의 인코더와 구조가 비슷합니다. 디코더는 합성곱층과 배치 정규화층, 그리고 활성화를 거치는 것이 기본 골자입니다. 감별자의 마지막 층에서는 이미지가 진짜인지 가짜인지를 판별하는 이진 분류층이 됩니다. 따라서 배치 정규화층은 사용하지 않습니다. 이진 분류층이 되므로 활성화 함수도 시그모이드 함수를 사용합니다.

 

▼ 감별자 정의

 

 

가중치 초기화하기

GAN의 합성곱층과 배치 정규화층은 특정한 방법으로 가중치를 초기화해야 합니다. 합성곱층의 가중치는 평균 0, 표준편차 0.02인 정규분포를 따르도록 설정하고, 배치 정규화층은 평균이 1.0, 표준편차이 0.02인 정규분포를 따르도록 설정합니다. 또한 배치 정규화층의 편향은 0으로 설정합니다. 평균이 0, 표준편차가 0.02가 되도록 설정하는 것이 가장 잘 학습된다고 알려져 있습니다. 가중치의 분포를 정의하는 함수를 만들어봅시다.

 

▼ GAN의 가중치 초기화 함수

 

def weights_init(m):
   # 층의 종류 추출
   classname = m.__class__.__name__
   if classname.find('Conv') != -1:
       # 합성곱층 초기화
       nn.init.normal_(m.weight.data, 0.0, 0.02)
   elif classname.find('BatchNorm') != -1:
       # 배치정규화층 초기화
       nn.init.normal_(m.weight.data, 1.0, 0.02)
       nn.init.constant_(m.bias.data, 0)

예제코드 바로가기 ➡️

 

❶ nn.init.normal_() 함수는 정규분포를 따라 가중치를 초기화합니다. 합성곱층에서 편향을 사용하지 않도록 bias=False를 사용했기 때문에 합성곱에 사용하는 가중치만을 초기화합니다. ❷ nn.init.constant_ ( ) 함수는 특정값을 지정해 가중치를 초기화합니다. 배치 정규화층의 편향은 0으로, 가중치는 정규분포를 따르게 설정합니다.

 

▼ 새로 등장한 함수

 

 

모델 학습하기

GAN은 감별자와 생성자를 번갈아가며 학습합니다. 먼저 감별자에게 진짜 이미지와 가짜 이미지를 구별하도록 ❶ 진짜 이미지 한 배치와 ❷ 생성자가 생성한 가짜 이미지 한 배치를 학습합니다. 한 번 학습된 감별자는 감별 능력이 완벽하진 않지만 조금 개선됩니다. 다음으로 생성자가 학습됩니다. 3 생성자가 만들어낸 이미지를 감별자가 진짜라고 판단하도록 가중치의 업데이트가 일어납니다.

위 과정을 여러 번 반복하면서 GAN의 학습이 이루어지게 됩니다.

 

▼ GAN의 학습 과정

 

이제 GAN의 학습 과정을 코드로 구현합시다. 먼저 학습에 필요한 요소들을 정의합니다.

 

▼ 학습에 필요한 요소 정의

device = "cuda" if torch.cuda.is_available() else "cpu"

# 생성자 정의
G = Generator().to(device)
# ❶ 생성자 가중치 초기화
G.apply(weights_init)

# 감별자 정의
D = Discriminator().to(device)
# ❷ 감별자 가중치 초기화
D.apply(weights_init)

import tqdm

from torch.optim.adam import Adam

G_optim = Adam(G.parameters(), lr=0.0001, betas=(0.5, 0.999))
D_optim = Adam(D.parameters(), lr=0.0001, betas=(0.5, 0.999))

예제코드 바로가기 ➡️

 

생성자와 감별자를 생성하고 ❶ 생성자의 가중치와 ❷ 감별자의 가중치를 초기화해주세요. 생성자와 감별자는 Adam 최적화를 이용해 학습하겠습니다.

다음은 학습 루프입니다. 감별자를 먼저 학습하겠습니다.

 

먼저 학습에 필요한 데이터셋과 모델의 정의합니다. 다음으로 기울기를 초기화하고 모델의 예측값을 출력한 뒤, 오차를 역전파합니다.

 

▼ 학습 루프 정의

for epochs in range(50):
   iterator = tqdm.tqdm(enumerate(loader, 0), total=len(loader))

   for i, data in iterator:
       D_optim.zero_grad()
      
       # ➊ 실제 이미지에는 1, 생성된 이미지는 0으로 정답을 설정
       label = torch.ones_like(
           data[1], dtype=torch.float32).to(device)
       label_fake = torch.zeros_like(
           data[1], dtype=torch.float32).to(device)
      
       # ➋ 실제 이미지를 감별자에 입력
       real = D(data[0].to(device))
      
       # ❸ 실제 이미지에 대한 감별자의 오차를 계산
       Dloss_real = nn.BCELoss()(torch.squeeze(real), label)
       Dloss_real.backward()

예제코드 바로가기 ➡️

 

❶ 감별자 학습에는 진짜 이미지와 생성자가 만들어낸 가짜 이미지가 사용됩니다. 따라서 진짜 이미지에는 참을, 가짜 이미지에는 거짓을 감별자에게 정답으로 알려줘야 합니다. 배치 크기만큼 1을 채워서 진짜 이미지를 인식하도록 정답을 만들어줍시다. label이 이에 해당합니다. 반대로 가짜 이미지를 인식하도록 0을 채워야 합니다. label_ fake가 이에 해당합니다.

 

▼ 처음 등장하는 함수

 

❷ 먼저 감별자를 학습합니다. 감별자에게 우선 실제 이미지를 학습시켜줍니다. 진짜 이미지를 넣고 감별자가 참을 반환하도록 학습됩니다. ❸ 마지막으로 감별자의 오차를 계산하기 위해 진짜 이미지에 대한 감별자의 분류 오차를 계산합니다. 진짜냐 가짜냐의 이진분류이므로 BCE 손실을 이용합니다.

다음은 감별자의 가짜 이미지에 대한 오차를 계산합니다.

 

▼ 감별자 학습

# ➊ 가짜 이미지 생성
       noise = torch.randn(label.shape[0], 100, 1, 1, device=device)
       fake = G(noise)
      
       # 가짜 이미지를 감별자에 입력
       output = D(fake.detach())
      
       # 가짜 이미지에 대한 감별자의 오차를 계산
       Dloss_fake = nn.BCELoss()(torch.squeeze(output), label_fake)
       Dloss_fake.backward()
      
       # ➋ 감별자의 전체 오차를 학습
       Dloss = Dloss_real + Dloss_fake
       D_optim.step()

예제코드 바로가기 ➡️

 

다음은 감별자가 가짜 이미지를 학습할 차례입니다. ❶ 먼저 생성자가 가짜 이미지를 만들어냅니다. 생성된 이미지는 감별자의 입력으로 들어가고 가짜 이미지에 대한 오차를 계산합니다. ❷ 전체 손실은 진짜 이미지에 대한 손실과 가짜 이미지에 대한 손실을 합해줘야 합니다. 손실을 계산하고 오차를 역전파해서 감별자를 학습해줍니다.

마지막으로 생성자를 학습하겠습니다.

 

▼ 생성자 학습

# ➊ 생성자의 학습
       G_optim.zero_grad()
       output = D(fake)
       Gloss = nn.BCELoss()(torch.squeeze(output), label)
       Gloss.backward()

       G_optim.step()

       iterator.set_description(f"epoch:{epochs} iteration:{i} D_loss:{Dloss} G_loss:{Gloss}")

torch.save(G.state_dict(), "Generator.pth")
torch.save(D.state_dict(), "Discriminator.pth")

예제코드 바로가기 ➡️

 

다음은 생성자의 학습입니다. 1 먼저 생성자가 만들어낸 이미지를 감별자에게 인식시킵니다. 감별자가 생성자가 만들어낸 이미지를 참으로 인식해야 하므로, 감별자의 예측을 label과 비교해서 손실을 계산해줍니다. 손실을 계산하고 오차를 역전파하면 생성자의 학습이 완료됩니다.

 

모델 성능 평가하기

무사히 학습이 완료됐다면 GAN이 만들어내는 이미지를 직접 확인해 봅시다.

 

▼ 모델 성능 확인하기

with torch.no_grad():
   G.load_state_dict(
       torch.load("./Generator.pth", map_location=device))

   # 특징 공간 상의 랜덤한 하나의 점을 지정
   feature_vector = torch.randn(1, 100, 1, 1).to(device)
   # 이미지 생성
   pred = G(feature_vector).squeeze()
   pred = pred.permute(1, 2, 0).cpu().numpy()

   plt.imshow(pred)
   plt.title("predicted image")
   plt.show()

예제코드 바로가기 ➡️

 

사람의 얼굴 같은 것을 모델이 만들었습니다. 아직 완벽하게 사람의 얼굴을 하고 있지 않지만, 눈, 코, 입, 귀 같은 얼굴의 특징을 잘 드러나 있네요. 더 완성도 있는 이미지를 원한다면 학습 에포크를 더 늘려서 학습하면 됩니다.

 

 

마무리

이번 장에서는 GAN을 이용해 이미지를 만드는 모델을 알아봤습니다. GAN의 학습 방법을 이용하면 단순히 정답과 비교하는 것보다 좋은 결과를 얻을 수 있습니다. 생성자의 결과를 감별자가 구별하고, 감별자를 속이도록 생성자가 학습되기 때문에 적대적 생성 신경망이라고도 불립니다. GAN의 입력으로 특징 공간 상의 임의의 점을 받았는데, 특징 공간 상의 점이 이미지의 특징을 잘 표현할 수 있기 때문입니다.

 

책 내용 중 궁금한 점, 공부하다가 막힌 문제 등 개발 관련 질문이 있으시다면

언제나 열려있는 <[Must Have] 텐초의 파이토치 딥러닝 특강> 저자님의

카카오채널로 질문해주세요!

이종민(텐초) 

일본 JAIST 정보과학 석사. 동경의 딥러닝 엔지니어로 Ghelia에서 B2B 인공지능 솔루션을 개발 중이다. 딥러닝의 유용함을 널리 알리고자 유튜버로  활동하고 글을 쓰고 책을 집필합니다. 쉬운 그림을 이용해 10초만에 핵심을 전달하자는 의미에서 닉네임을 텐초로 지어 활동하고 있습니다.

– SNS : www.youtube.com/c/텐초

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내