[2024.11.01] 필수 온라인 강의 Part15 Machine Learning Basic CH06 다양한 형태의 데이터 처리와 ML기반의 추천시스템
추천시스템
- 추천 시스템(Recommendation or Recommender System)
데이터를 활용해, 기하급수적으로 많은 옵션들 중에서 사람들이 원하는 것이 무엇인지
-> 예측하고, 범위를 좁혀주고, 결국 찾아주는 머신러닝의 한 분류
- 넷플릭스, 유튜브, 아마존, 멜론, 쿠팡, 웹툰, 등에서 사용하는 시스템 - 구성요소
- 사용자(User) : 추천을 받는 대상으로, 추천 시스템을 이용하는 서비스의 이용자
- 아이템(Item), 또는 콘텐츠(Contents) : 추천 시스템에서 추천되는 대상
- 활동 기록(Transaction Log)
- 아이템을 구매, 특정 페이지를 조회했는지 여부, 페이지 체류시간 등 사용자의 모든 행동에 대한 기록
- 피드백(Feedback)
- 활동 기록중 일부로, 특정 아이템에 대한 사용자의 선호, 또는 비선호를 드러낼 수 있는 행동들
- 직접적(Explicit) 피드백 : 좋아요 버튼, 별점, 리뷰 등 직접적으로 아이템에 대한 선호/비선호를 드러내는 행동
- 간접적(Implicit) 피드백 : 클릭(조회), 구매, 페이지 체류 시간 등, 선호/비선호의 판단에 간접적으로 활용할 수 있는 정보
- 유저 프로필(User Profile)
- 여러 활동기록과 피드백을 통해 수집한 유저에 대한 정보를 모아놓은 데이터 - 추천시스와 ML
- 추천시스템에서 사용하는 다양한 문제정의와 데이터가 이와 연관이 크다
- 기업의 수익과 직접적으로 연관된 기술
- 추천시스템은 기업이 돈을 벌도록 해주는, 수익모델과 직접적으로 연관된 경우가 많아, 수많은 분야에서, 매우 오래전부터 연구됨
- 다양한 형태의 데이터를 활용
- 다양한 형태의 문제정의를 통해 접근
- 비정형 데이터를 다룰 기술(딥러닝)이 충분히 발전하기 이전에도 연구되었던 기술
- 수많은 정형데이터의 활용기법 예시가 됨
다양한 형태의 추천 시스템
- 스트리밍 서비스
- 회사가 보유한 수없이 많은 콘텐츠 중 특정 유저가 좋아할만한 콘텐츠를 찾아 추천
- 동영상, 음악, 웹툰
- 유튜브, 넷플릭스, 멜론, 스포티파이, 웹툰 등 - 소셜 네트워크 서비스
- 콘텐츠 피드 (무한 스크롤링)
- 친구, 팔로우 제안
- 유해 정보, 혐오표현, 가짜뉴스 필터링
- 메타, 인스타그램, 트위터, 페이스북 등 - 교육 서비스
- 학생이 어떤 유형의 문제를 틀리는지 확인해 비슷한 유형의 문제나 공부해볼 분야를 추천해주는 형태 - 헬스케어 서비스
- 개인의 건강기록에 기반해서, 혹은 특정 연령대의 사람들에게 적합한 의료서비스를 추천해주는 등 - 금융 서비스
- 고객의 신용정보, 소비 패턴 등에 맞춰 금융상품이나 신용카드 등 서비스 추천
* 건강기록이나 금융정보 등은 매우 민감한 개인정보일 가능성이 크기 때문에 함부로 다루면 문제가 생길 수 있음 - 광고 플랫폼
- 웹페이지나 앱에서 광고 하나를 보기전 1초 남짓의 시간동안 수많은 회사들이 (물론 전부 자동화된 프로세스로) 광고 자리를 두고 경쟁 입찰에 참여 - 다양한 구조의 추천시스템 문제 정의
- 입력값
- 유저 프로필(User Profile) : 여러 활동기록과 피드백을 통해 수집한 유저에 대한 정보를 모아놓은 데이터
- 아이템 프로필(Item or Contents Profile) : 다양한 방식으로 수집된 각 콘텐츠에 대한 정보들
- 영화의 장르, 감독이나 출연배우 등에 기반한 추천
- 유저의 피드백 정보 : 다양한 방식으로 제공되는 유저의 피드백 정보를 활용해 추천시스템을 구축
- 직접적인 평가 ⇒ 0(정보없음) 또는 1~5점
- 선호/비선호 표시 ⇒ 0 또는 1/-1
- 상호작용 있음/없음 ⇒ 0 또는 1 - 어떤 결과값을 예측
- 분류 모델링
- 특정 유저가 다음으로 클릭할 아이템
- 특정 유저가 특정 아이템을 구매할지 아닐지
- 회귀 모델링
- 유저의 특정 아이템에 대한 선호도(평점) 예측
- 비지도학습
- 아이템간, 유저의 선호도 패턴간의 유사성 판단
- 특정 유저가 선호할만한 아이템의 순위 예측 (Ranking based)
기초 추천시스템 모델링 방법론
추천시스템의 세부분류
- 인기도 기반(Popularity-based) 추천
- 조회수, 평균 평점, 리뷰 개수, 좋아요/싫어요 등의 사용자 피드백 정보를 활용해, 다른 사용자들에게 가장 인기 있는 아이템을 새로운 유저에게 추천하는 방식.
- 쇼핑몰의 기본 인기순 추천 옵션
- 뉴스, 웹툰 등의 댓글 추천
- 모든 사용자에게 같은 추천을 제공하는 비개인화 추천 방식 어떻게 인기도를 측정, 비교하는가에 따라 상당히 고도화된 방식들도 존재
- 사용자의 추천/비추천과 게시물이 올라온 시간까지 고려해 인기도를 측정하는 레딧 공식
- 웹페이지간의 상호 연결관계를 활용하는 구글의 페이지 랭크방식 - 연관규칙(Association Rule Analysis)
- 데이터베이스 내에서 동시에 등장하는 빈도가 높은 컨텐츠의 조합을 찾아내는 방법론
- 특정 상품을 “장바구니”에 담으면, 다른 유저가 그 상품과 함께 구매한 상품들을 추천해주는 방식
- 세부 방법론 : Brute Force, Apriori Algorithm, FP-Growth Algorithm - 콘텐츠 기반 추천 (CBF, Contents-Based Filtering)
- 다양한 방식으로 수집된 각 컨텐츠에 대한 정보들
- 스릴러 장르를 선호하는 유저에게 또 다른 스릴러 장르의 영화를 추천하는 방식
- 크리스토퍼 놀란 감독의 영화를 좋아한 유저에게 해당 감독의 또다른 영화를 추천하는 방식 - 협업 필터링(CF, Collaborative Filtering)
- 사용자들, 또는 아이템 사이의 유사성을 기반으로 작동하는 추천시스템 방식
- 유사성 판단 방법
- 코사인 유사도(Cosine Similarity)
- 두 벡터 사이의 유사성을 측정하는 다양한 방식들 중 하나로, 공간상에서 두 벡터 사이의 각도의 코사인 값을 기준으로 유사성을 판단
- -1 에서 1 사이의 값으로 나타냄, 두 벡터가 서로 유사할수록 1 에 가까운 값이 나옴
- 두 벡터 사이의 내적 값을 길이의 곱으로 나누어 쉽게 계산
- 기억 기반(Memory-based) 협업 필터링
- 각각의 사용자들이 각각의 아이템을 선호하는지 여부를 나타낸 사용자-아이템 행렬을 기반으로, 사용자들 사이, 또는 아이템 사이의 유사성을 판단
- 주어진 사용자와 비슷한 사용자를 찾아내 활용하는 유저 기반(User-based) 협업필터링
- 주어진 아이템과 비슷한 아이템을 찾아내 활용하는 아이템 기반(Item-based or Contents-based) 협업필터링
- 사용자-아이템 선호도 행렬
- 모든 사용자-아이템 쌍에 대해, 사용자의 아이템에 대한 선호도를 수치로 나타낸 행렬
- 다양한 피드백 정보를 활용해 구축
- 직접적인 평가 ⇒ 0(정보없음) 또는 1~5점
- 선호/비선호 표시 ⇒ 0 또는 1/-1
- (구매 등)상호작용 있음/없음 ⇒ 0 또는 1
-> 대부분의 경우 유저는 전체 DB의 아이템 중 매우 적은 일부에 대해서만 상호작용
-> 사용자-아이템 행렬은 대부분 값이 상호작용 없음(0)으로 나타나는, 희소행렬(Sparse Matrix)이 됨
- 잠재 요인(Latent Factor)분석
- 유저들의 아이템에 대한 선호도, 사용자-아이템 행렬을 직접 사용하는 것이 아니라,
특정 피쳐에 대한 선호를 나타내는 잠재요인(Latent Factor)를 찾아내도록 학습하는 방법.
- 비지도학습 챕터에서 배웠던 임베딩(embedding)과 같은 개념 - 협업 필터링 방식의 한계점
- 콜드스타트(Cold Start)문제
- 유저의 선호도 패턴을 파악하려면 어느정도 데이터가 필요하기 때문에, 선호도 데이터가 없는 신규 유저의 경우 정확한 - 추천을 제공하기 힘듬
- 개인정보(Privacy) 문제 서비스의 이용자 본인이 아닌 다른 이용자의 정보를 저장하고 이를 기반으로 추천을 제공하기 때문에, 개인정보 처리와 관련된 법률적 문제 등이 생기기 쉬움
- 시간에 따른 선호도 변화 문제
- 사용자의 취향은 고정된 것이 아니라 시간에 따라 변화할 수 있는데, 협업필터링 방식의 추천시스템에서는 이를 반영하기 힘듬
협업 필터링을 활용한 평점 예측 실습
- 협업 필터링 방식으로 동작하는 추천시스템을 직접 구현
- 실습 데이터셋 준비
- The Movies Dataset : https://www.kaggle.com/datasets/rounakbanik/the-movies-dataset/data
- pandas 패키지의 read_csv함수를 사용해 다음과 같이 데이터를 읽어옴
rating_data = pd.read_csv('/content/ratings_small.csv')
movie_data = pd.read_csv('/content/movies_metadata.csv')
- 데이터 확인
rating_data
- userId, movieId, rating, timestamp의 형태로 변수를 저장
- 데이터셋을 불러와 전처리
- movie 메타 데이터 파일 사용
movie_data
- 영화의 id, title간 변환을 위한 dictionary생성
- id를 통해 타이틀을 찾을 수 있도록, 영화 메타데이터에서 id, 타이틀 정보만 선택하여 dictionary형태로 변형
- 위 dict의 key, value를 뒤집어 별도의 dict로 생성
# 빈 영화 메타데이터의 id에 title을 대응시키는 dict 생성
id_to_title = {row['id']: row['title'] for _, row in movie_data[['id', 'title']].iterrows()}
print(id_to_title['862'])
title_to_id = {v: k for k, v in id_to_title.items()}
print(title_to_id['Iron Man'])
- 평점 데이터셋 정보 파악
- pandas 패키지의 groupby기능을 활용해 영화별 평점 수 확인
- 히스토그램 형태로 시각화
# 유저 평점 데이터들 중 동일한 movieId를 가진 샘플들을 묶어(groupby) 각 영화에 대한 평점이 몇개인지 확인
movie_rating_count = rating_data.groupby('movieId')['rating'].count()
# 히스토그램 시각화
plt.figure(figsize=(8, 8))
fig = plt.hist(movie_rating_count, bins=200)
plt.ylabel('Number of movies', fontsize=12)
plt.xlabel('Number of ratings', fontsize=12)
plt.show()
- 매우 심한 long-tailed 분포를 보이는 것을 확인
- 전체 영화 중에서 10개, 100개 이상의 평점을 받은 데이터가 얼마나 있는지도 파악
print("평점 데이터 내에 있는 총 영화 수 :", len(rating_data['movieId'].unique()))
print("10개 이상의 평점을 받은 영화 수 :", len(movie_rating_count[movie_rating_count >= 10]))
print("100개 이상의 평점을 받은 영화 수 :", len(movie_rating_count[movie_rating_count >= 100]))
- 영화별 평균 평점의 분포 확인
- pandas의 groupby기능을 활용해 영화별 평가점수 평균값 확인
- 10개 이상의 평가를 받은 영화들 중 평균 rating이 가장 높은 영화 확인
# 영화별 받은 평가의 수 / 평균 점수를 각각 첫번째, 두번째 column으로 하는 데이터를 생성
movie_rating_info = rating_data.groupby("movieId")['rating'].agg(['count', 'mean'])
movie_rating_info.columns = ['rated_count', 'rating_mean']
movie_rating_info
- 영화별 평점 평균값 분포 시각화
# 영화별 평가점수 분포 확인
movie_rating_info['rating_mean'].hist(grid=False)
- 높은 평점을 받은 영화 확인
- 충분한(100개 이상) 평가를 받은 영화들 중 평균 평점이 높은 영화들의 id를 확인
# 100번 이상의 평가를 받은 영화 중, 평점이 높은 10개의 영화를 출력합니다.
indices_enough_eval = movie_rating_info['rated_count'] > 100
# 평가 수가 100개 이상인 영화들만 선택 후 최고 평점인 10개 영화의 id확인
top_10_movies = movie_rating_info[indices_enough_eval].nlargest(10, 'rating_mean')
top_10_movies
- 확인한 고평점 영화 id에 해당하는 영화 제목을 확인해 출력
for movie_id, (count, score) in top_10_movies.iterrows():
try:
# 영화 메타데이터 내에 해당 id의 영화가 있다면 타이틀을 출력
title = id_to_title[str(movie_id)]
except KeyError:
# 없다면 영화 id를 그대로 출력
title = f'unknown movie id: {movie_id}'
# 제목과 받은 평점, 평가 수를 함께 출력
print(f'{title}, score: {score:.03}, count: {int(count)}')
- 메모리 기반의 협업 필터링
- 유저-아이템 선호도 행렬 : 사용자들이 아이템에 대해 표현한 선호도를 담고 있는 행렬
- 각 사용자들의 특정 아이템에 대해 표현한 선호도를 나타냄.
- 협업 필터링 방식의 추천시스템을 만들기 위해 영화 평가 점수 데이터를 유저-아이템 선호도 행렬의 형태로 바꿈.
rating_data
- 평가 데이터를 유저-아이템 선호도 행렬 형태로 변환
- rating_data에서 불필요한 timestamp값 제거
rating_data.drop('timestamp', axis=1, inplace=True)
rating_data.head()
- pandas의 pivot_table기능을 활용해 유저-아이템 선호도 행렬을 생성
# pivot_table: 데이터 column 중에서 행, 열로 사용할 두개를 골라 각각을 축으로 데이터를 펼쳐두는 기능
user_movie_rating = rating_data.pivot_table('rating', index='userId', columns='movieId')
user_movie_rating
- 평가가 없는 빈 값들을 0으로 채우기
# 빈 값들을 0 으로 채워주기
user_movie_rating.fillna(0, inplace=True)
user_movie_rating
- 희소행렬(Sparse matrix)
- 위에서 생성한 유저-아이템 선호도 행렬은 대부분의 값이 0으로 비어있고, 소수의 값에만 0이 아닌 값들이 들어있는데, 이런 형태의 행렬을 희소행렬(또는 성긴 행렬, Sparse matrix)이라고 함
- 반대되는 개념으로서 일반적인 행렬을 밀집행렬(Dense matrix)
- 주어진 데이터가 희소행렬의 경우 0인 부분은 생략하고 나머지 0이 아닌 부분의 데이터만 저장해 효율적으로 메모리를 사용하는 CSR(Compressed Sparse Column), COO(list of COOrdinates)등의 여러 효율적인 데이터포멧을 사용할 수 있음
- 연산시에도 이런 포멧에 최적화된 여러 연산들을 사용하면 효율적이지만, 이 실습에서는 생략.
- 콘텐츠 기반의 협업 필터링
- 선호도 패턴의 비교를 통한 영화간 유사도 확인
- 사용자-영화 평점 matrix에 transpose를 취하기
user_ratings_matrix_T = user_movie_rating.transpose()
user_ratings_matrix_T
- 모든 row, 영화끼리의 코사인 유사도를 계산
# 모든 아이템(영화) 쌍마다 코사인유사도를 계산해 행렬의 형태로 저장
item_sim = cosine_similarity(user_ratings_matrix_T, user_ratings_matrix_T)
# 생성된 유사도 행렬에 원래의 movie id정보를 추가해 다시 dataframe형태로 변환
movie_ids = user_movie_rating.columns
item_sim_df = pd.DataFrame(
data=item_sim,
index=movie_ids,
columns=movie_ids
)
# 생성된 유사도 행렬 확인
print(item_sim_df.shape)
item_sim_df.head()
- 특정 영화와 가장 비슷한 영화목록 찾기
- 선택한 영화와 가장 유사한 5개 영화를 찾아보기
- 주석처리된 부분을 사용해 랜덤한 영화를 선택
# 직접 영화 제목을 지정하는 경우
movie_title = "Men in Black II"
target_movie_id = int(title_to_id[movie_title])
# # 랜덤으로 선택하는 경우
# available_ids = [movie_id for movie_id in rating_data['movieId'].unique() if str(movie_id) in id_to_title]
# target_movie_id = np.random.choice(available_ids)
# movie_title = id_to_title[str(target_movie_id)]
# 선택된 영화의 제목과 id를 출력
print("분석대상 영화: ", movie_title)
print("id: ", target_movie_id)
# (자기 자신을 제외하고) 가장 비슷한 5개 영화의 아이디를 찾기
result = item_sim_df[target_movie_id].sort_values(ascending=False)[1:6]
# 결과 출력
print("\n이 영화와 유사한 영화들: ")
for movie_id, similarity in result.items():
try:
# 영화 메타데이터 내에 해당 id의 영화가 있다면 타이틀을 출력
title = id_to_title[str(movie_id)]
except KeyError:
# 없다면 영화 id를 그대로 출력
title = f'unknown movie id: {movie_id}'
print(f"{title}, 유사도: {similarity:.03}")
- 랭킹(Ranking)기반의 추천시스템
- 이렇게 콘텐츠 기반 협업 필터링 기반의 영화 추천 결과를 사용하면 특정 영화에 대해 긍정 피드백을 남긴 유저에게 해당 영화와 비슷한 영화들의 순위를 보여줄 수 있음.
- 이런식으로 추천의 결과가 아이템들에 대해 해당 유저의 예상 선호도 순위의 형태로 나오는 방법론들을 순위, 랭킹 기반의 추천시스템이라고 부름.
- 사용자 기반의 협업 필터링
- 콘텐츠 기반과 같은 방식으로 유저간의 유사도 계산
# 모든 유저 쌍마다 코사인유사도를 계산해 행렬의 형태로 저장
item_sim = cosine_similarity(user_movie_rating, user_movie_rating)
# 생성된 유사도 행렬에 원래의 movie id정보를 추가해 다시 dataframe형태로 변환
movie_ids = user_movie_rating.index
item_sim_df = pd.DataFrame(
data=item_sim,
index=movie_ids,
columns=movie_ids
)
# 생성된 유사도 행렬 확인
print(item_sim_df.shape)
item_sim_df.head()
- 잠재요인 기반의 협업 필터링
- 특이값 분해(SVD, Singular value decomposition)
- 주어진 행렬 A를 특정한 성질을 가진 3개 행렬의 곱으로 표현하는 방법
- 분해된 결과 행렬에서 일부 중요하지 않은 값들을 버리게 되면, 다시 행렬을 복구했을 때 원본과 차이가 생기는 대신 원본 데이터을 좀더 단순화된 패턴으로 표현할 수 있는데, Truncated SVD는 SVD 행렬분해에 이러한 방식을 적용한 것
- SVD 행렬분해
user_movie_rating
# 전체 데이터의 평균을 구하고 결과를 출력
total_avg_rating = user_movie_rating.to_numpy().mean()
print(total_avg_rating)
# 이를 이용해 전체 평점 데이터의 평균을 0으로 맞추는 전저리를 진행
centered_user_movie_rating = user_movie_rating - total_avg_rating
centered_user_movie_rating
- SVD 행렬분해를 활용한 평점 예측
user_movie_rating
# 전체 데이터의 평균을 구하고 결과를 출력
total_avg_rating = user_movie_rating.to_numpy().mean()
print(total_avg_rating)
# 이를 이용해 전체 평점 데이터의 평균을 0으로 맞추는 전저리를 진행
centered_user_movie_rating = user_movie_rating - total_avg_rating
centered_user_movie_rating
- SVD를 이용한 유저-아이템 행렬의 Factorization
- Scipy의 svds 함수를 통해 유저-아이템 행렬에 Truncated SVD 적용
- 분해된 결과 행렬들을 다시 곱해, 원래의 유저-아이템 행렬을 불완전하게 복원하는 방식으로 원래 0이었던 빈 값들을 채워넣음.
# truncated svd 수행. 원본 행렬을 12개의 rank-1 행렬의 합으로 표현
U, sigma, Vt = svds(centered_user_movie_rating.to_numpy(), k=12)
# 원래의 유저-아이템 행렬을 불완전하게 복원
svd_user_predicted_ratings = (U * sigma) @ Vt + total_avg_rating
# 결과를 다시 dataframe으로 변환 후 값을 확인
df_svd_preds = pd.DataFrame(svd_user_predicted_ratings, columns=centered_user_movie_rating.columns)
df_svd_preds
- 원래 비어있던 평점값들이 Truncated SVD를 적용한 이후 예측값들로 채워진 결과를 확인
시계열 데이터의 처리
- 시계열 데이터(Time-series data)
- 시간 순서로 관측된 일련의 데이터. 일반적으로 동일한 시간간격마다 측정된 값들의 시퀀스
- 시간적 순서를 가지는 데이터로, 일반적으로 동일한 간격으로 연속된 시각에서 취한 순차적 데이터
- 시간별 기온, 음성 데이터, 연도별 전세계 총 인구수, 일별 교통사고 건수, 주식의 가격 데이터, 매일 아침 9시 서울의 기온 - 순차적 데이터(Sequential data)
- 어떠한 순서를 가지는 데이터로, 순서가 바뀌면 의미를 잃음
- 텍스트 데이터, 유전체(DNA) 데이터, 시계열 데이터 전체를 포함 - 규칙 성분과 불규칙 성분
- 불규칙 성분(Irregular Component)
- 시간에 따른 규칙적인 움직임과 무관한, 랜덤한 원인에 의한 변동.
- 예측이 불가능한 노이즈 성분으로 이해할
- 규칙 성분(또는 체계적 성분, Systematic or Regular Component)
- 시간에 따른 데이터의 변동 중에서, 시간에 따라 보이는 데이터의 패턴이나 경향성에 의해 설명될 수 있는,예측 가능한 변동 성분
- 규칙 성분은 다시 ● 추세(Trend) ● 계절성(Seasonality) ● 순환성(Cycle) 의 세가지 성분으로 나누어볼 수 있음
- 추세 (trend)
- 데이터가 단기적으로는 증감하며 변화하더라도 장기적으로 점차 증가하거나 점차 감소하는 등의 방향성을 보이는 것
- 인구 수, 전 세계 평균기온, 시간에 따른 강사의 체지방률 변화 등
- 계절성 (seasonality)
- 계절에 따른 변화처럼 일정하게, 정기적으로 데이터가 변화하는 것
- 성수기/비수기에 따른 비행기 탑승객 수, 평일/주말에 따른 식당의 손님 수, 낮과 밤의 기온, 조수 간만의 차, 서울의 월별 강수량 등등.
- 순환성 (cycle)
- 데이터가 주기적으로 변화하기는 하지만, 일정하지 않은 주기로 변화하는 것
- 2~7년 주기로 번갈아 발생하는 엘니뇨와 라니냐 현상에 따른 수온변화
- 대략 11년을 주기로 증가와 감소를 반복하는 태양 흑점의 개수 - 분석
- 기술적 분석 (Descriptive Analysis)
- 과거의 데이터를 이해하기 위한 분석과정.
- 데이터가 가진 패턴, 추세, 주기성 등의 규칙적인 정보를 파악해 데이터의 기본 구조와 패턴을 이해하고, 이상치를 탐지하거나 데이터의 일반적인 행동을 설명
- 예측적 분석 (Predictive Analysis)
- 미래의 데이터를 예측하기 위해 과거 데이터의 분석하는 것.
- 미래의 트렌드, 변동성 등을 예측하고, 비즈니스 전략을 수립하거나 의사결정을 지원 - 시계열 데이터의 특성
- 시계열 데이터로부터 데이터를 예측하는 것은 기본적으로 과거가 미래에 영향을 주기 때문에 가능
- 일반적으로 머신러닝에 쓰이는 데이터와는 달리 서로 다른 시점의 데이터가 서로 독립이 아니므로, 독립항등분포(IID) 가정이 성립하지 않는 다는 것을 주의!!!
- 독립 항등 분포 (IID; Independent and Identically Distributed)
- 어떤 확률변수들이 같은 확률분포로부터 생성된 독립적인 샘플이라면 각 변수를 지칭
- 머신러닝에서 다루는 대부분의 모델이 IID를 가정하므로 IID를 가정하지 못하는 데이터의 경우 추가적인 고려가 필요
- 자기 상관 (Autocorrelation)
- 한 시계열 내에서 자기자신과 다른 시점과의 상관관계
- t 시점과 시차 k 이후인 t+k 시점 데이터의 상관계수로 측정
- 마르코프 속성 (Markov property)
- 랜덤 프로세스의 미래 상태에 대한 조건부 확률 분포(과거 및 현재 값 모두에 대한 조건부)가 현재 상태에만 의존하는 것
- 마르코프 속성을 가정하고 예측 모델링하면 마르코프 모델
- 마르코프 속성을 만족하는 이산시간 랜덤 프로세스를 마르코프 체인
- 현재 상태가 이전 1개 상태만이 아닌 n개 상태에만 의존하면 n차 마르코프 속성
- 시계열의 정상성(Stationarity)
- 정상성(stationary) 데이터는 데이터를 생성하는 분포가 시간에 따라 변하지 않고 동일하게 유지되는 것.
- 화이트노이즈: 정상적
- 비정상성(non-stationary) 데이터는 반대로 생성 분포 자체가 시간이 지남에 따라서 변화하는 것.
- 주식의 가격: 비정상적 - 시계열 데이터의 분석 방법
- 시계열 데이터는 non-IID와 같은 여러 특성에 주의해 분석을 수행
- 정상성 검정(stationarity test)을 이용하여 데이터의 정상성 여부를 먼저 판단
-> 데이터를 정상적 데이터로 전처리(분해)
-> 이를 이용하여 모델을 구성
- 평균이 일정하도록, 분산 및 공분산에 연속적인(시점 t에 의존하는) 상관성이 없도록 변환
- 적용 분야에 따른 두 가지 접근법
- 시계열의 진동수를 활용한 분석
- 푸리에(Fourier) 분석
- 스펙트럼 밀도(Spectrum Density) 분석
- 웨이브렛(Wavelet) 분석
- 시간에 따른 변화를 분석
- 자기회귀모델(Autoregressive model)
- 이동평균(Moving Average)
- 추세(trend)분석
- 성분분해(decomposition) 등등 - 시계열데이터의 성분분해
- 시계열데이터를 각 성분으로 분해하여 예측이나 계절성에 따른 변화를 조정하는데 활용
- 주어진 시계열을 계절성, 추세와 불규칙 성분으로 분해하는 계절성 분해(Seasonal Decomposition) - 이동 평균 모델 (Moving Average model)
- p 기간 안의 시계열 데이터를 평균하여 시간 t의 추세-주기를 측정
- 시계열 데이터에서 단기적인 변동을 smoothing하여 제거하고, 장기적인 추세를 더 잘 확인 - 자기회귀 모델 (Auto-Regressive model)
- 시계열의 특정 스텝(t)을 예측하기 위해, 일정 스텝 수(p) 만큼의 과거 데이터들(t-p 부터 t-1까지)을 입력값으로 선형회귀모델을 학습하는 방식
시계열 데이터 처리 실습
- 데이터 불러오기
데이터 셋 : New York Stock Exchange - https://www.kaggle.com/datasets/dgawlik/nyse
- 뉴욕 주식시장에서 발생한 일일 주식거래 데이터셋
# 데이터 불러오기
data = pd.read_csv('/content/prices.csv')
# 불러온 데이터를 확인
data
- 데이터셋을 불러와 기본정보 확인
# 데이터 컬럼 명 확인
data.columns
# 데이터 정보 확인
data.info()
# 데이터 요약 통계량 확인
data.describe()
- 데이터셋 전처리 적용
- 문자열(str) 형태로 나와있는 시간 정보를 수치 형태로 변환
- pandas to_datetime 함수 활용해 주어진 문자열(str)타입 데이터를 datetime object로 변환
# 원래의 string type 날짜를 별도 column으로 백업해줍니다.
data["str_date"] = data["date"].copy()
# 날짜 타입 데이터 변환
data['date'] = pd.to_datetime(data['date'], format='%Y-%m-%d', infer_datetime_format=True)
# 변환 전후의 데이터 타입과 첫 5개 값을 비교해봅니다.
print("원본 데이터")
print("데이터 타입: ", data['str_date'].dtype)
print(data['str_date'][:5])
print("\ndatetime 변환 후")
print("데이터 타입: ", data['date'].dtype)
print(data['date'][:5])
- 변환된 datetime에서 연, 월, 일, 요일 등 정보를 숫자로 가져오기
# datetime의 "일" 정보만 따로 가져오기.
data['date'].dt.day.head()
# datetime의 "요일" 정보만 따로 가져오기
data['date'].dt.dayofweek.head()
- 파이썬의 built-in 라이브러리인 datetime을 활용해 생성한 object와 비교
# python의 built-in library로 datetime object를 생성
dt_example = datetime(2016, 1, 7)
# 생성한 예시 날짜와 데이터의 date영역 크기비교
# 2016년 1월 7일보다 작은 날짜인 첫 3개 값만 True로 나타난다.
print((data["date"] <= dt_example).head())
data["date"].head()
- 시계열 데이터 분석 실습
- symbol column을 활용해 APPLE의 데이터만 선택
# 주식회사 APPLE (symbol이 'AAPL')의 주식 데이터
apple = data[data['symbol'] == 'AAPL']
apple
- 데이터 확인 및 시각화
# APPLE의 일별 종가
ax = sns.lineplot(data=apple, x='date', y='close')
ax.set_title("APPLE's closing price per date")
plt.show()
- 분석대상 데이터 선택
- 주식의 액면분할 시점을 datetime object로 생성
- 위 시점을 기준으로 가격 데이터를 분할
- 분할 결과를 시각화해 확인
# 샘플수 비교
print(f"원본 데이터 샘플수: {len(apple)}")
# 액면분할 시점을 datetime object로 만들기
day_split = datetime(2014, 6, 9)
# "date"가 분할시점 이후인 데이터만 남기기
apple = apple[apple["date"] >= day_split]
print(f"액면분할 후 데이터 샘플수: {len(apple)}")
# 남은 데이터를 시각화
fig = sns.lineplot(data=apple, x='date', y='close')
fig.tick_params(axis='x', labelrotation=45)
fig.set_title("APPLE's closing price per date")
plt.show()
- 정상성 검정 (Stationarity test)
- 정상성(Stationarity)
- 시계열 데이터의 통계적 특성이 시간에 따라 일정하게 유지되는 성질을 의미
- 평균, 분산, 공분산 등이 시간에 따라 변하지 않는다는 것
- 정상성 검정(Stationarity test)은 시계열 데이터가 정상성을 가지고 있는지 확인하는 방법
- 대표적인 정상성 검정 방법 : Dickey-Fuller 검정, KPSS 검정
- 만약 데이터에 정상성이 있다면 결과의 p-value가 매우 작은 값으로 나옴.
- 시계열 데이터가 정상성을 가지면 예측 모델링이 더욱 용이
-> 데이터의 미래 행동이 과거 행동과 유사할 것이라는 가정 하에 예측 모델을 만들기 때문
# stationary test
dftest = adfuller(apple['close'])
# 결과값을 잘 보여주기 위한 함수
dfoutput = pd.Series(
dftest[0:4],
index=[
"Test Statistic",
"p-value",
"#Lags Used",
"Number of Observations Used",
],
)
for key, value in dftest[4].items():
dfoutput["Critical Value (%s)" % key] = value
print(dfoutput)
- 시계열데이터의 성분 분해
- 일반적으로 네 가지 주요 구성 성분으로 분해
- 추세(Trend)성분: 시간이 지남에 따라 시계열 데이터가 보이는 전반적인 방향 또는 경향을 나타냄
- 예를 들어, 회사의 매출이 점차 증가하는 경우, 이는 '상향 추세'를 나타냄
- 계절(Seasonal)성분: 일정한 시간 간격으로 반복되는 변동.
- 예를 들어, 매년 여름에 아이스크림 판매량이 증가하는 경우, 이는 '계절성'을 나타냄
- 주기(Cyclical)성분: 일정하지 않은 시간 간격으로 반복되는 변동
- 이는 경제 주기나 비즈니스 주기와 같은 장기적인 패턴을 포함
- 불규칙(Irregular)성분: 예측할 수 없는 무작위 변동
- 이는 자연재해, 경제적 충격 등 예측할 수 없는 이벤트에 의해 발생
- 앞의 세가지는 규칙성분으로 시계열 데이터의 예측에 중요한 역할
- statsmodels 패키지의 seasonal_decompose를 활용해 APPLE 주가 데이터를 분해
# 날짜 변수를 더 잘 시각화하기 위해 데이터 인덱스에 date 변수 넣기
apple.set_index('date', inplace=True)
# 계절성 변동의 주기 값 설정
period = 50
# 시계열데이터의 계절성 분해 방법 적용
result = seasonal_decompose(apple['close'], model='addictive', period=period)
# 결과 시각화
fig = result.plot()
fig.set_size_inches((10, 6))
plt.show()
- 이동평균과 자기회귀 모델링
- pandas 라이브러리의 rolling 함수를 이용하여 이동평균을 계산
# 원본 데이터셋을 복사해 이동평균값을 추가로 저장할 데이터프레임을 생성
apple_with_MA = apple.copy()
# APPLE 주식 가격의 5일, 10일 이동평균을 계산해 별도 column으로 저장
apple_with_MA['MA5'] = apple['close'].rolling(5).mean()
apple_with_MA['MA10'] = apple['close'].rolling(10).mean()
# 처음 100개 값만 선택해 결과를 시각화.
ax = sns.lineplot(data=apple_with_MA[['close', 'MA5', 'MA10']][:100], dashes=False, markersize=5, marker='o')
ax.set_title("APPLE's closing price and moving average")
plt.show()
- statsmodels 라이브러리의 AutoReg 함수를 이용해 자기회귀모델을 학습
# 자기 회귀 모델을 fitting
model = AutoReg(apple['close'], lags=5)
result = model.fit()
# 결과를 출력
print(result.summary())
filterwarnings(action='ignore') # python warnings를 무시하는 옵션
def plot_prediction(data, pred_start, pred_end, result):
# 관측된 데이터의 샘플 수
n_samples = len(data.index)
# 예측할 날짜들 정의
pred_dates = pd.date_range(start=pred_start, end=pred_end)
pred_index = list(pred_dates)
# 기존 관측된 날짜와 예측할 날짜에 대해 예측하기
prediction = result.get_prediction(end=n_samples + len(pred_dates))
# 예측값의 평균과 신뢰구간 계산
pred_mean = prediction.predicted_mean[n_samples + 1:]
pred_lower = prediction.conf_int()['lower'][n_samples + 1:]
pred_upper = prediction.conf_int()['upper'][n_samples + 1:]
# 시각화
fig, ax = plt.subplots(figsize=(10,6))
# 기존 관측된 시계열 데이터
sns.lineplot(data=data)
# 예측값과 신뢰구간 시각화
ax.plot(pred_index, pred_mean, label='prediction', zorder=2)
ax.fill_between(pred_index, pred_lower, pred_upper, color="gray", alpha=0.5, label='95% confidence interval', zorder=1)
# 그림 설정
ax.legend(loc='upper right')
plt.title(f'Prediction from {pred_start} to {pred_end}')
plt.show()
'Study > 머신러닝' 카테고리의 다른 글
머신러닝 Advanced_ 전처리 기법 (7) | 2024.11.05 |
---|---|
머신러닝 BASIC _ 모델 평가와 개선 (3) | 2024.11.04 |
머신러닝 BASIC _ 비지도학습 방법론 (2) | 2024.10.31 |
머신러닝 BASIC _ 지도학습 방법론 (4) | 2024.10.30 |
머신러닝 BASIC _ 모델과 데이터 (2) | 2024.10.30 |