Loading the catalog…
Loading the catalog…
요즘은 멀리 보며 공부를 하는 중입니다. 지난주를 돌아보며 지난주에는 회귀, 분류, 군집처럼 여러 머신러닝 문제를 살펴보고, 시계열 분석과 자연어 처리까지 학습했다. 마지막에는 퍼셉트론과 TensorFlow를 통해 신경망의 기본 구조도 접했다. 모델마다 사용하는 알고리즘은 달랐지만 데이터를 준비하고, 학습시키고, 새로운 데이터로 평가하는 흐름은 반복된다는 것을 알게 됐다. 이번 주는 CNN을 활용한 이미지 분류와 FastAPI를 이용한 웹 애플리케이션 개발 을 학습했다. 이미지 분류에서는 모델이 이미지의 특징을 어떻게 찾아내는지 살펴봤고, 웹 개발에서는 사용자의 요청이 서버와 데이터베이스를 거쳐 화면으로 돌아오는 과정을 따라갔다. 처음에는 딥러닝과 웹 개발이 서로 다른 내용처럼 느껴졌다. 하지만 두 실습 모두 데이터를 어떤 형태로 받아서 처리하고, 결과를 어떻게 확인하거나 전달하는지 생각해야 한다는 공통점이 있었다. 이번 주의 핵심 이미지 분류 모델의 학습 과정을 이해하고, 웹 애플리케이션에서는 화면·요청 처리·데이터 저장·사용자 권한이 연결되는 구조를 살펴봤다. CNN을 이용한 이미지 분류 이미지를 숫자 배열로 바라보기 먼저 CIFAR-10 데이터를 이용해 이미지 분류 모델을 만들었다. CIFAR-10은 비행기, 자동차, 새, 고양이 등 10개의 범주로 구성된 이미지 데이터셋이다. 훈련 데이터는 50,000장, 테스트 데이터는 10,000장이었고, 이미지 한 장의 크기는 32 × 32 × 3 이었다. 마지막의 3은 RGB 색상 채널을 의미한다. 이미지 한 장: (32, 32, 3) 높이 너비 RGB 훈련 데이터: (50000, 32, 32, 3) 사람은 사진을 보고 바로 고양이나 자동차를 떠올리지만, 모델이 받는 입력은 픽셀 값을 가진 배열이다. 그래서 이미지를 출력해 확인하는 것과 함께 배열의 모양, 정답 레이블의 형태를 확인하는 과정이 먼저 필요했다. 합성곱과 풀링의 역할 CNN에서는 Conv2D 로 이미지의 특징을 추출하고, MaxPool2D 로 특징 맵의 가로·세로 크기를 줄이는 과정을 반복했다. 합성곱은 작은 필터를 이미지의 여러 위치에 적용하면서 패턴을 찾는 과정으로 이해했다. 필터가 학습되면서 이미지 분류에 필요한 특징을 찾아내는 것이다. 풀링은 주변 영역에서 대표 값을 선택해 크기를 줄인다. 이번 실습에서는 최대 값을 선택하는 최대 풀링을 사용했다. from tensorflow.keras.layers import Conv2D, MaxPool2D model.add(Conv2D( filters=32, kernel_size=3, padding="same", activation="relu" )) model.add(MaxPool2D(pool_size=(2, 2), strides=2)) filters=32 는 32개의 필터로 특징 맵을 만들겠다는 의미이고, kernel_size=3 은 3 × 3 크기의 필터를 사용한다는 의미였다. 실습의 기본 보폭에서 padding="same" 은 가장자리를 채워 합성곱 전후의 가로·세로 크기를 유지하도록 했다. 특징 추출이 끝난 뒤에는 Flatten 으로 배열을 펼치고, Dense 층을 거쳐 10개 범주에 대한 예측값을 만들었다. RGB 이미지 ↓ 합성곱 → 풀링 반복 ↓ Flatten ↓ Dense ↓ 10개 범주에 대한 예측 아직 필터가 학습되는 계산을 완전히 설명하기는 어렵다. 그래도 층을 하나씩 추가하는 코드가 단순한 반복이 아니라, 이미지에서 특징을 추출하고 최종 분류로 연결하는 구조라는 점은 조금 이해할 수 있었다. 레이블과 손실 함수의 관계 CIFAR-10 실습에서는 정답을 원-핫 인코딩하지 않고 정수 레이블로 사용했기 때문에 sparse_categorical_crossentropy 를 선택했다. 출력층에는 10개 범주 중 하나를 고르는 문제에 맞게 softmax 를 사용했다. 5번의 epoch를 학습한 노트북의 테스트 정확도는 약 67.26% 였다. 이 값은 해당 실행과 설정에서 나온 결과로, CNN을 사용하면 항상 같은 정확도가 나온다는 뜻은 아니다. 학습 정확도와 검증 정확도를 함께 그래프로 보면서, 학습 데이터에서 성능이 좋아지는 것과 새로운 데이터에서도 잘 예측하는 것은 따로 확인해야 한다는 점을 다시 느꼈다. 다중 클래스와 다중 레이블 다음으로 의류 이미지의 색상과 종류를 함께 예측하는 실습을 진행했다. CIFAR-10은 한 이미지에 하나의 범주를 선택하는 다중 클래스 분류였다. 반면 의류 이미지는 한 장에 green 과 shirt 처럼 여러 레이블이 동시에 붙을 수 있는 다중 레이블 분류였다. 구분 정답의 예 실습의 출력층·손실 함수 다중 클래스 고양이 또는 자동차 softmax · sparse categorical crossentropy 다중 레이블 초록색이면서 셔츠 sigmoid · binary crossentropy 의류 데이터에서는 색상 6개와 의류 종류 5개를 합쳐 총 11개의 레이블을 만들었다. 해당하는 위치는 1, 나머지는 0으로 표시했다. green_shirt ↓ [black, blue, brown, green, red, white, dress, shirt, pants, shorts, shoes] [ 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0] 각 레이블에 해당할 가능성을 별도로 출력해야 하므로 마지막 층에는 sigmoid 를 사용했다. 같은 이미지 분류라도 정답을 어떻게 정의하느냐에 따라 레이블 구성과 출력층, 손실 함수가 달라진다 는 점이 중요하게 느껴졌다. 이미지 데이터 준비와 학습 관리 이미지 경로와 레이블을 함께 관리하기 의류 실습에서는 11,385장의 이미지 파일을 찾아 경로를 모으고, 폴더 이름에서 색상과 의류 종류를 분리해 레이블을 만들었다. 데이터를 훈련·검증·테스트용으로 나눈 뒤 이미지 경로와 11개 레이블을 DataFrame에 담고 CSV로 저장했다. 이미지를 직접 CSV에 넣는 것이 아니라, 이미지를 찾을 경로와 정답 정보 를 표로 관리한 것이다. 이후 ImageDataGenerator 를 이용해 이미지를 배치 단위로 읽었다. 원본 이미지마다 크기가 달랐기 때문에 입력 크기를 112 × 112 로 맞추고, 픽셀 값을 255로 나누어 0~1 범위로 정규화했다. 이미지 파일 + 폴더 이름 ↓ 경로와 레이블을 담은 DataFrame·CSV ↓ 이미지 제너레이터 ↓ 크기 통일·정규화·배치 생성 ↓ 모델 학습 여기서 배치는 한 번에 처리하는 데이터 묶음이고, epoch는 훈련 데이터를 한 바퀴 학습하는 단위였다. 배치 크기가 32이고 훈련 이미지가 5,578장이면, 남는 데이터까지 포함해 한 epoch에 175번의 배치 처리가 필요했다. 테스트 이미지를 예측할 때는 shuffle=False 로 순서를 유지했다. 그래야 예측값과 원래 이미지 경로를 같은 순서로 연결할 수 있다. 과적합과 Dropout 모델에는 Dropout 도 추가했다. 학습 중 일부 출력을 무작위로 0으로 만들어 특정 연결에 지나치게 의존하지 않도록 하는 방법이었다. 처음에는 학습을 많이 하거나 층을 늘리면 성능도 계속 좋아질 것 같았다. 하지만 훈련 손실이 줄어들어도 검증 손실이 다시 커질 수 있다는 것을 보면서, 모델의 복잡도와 반복 횟수를 함께 생각해야 한다는 점을 알게 됐다. 학습 결과를 볼 때 훈련 성능뿐 아니라 검증 성능도 함께 확인해야 한다. 훈련 데이터에 익숙해진 모델이 새로운 데이터에서도 잘 동작하는지는 별도의 문제다. 체크포인트와 조기 종료 검증 손실이 좋아졌을 때 가중치를 저장하는 ModelCheckpoint 와, 개선이 멈추면 학습을 종료하는 EarlyStopping 을 사용했다. checkpoint = tf.keras.callbacks.ModelCheckpoint( "best_performed_model.weights.h5", save_weights_only=True, save_best_only=True, monitor="val_loss" ) early_stop = tf.keras.callbacks.EarlyStopping( monitor="val_loss", patience=2 ) patience=2 는 검증 손실이 개선되지 않는 epoch를 2번 허용하는 설정이다. 실습에서는 콜백을 적용한 추가 학습이 예정된 10번을 모두 채우기 전에 종료됐다. 가중치만 저장한 경우에는 동일한 구조의 모델을 다시 만든 뒤 load_weights() 로 읽어야 했다. CIFAR-10 실습에서 모델 전체를 저장하고 불러온 방식과 비교하니, 저장하는 대상이 모델 전체인지 가중치인지 구분할 필요가 있었다. 데이터 증식 마지막으로 고양이 이미지 한 장을 회전하거나 뒤집고, 밝기·크기·위치를 바꾸어 여러 이미지로 만드는 데이터 증식을 살펴봤다. 같은 대상을 다양한 모습으로 보여주어 학습 데이터의 다양성을 높이는 방법으로 이해했다. 다만 어떤 변형이든 적용해도 되는 것은 아니고, 변형 후에도 원래 레이블의 의미가 유지되는지 생각해야 한다. 이번 노트북에서는 변형된 이미지를 출력해 확인했다. 증식 설정을 실제 분류 모델에 적용했을 때 성능이 얼마나 달라지는지는 다음에 직접 비교해 보고 싶다. FastAPI와 웹 요청의 구조 Python 함수가 웹 요청을 처리하기까지 이미지 분류 이후에는 FastAPI를 이용한 웹 개발을 시작했다. Python으로 작성한 함수가 URL과 HTTP 메서드에 연결되어 사용자의 요청을 처리하는 구조였다. from fastapi import FastAPI app = FastAPI() @app.get("/") async def welcome() -> dict: return {"message": "Hello World"} 브라우저에서 해당 주소로 GET 요청을 보내면 함수가 실행되고, 반환한 딕셔너리는 JSON 응답으로 전달됐다. 서버는 Uvicorn으로 실행하고, /docs 에서는 API 목록과 입력 형식을 확인하며 요청을 보내볼 수 있었다. 할 일 API와 CRUD 첫 실습에서는 Python 리스트에 할 일을 저장하고 등록·조회·수정·삭제 기능을 만들었다. 기능 HTTP 메서드 경로 예시 목록 조회 GET /todo 한 건 조회 GET /todo/{todo_id} 등록 POST /todo 수정 PUT /todo/{todo_id} 삭제 DELETE /todo/{todo_id} 같은 경로라도 HTTP 메서드에 따라 다른 기능을 실행할 수 있었다. 또 {todo_id} 처럼 URL 안에 들어가는 값은 경로 매개변수이고, ?page=1 처럼 전달하는 값은 쿼리 매개변수라는 차이도 살펴봤다. Pydantic과 APIRouter 입력 데이터의 모양은 Pydantic의 BaseModel 로 정의했다. 타입 힌트를 통해 필드의 자료형을 지정하면 입력을 검증하고, 변환 가능한 값은 해당 타입으로 변환하는 데 활용할 수 있었다. from pydantic import BaseModel class Todo(BaseModel): id: int item: str 기능별 요청은 APIRouter 로 묶고, 애플리케이션에서 include_router() 로 등록했다. 처음에는 파일을 나누는 것이 번거롭게 느껴졌지만, 기능이 늘어나니 데이터 모델과 요청 처리 코드를 구분하는 이유가 조금 보였다. 요청 처리의 기본 흐름 URL과 HTTP 메서드로 실행할 함수를 찾고, 입력을 검증한 뒤 필요한 작업을 수행해 응답을 반환한다. HTML 화면과 데이터베이스 연결 실습 코드를 실행한 할 일 목록 화면. 화면 설명을 위해 캡처용 예시 데이터를 사용했다. JSON 응답에서 HTML 화면으로 다음 실습에서는 Jinja2 템플릿을 연결해 할 일 목록을 HTML 화면으로 출력했다. 서버에서 조회한 데이터를 템플릿에 전달하고, 템플릿의 반복문으로 목록을 만들었다. {% for todo in todos %} <p>{{ todo.item }}</p> {% endfor %} HTML 폼에서 입력한 값은 Form 으로 받았다. 수정이나 삭제 작업 이후에는 RedirectResponse 와 303 상태 코드를 사용해 목록 페이지로 다시 GET 요청을 보내도록 했다. 폼 입력 → POST 요청 → 데이터 변경 ↓ 303 리다이렉트 ↓ GET으로 목록 조회 이 과정을 따라가면서 화면에 입력한 내용이 바로 저장되는 것이 아니라, 폼의 필드 이름과 서버 매개변수가 연결되고 요청 처리 함수가 실행된다는 점을 이해했다. 메모리 저장에서 SQLite로 처음 사용한 리스트는 서버가 종료되면 저장한 데이터가 사라진다. 이후에는 SQLite와 SQLModel을 연결해 데이터를 파일 기반 데이터베이스에 저장했다. from sqlmodel import Field, SQLModel class Todo(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) item: str table=True 로 테이블에 대응하는 모델을 정의하고, 기본키인 id 는 데이터베이스가 생성하도록 했다. 데이터 등록과 수정에는 session.add() , 변경 확정에는 session.commit() , 삭제에는 session.delete() 를 사용했다. session.add(Todo(item=item)) session.commit() 의존성 주입과 애플리케이션 구조 각 요청 처리 함수에는 Depends(get_session) 으로 데이터베이스 세션을 전달했다. 공통으로 필요한 준비 작업을 별도 함수로 두고, FastAPI가 실행해 그 결과를 전달하는 방식으로 이해했다. 프로젝트에서는 main.py 가 애플리케이션과 라우터를 연결하고, routes 는 요청 처리, models 는 데이터 구조, database 는 연결과 세션, templates 는 화면을 담당했다. 브라우저 요청 ↓ main.py → routes ↓ models + database ↓ templates ↓ HTML 응답 이전에 배운 아키텍처가 아직 추상적으로 느껴졌는데, 이번에는 파일마다 담당하는 역할을 직접 확인할 수 있었다. 특히 기능이 늘어나도 데이터베이스 연결이나 공통 템플릿을 함께 사용할 수 있다는 점이 인상적이었다. 이벤트 기능에서도 같은 구조를 사용해 제목, 설명, 장소와 태그를 저장했다. 쉼표로 입력한 태그를 리스트로 만들고 JSON 형태로 저장하면서, 화면의 입력 형태와 저장 형태가 다를 수 있다는 점도 배웠다. 이벤트 입력 폼과 데이터베이스에서 조회한 목록을 같은 화면에 표시한 모습. 로그인 상태와 사용자 권한 이메일과 비밀번호를 입력하는 회원가입·로그인 화면. 회원가입과 로그인 흐름 회원가입에서는 이메일 형식을 확인하고, 이미 등록된 이메일인지 조회한 뒤 사용자 정보를 저장했다. 로그인에서는 계정을 조회하고 비밀번호를 비교한 뒤 성공 여부에 따라 응답을 달리했다. 실습 코드는 기본 흐름을 확인하기 위해 비밀번호를 직접 저장하고 비교했다. 실제 서비스에서는 비밀번호 해시 등 추가 처리가 필요하다는 점도 구분해서 생각해야 한다. 세션으로 로그인 상태 유지하기 로그인 성공 후에는 세션에 이메일을 저장했다. SessionMiddleware 가 서명된 쿠키의 세션 정보를 읽어 요청에서 사용할 수 있게 했고, 로그아웃할 때는 세션을 비웠다. 로그인 정보 제출 ↓ 계정 확인 ↓ 세션에 사용자 정보 기록 ↓ 다음 요청에서 로그인 상태 확인 처음에는 로그인도 한 번의 함수 실행 정도로 생각했지만, 로그인 이후의 여러 요청에서도 사용자를 확인해야 한다는 점에서 상태 관리가 필요했다. 로그인 확인과 권한 확인 할 일과 이벤트 기능에는 Depends(require_login) 을 연결해 로그인한 사용자만 접근하도록 했다. 게시판은 종류와 작업에 따라 별도로 권한을 확인했다. 기능 실습에서 적용한 규칙 공지사항 읽기 로그인 여부와 관계없이 가능 공지사항 작성 관리자만 가능 자유게시판 읽기·작성 로그인 필요 게시글 수정·삭제 작성자 또는 관리자만 가능 로그인은 사용자가 누구인지 확인하는 과정이고, 권한 검사는 그 사용자가 어떤 작업을 할 수 있는지 확인하는 과정이었다. 화면에 버튼을 보여줄지 결정하는 것과 서버에서 작업을 허용할지 결정하는 것은 각각 필요하다. 서버에서도 요청자의 권한을 확인해야 한다. 또 로그인하지 않은 HTML 요청은 로그인 화면으로 이동시키고, API 요청에는 JSON 오류 응답을 반환했다. 같은 상황에서도 요청한 쪽이 화면인지 API인지에 따라 응답 형태를 고려하는 점이 흥미로웠다. 게시판과 첨부파일 처리 게시판 기능으로 구조 확장하기 게시판에서는 공지사항과 자유게시판을 구분하고, 제목·내용·작성자·등록 시간·조회수·첨부파일 경로를 관리했다. 게시판 종류는 Enum으로 정해진 값만 사용하도록 했고, 제목과 내용은 저장 전에 공백을 정리하고 유효성을 검사했다. 상세 화면에서는 조회수를 증가시키고, 수정 화면에서는 기존 내용을 폼에 채웠다. 할 일 실습에서 배운 CRUD가 게시판에도 반복됐지만, 여기에 권한 확인과 입력 검증이 추가되면서 실제 기능은 여러 단계로 이루어진다는 것을 느꼈다. 페이지네이션 게시글은 한 페이지에 10개씩 조회하고, 페이지 번호는 5개씩 묶어 표시했다. 전체 건수로 페이지 수를 계산하고, offset 과 limit 으로 현재 페이지에 필요한 데이터만 조회했다. 캡처용 게시글 12개 중 첫 페이지의 10개와 페이지 이동 링크를 표시한 화면. posts = session.exec( select(Board) .where(Board.category == category) .order_by(Board.id.desc()) .offset((page - 1) * PAGE_SIZE) .limit(PAGE_SIZE) ).all() 예를 들어 2페이지라면 앞의 10개를 건너뛰고 다음 10개를 가져오는 구조였다. 화면의 페이지 번호와 실제 데이터베이스 조회 범위를 함께 계산해야 한다는 점을 알게 됐다. 파일 업로드와 다운로드 자유게시판에는 첨부파일 기능도 추가했다. HTML 폼에 enctype="multipart/form-data" 를 지정하고, 서버에서는 UploadFile 로 파일을 받았다. 제목·내용 입력과 파일 선택이 가능한 게시글 작성 화면. 입력 내용은 캡처용 예시다. 파일은 무작위 식별자로 만든 폴더에 저장하고, 데이터베이스에는 파일 자체 대신 상대 경로를 기록했다. 실습에서는 파일 크기를 최대 10MB로 제한하고, 1MB씩 나누어 읽으면서 저장했다. 파일 선택과 폼 제출 ↓ 권한·입력값 확인 ↓ 파일 저장 → 저장 경로 확보 ↓ 게시글과 경로를 데이터베이스에 기록 첨부파일을 교체할 때는 새 파일을 저장하고 데이터베이스 변경이 성공한 뒤 기존 파일을 정리했다. 게시글을 삭제할 때도 연결된 파일을 함께 정리했다. 다운로드에서는 게시글의 읽기 권한을 확인하고, 저장 경로가 업로드 폴더 안에 있는지와 실제 파일이 존재하는지 확인한 뒤 FileResponse 를 반환했다. 첨부파일에서 중요한 관계 게시글 정보는 데이터베이스에, 실제 파일은 폴더에 저장된다. 수정·삭제 중 문제가 생기면 두 저장 상태가 어긋날 수 있으므로 파일과 데이터베이스를 함께 생각해야 한다. 처음에는 업로드가 파일을 저장하는 한 단계라고 생각했지만, 기존 파일 교체와 오류 처리, 권한 확인까지 연결되어 있었다. 기능이 많아질수록 정상적인 경우뿐 아니라 중간에 실패한 경우도 고려해야 한다는 점이 기억에 남았다. 6주차 회고 이번 주에는 CNN을 활용한 이미지 분류와 FastAPI 웹 개발을 함께 경험했다. 이미지 분류에서는 합성곱과 풀링으로 특징을 추출하고, 정답의 형태에 따라 출력층과 손실 함수를 다르게 구성했다. 또 검증 손실을 기준으로 학습을 관리하고, 좋은 가중치를 저장하는 과정도 살펴봤다. 웹 개발에서는 간단한 할 일 API가 HTML 화면과 데이터베이스를 갖춘 애플리케이션으로 확장됐다. 이후 로그인과 권한, 게시판과 첨부파일이 추가되면서 각각의 기능이 독립적으로 끝나는 것이 아니라 여러 구성 요소를 거쳐 동작한다는 것을 확인했다. 아직은 예제를 따라가며 각 코드의 역할을 이해하는 단계다. 그래도 파일 이름과 함수만 볼 때보다 요청이나 데이터가 이동하는 순서를 그려보면 전체 구조가 조금 더 잘 보였다. 이번 주 회고: 4L Liked 이미지를 분류한 결과를 원본 이미지와 함께 확인할 수 있어 모델의 출력을 이해하기 쉬웠다. 색상과 의류 종류를 동시에 예측하는 실습에서는 다중 레이블이라는 개념이 구체적으로 느껴졌다
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
[스나이퍼팩토리 x 카카오클라우드 AIaaS] 6주차 - CNN 이미지 분류와 FastAPI 웹 애플리케이션. 요즘은 멀리 보며 공부를 하는 중입니다. 지난주를 돌아보며 지난주에는 회귀, 분류, 군집처럼 여러 머신러닝 문제를 살펴보고, 시계열 분석과 자연어 처리까지 학습했다. 마지막에는 퍼셉트론과 TensorFlow를 통해 신경망의 기본 구조도 접했다. 모델마다 사용하는 알고리즘은 달랐지만 데이터를 준비하고, 학습시키고, 새로운 데이터로 평가하는 흐름은 반복된다는 것을 알게 됐다. 이번 주는 CNN을 활용한 이미지 분류와 FastAPI를 이용한 웹 애플리케이션 개발 을 학습했다. 이미지 분류에서는 모델이 이미지의 특징을 어떻게 찾아내는지 살펴봤고, 웹 개발에서는 사용자의 요청이 서버와 데이터베이스를 거쳐…
Open source