반응형

 

이번 글에서는 백준 26069번 문제를
hashSet를 활용해서 풀어보자

 

 

문제 핵심

  • N : 사람들이 만난 기록 수
  • 최대 길이 20의 문자열인 다른 문자열 A와 B가 공백과 함께 한줄에 주어진다.
  • ChongChong 기록에서 1회 이상 주어짐
  • ChongChong을 만난 사람은 무지개 댄스에 감염
    • ( ChongChong과 chongchong은 다른 이름 )

풀이 과정

ChongChong을 만난 순간 무지개 댄스를 추게 된다.

즉, 무지개 댄스를 한 명이라도 추고 있다면 모두 HashSet에 넣는다.

(Hash)Set에서는 중복값을 제외시켜 저장하기 때문에 모든 입력이 끝난 후  HashSet의 크기를 측정 한다. 

 

핵심 코드

1. 한 줄씩 읽어온다.

2. 두 사람 모두 댄스를 무지개 댄스를 추는지 확인

3. 한 명이라도 춘다면 HashSet에 삽입

        rainbowDanceHumans.add("ChongChong");
        for (int i = 0; i < N; i++) {
           stringTokenizer = new StringTokenizer(bufferedReader.readLine());
           String firstPerson = stringTokenizer.nextToken();
           String secondPerson = stringTokenizer.nextToken();

           if(rainbowDanceHumans.contains(firstPerson) || rainbowDanceHumans.contains(secondPerson)) {
               rainbowDanceHumans.add(firstPerson);
               rainbowDanceHumans.add(secondPerson);
           }
        }

 

Set에 특성만 활용한다면 아주 쉽게 풀 수 있는 문제이다.

(추가) 다들 환절기에 몸 조심 하시길 바랍니다.
두통에 2일 간 작성하지 못했네요..
백준 문제뿐만 아니라 Java class나 다양한 예제로 찾아 뵙겠습니다.

 

전체 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stringTokenizer;
        int N = Integer.parseInt(bufferedReader.readLine());

        HashSet<String> rainbowDanceHumans = new HashSet<>();

        rainbowDanceHumans.add("ChongChong");
        for (int i = 0; i < N; i++) {
           stringTokenizer = new StringTokenizer(bufferedReader.readLine());
           String firstPerson = stringTokenizer.nextToken();
           String secondPerson = stringTokenizer.nextToken();

           if(rainbowDanceHumans.contains(firstPerson) || rainbowDanceHumans.contains(secondPerson)) {
               rainbowDanceHumans.add(firstPerson);
               rainbowDanceHumans.add(secondPerson);
           }
        }

        System.out.println(rainbowDanceHumans.size());

    }
}
반응형
반응형
이번글에서는 백준 25192번 문제를 통해 집합과 맵에 대해 알아보고자 한다.
원래는 집합과 맵에 대해 설명하고 문제를 포스팅하지만,
해시, 트리, 맵, 집합 자바에서는 함께 사용되기 때문에
추후에 포스팅 하려고 한다.


문제 핵심

  • N : 채팅방 총 기록 수
  • ENTER : 사람의 입장
  • 나머지 : 유저의 닉네임
  • 구하려는 값 : 곰곰티콘 사용횟수

풀이 과정

채팅 봇으로 ENTER이후 처음으로 나타난 닉네임은 인사하는 곰곰티콘이다.

즉, 동일한 닉네임이 두번 나타났다면 일반채팅으로 곰곰티콘이 아니다.

우리는 곰곰티콘을 사용하는 순간으로 ENTER 이후 나타나는 닉네임을 한 번씩 체크하면 된다.

즉, Set 클래스를 이용해서 중복없이 저장하여 그 크기를 통해 알 수 있다.

 

핵심 코드

1. 한 줄씩 읽어온다.

2. ENTER인 HashSet을 초기화

3. 존재하지 않는 경우에만 값을 넣고 카운트 증가

HashSet<String> nickNames = new HashSet<>();

for (int i = 0; i < recordCount; i++) {

    String line = bufferedReader.readLine();

    if(line.equals("ENTER")) {
        nickNames.clear();
        continue;
    }

    if(!nickNames.contains(line)){
        nickNames.add(line);
        bearGreetCount++;
    }
}

 

다른 풀이

HashSet 클래스를 사용하며 Set이라는 클래스 의미를 생각하면 사실 위 코드는 Set 보다는 Hash를 이용하여 값을 빨리 찾아내는 방식이다. Set이라는 클래스도 모두 이용하려면 다음과 같다.

 

1. ENTER가 나온 경우에만 지금까지 넣었던 값 개수를 카운트 해주고 초기화 한다.

2. 이후 반복문이 종료된 이후에는 카운트 되지 않은 값들이 남아있기 때문에 추가로 카운트 한다.

HashSet<String> nickNames = new HashSet<>();

for (int i = 0; i < recordCount; i++) {

    String line = bufferedReader.readLine();

    if(line.equals("ENTER")) {
    	bearGreetCount += nickNames.size();
        nickNames.clear();
        continue;
    }

    nickNames.add(line);

}

bearGreetCount += nickNames.size();

 

