• 문제

XYZ 마트는 일정한 금액을 지불하면 10일 동안 회원 자격을 부여합니다. XYZ 마트에서는 회원을 대상으로 매일 한 가지 제품을 할인하는 행사를 합니다. 할인하는 제품은 하루에 하나씩만 구매할 수 있습니다. 알뜰한 정현이는 자신이 원하는 제품과 수량이 할인하는 날짜와 10일 연속으로 일치할 경우에 맞춰서 회원가입을 하려 합니다.

예를 들어, 정현이가 원하는 제품이 바나나 3개, 사과 2개, 쌀 2개, 돼지고기 2개, 냄비 1개이며, XYZ 마트에서 14일간 회원을 대상으로 할인하는 제품이 날짜 순서대로 치킨, 사과, 사과, 바나나, 쌀, 사과, 돼지고기, 바나나, 돼지고기, 쌀, 냄비, 바나나, 사과, 바나나인 경우에 대해 알아봅시다. 첫째 날부터 열흘 간에는 냄비가 할인하지 않기 때문에 첫째 날에는 회원가입을 하지 않습니다. 둘째 날부터 열흘 간에는 바나나를 원하는 만큼 할인구매할 수 없기 때문에 둘째 날에도 회원가입을 하지 않습니다. 셋째 날, 넷째 날, 다섯째 날부터 각각 열흘은 원하는 제품과 수량이 일치하기 때문에 셋 중 하루에 회원가입을 하려 합니다.

정현이가 원하는 제품을 나타내는 문자열 배열 want와 정현이가 원하는 제품의 수량을 나타내는 정수 배열 number, XYZ 마트에서 할인하는 제품을 나타내는 문자열 배열 discount가 주어졌을 때, 회원등록시 정현이가 원하는 제품을 모두 할인 받을 수 있는 회원등록 날짜의 총 일수를 return 하는 solution 함수를 완성하시오. 가능한 날이 없으면 0을 return 합니다.

 

 

  • 답안(내 풀이)
def solution(want, number, discount):
    answer = 0
    want_dict = {w: n for w, n in zip(want, number)}
    dict_keys = want_dict.keys()
    
    len_dis = len(discount)
    for i in range(len_dis):
        if len_dis - 9 >= i:
            now_dict = want_dict.copy()
            for j in discount[i: i+10]:
                if j in dict_keys:
                    now_dict[j] -= 1
            if all(i <= 0 for i in now_dict.values()):
                answer += 1
    
    return answer

 

  • 답안(더 나은 풀이)
from collections import Counter

def solution(want, number, discount):
    answer = 0
    want_dict = {w: n for w, n in zip(want, number)}

    for i in range(len(discount)-9):
        if want_dict == Counter(discount[i:i+10]): 
            answer += 1

    return answer

 

  • 이 문제의 키워드
    • 10개를 인덱싱할 수 있는 경우만 고려 대상일 때, 이중 for 문으로 고려대상인지를 판단했는데, 하나만으로도 가능
    • Counter는 원소들이 마구 적혀있는 list를 한번에 {key:count} 형태로 바꿔준다. 
    • 무작정 count -= 1로 count를 0을 만들고 비교하려고 하지말고, 그냥 새로 dict를 만들어 '==' 비교하는 방법 고려
  • 문제

한국중학교에 다니는 학생들은 각자 정수 번호를 갖고 있습니다. 이 학교 학생 3명의 정수 번호를 더했을 때 0이 되면 3명의 학생은 삼총사라고 합니다. 예를 들어, 5명의 학생이 있고, 각각의 정수 번호가 순서대로 -2, 3, 0, 2, -5일 때, 첫 번째, 세 번째, 네 번째 학생의 정수 번호를 더하면 0이므로 세 학생은 삼총사입니다. 또한, 두 번째, 네 번째, 다섯 번째 학생의 정수 번호를 더해도 0이므로 세 학생도 삼총사입니다. 따라서 이 경우 한국중학교에서는 두 가지 방법으로 삼총사를 만들 수 있습니다.

한국중학교 학생들의 번호를 나타내는 정수 배열 number가 매개변수로 주어질 때, 학생들 중 삼총사를 만들 수 있는 방법의 수를 return 하도록 solution 함수를 완성하세요.

 

  • 답안
