[git add -p vs git add .] git에 유용한 기능이 있어 공유해드립니다. 저는 보통git add .을 사용해왔는데요.-p옵션은 commit을 하기 전 staging area에 논리적인 hunk 단위로 추가하거나 삭제를 할 수 있는 유용한 옵션입니다.git add .을 사용하게 되면 파일 단위로 변경된 모든 부분이 staging area에 올라가 commit을 할 때 필요없는 디버깅 코드 부분이나, 테스트한 코드 부분이 함께 올라가게 됩니다. 하지만-p옵션을 사용하게 되면 변경사항이 존재하는 한 파일 내에서도y,n을 눌러 hunk 단위로 staging area에 올라갈 부분을 선택할 수 있습니다. 이렇게 한 번 스스로의 코드리뷰를 거치면서 github에 깔끔한 코드만 올라갈 수 있도록 하는 기능이 너무 좋은 것 같아 공유드립니다.git commit -v에서-v옵션도 비슷한 기능을 한다고 합니다. 알고 계신분도 계시겠지만, 모르셨다면 오늘부터 한번 써보시면 좋을 것 같아요!
자동화 업무를 하다보니 reboot하고 mysql에 자동으로 로그인하는 설정이 필요했습니다. 자동화를 한다고 했을 때 일반 shell 파일을 실행하면 password를 입력할 때 사용자의 입력이 없어 막히게 되는데요. 이때 쓸 수 있는것이expect명령어 입니다. expect를 사용하면 git, ssh, apt-get install (all yes) 등을 사용할 때 id, password를 입력하는 과정을 사용자의 입력 없이도 자동화할 수 있습니다.다음 명령어를 주어 설치할 수 있습니다.
extensive experiments on downstream tasks, such as ImageNet fine-tuning, linear probing, and semantic segmentation.
2. Method
2.1. Training Visual Tokenizer
Backbone : VIT(Vision Transformers)
224 x 224 images를 14 x 14 Grid image patch로 나눈다. 각 patch를 flatten하고, transformers input embedding으로 projection합니다.
VQ-KD(Vector Quntized Knowledge Distillation)은 2개의 모듈 Visual Tokenizer와 Decoder를 학습시킵니다. Visual Tokenizer는 vision Transformer encoder와 quantizer로 구성되어 있습니다. Tokenizer는 먼저 이미지를 벡터로 인코딩합니다. 다음에, vector quantizer는 codebook에서 각 patch의 인코딩된 벡터에 대해 nearest neighbor를 찾습니다.
이 distance는 cosine similarity로 codes를 찾는 것과 같습니다.
이미지를 visual tokens(z_i)로 quantizing 한 이후에 L2 normalized 된 codebook embeddings(l2norm(v_z_i))를 decoder에 넣어줍니다. decoder는 multi-layer transformers이고, output을 teacher model인 DINO와 CLIP의 semantic features를 재구성하도록 학습합니다. decoder output과 teacher guidance의 cosine similiarity를 최대화합니다.
quantization process는 미분 불가능하기 때문에 encoder에서 back-propagate를 수행하기 위해서 decoder의 input에서 gradients를 encoder의 output으로 복사합니다.
quantizer는 각각의 encoder output을 위한 nearest code를 찾기 때문에 gradients of codebook embeddings는 encoder의 유용한 최적화 방향을 일러줍니다.
Object function of VQKD
reconstruction loss(KD) + codebook loss + commitment loss
reconstruction loss : Decoder가 Teacher Model의 feature map을 모방하도록 학습
codebook loss : codebook vector가 encoder의 output을 잘 표현하도록 학습
commitment loss : encoder의 output이 codebook embedding과 값의 차이가 벌어지지 않도록 규제
D : tokenizer training에 사용된 이미지 개수
o_i : decoder output vector of i-th image patch
t_i : teacher model’s feature vector of i-th image patch
h_i : encoding vector of i-th image patch
v_z_i : quantized vector of i-th image patch
codebook 활용의 향상
codebook lookup을 할 때 차원 축소(32차원)와 l2 norm(equation 1)을 적용하였습니다.
낮은 차원(32차원)의 codebook embeddings에서 높은 차원으로 mapping한 후 decoder에 넣어줍니다.
EMA(Exponential Moving Averages)를 적용하여 안정적인 실험 결과를 얻었습니다.
class NormEMAVectorQuantizer(nn.Module):
def __init__(self, n_embed, embedding_dim, beta, decay=0.99, eps=1e-5,
statistic_code_usage=True, kmeans_init=False, codebook_init_path=''):
super().__init__()
self.codebook_dim = embedding_dim
self.num_tokens = n_embed
self.beta = beta
self.decay = decay
# learnable = True if orthogonal_reg_weight > 0 else False
self.embedding = EmbeddingEMA(self.num_tokens, self.codebook_dim, decay, eps, kmeans_init, codebook_init_path)
def forward(self, z):
# reshape z -> (batch, height, width, channel) and flatten
#z, 'b c h w -> b h w c'
z = rearrange(z, 'b c h w -> b h w c')
z = l2norm(z)
z_flattened = z.reshape(-1, self.codebook_dim)
self.embedding.init_embed_(z_flattened)
d = z_flattened.pow(2).sum(dim=1, keepdim=True) + \\ # distance for looking up nearest neighbor vector
self.embedding.weight.pow(2).sum(dim=1) - 2 * \\
torch.einsum('bd,nd->bn', z_flattened, self.embedding.weight) # 'n d -> d n'
encoding_indices = torch.argmin(d, dim=1) # visual tokens
z_q = self.embedding(encoding_indices).view(z.shape) # quantizing vector
encodings = F.one_hot(encoding_indices, self.num_tokens).type(z.dtype)
if self.training and self.embedding.update:
#EMA cluster size
bins = encodings.sum(0)
self.all_reduce_fn(bins)
# self.embedding.cluster_size_ema_update(bins)
ema_inplace(self.cluster_size, bins, self.decay)
zero_mask = (bins == 0)
bins = bins.masked_fill(zero_mask, 1.)
embed_sum = z_flattened.t() @ encodings
self.all_reduce_fn(embed_sum)
embed_normalized = (embed_sum / bins.unsqueeze(0)).t()
embed_normalized = l2norm(embed_normalized)
embed_normalized = torch.where(zero_mask[..., None], self.embedding.weight,
embed_normalized)
norm_ema_inplace(self.embedding.weight, embed_normalized, self.decay)
# compute loss for embedding
loss = self.beta * F.mse_loss(z_q.detach(), z)
# preserve gradients
z_q = z + (z_q - z).detach()
# reshape back to match original input shape
#z_q, 'b h w c -> b c h w'
z_q = rearrange(z_q, 'b h w c -> b c h w')
return z_q, loss, encoding_indices # quantizing vector, embedding loss, visual tokens
2.2 Pretraining BEIT v2
1. L_MIM : BEIT2에서 MIM(Masked Image Modeling)은 이미지 패치의 40%를 마스킹하여 마스킹 된 패치가 원래 이미지 패치의 어떤 Visual Token이었는지 맞추도록 학습하는 과정입니다. softmax classifer인 MIM Head는 마스킹된 인코딩 벡터를 입력으로 받아 visual token을 예측합니다.
D : pretraining images의 개수
M : 마스킹 된 패치
z_i : 원래 이미지 패치의 visual token
2. L_MIM_C : global representation을 위한 CLS token pretraining입니다. 패치 수준의 pretraining과 이미지를 종합하는 것의 간극을 줄이기 위해 학습을 합니다. MIM에서 사용되는 encoder의 l번째 layer의 N개의 이미지 패치 토큰들과 마지막 레이어의 CLS를 concat하여 얕은 decoder의 input으로 feed합니다. 마스킹된 벡터는 Visual Token을 예측하도록 학습됩니다. MIM loss를 줄이기 위하여 l+1부터 L번째 layer에 있는 모든 파라미터를 활용하여 마지막 레이어의 CLS token에 global information을 집어넣게 됩니다. BEIT v2 pretraining 최종적인 loss는 L_MIM + L_MIM_C 이 됩니다
3. 코드
samples, images, bool_masked_pos = batch # 마스킹 된 이미지 패치, 이미지, 마스킹 여부
images = images.to(device, non_blocking=True)
samples = samples.to(device, non_blocking=True)
bool_masked_pos = bool_masked_pos.to(device, non_blocking=True)
with torch.no_grad():
with torch.cuda.amp.autocast():
input_ids = vqkd.get_codebook_indices(images) # 원본 이미지로부터 visual token 뽑기
bool_masked_pos = bool_masked_pos.flatten(1).to(torch.bool)
labels = input_ids[bool_masked_pos] # 마스킹 된 부분의 visual token만 뽑도록 필터링
with torch.cuda.amp.autocast(): # enabled=False
outputs = model(samples, bool_masked_pos=bool_masked_pos)
if isinstance(outputs, list):
loss_1 = loss_fn(input=outputs[0], target=labels) # MIM Loss
loss_2 = loss_fn(input=outputs[1], target=labels) # MIM_C Loss
loss = loss_1 + loss_2
else:
loss = loss_fn(input=outputs, target=labels)
loss_value = loss.item()
Masked image modeling : ImageNet-1k 224 x 224 데이터셋을 사용하여 self-supervised learning으로 pretraining합니다. CLS token pretraining에서 ViT-B를 쓸 때는 l을 9, ViT-L을 쓸 때는 l을 21로 두었고, decoder의 depth를 2로 설정합니다. 이미지의 40%를 마스킹합니다.
3.2 Downstream task
Fine-tuning
Fine-tuning은 전체 파라미터를 학습시키는 것을 의미합니다. BEIT와 동일한 방식으로 finetuning합니다. BEIT v2는 ImageNet-1k와 ADE20K에서 SOTA를 달성하였습니다.
Linear probing
Linear probing은 backbone의 파라미터는 동결시키고 뒤에 붙힌 linear layers만 학습시키는 것을 의미합니다. Dall-e의 visual tokenizer를 사용하는 BEIT와 CAE의 성능을 웃돌았습니다.
3.3 Ablation Studies
Visual tokenizer training - model architecture와 codebook size가 VQKD에 미치는 영향을 알아봅니다. Table4는 decoder의 깊이가 깊어질수록 reconstruction은 잘하지만, codebook usage와 downstream task의 성능을 감소시키는 것을 나타냅니다. codebook의 차원을 축소하는 것이 codebook utilization을 향상시킵니다.
CLS token pretraining - table 5에서 head depth가 얕은 모델이(head depth=2) 깊은 모델(head depth=3)보다 성능이 더 좋습니다.
VQKD targets - table 6에서 보듯이 teacher model의 성능보다 더 좋은 성능을 내면서 VQKD의 scalability를 증명합니다.
최근에는 언어와 비전에서 합성 데이터를 활용하여 compositional language에 초점을 맞추는 데이터셋이 각광받고 있습니다.
CLEVER-Humans
NLVR
GQA
NLVR2는 사람이 작성한 언어와 웹 사진 모두에 초점을 맞추었습니다. 각 문장이 다른 label을 가질 수 있어 다양한 예시가 나올 수 있습니다. VQA와 관련이 있고 이미지와 질문 둘 다를 고려하도록 성능이 잘 나오도록 합니다.
3. Data Collection
NLVR2는 이미지들과 자연어 문장의 쌍으로 구성되어 있고, 이미지 쌍을 보고 문장이 true인지 false인지 판단하는 task입니다. Imagenet이나 COCO와 같이 객체나 그 특성에 초점이 맞춰진 이미지보다는 시각적으로 다양성 있는 이미지를 필요로 합니다
이미지 수집 방법
a) Find Sets of Images
google 검색 엔진을 통해 유사한 이미지를 구합니다. Table1에 있는 heuristic을 활용하여 검색하고자 하는 키워드의 synset을 붙이고 query로 검색하여 원하는 이미지를 다양하게 다운로드합니다.
b) Image pruning
crowdworker가 이미지가 저품질인지 확인하여 필터링합니다.
c) Set Construction
도토리 2개 이상을 검색했는데 1개의 도토리가 나온 이미지를 삭제하고, interesting한지, 이미지가 유사한지를 기준으로 정렬하여 하여 최종적으로 가장 왼쪽의 8개의 이미지를 뽑습니다. interesting의 여부는 table2의 criteria가 제시되어 있고 작업자들은 이러한 기준을 만족하는지 판단합니다. 이미지 셋을 선별하는 첫번째 기준은 interesting한지 여부이고 interesting한 이미지가 3장 이하이면 그 이미지 셋은 버립니다. 두번째는 유사도이고, 상위 8개의 사진이 선별되어야 합니다.
d) Sentence Writing
8개의 이미지에서 4쌍으로 나누어 2개는 True, 2개는 False이도록 문장을 작성합니다. 선택된 이미지 페어에서 문장 생성이 불가능할 경우가 있기 때문에 작업자에게 이미지 페어를 고를 수 있게 합니다. 문장 생성 작업이 단순한 객체의 존재 여부 등으로 그칠 수 있기 때문에 작업자에게 가이드라인을 제공하여 지켜줄 것을 당부했습니다. 이는 부록에서 추가설명합니다.
e) Validation
각각의 문장과 이미지 페어 데이터는 4개로 쪼개져 작업자에게 독립적으로 분배됩니다. 검증하는 사람들은 True인지 False인지 라벨링 작업을 수행하고 터무니 없는 문장들은 보고를 합니다. 문장을 작성했던 작업자와 검증 작업자의 라벨링이 일치하면 그 데이터는 검증을 통과하게 되고 그렇지 않으면 삭제됩니다.
Splitting the Dataset
초기의 8개 이미지 셋이 split set마다 겹치지 않게 검증 통과한 데이터를 development와 testing 2개에 랜덤하게 20%를 할당합니다.
Data Collection Management
언어적으로 다양성 있는 문장을 작성한 작업자에게 보너스를 주는 티어 시스템을 사용합니다. 어노테이션 작업이 시작될 때마다 각 작업자가 수행했던 작업을 보고 가이드라인을 잘 준수한 이들에게 보너스를 줬습니다. 일을 잘 하는 작업자들에게 더 많은 일거리를 제공하였습니다.
Collection Statics
27,678개의 이미지 셋인 387,426개의 이미지를 수집합니다. 저품질의 이미지를 버려서 19,500개 이미지 셋인 250,862개의 이미지가 남았습니다. synset이 포함되 있지 않은 이미지들, 비현실적인 예술작품, 이미지 콜라주들을 제거하여 8개 이미지 쌍으로 이루어진 17,685개의 이미지 셋으로 구성합니다.
31,418개의 문장을 생성했고, 검증 과정에서 1,875개의 문장이 터무니 없는 문장으로 삭제되었습니다. 동의율이 낮은 데이터도 삭제되어 107,292개의 데이터가 검증을 통과했습니다. 이는 127,502개의 unique한 이미지와 29,680으로 이러어진 문장들입니다.
4. Data Analysis
Agreement
검증자의 라벨과 문장 작성 작업자의 라벨이 불일치한 경우를 제거했을 때, Krippendorff’s alpha와 Fleiss’ kappa의 수치가 높아진 것으로 보아 신뢰도가 올라갔다는 것을 알 수 있다.
Synset
각 synset은 평균 752.9 ± 205.7 개이고 4개의 split에서 동일한 비율로 나타난다.
Language
NLVR은 단어의 종류가 262개였고, NLVR2는 7,547개로 상당히 증가했다. 또한 문장의 길이도 11.2에서 14.8로 증가했다.
VQA, GQA, CLEVER-Humans보다 문장이 더 길어졌다.
development set에서 800개의 문장과 다른 데이터셋의 문장들을 semantic, syntactic phenomena의 유무로 분석을 했습니다.
5. Estimating Human Performance
작업자 68명은 최소 100개의 검증 판단을 거치고 이들의 평균 점수와 표준편차를 계산하여 Human Performance를 측정합니다. 그들이 검증 set 전부를 test하지는 않았기 때문에 우리 데이터셋의 평가 결과와 완전히 비교할 수는 없지만 많은 작업자들의 평균 점수를 나타내고 있습니다.
a) majority : 모든 라벨이 True → 검증과정에서 일부 삭제되면서 True비율이 조금 더 많아졌지만 거의 절반 비율이라는 것을 알 수 있습니다.
b) Text : RNN을 사용하고 MLP를 붙여 prediction → 데이터가 균형있게 분포하므로 majority와 일치합니다.
c) Image : CNN을 사용하고 MLP를 붙여 prediction
language와 vision을 둘 다 사용
CNN + RNN
MAXENT
N2NMN
FiLM
MAC
7. Conclusion
NLVR2는 시각적으로 복잡하고 자연스러운 사진, 인간이 작성한 캡션에 포커스를 맞추고 이전의 말뭉치 데이터셋보다 compositional visual reasoning을 더 어렵게 만드는데 주안점을 두었습니다. 폭넓은 linguistic phenomena를 포함하고 있고 많은 베이스라인으로 실험해봤을 때 상당히 어려운 태스크임을 증명하고 있습니다.
8. Reference
synset : A set of one or more synonyms that are interchangeable in some context without changing the truth value of the proposition in which they are embedded.
논문에 있는 FAQ 중 저번 스터디 때 나왔던 질문 위주(데이터셋에서 문장이 잘 정제되어 있는데 어떻게 하면 좋은 데이터셋을 얻을 수 있을지, 데이터셋을 만드는 과정에서 특별히 어려운 점은 없었는지)로 정리해도록 하겠습니다.
Data Collection Details
sentence writing
Table 8에는 작업자에게 그들의 문장 작성 중 피해야할 점들을 정리해 둔 것입니다. development set에서 100개의 문장을 분석했을 때 지침을 어긴 문장이 13%에 달합니다. 가장 흔하게 위반한 것은 unselected images에 존재하지 않는 객체를 언급하는 것입니다. 그러한 문장은 unselected 페어의 문맥에서 False로 라벨이 되는데 이러한 문장은 이미지에 문장에서 언급된 객체가 존재하지 않으므로 쉽게 False로 판단해버릴 수 있게 됩니다. 예를 들면, 아래 그림에서 hole과 golf flagpole이 존재하지 않기 때문에 모델은 이미지에서 객체간의 공간적인 관계 등을 고려하는 것이 아니라 존재의 유무만 판단해 단순하게 접근하게 될 것입니다.
Data Collection Management
보너스 시스템을 활용하여 작업자들에게 언어적으로 다양한 문장을 작성하도록 독려했습니다. 최소한 75% 이상의 문장이 지침을 잘 준수했다면 그들은 보너스를 많이 받게 됩니다. 50~75% 정도만 지침을 준수했다면 낮은 보너스를 받게 됩니다. 이러한 시스템은 작업자들로 하여금 지침을 잘 준수하게 독려할 수 있었습니다
회사 업무를 보다보면 회사 이메일로 hiworks를 많이 사용하는데요. 이 때, window는 outlook을 많이 사용하고, linux 사용자는 thunderbird를 많이 사용하게 됩니다.
비슷한 prefix를 가진 이메일이 주기적으로 들어와 파일을 다운로드 받아야하는데 이를 자동화할 수 있는 방법이 없을까 하다가 몇몇 코드를 참고하여 작성해보았습니다.
pop3는 pop3 서버로부터 로컬로 이메일을 다운로드 받아보는 형식으로 되어있습니다. hiworks는 pop3서버를 제공하고 있고, 아래 코드에서와 같이 server domain, 본인의 id, password를 적어주면 이메일에 접속하여 본인의 메일을 열람할 수 있습니다. python에서도 poplib라는 라이브러리가 구현이 되어있어 파일을 다운로드 받을 수 있습니다.
주의할 점은 parsing이 잘 되지 않는 부분이 군데군데 있어서 text에 '=?UTF-8?B?' ~~base64 encoding string~~ '?=' 로 쌓여 있는 것들이 있는데 전처리를 잘 해준 다음 base64로 decoding을 해주어야 합니다.
get_email_by_index의 마지막 파라미터로 들어가는 값은 이메일의 번호입니다. 이메일 번호는 가장 옛날 이메일이 1번이라고 보시면 되겠습니다.
import poplib, os
from email.parser import Parser
import base64
# pop3 server domain.
pop3_server_domain = 'pop3s.hiworks.com'
# pop3 server connection object.
pop3_server_conn = None
'''
This method will connect to the global pop3 server
and login with the provided user email and password.
'''
def connect_pop3_server(user_email, user_password):
# use global pop3_server_conn variable in this function.
global pop3_server_conn
# if pop3 server connection object is null then create it.
if(pop3_server_conn is None):
print('********************************* start connect_pop3_server *********************************')
# create pop3 server connection object.
pop3_server_conn = poplib.POP3(pop3_server_domain)
pop3_server_conn.set_debuglevel(1)
# get pop3 server welcome message and print on console.
welcome_message = pop3_server_conn.getwelcome()
print('Below is pop3 server welcome messages : ')
print(welcome_message)
# send user email and password to pop3 server.
pop3_server_conn.user(user_email)
pop3_server_conn.pass_(user_password)
return pop3_server_conn
'''
Close the pop3 server connection and release the connection object.
'''
def close_pop3_server_connection():
global pop3_server_conn
if pop3_server_conn != None:
pop3_server_conn.quit()
pop3_server_conn = None
'''
Get email messages status of the given user.
'''
def get_user_email_status(user_email, user_password):
# connect to pop3 server with the user account.
connect_pop3_server(user_email, user_password)
print('********************************* start get_user_email_status *********************************')
# get user total email message count and email file size.
(messageCount, totalMessageSize) = pop3_server_conn.stat()
print('Email message numbers : ' + str(messageCount))
print('Total message size : ' + str(totalMessageSize) + ' bytes.')
'''
Get user email index info。
'''
def get_user_email_index(user_email, user_password):
connect_pop3_server(user_email, user_password)
print('********************************* start get_user_email_index *********************************')
# get all user email list info from pop3 server.
(resp_message, mails_list, octets) = pop3_server_conn.list()
# print server response message.
print('Server response message : ' + str(resp_message))
# loop in the mail list.
for mail in mails_list:
# print each mail object info.
print('Mail : ' + str(mail))
print('Octets number : ' + str(octets))
'''
Get user account email by the provided email account and email index number.
'''
def get_email_by_index(user_email, user_password, email_index):
connect_pop3_server(user_email, user_password)
print('********************************* start get_email_by_index *********************************')
# retrieve user email by email index.
(resp_message, lines, octets) = pop3_server_conn.retr(email_index)
print('Server response message : ' + str(resp_message))
print('Octets number : ' + str(octets))
# join each line of email message content to create the email content and decode the data with utf-8 charset encoding.
msg_content = b'\r\n'.join(lines).decode('utf-8')
# print out the email content string.
# print('Mail content : ' + msg_content)
# parse the email string to a MIMEMessage object.
msg = Parser().parsestr(msg_content)
# '=?UTF-8?B?', '?='를 삭제 후 그 사이에 있는 것들을 base64, utf8로 디코딩
temp_subject = ''.join([base64.b64decode(m.replace('?=','').encode('utf-8')).decode('utf-8') for m in msg['Subject'].split('=?UTF-8?B?')])
del msg['Subject']
msg['Subject'] = temp_subject
parse_email_msg(msg)
# Parse email message.
def parse_email_msg(msg):
print('********************************* start parse_email_msg *********************************')
parse_email_header(msg)
parse_email_body(msg)
# Delete user email by index.
def delete_email_from_pop3_server(user_email, user_password, email_index):
connect_pop3_server(user_email, user_password)
print('********************************* start delete_email_from_pop3_server *********************************')
pop3_server_conn.dele(email_index)
print('Delete email at index : ' + email_index)
# Parse email header data.
def parse_email_header(msg):
print('********************************* start parse_email_header *********************************')
# just parse from, to, subject header value.
header_list = ('From', 'To', 'Subject')
# loop in the header list
for header in header_list:
# get each header value.
header_value = msg.get(header, '')
print(header + ' : ' + header_value)
# Parse email body data.
def parse_email_body(msg):
print('********************************* start parse_email_body *********************************')
# if the email contains multiple part.
if (msg.is_multipart()):
# get all email message parts.
parts = msg.get_payload()
# loop in above parts.
for n, part in enumerate(parts):
# get part content type.
content_type = part.get_content_type()
print('---------------------------Part ' + str(n) + ' content type : ' + content_type + '---------------------------------------')
parse_email_content(msg)
else:
parse_email_content(msg)
# Parse email message part data.
def parse_email_content(msg):
# get message content type.
content_type = msg.get_content_type().lower()
print('---------------------------------' + content_type + '------------------------------------------')
# if the message part is text part.
if content_type=='text/plain' or content_type=='text/html':
# get text content.
content = msg.get_payload(decode=True)
# get text charset.
charset = msg.get_charset()
# if can not get charset.
if charset is None:
# get message 'Content-Type' header value.
content_type = msg.get('Content-Type', '').lower()
# parse the charset value from 'Content-Type' header value.
pos = content_type.find('charset=')
if pos >= 0:
charset = content_type[pos + 8:].strip()
pos = charset.find(';')
if pos>=0:
charset = charset[0:pos]
if charset:
content = content.decode(charset)
print(content)
# if this message part is still multipart such as 'multipart/mixed','multipart/alternative','multipart/related'
elif content_type.startswith('multipart'):
# get multiple part list.
body_msg_list = msg.get_payload()
# loop in the multiple part list.
for body_msg in body_msg_list:
# parse each message part.
parse_email_content(body_msg)
# if this message part is an attachment part that means it is a attached file.
elif content_type.startswith('image') or content_type.startswith('application'):
# get message header 'Content-Disposition''s value and parse out attached file name.
attach_file_info_string = msg.get('Content-Disposition')
prefix = '?UTF-8?B?'
start_pos = attach_file_info_string.find(prefix)
end_pos = attach_file_info_string.find('?=')
attach_file_name = attach_file_info_string[start_pos + len(prefix): end_pos]
attach_file_name = base64.b64decode(attach_file_name.encode('utf-8')).decode('utf-8')
# get attached file content.
attach_file_data = msg.get_payload(decode=True)
# get current script execution directory path.
current_path = os.path.dirname(os.path.abspath(__file__))
# get the attached file full path.
attach_file_path = current_path + '/' + attach_file_name
# write attached file content to the file.
with open(attach_file_path,'wb') as f:
f.write(attach_file_data)
print('attached file is saved in path ' + attach_file_path)
else:
content = msg.as_string()
print(content)
if __name__ == '__main__':
user_email =
user_password =
get_user_email_status(user_email, user_password)
get_user_email_index(user_email, user_password)
get_email_by_index(user_email, user_password, 1)
close_pop3_server_connection()
기존에는 평가를 하는 AI가 개발되어왔다면, GAN은 두 AI가 게임을 하듯 경쟁하며 성장하는 방식으로 개발됩니다. 예를 들면, 분류 문제에서 Resnet 모델은 강아지 이미지를 보고, 강아지인지, 고양이인지 평가를 내립니다. 반면에, Gan은 Generator라는 AI가 실제 이미지와 유사한 이미지를 생성해내고, 이를 Discriminator라는 AI가 실제 이미지인지, Generator가 생성한 가짜 이미지인지 평가하며 서로 경쟁합니다.
Generator가 임의의 벡터를 input으로 받아 학습 데이터와 유사한 강아지의 사진을 만들어내고, Discriminator가 실제 학습 데이터인지, Generator가 생성한 가짜 강아지 이미지인지 구분을 하게 됩니다. Generator는 Discriminator가 가짜 이미지인지 구분하지 못하도록 정교하게 이미지를 만들어내도록 학습하고, Discriminator는 실제 이미지를 실제 이미지로, 가짜 이미지를 가짜 이미지로 평가하도록 학습합니다.
수식
수식이 복잡해 보이지만 하나씩 뜯어보겠습니다. 진짜는 1, 가짜는 0이라고 가정합니다. 수식에서 x는 실제 이미지, G(z)는 Generator가 생성한 가짜 이미지를 의미합니다. Generator는 Discriminator가 가짜 이미지를 진짜라고 판단하도록 학습해야 합니다. 따라서, D(G(z))=1 이 목표가 될 것입니다. 전체 loss function이 작아지도록 학습을 하게 됩니다. 반면에, Discriminator는 D(x)=1, D(G(z))=0가 목표가 될 것입니다. 따라서 전체 loss function이 커지도록 학습을 하게 됩니다.
여러 언론사에서 쏟아지는 뉴스, 특히 속보성 뉴스를 보면 비슷비슷한 제목의 기사가 많아 정작 필요한 기사를 찾기가 어렵다. Daum 뉴스의 개발 업무를 맡게 된 신입사원 튜브는 사용자들이 편리하게 다양한 뉴스를 찾아볼 수 있도록 문제점을 개선하는 업무를 맡게 되었다.
개발의 방향을 잡기 위해 튜브는 우선 최근 화제가 되고 있는 "카카오 신입 개발자 공채" 관련 기사를 검색해보았다.
카카오 첫 공채..'블라인드' 방식 채용
카카오, 합병 후 첫 공채.. 블라인드 전형으로 개발자 채용
카카오, 블라인드 전형으로 신입 개발자 공채
카카오 공채, 신입 개발자 코딩 능력만 본다
카카오, 신입 공채.. "코딩 실력만 본다"
카카오 "코딩 능력만으로 2018 신입 개발자 뽑는다"
기사의 제목을 기준으로 "블라인드 전형"에 주목하는 기사와 "코딩 테스트"에 주목하는 기사로 나뉘는 걸 발견했다. 튜브는 이들을 각각 묶어서 보여주면 카카오 공채 관련 기사를 찾아보는 사용자에게 유용할 듯싶었다.
유사한 기사를 묶는 기준을 정하기 위해서 논문과 자료를 조사하던 튜브는 "자카드 유사도"라는 방법을 찾아냈다.
자카드 유사도는 집합 간의 유사도를 검사하는 여러 방법 중의 하나로 알려져 있다. 두 집합A,B사이의 자카드 유사도J(A, B)는 두 집합의 교집합 크기를 두 집합의 합집합 크기로 나눈 값으로 정의된다.
예를 들어 집합A= {1, 2, 3}, 집합B= {2, 3, 4}라고 할 때, 교집합A ∩ B= {2, 3}, 합집합A ∪ B= {1, 2, 3, 4}이 되므로, 집합A,B사이의 자카드 유사도J(A, B)= 2/4 = 0.5가 된다. 집합 A와 집합 B가 모두 공집합일 경우에는 나눗셈이 정의되지 않으니 따로J(A, B)= 1로 정의한다.
자카드 유사도는 원소의 중복을 허용하는 다중집합에 대해서 확장할 수 있다. 다중집합A는 원소 "1"을 3개 가지고 있고, 다중집합B는 원소 "1"을 5개 가지고 있다고 하자. 이 다중집합의 교집합A ∩ B는 원소 "1"을 min(3, 5)인 3개, 합집합A ∪ B는 원소 "1"을 max(3, 5)인 5개 가지게 된다. 다중집합A= {1, 1, 2, 2, 3}, 다중집합B= {1, 2, 2, 4, 5}라고 하면, 교집합A ∩ B= {1, 2, 2}, 합집합A ∪ B= {1, 1, 2, 2, 3, 4, 5}가 되므로, 자카드 유사도J(A, B)= 3/7, 약 0.42가 된다.
이를 이용하여 문자열 사이의 유사도를 계산하는데 이용할 수 있다. 문자열 "FRANCE"와 "FRENCH"가 주어졌을 때, 이를 두 글자씩 끊어서 다중집합을 만들 수 있다. 각각 {FR, RA, AN, NC, CE}, {FR, RE, EN, NC, CH}가 되며, 교집합은 {FR, NC}, 합집합은 {FR, RA, AN, NC, CE, RE, EN, CH}가 되므로, 두 문자열 사이의 자카드 유사도J("FRANCE", "FRENCH")= 2/8 = 0.25가 된다.
입력 형식
입력으로는str1과str2의 두 문자열이 들어온다. 각 문자열의 길이는 2 이상, 1,000 이하이다.
입력으로 들어온 문자열은 두 글자씩 끊어서 다중집합의 원소로 만든다. 이때 영문자로 된 글자 쌍만 유효하고, 기타 공백이나 숫자, 특수 문자가 들어있는 경우는 그 글자 쌍을 버린다. 예를 들어 "ab+"가 입력으로 들어오면, "ab"만 다중집합의 원소로 삼고, "b+"는 버린다.
다중집합 원소 사이를 비교할 때, 대문자와 소문자의 차이는 무시한다. "AB"와 "Ab", "ab"는 같은 원소로 취급한다.
출력 형식
입력으로 들어온 두 문자열의 자카드 유사도를 출력한다. 유사도 값은 0에서 1 사이의 실수이므로, 이를 다루기 쉽도록 65536을 곱한 후에 소수점 아래를 버리고 정수부만 출력한다.
예제 입출력
str1 str2 answer
FRANCE
french
16384
handshake
shake hands
65536
aa1+aa2
AAAA12
43690
E=M*C^2
e=m*c^2
65536
전략
문제접근: 자카드 유사도를 사용하기 위하여 문자열을 두 글자씩 끊어서 중복을 허용하는 집합 꼴을 만들어야한다.
풀이: 딕셔너리를 생성하여 두 글자를 key, 그리고 두 글자가 나온 횟수를 value에 저장한다. 교집합의 개수를 먼저 세고 합집합은 개수는 str1의 모든 value와 str2의 모든 value를 더한 후 교집합의 개수를 빼서 구한다.
솔루션(나의 풀이)
from collections import defaultdict
def solution(str1, str2):
str1,str2=str1.lower(),str2.lower()
str1_dic,str2_dic = defaultdict(int),defaultdict(int)
for i in range(len(str1)-1):
element = str1[i:i+2]
if element.isalpha():
str1_dic[element]+=1
for i in range(len(str2)-1):
element = str2[i:i+2]
if element.isalpha():
str2_dic[element]+=1
intersection = 0
for key,value in str1_dic.items():
if str2_dic[key] is not None:
intersection += min(value,str2_dic[key])
union = sum(list(map(lambda x:x[1],str1_dic.items())))+sum(list(map(lambda x:x[1],str2_dic.items())))-intersection
if union ==0:
return 65536
return int(intersection/union * 65536)
파이써닉한 코드
def solution(str1,str2):
str1 = [str1[i:i+2].lower() for i in range(len(str1)-1) if str1[i:i+2].isalpha()]
str2 = [str2[i:i+2].lower() for i in range(len(str2)-1) if str2[i:i+2].isalpha()]
intersection = set(str1) & set(str2)
union = set(str1) | set(str2)
inter = sum([min(str1.count(word), str2.count(word)) for word in intersection])
uni = sum([max(str1.count(word),str2.count(word)) for word in union])
if uni ==0:
return 65536
return int(inter/uni * 65536)