이번 문제에서 사용한 HashSet 또한 다른 문제 풀이에 자주 사용되는 클래스이다.
Hash, Set, Map, Tree 등 다양한 구조를 이해하는 것이 좋다.
기본 구조로써 기본 개념을 숙지하고 넘어가자.

 

전체 코드

package org.example;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

        int recordCount = Integer.parseInt(bufferedReader.readLine());

        int bearGreetCount = 0;

        HashSet<String> nickNames = new HashSet<>();

        for (int i = 0; i < recordCount; i++) {

            String line = bufferedReader.readLine();

            if(line.equals("ENTER")) {
                nickNames.clear();
                continue;
            }


            if(!nickNames.contains(line)){
                nickNames.add(line);
                bearGreetCount++;
            }
        }

        System.out.println(bearGreetCount);
    }
}

반응형
반응형
이번 글에서는 백준 1037번 문제를 통해,
알고리즘 기본 문제 단골인 소수, 약수, 공배수 들 중 하나인 약수를 활용하여 문제를 풀어보자

문제 핵심

  • 첫째 줄 : 약수의 개수
  • 둘째 줄 : 약수들
  • 구하려는 정수 : N
    • ( 풀이 과정에서는 구하려는 정수 N을 x라고 설명 )

풀이 과정

자연수 x을 구하려면 1과 x을 제외한 주어진 약수 중에서 선택해야 한다.

만약 이 때 조건과 같이 30의 약수를 차례대로 나열하면 [ 2, 3, 5, 6, 10, 15 ] 이다.

그렇다면 양쪽에서 순서대로 모두 짝지어서 곱한다면 x인 30을 구할 수 있다.

즉, 제일 큰 값과 작은 값을 구한 후 곱해주면 된다.

 

핵심 코드

for (int i = 0; i < N; i++) {
    int num = Integer.parseInt(stringTokenizer.nextToken());
    if(num > max) {
        max = num;
    }
    if(num < min) {
        min = num;
    }
}

 

예외

9와 같은 약수는 1과 9는 제공되지 않기 때문에 3 숫자 하나만 주어진다.

이때 3의 제곱이 x가 되므로 첫째 줄이 1이라면 제곱해준다.

if(N == 1) {
    int num = Integer.parseInt(bufferedReader.readLine());
    stringBuilder.append(num * num);
}

 

단계적 풀이에서 브론즈 문제 수준으로
사실 비교적 아주 쉬운 문제이다.

이제는 마지막 브론즈 문제로 앞으로는 실버 상위부터 골드 문제가 다량으로 나올 예정이다.
지금까지 기초와 개념을 활용한다면 문제없이 풀 수 있을 것이다.


전체 코드

package org.example;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stringTokenizer;
        StringBuilder stringBuilder = new StringBuilder();
        int N = Integer.parseInt(bufferedReader.readLine());

        if(N == 1) {
            int num = Integer.parseInt(bufferedReader.readLine());
            stringBuilder.append(num * num);
        } else {
            int max = 0;
            int min = Integer.MAX_VALUE;

            stringTokenizer = new StringTokenizer(bufferedReader.readLine());
            for (int i = 0; i < N; i++) {
                int num = Integer.parseInt(stringTokenizer.nextToken());
                if(num > max) {
                    max = num;
                }
                if(num < min) {
                    min = num;
                }
            }

            stringBuilder.append(max * min);
        }

        System.out.println(stringBuilder);

    }
}
반응형
반응형
이번 글에서는 백준 110번 문제를 통해 이항계수를 활용하고
최대 값을 넘어서는 순간을 다루는 방법을 알아보자.

문제 핵심

  • 서쪽 ( N )
  • 동쪽 ( M )
  • N <= M
  • 최대 한 개의 다리만 연결

풀이 과정

이 문제는 조합론으로 mCn이라고 할 수 있다.

다만 M의 최대값이 30일 때 factorial의 값은 다음과 같다.

 

  • 1! = 1
  • 2! = 2
  • 3! = 6
  • 4! = 24
  • 5! = 120
  • 6! = 720
  • 7! = 5040
  • 8! = 40,320
  • 9! = 362,880
  • 10! = 3,628,800
  • 11! = 39,916,800
  • 12! = 479,001,600
  • 13! = 6,227,020,800
  • 14! = 87,178,291,200
  • 15! = 1,307,674,368,000
  • 16! = 20,922,789,888,000
  • 17! = 355,687,428,096,000
  • 18! = 6,402,373,705,728,000
  • 19! = 121,645,100,408,832,000
  • 20! = 2,432,902,008,176,640,000
  • 21! = 51,090,942,171,709,440,000
  • 22! = 1,124,000,727,777,607,680,000
  • 23! = 25,852,016,738,884,976,640,000
  • 24! = 620,448,401,733,239,439,360,000
  • 25! = 15,511,210,043,330,985,984,000,000
  • 26! = 403,291,461,126,605,635,584,000,000
  • 27! = 10,888,869,450,418,352,160,768,000,000
  • 28! = 304,888,344,611,713,860,501,504,000,000
  • 29! = 8,841,761,993,739,701,954,543,616,000,000
  • 30! = 265,252,859,812,191,058,636,308,480,000,000

한계는 이미 초과했다.

이 때 우리는 각 프로그래밍언어에서 제공하는 클래스를 사용하면 된다.