def solution(numbers):
    len_num = len(numbers)

    cnt = 0
    for i in range(len_num):
        for j in range(i+1, len_num):
            for k in range(j+1, len_num):
                if numbers[i] + numbers[j] + numbers[k] == 0:
                    cnt += 1

    return cnt

 

 

  • 이 문제의 키워드
    • 리스트의 길이가 3부터 13까지로 정해져있으므로 완전탐색해도 시간복잡도 기준에 위배되지 않는 상황
    • 시간 복잡도가 크지 않다면 완전탐색(brutal force)으로 해결
    • 조합 combinations를 활용할 수도 있겠지만 이점 많지 않음

도커(docker)란?

도커는 애플리케이션을 컨테이너(container)라는 격리된 환경에서 실행할 수 있도록 해주는 오픈 소스 가상화 기술

가상화 기술은 이전부터 사용했지만(Virtual Machine 등), docker의 장점이 있다.

 

격리 시켜야 하는 이유?

하나의 물리적 시스템 안에서 여러 애플리케이션을 구동하면, 충돌이 일어나거나 관리 어려움(하나의 애플리케이션의 에러로 다른 시스템까지 영향)

 

배포 환경을 옮길 때에도 서버에 이전 서버와 같이 모든 환경 세팅을 새로 해야하는 번거러움 존재(도커로 이식성 증가)

 

도커 vs Virtual Machine(VM, 가상머신)

 

오른쪽 VM의 경우, 격리시키고자 하는 App을 위해 게스트 OS를 각각 올려 리소스를 할당한다.

그러나, 얼마만큼의 리소스를 할당할 것인지 미리 정의해야하고, 그로 인해 낭비되는 리소스 존재 가능성이 있다.

 

왼쪽 docker container를 적용하면, 하나의 OS를 docker를 통해 App 들이 공유하게 되어 리소스를 효율적으로 활용 가능

게스트 OS를 사용하지 않아 가볍고 쉬움.

 

도커 주요 개념 및 특징

  • 컨테이너: 애플리케이션과 그 종속성을 포함하는 실행 가능한 소프트웨어 패키지. 애플리케이션이 동작하는 환경을 코드로 정의
  • 이미지: 컨테이너 실행에 필요한 파일과 설정 값 등을 포함하는 불변의 템플릿. 코드, 런타임, 라이브러리, 환경변수 등
  • 도커 허브: 도커 이미지를 공유할 수 있는 중앙 저장소
  • 도커 파일: 컨테이너 이미지를 빌드하기 위한 명세서

 

도커가 쓰이는 분야

  • 지속적인 통합 및 배포(CI/CD), 데브옵스(DevOps)
  • 애플리케이션 실행 환경 통일
  • 딥러닝 환경 설정과 고정, 통일
  • 마이크로서비스 아키텍처의 독립 개발, 배포

'AI 인공지능 > MLOps' 카테고리의 다른 글

[CI/CD] CI/CD 기초 개념  (0) 2024.04.09
[Wandb] YOLOv8 & Wandb sweep 함께 사용하기  (0) 2024.03.26
  • 문제

햄버거 가게에서 일을 하는 상수는 햄버거를 포장하는 일을 합니다. 함께 일을 하는 다른 직원들이 햄버거에 들어갈 재료를 조리해 주면 조리된 순서대로 상수의 앞에 아래서부터 위로 쌓이게 되고, 상수는 순서에 맞게 쌓여서 완성된 햄버거를 따로 옮겨 포장을 하게 됩니다. 상수가 일하는 가게는 정해진 순서(아래서부터, 빵 – 야채 – 고기 - 빵)로 쌓인 햄버거만 포장을 합니다. 상수는 손이 굉장히 빠르기 때문에 상수가 포장하는 동안 속 재료가 추가적으로 들어오는 일은 없으며, 재료의 높이는 무시하여 재료가 높이 쌓여서 일이 힘들어지는 경우는 없습니다.

예를 들어, 상수의 앞에 쌓이는 재료의 순서가 [야채, 빵, 빵, 야채, 고기, 빵, 야채, 고기, 빵]일 때, 상수는 여섯 번째 재료가 쌓였을 때, 세 번째 재료부터 여섯 번째 재료를 이용하여 햄버거를 포장하고, 아홉 번째 재료가 쌓였을 때, 두 번째 재료와 일곱 번째 재료부터 아홉 번째 재료를 이용하여 햄버거를 포장합니다. 즉, 2개의 햄버거를 포장하게 됩니다.

