728x90
  • 실습한 내용은 데이콘 경진대회 1등 솔루션 책 내용입니다.
  • 제 1장인 KBO 타자 OPS 예측 실습 내용을 포스팅 하겠습니다.
  • 전체적인 진행 설명은 파일안에 기록했습니다

KBO 타자 OPS 예측.ipynb
1.10MB

2021/02/10 - [기록 note] - 2021-02-10 기록(데이콘_KBO 실습)

2021/02/11 - [기록 note] - 2021-02-11 기록(데이콘_KBO 실습2)

2021/02/12 - [기록 note] - 2021-02-12 기록(데이콘_KBO 실습3)

2021/02/13 - [기록 note] - 2021-02-13 기록(데이콘_KBO 실습4)

 

KBO 타자 OPS 예측

#import 
from matplotlib import font_manager, rc
import matplotlib 
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
import platform

#window 폰트 설정
font_name=font_manager.FontProperties(fname='c:/Windows/Fonts/malgun.ttf').get_name()
rc('font', family=font_name)

#그래프의 마이너스 표시가능 설정
matplotlib.rcParams['axes.unicode_minus']=False

 

1.EDA(탐색적 데이터 분석)

 

프리시즌 살펴보기

#프리시즌 데이터로드
preseason_df=pd.read_csv('D:/dacon/KBO 타자 OPS 예측/Pre_Season_Batter.csv')
#정규시즌 데이터로드
regular_season_df=pd.read_csv('D:/dacon/KBO 타자 OPS 예측/Regular_Season_Batter.csv')
#데이터크기 확인
print(preseason_df.shape)
#데이터 상단 출력 
display(preseason_df.head())

#데이터 기초통계량 확인
display(preseason_df.describe())

#데이터 시각화
preseason_df.hist(figsize=(10,9))
plt.tight_layout() # 그래프 간격 설정
plt.show()

#정규시즌 데이터에서 2002년 이후의 연도별기록된 선수의 수
regular_count=regular_season_df.groupby('year')['batter_name'].count().rename('regular')

#프리시즌 데이터에서 2002년 이후의 연도별기록된 선수의 수
preseason_count=preseason_df.groupby('year')['batter_name'].count().rename('preseason')

#합치기
pd.concat([regular_count,preseason_count, np.round(preseason_count/regular_count,2).rename('ratio')],axis=1).transpose().loc[:,2002:]

 

#타자의 이름과 연도를 이용해 새로운 인덱스를 생성
regular_season_df['new_idx']=regular_season_df['batter_name']+regular_season_df['year'].apply(str)
preseason_df['new_idx']=preseason_df['batter_name']+preseason_df['year'].apply(str)

#새로운 인덱스의 교집합
intersection_idx=list(set(regular_season_df['new_idx']).intersection(preseason_df['new_idx']))
#ket_point: intersaction을 활용한 교집합 

#교집합에 존재하는 데이터만 불러오기
regular_season_new=regular_season_df.loc[regular_season_df['new_idx'].apply(lambda x:x in intersection_idx)]
regular_season_new=regular_season_new.sort_values(by='new_idx').reset_index(drop=True)

#비교를 위한 인덱스 정렬
preseason_new=preseason_df.loc[preseason_df['new_idx'].apply(lambda x:x in intersection_idx)]
preseason_new=preseason_new.sort_values(by='new_idx').reset_index(drop=True)

#검정코드
print(preseason_new.shape, preseason_new.shape)
sum(preseason_new['new_idx']==regular_season_new['new_idx'])

(1358, 30) (1358, 30)
1358
  • intersaction을 활용한 교집합+set을 이용한 중복제거(두시즌 모두 참여한 선수 추출)
  • apply와 lambda를 이용한 적용방법
#정규시즌과 프리시즌의 상관관계 계산
correlation=regular_season_new["OPS"].corr(preseason_new["OPS"])
sns.scatterplot(regular_season_new["OPS"], preseason_new["OPS"])
plt.title('correlation(상관계수):'+str(np.round(correlation,2)), fontsize=20)
plt.xlabel('정규시즌 OPS:', fontsize=12)
plt.ylabel('프리시즌 OPS:', fontsize=12)
plt.show()

 

정규시즌 데이터 분석

#기초통계량 확인
regular_season_df.describe()

#시각화 작업
regular_season_df.hist(figsize=(10,9))
plt.tight_layout()
plt.show()

 

plt.figure(figsize=(15,6))
plt.subplot(1,2,1) #1행 2열의 첫번쨰 (1행 1열) 그래프
g= sns.boxplot(x='year', y='OPS', data=regular_season_df, showfliers=False)
g.set_title('연도별 OPS 상자그림', size=20)
g.set_xticklabels(g.get_xticklabels(), rotation=90)
plt.subplot(1,2,2)
plt.plot(regular_season_df.groupby('year')['OPS'].median()) #OPS 값이 한쪽으로 치우쳐 있으므로 중앙값으로 평균을 낸다
plt.title('연도별 OPS 중앙값:', size=20)
plt.show()

 

#2000년도 이전의 변동폭이 크기 때문에 좀더 자세히 살펴본다
pd.crosstab(regular_season_df['year'], 'count').T
#2000년 이전의 데이터 수가 작아서 변동성이 컸음을 알수있음

 

#팀별/연도별 OPS 

#연도별 팀의 OPS 중앙값 계산
med_OPS=regular_season_df.pivot_table(index=['team'], columns='year', values="OPS", aggfunc="median")

#2005년 이후에 결측치가 존재 하지 않은 팀만 확인
team_idx=med_OPS.loc[:,2005:].isna().sum(axis=1)<=0  

plt.plot(med_OPS.loc[team_idx,2005:].T)
plt.legend(med_OPS.loc[team_idx,2005:].T.columns, loc='center left', bbox_to_anchor=(1,0.5)) #그래프 범례를 그래프 밖에 위치
plt.title('팀별 성적')
plt.show()

  • team_idx=med_OPS.loc[:,2005:].isna().sum(axis=1)<=0 결측치가 있는걸 제거 하는 코드

 

키와 몸무게가 성적과 관련이 있는지 확인

import re 
regular_season_df['weight']=regular_season_df['height/weight'].apply(lambda x: int(re.findall('\d+', x.split('/')[1])[0]) if pd.notnull(x) else x )

regular_season_df['height']=regular_season_df['height/weight'].apply(lambda x: int(re.findall('\d+', x.split('/')[0])[0]) if pd.notnull(x) else x )

print(regular_season_df['height/weight'][0], regular_season_df['weight'][0],regular_season_df['height'][0])

177cm/93kg 93.0 177.0

 

#몸무게/카 계산
regular_season_df['weight_per_height']=regular_season_df['weight']/ regular_season_df['height']
plt.figure(figsize=(15,5)) #그래프 조정
plt.subplot(1,2,1)

#비율과 출루율 상관관계
correlation= regular_season_df['weight_per_height'].corr(regular_season_df['OBP'])
sns.scatterplot(regular_season_df['weight_per_height'], regular_season_df['OBP'])
plt.title(" '몸무게/키'와 출루율 correlation(상관관계):"+str(np.round(correlation,2)),fontsize=15)
plt.ylabel('정규시즌 OBP', fontsize=12)
plt.xlabel('몸무게/키', fontsize=12)

#비율과 장타율 상관관계
plt.subplot(1,2,2)
correlation=regular_season_df['weight_per_height'].corr(regular_season_df['SLG'])
sns.scatterplot(regular_season_df['weight_per_height'],regular_season_df['SLG'])
plt.title(" '몸무게/키'dhk 장타율 correlation(상관관계)"+str(np.round(correlation,2)), fontsize=15)
plt.ylabel('정규시즌 SLG', fontsize=12)
plt.xlabel('몸무게/키', fontsize=12)
plt.show()

 

regular_season_df['position'].value_counts()

내야수(우투우타)    643
외야수(우투우타)    230
외야수(좌투좌타)    201
포수(우투우타)     189
외야수(우투좌타)    184
내야수(우투좌타)    141
내야수(좌투좌타)     36
포수(우투좌타)      14
내야수(우투양타)      7
외야수(우투양타)      7
Name: position, dtype: int64

 

# 포지션 세부적으로 분리하기
#postion
regular_season_df['pos']=regular_season_df['position'].apply(lambda x:x.split('(')[0] if pd.notnull(x) else x)

#우타,좌타,양타 : 손잡이
regular_season_df['hit_way']=regular_season_df['position'].apply(lambda x: x[-3:-1] if pd.notnull(x) else x)

print(regular_season_df['position'][0], regular_season_df['pos'][0], regular_season_df['hit_way'][0])

내야수(우투우타) 내야수 우타
  • 선수들의 포지션을 통해서 왼손잡이,오른손잡이를 알아내고 성적과 상관관계를 알아내는 추론과정
plt.figure(figsize=(15,5)) 
plt.subplot(1,2,1)
ax= sns.boxplot(x='pos', y='OPS', data=regular_season_df,
                showfliers=False) # 박스 범위 벗어난 아웃라이어 표시하지 않기

#position별 ops 중앙값
median= regular_season_df.groupby('pos')['OPS'].median().to_dict() #{'내야수': 0.706, '외야수': 0.7190000000000001, '포수': 0.639}

#position 별 관측치 수 -> 그래프에 넣을 값
nobs=regular_season_df['pos'].value_counts().to_dict()  #{'내야수': 827, '외야수': 622, '포수': 203}

#키 값을 'n:값' 형식으로 변환하는 코드
for key in nobs: nobs[key] = "n:"+str(nobs[key])  #for key의 keyr가 아닌 다른 텍스트를 넣으면 n:n:n:값 형식으로 형태가 이상해짐
    
#그래프의 Xticks text 값 얻기
xticks_labels = [item.get_text() for item in ax.get_xticklabels()]  #['내야수', '외야수', '포수']


#ax안에 텍스트 위치와 내용 넣기
for label in ax.get_xticklabels(): #x축 인자 즉, 내야수,외야수, 포수를 차례대로 label 넣는다
#     print(xticks_labels.index(label.get_text()))  # 0,1,2 차례대로
#     print(label.get_text())                       #내야수 외야수 포수
    
    ax.text(xticks_labels.index(label.get_text()), #x의 위치--> 숫자로 인덱스가 출력
           median[label.get_text()]+0.03,          #y의 위치 
            nobs[label.get_text()],                #들어갈 텍스트 내용
           horizontalalignment='center', size='large', color='w', weight='semibold')
    print(label.get_text())
ax.set_title('포지션별 OPS')

plt.subplot(1,2,2)
ax= sns.boxplot(x='hit_way', y='OPS', data=regular_season_df, showfliers=False)

#타자 방향별 OPS 중앙값
median=regular_season_df.groupby('hit_way')['OPS'].median().to_dict() 

#타자 방향 관측치 수
nobs = regular_season_df['hit_way'].value_counts().to_dict()

#키 값을 'n:값' 형식으로 변환
for key in nobs: nobs[key] = 'n:'+str(nobs[key])

#그래프의 xticks text 값 얻기
xticks_labels=[item.get_text() for item in ax.get_xticklabels()] #hit_way의 인덱스가 리스트 형식으로 묶인다

#tick은 tick의 위치, label은 그에 해당하는 text 값
for label in ax.get_xticklabels():
    ax.text(
    xticks_labels.index(label.get_text()),
    median[label.get_text()]+0.03,
    nobs[label.get_text()], horizontalalignment='center', size= 'large',
    color='w', weight='semibold')