Java에서는 BigInteger가 있다.

 

핵심 코드

이항 계수1 문제 처럼 나왔지만 이번에는 동쪽이 숫자가 항상 크다.

( 값을 받을 때 반대로 받았다. 헷갈리지 않게 N M으로 선언하는게 좋다. )

int T = Integer.parseInt(bufferedReader.readLine());
int N, K;
BigInteger result;

for (int i = 0; i < T; i++) {
    stringTokenizer = new StringTokenizer(bufferedReader.readLine());
    K = Integer.parseInt(stringTokenizer.nextToken());
    N = Integer.parseInt(stringTokenizer.nextToken());


    result = factorial(N).divide(factorial(K).multiply(factorial(N - K)));
    stringBuilder.append(result).append("\n");
}

 

 

이번 문제는 자료형의 최대값을 한참 넘어서기 때문에,
그 부분만 해결할 수 있다면 이전 이항계수 문제를 활용해 아주 쉽게 풀 수 있는 문제이다.

꼭 BigInteger같은 클래스를 사용하지 않더라도 비슷한 방식으로
직접 문자로 만들어서 처리하는 방법도 존재한다.

 

전체코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.StringTokenizer;

public class Main {

    public static BigInteger factorial(int n) {
        BigInteger result = BigInteger.ONE;
        for (int i = 2; i <= n; i++) {
            result = result.multiply(BigInteger.valueOf(i));
        }
        return result;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stringTokenizer;
        StringBuilder stringBuilder = new StringBuilder();

        int T = Integer.parseInt(bufferedReader.readLine());
        int N, K;
        BigInteger result;

        for (int i = 0; i < T; i++) {
            stringTokenizer = new StringTokenizer(bufferedReader.readLine());
            K = Integer.parseInt(stringTokenizer.nextToken());
            N = Integer.parseInt(stringTokenizer.nextToken());


            result = factorial(N).divide(factorial(K).multiply(factorial(N - K)));
            stringBuilder.append(result).append("\n");
        }

        System.out.println(stringBuilder);
    }
}
반응형
반응형

 

인공지능 모델 선정 중 무겁지 않고, 빠른 학습이 가능한 모델을 찾아보다
MobileNet을 보게 되었고, 여러 블로그와 논문을 보고 정리하는 글이다.

 

 

MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications

We present a class of efficient models called MobileNets for mobile and embedded vision applications. MobileNets are based on a streamlined architecture that uses depth-wise separable convolutions to build light weight deep neural networks. We introduce tw

arxiv.org

MobileNet ( 경량 모델 )

  • 모바일 및 임베 디드 비전 애플리케이션 용
  • 깊이별로 분리 가능한 컨볼류션 사용 - 가벼운 깊이를 구축
  • 소규모 네트워크에 관한 많은 논문들은 크기에만 초점을 맞추고 속도는 고려하지 않는데, MobileNet은 속도를 최적화 하는데 중점을 둔다. ( 물론 소규모 네트워크도 제공 하면서 )

Depthwise Separable Convolution

  • MobileNet Model은 Depthwise Separable Convolution을 기반으로 한다.
  • 표준 컨볼루션 ( standard convolution)을 두 단계로 분리하는 방법입니다.
  • 두 단계는 Depthwise Convolution과 1 x 1 PointWise Convolution 이다.
    • Depthwise Convolution: 각 입력 채널에 단일 필터를 적용합니다. 즉, 각 채널은 독립적으로 필터링됩니다.
      • 필터 적용시 Input채널의 개수와 필터의 수는 동일하게 작동합니다.
    • Pointwise Convolution: 이 후, 1×1 크기의 필터를 사용하여 Depthwise Convolution의 출력들을 결합합니다.

기존 표준 컨볼루션( Standard Convolution )의 총 연산량

  • Dk : 입력 값의 크기
  • M : 채널 입력 수
  • N : 출력 채널 수
  • Df : filter 크기

Depthwise Separable Convolution

  • Depthwise Convolution은 각 채널에 대해 개별적인 필터를 적용

Depthwise Separable Convolution

  • Depthwise Convolution은 각 채널에 대해 개별적인 필터를 적용

Depthwise Separable Convolution + Pointwise Convolution 연산량


Mobile Net Body Architecture

  • 초반부: 3×3 Conv + Depthwise Separable Convolution로 채널 확장
  • 중반부: Depthwise Conv를 반복해 특징 추출
  • 후반부: 1×1 Pointwise Conv로 연산량 줄이고 Global Average Pooling 적용
  • 마지막: Fully Connected Layer + Softmax로 최종 분류

 

  • 전체 연산량 중 **1×1 컨볼루션 (Pointwise Conv)**이 94.86% 차지 → 모델 최적화의 핵심!

Width Multiplier ( α )

  • 경량 네트워크이긴 하나, 특정 사용 사례에 맞춰 더 작은 모델이 필요할 수 있다.
  • Width Multiplier (α) 라는 매개변수를 도입하여 네트워크의 전체적인 채널 수를 줄이는 방식을 사용.
  • 특정 레이어에서 **입력 채널 수(M)과 출력 채널 수(N)**를 α 배로 줄임.
    • 입력 채널: αM
    • 출력 채널: αN

