Post

SK네트웍스 Family AI 캠프 1기 8주차 회고

SK Networks AI Camp

Main image

Weekly 회고 - 8주차

:warning: 포스트를 읽기 전에..
이 포스트는 SK네트웍스 Family AI 캠프를 다니면서 느낀 개인적인 생각을 정리한 포스트입니다.
배운 내용이나 스킬셋에 대한 설명은 별도로 작성하지 않았기 때문에, 그에 대한 정보는 다른 포스트를 참고해주세요!

:thumbsup:Liked

이번 주 캠프를 진행하면서 좋았던 점은 슬슬 생활 패턴이 적응되고 있다는 것이 느껴진다는 점입니다.
사실 저번 주까지는 적응이 된 것 같다고 생각은 하면서도 ‘여유’까진 느끼지 못했는데, 이번 주는 회고 쓸 시간도 있고 코딩 테스트 풀어볼 시간도 있는 걸 보니 확실히 조금은 ‘여유’가 생겼다고 말할 수 있을 것 같습니다.
그리고 이것보다 더 좋은 점은 팀으로 진행하고 있던 과제가 어느정도 윤곽이 잡혀간다는 점입니다!:satisfied:
저번 주까지는 회원가입이나 로그인 기능은 고사하고 간단한 게시판도 제대로 만들어지지 않았었는데, 이번 주 내내 팀원들끼리 열심히 진행한 끝에 리뷰 게시판이랑 간단한 상품 등록 페이지, 회원가입 및 로그인까지 구현해내는 데에 성공했습니다.
사실 처음 시작할 때까지만 해도, “게시판 + 상품 등록 + 회원가입 + 로그인 + 동향 분석까지.. 우리가 전부 다 해낼 수 있을까?” 라는 생각이 컸는데 강의 내용을 따라가며 과제를 진행해보니 쉽진 않았지만 그래도 너무 어렵진 않게 진행할 수 있었습니다.
이젠 간단한 오류정도는 혼자 해결하는 방법을 깨달아서 매우 재밌게 과제를 진행하고 있습니다.:relaxed:
그리고 제가 발견한 빠르고 쉽게 코딩 실력 늘리는 법에 대한 설명을 아래에 적어두었습니다.
coder_memes1
빠르고 쉽게 실력을 늘리는 방법 같은 건 없으니 이걸 본다면 당장 코드짜러 가도록 하자.


:books:Learned

이번 주도 저번 주와 마찬가지로 굉장히 많은 내용을 배웠습니다. 전부 다 얘기하려면 주말 내내 써도 시간이 부족하기 때문에 개인적으로 중요하다고 생각되는 데이터 전처리와 간단한 딥러닝 학습 및 예측에 대해 얘기해보도록 하겠습니다!
(엄청 어려워보이는 내용이지만 사실은 그렇지 않습니다.)
deep-learning memes1 강의에서는 아무 의미 없는 무작위 데이터를 전처리하고 학습시키는 것보다는 그래도 어느정도 의미가 있어보이는 데이터를 만들기 위해서 Gaussian Distribution을 이용하였습니다.
우선 주문 데이터를 만들기 이전에, 주문을 하는 임의의 유저를 runscript를 이용하여 생성하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
account_ids = list(Account.objects.values_list('id', flat=True))


def create_account(login_type_id, role_type_id, nickname, email):
    # Ensure nickname is unique
    unique_nickname = nickname
    count = 1
    while Profile.objects.filter(nickname=unique_nickname).exists():
        unique_nickname = f"{nickname}_{count}"
        count +=1

    account = Account.objects.create(loginType_id=login_type_id, roleType_id=role_type_id)
    Profile.objects.create(id=account.id, nickname=unique_nickname, email=email, account_id=account.id)
    return account.id