ax.set_title('타석방향별 OPS')
plt.show()

  • to_dict과 그래프 적용하는 과정
  • xticks_labels.index(label.get_text(): x축 index 추출 과정

커리어 변수를 이용하여 외/내국인 차이를 탐색

#career를 split
foreign_country = regular_season_df['career'].apply(lambda x:x.replace('-','').split(' ')[0])

#외국인만 추출
foreign_country_list= list(set(foreign_country.apply(lambda x:np.nan if '초' in x else x))) #초가 있으면 nan으로 처리하고 그게 아니라면 x출력
                                                                                              #nan이 1개인 이유 : set함수  
#결측치 처리 
foreign_country_list = [x for x in foreign_country_list if str(x) != 'nan']
foreign_country_list

['쿠바', '도미니카삼성', '캐나다', '도미니카', '네덜란드', '미국']

 

regular_season_df['country']=foreign_country
regular_season_df['country']=regular_season_df['country'].apply(lambda x: x if pd.isnull(x) else ('foreign' if x in foreign_country_list else 'korean'))
regular_season_df[['country']].head()

plt.figure(figsize=(15,5))
ax= sns.boxplot(x='country', y='OPS', data=regular_season_df, showfliers=False)

#국적별 OPS 중앙값 dict
median= regular_season_df.groupby(['country'])['OPS'].median().to_dict()

#내외국인 관측치 수
nobs = regular_season_df['country'].value_counts().to_dict()

#키 값을 n:값 형태로 변경
for key in nobs : nobs[key] = 'n:'+str(nobs[key]) #['foreign', 'korean']

#그래프의 Xticks text 값 얻기
xticks_labels=[item.get_text() for item in ax.get_xticklabels()]
for label in ax.get_xticklabels():
    ax.text(
    xticks_labels.index(label.get_text()),
        median[label.get_text()]+0.03,
        nobs[label.get_text()],
        horizontalalignment='center', size='large', color='w', weight='semibold')
ax.set_title('국적별 OPS')
plt.show()

#결측치라면 그대로 0으로 두고, 만원이 포함된다면 숫자만 뽑아서 초봉으로 넣어준다.
#그외 만원 단위가 아닌 초봉은 결측치로 처리한다.
regular_season_df['starting_salary']=regular_season_df['starting_salary'].apply(lambda x:x if pd.isnull(x) else(int(re.findall('\d+',x)[0]) if '만원' in x else np.nan))

plt.figure(figsize=(15,5))
plt.subplot(1,2,1)
b=sns.distplot(regular_season_df['starting_salary'].\
              loc[regular_season_df['starting_salary'].notnull()], hist=True)
b.set_xlabel('staring salary', fontsize=12)
b.set_title('초봉의 분포', fontsize=20)

plt.subplot(1,2,2)
correlation=regular_season_df['starting_salary'].corr(regular_season_df['OPS'])
b=sns.scatterplot(x=regular_season_df['starting_salary'], y=regular_season_df['OPS'])
b.axes.set_title('correlation(상관계수):'+str(np.round(correlation,2)), fontsize=20)
b.set_ylabel('정규시즌 OPS:',fontsize=12)
b.set_xlabel("초봉",fontsize=12)
plt.show()

 

 

일별 데이터 분석

day_by_day_df=pd.read_csv('D:/dacon/KBO 타자 OPS 예측/Regular_Season_Batter_Day_by_Day_b4.csv')
display(day_by_day_df.shape, day_by_day_df.head())

(112273, 20)

 

#날짜(date)를 '.'을 기준으로 나누고 첫 번째 값을 월(month)로 지정
day_by_day_df['month']=day_by_day_df['date'].apply(lambda x:str(x).split('.')[0]) #숫자는 split 안됨, str로 변경 후 사용

#각 연도의 월별 평균 누적 타율(avg2) 계산
agg_df= day_by_day_df.groupby(['year','month']).mean().reset_index()
agg_df

#피벗 데이털로 재구성하기
agg_df=day_by_day_df.pivot_table(index='month', columns='year', values='avg2')
agg_df

  • date변수를 월을 추출해서 월별 평균 타율을 추출과정
#그래프의 간소화를 위해 결측치가 있는 3월과 10월제외한다.
display(agg_df.iloc[2:,10:])
plt.plot(agg_df.iloc[2:,10:]) #2011~2018년도
plt.legend(agg_df.iloc[2:,10:].columns, loc='center left', bbox_to_anchor=(1,0.5)) #범례 그래프 밖에 위치
plt.title('연도별 평균 타율')
plt.show()

 

데이터 전처리

결측치 처리 및 데이터 오류 처리

# 수치형 타입의 변수 저장
numberics =['int16','int32','int64','float16','float32','float64']
num_cols=regular_season_df.select_dtypes(include=numberics).columns #columns을 붙여야 열 이름만 추출
  • 수치형 타입을 이용해서 해당되는 데이터를 찾아내는 과정
  • select_dtypes
regular_season_df.loc[regular_season_df[num_cols].isna().sum(axis=1)>0 , num_cols].head() 
#ex 0번 인덱스에 하나라도 결측치가 있으면 num_cols와 매칭하여 보여준다.
#0번 인덱스에 결측치가 있을때 그 결측치가 num_cols중에 발생한 것인지 확인하기 위해서 필요한 코드

  • 결측치 여부를 부등호로 처리
#수치형 변수에 포함되는 데이터 타입 선정
numberics =['int16','int32','int64','float16','float32','float64']

#정규시즌 데이터에서 결측치를 0으로 채우기
regular_season_df[regular_season_df.select_dtypes(include=numberics).columns]=regular_season_df[regular_season_df.select_dtypes(include=numberics).columns].fillna(0)

#일별 데이터에서 결측치를 0으로 채우기
day_by_day_df[day_by_day_df.select_dtypes(include=numberics).columns]=day_by_day_df[day_by_day_df.select_dtypes(include=numberics).columns].fillna(0)

#프리시즌 데이터에서 결측치를 0으로 채우기
preseason_df[preseason_df.select_dtypes(include=numberics).columns]=preseason_df[preseason_df.select_dtypes(include=numberics).columns].fillna(0)

 

#수치형 변수의 결측치를 다루기 전에 먼저 결측치의 현황을 파악 후 결측치 처리 방법을 정해야 한다
not_num_cols=[x for x in regular_season_df.columns if x not in num_cols ]

#수치형이 아닌 변수 중 결측치가 하나라도 존재하는 행 출력
regular_season_df.loc[regular_season_df[not_num_cols].isna().sum(axis=1)>0, not_num_cols].head()
#결측치 해당 변수는 분석에 사용안하므로 결측치 처리 안함

#잘못된 결측치 데이터를 삭제
#삭제할 데이터 추출 
drop_index= regular_season_df.loc[
    #안타가 0개 이상이면서 장타율이 0인 경우
    ((regular_season_df['H']>0) & (regular_season_df['SLG']>0))|
    
    #안타가 0개 이상 혹은 볼넷이 0개 이상 혹은 몸에 맞은 볼이 0개 이상이면서 출루율이 0인 경우
    (((regular_season_df['H']>0)|
     (regular_season_df['BB']>0)|
     (regular_season_df['HBP']>0))&
    (regular_season_df['OBP']==0))
].index

#데이터 삭제
regular_season_df=regular_season_df.drop(drop_index).reset_index(drop=True)

규정타수정의

#정규시즌 데이터로드
regular_season_df=pd.read_csv('D:/dacon/KBO 타자 OPS 예측/Regular_Season_Batter.csv')

plt.figure(figsize=(6,3))
plt.plot('AB','OPS', data=regular_season_df, linestyle='none', marker='o', markersize=2, color='blue', alpha=0.4)
plt.xlabel('AB', fontsize=14)
plt.ylabel('OPS', fontsize=14)
plt.xticks(list(range(min(regular_season_df['AB']),max(regular_season_df['AB']),30)),rotation=90)
plt.vlines(30,ymin=min(regular_season_df['OPS']), ymax=max(regular_season_df['OPS']),linestyle='dashed',colors='r')
plt.show()

 

#OPS 이상치 탐색을 위한 수치 정의 
Q1= regular_season_df['OPS'].quantile(0.25) 
Q3= regular_season_df['OPS'].quantile(0.75)
IQR=Q3-Q1

#실제 OPS 이상치 탐색
regular_season_df.loc[(regular_season_df['OPS']<(Q1-1.5*IQR))|
                     (regular_season_df['OPS']>(Q3+1.5*IQR))].sort_values(by=['AB'], axis=0, ascending=False)[['batter_name','AB','year','OPS']].head(10)

  • IQR를 통해 규장타수의 타당성을 확인
major_ticks= list(np.round(np.linspace(7.01,7.31,31),2))

july=(day_by_day_df['date']>=7) & (day_by_day_df['date']<8) #7월만 불러오는 index
plt.plot(major_ticks, day_by_day_df['date'].loc[july].value_counts().sort_index(), marker='o')
plt.xticks(major_ticks, rotation=90)
plt.show()
  • 경기에 출전한 선수의 합을 통해서 휴식기를 알아낸다
  • 이를 통해 상반기 하반기를 구별

시간변수

  • 선수별 과거 성적을 생성하는 함수 정의
#시간변수를 생성하느 함수 정의
def lag_function(df,var_name, past):
    # df = 시간변수를 생성할 데이터 프레임
    # var_name= 시간변수 생성의 대상이 되는 변수 이름
    # past= 몇 년 전의 성적을 생성할지 결정(정수형)
    
    df.reset_index(drop=True, inplace=True)
    #시간변수 생성
    df['lag'+str(past)+'_'+var_name] = np.nan #결측치로 채워 넣어 놓는다
    df['lag'+str(past)+'_'+'AB'] = np.nan
    
    for col in ['AB',var_name]:
        for i in range(0, (max(df.index)+1)):
            val=df.loc[(df['batter_name']==df['batter_name'][i])& #이름이 가르시아 이면서
                       (df['year']==df['year'][i]-past),col]   #년도는 i년도
            #과거 기록이 결측치가 아니라면 값을 넣기
            if len(val)!=0:
                df.loc[i,'lag'+str(past)+'_'+col]=val.iloc[0] #i번째 행에 삽입
    
    #30타수 미만 결측치 처리
    df.loc[df['lag'+str(past)+'_'+'AB']<30,
          'lag'+str(past)+'_'+var_name]=np.nan   #var_name 행의 존재하는 30미만은 제거하고
    df.drop('lag'+str(past)+'_'+'AB', axis=1, inplace=True)  #AB열을 제거 하여 var_name만 남김
    return df
  • 과거 시간을 for문으로 채우기 전에 결측치로 채워 넣은 점-> 과거 기록이 없으면 결측치로 채우기 위함
# 상관관계를 탐색할 변수 선택
numberics =['int16','int32','int64','float16','float32','float64']
numberics_cols=list(regular_season_df.select_dtypes(include=numberics).drop(['batter_id','year','OPS','SLG'], axis=1).columns)
regular_season_temp=regular_season_df[numberics_cols+['year','batter_name']].copy()
regular_season_temp= regular_season_temp.loc[regular_season_temp['AB']>=30]

# #시간변수 생성 함수를 통한 지표별 1년 전 성적 추출
for col in numberics_cols:
    regular_season_temp=lag_function(regular_season_temp,col ,1)
    
numberics_cols.remove('OBP')
regular_season_temp.drop(numberics_cols, axis=1, inplace=True)

#상관관계 도출
corr_matrix= regular_season_temp.corr()
corr_matrix= corr_matrix.sort_values(by='OBP', axis=0, ascending=False)
corr_matrix= corr_matrix[corr_matrix.index]

#상관관계의 시각적 표현
f, ax = plt.subplots(figsize=(12,12))
corr= regular_season_temp.select_dtypes(exclude=['object','bool']).corr()

#대각 행렬을 기준으로 한쪽만 설정
mask= np.zeros_like(corr_matrix, dtype=np.bool)
mask[np.triu_indices_from(mask)]=True

g= sns.heatmap(corr_matrix, cmap='RdYlGn_r', vmax=1, mask=mask, center=0, annot=True, fmt='.2f', square=True, linewidths=.5, cbar_kws={'shrink':.5})
plt.title('Diagonal Correlation Heatmap')

#희생 플라이 구하기

#OBP(출루율) 계산 공식 이용하여 SF(희생 플라이) 계산 > (H+BB+HBP)/OBP-(AB+BB+HBP)
regular_season_df['SF']= regular_season_df[['H','BB','HBP']].sum(axis=1)/ regular_season_df['OBP']-\
regular_season_df[['AB','BB','HBP']].sum(axis=1)

regular_season_df['SF'].fillna(0, inplace=True) #결측치 채우기
regular_season_df['SF']=regular_season_df['SF'].apply(lambda x: round(x,0)) 

#한 타수당 평균 희생 플라이 계산 후 필요한 것만 추출
#regular_season_df는 각 연도별 전체 데이터이다. 
#이를통해서 한 타수당 평균 플라이 계산 후 일일 데이터에서 상반기데이터만 취하여 희생플라이 계산 ->  선수 별 상반기 출루율 계산
regular_season_df['SF_1']=regular_season_df['SF']/regular_season_df['AB']
regular_season_df_SF=regular_season_df[['batter_name','year','SF_1']]
regular_season_df_SF

#day_by_day_df에서 연도별 선수의 시즌 상반기 출루율과 관련된 성적 합 구하기
sum_hf_yr_OBP=day_by_day_df.loc[day_by_day_df['date']<=7.18].groupby(['batter_name','year'])['AB','H','BB','HBP'].sum().reset_index()

#day_by_day_df와 regular_season에서 구한 희생 플라이 관련 데이터 합치기
sum_hf_yr_OBP=sum_hf_yr_OBP.merge(regular_season_df_SF, how='left', on=['batter_name','year'])

#선수별 상반기 희생 플라이 수 계산
sum_hf_yr_OBP['SF']=(sum_hf_yr_OBP['SF_1']*sum_hf_yr_OBP['AB']).apply(lambda x:round(x,0))
sum_hf_yr_OBP.drop('SF_1', axis=1, inplace=True) #SF_1 삭제

#선수별 상반기 OBP(출루율)계산
sum_hf_yr_OBP['OBP']=sum_hf_yr_OBP[['H','BB','HBP']].sum(axis=1)/ sum_hf_yr_OBP[['AB','BB','HBP','SF']].sum(axis=1)

#OBP 결측치를 0으로 처리
sum_hf_yr_OBP['OBP'].fillna(0,inplace=True)

#분석에 필요하지 않은 열 제거
sum_hf_yr_OBP = sum_hf_yr_OBP[['batter_name','year','AB','OBP']]
sum_hf_yr_OBP

 

추가 변수 생성

#나이 변수 생성
regular_season_df['age']=regular_season_df['year']-regular_season_df['year_born'].apply(lambda x:int(x[:4]))

#나이,평균 출루율,출루율 중앙값으로 구성된 데이터프레임 구축
temp_df=regular_season_df.loc[regular_season_df['AB']>=30].groupby('age').agg({'OBP':['mean','median']}).reset_index()
temp_df.columns= temp_df.columns.droplevel()
temp_df.columns=['age','mean_OBP','median_OBP']

#나이에 따른 출루율 시각화
plt.figure(figsize=(12,8))
plt.plot('age','mean_OBP', data=temp_df, marker='o', markerfacecolor='red', markersize=12, color='skyblue', linewidth=4)
plt.ylabel('평균OBP')
plt.xlabel('나이')
plt.show()

#나이를 포함한 변수 선택
sum_hf_yr_OBP=sum_hf_yr_OBP.merge(regular_season_df[['batter_name','year','age']],
                                 how='left',on=['batter_name','year'])

#총 3년 전 성적까지 변수를 생성
sum_hf_yr_OBP= lag_function(sum_hf_yr_OBP,'OBP',1)
sum_hf_yr_OBP= lag_function(sum_hf_yr_OBP,'OBP',2)
sum_hf_yr_OBP= lag_function(sum_hf_yr_OBP,'OBP',3)
sum_hf_yr_OBP

 

데이터 사후 처리

round(sum_hf_yr_OBP[['lag1_OBP','lag2_OBP','lag3_OBP']].isna().sum()/ sum_hf_yr_OBP.shape[0],2)

#1. 선수별 OBP 평균
#SF = (H+BB+HBP)/OBP-(AB+BB+HBP)
#OBP = (H+BB+HBP) / (AB+BB+HBP+SF)
player_OBP_mean= regular_season_df.loc[regular_season_df['AB']>=30].groupby('batter_name')['AB','H','BB','HBP','SF'].sum().reset_index()
player_OBP_mean['mean_OBP']=player_OBP_mean[['H','BB','HBP']].sum(axis=1)/player_OBP_mean[['AB','BB','HBP','SF']].sum(axis=1)

#2. 시즌별 OBP평균 
season_OBP_mean=regular_season_df.loc[regular_season_df['AB']>=30].groupby('year')['AB','H','BB','HBP','SF'].sum().reset_index()
season_OBP_mean['mean_OBP']=season_OBP_mean[['H','BB','HBP']].sum(axis=1)/season_OBP_mean[['AB','BB','HBP','SF']].sum(axis=1)
season_OBP_mean=season_OBP_mean[['year','mean_OBP']]

##player_OBP_mean(선수별 평균) 열 추가
sum_hf_yr_OBP=sum_hf_yr_OBP.merge(player_OBP_mean[['batter_name','mean_OBP']], how='left', on='batter_name')

#선수평균의 성적이 결측치이면 데이터에서 제거
sum_hf_yr_OBP=sum_hf_yr_OBP.loc[~sum_hf_yr_OBP['mean_OBP'].isna()].reset_index(drop=True)  #~하면 False 가 True로 반전됨, 평균값이 없는 선수는 제외 시키기위함?
sum_hf_yr_OBP

#결측치 처리하는 함수 정의
def lag_na_fill(data_set,var_name,past,season_var_mean_data):
    #data_set: 이용할 데이터 셋
    #var_name: 시간변수르 만들 변수 이름
    #season_var_mean_data: season별로 var_name의 평균을 구한 데이터
    
    for i in range(0, len(data_set)):
        if np.isnan(data_set['lag'+str(past)+'_'+var_name][i]): # 결측치가 존재하면 True를 반환
                                                                 #선수별 var_name 평균    +     #시즌별 var_name평균     
            data_set.loc[i,'lag'+str(past)+'_'+var_name]=(data_set.loc[i,'mean_'+var_name]+season_var_mean_data.loc[season_var_mean_data['year']==\
                                                           (data_set['year'][i]-past),'mean_'+var_name].iloc[0])/2
        
    return data_set
  • 결측치를 (선수별 평균+시즌별 평균)/2로 대체한다는 인사이트
  • np.isnan
season_OBP_mean.loc[season_OBP_mean['year']==1993,'mean_'+'OBP']

0    0.333333
Name: mean_OBP, dtype: float64

 

season_OBP_mean.head(2)

#생성한 함수를 이용해 결측치 처리
sum_hf_yr_OBP=lag_na_fill(sum_hf_yr_OBP,'OBP',1,season_OBP_mean) #1년 전 성적 대체

sum_hf_yr_OBP=lag_na_fill(sum_hf_yr_OBP,'OBP',2,season_OBP_mean) #2년 전 성적 대체

sum_hf_yr_OBP=lag_na_fill(sum_hf_yr_OBP,'OBP',3,season_OBP_mean) #3년 전 성적 대체
sum_hf_yr_OBP

  • 과거 성적데이터를 쓴다는 인사이트와 구현하는 코드
  • 함수를 정의하는 과정 방법 

SLG 데이터 처리

#상관관계를 탐색할 변수 선택

numberics_cols=list(regular_season_df.select_dtypes(include=numberics).drop(['batter_id','year','OPS','OBP'], axis=1).columns)
regular_season_temp = regular_season_df[numberics_cols+['year','batter_name']].copy()
regular_season_temp=regular_season_temp.loc[regular_season_temp['AB']>=30]

#시간변수 생성 함수를 통한 지표별 1년 전 성적추출
for col in numberics_cols:
    regular_season_temp=lag_function(regular_season_temp,col,1)
numberics_cols.remove('SLG') #SLG를 상관관계표에서 비교해야 하므로 미리 삭제목록에서 제외시킨다
regular_season_temp.drop(numberics_cols, axis=1,inplace=True)

#상관관계 도출
corr_matrix=regular_season_temp.corr()
corr_matrix=corr_matrix.sort_values(by='SLG', axis=0, ascending=False)
corr_matrix=corr_matrix[corr_matrix.index]

#상관관계 시각적 표현
f,ax = plt.subplots(figsize=(12,12)) #fig 사이즈, ax : axes 생성된 그래프 낱낱개
corr=regular_season_temp.select_dtypes(exclude=['object','bool']).corr()

#대각 행렬을 기준으로 한쪽만 설정
mask= np.zeros_like(corr_matrix, dtype=np.bool)
mask[np.triu_indices_from(mask)]=True

g= sns.heatmap(corr_matrix, cmap='RdYlGn_r', vmax=1, mask=mask, center=0, annot=True, fmt='.2f', square=True, linewidths=.5, cbar_kws={'shrink':.5})
plt.title('Diagonal Correlation Heatmap')

  • SLG 예측에 필요한 변수를 파악하기 위한 상관성
#day_by_day_df에서 연도별 선수의 시즌 상반기 장타율과 관련된 성적 합 구하기
sum_hf_yr_SLG=day_by_day_df.loc[day_by_day_df['date']<=7.18].groupby(['batter_name','year'])['AB','H','2B','3B','HR'].sum().reset_index()

#상반기 장타율 계산                                                 #sum(axis=1) :행 별로 더해진다#
sum_hf_yr_SLG['SLG']=(sum_hf_yr_SLG['H']-sum_hf_yr_SLG[['2B','3B','HR']].sum(axis=1)+sum_hf_yr_SLG['2B']*2+sum_hf_yr_SLG['3B']*3+\
                      sum_hf_yr_SLG['HR']*4)/sum_hf_yr_SLG['AB']

#SLG결측치를 0으로 처리
sum_hf_yr_SLG['SLG'].fillna(0, inplace=True)

#필요한 칼럼만 불러오고 나이계산
sum_hf_yr_SLG=sum_hf_yr_SLG[['batter_name','year','AB','SLG']]
sum_hf_yr_SLG=sum_hf_yr_SLG.merge(regular_season_df[['age','batter_name','year']], how='left', on=['batter_name','year'] )
sum_hf_yr_SLG.head()

# 총 3년 전 성적까지 변수를 생성
sum_hf_yr_SLG=lag_function(sum_hf_yr_SLG,'SLG',1)
sum_hf_yr_SLG=lag_function(sum_hf_yr_SLG,'SLG',2)
sum_hf_yr_SLG=lag_function(sum_hf_yr_SLG,'SLG',3)

display(sum_hf_yr_SLG.head())

#전체 데이터에서 결측치가 차지하는 비율보기
round(sum_hf_yr_SLG[['lag1_SLG','lag2_SLG','lag3_SLG']].isna().sum()/sum_hf_yr_SLG.shape[0],2)

#결측치를 시즌성적,선수의 평균 성적을 이용해 결측치 처리

#선수별 SLG평균 데이터(player_SLG_mean) 생성
player_SLG_mean= regular_season_df.loc[regular_season_df['AB']>=30].groupby('batter_name')['AB','H','2B','3B','HR'].sum().reset_index()

player_SLG_mean['mean_SLG']= (player_SLG_mean['H']-player_SLG_mean[['2B','3B','HR']].sum(axis=1)+player_SLG_mean['2B']*2+player_SLG_mean['3B']*3+\
                      player_SLG_mean['HR']*4)/player_SLG_mean['AB']

#시즌별 SLG 평균 데이터(season_SLG_mean) 생성
season_SLG_mean=regular_season_df.loc[regular_season_df['AB']>=30].groupby('year')['AB','H','2B','3B','HR'].sum().reset_index()
season_SLG_mean['mean_SLG']=(season_SLG_mean['H']-season_SLG_mean[['2B','3B','HR']].sum(axis=1)+season_SLG_mean['2B']*2+season_SLG_mean['3B']*3+\
                      season_SLG_mean['HR']*4)/season_SLG_mean['AB']

#선수 평균의 SLG(player_SLG_mean)를 새로운 변수에 더한다
sum_hf_yr_SLG=sum_hf_yr_SLG.merge(player_SLG_mean[['batter_name','mean_SLG']], how='left', on='batter_name')

#선수 평균의 성적이 결측치이면 데이터에서 제거
sum_hf_yr_SLG=sum_hf_yr_SLG.loc[~sum_hf_yr_SLG['mean_SLG'].isna()].reset_index(drop=True) #mean_SLG가 있는 것만 추출해서 인덱스 정리
sum_hf_yr_SLG

 

#결측치 처리
sum_hf_yr_SLG=lag_na_fill(sum_hf_yr_SLG,'SLG',1, season_SLG_mean) #1년전 성적 대체
sum_hf_yr_SLG=lag_na_fill(sum_hf_yr_SLG,'SLG',2, season_SLG_mean) #2년전 성적 대체
sum_hf_yr_SLG=lag_na_fill(sum_hf_yr_SLG,'SLG',3, season_SLG_mean) #3년전 성적 대체

display(sum_hf_yr_SLG.head())
round(sum_hf_yr_SLG[['lag1_SLG','lag2_SLG','lag3_SLG']].isna().sum()/sum_hf_yr_SLG.shape[0],2)

 

모델구축과 검증

lasso,RIdge

#30태수 이상의 데이터만 학습
sum_hf_yr_OBP=sum_hf_yr_OBP.loc[sum_hf_yr_OBP['AB']>=30]
sum_hf_yr_SLG=sum_hf_yr_SLG.loc[sum_hf_yr_SLG['AB']>=30]

#2018년 데이터를 test 데이터로, 2018 이전은 train 데이터로 나눈다
OBP_train= sum_hf_yr_OBP.loc[sum_hf_yr_OBP['year']!=2018]
OBP_test= sum_hf_yr_OBP.loc[sum_hf_yr_OBP['year']==2018]

SLG_train= sum_hf_yr_SLG.loc[sum_hf_yr_SLG['year']!=2018]
SLG_test= sum_hf_yr_SLG.loc[sum_hf_yr_SLG['year']==2018]
print(OBP_train.shape,OBP_test.shape,SLG_train.shape,SLG_test.shape)

(872, 9) (150, 9) (872, 9) (150, 9)

 

#평가지표 정의
def wrmse(v,w,p):
    #v : 실제값
    #w : 타수 
    #p : 예측값
    return sum(np.sqrt(((v-p)**2*w)/sum(w)))
  • 이 대회에서만 쓰인 평가지표
#랏지와 라소 선형모델
from sklearn.linear_model import Ridge,Lasso
from sklearn.model_selection import GridSearchCV

#log 단위(1e+01)로 1.e-04 ~1.e+01 사이의 구간에 대해 parameter를 탐색한다
lasso_params={'alpha':np.logspace(-4,1,6)} #array([1.e-04, 1.e-03, 1.e-02, 1.e-01, 1.e+00, 1.e+01])
ridge_params={'alpha':np.logspace(-4,1,6)}

#GridSeachCV를 이용하여 dict에 lasso,Ridge OBP 모델을 저장한다.
OBP_linear_models={
    'Lasso':GridSearchCV(Lasso(), param_grid=lasso_params).fit(OBP_train.iloc[:,-5:],OBP_train['OBP']).best_estimator_,
    'Ridge':GridSearchCV(Ridge(), param_grid=lasso_params).fit(OBP_train.iloc[:,-5:],OBP_train['OBP']).best_estimator_,
}

#GridSeachCV를 이용하여 dict에 lasso,Rigde SLG 모델을 저장한다.
SLG_linear_models={
    'Lasso':GridSearchCV(Lasso(), param_grid=lasso_params).fit(SLG_train.iloc[:,-5:],SLG_train['SLG']).best_estimator_,
    'Ridge':GridSearchCV(Ridge(), param_grid=lasso_params).fit(SLG_train.iloc[:,-5:],SLG_train['SLG']).best_estimator_,
}

 

Randomforest

import time
from sklearn.ensemble import RandomForestRegressor
start=time.time() #시작시간

#랜덤 포레스트의 파라미터 범위 정의 
RF_params = {
    "n_estimators":[50,100,150,200,300,500,100],
    "max_features":['auto','sqrt'],
    "max_depth":[1,2,3,4,5,6,10],
    "min_samples_leaf":[1,2,4],
    "min_samples_split":[2,3,5,10]}

#GridsearchCV를 이용하여 dict에 OBP RF 모델을 저장
OBP_RF_models={
    "RF":GridSearchCV(
    RandomForestRegressor(random_state=42), param_grid=RF_params, n_jobs=-1).fit(OBP_train.iloc[:,-5:],OBP_train['OBP']).best_estimator_}

#GridsearchCV를 이용하여 dict에 SLG RF 모델을 저장
SLG_RF_models={
    "RF":GridSearchCV(
    RandomForestRegressor(random_state=42), param_grid=RF_params, n_jobs=-1).fit(SLG_train.iloc[:,-5:],SLG_train['SLG']).best_estimator_}

print(f"걸린시간 : {np.round(time.time() -start,3)}초") #현재시간-시작시간(단위 초)

 

XGBoost

import xgboost as xgb
# from xgboost import XGBRegressor
start=time.time()

#xgboost parameter space를 정의
XGB_params={
    'min_child_weight':[1,3,5,10],
    'gamma':[0.3,0.5,1,1.5,2,5],
    'subsample':[0.6,0.8,1.0],
    'colsample_bytree':[0.6,0.8,1.0],
    'max_depth':[3,4,5,7,10]}

#GridSearchCV를 통해 파라미터를 탐색 정의한다
XGB_OBP_gridsearch= GridSearchCV(xgb.XGBRegressor(random_state=42),
                                param_grid=XGB_params, n_jobs=-1)

XGB_SLG_gridsearch= GridSearchCV(xgb.XGBRegressor(random_state=42),
                                param_grid=XGB_params, n_jobs=-1)

#모델 학습
XGB_OBP_gridsearch.fit(OBP_train.iloc[:,-5:],OBP_train['OBP'])
XGB_SLG_gridsearch.fit(SLG_train.iloc[:,-5:],SLG_train['SLG'])

print(f"걸린시간 : {np.round(time.time() -start,3)}초")

 

알고리즘별 성능비교

#테스트 데이터셋(2018년)의 선수들의 OBP예측
Lasso_OBP=OBP_linear_models['Lasso'].predict(OBP_test.iloc[:,-5:])
Ridge_OBP=OBP_linear_models['Ridge'].predict(OBP_test.iloc[:,-5:])
RF_OBP=OBP_RF_models['RF'].predict(OBP_test.iloc[:,-5:])
XGB_OBP=XGB_OBP_gridsearch.predict(OBP_test.iloc[:,-5:])

#test 데이터의 WRMSE 계산
wrmse_score=[wrmse(OBP_test['OBP'],OBP_test['AB'],Lasso_OBP),  #실제값,타수,예측값 순
            wrmse(OBP_test['OBP'],OBP_test['AB'],Ridge_OBP),
            wrmse(OBP_test['OBP'],OBP_test['AB'],RF_OBP),
            wrmse(OBP_test['OBP'],OBP_test['AB'],XGB_OBP)]

x_lab=['Lasso','Ridge','RF','XGB']

plt.bar(x_lab,wrmse_score)
plt.title('WRMSE of OBP', fontsize=20)
plt.xlabel('model', fontsize=8)
plt.ylabel("",fontsize=18)
plt.ylim(0,0.5)

#막대 그래프 위에 값 표시
for i,v in enumerate(wrmse_score):
    plt.text(i-0.1,v+0.01,str(np.round(v,3))) #x좌표,y좌표, 텍스트
plt.show()

#테스트 데이터셋(2018년)의 선수들의 SLG예측
Lasso_SLG=SLG_linear_models['Lasso'].predict(SLG_test.iloc[:,-5:])
Ridge_SLG=SLG_linear_models['Ridge'].predict(SLG_test.iloc[:,-5:])
RF_SLG=SLG_RF_models['RF'].predict(SLG_test.iloc[:,-5:])
XGB_SLG=XGB_SLG_gridsearch.predict(SLG_test.iloc[:,-5:])

#test 데이터의 WRMSE 계산
wrmse_score_SLG=[wrmse(SLG_test['SLG'],SLG_test['AB'],Lasso_SLG),  #실제값,타수,예측값 순
            wrmse(SLG_test['SLG'],SLG_test['AB'],Ridge_SLG),
            wrmse(SLG_test['SLG'],SLG_test['AB'],RF_SLG),
            wrmse(SLG_test['SLG'],SLG_test['AB'],XGB_SLG)]

x_lab=['Lasso','Ridge','RF','XGB']

plt.bar(x_lab,wrmse_score_SLG)
plt.title('WRMSE of SLG', fontsize=20)
plt.xlabel('model', fontsize=8)
plt.ylabel("",fontsize=18)
plt.ylim(0,0.9)

#막대 그래프 위에 값 표시
for i,v in enumerate(wrmse_score_SLG):
    plt.text(i-0.1,v+0.01,str(np.round(v,3))) #x좌표,y좌표, 텍스트
plt.show()

결과해석 및 평가
- 변수의 중요도를 랜덤포레스트를 통해 알수가 있다

plt.figure(figsize=(15,6))

#가로막대 그래프
plt.subplot(1,2,1)
plt.barh(OBP_train.iloc[:,-5:].columns, OBP_RF_models['RF'].feature_importances_)
plt.title('Feature importance of RF in OBP')

plt.subplot(1,2,2)
plt.barh(SLG_train.iloc[:,-5:].columns, SLG_RF_models['RF'].feature_importances_)
plt.title('Feature importance of RF in SLG')
plt.show()

라쏘와 릿지 회귀모델

#Lasso에서 GridSearchCV로 탐색한 최적의 alpha값 출력
print('Alpha:',OBP_linear_models['Lasso'].alpha)

#Lasso model의 선형계수 값 출력
display(pd.DataFrame(OBP_linear_models['Lasso'].coef_.reshape(-1,5),
                    columns=OBP_train.iloc[:,-5:].columns, index=['coefficient']))

#Lasso에서 GridSearchCV로 탐색한 최적의 alpha값 출력
print('Alpha:',SLG_linear_models['Lasso'].alpha)

#Lasso model의 선형계수 값 출력
display(pd.DataFrame(SLG_linear_models['Lasso'].coef_.reshape(-1,5),
                    columns=SLG_train.iloc[:,-5:].columns, index=['coefficient']))

from sklearn.linear_model import lars_path

plt.figure(figsize=(15,4.8))
plt.subplot(1,2,1)

#OBP모델의 alpha값의 변화에 따른 계수의 변화를 alpha,coefs에 저장
alphas,_,coefs = lars_path(OBP_train.iloc[:,-5:].values, OBP_train['OBP'], method='lasso',verbose=True)

#피처별 alpha값에 따른 선형 모델 계수의 절댓값의 합 
xx= np.sum(np.abs(coefs.T),axis=1) #coefs.T.shape : (6,5), axis=1하니까 행으로 합쳐지는 것같다. 총 6개 값이 나온다

#계수의 절댓값 중 가장 큰 값으로 alpha에 따른 피처의 계수의 합을 나눈다
xx/=xx[-1] #0.81069777을 각 원소에 나눈다

plt.plot(xx, coefs.T)
plt.xlabel('|coef|/max|coef|')
plt.ylabel('cofficients')
plt.title('OBP LASSO path')
plt.axis('tight')
plt.legend(OBP_train.iloc[:,-5:].columns)

plt.subplot(1,2,2)
#SLG모델에서 alptha값의 변화에 따른 계수의 변화를 alpha, coefs에 저장
alphas,_,coefs = lars_path(SLG_train.iloc[:,-5:].values, SLG_train['SLG'], method='lasso',verbose=True)

#피처별 alpha값에 따른 선형 모델 계수의 절댓값의 합 
xx= np.sum(np.abs(coefs.T),axis=1)

#계수의 절댓값 중 가장 큰 값으로 alpha에 따른 피처의 계수의 합을 나눈다
xx/=xx[-1]

plt.plot(xx, coefs.T)
plt.xlabel('|coef|/max|coef|')
plt.ylabel('cofficients')
plt.title('SLG LASSO path')
plt.axis('tight')
plt.legend(SLG_train.iloc[:,-5:].columns)
plt.show()

  • 위 그래프 원리는 좀더 공부가 필요해 보임

 

앙상블

print('OBP model averaging:', wrmse(OBP_test['OBP'], OBP_test['AB'],(Lasso_OBP+RF_OBP)/2))
print('SLG model averaging:', wrmse(SLG_test['SLG'], SLG_test['AB'],(Lasso_SLG+RF_SLG)/2))

OBP model averaging: 0.3181395239559683
SLG model averaging: 0.6717303946958075

 

단순화된 모델 생성

#전처리된 데이터를 다른 곳에 저장
sum_hf_yr_OBP_origin=sum_hf_yr_OBP.copy()

#전체 희생 플라이 계산
regular_season_df_SF['SF']=regular_season_df[['H','BB','HBP']].sum(axis=1)/regular_season_df['OBP']-regular_season_df[['AB','BB','HBP']].sum(axis=1)
regular_season_df['SF'].fillna(0, inplace=True) #결측값은 0으로
regular_season_df['SF']=regular_season_df['SF'].apply(lambda x:round(x,0)) #정수형태로 변경

#한 타수당 평균 희생 플라이 계산 후 필요한 것만 추출 
regular_season_df['SF_1']=regular_season_df['SF']/regular_season_df['AB']
regular_season_df_SF=regular_season_df[['batter_name','year','SF_1']]

# day_by_day_df에서 연도별 선수의 시즌 상반기 출루율과 관련된 성적 합 구하기 +BB,RBI 추가
sum_hf_yr_OBP= day_by_day_df.loc[day_by_day_df['date']<=7.18].groupby(['batter_name','year'])['AB','H','BB','HBP','RBI','2B','3B','HR'].sum().reset_index()

# day_by_day_df와 regular_season에서 구한 희생플라이 관련 데이터 합치기
sum_hf_yr_OBP=sum_hf_yr_OBP.merge(regular_season_df_SF, how='left', on=['batter_name','year'])

#한 타수당 평군 희생플라이 계산, 정규시즌에서 구한 희생플라이 비율을 일일데이터에 적용 
sum_hf_yr_OBP['SF']=(sum_hf_yr_OBP['SF_1']*sum_hf_yr_OBP['AB']).apply(lambda x:round(x,0))
sum_hf_yr_OBP.drop('SF_1', axis=1, inplace=True)

 

#상반기 OBP(출루율)
sum_hf_yr_OBP['OBP']=sum_hf_yr_OBP[['H','BB','HBP']].sum(axis=1)/sum_hf_yr_OBP[['AB','BB','HBP','SF']].sum(axis=1)
sum_hf_yr_OBP['OBP'].fillna(0, inplace=True)

#TB계산
sum_hf_yr_OBP['TB']=sum_hf_yr_OBP['H']+sum_hf_yr_OBP['2B']*2+sum_hf_yr_OBP['3B']*3+sum_hf_yr_OBP['HR']*4
sum_hf_yr_OBP= sum_hf_yr_OBP[['batter_name', 'year','AB','OBP','BB','TB','RBI']]

#나이추가
sum_hf_yr_OBP=sum_hf_yr_OBP.merge(regular_season_df[['batter_name', 'year','age']],
                                 how='left', on=['batter_name','year'])

#평균 OBP추가
sum_hf_yr_OBP = sum_hf_yr_OBP.merge(player_OBP_mean[['batter_name','mean_OBP']], how='left', on='batter_name')

sum_hf_yr_OBP=sum_hf_yr_OBP.loc[~sum_hf_yr_OBP['mean_OBP'].isna()].reset_index(drop=True)

 

#각 변수에 대한 1년 전 성적 생성
sum_hf_yr_OBP=lag_function(sum_hf_yr_OBP,'BB',1)
sum_hf_yr_OBP=lag_function(sum_hf_yr_OBP,'TB',1)
sum_hf_yr_OBP=lag_function(sum_hf_yr_OBP,'RBI',1)
sum_hf_yr_OBP=lag_function(sum_hf_yr_OBP,'OBP',1)

sum_hf_yr_OBP=sum_hf_yr_OBP.dropna() #결측치 포함한 행 제거

#변수리스트 지정
feature_list1=['age','lag1_OBP','mean_OBP']
feature_list2=['age','lag1_OBP','lag1_BB','lag1_TB','lag1_RBI','lag1_OBP','mean_OBP']

 

#학습시킬 데이터 30타수 이상만 학습
sum_hf_yr_OBP= sum_hf_yr_OBP.loc[sum_hf_yr_OBP['AB']>=30]

#2018 test로 나누고 나머지는 학습
OBP_train=sum_hf_yr_OBP.loc[sum_hf_yr_OBP['year']!=2018]
OBP_test=sum_hf_yr_OBP.loc[sum_hf_yr_OBP['year']==2018]

#gridSearch를 이용한 학습
OBP_RF_models_1={
    'RF':GridSearchCV(RandomForestRegressor(random_state=42), param_grid=RF_params, n_jobs=-1).fit(OBP_train.loc[:,feature_list1], OBP_train['OBP']).best_estimator_
}

OBP_RF_models_2={
    'RF':GridSearchCV(RandomForestRegressor(random_state=42), param_grid=RF_params, n_jobs=-1).fit(OBP_train.loc[:,feature_list2], OBP_train['OBP']).best_estimator_
}

 

#예측
RF_OBP1= OBP_models_1['RF'].predict(OBP_test.loc[:,feature_list1])
RF_OBP2= OBP_models_2['RF'].predict(OBP_test.loc[:,feature_list2])

#wrmse 계산
wrmse_score= [wrmse(OBP_test['OBP'], OBP_test['AB'],RF_OBP1),
             wrmse(OBP_test['OBP'], OBP_test['AB'],RF_OBP2)]
x_lab=['simple','complicate']

plt.bar(x_lab, wrmse_score)
plt.title('WRMSE of OBP', fontsize=20)
plt.xlabel('model', fontsize=18)
plt.xlabel('', fontsize=18)
plt.ylim(0,0.5)

#막대그래프 위에 값 표시
for i,v in enumerate(wrmse_score):
    plt.text(i-0.1, v+0.01, str(np.round(v,3)))
plt.show()

 

#최종 제출을 위한 워래 데이터 복구
sum_hf_yr_OBP=sum_hf_yr_OBP_origin.copy()

테스트 데이터 정제

submission=pd.read_csv('D:/dacon/KBO 타자 OPS 예측/submission.csv')
submission['year']=2019 

#2019년의 age계산
batter_year_born=regular_season_df[['batter_id','batter_name','year_born']].copy()

#중복선수 제거
batter_year_born=batter_year_born.drop_duplicates().reset_index(drop=True)

submission=submission.merge(batter_year_born, how='left', on=['batter_id','batter_name'])
submission['age']=submission['year']-submission['year_born'].apply(lambda x: int(x[:4]))
submission.head()

# submission OBP,SLG 파일 2개로 만들어 합치기
submission_OBP=submission.copy()
submission_SLG=submission.copy()

OBP

# 앞서 전처리한 데이터를 이용해 평균 성적 기입
submission_OBP=submission_OBP.merge(sum_hf_yr_OBP[['batter_name','mean_OBP']].drop_duplicates().reset_index(drop=True), 
                        how='left', on='batter_name')

#과거 성적 값 채우기
for i in [1,2,3]:
    temp_lag_df=sum_hf_yr_OBP.loc[
        (sum_hf_yr_OBP['year']==(2019-i))&
        (sum_hf_yr_OBP['AB']>=30),['batter_name','OBP']].copy()
    temp_lag_df.rename(columns={'OBP':'lag'+str(i)+'_OBP'}, inplace=True)
    submission_OBP=submission_OBP.merge(temp_lag_df, how='left', on='batter_name')
submission_OBP.head()

 

 

case1 
- 일별 데이터에 기록이 없어서 mean_OBP가 없는 경우
- 김주찬,이범호

for batter_name in ['김주찬','이범호']:
    #30타수 이상인 해당선수의 인덱스
    cond_regular=(regular_season_df['AB']>=30) & (regular_season_df['batter_name']==batter_name)
    
    #타수를 고려해 평균 OBP계산
    mean_OBP= sum(regular_season_df.loc[cond_regular,'AB']*\
                  regular_season_df.loc[cond_regular,'OBP'])/\
    sum(regular_season_df.loc[cond_regular,'AB'])
    
    submission_OBP.loc[(submission_OBP['batter_name']==batter_name),'mean_OBP']=mean_OBP #계산한 평균값으로 대체
    
    #regular_season_df으로부터 1,2,3년전 성적 구하기
    cond_sub=submission_OBP['batter_name']==batter_name
    
    #타수가 30이면서 김주찬,이범호인 사람의 2018년 기록을 lag1_OBP에 삽입
    submission_OBP.loc[cond_sub,'lag1_OBP']=regular_season_df.loc[(cond_regular)&(regular_season_df['year']==2018),'OBP'].values
    
    #타수가 30이면서 김주찬,이범호인 사람의 2017년 기록을 lag1_OBP에 삽입
    submission_OBP.loc[cond_sub,'lag1_OBP']=regular_season_df.loc[(cond_regular)&(regular_season_df['year']==2017),'OBP'].values
    
    #타수가 30이면서 김주찬,이범호인 사람의 2016년 기록을 lag1_OBP에 삽입
    submission_OBP.loc[cond_sub,'lag1_OBP']=regular_season_df.loc[(cond_regular)&(regular_season_df['year']==2016),'OBP'].values
    

case2
- 1998년 혹은 1999년 출생의 신인급 선수
- 성장가능성을 기대 할수 있으므로 2018년 시즌의 성적으로 출루율의 평균을 대체

for i in np.where(submission_OBP['batter_name'].isin(['고명성','전민재','김철호','신범수','이병휘'])):
    submission_OBP.loc[i,'mean_OBP']=season_OBP_mean.loc[season_OBP_mean['year']==2018,'mean_OBP']

case3

- 2018년 하반기 성적만 있는 경우
- 정규시즌 성적을 바탕으로 평균 출루율 / 1년 전 출루율 수치를 대체

for batter_name in ['전병우','샌즈']:
    #30타수 이상인 해당 선수의 index추출
    cond_regular=(regular_season_df['AB']>=30)&(regular_season_df['batter_name']==batter_name)
    
    #타수를 고려해 선수의 평균 OBP 계산
    mean_OBP=sum(regular_season_df.loc[cond_regular, 'AB']* regular_season_df.loc[cond_regular,'OBP'])/\
            sum(regular_season_df.loc[cond_regular,'AB'])
    
    submission_OBP.loc[(submission_OBP['batter_name']==batter_name),'mean_OBP']=mean_OBP
    
    #2018년 데이터로부터 2019년 1년 전 성적 기입
    cond_sub= submission_OBP['batter_name']==batter_name
    submission_OBP.loc[cond_sub,'lag1_OBP']=regular_season_df.loc[(cond_regular)&(regular_season_df['year']==2018),'OBP'].values

 

case3

- 은퇴 혹은 1군 수준의 성적을 보여주지 못한 선수
- 하위25%의 성적으로 대체

#평균 성적이 결측치인 선수들에 대해 평균 OBP의 하위25% 성적 기입
submission_OBP.loc[submission_OBP['mean_OBP'].isna(),'mean_OBP']=np.quantile(player_OBP_mean['mean_OBP'],0.25)

#과거 데이터 채우기
for i in [1,2,3]:
    #i년 전 OBP 결측치 제거 
    submission_OBP=lag_na_fill(submission_OBP,'OBP',i,season_OBP_mean)
submission_OBP.head()

 

 

SLG

# 앞서 전처리한 데이터를 이용해 평균 성적 기입
submission_SLG=submission_SLG.merge(sum_hf_yr_SLG[['batter_name','mean_SLG']].drop_duplicates().reset_index(drop=True), 
                        how='left', on='batter_name')

#과거 성적 값 채우기
for i in [1,2,3]:
    temp_lag_df=sum_hf_yr_SLG.loc[
        (sum_hf_yr_SLG['year']==(2019-i))&
        (sum_hf_yr_SLG['AB']>=30),['batter_name','SLG']].copy()
    temp_lag_df.rename(columns={'SLG':'lag'+str(i)+'_SLG'}, inplace=True)
    submission_SLG=submission_SLG.merge(temp_lag_df, how='left', on='batter_name')
submission_SLG.head()

submission_SLG['batter_name'].loc[submission_SLG['mean_SLG'].isna()].values

#case1
for batter_name in ['김주찬','이범호']:
    #30타수 이상인 해당선수의 인덱스
    cond_regular=(regular_season_df['AB']>=30) & (regular_season_df['batter_name']==batter_name)
    
    #타수를 고려해 평균 OBP계산
    mean_SLG= sum(regular_season_df.loc[cond_regular,'AB']*\
                  regular_season_df.loc[cond_regular,'SLG'])/\
    sum(regular_season_df.loc[cond_regular,'AB'])
    
    submission_SLG.loc[(submission_SLG['batter_name']==batter_name),'mean_SLG']=mean_SLG #계산한 평균값으로 대체
    
    #regular_season_df으로부터 1,2,3년전 성적 구하기
    cond_sub=submission_SLG['batter_name']==batter_name
    
    #타수가 30이면서 김주찬,이범호인 사람의 2018년 기록을 lag1_OBP에 삽입
    submission_SLG.loc[cond_sub,'lag1_SLG']=regular_season_df.loc[(cond_regular)&(regular_season_df['year']==2018),'SLG'].values
    
    #타수가 30이면서 김주찬,이범호인 사람의 2017년 기록을 lag1_OBP에 삽입
    submission_SLG.loc[cond_sub,'lag1_SLG']=regular_season_df.loc[(cond_regular)&(regular_season_df['year']==2017),'SLG'].values
    
    #타수가 30이면서 김주찬,이범호인 사람의 2016년 기록을 lag1_OBP에 삽입
    submission_SLG.loc[cond_sub,'lag1_SLG']=regular_season_df.loc[(cond_regular)&(regular_season_df['year']==2016),'SLG'].values
    
    
#case2
for i in np.where(submission_SLG['batter_name'].isin(['고명성','전민재','김철호','신범수','이병휘'])):
    submission_SLG.loc[i,'mean_SLG']=season_SLG_mean.loc[season_SLG_mean['year']==2018,'mean_SLG']
    
#case3
for batter_name in ['전병우','샌즈']:
    #30타수 이상인 해당 선수의 index추출
    cond_regular=(regular_season_df['AB']>=30)&(regular_season_df['batter_name']==batter_name)
    
    #타수를 고려해 선수의 평균 OBP 계산
    mean_SLG=sum(regular_season_df.loc[cond_regular, 'AB']* regular_season_df.loc[cond_regular,'SLG'])/\
            sum(regular_season_df.loc[cond_regular,'AB'])
    
    submission_SLG.loc[(submission_SLG['batter_name']==batter_name),'mean_SLG']=mean_SLG
    
    #2018년 데이터로부터 2019년 1년 전 성적 기입
    cond_sub= submission_SLG['batter_name']==batter_name
    submission_SLG.loc[cond_sub,'lag1_SLG']=regular_season_df.loc[(cond_regular)&(regular_season_df['year']==2018),'SLG'].values
#case4
#평균 성적이 결측치인 선수들에 대해 평균 SLG의 하위25% 성적 기입
submission_SLG.loc[submission_SLG['mean_SLG'].isna(),'mean_SLG']=np.quantile(player_SLG_mean['mean_SLG'],0.25)

#과거 데이터 채우기
for i in [1,2,3]:
    #i년 전 SLG 결측치 제거 
    submission_SLG=lag_na_fill(submission_SLG,'SLG',i,season_SLG_mean)
submission_SLG.head()

 

### OBP,SLG 둘다 lasso 모델에서 가장 좋은 성능을 보였으므로 lasso로 예측을 시행한다

#Lasso를 이용한 OBP 예측
predict_OBP=OBP_linear_models['Lasso'].predict(submission_OBP.iloc[:,-5:])
#Lasso를 이용한 SLG 예측
predict_SLG=SLG_linear_models['Lasso'].predict(submission_OBP.iloc[:,-5:])

final_submission=submission[['batter_id','batter_name']]
final_submission['OPS']=predict_SLG+predict_OBP #OBP+SLG= OPS
final_submission.head()

반발계수의 변화

#시즌별 전체 OBP 계산(30타수 이상인 선수들의 기록만 이용)
season_OBP=regular_season_df.loc[regular_season_df['AB']>=30].groupby('year').agg({'AB':'sum','H':'sum','BB':'sum','HBP':'sum','SF':'sum'}).reset_index()
season_OBP['OBP']=season_OBP[['H','BB','HBP']].sum(axis=1)/ season_OBP[['AB','BB','HBP','SF']].sum(axis=1)

#시즌별 전체 SLG 계산(30타수 이상인 선수들만의 기록만 사용)
season_SLG=regular_season_df.loc[regular_season_df['AB']>=30].groupby('year').agg({'AB':'sum','H':'sum','2B':'sum','3B':'sum','HR':'sum'}).reset_index()
season_SLG['SLG']=((season_SLG['H']- season_SLG[['2B','3B','HR']].sum(axis=1))+\
                  season_SLG['2B']*2+season_SLG['3B']*3+season_SLG['HR']*4)/season_SLG['AB']

#season_OBP와season_SLG 병합 후 season_OPS를 생성해 계산
season_OPS=pd.merge(season_OBP[['year','OBP']], season_SLG[['year','SLG']], on='year')
season_OPS['OPS']=season_OBP['OBP']+season_SLG['SLG']

#시즌별 전체 홈런 수와 한 선수당 평균 홈런 수 계산
season_HR=regular_season_df.loc[regular_season_df['AB']>=30].groupby('year').agg({'HR':['sum','mean','count']}).reset_index()
season_HR.columns=['year','sum_HR','mean_HR','count']

#기존의 OPB 데이터셋과 병합
season_OPS=season_OPS.merge(season_HR, on='year', how='left')
display(season_OPS)

# 2000년도 이전의 데이터 수가 충분치 않아 고려하지 않는다
season_OPS.loc[season_OPS['year']>2000]

#2018년의 평균 홈런 개수를 시즌별평균 홈런 수에서 뺀다
season_OPS['HR_diff']=season_OPS['mean_HR']-season_OPS['mean_HR'].iloc[-1]
difference=season_OPS.sort_values(by='HR_diff')[['year','OPS','HR_diff']]
display(difference.reset_index(drop=True).head(12))

final_submission['OPS'] =final_submission['OPS']-0.038
display(final_submission.head(10))
# final_submission.to_csv('submissionb.csv', index=False) #최종 제출 파일 

이번 실습은 야구에 대한 도메인 지식이 부족한 상태에서 진행한거라 그런지 이해하기가 시간이 걸렸습니다

모델구축 부분에서는 제 자원이 부족해서 미처 실행하지 못한점이 아쉬웠습니다

이 실습을 통해서 결측치를 다루는 방법을 볼수 있었다는 점이 가장 좋았습니다

제 생각보다 결측치 다루는게 난이도가 있었습니다

개인 프로젝트 진행 시 좋은 참고가 될것같습니다

 

 

728x90

'실습 note' 카테고리의 다른 글

OpenCV_4(기하학적 변환)  (0) 2021.02.22
버스 승차인원 예측 실습(데이콘 경진대회 1등 솔루션)  (0) 2021.02.20
OpenCV_3(필터링)  (0) 2021.02.19
OpenCV_2(기본 영상처리)  (0) 2021.02.18
OpenCV_1(기초 사용법)  (0) 2021.02.17
728x90

 

 

평균값 필터_1

### 평균값 필터

import sys
import numpy as np
import cv2


src = cv2.imread('rose.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

''' 방법1
#ndarray
#1/9 float이므로 자동으로 dtype이 float64
#경우따라 비트를 32,64 선택해도 된다
kernel= np.array([[1/9,1/9,1/9],
                 [1/9,1/9,1/9],
                 [1/9,1/9,1/9]], dtype=np.float32)
'''

#필터링함수
#-1:입력영상과 동일한 데이터 타입이 만들어진다. 현재입력영상은 그레이스케일
# dst=cv2.filter2D(src, -1,kernel)

'''
#방법2
kernel=np.ones((3,3), dtype=np.float64)/9.
'''
#방법3
dst=cv2.blur(src,(3,3))

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

 

평균값 필터_2

### 평균값 필터2

import sys
import numpy as np
import cv2

src = cv2.imread('rose.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

cv2.imshow('src', src)

for ksize in (3, 5, 7): #3x3, 5x5, 7x7
    dst = cv2.blur(src, (ksize, ksize))

    desc = 'Mean: {}x{}'.format(ksize, ksize)
    cv2.putText(dst, desc, (10, 30), cv2.FONT_HERSHEY_SIMPLEX,
                1.0, 255, 1, cv2.LINE_AA)

    cv2.imshow('dst', dst)
    cv2.waitKey()

cv2.destroyAllWindows()

 

가우시안 필터_1

### 가우시안 필터

import sys
import numpy as np
import cv2


src = cv2.imread('rose.bmp', cv2.IMREAD_GRAYSCALE)

#커널크기는 (0,0)을 사용함으로써 sigma값을 계산하여 자동으로 크기를 결정한다
#사용자가 강제로 커널크기를 결정하면 가우시안의 분포 모양 가중치를 사용 못하기 때문에 지양한다
#sigmaX=1
#1.커널크기는 8*sigma+1(0.0좌표를 추가로 더한다), 9*9 커널크기 : 주로 float 타입 영상에 사용
#2.커널크기는 6*sigma+1(0.0좌표를 추가로 더한다), 7*7 커널크기 : 주로 uint8 타입 영상에 사용
#블러를 강하게,더 흐릿하게 할땐 시그마 값을 올린다
dst= cv2.GaussianBlur(src, (0,0),1) #7*7커널크기, sigmax=3, 6*3+1=19*19커널
dst2 = cv2.blur(src, (7, 7))

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.imshow('dst2', dst2)
cv2.waitKey()

cv2.destroyAllWindows()

 

가우시안 필터_2

### 가우시안필터 크기에 따른 영상변화

import sys
import numpy as np
import cv2


src = cv2.imread('rose.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

cv2.imshow('src', src)

for sigma in range(1, 6):
    # sigma 값을 이용하여 가우시안 필터링
    dst = cv2.GaussianBlur(src, (0, 0), sigma)

    desc = 'sigma = {}'.format(sigma)
                        #(x축의 글자 시작점 좌표,y축 글 시작점 좌표)
    cv2.putText(dst, desc, (10, 30), cv2.FONT_HERSHEY_SIMPLEX,
                1.0, 255, 1, cv2.LINE_AA)

    cv2.imshow('dst', dst)
    cv2.waitKey()

cv2.destroyAllWindows()

 

샤프닝 필터_1

### 샤프닝

import sys
import numpy as np
import cv2

src = cv2.imread('rose.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

blr = cv2.GaussianBlur(src,(0,0),2) #블러가 된 영상, 부드러워진 영상, 차이값을 얻디위해 블러처리

'''
엣지 포인트만 남아서 윤곽선으로 보인다. 
이 윤곽선을 나타내는 픽셀값을 원본 영상에 더하면 되는데
subtract는 음수의 값을 0으로 바꾸기 때문에 완전한 엣지 픽셀모양이 아니다
이를 해결하기 위해서 addweighted를 사용한다
dst =cv2.subtract(src,blr) 
'''
#src에 1가중치, blr에 -1 가중치 준뒤
#결과값을 잘 보기 위해서 128을 더해준다
tmp= cv2.addWeighted(src,1,blr,-1,128) #회색부분은 윤곽선이 아닌 부분임,흰색또는 검정색 부분이 엣지 포인트

#방법1
#아래처럼하면 tmp객체를 이용한 덧셈을 할 필요가 없다
#원하는 결과물은 (원본-부드러운영상)+원본= "2원본-부드러워진영상"이므로
#가중치를 아래처럼 설정한다.
dst= cv2.addWeighted(src,2,blr,-1,128) #가중치에 -가 붙으면 뺄셈이 가능하다

#방법2
#numpy형식이므로 아래 처럼 src-blr 형태로가능
#타입이 float으로 계산해야 saturate가 되지 않는다
# dst=np.clip(2.0*src-blr, 0,255).astype(np.uint8) #float64형태->np.uint8 변환해야 0~255 값 표현가능(2**8=256)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

 

샤프닝 필터_2

### 컬러샤프닝

import sys
import numpy as np
import cv2

src = cv2.imread('rose.bmp') #컬러

if src is None:
    print('Image load failed!')
    sys.exit()

#컬러영상:밝기 채널만 활용한다
src_ycrcb = cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb)

                    #0: Y(밝기)plane
src_f = src_ycrcb[:, :, 0].astype(np.float32) #np.float32 실수타입으로 바꾼다. 정교한 결과를 위해서

#GaussianBlur은 입력영상의 타입을 출력영상 타입과 같게 한다
#src_f가 실수값이라서 blr도 실수값을 가진다.
#실수값을 가진다는 건 213.3243352 의3243352이라는 미세한 픽셀값이 살아 있다는 의미
#따라서 미세한 값까지 반영한 픽셀값을 uint8로 변경해서 출력하면 보다 더 정교한 결과를 얻을수 있다 
blr = cv2.GaussianBlur(src_f, (0, 0), 2.0)

#더해진 값을 0번째 plane에 덮어쓰기
src_ycrcb[:, :, 0] = np.clip(2.* src_f - blr, 0, 255).astype(np.uint8)

dst = cv2.cvtColor(src_ycrcb, cv2.COLOR_YCrCb2BGR) #imshow는 BGR채널만 취급한다

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

 

median 필터

### median 필터 

import sys
import numpy as np
import cv2

src = cv2.imread('noise.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

#입력영상에서 3*3 커널 픽셀을 정렬 한 후 median값을 결과영상 가운데 값에 그대로 넣는다
#후추노이즈 제거에 효과적
dst = cv2.medianBlur(src, 3)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()
   

 

### 잡음제거 필터

import sys
import numpy as np
import cv2

src = cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

#커널크기는 자동적으로 계산되기 때문에 -1값으로 입력하는게 좋다
#10(시그마 값임): 이 값을 기준으로 엣지 부분인지 아닌지를 판단한다.(sigmaColor)
#5: sigmaspace -> 가우시안블러의 시그마기능과 완전히 같다
#5이상의 값을 넣으면 가우시간 필터값이 커지게 된다 (8*sigma+1 또는 6*sigma+1)
#필터크기가 커진다-> 연산량증가 -> 속도 저하
dst = cv2.bilateralFilter(src, -1, 10, 5)

'''
sigmaColor추가설명
위 예제처럼 표준편차가 10이라고 한다면
가우시안 분포x 값이 -10<x<10 사이에 67%값들이 모여있다고 설명할수 있다
이때, 보통 플러스마이너스 2시그마, or 3시그마 보다 큰 픽셀이라면 
엣지로 인식해서 블러를 적용안하고 살린다. 
'''


cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

 

카툰필터 

### 카툰 필터

# 카툰 필터 카메라

import sys
import numpy as np
import cv2

#자연스러운 컬러를 단순한 컬러로 바꾸고 ->cv2.bilaterFilter
#검정색 엣지를 단순한 컬러 영상에 ->cv2.Canny
#합친다 ->cv2.bitwise_and():검정색 엣지를  0픽셀값으로 변경하고, 그외 하얀 부분은 단순한 컬로 영상으로 채운다
def cartoon_filter(img):
    
    #크기 반으로 축소
    h,w =img.shape[:2]
    img =cv2.resize(img, (w//2, h//2)) #축소 후 연산하면 연산량도 줄어들뿐만 아니라 단순한 컬러의 효과가 극대화된다

    blr= cv2.bilateralFilter(img, -1, 20, 7) #과장하게 만들기 위해 시그마 값을 좀 높게 설정
    
    #50~120으로하면 평탄한 부분이 검정,엣지가 하얀색이되므로 255로 빼서 반전시킨다
    #평탄한부분이 하얀, 엣지가 검정으로 반전되는 것
    edge=cv2.Canny(img,50,120) #컬러영상을 내부적으로 그레이로 바꾼다
    edge=cv2.cvtColor(edge,cv2.COLOR_GRAY2BGR) #blr가 컬러이므로 합치기 위해서 타입을 컬러로 통일
    dst= cv2.bitwise_and(blr,edge)
    
    #크기 정상으로 다시 확대
    dst=cv2.resize(dst,(w,h), interpolation=cv2.INTER_NEAREST) #interpolation의 디폴트 값으로 하면 블러처리된 느낌을 준다
                                                               #지금은 급격한 변화를 줘야 하므로 따로 설정한다

    return dst

#그레이로 컨버트 시킨 후 
#가우시안블러로 처리한다. 
#가우시안블러를 사용하면 엣지 부분이 완만한 곡선으로 블러처리되는데
#이 부분을 검정색으로처리하고, 나머지는 모두 흰색으로 처리한다
#검정과 흰색을 처리하는 연산은 가우시안블러를 한 영상을 그레이영상에 나눗셈하여 얻을수 있다
#가우시안블러가 큰 쪽(엣지가 발생한 영역)은 0.xx 값으로 나온다.
#엣지영역에 가우시안블러를 처리하면 급격한 경사을 완만한 곡선으로 만들어준다
#급격한 경사를 이루는 부분의 높이가 완만한곡선의 높이보다 낮으므로
#완만한곡선의 높이를 나누면 0.xx로 되고
#반대로 그레이값의 높이가 더 큰 평탄화 부분은 픽셀 1.xxx로 결과값을 얻게 된다
#0.xx 와 1.xx 이둘이 남겨 되는데 0픽셀이나 1픽셀이나 구분을 못하므로 곱하기 255로 해준다
#결론적으로 0*255, 1*255이 되므로 엣지 부분이 검정색, 나머지가 하얀색으로 된다
def pencil_sketch(img): 
    gray= cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blr= cv2.GaussianBlur(gray, (0,0), 3)#가우시안블러처리
    dst=cv2.divide(gray, blr, scale=255) #나눠준후 255곱하기
    return dst


cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print('video open failed!')
    sys.exit()

cam_mode = 0 # 0이 오리지널, 1:카툰필터, 2:스케치 필터

while True:
    ret, frame = cap.read()

    if not ret:
        break

    if cam_mode == 1:
        frame = cartoon_filter(frame)
    elif cam_mode == 2:
        frame = pencil_sketch(frame)
        # frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR) #없어도 작동한다

    cv2.imshow('frame', frame)
    key = cv2.waitKey(1)

    if key == 27:
        break
    elif key == ord(' '):
        cam_mode += 1
        if cam_mode == 3:
            cam_mode = 0


cap.release()
cv2.destroyAllWindows()

 

728x90
728x90

 

2021.02.18 - [기록 note] - 2021-02-18(OpenCV_2)

 

2021-02-18(OpenCV_2)

어제에 이어 오늘도 기초공부를 진행했습니다 조금씩 내용 난이도가 올라가면서 강의 포즈 하고 생각해야할 것들이 생기기 시작했습니다 그래서 하루에 강의를 몇개를 목표로 정해야 할지 난

ghdrldud329.tistory.com

 

키보드 키를 이용한 영상 조작

import sys
import numpy as np
import cv2


img = cv2.imread('cat.bmp', cv2.IMREAD_GRAYSCALE)

if img is None:
    print('Image load failed!')
    sys.exit()

cv2.namedWindow('image')
cv2.imshow('image', img)

while True:
    keycode = cv2.waitKey()
    if keycode == ord('i') or keycode == ord('I'):
        img = ~img
        cv2.imshow('image', img)
    elif keycode == 27:
        break

cv2.destroyAllWindows()

 

 

마우스 이벤트

import sys
import numpy as np
import cv2

oldx = oldy=-1 
def on_mouse(event,x,y,flags,param): #event: ==, flags: & 사용하기
    global img,oldx,oldy

    if event == cv2.EVENT_LBUTTONDOWN: #눌렀을때 좌표
        oldx,oldy=x,y
        print('EVENT_LBUTTONDOWN:{},{}'.format(x,y))
    elif event== cv2.EVENT_LBUTTONUP: #뗐을때 좌표
        print('EVENT_LBUTTONUP:{},{}'.format(x,y))
    elif event == cv2.EVENT_MOUSEMOVE:
        #  cv.EVENT_FLAG_LBUTTON: 마우스 왼쪽 버튼이 눌려져 있음
        if flags & cv2.EVENT_FLAG_LBUTTON: #마우스가 움직이면서 왼쪽버튼이 눌러져 있다면 빨간선을 그려라
            # print('EVENT_FLAG_LBUTTON:{},{}'.format(x,y))

            #원 그림 그리기_문제점: 빠르게 이동하면 간격이 벌어짐-> line으로 그리기
            # cv2.circle(img,(x,y),5, (0,0,255), -1)

            #line 으로 그림 그리기
            cv2.line(img, (oldx,oldy),(x,y),(0,0,255),5, cv2.LINE_AA)
            cv2.imshow('image',img)
            oldx,oldy=x,y



img = np.ones((480, 640, 3), dtype=np.uint8) * 255

cv2.namedWindow('image')
cv2.setMouseCallback('image', on_mouse)

cv2.imshow('image', img)
cv2.waitKey()

cv2.destroyAllWindows()

 

트랙바 생성

### trackbar

import numpy as np
import cv2

# def on_level_changed(pos): #개선 하기 전 코드
    # global img
    #print(pos) #현재 레벨 값을 보여주기
    
    #img:uint8타입이라서 0~255값 밖에 가지지 못한다
    #pos가 16일때는 256이므로 255보다 크기 때문에 0으로 초기화된다
    #밑에 개선 후 코드로 작성한다
#    img[:,:]=pos*16 #1일때16,2일때 32, ~ 

    # cv2.imshow('image',img)

def on_level_changed(pos): #개선 후 코드
    global img
    
    # 방법1
    # level=pos*16
    # if level>=255:
    #     level=255
    # img[:,:]=level

    #방법2
    level= pos*16
    level= np.clip(level,0,255) #범위를 0~255로 지정, 음수가 나와도 0, 256이 나와도 255
    img[:,:]=level
    cv2.imshow('image',img)   

img = np.zeros((480, 640), np.uint8)
# cv2.namedWindow('image')

#트랙바-> 창이 생성된 이후에 createTrackbar를 호출해야한다
# cv2.createTrackbar() <- namedWindow밑에 삽입, namedWindow가 없다면 imshow밑에 삽입

cv2.imshow('image', img)

                #(트랙바이름,생성할 창이름,초기값,최대값,콜백함수)
                # 최대값16-> 256을 16단계로 쪼개서 나타낸다
                #level값이 변경되었을때 on_level_changed함수를 실행해라 라는 의미
cv2.createTrackbar('level','image',0,16,on_level_changed)

cv2.waitKey()
cv2.destroyAllWindows()

 

시간측정

### time_check

import sys
import time
import numpy as np
import cv2


img = cv2.imread('hongkong.jpg')

tm = cv2.TickMeter() #객체생성

tm.reset() #초기화 
tm.start() #실행할 edge 위아라 각각 start, stop 배치
t1 = time.time()

edge = cv2.Canny(img, 50, 150)

tm.stop() #실행할 edge 위아라 각각 start, stop 배치
print('time:', (time.time() - t1) * 1000)
print('Elapsed time: {}ms.'.format(tm.getTimeMilli()))

 

두 비디오 전환

### video_effect

# ==========================합성하기 전 데이터 준비==============================
import sys
import numpy as np
import cv2

# 두 개 동영상열어서 각 객체에 지정하기
cap1= cv2.VideoCapture('video1.mp4') #원숭이 영상
cap2= cv2.VideoCapture('video2.mp4') #코끼리 영상

#예외처리 코드
if not cap1.isOpened() or not cap2.isOpened(): #둘중에 하나라도 열리지 않을경우
    print('video open failed!')
    sys.exit()

# 두 동영상의 크기, FPS는 같다고 가정(FPS:초당 24.75프레임, 초당 정지 사진을 24개씩 보여준다는 의미)
frame_cnt1=round(cap1.get(cv2.CAP_PROP_FRAME_COUNT)) #1번 영상의 전체 프레임수
frame_cnt2=round(cap2.get(cv2.CAP_PROP_FRAME_COUNT)) #2번 영상의 전체 프레임수
fps= cap1.get(cv2.CAP_PROP_FPS) #24 출력됨

#첫번째영상 끝부분 2초,두번째 앞부분 2초 영상을 합성이 되게끔 
effect_frames= int(fps*2) #초당 24프레임 곱하기 2 = 48프레임을 가진다(합성부분)

print('frame_cnt1',frame_cnt1)
print('frame_cnt2',frame_cnt2)
print('FPS',fps)

delay= int(1000/fps) #두 영상 사이의 시간 간격을 계산, 두 영상 재생시간(저장할때 실제 영상시간이 저장된다)

#영상크기 뽑아내기
w= round(cap1.get(cv2.CAP_PROP_FRAME_WIDTH)) #반올림해서 정수로 만듦
h= round(cap1.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc= cv2.VideoWriter_fourcc(*'DIVX') #영상 압축

#출력 동영상 객체 생성
#영상 저장하기 위해서는 가로세로크기, 압축방식, fps 
out= cv2.VideoWriter('output.avi', fourcc,fps, (w,h)) #영상저장

# ==============================두영상을 단순히 이어붙이기==============================
#첫번째 영상
'''
while True:
    ret1,frame1 = cap1.read()

    if not ret1: #영상이 끝나면 while문을 빠져 나오기
        break
    out.write(frame1) #1번째 영상 고대~로 저장

    cv2.imshow('frame',frame1)
    cv2.waitKey(delay)

#두번째 영상
while True:
    ret2,frame2 = cap2.read()

    if not ret2: #영상이 끝나면 while문을 빠져 나오기
        break
    out.write(frame2) #2번째 영상 고대~로 저장

    cv2.imshow('frame',frame2) #frame이라는 창을 이어쓰기
    cv2.waitKey(delay)   
'''
# ============================밀어내기 합성================================
#첫번째 영상 나오고->합성영상-> 두번째 영상으로 마무리 순

#첫번째 영상
for i in range(frame_cnt1- effect_frames): #1번영상의 48프레임은 남겨두고 out영상에 그대로 저장된다
    ret1, frame1 =cap1.read()

    if not ret1:
        break

    out.write(frame1) #1번영상 저장

    cv2.imshow('frame',frame1) #1번영상 보여준 후 합성된 영상 2초짜리 보여준다
    cv2.waitKey(delay)

#합성영상
for i in range(effect_frames): #effect_frames: 1번 영상 2번 영상 겹치는 프레임(48프레임=2초동안)
    ret1, frame1=cap1.read() 
    ret2, frame2=cap2.read()

    #합성
    # 첫번째 영상 밀어낼때 얼만큼 간격으로 영상을 바꿀건지 정하기
    #dx = (w*i//effect_frames) w:1280
    dx = int(w*i/effect_frames) # 1280/48 = 가로가 약27개 픽셀단위로 밀어낸다
                           #for문이 실행되면서 27*i 만큼 점차 픽셀이 늘어나는 것
    #0,27,54,81,~ 합성할 부분의 픽셀이 늘어난다 -> ppt의 밀어내기 기능과 유사, 2번째 영상으로 전환 되어 간다
    #몇초간? 2초(48프레임=effect_frame)동안,
    frame = np.zeros((h,w,3), dtype=np.uint8) #컬러영상에 맞게 설정하여 두 영상을 대입한다
    frame[:,0:dx]= frame2[:,0:dx] #영상앞부분 부터 서서히 2번째 영상으로 채워진다 왜? dx의 픽셀이 for돌아갈수록 늘어나니까
    frame[:,dx:w] =frame1[:,dx:w] #앞부분에 채워진 두번영상 이후 첫번째 영상을 넣어야 한다. w:1280(가로 끝까지)

   # ============================디졸브합성================================

    #두 영상 디졸브로 합성하기: 두번째영상을 서서히 나타나게 함, 합성영상에서 두 영상의 가중치를 다르게 조정
    
    # alpha=1.0-i/effect_frames #frame1 가중치를 서서히 낮춘다. effect_frames가 48이니까 i가 증가할수록 가중치가 낮아지는 것.

                        #alpha:frame1의 가중치
    # frame= cv2.addWeighted(frame1, alpha, frame2, 1-alpha,0) #0: gamma-> 전체 가중치에 추가로 더하는 가중치

    # =====================================================================
    out.write(frame)
    cv2.imshow('frame',frame)
    cv2.waitKey(delay)

cap1.release()
cap2.release()
out.release()
cv2.destroyAllWindows()

 

영상 밝기 조절

### 영상의 밝기 조절

import sys
import numpy as np
import cv2

#src: 입력을 의미, dst: 출력을 의미
src= cv2.imread('lenna.bmp',cv2.IMREAD_GRAYSCALE)


#grayscale 영상을 100만큼 밝게 하는 경우
'''
dst=cv2.add(src,100) 

dst=src+100 #src가 ndarray이기 때문에 브로드캐스팅으로 덧셈이 가능
            #하지만 255보다 큰 값은 0에 가까운 값으로 변하게 되는 문제점발생
#numpy 개선코드
            #100. : 점.을찍어서 실수 단위로 연산이 되게 해야한다
            #결과가 실수 단위로 계산이 된다
            #그 후 astype으로 다시 컨버트해준다
dst=np.clip(src+100.,0,255).astype(np.uint8) #결과가 0보다 작으면 0으로,255보다 크면 255로 만들기
'''

#컬러로 불러와서 100만큼 밝게 하는 경우
'''
src= cv2.imread('lenna.bmp')
#아래결과는 블루색깔이 많아진다 왜일까?
dst=cv2.add(src,100)  #여기서 100은 사실 네개의 실수값 네개로 구성된 스칼라 값이다
                      #따라서 실제 컴퓨터는(100,0,0,0)로 구성이된다 순서 상 100이 블루이므로 블루색이 100만큼 더 증가
'''

#동일하게 모두 밝게 할때
dst=cv2.add(src, (100,100,100,0))            

cv2.imshow('src',src)
cv2.imshow('dst',dst)
cv2.waitKey()

cv2.destroyAllwindows()

 

산술연산

### 영상의 산술연산 

import sys
import numpy as np
import cv2
from matplotlib import pyplot as plt


src1 = cv2.imread('lenna256.bmp', cv2.IMREAD_GRAYSCALE)
src2 = cv2.imread('square.bmp', cv2.IMREAD_GRAYSCALE)

if src1 is None or src2 is None:
    print('Image load failed!')
    sys.exit()

dst1 = cv2.add(src1, src2, dtype=cv2.CV_8U) #덧셈
dst2 = cv2.addWeighted(src1, 0.5, src2, 0.5, 0.0) #두 가중치 0.50 => 두 입력영상의 윤곽을 골고루 가지는 평균영상
dst3 = cv2.subtract(src1, src2) #두 영상을 뺄셈
dst4 = cv2.absdiff(src1, src2) #두영상 중 가장 차이가 나는 곳을 표현 ex)틀린그림 찾기에서 틀린 부분만 부각되어 표현될것이다

plt.subplot(231), plt.axis('off'), plt.imshow(src1, 'gray'), plt.title('src1')
plt.subplot(232), plt.axis('off'), plt.imshow(src2, 'gray'), plt.title('src2')
plt.subplot(233), plt.axis('off'), plt.imshow(dst1, 'gray'), plt.title('add')
plt.subplot(234), plt.axis('off'), plt.imshow(dst2, 'gray'), plt.title('addWeighted')
plt.subplot(235), plt.axis('off'), plt.imshow(dst3, 'gray'), plt.title('subtract')
plt.subplot(236), plt.axis('off'), plt.imshow(dst4, 'gray'), plt.title('absdiff')
plt.show()

 

컬러영상

### 컬러 영상과 색 공간

import sys
import numpy as np
import cv2


# 컬러 영상 불러오기
src = cv2.imread('candies.png', cv2.IMREAD_COLOR)

if src is None:
    print('Image load failed!')
    sys.exit()

# 컬러 영상 속성 확인
print('src.shape:', src.shape)  # src.shape: (480, 640, 3)
print('src.dtype:', src.dtype)  # src.dtype: uint8

#컬러 공간 변환
'''
src_hsv=cv2.cvtColor(src, cv2.COLOR_BGR2HSV) 
plane=cv2.split(src_hsv)
'''

#채널분리
plane=cv2.split(src)#3개의 영상이 들어있는 리스트가 반환


cv2.imshow('src', src)
cv2.imshow('plane[0]',plane[0]) #블루
cv2.imshow('plane[1]',plane[1]) #그린
cv2.imshow('plane[2]',plane[2]) #레드
cv2.waitKey()

cv2.destroyAllWindows()

 

히스토그램_1

### 히스토그램1

import sys
import numpy as np
import matplotlib.pyplot as plt
import cv2


# 그레이스케일 영상의 히스토그램
src= cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE) #채널이 하나 ->[0]

if src is None:
    print('IMAGE load failed')
    sys.exit()
                         #[0]:gray채널     #bin의 수256      #gray 범위가 0부터256까지
hist = cv2.calcHist([src], [0], mask=None, histSize=[256], ranges=[0,256])
cv2.imshow('src',src)
cv2.waitKey(1) #이 구간에서 1ms 대기 후 아래 코드로 넘어가라는 의미

plt.plot(hist)
plt.show()

# 컬러 영상의 히스토그램
src= cv2.imread('lenna.bmp') 

if src is None:
    print('IMAGE load failed')
    sys.exit()

colors=['b','g','r']
bgr_planes= cv2.split(src) #bgr 각 채널을 따로 분리해서 아래 for문을 돌린다

#세개의 색상성분의 히스토그램을 계산하는데,
#각각을 일차원 그레이스케일 형태로 생각하고 계산하는 것임
for (p,c) in zip(bgr_planes, colors):
    hist= cv2.calcHist([p], [0], None, [256],[0,256])
    plt.plot(hist, color=c) #컬러값을 지정해준다

cv2.imshow('src',src)
cv2.waitKey(1)
plt.show()

 

히스토그램_2

### 히스토그램2

import sys
import numpy as np
import matplotlib.pyplot as plt
import cv2

#cv2를 이용해서 히스토그램을 만드는 함수
def getGrayHistImage(hist):
                    #세로100, 가로256, 밝기 255의  흰 창 
    imgHist = np.full((100, 256), 255, dtype=np.uint8)

    #흰 창에 하나씩 그림을 그리는 것
    histMax = np.max(hist) #세로가 창을 벗어나가는 것을 제한 하기 위함
    print("histMax:",histMax)
    for x in range(256):
        pt1 = (x, 100)      
                            #hist[x, 0]: 높이를 뜻함, 2745가 가장 큰값  100-int(2745*100/2745))=0 
        pt2 = (x, 100 - int(hist[x, 0] * 100 / histMax)) #가장 높은 값이 100이 되도록(공부필요)
        print('hist[x, 0]',hist[x, 0], 'pt2', pt2) # 최고치 hist[x, 0] 2745.0 일때 pt2는 (155, 0) 즉 155픽셀이 2745개가 있다
                                                #x축을 나타내는 pt1=(x,100)이다. 기존 좌표는 (1,0), (2,0)으로 y값이 0으로 고정이 되는데
                                                #이 그래프는 y=100이 0의 역할을 한다 즉(1,0)=(1,100)
                                                #따라서 최대높이인 2745값을 pt2를 이용해 높이를 계산하면 0이 나온다 (가장 높은 높이 0)
                                                #0과100이 반대개념이라 생각하면 된다
        
        cv2.line(imgHist, pt1, pt2, 0) #pt1 부터 pt2까지 그림을 그린다
                                     #pt1이 가로 좌표,pt2가 세로좌표, 이 둘의 좌표를 이은다
                                     #밑바닥에서 시작해서 위로 뻗어가는 직선의 선들을 쭉 생성하면 히스토그램 그림이 된다  

    return imgHist


src = cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

hist = cv2.calcHist([src], [0], None, [256], [0, 256])
histImg = getGrayHistImage(hist)

cv2.imshow('src', src)
cv2.imshow('histImg', histImg)
cv2.waitKey()

cv2.destroyAllWindows()

 

Contrast_1

### 명함비1

import sys
import numpy as np
import cv2


src = cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

alpha=1.0 #값이 커질수록 더 급격한 기울기를 갖는다= 급격한 명도

#np.clip 쓰는이유: sturate용도
#아래 공식은 강의에서 제시한 명함비 공식
#(128,128)을 지나고 y절편이 -128인 직선이다
#장점:영상에서 가장 많이 분포하는 픽셀부분에 명함 부여하기가 좋다
#단점: 아주 밝은 영상이나 어두운 영상의 픽셀은 주로 양끝단에 위치하므로 sturate로 인해 0 또는 255로 고정이된다
#다시말해 아주 밝거나 어두운 영상에는 명함을 주지 못한다
#alpha가 실수형, dst도 실수형으로 계산하기 때문에 마지막에는 astype(np.uint8)로 변경
dst=np.clip((1+alpha)*src-128*alpha,0,255).astype(np.uint8)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

 

Contrast_2

### 명함비2 

import sys
import numpy as np
import cv2

def getGrayHistImage(hist):
    imHist= np.full((100,256), 255, dtype=np.uint8)

    histMax=np.max(hist)
    for x in range(256):
        pt1=(x,100)
        pt2=(x,100-int(hist[x,0]*100/histMax))
        cv2.line(imHist,pt1,pt2,0)
    return imHist

src = cv2.imread('Hawkes.jpg', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

#히스토그램 스트레칭: 명도 자동 조절해준다
                    # None:dst를 None로 해주면된다
                    # 0:alpha, 255:beta
#함수 적용한 경우
# dst= cv2.normalize(src,None,0,255, cv2.NORM_MINMAX)

#numpy로 계산한 경우_공식은 책 참고(p222)
gmin= np.min(src)
gmax= np.max(src)
                    #255.: 실수형으로 계산 후 마무리는 uint8
                    #src: 픽셀값
dst= np.clip((src-gmin)*255./(gmax-gmin),0,255).astype(np.uint8)

#정리1:입력영상의 최소값을 0(알파)이 되게 하고, 최대값을 255(베타)로 만든다. (늘려준다)
#정리2:만일 입력영상의 최소값이 애초에 0이고, 최대값이 애초에 255라면 늘려봤자 그대로 일것이다
#정리3:최소값 픽셀이 만일50이고 최대값이 230이라면 스트레칭의 명함효과를 볼수 있다

# ==============================
#히스토그램 시각화하기
#입력영상에 대한 히스토그램
hist = cv2.calcHist([src], [0], None, [256], [0,256])
histImg=getGrayHistImage(hist)

#명함효과 넣은 영상 히스토그램
hist2 = cv2.calcHist([dst],[0],None,[256],[0,256])
histImg2= getGrayHistImage(hist2)

# ==============================

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.imshow('histImg', histImg)
cv2.imshow('histImg2', histImg2)
cv2.waitKey()

cv2.destroyAllWindows()

 

히스토그램 평활화

### 평활화

import sys
import numpy as np
import cv2


# 그레이스케일 영상의 히스토그램 평활화
src = cv2.imread('Hawkes.jpg', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

'''
dst = cv2.equalizeHist(src)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()
'''
# 컬러 영상의 히스토그램 평활화(348p)
src = cv2.imread('field.bmp')

if src is None:
    print('Image load failed!')
    sys.exit()

#src_ycrcb를 각 채널로 분리후 밝기 정보를 가진 Y만을 평활화 한다
src_ycrcb = cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb)
planes = cv2.split(src_ycrcb) #Y,Cr,Br =>planes는 세개짜리의 영상을 가진 리스트이다 

#Y성분만 평활화진행
planes[0]=cv2.equalizeHist(planes[0])

#YCrCR-> BGR 변환 후 imshow()에 넣는다. imshow는 BGR만 취급한다
dst_ycrcb=cv2.merge(planes) #분리한 채널을 다시 합친다
dst= cv2.cvtColor(dst_ycrcb, cv2.COLOR_YCrCb2BGR)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

 

특정컬러추출_1

### 특색영상추출

import sys
import numpy as np
import cv2


src = cv2.imread('candies.png') #밝은 영상
#src = cv2.imread('candies2.png') #어두운 영상

if src is None:
    print('Image load failed!')
    sys.exit()

#HSV로 변환
src_hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)

                        #그레이 영상이라면 스칼라 하나씩 
                        #(B,G,R)          (B,G,R)
                        #ex)블루 성분은 0~100까지 성분을 고른다
                        #lowerb,          opperb
                        #하한값 상한값 사이의 값만 골라서 dst리턴해줌(0 or 255인 마스트영상이다(이진영상))
dst1 = cv2.inRange(src, (0, 128, 0), (100, 255, 100)) #어두운 영상은 색구별 성능 떨어짐
 
                            #(H,S,V)          (H,S,V)
dst2 = cv2.inRange(src_hsv, (50, 150, 0), (80, 255, 255)) #어두운 영상에도 색 구분 성능 좋음(권장)

cv2.imshow('src', src)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()

cv2.destroyAllWindows()

 

특정컬러추출_1

### 트랙바를 이용한 색상 영역 추출

import sys
import numpy as np
import cv2


src = cv2.imread('candies.png')

if src is None:
    print('Image load failed!')
    sys.exit()

src_hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)

def on_trackbar(pos):
            #getTrackbarPos: dst창에 있는 h_min 트랙바의 위치를 받는 함수
    hmin= cv2.getTrackbarPos('H_min','dst') #H_min 트랙바 값의 위치값(사용자가 지정할수 있음)
            #getTrackbarPos: dst창에 있는 h_max 트랙바의 위치를 받는 함수
    hmax= cv2.getTrackbarPos('H_max','dst') #H_min 내용과 같음

    dst= cv2.inRange(src_hsv, (hmin,150,0),(hmax,255,255))
    cv2.imshow('dst',dst)

cv2.imshow('src', src)
cv2.namedWindow('dst')
                                #50:시작값, 179:트랙바 범위
cv2.createTrackbar('H_min', 'dst', 50, 179, on_trackbar)
cv2.createTrackbar('H_max', 'dst', 80, 179, on_trackbar)
on_trackbar(0)

cv2.waitKey()

cv2.destroyAllWindows()

 

히스토그램 역투영_1

### 히스토그램 역투영

import sys
import numpy as np
import cv2


# 입력 영상에서 ROI를 지정하고, 히스토그램 계산

src = cv2.imread('cropland.png')

if src is None:
    print('Image load failed!')
    sys.exit()

#선택된 영억이 x, y, w, h 사각형 형태의 정보로 저장된다
x, y, w, h = cv2.selectROI(src) #찾고 싶은 색깔 영역을 드래그 & 스페이스바,enter key

#히스토그램 계산(18~30번줄 까지)
src_ycrcb = cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb)
crop = src_ycrcb[y:y+h, x:x+w] #사용자가 선택한 사각형 영역의 부분 영상
                               #x,y는 시작점 좌표

channels = [1, 2] #Ycrcb에서 Y를 쓰지 않는다. 1:Cr, 2:Cb, Y는 밝기정보, 조명에 대한 영향을 무시하기 위해서 Y를 사용X 
cr_bins = 128 #원래는 256써야하지만 단순화 시킴
cb_bins = 128
histSize = [cr_bins, cb_bins]
cr_range = [0, 256] #256이라고 써야 256를 제외한 255까지로 인식한다
cb_range = [0, 256]
ranges = cr_range + cb_range #[0,256,0,256] => Cr1이 0~255,Cb2는 0~255 인식하는 코드 
hist = cv2.calcHist([crop], channels, None, histSize, ranges)#계산된 히스토그램을 사용해서 역투영실시

hist_norm = cv2.normalize(cv2.log(hist+1), None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)

# 입력 영상 전체에 대해 히스토그램 역투영

backproj = cv2.calcBackProject([src_ycrcb], channels, hist, ranges, 1)
dst = cv2.copyTo(src, backproj)

cv2.imshow('backproj', backproj)
cv2.imshow('hist_norm', hist_norm)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

 

히스토그램 역투영_2

### 히스토그램 역투영2

import sys
import numpy as np
import cv2


# CrCb 살색 히스토그램 구하기
ref = cv2.imread('kids1.png', cv2.IMREAD_COLOR)
mask = cv2.imread('kids1_mask.bmp', cv2.IMREAD_GRAYSCALE)

if ref is None or mask is None:
    print('Image load failed!')
    sys.exit()

ref_ycrcb = cv2.cvtColor(ref, cv2.COLOR_BGR2YCrCb)

#Y:0, Cr:1, Cb:2
channels = [1, 2]

#앞의 0,256은 Cr범위, 뒤에 0,256은 Cb
ranges = [0, 256, 0, 256]

#mask에 대해서 히스토그램을 구할때는 mask를 적어 넣고
#입력영상 전체에 대해서 히스토그램을 구할때는 None주면 된다
#구하고자 하는 색상의 mask영상을 먼저 히스토그램으로 계산한 후 
#계산된 히스토그램을 통해서 역투영 한다 
#histSize=[128,128]
hist = cv2.calcHist([ref_ycrcb], channels, mask,[128,128], ranges)#계산된 hist는 이차원 형태(x,y) 
print('hist',hist)

#화면으로 보기위해서 로그 그레이스케일로 변환하는 코드
#log 왜 사용한걸까: 히스토그램에서 가장 큰 값을 가지는 것들은 다른 것에 비해 너무 큰 값이 나오면
#정규화 한 후에는 큰 거 몇개의 픽셀만 돋보이고 나머지는 0에 가까운 검정색이 된다
#즉, 비율을 맞춰주기 위해서
#hist값이 0이 나올수 있으므로 +1을 해줌으로써 최소값이 0이 되게끔 설정한다
hist_norm= cv2.normalize(cv2.log(hist+1), None,0,255,cv2.NORM_MINMAX,cv2.CV_8U)

# 입력 영상에 히스토그램 역투영 적용
src = cv2.imread('kids2.png', cv2.IMREAD_COLOR) #역투영 할 이미지

if src is None:
    print('Image load failed!')   
    sys.exit()

src_ycrcb = cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb)

#역투영
#backproj는 그레이스케일이기 때문에 타입변경없이 바로 imshow하면된다  
backproj= cv2.calcBackProject([src_ycrcb], channels, hist, ranges,1)


cv2.imshow('src', src)
cv2.imshow('hist_norm', hist_norm) #단지 히스토그램을 보기위해서 hist_norm을 구함
cv2.imshow('backproj', backproj)
cv2.waitKey()
cv2.destroyAllWindows()

 

크로마키 합성

### 크로마 키 합성

import sys
import numpy as np
import cv2


# 녹색 배경 동영상
cap1 = cv2.VideoCapture('woman.mp4')

if not cap1.isOpened():
    print('video open failed!')
    sys.exit()

# 비오는 배경 동영상
cap2 = cv2.VideoCapture('raining.mp4')

if not cap2.isOpened():
    print('video open failed!')
    sys.exit()

# 두 동영상의 크기, FPS는 같다고 가정
w = round(cap1.get(cv2.CAP_PROP_FRAME_WIDTH))
h = round(cap1.get(cv2.CAP_PROP_FRAME_HEIGHT))
frame_cnt1 = round(cap1.get(cv2.CAP_PROP_FRAME_COUNT))
frame_cnt2 = round(cap2.get(cv2.CAP_PROP_FRAME_COUNT))
print('w x h: {} x {}'.format(w,h))
print('frame_cnt1:', frame_cnt1)
print('frame_cnt2:', frame_cnt2)

fps = cap1.get(cv2.CAP_PROP_FPS)
delay = int(1000 / fps)

#출력 동영상 객체 생성
# fourcc= cv2.VideoWriter_fourcee(*'DIVX')
# out = cv2.VideoWriter('output.avi', fourcc, fps, (w,h))

# 합성 여부 플래그
#경우에 따라 원본을 보여줄지, 합성한 영상을 보여줄지를 결정하는 변수
#True이면 합성영상 on 
do_composit = False

# 전체 동영상 재생
while True:
    ret1, frame1 = cap1.read() #woman 영상

    if not ret1: #맨 마지막 까지가게 되면 불러올 프레임이 없기 때문에
        break      #break가 걸린다.  ret1=False, frame1=None 값이 저장된다
    
    # do_composit 플래그가 True일 때에만 합성
    if do_composit:
        ret2, frame2 = cap2.read()

        if not ret2:
            break
        
        #만일사이즈가 다를경우 resize를 해준다
        # frame2= cv2.resize(frame2, (w,h)) #w,h는 cap1의 크기

        # HSV 색 공간에서 녹색 영역을 검출하여 합성
        hsv = cv2.cvtColor(frame1, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, (50, 150, 0), (70, 255, 255))
        
        #두번째 영상에서 마스크가 흰색으로 되어 있는 부분만 frame1쪽으로 복사한다
        cv2.copyTo(frame2, mask, frame1)

    # out.write(frame1) #저장
    cv2.imshow('frame', frame1)
    key = cv2.waitKey(delay) #41ms를 기다리고 다음영상을 받아온다
 
    # 스페이스바를 누르면 do_composit 플래그를 변경
    if key == ord(' '):
        do_composit = not do_composit
    elif key == 27:
        break

cap1.release()
cap2.release()
# out.release()
cv2.destroyAllWindows()

 

 

 

 

 

 

728x90
728x90
  • 강의 수강하면서 실습한 내용을 업로드합니다.
  • 필기 내용은 코드와 함께 주석으로 입력했습니다.
  • 한 실습 당 여러 방법이 섞여 있어서 주석처리를 예의주시 필요
  • 실습 날짜: 2021-02-17, OpenCV 첫시작

영상 기본 조작 

import cv2
import sys

#특정 키를 가지고 창을 끌때

print('Hellow, opencv', cv2.__version__)

# img= cv2.imread('cat.bmp') #img: image를 뜻함
img = cv2.imread('cat.bmp', cv2.IMREAD_GRAYSCALE) #BGR이미지를 gray로 열고 싶을때는 cv2.IMREAD_GRAYSCALE 삽입하기

#예외처리
if img is None: #영상 파일 불러오지 못했을때 실행되는 코드,
    print('Image load failed!')
    sys.exit() 
cv2.imwrite('cat_gray.png', img) #컬러 이미지를 gray로 저장하기 

cv2.namedWindow('image') #창을 하나 생성해주는 함수, 창 이름을 image 지정 
                        #띄울 이미지가 크다면 이 함수를 적고 flags를 normal로 지정해서 창사이즈 변경가능하도록 설정
                        #이미지가 작으면 생략해도 cv2.imread가 알아서 창을 만들어준다(auto창, 사이즈 변경 불가)

cv2.imshow('image',img) #창에 영상을 보여주는 함수, 첫번째 인자는 띄울 창 이름, 두번째 인자는 띄울 이미지 , 그리고 키보드 입력 있을때 까지 대기

#특정 키를 눌렀을때 종료하도록 하는 코드, 아스킷을 이용
# while True:
#     if cv2.waitKey()==27: #esc키의 아스킷 숫자 27
#         break


#아스킷이 아닌 키보드알파벳을 이용할때
while True:
    if cv2.waitKey()==ord('q'): #특정 알파벳을 눌러서 종료하고 싶을때는 ord() 안에 해당 알파벳 넣기
        break

# key=cv2.waitKey(2000) #영상이 실제로 나올수 있게끔 하는 함수, 2000-> 2초
# print(key)

cv2.destroyAllWindows() #기존에 띄운 창을 모두 닫은 함수

 

여러 이미지로 슬라이드 쇼 생성하기

#슬라이드 쇼 만들기

import sys, os
import glob
import cv2

#문자열의 리스트 형태로 저장
img_files= glob.glob('.\\images\\*.jpg') #images안에 .jpg파일을 문자열로 모두 가져온다

for f in img_files:
    print(f) #.\images\autumn_forest_park_128379_1920x1080.jpg  형태

#전체창 만들기setWindowProperty(), window normal로 만들어야 전체화면이 가능하다
cv2.namedWindow('image',cv2.WINDOW_NORMAL) #WINDOW_NORMAL속성의 창을 생성 후
cv2.setWindowProperty('image', cv2.WND_PROP_FULLSCREEN,  #setWindowProperty를 통해 전체화면 속성으로 변경
                                cv2.WINDOW_FULLSCREEN)

cnt= len(img_files)
idx=0

while True:
    img = cv2.imread(img_files[idx])

    if img is None:
        print('Image load failed!')
        break

    cv2.imshow('image',img)

    if cv2.waitKey(1000)==27: #ESC
        break

    # if cv2.waitKey(1000)==0: #키보드 아무 값을 쳐도 0보다는 큼 즉 아무키 눌렀을때 꺼진다
                              #아무것도 안누르면 -1을 리턴
    #     break

    idx +=1

    if idx>=cnt: 
        idx=0

cv2.destroyAllWindows()






 

Matplot으로 영상 load

import matplotlib.pyplot as plt
import cv2


# 컬러 영상 출력
imgBGR = cv2.imread('cat.bmp')

#matplotlib은 RGB순서를 기대하기 때문에 
#opencv BGR을 cvtColor통해서 RGB로 순서 변경 후 matplotlib에 적용 
imgRGB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2RGB)

plt.axis('off') #눈금 제거
plt.imshow(imgRGB)
plt.show()

# 그레이스케일 영상 출력
#2차원 형태로 되어 있다고 보면된다. 밝기 값만 있다
imgGray = cv2.imread('cat.bmp', cv2.IMREAD_GRAYSCALE)

plt.axis('off')
plt.imshow(imgGray, cmap='gray')
plt.show()

# 두 개의 영상을 함께 출력
plt.subplot(121), plt.axis('off'), plt.imshow(imgRGB) #컬러버전 
plt.subplot(122), plt.axis('off'), plt.imshow(imgGray, cmap='gray') #gray버전
plt.show()

 

영상불러오기 & 픽셀 값 변경 

import sys
import cv2


# 영상 불러오기
img1 = cv2.imread('cat.bmp', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('cat.bmp', cv2.IMREAD_COLOR)

if img1 is None or img2 is None:
    print('Image load failed!')
    sys.exit()

#영상의 크기를 참조하는 방법

#그레이스케일
h,w = img1.shape
print('w x h= {} x {}'.format(w,h)) 

#컬러-> 에러뜸  왜? (w,h) 크기의 3개 이미지를 두개 변수로 받을려고 했기 때문에
# h,w = img2.shape
# print('w x h= {} x {}'.format(w,h))

#해결방법-> (w,h,c) 에서 w,h만 가져오도록 [:2] 적용
h,w = img2.shape[:2] #그레이스케일에 사용해도 무방
print('w x h= {} x {}'.format(w,h))


#영상의 픽셀 값을 참조하는 방법
#영상의 (20,10)좌표의 픽셀 값을 알고 싶을때
x=20
y=10 
p1= img1[y,x]
print(p1) #grayscale 픽셀값이 238(밝은 편)

p2= img2[y,x]
print(p2) # [237 242 232] 블루,그린,레드 순

#이를 이용해서 특정 픽셀값에 내가 원하는 값을 대입 할수있다
'''
for y in range(h):  #각 픽셀을 for문으로 돌아다니면서 아래 조건으로 픽셀값을 바꾸는 코드-> 느림
    for x in range(w):
        img1[y,x]=0 #검정색
        img2[y,x]=(0,255,255) #노란색
'''
#위코드는 사용 지양(느림),아래코드 추천
img1[:, :] = 0
img2[:, :] = (0,255,255)

cv2.imshow('img1', img1)
cv2.imshow('img2', img2)
# cv2.waitKey()

cv2.destroyAllWindows()

 

부분영상 추출 & 복사

import numpy as np
import cv2

# 새 영상 생성하기
img1 = cv2.imread('HappyFish.jpg') #물고기 그림
img2=img1[50:160, 150:300]
img3=img1[50:160, 150:300].copy()

#(105,225) 좌표에, 반지름 20, 컬러는 (0,0,255), 두께2
                 #(x, y) 
#이미지를 짜른 순간 좌표는 짤려진 이미지 좌측 상단이 0,0으로 초기화                               
cv2.circle(img2, (50,50), 20, (0,0,255),2) 

cv2.imshow('img1', img1)
cv2.imshow('img2', img2)
# cv2.imshow('img3', img3)
cv2.waitKey()
cv2.destroyAllWindows()

 

마스크 연산 & ROI

import sys
import cv2

# 마스크 영상을 이용한 영상 합성
# src = cv2.imread('airplane.bmp', cv2.IMREAD_COLOR)
# mask = cv2.imread('mask_plane.bmp', cv2.IMREAD_GRAYSCALE)
src =cv2.imread('opencv-logo-white.png', cv2.IMREAD_UNCHANGED)

#알파와 BGR 구성
mask =src[:, :, -1] #투명한 알파 채널
src= src[:, :, 0:3] #BGR만 따로
dst = cv2.imread('field.bmp', cv2.IMREAD_COLOR)

h,w = src.shape[:2]
crob= dst[0:h,0:w] #직접 dst 사진의 좌표를 지정해서 위치를 알려줘야 한다

#CV2를 이용한 방법
# cv2.copyTo(src, mask,dst) #crob을 넣지않고 왼쪽 코드처럼 실행하면 로고와 dst 크기가 맞지 않아서 합성이 안됨
cv2.copyTo(src, mask,crob)

#numpy로 이용한 방법
# dst[mask>0] = src[mask>0] #마스크가 0이상인 부분에 True값이 반환 될것이고 그 반환된 
                            #그 자리 그대로 src자리에 대입되면, 비행기가 싹뚝 뽑혀서 dst의 True위치에 비행기를 넣는다   

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.imshow('mask', mask)
cv2.waitKey()
cv2.destroyAllWindows()


그리기 함수

import numpy as np
import cv2

img = np.full((400, 400, 3), 255, np.uint8)

            #(가로,세로)(가로,세로)
cv2.line(img, (50, 50), (200, 50), (0, 0, 255), 5)
cv2.line(img, (50, 60), (150, 160), (0, 0, 128))

#직사각형은 두가지 방법으로 그릴수 있다

                #50,200 좌측상단 좌표, 150:가로, 100:세로
cv2.rectangle(img, (50,200,150,100), (0,0,225),2)

                #좌측상단좌표,우측하단좌표,color지정,두께
cv2.rectangle(img, (70,220),(180,280),(0,128,0),-1) #두께 음수는 내부를 채운다

#cv2.LINE_8로 원을 그리면 거친 느낌의 원을 만든다
#cv2.LINE_AA를 넣어주면 반듯한 원이 만들어진다 
#사각형은 cv2.LINE_AA 사용 안해도 되지만 원이나, 문자열 같은걸 이용할때는 애용하는게 좋다. 거친면이 사라진다                                               
cv2.circle(img, (300,100), 30,(255,255,0), -1, cv2.LINE_AA)
cv2.circle(img, (300,100),60,(255,0,0), 3, cv2.LINE_AA)

#다각형의 꼭지점을 넘파이 array로 만든다
pts = np.array([[250,200],[300,200],[350,300],[250,300]]) #이걸 넣을때 리스트 형태로 감싸서 넣어야한다

                        #True를 주면 시작점과 끝점을 이어준다(폐곡선)
cv2.polylines(img, [pts], True, (250,0,255),2)

#문자열 넣기
text= 'Hello? OpenCV'+cv2.__version__

                      #(x,y)인데 좌측하단 기준임
cv2.putText(img, text, (50,350), cv2.FONT_HERSHEY_SIMPLEX,0.8, #폰트설정
(0,0,255), 1, cv2.LINE_AA)

cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

 

카메라와 동영상 처리1

==================카메라 실습=========================
cap = cv2.VideoCapture()
# cap.open(0) #기본 카메라를 open하겠다는 의미
# cap = cv2.VideoCapture(0) #위와 같은 결과

# if not cap.isOpened(): #카메라 제대로 열렸는지 확인
#     print('camera open failed')
#     sys.exit()

# #카메라의 가로,세로를 가져오는 방법
# w= int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
# h= int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# print(w, h)

# # #카메라 크기를 변경하는 방법- 내 컴퓨터에는 지원 안되는듯
# # cap.set(cv2.CAP_PROP_FRAME_WIDTH),320)
# # cap.set(cv2.CAP_PROP_FRAME_HEIGHT),240)
# # print(w, h)


# #한 프레임씩 계속 받아오는 코드 생성
# while True:
#     #프레임을 받아오는 함수 는 cap.read()
#     #read()가 리턴하는 것이 두가지 : ret(불리언), image
#     #불리언의 True: 사진이 제대로 받은경우
#     #즉 read()는 불리언과 ndarray(이미지) 두가지를 준다
#     ret, frame = cap.read() #현재 열려 있는 카메라 디바이스에서 한 프레임씩 받아오는 작업을 한다

#     if not ret: #True가 아니면
#         break

#     #이 부분에는 가져온 정지 영상을 처리하는 코드를 작성 할수 있다
#     #윤곽선 추출
#     edge= cv2.Canny(frame,50,150)

#     cv2.imshow('edge',edge)
#     cv2.imshow('frame',frame) #'frame'의 창은 굳이 만들지 않아도 알아서 만든다(특이 경우는 예외)
#     if cv2.waitKey(20)==27: #20ms를 기다리고 꺼진다 하지만 여기에 ESC를 누르면 while문을 빠져나온다
#         break

# cap.release() #open했던 cap을 해체 시킨다
# cv2.destroyAllWindows()


# ======================동영상 실습==============================
import sys
import cv2


# 카메라 열기
cap = cv2.VideoCapture('video1.mp4')

if not cap.isOpened():
    print("video open failed!")
    sys.exit()

# 카메라 프레임 크기 출력
print('Frame width:', int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)))
print('Frame height:', int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))

# 카메라 프레임 처리
while True:
    ret, frame = cap.read()

    if not ret:
        break
    edge= cv2.Canny(frame,50,150)


    # inversed = ~frame  # 반전
    cv2.imshow('edge',edge)
    cv2.imshow('frame', frame)
    # cv2.imshow('inversed', inversed)

    if cv2.waitKey(20) == 27:
        break

cap.release()
cv2.destroyAllWindows()

 

카메라와 동영상 처리2

import sys
import cv2


cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print("Camera open failed!")
    sys.exit()

w = round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) #float형태로 나오기 때문에 정수로 만들기 위해서 
h = round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS) 