ex) 기본 MobileNet에서 어떤 레이어의 필터 수가 64라면,

  • α = 1.0 (기본 모델) → 64개
  • α = 0.75 → 48개
  • α = 0.5 → 32개
  • α = 0.25 → 16개

MobileNet의 연산량과 파라미터 수는 α² 비율로 감소.

  • 예를 들어 α = 0.5로 설정하면, 기본 모델 대비 연산량과 파라미터 수가 약 1/4로 줄어듦.

Resolution Multiplier ( ρ )

  • MobileNet에서 입력 이미지와 내부 feature map 크기를 줄이는 하이퍼파라미터입니다.
  • 연산량이 ρ² 비율로 감소 (해상도가 줄어들면 픽셀 개수가 제곱 비율로 감소하기 때문)

ex ) 이미지 해상도가 224, 224 라면,

  • ρ = 1.0 → 해상도 유지 (224×224)
  • ρ = 0.75 → 해상도가 75%로 감소 (192×192)
  • ρ = 0.5 → 해상도가 50%로 감소 (112×112)
  • ρ = 0.25 → 해상도가 25%로 감소 (56×56)

트레이드 오프 ( Trade-off )

  • α를 줄이면 모델 크기가 감소하여 속도가 빨라지지만, 정확도도 떨어질 수 있음.
  • 적절한 α 값을 선택하여 성능과 속도의 균형을 맞추는 것이 중요.
  • 연산량이 ρ² 비율로 감소하여 속도가 빨라지지만, 정확도도 떨어질 수 있음.
  • 하지만 해상도가 줄어들면 모델의 정확도가 낮아질 가능성이 있음

 


무거운 모델에 비해 다소 정확도가 낮을 수 있지만 ,
소규모 모델은 빠른 속도와 더 적은 리소스를 요구하는 장점이 있다.

이러한 점들을 고려하여 현재 모델을 배포할 환경에 맞는 선택을 하는 것이 중요하다.
또한, 데이터 가공 역시 중요한 요소이므로 이를 병행하여 고려하는 것이 좋다.
반응형
반응형
내 컴퓨터를 시작으로 지인 컴퓨터 3대 조립하였다.
이번에도 지인 컴퓨터 주문이 또 들어왔다.
이번에는 그림작업 위주의 사양에 맞추긴 하지만, 
금액적 한계로 본체 + 모니터 약 200 안으로 맞추기로 하였다.

 

부품 선정

조립시 필요한 부품은 다음과 같다. ( 본체 )

  1. CPU
  2. 메인보드
  3. 그래픽 카드
  4. CPU 쿨러
  5. 램 카드
  6. ssd
  7. 파워
  8. 케이스

주로 다나와 PC견적에서 부품 선정 후 가격비교로 하나씩 구매하는 방식으로 구매하고 있다. 총 실 구매가 ( 카드 - 188만 )

 

선정 순서

선정 하기 전에 항상 부품 시장 조사를 하는게 아니다 보니 이런 요청이 있을 때마다 단기적으로 찾아보곤 한다.

그 때마다 즐겨보는 유튜버가 있는데 정말 초보자도 이해가 쉽게 엑셀로 정리하여 설명해주신다. 한 번 참고하길 바란다.

( 저렇게 엑셀에 하나하나 정리한 거 보고 참 대단한 사람이다 느낀다 )

 

 

1. 메인 보드 + CPU + 쿨러

메인보드와 CPU 호환성, CPU에 온도에 맞는 쿨러 선정이 우선시 된다고 생각하기에 항상 처음으로 선정한다.

CPU는 주로 I사을 선호한다.

( 물론 작업만 할 경우 높은 가격의 제품들이 필요하고 그럴 경우 멀티코어의 R사도 추천하지만,

진짜 작업만 할거라면 아예 A사 제품을 추천한다. )

그러나 겸사겸사 게임도 하고 여러 취미 생활과 중간 레벨(가격) 때문에 I사이 복합적으로 괜찮다.

 

메인보드는 항상 A사를 선호하는 편이다.

메인보드은 M사 와 A사를 생각하는데 같은 기판 기준 A사가 좀 더 낫다는 평이 많기도 하고, 쿨링 면에서도 우수하다.

( 실제로 A사 노트북을 사용했는데 온도 관리 측면에서 만족했다. )

다만 요즘 A/S 문제가 있다고 하는데 이 점 참고하면 되겠다.

 

쿨러는 공냉과 수냉 선택이 먼저인데, 내 컴퓨터는 수냉이지만, 조립 요청한 지인들 모두 공냉으로 맞춰드렸다.

이유는 둘 다 물론 관리 해야하지만, 할 줄 모른다면 공냉이 가격적으로도 괜찮다.

공냉도 영상에서 본 것으로 추천한다.

2. 그래픽 카드 ( + 램 카드 )

이후 가격에 맞춰서 그래픽 카드를 선정한다.

 

R사 제품도 괜찮다는 평이 몇 년전부터 있긴 했으나 N사에 대한 개인적인 생각이 확고하기 때문에 N사를 선호한다.

아직까지는 하드웨어가 비슷한 수준이라고 할 수 있으나 소프트웨어 측면에서는 아직까지는 R사는 힘들지 않나 라고 생각한다. 그만큼 지원이 많이 되는 N사 소프트웨어를 생각했을 때, 좀 더 가격을 주더라도 N제품을 사용할 것이다.

 