상수에게 전해지는 재료의 정보를 나타내는 정수 배열 ingredient가 주어졌을 때, 상수가 포장하는 햄버거의 개수를 return 하도록 solution 함수를 완성하시오.

 

  • 답안
def solution(ingredient):
    answer = 0
    stack = []
    
    for i in ingredient:
        stack.append(i)
        if stack[-4:] == [1, 2, 3, 1]:
            answer += 1
            for _ in range(4):
                stack.pop()
    
    return answer

 

 

  • 이 문제 키워드
    • 리스트 원소의 나열이 특정 조건을 만족하면 처리하도록 할 때, stack 구조 활용
    • 만족하는 경우를 제외하고 나머지를 고려하고 싶을 때, pop()으로 제거
    • stack에서 또 for문을 쓰는게 아니라, [-4:]와 같이 고정 인덱싱으로 조건문 확인 가능

리눅스 터미널에서 DB를 다뤄야 할 때가 있다. 평소에는 SQLyog와 같은 GUI 툴을 사용해서 DB설계와 쿼리 입력 등을 하지만, Stored Procedure를 사용한다거나 할 때, 터미널에서 지속적인 실행결과를 보고 싶을 땐 터미널로 실행해야했다.

 

현재, 서비스하고 있는 프로젝트에서 로그 집계하는 부분을 더 의미있게 하기 위해 텀을 5분~10분을 두고 특정 쿼리를 계속 실행하면서 그 텀 동안의 집계 결과 들을 보고 싶었다.

 

따라서 내 개발환경인 WSL2에서 MySQL을 설치하고 DB에 접속해 쿼리를 실행시켜보았다.

 

  • WSL에 MySQL 설치하기
# mysql 설치
sudo apt-get update
sudo apt-get install mysql-server

 

  • MySQL 실행하기

터미널로 mysql을 실행할 때, 미리 mysql을 리눅스에 실행시켜서 계속 돌아가게 해놓고,

내 DB 정보로 접속하는 방식으로 접근한다.

# mysql 실행
sudo systemctl start mysql 

# 혹은
sudo /etc/init.d/mysql start

 

  • 내 서버에 접속하기
# 내 DB 서버 실행
mysql -u <user 이름> -p <DB 계정 주소> -D <DB 이름> -P <포트 번호>
  • 문제

네오와 프로도가 숫자놀이를 하고 있습니다. 네오가 프로도에게 숫자를 건넬 때 일부 자릿수를 영단어로 바꾼 카드를 건네주면 프로도는 원래 숫자를 찾는 게임입니다.

다음은 숫자의 일부 자릿수를 영단어로 바꾸는 예시입니다.

1478 → "one4seveneight"
234567 → "23four5six7"
10203 → "1zerotwozero3"
이렇게 숫자의 일부 자릿수가 영단어로 바뀌어졌거나, 혹은 바뀌지 않고 그대로인 문자열 s가 매개변수로 주어집니다. s가 의미하는 원래 숫자를 return 하도록 solution 함수를 완성해주세요.

 

  • 답안
def solution(s):
    answer = ''
    temp = ''
    num_dict = {'zero': '0', 'one': '1', 'two': '2', 'three': '3', 'four': '4',
               'five': '5', 'six': '6', 'seven': '7', 'eight': '8', 'nine': '9'}
    for i in s:
        if i.isdigit():
            answer += i
        else:
            temp += i
            if temp in num_dict:
                answer += num_dict[temp]
                temp = ''
    return int(answer)

 

  • 이 문제의 키워드
    • 문자열을 숫자로 변환하도록 미리 dictionary 선언
    • temp를 설정 해놓고, 완성되면 리셋하는 로직 잘 사용하자
  • 문제

문자열 s에는 공백으로 구분된 숫자들이 저장되어 있습니다. str에 나타나는 숫자 중 최소값과 최대값을 찾아 이를 "(최소값) (최대값)"형태의 문자열을 반환하는 함수, solution을 완성하세요.
예를들어 s가 "1 2 3 4"라면 "1 4"를 리턴하고, "-1 -2 -3 -4"라면 "-4 -1"을 리턴하면 됩니다.