#DIVX를 이용해서 정수값을 리턴해준다 fourcc : 압축 방식
fourcc = cv2.VideoWriter_fourcc(*'DIVX') # *'DIVX' == 'D', 'I', 'V', 'X'
delay = round(1000 / fps) #프레임간의 시간간격

out = cv2.VideoWriter('output.avi', fourcc, fps, (w, h)) #컬러영상 저장

if not out.isOpened():
    print('File open failed!')
    cap.release()
    sys.exit()

while True:
    ret, frame = cap.read()

    if not ret:
        break

    # inversed = ~frame
    edge= cv2.Canny(frame, 50,150) #edge는 그레이라서 컬러로 바꾼다음에 저장해야 19번 코드와 호환됨
    edge_color= cv2.cvtColor(edge, cv2.COLOR_GRAY2BGR)
    
    #out.write(frame) #소리는 x, 영상만 저장됨
    out.write(edge_color)

    cv2.imshow('frame', frame)
    cv2.imshow('edge', edge)
    cv2.imshow('edge_color', edge_color)
    # cv2.imshow('inversed', inversed)

    if cv2.waitKey(delay) == 27:
        break

cap.release()
out.release()
cv2.destroyAllWindows()

 

728x90
728x90

참고서적-파이썬 머신러닝 가이드