램 카드는 요즘 발전하는 속도에 비하면 최소 16G 이상이다. 여유가 있다면 32G 가 좋다.

사실 브랜드는 S사를 선호 했지만, 사실 오버 쿨럭(높은 성능) 할게 아니라면 가성비를 챙기는 게 좋은 것 같다.

때문에 영상에서 본 T사 추천한다. 

3. 파워와 그 외 나머지

솔직히 파워는 S사이다. 품질상으로는 좋다고 느끼지만 가격이 비싼거는 사실이다.

같은 크기의 파워로 놓고 봤을 때 가성비를 찾는다면 M사 정도로 생각하며, 파워 전력이 올라간다면 높은 등급을 고려했으면 한다.

 

SSD카드도 가성비를 놓고 보면 많지만, 현재 IT 세계에서 제일 중요한 것은 데이터다.

물론 그 데이터를 인터넷에서 지키는 것도 중요하지만, 하드웨어적으로도 높은 품질을 유지해야하기 때문에 S사 추천한다.

 

케이스는 요즘 그래픽 카드를 생각했을 때 튼튼한게 최고다.

요즘은 그래픽 카드 지지대도 포함되서 온다.

처음 조립 당시에는 그런게 없었는데..

가격대 별로 있으나 여러개 있으나 D사 추천한다.

 

조립

이번에는 나름 선 정리한다고 해서 정리해 보았다.

 

작동 테스트

 

인식 확인

그래픽 드라이버 확인

매번 150 ~ 200만원의 부품들이 왔다갔다 하는데
솔직히 이제는 익숙하다고 해도 무섭다. 실수라도 하면 수십만원이 사라진다.
( 실제로 본인 본체 업그레이드 중 기존의 SSD 카드 저장 메모리 부분을 깨먹어서 날려먹었다... )
지인 컴퓨터에서는 실수한 적 없으나 다들 조심하여 조립하기 바란다.

더 나은 조립이나 의견있으면 댓글로 남겨주시면 감사하겠습니다.  
반응형
반응형

 

다음 문제를 통해 재귀 함수와 이항계수를 활용해 보자.

 

문제 핵심

  • N : 자연수
  • K : 정수

풀이 과정

이항 계수는 다음과 같은 공식에 따라 이루어 진다. 이걸 코드로 구현해 보자.

재귀 방식

재귀를 통해 factorial를 해결한다.

public static int factorial(int n){
    if(n == 1){
        return 1;
    }
    return factorial(n-1) * n;
}

반복문 방식

반복문을 통해 factorial를 해결한다.

public static int factorial(int n){
    int result = 1;
    for (int i = n; i > 1; i--){
        result *= i;
    }
    return result;
}

 

 

int result = factorial(N) / (factorial(K) * factorial(N - K));

 

재귀 방식은 위와 같이 StackOverflow현상이 일어났다.
자바의 JVM의 기준 스택 크기의 한계 때문에 재귀 함수 호출 과정에서
StackOverflow현상이 일어난 것으로 보인다.

때문에 반복문을 통해 이를 해결하였다.
반응형
반응형
인공지능 모델을 만들었다면, 모델을 저장하고 활용할 수 있어야 한다.
이를 위해 Docker를 통해 이미지 빌드하고 Container를 실행하는 과정까지 
포스팅 하려고 한다.

이번 글에서는 이미 저장한 모델을 가지고 Docker 중점에서 작성된 글이다.

 


 

Docker 설치

다음 사이트를 통해 Docker를 다운 받아준다.

 

Docker Desktop: The #1 Containerization Tool for Developers | Docker

Docker Desktop is collaborative containerization software for developers. Get started and download Docker Desktop today on Mac, Windows, or Linux.

www.docker.com

 

 

 

 

설치가 완료되었다면 컴퓨터를 다시 시작 해준다.

 

모델 로드 확인 ( app.py )

다음과 같은 코드를 통해 모델 로드를 확인한다.

모델을 저장할 때 환경과 동일하게 구성해야 함으로 그 때 사용했던 가상환경을 불러와 실행해 준다.

모델을 파일로 저장했다면 - 파일경로
model = tf.keras.models.load_model("model/모델 파일 경로")​

 

모델을 h5 파일로 저장했다면 - 파일
model = tf.keras.models.load_model("model/모델 파일 명.h5")​

 

from flask import Flask, request, jsonify
import tensorflow as tf
import numpy as np
import cv2
import os

# app = Flask(__name__)

model = tf.keras.models.load_model("./모델 경로")
print(model.summary())

 

 

flask HTTP 통신 테스트 ( app.py )

이미지 분류모델을 불러오고, 이미지를 보편적 크기인 ( 224, 224, 3 )로 resize 해준다.

이미지를 분류모델에 맞게 예측한 후 결과를 출력해 준다.

이 때 통신할 접속 경로는 (로컬호스트:5002/predict) 이다.

from flask import Flask, request, jsonify
import tensorflow as tf
import numpy as np
import cv2
from io import BytesIO
from PIL import Image

app = Flask(__name__)
model = tf.keras.models.load_model("./모델 경로")