return
"1 2 3 4" "1 4"
"-1 -2 -3 -4" "-4 -1"
"-1 -1"  "-1 -1"

 

  • 답안
def solution(s):
    answer = ''
    nums = s.split(' ')
    num_list = list(map(int, nums))
    
    min_num = min(num_list)
    max_num = max(num_list)
    
    answer = f'{min_num} {max_num}'
    return answer

 

  • 이 문제의 키워드
    • 문자열을 여러 요소로 나눈다던가, 공백을 제거해주는 과정이 필요할 때, s.split(' ') 이용하기
    • int(a), int(b) 여러번 쓸 필요없이 map(int, list) 로 한번에 처리해주자
    • map은 <map ~~> 객체로 저장되므로 list() 해주자
    • 쉽지만 문자열을 다루는 법과 map의 사용에 대해 다시 한번 생각해 볼 수 있는 문제
  • 문제

효진이는 멀리 뛰기를 연습하고 있습니다. 효진이는 한번에 1칸, 또는 2칸을 뛸 수 있습니다. 칸이 총 4개 있을 때, 효진이는
(1칸, 1칸, 1칸, 1칸)
(1칸, 2칸, 1칸)
(1칸, 1칸, 2칸)
(2칸, 1칸, 1칸)
(2칸, 2칸)
의 5가지 방법으로 맨 끝 칸에 도달할 수 있습니다. 멀리뛰기에 사용될 칸의 수 n이 주어질 때, 효진이가 끝에 도달하는 방법이 몇 가지인지 알아내, 여기에 1234567를 나눈 나머지를 리턴하는 함수, solution을 완성하세요. 예를 들어 4가 입력된다면, 5를 return하면 됩니다.

 

  • 답안
def solution(n):
    dp = [0] * (n+3)
    dp[1] = 1
    dp[2] = 2
    
    for i in range(3, n+3):
        dp[i] = dp[i-2] + dp[i-1]
    return dp[n] % 1234567

 

 

  • 이 문제 키워드
    • 피보나치로 해결. 재귀를 사용하면 시간효율성 낮고, depth 조절을 해야된다. DP로 하면 반복 범위가 정해지는 효과
    • 피보나치를 판단하는 방법은 각 경우를 계산해서 1, 2, 3, 5, 8, 13,,,, 해보니 피보나치! 이게 아님
    • 5칸의 경우: * -> 점프를 의미
      • Case 1: * * * * / *       4번째 점프까지하고 1칸 건너는 한가지 경우 (n-1)항 경우의수 x 1
      • Case 2: * * * / * *       3번째 점프까지하고 2칸 건너는 한가지 경우 (n-2)항 경우의수 x 1 
      • 그래서 5번째 칸까지 건너는 경우의 수가 (n-1)항 경우의수x1 + (n-2)항 경우의수x1 이 된 것! -> 피보나치
    • 다만, for i in range(3, n+3): 처럼 범위 지정할 때, 시작점이 끝점보다 작은지 꼭 확인하자.
  • 문제

얀에서는 매년 달리기 경주가 열립니다. 해설진들은 선수들이 자기 바로 앞의 선수를 추월할 때 추월한 선수의 이름을 부릅니다. 예를 들어 1등부터 3등까지 "mumu", "soe", "poe" 선수들이 순서대로 달리고 있을 때, 해설진이 "soe"선수를 불렀다면 2등인 "soe" 선수가 1등인 "mumu" 선수를 추월했다는 것입니다. 즉 "soe" 선수가 1등, "mumu" 선수가 2등으로 바뀝니다.

선수들의 이름이 1등부터 현재 등수 순서대로 담긴 문자열 배열 players와 해설진이 부른 이름을 담은 문자열 배열 callings가 매개변수로 주어질 때, 경주가 끝났을 때 선수들의 이름을 1등부터 등수 순서대로 배열에 담아 return 하는 solution 함수를 완성해주세요.

 

입출력 예
players callings result
["mumu", "soe", "poe", "kai", "mine"] ["kai", "kai", "mine", "mine"] ["mumu", "kai", "mine", "soe", "poe"]

 

  • 답안