저번 블로그의 XGBoost, LightGBM 실습에 이어서 신용카드 사기 검출 실습을 하면서 공부를 해봤습니다~!

 

1. Import

import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
os.chdir('./Data/creditcard')
credit_df=pd.read_csv('creditcard.csv')

#의미없는 Time 변수 제거
del credit_df['Time']

 

2. 데이터 분리

#데이터프레임 복사후 데이터 가공처리
def get_train_test_split(df=None):
    #인자로 입력된 DataFrame의 사전 데이터 가공(Time 변수 삭제)이 완료된 복사 DataFrame 반환
    df_copy=credit_df
    
    #DataFrame의 맨 마지막 칼럼이 레이블, 나머지는 피처들
    x_features=df_copy.iloc[:,:-1]
    y_target=df_copy.iloc[:,-1]
    
    #데이터 분리, stratify 기반으로 분리-train,test 비율 동일하게
    from sklearn.model_selection import train_test_split
    x_train,x_test,y_train,y_test= train_test_split(x_features,y_target, test_size=0.3, random_state=0,stratify=y_target)
    
    #데이터 반환 
    return x_train,x_test,y_train,y_test
    
x_train,x_test,y_train,y_test=get_train_test_split(credit_df)

Time 변수를 제거 후에 feature와 label을 분리하는데 분리 시 샘플링 방식은 나뉟stratify로 해줍니다.