def preprocess_image(image):
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    image = cv2.resize(image, (224, 224))
    image = image.astype(np.float32) / 255.0
    image = np.expand_dims(image, axis = 0)

    return image

def predict(file):
    image = Image.open(BytesIO(file.read()))
    image = np.array(image)

    processed_image = preprocess_image(image)

    predictions = model.predict(processed_image)
    return predictions

@app.route('/predict',methods=['POST'])
def predict_image():
    if 'image' not in request.files:
        return jsonify({"error" : "Not found image file"}), 400
    
    file = request.files['image']

    if file.filename == '':
        return jsonify({"error" : "Not selected image file"}), 400
    
    
    result = predict(file)

    return jsonify({"result": int(np.argmax(result, axis=1)[0])})

if __name__ == '__main__':
    app.run(debug=False, port=5002)

 

통신 확인

먼저 두 개의 명령 프롬프트를 실행한다.

1. conda

해당 가상환경을 실행 시키고 파일 위치로 이동 후 다음을 입력한다.

( 그렇다면 서버가 열릴 것이다. )

python app.py

2. cmd

이후 cmd에서 이미지파일과 함께 curl 명령어로 POST 요청을 한다.

이 때 상대경로가 아닌 절대경로로 C:부터 시작하여 전부 입력해준다.

  • -X POST : HTTP POST 요청
  • -F : multipart/form-data 형식 데이터 첨부
    • image필드 파일 이름=이미지파일경
curl -X POST -F "image=@이미지 절대 경로.jpg" http://127.0.0.1:5002/predict

 

이후 json 형태의 파일로 결과값이 돌아온다.

{"result":모델 예측 값}

 

Dockerfile 생성

가상환경 버전 확인

실행할 때와 같이 같은 버전으로 만들어줘야 한다. 때문에 가상환경의 라이브러리들의 버전을 먼저 확인하자

다음 명령어를 통해 버전을 확인 할 수 있다.

conda list

 

Docker build 시 사용할 라이브러리 버전 ( requirement.txt )

다음과 같이 버전을 적어준다.

gunicorn은 flask로 배포시에 개발 서버 모드로 실행된다.

WSGI (Web Server GateWay Interface) 서버를 사용하라는 경고

flask의 내장 서버는 개발용으로만 설계되었다.

numpy=1.23.0
python=3.9.18
opencv-python=4.11.0.86
tensorflow=2.10.0
flask=3.1.0
pillow= 11.1.0 #PIL
gunicorn==20.1.0 #flask warning

 

 

Dockerfile 생성

docker build할 파일을 생성한다. 

이는 txt파일로 생성한 후 확장자를 제거해준다.

# 파이썬 버전
FROM python:3.9

WORKDIR /app

# opencv에 필요한 라이브러리
RUN apt-get update && apt-get install -y \ libglib2.0-0 libsm6 libxext6 libxrender-dev libgl1-mesa-glx

# 라이브러리 버전 파일
COPY requirements.txt .

# 라이브러리 다운로드
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# 5002 포트 개방
EXPOSE 5002

CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5002", "app:app"]

 

Docker DeskTop 확인 후 실행

1. 다음 명령어를 통해 docker-desktop 가상환경이 존재하는지 확인

wsl -l -v

 

 

2. docker image build

-t : tag(이름) 설정 ex) 아래 명령어는 app이라는 이름을 가진 image 생성

. : 현재 디렉토리에서 Dockerfile를 찾아 build

docker build -t app .

 

3. docker container run

docker 이미지 기반으로 컨테이너를 실행

-p 5002:5002 -> 첫 번째는 호스트 머신, 두 번째는 컨테이너 내에서 사용할 포트

( localhost:5002 통해 접근하여 docker 5002 port에 전달 )

docker run -p 5002:5002 app

 

4. curl를 통해 post request

위와 같은 과정을 거쳤다면 cmd창에서 curl명령어를 통해 POST요청을 할 수 있다.

curl -X POST -F "image=@이미지 절대 경로.jpg" http://localhost:5002/predict
반응형
반응형

 

이번 글에서는 큐, 스택에 마무리 문제인 24511번 문제를 해결해 보자.
시간 제한과 메모리 제한이 까다로울 것으로 예상된다.

 

 

문제 핵심

  • 첫째 줄 - 자료구조의 수 : N
  • 둘째 줄 - 수열의 종류 : 0 ( Queue ), 1 ( Stack )
  • 셋째 줄 - 자료구조의 초기값 설정
  • 넷째 줄 - 삽입할 수열의 길이 : M
  • 다섯째 줄 - 삽입할 수열의 원소들

풀이 과정

첫 번째 예제의 과정 중 첫 번째 스텝을 다음과 같이 나타낼 수 있다.

그림 처럼 queue와 stack을 구현하여 값을 삽입하고 제거하는 과정으로 구현해보자. 

아마 메모리에서 먼저 초과되지 않을까 싶다.

 

 

1. 자료구조 저장

0, 1과 구별 값에 따라 큐인지 스택인지 구별하여 저장한다.

List<Object> dataStructure = new ArrayList<Object>();
for (int i = 0; i < N; i++) {
    int type = Integer.parseInt(stringTokenizer.nextToken());

    if (type == 0) {
        dataStructure.add(new LinkedList<Integer>());
    } else {
        dataStructure.add(new Stack<Integer>());
    }

}

 

