
서울 지하철 5호선 아침 7시 19분 아차산역 출발 ~ 54분 공덕역 도착하는 열차에 타는 사람들을 3주간 관찰했다.
사실 무턱대고 모았다.
역시나 중구난방의 값들...
일단 없는 값들은 not_available로 표시하며 엑셀로 정리했다.
교수님 피드백:
pip install pandas openpyxl
openpyxl : openpyxl은 파이썬에서 엑셀 파일(.xlsx 포맷)을 읽고 쓸 수 있게 해주는 라이브러리이다. 나는 엑셀로 데이터셋을 준비할거기 때문에 openpyxl도 필요하다.
여기서 트릭! sqlite도 깔아야하는거 아닌가?!
아니다! sqlite python의 스탠다드 라이브러리다!
https://docs.python.org/3/library/sqlite3.html
import pandas as pd
import sqlite3
from datetime import datetime
# 오늘날짜(yyyymmdd)
today_date = datetime.today().strftime('%Y%m%d')
# 엑셀 파일 경로 (절대/상대값 없으면 지금 디렉토리리)
excel_file = f'dataset_{today_date}.xlsx'
#데이터 프레임으로 엑셀파일 읽기 (데이터프레임은 판다스의 데이터 형식)
df = pd.read_excel(excel_file)
#SQLite 데이터베이스 파일 경로 (지금 디렉토리에 위치)
sqlite_db = 'database.db'
#SQLite 데이터베이스 연결
## connect 함수는 이 코드와 해당 sqlite 데이터베이스간의 연결을 설정하며, 그 db가 원래 없으면 만들기까지 함)
conn = sqlite3.connect(sqlite_db)
#앞서 읽은 데이터프레임을 SQLite 데이터베이스에 저장하기
table_name = f'data_table_{today_date}'
df.to_sql(table_name, conn, if_exists='replace', index=False)
#연결닫기
conn.close()
print(f"Excel file {excel_file} has been successfully converted to SQLite database as {table_name}!")
만들어진 sqlite를 vscode로 보면 이렇게 깨진다!

이걸 확인하는 방법은 데이터베이스를 전문적으로 열람/관리/조정할 수 있는 서비스인데 그 중 DBeaver를 사용했다.