stratify 값을 y_target으로 지정해주면 각각의 class 비율(ratio)을 train / validation에 유지해 줍니다. (한 쪽에 쏠려서 분배되는 것을 방지합니다) 

 

# 레이블 값이 비율을 백분율로 환산하여 서로 비슷하게 분활됐는지 확인
print('학습 데이터 레이블 값 비율')
print(y_train.value_counts()/y_train.shape[0]*100)
print('테스트 데이터 레이블 값 비율')
print(y_test.value_counts()/y_test.shape[0]*100)

학습 데이터 레이블 값 비율
0    99.827451
1     0.172549
Name: Class, dtype: float64
테스트 데이터 레이블 값 비율
0    99.826785
1     0.173215
Name: Class, dtype: float64

 

3. 로지스틱 회귀 VS LightGBM 성능 비교

def get_clf_eval(y_test,pred,pred_proba):
    from sklearn.metrics import f1_score, accuracy_score,precision_score,recall_score,confusion_matrix, roc_auc_score
    confusion = confusion_matrix(y_test, pred)
    accuracy= accuracy_score(y_test, pred)
    precision=precision_score(y_test, pred)
    recall= recall_score(y_test, pred)
    #F1스코어 추가
    f1 =f1_score(y_test,pred)
    print('오차 행렬')
    print(confusion)
    #ROC-AUC 
    roc_auc=roc_auc_score(y_test,pred_proba)
    print('정확도 {0:.4f}, 정밀도 {1:.4f}, 재현율 {2:.4f}, F1:{3:.4f}, AUC:{4:.4f}'.format(accuracy,precision,recall,f1,roc_auc))