위와 같이 유저를 생성한 이후에 아래와 같이 주문 데이터를 생성하는 함수를 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def create_random_order(account_id):
    try:
        with transaction.atomic():
            order = Orders.objects.create(account_id=account_id, status=OrderStatus.PENDING)

            # Determine the number of items in this order (Normal distribution around 3 items, std dev of 1)
            num_items = int(random.gauss(3, 1))
            num_items = max(1, min(num_items, 5))

            for _ in range(num_items):
                product = random.choice(products)
                quantity = int(random.gauss(5, 2))
                quantity = max(1, min(quantity, 10))
                price = product.productPrice

                OrdersItem.objects.create(
                    orders=order,
                    product=product,
                    price=price,
                    quantity=quantity
                )

    except Exception as e:
        print(f"Error creating order for account {account_id}: {e}")

아래와 같이 코드를 작성하면 7명의 유저 데이터와 10000건의 주문 데이터가 생성됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Create accounts if less than 7
if len(account_ids) < 7:
    for i in range(len(account_ids), 7):
        nickname = f"User{i + 1}"
        email = f"user{i + 1}@example.com"
        account_id = create_account(1, 1, nickname, email)
        account_ids.append(account_id)

# Generate 10,000 orders
for _ in range(10000):
    account_id = random.choice(account_ids)
    create_random_order(account_id)

print("Sample data generation completed.")

그리고 생성된 주문 데이터를 이용해 데이터 전처리를 진행하기 위해 아래와 같이 주문 정보를 엑셀로 추출합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def export_orders_to_excel(file_path):
    orders_items = OrdersItem.objects.select_related('orders', 'product').all()

    data = []
    for order_item in orders_items:
        order = order_item.orders
        product = order_item.product
        data.append({
            'accountId': order.account_id,
            'productId': product.productId,
            'quantity': order_item.quantity,
            'price': order_item.price
        })

    df = pd.DataFrame(data)

    df.to_excel(file_path, index=False, engine='openpyxl')
    print(f"Exported orders data to {file_path}")


file_path = "orders_data.xlsx"
export_orders_to_excel(file_path)

그리고 주문 정보와 함께 사용하여 딥러닝 훈련 및 예측을 진행할 조회수 데이터를 아래와 같이 생성하고 엑셀로 추출합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def generate_and_export_view_counts(filePath):
    accountList = list(Account.objects.all())
    productList = list(Product.objects.all())

    data = []

    for account in accountList:
        for product in productList:
            viewCount = random.randint(1000, 10000)

            data.append({
                'accountId': account.id,
                'productId': product.productId,
                'viewCount': viewCount
            })

    df = pd.DataFrame(data)
    df.to_excel(filePath, index=False, engine='openpyxl')

    print(f'조회수 추출 완료: {filePath}')


filePath = "view_counts_data.xlsx"
generate_and_export_view_counts(filePath)

이제 주문 정보 데이터와 조회수 데이터를 하나로 합쳐보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pandas as pd

# 입력값으로 넘겨준 경로에서 데이터를 읽어옵니다.
def load_data(orders_file, view_counts_file):
    orders_df = pd.read_excel(orders_file)
    view_count_df = pd.read_excel(view_counts_file)

    return orders_df, view_count_df

# 두 개 데이터프레임을 accountId와 productId를 기준으로 left join합니다.
def merge_data(orders_df, view_counts_df):
    merged_df = pd.merge(orders_df, view_counts_df, on=['accountId', 'productId'], how='left')
    return merged_df

# 조회수가 누락된 데이터가 있는 경우, 누락된 자리를 0으로 채워줍니다.
def preprocess_data(merged_df):
    merged_df['viewCount'] = merged_df['viewCount'].fillna(0)
    return merged_df

# 병합 및 누락값 처리가 끝난 데이터프레임을 엑셀로 추출합니다. 
def export_data_to_excel(df, filePath):
    df.to_excel(filePath, index=False, engine='openpyxl')
    print(f"전처리 완료: {filePath}")


