반응형

 

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

 

문제 핵심

  • 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);
    }
}
반응형

+ Recent posts