성능 평가에 사용되는 함수를 먼저 만들어줍니다.

 

1. 정밀도 = TP/(FP+TP) - 예측을 기준으로 

FP+TP : 예측을 positive로 한 전체 데이터의 수 

TP: 예측과 실제 값이 positive로 맞춘 데이터의 수

-예측을 positive로 한 것들 중에 예측과 실제의 값이 positive로 일치한 데이터의 비율을 말합니다

-일반 메일을 스팸메일이라고 잘못 분류하는 경우에는 재현율 보다 정밀도를 우선 시 합니다.

2. 재현율= TP/(FN+TP) - 실제 값을 기준으로 

FN+TP : 실제 값이 positive인 데이터의 수

- 실제 값이 positive인 것들 중에  예측과 실제의 값이 positive로 일치한 데이터의 비율을 말합니다

  민감도 또는 TPR(true positive Rate) 이라고도 불립니다

- 특히 재현율은 암 판단 모델을 평가할 때입니다. 실제로 양성인 환자를 음성으로 잘못 판단 내리면

  치명적이기 때문에 재현율 평가가 중요합니다

 

위 내용을 정리를 하다 보니까 추가적인 설명이 필요할 것 같아서 제 생각을 적어봤습니다

-정밀도와 재현율의 차이는  분모가 FT 냐 FN 이냐의 차이입니다. 