from fastapi import FastAPI
import pandas as pd
import xgboost as xgb
from sklearn.model_selection import train_test_split
from pydantic import BaseModel
import sqlite3
from datetime import datetime
today_date = datetime.today().strftime('%Y%m%d')
# 1. SQlite 데이터 가져오기
sqlite_db = 'database.db' ## 파일경로
conn = sqlite3.connect(sqlite_db) ## 데이터베이스 연결
df = pd.read_sql_query(f"SELECT * FROM data_table_{today_date}", conn) ##SQLite 실행해서 불러오기
print("1. SQlite 데이터 가져오기: done")
conn.close() ## 연결종료
# 2. XGBoost 모델 학습
## 범주형 데이터 원-핫 인코딩
df_encoded = pd.get_dummies(df, columns=['gender', 'makeup', 'mask', 'glass', 'hair_setting', 'coat_length', 'coat_style', 'coat_color', 'bottom_style', 'bottom_color', 'shoes_style', 'shoes_color', 'bag_style', 'bag_material', 'bag_color'])
## 결과값의 이진수 변환
df['destination_target'] = df['destination_target'].map({'Y': 1, 'N': 0})
## 데이터 라벨 분리 with 원-핫
X = df_encoded.drop('destination_target', axis=1)
y = df['destination_target']
## 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state= 42)
## XGBoost DMatrix 생성
train_dmatrix = xgb.DMatrix(data=X_train, label=y_train)
test_dmatrix = xgb.DMatrix(data=X_test, label=y_test)
## 파라미터 설정
params = {
"objective":"multi:softmax",
"num_class" : 2,
"max_depth": 3,
"learning_rate" : 0.1,
}
## 모델 만들기
model = xgb.train(params, train_dmatrix, num_boost_round = 50)
## 모델 예측 및 평가
preds = model.predict(test_dmatrix)
accuracy = sum(preds == y_test) / len(y_test)
print(f"Test Accuracy: {accuracy:.4f}" )
## 모델 저장
model.save_model('xgboost_model.json')
print("2. XGBoost 모델 학습 : done")
# 3. FastAPI를 사용한 예측 API 구현
## FastAPI 인스턴스 생성
app = FastAPI()
## FastAPI health check
@app.get("/")
async def root() :
return{"message":"Hello This is Subway People"}
## XGBoost 모델 로드
model = xgb.Booster()
model.load_model('xgboost_model.json')
## 요청데이터 모델 정의
class PredictRequest(BaseModel):
gender: str
age: int
makeup: str
mask: str
glass: str
hair_setting: str
coat_length: str
coat_style: str
coat_color: str
bottom_style: str
bottom_color: str
shoes_style: str
shoes_color: str
bag_style: str
bag_material: str
bag_color: str
## 예측 API 엔드포인트 정의
@app.post("/predict")
def predict(data: PredictRequest) :
print(data) # 입력데이터 한번 보여주기; 디버깅용
### 요청 데이터를 데이터프레임으로 변환
data_dict = data.dict()
input_data = pd.DataFrame([data_dict])
print("입력 데이터프레임 변환 완료:", input_data)
### 원핫인코딩
input_encoded = pd.get_dummies(input_data)
print("원-핫 인코딩 완료:", input_encoded)
### 학습 데이터의 컬럼 구조 유지
for col in X_train.columns:
if col not in input_encoded.columns:
input_encoded[col] = 0
### 모델 학습시 사용한 컬럼 순서에 맞게 정렬
input_encoded = input_encoded.reindex(columns=X_train.columns)
print("컬럼 정렬 완료:", input_encoded.columns)
### 학습 데이터와 예측 데이터의 컬럼 비교
train_columns = set(X_train.columns)
pred_columns = set(input_encoded.columns)
missing_in_pred = train_columns - pred_columns
missing_in_train = pred_columns - train_columns
print("학습 데이터에 있지만 예측 데이터에 없는 컬럼:", missing_in_pred)
print("예측 데이터에 있지만 학습 데이터에 없는 컬럼:", missing_in_train)
### DMatrix 생성
input_dmatrix = xgb.DMatrix(data=input_encoded)
prediction = model.predict(input_dmatrix)
prediciton_str = ("Yes, she/he is our target." if prediction[0] == 1 else "No, she/he is not our target.")
return {"prediction": prediciton_str}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
# curl -X POST -F 'file=@request-data.json' http://127.0.0.1:8000/predict (주의: window에서는 안되는 명령어 / git bash에서 실행할 것)
모델을 학습시킬 때 가지고 있는 데이터를 학습용 세트와 테스트용 세트로 나눠야 정확도를 높일 수 있습니다. 이는 과적합(overfitting) 방지하는데 목적인데요, 즉 모델이 TRAIN 데이터에만 지나치게 맞춰지지 않도록 합니다. 과적합은 TRAIN 데이터에서는 성능이 좋지만, 새로운 데이터에서는 성능이 떨어지는 현상을 말합니다.
1. 데이터 분할하기
주로 train_test_split 함수를 사용하여 데이터를 분할합니다. 이 함수는 데이터셋을 TRAIN 데이터와 TEST 데이터로 무작위로 나누어줍니다.
2. 분할 비율 정하기
일반적으로 TRAIN 데이터와 TEST 데이터의 비율은 80:20 또는 70:30으로 설정합니다. 이는 데이터의 양과 모델의 복잡도에 따라 조정될 수 있습니다.
from sklearn.model_selection import train_test_split
#(중략)
## 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state= 42)
3. train 세트로 모델 만들기
DMatirx를 만들어 모델을 완성한다.
## 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state= 42)
## XGBoost DMatrix 생성
train_dmatrix = xgb.DMatrix(data=X_train, label=y_train)
test_dmatrix = xgb.DMatrix(data=X_test, label=y_test)
## 파라미터 설정
params = {
"objective":"multi:softmax",
"num_class" : 13,
"max_depth": 3,
"learning_rate" : 0.1,
}
## 모델 만들기
model = xgb.train(params, train_dmatrix, num_boost_round = 50)
4. test 세트 대조
train 세트로 만든 모델에 test 세트를 넣어 accuracy를 뽑아본다.
## 모델 예측 및 평가
preds = model.predict(test_dmatrix)
accuracy = sum(preds == y_test) / len(y_test)
print(f"Test Accuracy: {accuracy:.4f}" )
5. 이후 과정 설계
4의 결과에 따라 모델에 추가적인 조절을 주는 자동화를 만들 수 있다.
원-핫 인코딩은 범주형 데이터를 숫자 벡터로 변환하는 기법 중 하나입니다. 이 기법은 범주형 변수의 각 가능한 값을 이진수 벡터로 표현합니다. 원-핫 인코딩의 주요 목적은 머신러닝 모델이 범주형 데이터를 효과적으로 학습할 수 있도록 하는 것입니다.
특히 이번에 사용한 xgboost는 분류되지 않은 str 값은 학습할 수 없기에 에러가 난다!!! 반드시 범주형 변수로 만들어줘야한다!
[예시]
(원-핫 인코딩 전)
color
0 red
1 blue
2 green
3 blue
4 red
(원-핫 인코딩 코드)
data = pd.DataFrame({'color': ['red', 'blue', 'green', 'blue', 'red']})
(원-핫 인코딩 후)
color_blue color_green color_red
0 0 0 1
1 1 0 0
2 0 1 0
3 1 0 0
4 0 0 1
참고로 위의 실제 코드에서는 컬럼값만 넣으면 되는 방법을 사용했다
df_encoded = pd.get_dummies(df, columns=['gender', 'makeup', 'mask', 'glass', 'hair_setting', 'coat_length', 'coat_style', 'coat_color', 'bottom_style', 'bottom_color', 'shoes_style', 'shoes_color', 'bag_style', 'bag_material', 'bag_color'])
주의할 점
장점
모델의 학습 효율성: 범주형 데이터를 숫자 벡터로 변환하여 머신러닝 모델이 데이터를 쉽게 학습할 수 있습니다.
데이터의 해석력 향상: 각 범주가 고유한 이진 벡터로 표현되므로, 모델이 각 범주의 영향을 독립적으로 학습할 수 있습니다.
단점
차원의 저주: 범주의 수가 많아질수록 데이터의 차원이 증가하여 계산 비용이 늘어날 수 있습니다.
메모리 사용량 증가: 많은 범주형 변수를 가진 데이터셋의 경우 메모리 사용량이 증가할 수 있습니다.
이 데이터 구조에서 destination_target 열은 타깃 변수이므로 모델 학습에 직접적으로 영향을 미칩니다.
타깃변수는 반드시 숫자로 변환할 필요가 있습니다.
그중 우리의 목적이 binary:logistic, binary:logitraw 등 이진분류인 경우 클래스 라벨을 이진법으로 바꿔줘야합니다.
df['destination_target'] = df['destination_target'].map({'Y': 1, 'N': 0})
XGboost의 파라미터에는 어떤 목적인지를 목적함수로 선택해야하고 그 값에 따라 필요한 전처리 및 도출되는 결과의 값이 달라진다.
multi:softprob: 다중 클래스 분류를 수행하며, 각 클래스에 대한 확률 값을 예측합니다. 출력은 각 클래스에 대한 확률 값입니다.
이 중에서 나는 binary:logistic를 선택하려했지만, 확율로 값을 받는 것보다 예측값 그 자체를 받는 것이 더 편리할 것 같아 multi:softmax를 선택했다.
작업중 가장 나를 분노하게 만든 문제. 이해하는데 오랜 시간이 걸렸다.
에러의 뜻은 단순하다. 말 그대로 원래 학습한 데이터의 feature 이름과 예측값을 얻기 위해 api에 새로 입력한 데이터의 feature 이름이 다른것 (즉 행의 이름들이 다르다는 것이다)
수십번을 확인했는데도 모르겠는것이다!!
알고보니 앞서 설명한 (2) 원-핫 인코딩 문제로 발생하는 것이었다.
(원-핫 인코딩 전)
color
0 red
1 blue
2 green
3 blue
4 red
(원-핫 인코딩 후)
color_blue color_green color_red
0 0 0 1
1 1 0 0
2 0 1 0
3 1 0 0
4 0 0 1
예시에서 나온 것처럼 원래 color라는 행에 여러 종류의 값이 있던 것을 범주화해서 범주 별로 하나의 행으로 늘려버린 것이니... 원래 19개의 행이라고 생각했던 나의 학습 데이터셋의 행이 50개 이상이 되어 있던 것이다.
그리고 나는 계속 19개의 행만을 생각하고서 19개의 행을 똑같이 가진 json을 api에 post했던 것이다.
이 문제를 이해하고나서 조치한 바는 아래와 같다.
### 원핫인코딩
input_encoded = pd.get_dummies(input_data)
print("원-핫 인코딩 완료:", input_encoded)
### 학습 데이터의 컬럼 구조 유지
for col in X_train.columns:
if col not in input_encoded.columns:
input_encoded[col] = 0
### 모델 학습시 사용한 컬럼 순서에 맞게 정렬
input_encoded = input_encoded.reindex(columns=X_train.columns)
print("컬럼 정렬 완료:", input_encoded.columns)
### 학습 데이터와 예측 데이터의 컬럼 비교
train_columns = set(X_train.columns)
pred_columns = set(input_encoded.columns)
missing_in_pred = train_columns - pred_columns
missing_in_train = pred_columns - train_columns
print("학습 데이터에 있지만 예측 데이터에 없는 컬럼:", missing_in_pred)
print("예측 데이터에 있지만 학습 데이터에 없는 컬럼:", missing_in_train)
### DMatrix 생성
input_dmatrix = xgb.DMatrix(data=input_encoded)
바로 위의 예시에서 볼 수 있듯이, 과정과정에 print 구문을 넣으면 어디에서 에러가 났는지 확실히 알 수 있어서 편하다.
https://velog.io/@yooooooona/Git-Hub
setting > collaborators