2. 초기 값 저장

세번 째 줄의 초기 값들을 저장한다.

이 때 초기값들은 instanceof를 통해 자료구조를 판별 후 맞는 메소드를 사용한다.

for (int i = 0; i < N; i++) {
    int num = Integer.parseInt(stringTokenizer.nextToken());
    if (dataStructure.get(i) instanceof Queue) {
        ((Queue<Integer>) dataStructure.get(i)).offer(num);
    } else {
        ((Stack<Integer>) dataStructure.get(i)).add(num);
    }
}

 

3. 메인 과정

새로운 값 들이 큐와 스택을 지나면서 반복되는 과정이다.

이 때, 사실 스택이 값을 거쳐가기만 하기 때문에 의미가 없다.

for (int i = 0; i < M; i++) {
    int num = Integer.parseInt(stringTokenizer.nextToken());
    int temp = num;
    for (int j = 0; j < N; j++) {
        if (dataStructure.get(j) instanceof Queue) {
            ((Queue<Integer>) dataStructure.get(j)).add(temp);
            temp = ((Queue<Integer>) dataStructure.get(j)).remove();
        }
    }

    stringBuilder.append(temp).append(" ");

}

 

 

결과는 시간 초과로 메모리 문제가 아닌 2 중 for문에 N과 M의 최악의 경우 (On2)
100,000 * 100,000 = 10,000,000,000 기 때문에 벌어진 일이다.
그렇다면 2 중 for문의 구조를 제거하고,
스택구조에서 값은 지나치기만 하기 때문에 경우에서 제거한다.

 

4. 자료구조의 재구성

그렇다면 큐의 구조일 때 값을 교체하는 과정이고, 그게 여러개라면 하나씩 밀려나는 과정이다.

큐 여러개가 하나의 값만 가지고 있기 때문에 여러개의 큐가 하나의 큐가 되어버린다.

( 즉, 다음과 같은 구조일 때 새로운 값이 들어온다면 하나의 큐처럼 행동한다. )

그렇다면 하나의 큐를 생성하고 큐일 경우에만 값을 추가한다.

Deque<String> queue = new ArrayDeque<>();
StringBuilder stringBuilder = new StringBuilder();
int N = Integer.parseInt(bufferedReader.readLine());


String[] dataStructureString = bufferedReader.readLine().split(" ");
String[] valueString = bufferedReader.readLine().split(" ");
for (int i = 0; i < N; i++) {
    int type = Integer.parseInt(dataStructureString[i]);
    if(type == 0){
        queue.addLast(valueString[i]);
    }
}

 

5. 메인 과정 재구성

다음과 같이 큐의 삽입과 삭제를 반복하면 O(n)의 경우의 수와 메모리 문제가 모두 해결된다.

int M = Integer.parseInt(bufferedReader.readLine());
String[] addValueString = bufferedReader.readLine().split(" ");

for (int i = 0; i < M; i++) {
    queue.addFirst(addValueString[i]);
    stringBuilder.append(queue.removeLast()).append(" ");
}

 

이번 문제는 마치 알고리즘이 큐처럼 행동하게 하여 해결할 수 있었다.
다음과 같이 자료구조의 개념을 이해한다면 여러방법으로 구현할 수 있다.
줄 서거나(큐), 박스에 물건을 넣고 빼거나(스택) 처럼 한 가지로 제한되지 않는다는 점이다.

시간과 메모리를 줄여야 한다는 점에서 이런 구조를 생각할 수 있었던 문제였다.

 

실패 전체 코드


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        List<Object> dataStructure = new ArrayList<Object>();
        StringTokenizer stringTokenizer;
        StringBuilder stringBuilder = new StringBuilder();
        int N = Integer.parseInt(bufferedReader.readLine());

        stringTokenizer = new StringTokenizer(bufferedReader.readLine());

        for (int i = 0; i < N; i++) {
            int type = Integer.parseInt(stringTokenizer.nextToken());

            if (type == 0) {
                dataStructure.add(new LinkedList<Integer>());
            } else {
                dataStructure.add(new Stack<Integer>());
            }

        }

        stringTokenizer = new StringTokenizer(bufferedReader.readLine());

        for (int i = 0; i < N; i++) {
            int num = Integer.parseInt(stringTokenizer.nextToken());
            if (dataStructure.get(i) instanceof Queue) {
                ((Queue<Integer>) dataStructure.get(i)).offer(num);
            } else {
                ((Stack<Integer>) dataStructure.get(i)).add(num);
            }
        }

        int M = Integer.parseInt(bufferedReader.readLine());

        stringTokenizer = new StringTokenizer(bufferedReader.readLine());

        for (int i = 0; i < M; i++) {
            int num = Integer.parseInt(stringTokenizer.nextToken());
            int temp = num;
            for (int j = 0; j < N; j++) {
                if (dataStructure.get(j) instanceof Queue) {
                    ((Queue<Integer>) dataStructure.get(j)).add(temp);
                    temp = ((Queue<Integer>) dataStructure.get(j)).remove();
                }
            }

            stringBuilder.append(temp).append(" ");

        }

        System.out.println(stringBuilder);
    }
}