정밀도의 FT은 False인 값을 True로 예측을 한 것이고

재현율 FN은 False을 올바르게 Negative로 예측했느냐 차이인 것 같습니다

 

위 예시처럼 재현율의 암 판단은 양성을 양성이라고 제대로 판단해야 하기 때문에 FN이 중요하고

정밀도는 실제로 양성이 아닌, 메일로 따지면 스팸메일이 아닌 정상메일의 데이터 예측을 positive 양성으로

예측해서 스팸메일로 분류하는 경우입니다. 

 

3. AUC= ROC 곡선 면적 밑의 면적

AUC 스코어는 이진 분류의 예측 성능 지표입니다.  

간단히 적어보면, ROC 곡선은 FPR (x축)의 변함에 따라 TRP(y축)의 곡선 형태를 보여줍니다.

FPR(False positive Rate) = FP / (FP+TN) = 1-TNR = 1- 특이성

TRP(True positive Rate) = TP / (FN+TP) = 재현율이며 민감 도라고 불리기도 합니다.

TNR(True Negative Rate) = TRP에 대응하는 지표로 특이성이라 실제 값이 음성(Negative)을 정확히 예측해야

수준을 나타냅니다 (코로나 음성인 사람은 음성으로 정확히 예측해야 한다)

반대로, 코로나 양성인 사람을 양성으로 정확히 예측해야 하는 건 당연히 TRP, 재현율 일 겁니다 

 

그러면 FPR은 1-특이성이므로 음성인 사람을 양성이라고 예측한 값일 겁니다

ROC는 이 FRP을 0~1까지 변경하면서 TRP의 변화 값을 구하여 곡선으로 나타냅니다.

 

 