if __name__ == "__main__":
    orders_info_file = '../../orders_data.xlsx'
    view_count_info_file = '../../view_counts_data.xlsx'
    output_file = '../../preprocessed_orders_data.xlsx'

    orders_df, view_counts_df = load_data(orders_info_file, view_count_info_file)
    merged_df = merge_data(orders_df, view_counts_df)
    preprocessed_df = preprocess_data(merged_df)
    export_data_to_excel(preprocessed_df, output_file)

두 가지 데이터를 합치는 과정을 끝났습니다. 이제 이 중에서 중복되는 결과가 발생하는 데이터를 제거해보겠습니다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import pandas as pd


def load_data(file_path):
    return pd.read_excel(file_path)

# accountId와 productId가 동일한 행동 중에서 하나만 남기고 제거
def remove_duplicates(df: pd.DataFrame):
    unique_df = df.drop_duplicates(subset=['accountId', 'productId'], keep='first')
    return unique_df


def save_data(df, file_path):
    df.to_excel(file_path, index=False, engine='openpyxl')
    print(f'Saved unique data to {file_path}')


if __name__ == '__main__':
    input_file = '../../preprocessed_orders_data.xlsx'
    output_file = '../../orders_data_after_drop_duplication.xlsx'

    df = load_data(input_file)
    unique_df = remove_duplicates(df)
    save_data(unique_df, output_file)

위와 같은 코드를 실행하면 accountId와 productId가 동일한 데이터들은 하나만 남기고 제거됩니다. 이제부터는 FastAPI를 이용하여 딥러닝 모델 학습 및 예측을 진행해보도록 하겠습니다.
이 부분은 코드량이 매우 많기 때문에 모든 코드를 작성하진 않고, 몇 가지 부분만 가져오도록 하겠습니다.
우선 앞에서 전처리가 완료된 데이터를 가져와서 정규 분포를 따르도록 스케일링합니다.
이 부분에선 StandardScaler가 사용됩니다. 그 후, train_test_split을 사용하여 학습 및 검증 데이터를 분리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
# orders_analysis_service_impl.py
X_train, X_test, y_train, y_test = await self.__ordersAnalysisRepository.splitTrainTestData(X_scaled, y)
print(f"X_train: {X_train}, y_train: {y_train}, "
      f"X_test: {X_test}, y_test: {y_test}")

# orders_analysis_repository_impl.py
async def splitTrainTestData(self, X_scaled, y):
    X_train, X_test, y_train, y_test = train_test_split(
        X_scaled, y, test_size=0.2, random_state=42
    )

    return X_train, X_test, y_train, y_test