Postman에서 Collection은 자주 사용하는 엔드포인트를 조직화하고 저장하는 데 매우 유용합니다. 각 Collection은 다양한 요청(requests)을 그룹화할 수 있고, 이를 통해 테스트하고 문서화하는 과정이 훨씬 더 효율적입니다.

주요 기능
추가방법
NEW(+)를 누르고 SAVE를 눌러서 새로운 REQUEST를 저장한다. 저장된 REQUEST는 왼쪽 COLLECTION에 저장된 것을 확인할 수 있다.

헬스 체크(Health Check)는 서버나 애플리케이션의 상태를 확인하기 위해 중요한 기능입니다. 다양한 상황에서 서버의 정상적인 작동을 확인하기 위한 과정으로 이 엔드포인트를 활용, 이 값을 트리거로 하여 다양한 작업을 하기도 합니다.
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/healthcheck")
def health_check():
return{"status":"healthy"}
if __name__ == "__main__":
uvicorn.run("api_healthcheck:app", host="0.0.0.0", port=8000, reload=True)
참고로 위의 코드는 main.py 파일이 아닌 api_healthcheck.py 파일을 활용하는 상황이다.
오랜만에 git init하고 하려니 귀찮아서 가장 쉬운 방법을 선택했다. 리모트 레포지토리를 이미 만들었으니, 그걸 원하는 위치에 clone해오는 것이다.
clone 한 후 main 상태에서 첫 commit을 만들어서 push했다.
ssh 명령어는 Secure Shell을 사용하여 원격 서버에 접속하는 데 사용됩니다. -p 옵션은 서버가 SSH 연결을 수신하는 포트를 지정합니다.
ssh -p [포트번호] [사용자명@서버주소]
포트는 네트워크 통신에서 특정 서비스를 식별하기 위해 사용되는 논리적 번호입니다. 포트는 IP 주소와 함께 사용되어 데이터가 적절한 애플리케이션으로 전달되도록 합니다. 즉, 나의 컴퓨터와 서버의 컴퓨터는 서로 주고받는 데이터를 각자 사용하고 있는 다른 서비스/작업의 데이터들의 통신과 섞이지 않게, 해당 포트번호에서 주고받게 됩니다.
The authenticity of host '' can't be established.
블라블라 key fingerprint is 블라블라
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
해당 호스트(서버)의 신뢰성을 확인할 수 없다.
이 서버의 공개키는 이건데, 아직 우리에게 저장되어있지 않다. 그래도 연결할 것인가??
처음 서버에 접속할 때 이러한 메시지가 나오는 것은 정상입니다. 서버가 신뢰할 수 있는 서버라는 것을 확인했다면 y를 눌러 진행합니다. 그러면 서버의 공개 키가 여러분의 로컬 머신에 저장되고, 이후 같은 서버에 접속할 때는 더 이상 이 경고 메시지가 나타나지 않습니다.
이 과정은 SSH를 사용하여 처음 접속할때 일반적으로 발생하는 보안 절차 중 하나입니다. 걱정하지 않으셔도 됩니다!
터미널에 exit 또는 단축키 ctrl+d
필요한 최소한의 것들을 세팅하자.
파이썬, git, virtualenv, FastAPI, Uvicorn
#apt-get: 리눅스 시스템의 패키지 관리자 최신 업데이트하기
sudo apt-get update
# 파이썬, pit, git
sudo apt-get install python3 python3-pip git
# pip3로 깔아야하는 것들
pip3 install virtualenv fastapi uvicorn pandas openpyxl xgboost scikit-learn pydantic
(기타)
# curl : 엔드포인트를 터미널에서 작업하기 위한
sudo apt-get install curl
# jq : curl 로 json 결과값을 읽어올때 필요한...
sudo apt-get install jq
새롭게 git id와 pwd를 가지고 로그인하라고 뜨지만.. 비번을 사용하는 로그인은 이제 금지했다는 깃... (아니 그럼 왜 물어봐...)
새로운 환경에서 clone을 하려면 토큰을 사용해야한다.
우측 프로필의 settings > 왼쪽 제일 하단의 Developer settings