로지스틱 회귀

from sklearn.linear_model import LogisticRegression

lr_clf= LogisticRegression()
lr_clf.fit(x_train,y_train)
lr_pred=lr_clf.predict(x_test)
lr_pred_proba=lr_clf.predict_proba(x_test)[:,1]

get_clf_eval(y_test,pred,lr_pred_proba)

오차 행렬
[[85281    14]
 [   57    91]]
정확도 0.9992, 정밀도 0.8667, 재현율 0.6149, F1:0.7194, AUC:0.9564

 

로지스틱 실행결과 재현율이 0.61로 다른 평가지표에 비해 낮아 보입니다.  이번엔 LightGBM 모델 결과를 보기 앞서서

get_model_train_eval 함수를 만들어서 학습 및 평가를 간단히 하겠습니다

 

# 함수로 만들기
def get_model_train_eval(model, ftr_train=None,ftr_test=None, tar_train=None,tar_test=None):
    model.fit(x_train,y_train)
    pred=model.predict(x_test)
    pred_proba=model.predict_proba(x_test)[:,1]
    get_clf_eval(tar_test,pred,pred_proba)

LightGBM

 

from lightgbm import LGBMClassifier

lgbm_clf = LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1, boost_from_average=False)
get_model_train_eval(lgbm_clf,  ftr_train=x_train,ftr_test=y_train, tar_train=x_test,tar_test=y_test)

오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9790

로지스틱 모델보다 전반적인 성능이 개선되었습니다.

amount 변수의 분포를 확인 후, 값이 한쪽으로 치우쳐 있다면 scaling을 진행하여 다시 예측해보겠습니다

 

데이터 분포도 변환 후 모델 학습/예측/평가

import seaborn as sns
plt.figure(figsize=(8,4))
plt.xticks(range(0,30000,1000), rotation=60)
#히시토그램 시각화
sns.distplot(credit_df['Amount'])

# 1000불 이하가 대부분이고, 2700불은 드물지만 존재함, 이를 stardardScaler로 표준 정규 분포 형태로 변환 후 예측성능 비교실시

전처리 작업으로 scaling 하는 함수를 만듭니다

def get_preprocessed_df(df=None):
    from sklearn.preprocessing import StandardScaler
    df_copy=credit_df
    scaler=StandardScaler()
    amount_n= scaler.fit_transform(df_copy['Amount'].values.reshape(-1,1))
    
    #변환된 amount를 Amount_scaled로 이름 변경 후, DataFrame 맨 앞 컬럼으로 입력
    df_copy.insert(0, "Amount_scaled",amount_n)
    
    #기존 Time, amount 제거
    df_copy.drop(["Amount"], axis=1, inplace=True)
    return df_copy

scaling은 평균 0, 분산 1을 만드는 StandardScaler를 사용합니다.

스케일링 후 로지스틱, LightGBM을 다시 실행해 봅니다

 

#amount를 정규 분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행
x_train,x_test,y_train,y_test = get_train_test_split(df_copy)

print("### 로지스틱 회귀 예측 성능 ###")
lr_clf= LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

print("### lightGBM 예측 성능 ###")
lightgbm_clf=LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1,boost_from_average=False)
get_model_train_eval(lightgbm_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85282    13]
 [   56    92]]
정확도 0.9992, 정밀도 0.8762, 재현율 0.6216, F1:0.7273, AUC:0.9647

### lightGBM 예측 성능 ###
오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.981

 

스케일링 전후 차이가 미비했습니다(교재와는 다른 결과를 보였습니다.

amount를 log을 취해서 정규분포와 근사하게 만든 후 다시 모델을 실행해 보겠습니다.

 

로지스틱 & LightGBM(amount log 변환 件)

def get_preprocessed_df(df=None):
    df_copy=df.copy()
    
    #넘파이의 log1p()를 이용해 amount를 로그 변환
    amount_n= np.log1p(df_copy['Amount'])
    df_copy.insert(0, "Amount_Scaled", amount_n)
    df_copy.drop(["Amount"], axis=1, inplace=True)
    return df_copy

df_copy=get_preprocessed_df(credit_df)

#amount를 정규 분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행
x_train,x_test,y_train,y_test = get_train_test_split(df_copy)

print("### 로지스틱 회귀 예측 성능 ###")
lr_clf= LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

print("### lightGBM 예측 성능 ###")
lightgbm_clf=LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1,boost_from_average=False)
get_model_train_eval(lightgbm_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85282    13]
 [   56    92]]
정확도 0.9992, 정밀도 0.8762, 재현율 0.6216, F1:0.7273, AUC:0.9647

### lightGBM 예측 성능 ###
오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9812

amount의 log변환도 같은 위 결과와 같은 성능을 보였습니다.

개선이 미비한 원인 중 이상치의 존재일 가능성이 있으므로 이를 제거 후 성능 비교를 다시 해봅니다.

 

이상치 제거 후 성능 비교

-IQR로 이상치를 파악하기 전에 먼저 corr()를 이용해서 레이블의 중요 피처를 구별하여 해당되는 것들만 제거합니다.

# 상관도
import seaborn as sns

try:
    del credit_df['Amount_scaled']
except:
    pass
plt.figure(figsize=(16,16))
corr=credit_df.corr()
sns.heatmap(corr, cmap="RdBu")

레이블인 class와 상관관계가 높은 것은 v14,17인데, 이 중에서 v14 만 이상치 제거 실시합니다

 

import numpy as np

#이상치 인덱스를 반환하는 함수 생성
def get_outlier(df=None, column=None, weight=1.5):
    #fraud에 해당하는 column 데이터만 추출, 1/4 분위와 3/4 분위 지점을 np.percentile로 구함
    fraud = df[df['Class']==1][column]
    quantile_25 = np.percentile(fraud.values, 25)
    quantile_75 = np.percentile(fraud.values, 75)
    
    #IQR을 구하고. IQR에 1.5를 곱해 최댓값과 최솟값 지점 구함
    iqr= quantile_75-quantile_25
    iqr_weight=iqr*weight
    lowest_val=quantile_25-iqr_weight
    highest_val=quantile_75+iqr_weight
    
    #최대값보다 크거나, 최솟값보다 작은 값을 이상치 데이터로 설정하고 DataFram3 index 반환
    outlier_index = fraud[(fraud< lowest_val) | (fraud> highest_val)].index
    return outlier_index

 get_outlier 함수를 통해 V14칼럼의 이상치를 제거해줍니다

outlier_index=get_outlier(df=credit_df, column='V14', weight=1.5)
print('이상치 데이터 인덱스:', outlier_index)

이상치 데이터 인덱스: Int64Index([8296, 8615, 9035, 9252], dtype='int64')
def get_preprocessed_df(df=None):
    df_copy=df.copy()
    
    #넘파이의 log1p()를 이용해 amount를 로그 변환
    amount_n= np.log1p(df_copy['Amount'])
    df_copy.insert(0, "Amount_Scaled", amount_n)
    try:
        df_copy.drop(["Time","Amount"], axis=1, inplace=True)
    except:
        df_copy.drop(["Amount"], axis=1, inplace=True)  
    
    #이상치 제거하는 로직 추가
    outlier_index=get_outlier(df=df, column='V14', weight=1.5)
    df_copy.drop(outlier_index, axis=0, inplace=True)
    return df_copy

 

이상치를 제거한 로지스틱 VS LightGBM 성능 비교 

#amount를 정규 분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행
df_copy=get_preprocessed_df(credit_df)
x_train,x_test,y_train,y_test = get_train_test_split(df_copy)

print("### 로지스틱 회귀 예측 성능 ###")
lr_clf= LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)
print('\n')

print("### lightGBM 예측 성능 ###")
lightgbm_clf=LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1,boost_from_average=False)
get_model_train_eval(lightgbm_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)

### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85281    14]
 [   57    91]]
정확도 0.9992, 정밀도 0.8667, 재현율 0.6149, F1:0.7194, AUC:0.9564

### lightGBM 예측 성능 ###
오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9790

이상치 제거 후 성능 비교를 해본 결과 눈에 띄는 개선 점은 없었습니다.

이번엔 smote(오버 샘플링)을 적용하여 모델 성능 비교를 해보겠습니다.

 

from imblearn.over_sampling import SMOTE

#smote는 train 데이터에만 적용한다
smote= SMOTE(random_state=0)
x_train_over,y_train_over= smote.fit_sample(x_train,y_train)
print("SMOTE 적용 전 학습용 피처/레이블 데이터 세트:", x_train.shape, y_train.shape)
print("SMOTE 적용 후 학습용 피처/레이블 데이터 세트:", x_train_over.shape, y_train_over.shape)
print("SMOTE 적용 후 레이블 값 분포: \n", pd.Series(y_train_over).value_counts())

SMOTE 적용 전 학습용 피처/레이블 데이터 세트: (199364, 29) (199364,)
SMOTE 적용 후 학습용 피처/레이블 데이터 세트: (398040, 29) (398040,)
SMOTE 적용 후 레이블 값 분포: 
 1    199020
0    199020
Name: Class, dtype: int64

smote는 학습 데이터 세트에만 적용시켜야 합니다. smote을 적용하여 199364건->398040건으로 2배 가까이 증가했습니다. 이 데이터를 기반으로 모델을 실행하겠습니다.

 

#로지스틱
lr_clf=LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train_over,ftr_test=x_test, tar_train=y_train_over,tar_test=y_test)

오차 행렬
[[85281    14]
 [   57    91]]
정확도 0.9992, 정밀도 0.8667, 재현율 0.6149, F1:0.7194, AUC:0.9564
#LightGBM
lgbm_clf =LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1, boost_from_average=False)
get_model_train_eval(lgbm_clf, ftr_train=x_train_over,ftr_test=x_test, tar_train=y_train_over,tar_test=y_test)

오차 행렬
[[85290     5]
 [   36   112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9790

smote 적용 후, 성능 결과를 보면 이전과 동일하거나 미비한 변화를 보입니다

서적에는 모델마다 변화량이 조금씩 달랐는데 제가 실습하는 동안의 성능 비교는 비슷하거나 변화가 없었습니다.

728x90
728x90

Voting classifier

import pandas as pd
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import seaborn as sns
import matplotlib.pyplot as plt

#데이터 불러오기
cancer = load_breast_cancer()

data_df= pd.DataFrame(cancer.data, columns=cancer.feature_names)
data_df.head()

* voting의 인자는 estimators, voting 
* estimators : 개별 분류기를 튜플로 담아 넣는다 
* voting : hard or soft  (defalt hard)

# 개별 분류기 객체 생성
lr_clf= LogisticRegression()
knn_clf=KNeighborsClassifier(n_neighbors=8)

#개별 모델을 앙상블 모델로 구현
#soft : 각 분류기에서 출력된 레이블의 확률을 평균 낸것들 가장 높은걸 최종결과값으로 선정
vo_clf =VotingClassifier(estimators=[("LR",lr_clf), ('KNN', knn_clf)], voting='soft')

x_train,x_test,y_train,y_test = train_test_split(cancer.data, cancer.target, test_size=0.2, random_state=156)

#votinclassifier 학습/예측/평가
vo_clf.fit(x_train, y_train)
pred=vo_clf.predict(x_test)
print('voting 분류기 정확도: {0:.4f}'.format(accuracy_score(y_test,pred)))

#개별 모델의 학습/예측/평가
classifiers= [lr_clf, knn_clf]
for classifier in classifiers:
  classifier.fit(x_train,y_train)
  pred=classifier.predict(x_test)
  class_name= classifier.__class__.__name__
  print('{0} 정확도: {1:.4f}'.format(class_name, accuracy_score(y_test,pred)))


랜덤포레스트

* 중첩되는 데이터 세트를 생성 (부트스트래핑)
* 부트스트랩: 통계학에서는 데이터 세트를 임의로 만들어서 개별 평균의 분포도를 측정하는 목적을 위한 샘플링 방식을 지칭
from sklearn.ensemble import RandomForestClassifier 

#데이터 분리
x_train,x_test,y_train,y_test = train_test_split(cancer.data, cancer.target, test_size=0.2, random_state=156)

#랜덤 포레스트 학습 및 별도의 테스트 세트로 예측 성능 평가
rf_clf=RandomForestClassifier(random_state=0)
rf_clf.fit(x_train,y_train)
pred=rf_clf.predict(x_test)
accuracy = accuracy_score(y_test,pred)
print('랜덤 포레스트 정확도:{0:.4f}'.format(accuracy))

랜덤포레스트(하이퍼 파라미터 및 튜닝)

from sklearn.model_selection import GridSearchCV

params={
    'n_estimators': [100],
    'max_depth':[6,8,10,12],
    'min_samples_leaf':[8,12,18],
    'min_samples_split':[8,16,20]
}
rf_clf = RandomForestClassifier(random_state=0, n_jobs=-1)
grid_cv= GridSearchCV(rf_clf, param_grid=params, cv=2, n_jobs=-1)
grid_cv.fit(x_train,y_train)

print('최적 하이퍼 파라미터: \n', grid_cv.best_params_)
print('최고 예측 정확도: {0:.4f}'.format(grid_cv.best_score_))


최적 하이퍼 파라미터: 
 {'max_depth': 6, 'min_samples_leaf': 8, 'min_samples_split': 8, 'n_estimators': 100}
최고 예측 정확도: 0.9451
new_rf_clf= RandomForestClassifier(random_state=0 , n_estimators=300, max_depth=6, min_samples_split=8, min_samples_leaf=8)
new_rf_clf.fit(x_train,y_train)
pred=new_rf_clf.predict(x_test)
accuracy=accuracy_score(y_test, pred)
print("램던 포레트스 정확도:{0:.4f}".format(accuracy))

램던 포레트스 정확도:0.9474

feature importance

ttr_importances_values= new_rf_clf.feature_importances_
feature_importances = pd.Series(ttr_importances_values, index= cancer.feature_names)
tfr_top20=feature_importances.sort_values(ascending=False)[:20]

plt.figure(figsize=(16,10))
plt.title('Feature importtances Top 20')
sns.barplot(x=tfr_top20, y=tfr_top20.index)
plt.show()


GBM(Gradiant boosting machine)

* 여러 개의 약한 학습기를 순차적으로 학습-예측 후 잘못 예측한 데이터에 가중치를 부여함으로써 오류개선
* 가중치 업데이트 방식은 경사 하강법이며 오류 값은 true-pred 값
* 오류값을 최소화하는 방향으로 도달
* 과적합에도 뛰어난 예측 성능 발휘, 수행시간의 증가

from sklearn.ensemble import GradientBoostingClassifier
import time

#데이터 불러오기
x_train,x_test,y_train,y_test = train_test_split(cancer.data, cancer.target, test_size=0.2, random_state=156)

#GBM 수행시간 측정을 위한 시작 시간 설정
start_time =time.time()

gb_clf=GradientBoostingClassifier(random_state=0)
gb_clf.fit(x_train,y_train)
pred=gb_clf.predict(x_test)
accuracy = accuracy_score(y_test, pred)

print('GBM 정확도: {0:.4f}'.format(accuracy))
print('GBM 수행시간: {0:.1f}초'.format(time.time()-start_time))


GBM 정확도: 0.9561
GBM 수행시간: 0.4초

GBM 하이퍼 파라미터 및 튜닝

params={
    'n_estimators':[100,200,300,400,500],
    'learning_rate':[0.05,0.1]
}
grid_cv= GridSearchCV(gb_clf, param_grid=params, cv=2, verbose=1)
grid_cv.fit(x_train,y_train)
pred=grid_cv.predict(x_test)

print('최적의 하이퍼 파라미터:\n', grid_cv.best_params_)
print('최고 예측 정확도:{0:.4f}'.format(grid_cv.best_score_))


최적의 하이퍼 파라미터:
 {'learning_rate': 0.1, 'n_estimators': 100}
최고 예측 정확도:0.9385
#GridSearchCV를 이용해 최적으로 학습된 estimator
gb_pred=grid_cv.best_estimator_.predict(x_test)
gb_accuracy = accuracy_score(y_test,gb_pred)
print("GBM 정확도:{0:.4f}".format(gb_accuracy))

GBM 정확도:0.9561

XGBoost

from xgboost import XGBClassifier

xgb_wrapper= XGBClassifier(n_estimators=400, learning_rate=0.1, max_depth=3)
                                  #early_stopping_rounds: 조기종료까지 최소반복횟수  
evals =[(x_test, y_test)]
xgb_wrapper.fit(x_train, y_train, early_stopping_rounds=100, eval_metric='logloss', eval_set=evals, verbose=True)
w_pred=xgb_wrapper.predict(x_test)
w_pred_proba= xgb_wrapper.predict_proba(x_test)[:,1]
def get_clf_eval(y_test,pred):
  from sklearn.metrics import f1_score, accuracy_score,precision_score,recall_score,confusion_matrix
  confusion = confusion_matrix(y_test, pred)
  accuracy= accuracy_score(y_test, pred)
  precision=precision_score(y_test, pred)
  recall= recall_score(y_test, pred)
  #F1스코어 추가
  f1 =f1_score(y_test,pred)
  print('오차 행렬')
  print(confusion)
  #f1 score print 추가
  print('정확도 {0:.4f}, 정밀도 {1:.4f}, 재현율 {2:.4f}, F1:{3:.4f}'.format(accuracy,precision,recall,f1))
 
get_clf_eval(y_test,pred)

오차 행렬
[[35  2]
 [ 3 74]]
정확도 0.9561, 정밀도 0.9737, 재현율 0.9610, F1:0.9673

feature importance

from xgboost import plot_importance
fig,ax = plt.subplots(figsize=(10,12))
plot_importance(xgb_wrapper, ax=ax)


LightGBM

* 균형트리를 추구하지 않고 최대 손실값을 가지는 리프 노드를 계속 분할하는 방식

from lightgbm import LGBMClassifier

lgbm_wrapper = LGBMClassifier(n_estimators=400)

#w조기 중단 설정
evals=[(x_test,y_test)]
lgbm_wrapper.fit(x_train,y_train,early_stopping_rounds=100, eval_metric='logloss', eval_set=evals, verbose=True)
preds= lgbm_wrapper.predict(x_test)
pred_proba= lgbm_wrapper.predict(x_test)

get_clf_eval(y_test,preds)

오차 행렬
[[33  4]
 [ 2 75]]
정확도 0.9474, 정밀도 0.9494, 재현율 0.9740, F1:0.9615

feature importance

#중요도 시각화
from lightgbm import plot_importance
# %matplotlib inline

fig,ax=plt.subplots(figsize=(10,12))
plot_importance(lgbm_wrapper,ax=ax)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90

+ Recent posts