성공 전체 코드


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Deque;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        Deque<String> queue = new ArrayDeque<>();
        StringBuilder stringBuilder = new StringBuilder();
        int N = Integer.parseInt(bufferedReader.readLine());


        String[] dataStructureString = bufferedReader.readLine().split(" ");
        String[] valueString = bufferedReader.readLine().split(" ");
        for (int i = 0; i < N; i++) {
            int type = Integer.parseInt(dataStructureString[i]);
            if(type == 0){
                queue.addLast(valueString[i]);
            }
        }

        int M = Integer.parseInt(bufferedReader.readLine());
        String[] addValueString = bufferedReader.readLine().split(" ");

        for (int i = 0; i < M; i++) {
            queue.addFirst(addValueString[i]);
            stringBuilder.append(queue.removeLast()).append(" ");
        }
        System.out.println(stringBuilder);
    }
}
반응형
반응형
이번 글에서는 백준 2346번 문제를 통해
queue 개념에 관해 좀 더 복잡한 문제를 풀어보자

문제 핵심

  • N : 풍선의 개수 ( 자연수 ) 1 ~ 1000
  • 각 종이에 이동할 숫자가 적혀 있다. ( 0은 없다 )
  • 적힌 종이 별로 양수면 오른쪽, 음수는 왼쪽으로 이동한다.

풀이 과정

다음 Deque를 통해 문제를 풀어 나가며, 인덱스를 기억해 두는 것이 핵심이다.

 

1. 풍선 번호와 종이 저장

1. 큐에 인덱스 값들을 저장한다.

2. move 정수 배열에 해당 종이를 저장한다.

        Deque<Integer> indexQueue = new ArrayDeque<Integer>();

        int[] move = new int[N];
        for (int i = 0; i < N; i++) {
            move[i] = Integer.parseInt(stringTokenizer.nextToken());
            indexQueue.add(i);
        }

 

 

2. 반복

첫 시작은 앞에서부터 시작하기 때문에 미리 처리 한다.

큐에서 꺼낸 값은 인덱스 값이고, 이 인덱스 값을 가지고 move 정수 배열에서 이동할 거리를 확인할 수 있다.

        int removeIndex = indexQueue.removeFirst();
        stringBuilder.append(removeIndex + 1).append(" ");

 

값을 제외한 후에 for문을 통해 값을 원형 큐 처럼 뒤로 보내기 때문에 -1 한다.

ex) 3번 이동해야 할 때, 값을 제거한다면, 값들이 땡겨지기 때문에 2번 이동한다.

 

        while(!indexQueue.isEmpty()) {
            int step = move[removeIndex];

            if(step < 0) {
                for(int j = 0; j < -step - 1; j++) {
                    indexQueue.addFirst(indexQueue.removeLast());
                }
                removeIndex = indexQueue.removeLast();

            } else {
                for(int j = 0; j < step - 1; j++) {
                    indexQueue.addLast(indexQueue.removeFirst());
                }
                removeIndex = indexQueue.removeFirst();
            }
            stringBuilder.append(removeIndex + 1).append(" ");
        }

 

아래의 실패 코드로 이중 리스트 구조로 만들었으나 메모리 초과로 실패하게 되었다.
이 후 불필요한 메모리를 제거하기 위해 분리하여 생성하였고, 성공하였다.

물론 여러 방법 중 하나이니 '틀리다' 라고 말할 수 없겠지만, 
이번 경우는 불필요한 메모리 사용으로 틀렸다고 생각한다.

요즘 컴퓨터 성능은 메모리나 속도면에서 문제 없지만, 막대한 수치가 들어온다면 영향을 끼친다.
때문에 '불필요한' 코드는 줄이도록 하자

실패 코드

다음과 같이 큐 안에 index와 종이의 값들을 모두 넣어서 처리하였으나 과도하게 메모리가 사용되어 메모리 초과되었다.

이후 불필요한 메모리를 제거하기 위해 분리하여 생성하였다.

        Deque<int[]> queue = new LinkedList<>();

 

전체 코드

package org.example;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(input.readLine());
        StringTokenizer stringTokenizer = new StringTokenizer(input.readLine());
        StringBuilder stringBuilder = new StringBuilder();
        Deque<Integer> indexQueue = new ArrayDeque<Integer>();

        int[] move = new int[N];
        for (int i = 0; i < N; i++) {
            move[i] = Integer.parseInt(stringTokenizer.nextToken());
            indexQueue.add(i);
        }

        int removeIndex = indexQueue.removeFirst();
        stringBuilder.append(removeIndex + 1).append(" ");

        while(!indexQueue.isEmpty()) {
            int step = move[removeIndex];

            if(step < 0) {
                for(int j = 0; j < -step - 1; j++) {
                    indexQueue.addFirst(indexQueue.removeLast());
                }
                removeIndex = indexQueue.removeLast();

            } else {
                for(int j = 0; j < step - 1; j++) {
                    indexQueue.addLast(indexQueue.removeFirst());
                }
                removeIndex = indexQueue.removeFirst();
            }
            stringBuilder.append(removeIndex + 1).append(" ");
        }
        System.out.println(stringBuilder);
    }
}
반응형

+ Recent posts