Personal access tokens > Generate new token


이후에 tocken 문자열을 보여주는데 바로 복붙해서 어딘가 저장해두어야한다. 이 화면에서 나가면 다신 볼 수 없다.
이 화면에서 토큰들을 관리/삭제할 수 있다.

클론 시 토큰 사용하기
git clone https://username:YOUR_PERSONAL_ACCESS_TOKEN@github.com/username/repositoryname
서버에서 터미널로 api를 작동시키되, 백엔드로 작동시켜서 같은 터미널로 엔드포인트를 명령할 수 있어야한다.
uvicorn api_healthcheck:app --host 0.0.0.0 --port 8000 --reload &
그러면 평소처럼 INFO:가 주르륵 나오는데 그상태에서 한번 엔터를 치면 프롬프트가 생기며 터미널을 사용할 수 있다.
curl -i http://172.18.0.3:8000/healthcheck
ps aux | grep uvicorn
(결과)
user 12345 0.0 0.1 123456 1234 ? Ssl 00:00 0:00 uvicorn api_healthcheck:app --host 0.0.0.0 --port 8000 --reload
그 결과로 나오는 UNICORN의 프로세스의 ID(user 바로 뒤에나오는 2번째 열 내용)을 kill 해준다.
kill 12345
참고로 ps aux하면 무조건 하나의 행은 ps aux때문에 존재한다.
조교님이 각 서버의 url을 주셨다
이렇게 서버의 url을 만들어서 하면 위처럼 터미널 하나로 끙끙거릴 필요 없이 로컬 컴퓨터의 postman으로도 서버의 api를 확인 할 수 있다.
결과!