위와 같이 코드를 작성하여 학습용 데이터와 검증용 데이터를 분리해냈습니다. 그 다음엔 인공신경망 층을 쌓아서 모델을 만들어봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
async def createModel(self):
    model = tf.keras.models.Sequential([
        tf.keras.layers.Dense(128, activation='relu', input_shape=(1,)),
        tf.keras.layers.Dropout(0.3),
        tf.keras.layers.Dense(64, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(64, activation='tanh'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(32, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(32, activation='tanh'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(16, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(16, activation='tanh'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(8, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(8, activation='tanh'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(4, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(4, activation='tanh'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(2, activation='relu'),
        tf.keras.layers.Dense(2, activation='tanh'),
        tf.keras.layers.Dense(1),
    ])

    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
        # loss='mean_squared_error',
        loss='mse',
        metrics=['mae']
    )

    return model

위와 같이 relutanh 활성 함수, 그리고 dropout을 이용하여 인공신경망 층을 쌓았고, optimizer로는 Adam, loss function은 mean squared error를 사용하였습니다.
생성된 모델에 훈련 데이터를 입력시켜 모델 학습을 진행할 수 있습니다.

1
2
3
4
5
6
7
# orders_analysis_repositiory_impl.py
async def fitModel(self, model, X_train, y_train):
    model.fit(X_train, y_train, epochs=100, validation_split=0.2, batch_size=32, verbose=0)
    return model

# orders_analysis_service_impl.py
model.save(f"ordersModel_{index + 1}.h5")

모델 학습 과정이 궁금하다면 verbose 파라미터를 1로 설정하여 과정을 볼 수도 있습니다.
학습된 모델은 예측에 사용하기 위해 별도로 저장해주었습니다.
그리고 마지막으로 저장된 모델을 불러와서 예측을 진행할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# orders_anlaysis_repository_impl.py
async def predictFromModel(self, ordersModel, X_pred_scaled):
    return ordersModel.predict(X_pred_scaled).flatten()[0]

# orders_analysis_service_impl.py
async def predictQuantityFromModel(self, viewCount):
    print(f"predictQuantityFromModel -> viewCount: {viewCount}")

    scaler = joblib.load('ordersModelScaler.pkl')

    ordersPredictionList = []

    for index in range(1, 11):
        ordersModel = tf.keras.models.load_model(f"ordersModel_{index}.h5", custom_objects={'mse': 'mse'})
        X_pred = np.array([[viewCount]])
        X_pred_scaled = await self.__ordersAnalysisRepository.transformFromScaler(scaler, X_pred)

        ordersPredict = await self.__ordersAnalysisRepository.predictFromModel(ordersModel, X_pred_scaled)

        ordersPredictionList.append(float(ordersPredict))

    averagePrediction = np.mean(ordersPredictionList)

    print(f"averagePrediction: {averagePrediction}")
    return averagePrediction

위와 같이 코드를 작성하고 실행하면, 상품의 조회수에 따른 구매 개수의 예측값이 반환됩니다.


:face_with_spiral_eyes:Lacked

이번 주는 스스로 느끼기에 피곤함을 많이 느꼈던 한 주인 것 같습니다.
확실히 강의 이후나 주말엔 여유가 생겼지만 오전 강의를 들을 때나 점심 이후 강의 시간에 피곤함이 몰려와서 매우 고생을 했던 기억이 있습니다.
체력을 늘리기 위해서 매일 아침 운동도 하고 영양제도 많이 챙겨먹고 있지만, 더워지는 날씨와 출퇴근 지하철이 저를 힘들게 하는 것 같다는 생각이 듭니다.:sob:
결과적으로 이번 주에 부족했던 점은 오전과 점심 시간 이후 한시간정도 강의에 집중하기 못했다는 점이라고 생각합니다. 다음 주엔 껌이나 사탕 같은 것을 넉넉히 준비해서 피곤할 때마다 입을 움직일 수 있도록 해야될 것 같습니다..!


:thought_balloon:Longed for

앞으로 바라는 점은 제가 계속해서 강의를 열심히 따라가서 지금처럼 꾸준히 발전하는 사람이 되었으면 하는 것입니다.
적당한 선에서 만족하고 타협하는 것이 아니라 스스로 계속 밀어붙이면서 제가 어느정도까지 성장할 수 있는지 알아보고 싶습니다.
지금도 매일매일 피곤하다는 말을 달고살긴하지만 아직 운동을 그만두거나 아침에 못일어나는 게 아닌걸 보면 아직은 이렇게 계속 진행해도 될 것 같다는 생각이 듭니다.
그리고 하나 더 바라는 점이 있다면 이 캠프에서 비전공으로 노력하고 있는 분들께,
절대 포기하지 말고 끝까지 가보자” 라는 말을 전해주고 싶습니다.
사실 많이 힘든건 제가 아니라 이런 것을 처음 접해보는 사람들이라고 생각합니다. 강의 내용을 따라잡기 위해서 저보다 많은 시간을 쏟으면서 노력하는 여러분을 응원합니다.
조금만 더 버텨봅시다!:fire:
meme1

This post is licensed under CC BY 4.0 by the author.