def solution(players, callings):
    answer = []
    pl_dict = {value: idx for idx, value in enumerate(players)}
    
    for call in callings:
        ord_now = pl_dict[call]
        ord_fast = ord_now - 1 
        
        players[ord_now] = players[ord_fast] 
        players[ord_fast] = call
        
        pl_dict[call] -= 1
        pl_dict[players[ord_now]] += 1
        
    answer = players
    
    return answer

 

  • 이 문제 키워드
    • 해시로 해결하는 문제였음. 그러나, dict 는 key -> value 기능이 있지만 value -> key 조회 기능은 따로없음
    • key -> value 조회를 위해 dict를 설정하고, value -> key로 값 조회, 수정 할 때는 list에 작업
    • dict 에서 순위(value) 값에 변화를 주고, list에서 이 순위(index)를 조회해 변화를 주는 방향을 활용하자

Wandb란?

  • wandb는 ML/AI 모델 학습 시, 학습 상황을 tracking(추적) 하여 진행이 잘 되고 있는지 확인할 수 있는 툴
  • 또한, 여러 하이퍼파라미터 조합으로 학습을 시도하여 최적의 하이퍼파라미터를 찾아내도록 돕는 툴
  • 학습 결과를 기록하고 관리하여 성능 개선을 돕는 툴

 

wandb와 yolov8 (ultralytics) 모델 함께 사용하기

import wandb
wandb.login()

from ultralytics import YOLO
import ultralytics
from wandb.integration.ultralytics import add_wandb_callback
  • wandb.login() -> wandb 사이트에서 API 확인 -> 입력

 

wandb.init(project='sample_project', job_type='training')
  • wandb.init() 으로 어떤 프로젝트인지, 어떤 작업으로 wandb를 동작하게 할건지 설정

 

sweep_config = {
    'method': 'random',
    'metric': {'goal': 'maximize', 'name': 'metrics/mAP50(B)'},
    'parameters': {
        'batch_size': {
            'values': [16, 32, 64]
        },
        'learning_rate': {
            'distribution': 'uniform',
            'min':0.0001,
            'max':0.001
        } 
    }
}
  • 내가 하고 싶은 건 'sweep' 이므로 sweep_config를 설정
  • sweep은 한 모델만의 학습을 wandb로 모니터링하는 게아니라, 여러 하이퍼파라미터들을 바꿔가며 알아서 여러번 학습하고, 그 결과를 저장/비교할 수 있도록 한 것

  • learning rate decay, category, learning rate 라는 초매개 변수들의 여러 조합을 변화시키면서 학습할 때,
  • 각 학습의 결과 Accuracy가 가장 높은 조합은 무엇인지 찾아낼 수 있음

 

  • 내 config 에서는 batch_size와 learning_rate만 조절

 

sweep_id = wandb.sweep(sweep=sweep_config, project='sds')
def yolo_train():
    with wandb.init() as run:
        config = wandb.config

        # 모델 정의 및 하이퍼파라미터 사용
        model = YOLO('yolov8n.pt')
        add_wandb_callback(model, enable_model_checkpointing=True)
        results = model.train(data='/data/sds/datasets/version_1/data.yaml', name='version_1_',
                              plots=True, batch=config.batch_size, epochs=10, imgsz=640,
                              cos_lr=True, lrf=0.01, lr0=config.learning_rate)
        wandb.log({
            'epoch': epoch, 
            'val_loss': val/dfl_loss
        })
  • 자동으로 매번 학습을 시도할 함수를 정의해줌
  • batch=config.batch_size 처럼 변화를 주면서 학습시킬 변수를 설정해놓음
  • 나중에 학습 과정에서 로그를 남기고 싶은 값은 wandb.log로 설정해놓음 

 

wandb.agent(sweep_id, function=yolo_train)
  • 이제 wandb.agent로 알아서 yolo_train이라는 함수를 사용해서 학습을 시켜라 라고 지정해놓으면 끝

 

 

 

왼쪽 탭에 sweep들 (파라미터 조합을 여러가지로 시도해서 학습하고 있는 각 모델 경우의 수)로

알아서 agent가 잘 학습하고 있는 것을 보여준다. 이제 학습이 끝나고 가장 mAP가 높았던 파라미터를 최적으로 선정하면 끝!

'AI 인공지능 > MLOps' 카테고리의 다른 글

[CI/CD] CI/CD 기초 개념  (0) 2024.04.09
[Docker] Docker란 무엇이고 왜 쓸까?  (0) 2024.04.02

+ Recent posts