1. LightGBM이란?

LightGBM(Light Gradient Boosting Machine)은 Microsoft에서 개발한 고성능 그래디언트 부스팅 프레임워크입니다. 

기존의 그래디언트 부스팅(Gradient Boosting)방법론을 개선하여 대규모 데이터셋에서도 빠른 학습과 예측을 제공하도록 설계되었습니다. 

LightGBM은 특히 대규모 데이터와 복잡한 특징 공간을 가진 문제에서 빠른 처리 속도와 적은 메모리 사용을 강점으로 가집니다. 

 

1 - 1. LightGBM의 역사

GBDT(Gradient Boosting Decision Tree)는 널리 사용되는 기계학습 알고리즘인데, 여러 효과적인 구현 중 하나가 XGBoost 모델입니다. 

이 알고리즘은 특성 차원이 높을 때와 모든 가능한 분할 지점의 정보 이득을 추정해야 할 때 시간이 많이 소요되는 문제가 있습니다. 

이를 극복하기 위해 LightGBM이 개발되었는데, Gradient-based One-side Sampling(GOSS)과 Exclusive Feature Bundling(EFB)과 같은 알고리즘을 사용했습니다. 

이 알고리즘을 통해 전체 데이터셋의 일부만을 사용하여 각 트리를 훈련시킬 수 있으며, 고차원의 희소 특징을 효율적으로 처리할 수 있습니다. 

 

 

2. LightGBM 특징 및 인기요인

LightGBM은 데이터의 개수가 적을 때(10000개 이하) 과적합이 발생할 수 있지만 다양한 장점 덕분에 자주 사용됩니다. 

 

고속처리 및 효율성

LightGBM은 병렬 처리 및 데이터 샘플링 최적화를 통해 기존 그래디언트 부스팅 방식보다 훨씬 빠른 학습이 가능하게 합니다. 

메모리 효율성을 통한 최적화

연속형 변수에 대해 구간을 만듦으로써 계산 과정에서 메모리 사용량을 줄입니다. 

기존 방식에 비해 높은 처리 속도를 제공하며, 리소스가 제한적인 환경에서도 효율적으로 모델이 운용될 수 있도록 합니다. 

결측치 자동 처리

별도의 결측치 처리 과정 없이도, 알고리즘은 결측치가 있는 데이터를 자동으로 인식하고 이를 학습 과정에 사용합니다. 

범주형 변수 자동 처리

LightGBM은 범주형 변수를 효과적으로 처리할 수 있습니다.

자료형을 category로 바꾸어 주면 되며, 이외의 별도의 인코딩 과정이 필요하지 않습니다. 

target = train['credit']
independent = train.drop(['index', 'credit'], axis = 1)

object_cols = [col for col in independent.columns if independent[col].dtype == "object"]
independent[object_cols] = independent[object_cols].astype('category')

위와 같은 형식으로 바꿔주는 작업을 거치면 됩니다. 

 

스케일링 불필요

LightGBM은 트리 기반의 모델로서 입력 변수의 스케일에 민감하지 않습니다. 

따라서 별도의 스케일링 작업 없이도 충분히 학습할 수 있습니다. 

높은 정확도

LightGBM은 XGBoost와 비교하여 동등하거나 때때로 더 뛰어난 정확도를 제공합니다. 

 

 

3. XGBoost 모델과의 차이

3.1 XGBoost란?

XGBoost(eXtreme Gradient Boosting)는 고성능 그래디언트 부스팅 라이브러리로, 정형 데이터 분석 대회에서 널리 사용되어 왔습니다. 

XGBoost는 그래디언트 부스팅의 전통적인 방식을 발전시켜 특히 병렬 처리와 과적합 방지 기능에서 강력한 성능을 발휘합니다. 

 

3.2 LightGBM vs XGBoost

XGBoost와 LightGBM은 모두 그래디언트 부스팅에 기반한 라이브러리이지만, 주요 차이점이 몇 가지 있습니다. 

이들은 모두 데규모 데이터셋에서 뛰어난 성능을 제공하며, 병렬 처리 기능을 통해 훈련 속도를 향상시킵니다. 

 

차이점1. 성장 방식

XGBoost는 Level-wise 방식을 사용하여 균형 잡힌 트리를 만들어 과적합을 방지하는 반면, 

LightGBM은 Leaf-wise 방식을 사용하여 비대칭적인 트리를 빠르게 성장시킬 수 있습니다. 

 

[Level-wise]

위의 이미지는 Level-wise 성장 방식을 나타낸 것입니다. 

Level-wise 성장 방식에서는 모든 노드가 같은 레벨에 있을 때까지 자식 노드를 확장합니다.

Level-wise 성장 방식의 경우 트리가 균형 성장을 하면서 트리의 높이가 최소화되기 때문에 과적합 방지할 수 있다는 장점이 있지만, 많은 메모리를 사용한다는 단점이 있습니다. 

 

[Leaf-wise]

반면, Leaf-wise 접근 방식은 트리의 성장을 최적화하여 가장 큰 손실 감소를 제공하는 노드를 우선적으로 확장합니다.

이 방법은 종종 더 깊은 트리를 만들어 과적합의 우려가 있지만, 적절하게 모델을 구성할 경우 과적합의 위험을 관리하면서도 정확한 모델을 빠르게 구축할 수 있습니다. 

 

차이점2. 속도 및 대규모 데이터 처리

XGBoost는 병렬 처리 기능을 사용하여 동시에 트리를 구축하고 학습 속도를 향상시키는 능력이 있습니다. 

그러나 이 과정에서 전체 데이터셋의 모든 특성을 스캔해야 하므로, 매우 큰 데이터셋의 경우 여전히 상당한 계산 비용과 시간이 소요될 수 있습니다. 

 

LightGBM은 히스토그램 기반 분할 방식을 사용하여 이러한 계산 비용을 대폭 줄입니다. 

이 방식에서 LightGBM은 데이터의 모든 연속형 변수를 미리 정의된 구간(bin)으로 변환하고, 이 구간 정보를 바탕으로 트리의 분할을 결정합니다. 

이 방법은 데이터를 스캔하는 데 필요한 시간을 크게 줄이며, 메모리 사용도 최소화합니다. 

 

차이점3. 범주형 변수 처리

XGBoost는 범주형 데이터를 처리하기 위해 라벨 인코딩, 원 핫 인코딩과 같이 수치형 데이터로 변환을 해야합니다. 

반면, LightGBM은 범주형 변수의 자료형을 카테고리로 변환하기만 해도 학습이 가능합니다. 

 

 

4. LightGBM 활용

LightGBM은 지도학습 라이브러리로, 주로 정형 데이터를 분석할 때 사용합니다.

 

 

5. LightGBM 세부설정

5.1. 주요 하이퍼파라미터 

  • n_esitmators: 부스팅 단계의 횟수, 즉 모델이 생성할 트리의 수를 지정합니다. 해당 하이퍼파라미터의 값이 클수록 더 많은 트리를 모델에 추가하여 복잡한 데이터 패턴을 학습할 수 있으나, 높은 값은 과적합을 초래할 수 있습니다.
  • max_depth: 트리의 최대 깊이를 설정합니다. 깊이가 깊어질수록 모델은 더 복잡해지며, 과적합의 위험이 커집니다.
  • num_leaves: 트리가 가질 수 있는 리프 노드(말단 노드)의 수를 지정합니다. 리프 노드는 루트 노드(최상위 노드)와 내부 노드(분기 노드)를 제외한, 최종적인 결정이 이루어지는 노드를 의미합니다. 이 값이 크면 모델의 복잡도가 증가하여 예측 성능이 향상될 수 있지만, 너무 크면 과적합을 유발할 수 있습니다.

 

6. LightGBM 모델 학습 - 분류

6-1. 독립변수, 종속변수 설정 및 범주형 자료형 변환

종속변수(target)를 분리하고, 나머지 변수들을 독립변수(independent)로 사용합니다.

범주형 변수들은 LightGBM이 데이터를 처리할 수 있도록 category로 데이터 타입을 변환합니다. 이 변환은 문자열 변수를 인코딩 등의 방법을 이용하여 전처리하지 않는 한 LightGBM 모델을 사용하기 위해서는 필수적인 과정입니다.

target = train['credit']
independent = train.drop(['index', 'credit'], axis = 1)

object_cols = [col for col in independent.columns if independent[col].dtype == "object"]
independent[object_cols] = independent[object_cols].astype('category')

 

6-2. 학습/검증 데이터 설정

독립변수 independent와 종속변수 target을 학습과 검증 데이터로 분리합니다. 

이후에는 학습 데이터로 LightGBM 모델을 학습시키고, 검증 데이터를 이용하여 각 시도에 대한 검증 점수를 확인하면 됩니다. 

from sklearn.model_selection import train_test_split

X_train, X_valid, y_train, y_valid = train_test_split(independent, target, test_size=0.2, random_state=42)

 

6-3. LightGBM 모델 학습

결측치 처리를 하지 않고 문자열 변수의 자료형을 category로만 설정하고, 인코딩과 같은 방법을 사용하지 않음에도 학습이 진행되는데 그 이유는 이 모델이 결측치와 범주형 변수를 자동으로 처리할 수 있는 기능이 있어, 모델 학습에서 별도의 전처리 없이도 학습이 가능합니다. 

from lightgbm import LGBMClassifier

# LightGBM 모델 정의 및 학습
base_lgbm = LGBMClassifier(random_state = 42) 
base_lgbm.fit(X_train, y_train)
# 검증점수 확인
print("LGBM 모델 정확도:", base_lgbm.score(X_valid, y_valid))

 

 

6-4. 하이퍼파라미터 설정

from lightgbm.callback import early_stopping, log_evaluation

# LightGBM 모델 정의
tuning_lgbm = LGBMClassifier(n_estimators=300, max_depth=6, random_state = 42)

# 조기 종료와 학습 로그 출력 콜백 정의
early_stop = early_stopping(stopping_rounds=5)
# 조기 종료를 설정하는 부분이며,이 코드의 경우 5번의 부스팅 동안 성능 개선이 없을 때 종료합니다.

log_eval = log_evaluation(period=20)
## 모델 학습 과정에서 지정된 주기(20라운드)마다 평가지표를 출력합니다.

# 모델 학습
tuning_lgbm.fit(
    X_train, y_train,
    eval_set=[(X_valid, y_valid)],
    eval_metric='multi_logloss',
    callbacks=[early_stop, log_eval]
)

# 정확도 출력
print("하이퍼파라미터가 튜닝된 LGBM 모델 정확도:", tuning_lgbm.score(X_valid, y_valid))

 

 

 

6-5. 학습 과정의 성능 지표 모니터링

평가지표의 변화가 멈추거나, 특정 지점에서 성능이 개선되지 않는다면 과적합이 발생하거나 학습이 더 이상 효과적이지 않음을 의미할 수 있습니다. 이러한 경우 모델 학습 중 모니터링을 하면서 적절한 트리의 수가 얼마인지(n_estimators), 조기종료(early_stopping)를 해야할지 여부를 결정할 수 있습니다. 

 

로그 손실(log loss)은 분류모델의 평가지표이며, 모델이 잘 예측을 수행할수록 낮은 값을 가집니다.

import lightgbm as lgb
import matplotlib.pyplot as plt

# 모델 학습 중 사용된 metric과 동일한 'multi_logloss'를 사용
loss_plot = lgb.plot_metric(tuning_lgbm.evals_result_, metric='multi_logloss')

## plot_metric 함수는 LightGBM의 학습 중에 기록된 평가지표를 시각화할 때 사용합니다. 
## 각 반복마다 기록된 평가 지표는 evals_result_ 속성에 기록되는데,
## 이 속성에는 지정된 검증 데이터셋에 대한 모델의 성능 평가 결과가 저장됩니다.
## 이 코드를 실행함으로써 평가지표가 어떻게 변화하는지 확인할 수 있습니다.

plt.show()

 

 

 

 

6-6. 모델의 피처중요도 시각화

importance_plot = lgb.plot_importance(tuning_lgbm.booster_, max_num_features=10)
plt.show()

 

plot_importance 함수는 모델 학습 중 각 피처의 중요도를 바 차트(bar chart)로 시각화하는 데 사용됩니다. 

이 함수는 tuning_lgbm.booster_ 에서 제공하는 정보를 기반으로 작동합니다. 

booster_ 속성은 LightGBM 모델의 부스터(Booster) 객체를 포함하고 있으며, 이는 모델이 생성한 결정 트리들의 집합입니다. 

각 피처의 중요도는 이 결정 트리들 내에서 해당 피처가 얼마나 자주 사용되었는지를 통해 계산됩니다. 

max_num_features 매개변수를 통해 표시되는 최대 피처의 수를 제한하여, 가장 중요도가 높은 피처만 시각화하도록 설정할 수 있습니다. 

 

 

6-7. LightGBM 예측값 

pred = tuning_lgbm.predict(X_valid)
logloss_pred = tuning_lgbm.predict(X_valid)

print(pred)
print('-'*40)
print(logloss_pred)

 

 

7. XGBoost와 학습 속도 비교

import pandas as pd
import time
from sklearn.model_selection import train_test_split
from lightgbm import LGBMClassifier
from xgboost import XGBClassifier

train = pd.read_csv('당뇨_train.csv')

target_col = 'Outcome'
target = train[target_col]
train = train.drop(['ID', target_col], axis = 1)

# 2. 학습 / 검증 데이터 분리
X_train, X_valid, y_train, y_valid = train_test_split(train, target, test_size=0.2, random_state=42)



# LightGBM 모델 학습
start_lgbm = time.time()
model_lgbm_classifier = LGBMClassifier(random_state = 42) 
model_lgbm_classifier.fit(X_train, y_train) 
end_lgbm = time.time()

# XGBoost 모델 학습
start_xgb = time.time()
model_xgb_classifier = XGBClassifier(random_state = 42)
model_xgb_classifier.fit(X_train, y_train)
end_xgb = time.time()



valid_score_lgbm_classifier = model_lgbm_classifier.score(X_valid, y_valid)
valid_score_xgb_classifier = model_xgb_classifier.score(X_valid, y_valid)

print("LGBM Classifier Validation Score:", valid_score_lgbm_classifier) 
print("XGB Classifier Validation Score:", valid_score_xgb_classifier)

print("LGBM 모델의 학습 시간은", end_lgbm-start_lgbm,"초 입니다.")
print("XGB 모델의 학습 시간은", end_xgb-start_xgb,"초 입니다.")

 

 

 

8. LightGBM 모델 학습 - 회귀

8-1. 학습

from lightgbm.callback import early_stopping, log_evaluation
from lightgbm import LGBMRegressor

# LightGBM 모델 초기화
tuning_lgbm = LGBMRegressor(
                            n_estimators=300, 
                            max_depth=8, 
                            learning_rate= 0.01,
                            random_state = 42
)

# 조기 종료 및 학습 로그 출력 콜백 정의
early_stop = early_stopping(stopping_rounds=15)
log_eval = log_evaluation(period=20)

# 모델 학습
tuning_lgbm.fit(
    X_train, y_train,
    eval_set=[(X_valid, y_valid)],
    eval_metric='rmse',
    callbacks=[early_stop, log_eval]
)

# 정확도 출력
print("LightGBM 회귀모델의 결정계수:", tuning_lgbm.score(X_valid, y_valid))

 

 

8-2. 평가지표 변화 추이 확인

import lightgbm as lgb
import matplotlib.pyplot as plt

# 모델 학습 중 사용된 metric과 동일한 'rmse'를 사용
loss_plot = lgb.plot_metric(tuning_lgbm.evals_result_, metric='rmse')
plt.show()

 

 

8-3. 피처 중요도 확인

importance_plot = lgb.plot_importance(tuning_lgbm.booster_, max_num_features=5)
plt.show()

 

 

8-4. 예측값 획득

pred = tuning_lgbm.predict(test_independent)
submission['box_off_num'] = pred
submission.head()

쇼핑몰 지점별 매출액 예측 AI.pdf
1.74MB
Untitled-1.ipynb
1.93MB

'프로그래밍 > 프로젝트' 카테고리의 다른 글

자기상관분석  (0) 2024.05.21
시계열 데이터의 정상성  (0) 2024.05.21
ARIMA 모델 검증 및 예측 정확도 평가  (0) 2024.05.21
ARIMA 모델  (0) 2024.05.21
데이콘 - 고객 대출 등급 분류 프로젝트  (1) 2024.02.09

1. 정규 표현식 

 

1.1 정규 표현식이란? 

정규 표현식(Regular Expression)은 텍스트 내에서 문자열의 패턴을 찾기 위해 사용되는 일련의 문자와 특수문자의 조합.

이를 통해 데이터를 검색하고, 대체하고, 추출하는 등의 작업을 수행할 수 있다. 

 

1.2 정규 표현식 사용 시기

  • 데이터의 형식 검사
  • 특정 패턴이나 조건에 맞는 문자열을 검색
  • 데이터 정제 및 가공, 특정 정보 추출

 

1.3 정규 표현식의 장점

  • 수동으로 검사하기 어려운 대량의 텍스트 데이터를 빠르게 처리 가능
  • 다양한 문자열 패턴과 조건을 간단한 식으로 표현할 수 있어 데이터를 매우 유연하게 데이터를 다룰 수 있음.
  • 정규 표현식 문법은 언어마다 거의 동일하므로, 다른 프로그래밍 언어에서도 손쉽게 활용 가능

 

1.4 정규 표현식 주의사항

  • 복잡하거나 비효율적으로 작성된 정규 표현식은 성능 저하를 유발할 수 있음.
  • 광범위하게 매치되는 패턴이 있거나, 놓친 특수 케이스가 발생할 수 있음.
  • 작성한 방식에 따라 가독성이 낮아질 수 있음.
  • 정규표현식 대신 간단한 문자열 처리 함수를 사용하는 것이 더 효율적일 수 있음. 

 

2. 메타문자 

 

2.1 메타문자란?

메타문자는 정규 표현식에서 특별한 의미를 갖는 문자들을 말한다.

이들은 단순한 글자가 아니라, 데이터를 검색하고 패턴을 정의하는 데 사용된다.

 

2.2 메타문자의 종류

  • . : 어떤 한 개의 문자와 일치한다. 단, 행을 바꾸는 개행 문자(\n)는 제외된다.
  • ^: 문자열의 시작을 나타낸다.
  • $: 문자열의 끝을 나타낸다.
  • |: 두 패턴 중 하나와 일치한다. OR 연산자라고 한다.
  • \: 특수 문자를 일반 문자로 사용하거나, 특수 시퀀스를 나타내는 데 사용된다. 

 

2.3 주의사항

  • 메타문자는 그 자체로 특별한 의미를 갖기 때문에, 글자 그대로 매치하려면 \를 앞에 붙여 이스케이프해야 한다. 예를 들어, . 자체를 찾으려면 \. 를 사용해야한다. 
  • 메타문자를 사용할 때는 패턴이 어떤 문자열과 일치하는지 정확히 이해하고 사용하는 것이 중요하다.
  • 메타문자를 사용할 때 예상치 못한 결과를 가져올 수 있으므로 주의해서 사용해야 한다. 

 

3. 특수시퀀스와 반복자 이해하기

 

3.1 특수시퀀스란?

특수 시퀀스는 정규 표현식에서 자주 사용되는 특정 패턴을 간단한 코드로 나타낼 때 사용한다. 

 

3.2 특수시퀀스의 종류

  • \d: 모든 숫자와 일치한다. 예를 들어, \d는 '0', '1', '2', '3', .... , '9'와 매치된다.
  • \D: 숫자가 아닌 모든 문자와 일치한다. 
  • \s: 모든 공백 문자와 일치한다.
  • \S: 공백 문자가 아닌 모든 문자와 일치한다. 
  • \w: 문자, 숫자, 밑줄 문자와 일치한다.
  • \W: \w에 해당하지 않는 모든 문자와 일치한다. 

 

3.3 반복자란?

반복자는 특정 문자 또는 문자 집합이 몇 번 반복되어야 하는지 정의한다.

반복자를 사용하면, 동일한 문자 또는 패턴의 반복을 간결하게 표현할 수 있다. 

 

3.4 반복자의 종류

  • * : 바로 앞의 문자가 0회 이상 반복될 때 일치한다. 즉, 문자가 없거나 여러 번 있을 수 있다.
  • + : 바로 앞의 문자가 1회 이상 반복될 때 일치한다. 이는 적어도 한 번은 해당 문자가 있어야 한다는 것을 의미한다.
  • ? : 바로 앞의 문자가 0회 또는 1회 있을 때 일치한다. 이는 문자가 있거나 없을 수 있다는 것을 의미한다. 
  • {n} : 바로 앞의 문자가 정확히 n회 반복될 때 일치한다. 
  • {n, } : 바로 앞의 문자가 n회 이상 반복될 때 일치한다. 
  • {n, m} : 바로 앞의 문자가 최소 n회, 최대 m회 반복될 때 일치한다. 

 

3.5 예시

  • \d{2, 4} 는 숫자이면서 최소 2자리 최대 4자리의 연속된 숫자와 일치한다. 
  • \S+ 는 문자열 Hello World!가 있을 때 공백이 아닌 문자가 1회이상 연속되는 부분을 찾는다. 따라서 Hello와 World! 를 각각 별도로 찾아내어 일치시킨다. 

 

4. 적용 예시

 

법규위반 열 데이터 정제 : 문자열 탐색

# '법규위반' 열에서 '안전' 단어가 포함된 경우 찾기
pattern = r'안전'
train['법규위반_안전'] = train['법규위반'].str.contains(pattern)

display(train.head(3))

 

r'안전'에서 r은 raw string을 나타낸다. 

파이썬에서 raw string은 문자열 앞에 'r'을 붙여 표시하며 문자열 내에 특수 시퀀스(\s, \w, \등)가 특별한 처리없이 문자 그대로 취급하도록 한다. 

raw string을 사용하면 이스케이프 문자가 별도의 처리 없이도 정확히 인식되기 때문에 편리하게 사용할 수 있다. 

 

train['법규위반_안전'] = train['법규위반'].str.contains(pattern)은 train 데이터 프레임의 법규위반 열에서 각 값에 대해 패턴을 확인하고, 해당 패턴이 맞을 경우 True, 아닐 경우 False를 법규 위반_안전 열에 반환한다. 

 

즉, str.contains 메서드는 데이터 프레임의 각 행에서 지정된 패턴 또는 문자열이 존재하는지 여부를 확인하고, 이를 통해 불리언(참/거짓) 값을 생성한다.

 

 

도로형태 열 데이터 정제 : 값 대체

# '도로형태' 열에서 ' - ' 이후 문자열 제거
pattern = r'\s-\s.*'
train['도로형태_대분류'] = train['도로형태'].str.replace(pattern, '', regex=True)

display(train.head(3))

 

변수 pattern에 정의된 패턴은 공백(\s)과 하이픈(-), 공백(\s)을 포함한 뒤의 모든 문자(.)를 찾는 것을 목표로 한다. 

* 은 0회 이상 반복될 수 있다는 것을 의미한다. 

 

str.replace 메서드는 정규표현식을 사용하여 문자열에서 원하는 패턴을 찾아 다른 문자열로 치환할 수 있게 해주는 함수이다. 

코드는 pattern을 발견하면, 그 패턴에 해당하는 문자열을 제거('')한다. 

regex = True은 해당 패턴에서 정규 표현식을 사용한다는 의미이다. 

 

 

시군구 열 데이터 정제 : 캡처 그룹을 활용한 지역명 추출

 

캡처 그룹이란?

 

캡처 그룹(capture group)은 정규표현식 내에서 특정 부분을 하나의 단위로 묶기 위해 사용하는 기술이다. 

이를 통해 복잡한 문자열 패턴 내에서 특정 부분을 식별하고 추출하는 데 사용된다. 

각 캡처 그룹은 괄호 ()를 사용하여 정의되며, 정규표현식 내에서 하나 이상의 문자열을 그룹으로 묶을 수 있는데, 복수의 선택 사항 중 하나를 식별하고, 필요한 부분만을 추출할 수 있다. 

 

사용방법

r'()' 의 괄호 () 안에 여러 문자열 또는 패턴을 넣어 복수의 선택지 중에서 일치하는 요소를 찾을 수 있다. 

r'(창원 | 마산)' 캡처그룹은 '창원' 또는 '마산'과 일치하는 문자열을 찾는다. 

이러한 방식은 데이터에서 특정 키워드나 패턴을 필터링할 때 유용하다. 

또한, 캡처 그룹은 정규표현식 내에서 특정 부분의 반복을 지정하는 데에도 사용된다. 

예를 들어 r'(ab)+' 는 'ab', 'abab', 'ababab' ... 등 문자열이 한 번 이상 반복되는 경우와 일치한다. 

 

pattern = r'(남구|달서구)'

# '남구' 또는 '달서구'를 추출하고, 해당되지 않는 경우 '기타'로 표시
train['시군구_특정'] = train['시군구'].str.extract(pattern)
train['시군구_특정'] = train['시군구_특정'].fillna('기타')

display(train.head(3))

 

pattern = r '(남구 | 달서구)'

 

이 정규 표현식은 '남구' 또는 '달서구' 문자열을 찾기 위해 그루핑을 사용하였다. 

 

train['시군구_특정'] = train['시군구'].str.extract(pattern)

 

위 코드는 판다스(pandas)의 str.extract 메서드를 사용하여 시군구 열(columns)이 패턴(pattern)과 일치하는 부분을 추출한다. 

위의 코드를 실행한 결과, '시군구' 칼럼에 패턴에 해당하는 값이 존재하지 않을 경우 결측값이 반환된다. 

fillna 메서드를 사용하여 결측값을 모두 '기타'로 대체할 수 있다. 

 

 

 

사고일시 열 정제 : 연, 월, 일, 시간 추출

time_pattern = r'(\d{4})-(\d{2})-(\d{2}) (\d{2})'

train[['연', '월', '일', '시간']] = train['사고일시'].str.extract(time_pattern)
display(train.head(3))

 

time_pattern = r '(\d{4})-(\d{2})-(\d{2}) (\d{2})'

 

(\d{n})

이 패턴은 n자리의 숫자를 찾는데 사용된다.

여기서 \d는 숫자를 나타내고, {n}은 연속되는 숫자의 개수를 의미한다.

따라서 \d{4}는 4자리 숫자를 찾는 패턴이다. 

 

이를 활용하여, 각 자리의 값을 순서대로 찾는데 사용되는데, 각 숫자 그룹은 하이픈(-)과 공백() 을 통해 구분된다. 

 

train[['연', '월', '일', '시간']] = train['사고일시'].str.extract(time_pattern)

위 코드는 위에서 정의한 time_pattern을 이용하여 연, 월, 일, 시간 칼럼을 생성한다. 

특이한 점은 이전 스텝에서 str.extract 메서드를 사용했을 때와 달리 연, 월, 일, 시간 4개의 반환값을 가진다. 

 

이는 정규표현식에서 하이픈(-)을 제외한 \d{n} 형태가 각각 별도의 캡처 그룹으로 지정되기 때문이다. 

정규표현식의 각 캡처 그룹은 괄호 ()를 사용하여 정의되며, 이들은 매칭된 문자열의 특정 부분을 추출하는데 사용된다. 

str.extract 메서드는 이러한 캡처 그룹에 해당하는 각 부분을 별도의 칼럼으로 반환하므로, 여기서는 총 네 개의 칼럼(연, 월, 일, 시간)이 생성된다. 

1. 로지스틱 회귀분석 개념

1.1. 로지스틱 회귀분석이란?

로지스틱 회귀분석은 20세기 초에 발전한 통계방법입니다. 이 모델은 로지스틱 함수에서 이진 분류 문제를 풀기위해 발전되었습니다. 이 모델은 주로 예/아니오, 성공/실패와 같이 두 가지 범주로 결과가 나뉘는 경우에 사용됩니다. 로지스틱 회귀분석의 핵심 개념은 다음과 같습니다.

  • 확률 추정: 로지스틱 회귀는 주어진 데이터가 특정 클래스에 속할 확률을 추정합니다. 이 확률은 0과 1 사이의 값으로, 예측된 확률이 특정 임계값(보통 0.5) 이상이면 하나의 클래스로, 이하면 다른 클래스로 분류됩니다.
  • 시그모이드 함수: 로지스틱 회귀는 시그모이드 함수(또는 로지스틱 함수)를 사용하여 입력 데이터의 선형 조합을 0과 1 사이의 확률 값으로 변환합니다. 시그모이드 함수는 S자 형태의 곡선을 그리며, 이 함수는 선형 조합의 결과를 확률로 매핑합니다.
  • 최대 우도 추정: 로지스틱 회귀 모델은 최대 우도 추정(Maximum Likelihood Estimation, MLE) 방법을 사용하여 모델 파라미터를 추정합니다. 이는 주어진 데이터에 대해 관측된 결과의 확률을 최대화하는 파라미터 값을 찾는 과정입니다.
  • 이진 분류: 로지스틱 회귀는 기본적으로 이진 분류를 위해 설계되었지만, 원-대-다(One-vs-Rest) 방식이나 원-대-원(One-vs-One) 방식을 통해 다중 클래스 분류 문제에도 적용될 수 있습니다.

1.2. 언제 사용하면 좋을까요?

로지스틱 회귀분석은 주로 이진 분류(Binary Classification) 문제를 해결하기 위해 사용됩니다. 이는 결과가 두 가지 범주(예: 예/아니오, 성공/실패) 중 하나로 나누어지는 경우에 적합합니다. 주로 데이터 수가 많지 않은 경우 간단한 모델이 필요할 때 사용합니다. 또한, 분석결과에 대한 설명과 해석이 중요할 때 사용하면 좋습니다.

1.3. 장점

  • 해석 용이성: 로지스틱 회귀 모델은 결과를 해석하기 쉽습니다. 각 특성의 가중치를 분석하여 어떤 특성이 결과에 더 큰 영향을 미치는지 이해할 수 있습니다.
  • 확률 추정: 결과의 확률을 제공하여, 단순한 분류뿐만 아니라 결과의 불확실성을 평가할 수 있습니다.
  • 유연성: 다른 회귀 모델과 마찬가지로 다양한 유형의 데이터에 적용할 수 있으며, 커널 방법 등을 사용해 비선형 관계를 모델링할 수도 있습니다.

1.4. 한계점

  • 비선형 관계의 제한적 모델링: 로지스틱 회귀는 기본적으로 선형 관계를 가정합니다. 복잡한 비선형 관계를 모델링하기 위해서는 추가적인 기법이 필요합니다.
  • 특성 선택의 중요성: 중요하지 않거나 상관관계가 높은 특성이 포함되어 있으면 모델의 성능이 저하될 수 있습니다.
  • 과적합의 위험성: 특성의 수가 많거나 모델이 복잡할 경우 과적합(Overfitting)이 발생할 수 있으며, 이를 피하기 위해 적절한 규제가 필요합니다.

 

2. 로지스틱 회귀분석 이론

오즈(Odds): 특정 사건이 발생할 확률과 그 사건이 발생하지 않을 확률 간의 비율 입니다.

예를 들어, 어떤 사건의 발생 확률이 0.75라고 가정해 봅시다. 이 경우, 이 사건이 발생하지 않을 확률은 0.25가 됩니다. 그러면 이 사건의 오즈는 다음과 같이 계산됩니다. 이것은 사건이 발생할 확률이 발생하지 않을 확률보다 3배 높다는 것을 의미합니다.

로그 변환: 로지스틱 회귀에서는 종속 변수의 로그 오즈(log odds)를 독립 변수들의 선형 조합으로 모델링합니다. 즉, 로지스틱 회귀는 확률을 직접 모델링하지 않고, 오즈를 로그 변환하여 사용합니다. 오즈는 0이상의 값만 존재하지만, 로그변환을 하면 값의 범위가 실수 전체로 확장되어, 종속변수와 독립 변수 사이의 관계를 선형방정식으로 표현할 수 있습니다. 로그 오즈는 다음과 같이 정의됩니다.

이 식에서 각각의 요소는 다음과 같은 의미를 갖습니다

  • , , , ..., 은 모델의 계수(가중치) 입니다. 이들은 각 독립 변수가 종속 변수에 미치는 영향의 크기를 나타냅니다.
  • , ,... ,Xn  독립 변수(설명 변수) 입니다. 이들은 분석 대상이 되는 데이터의 특성을 나타냅니다.

위 식을 P(Y=1)에 대해 정리하면 아래와 같습니다.

위 식은 시그모이드 함수와 같은 형태입니다. 데이터의 독립 변수들을 이용하여 하나의 선형 값 z를 계산합니다. 그리고 0과 1 사이의 값(확률)으로 변환합니다.

로지스틱 회귀에서 시그모이드 함수는 입력 데이터의 선형 조합을 확률 값으로 변환하는 데 사용됩니다. 예를 들어, 로지스틱 회귀 모델에서는 데이터의 특성과 가중치의 선형 조합을 계산한 다음, 이 값을 시그모이드 함수의 입력으로 사용하여 0과 1 사이의 값을 얻습니다. 이 값은 특정 클래스에 속할 확률로 해석됩니다. 식으로 표현하면 아래와 같습니다.

위 수식을 그래프로 표현하면 아래와 같습니다.

  • 빨간 점: 실제 데이터
  • 파란색 선: 로지스틱 회귀모델

 

3. LogisticRegression() 매개변수

penalty 정규화 종류. 'l1', 'l2', 'elasticnet', 'none' 중 선택. 기본값은 'l2'.
dual 이중 또는 원시 방법 선택. 기본값은 False.
tol 최적화 중단을 위한 허용 오차. 기본값은 1e-4.
C 정규화 강도의 역수. 값이 작을수록 강한 정규화. 기본값은 1.0.
fit_intercept 모델에 절편(상수 항) 포함 여부. 기본값은 True.
intercept_scaling 절편에 적용되는 스케일링 팩터. fit_intercept가 True일 때 사용.
class_weight 클래스 불균형을 처리하기 위한 가중치. 기본값은 None.
random_state 난수 발생기 시드. 결과 재현성을 위함.
solver 최적화 문제를 해결하기 위한 알고리즘. 'newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga' 등 선택 가능.
max_iter 최적화를 위한 최대 반복 횟수. 기본값은 100.
multi_class 다중 클래스 분류 전략. 'auto', 'ovr', 'multinomial' 중 선택.
verbose 로그 출력 상세도.
warm_start 이전 호출의 솔루션을 재사용하여 피팅을 초기화 여부. 기본값은 False.
n_jobs 병렬 처리를 위한 CPU 코어 수. 기본값은 None (1개 코어 사용).

 

예시

model = LogisticRegression(penalty='l2',       # L2 정규화 사용
                           C=0.5,              # 정규화 강도 (낮을수록 강한 정규화)
                           fit_intercept=True, # 절편을 포함
                           random_state=42,    # 결과 재현을 위한 난수 시드
                           solver='lbfgs',     # 최적화를 위한 알고리즘
                           max_iter=100,       # 최대 반복 횟수
                           multi_class='auto', # 다중 클래스 처리 방식
                           verbose=0,          # 로그 출력 정도 (0은 출력하지 않음)
                           n_jobs=1           # 사용할 CPU 코어 수 (1은 하나의 코어 사용)
)

 

 

4. 파이썬 예제코드

# 데이터 셋 준비
X, y = make_classification(n_features=1, n_samples=300,n_redundant=0, n_informative=1,
                            n_clusters_per_class=1, class_sep=0.5,random_state=7)

X.shape, y.shape

 

make_classifications 함수는 Scikit-learn의 데이터셋 생성 도구로 연습용 데이터셋을 생성한다.  

 

파라미터 설명

  • n_features = 1: 독립 변수(특성)의 수를 1로 설정
  • n_samples = 300: 샘플(데이터 포인트)의 총 개수를 300으로 설정
  • n_redundant = 0: 중복되는(불필요한) 특성의 수를 0으로 설정
  • n_informative = 1: 유익한(목표 변수와 관계 있는) 특성의 수를 1로 설정
  • n_clusters_per_class = 1: 각 클래스별 클러스터의 수를 1로 설정. 이는 각 클래스가 하나의 밀집된 클러스터로 구성되어 있음을 의미한다.  
  • class_sep = 0.5: 클래스 간 분리 정도를 설정한다. 값이 클수록 클래스 간의 분리가 뚜렷해진다. 
  • random_state = 0: 결과의 재현 가능성을 위해 랜덤 상태(seed)를 0으로 고정한다. 

 

로지스틱 회귀분석 결과 해석

import statsmodels.api as sm

X_con = sm.add_constant(X) # 상수항 추가
sm_model = sm.Logit(y, X_con) # 모델 생성
result = sm_model.fit() # 모델 학습
print(result.summary()) # 결과 확인

 

Dep.Variable: 종속 변수 Y

No.Observations: 분석에 사용된 관측치의 수

Df Residuals: 잔차의 자유도는 97

Method: 모델 최적화 방법으로 사용된 방법은 MLE(Maximum Likelihood Estimation)

Pseudo R-squ: 이 모델이 데이터에 대해 어느 정도 설명력을 가지고 있는지 나타낸다. 높을수록 모델이 데이터에 대해 더 높은 설명력을 가진다고 할 수 있음.

Log-Likelihood: 로그 우도 값은 -50.00

LL-Null: 모델 없이(상수항만 있는 경우)로그 우도 값은 -100.00

LLR p-value: 로그 우도 비 테스트의 p-값은 1.000e-10로, 모델이 통계적으로 유의미함을 의미.

 

 

우도(Likelihood) 

: 우도는 주어진 모델 매개변수에서 관측된 데이터가 나타낼 확률을 의미한다. 로지스틱 회귀에서는 관측된 데이터가 주어진 매개변수(예: 회귀 계수)에 대해 나타날 가능성을 수치적으로 나타낸다. 

베타값에서 관측치 y가 나타날 조건부 확률이다. 

 

 

로그 우도(Log-Likelihood)

 우도의 로그 값을 취하는 이유는 여러가지이다. 

 첫째, 로그를 취하면 수치적 안정성이 증가한다. 우도는 확률의 곱셈으로 계산되기 때문에 매우 작은 숫자가 될 수 있으며, 이는 컴퓨터에서의 계산에서 부정확성을 야기할 수 있다. 로그를 취하면 곱셈이 합셈으로 변환되어 이러한 문제를 완화한다. 

 둘째, 로그를 취하면 최적화 문제를 해결하기가 수학적으로 더 쉬워진다. 즉, 최대 우도를 찾는 문제가 더 단순한 형태로 변환된다. 

 Log-Likelihood 값은 높을수록 좋다. 높은 값은 모델이 데이터를 더 잘 설명하고 있음을 의미한다.

제너레이터는 딥러닝에서 학습용 데이터의 Batch를 만들 때 많이 사용된다.  

제너레이터를 이해하기 위해서는 다음과 같은 함수를 먼저 소개한다.  

시작할 때 print를 이용하여 메시지를 출력하고, 0부터 4까지 각 값의 제곱을 val이라는 리스트에 추가하여 반환해주는 함수이다.  

def method():
	print("Start method()")
    val = []
    for x in range(0, 5):
    	val.append( x**2 )
   	return val

 

그러나 Return 키워드를 사용한 코드에는 성능 문제가 있다.  

만약에 range(0, 10000) 같이 범위가 매우 커지면, 메모리 공간도 부족하고 성능도 저하된다.  

 

out = method()
for i in range(0, 5):
	print(out[i])

 

out = method() 가 실행되는 순간 for문을 돌고 나온 후 리스트가 반환된다.  

다음과 같이 출력값이 나온다.  

# Start method()
# 0
# 1
# 4
# 9
# 16

 

제너레이터는 이와 같은 메모리 문제와 성능 저하 문제를 해결해줄 수 있다.  

Yield 키워드를 사용하여 제너레이터를 이용할 수 있다.  

def generator():
	print("Start generator()")
    for x in range(0, 5):
    	yield x**2

 

방금 전 살펴보았던 method()와는 다르게 다음과 같이 generator()를 실행해도 메시지가 출력되지는 않는다.  

gen = generator()

 

계산 부분이 아직 실행되지 않은 채로 제너레이터가 선언만 되어 있는 상태이다.  

다음과 같이 next()를 사용해야 실제 계산이 실행된다. 

for i in range(0, 5):
	print(next(gen))

 

next를 호출할 때마다 yield까지만 실행이 된다.  

그러므로 메모리 공간도 아낄 수 있고 성능 저하도 막을 수 있다.  

 

 

'프로그래밍 > Python' 카테고리의 다른 글

Python - Closure 함수  (0) 2023.12.15
Python - 클래스 속성과 메서드 사용  (2) 2023.11.25
Python - 인스턴스 변수 vs 정적변수  (0) 2023.11.25
Python - 클래스 생성자  (2) 2023.11.25
Python - 클래스  (1) 2023.11.25

7-1 인공 신경망.ipynb
0.07MB
7-2 심층 신경망.ipynb
0.03MB
7-3 신경망 모델 훈련.ipynb
0.24MB

'프로그래밍 > 머신러닝' 카테고리의 다른 글

LightGBM  (1) 2024.09.04
정규 표현식 (Regular Expression)  (0) 2024.08.23
로지스틱 회귀분석  (0) 2024.08.13
혼자 공부하는 머신러닝 + 딥러닝(머신러닝)  (0) 2024.07.19

1-3 마켓과 머신러닝.ipynb
0.05MB
2-1 훈련 세트와 테스트 세트 240701.ipynb
0.07MB
2-2 데이터 전처리 240701.ipynb
0.29MB
3-1 k-최근접 이웃 회귀.ipynb
0.03MB
3-2 선형 회귀.ipynb
0.11MB
3-3 특성 공학과 규제.ipynb
0.08MB
4-1 로지스틱 회귀.ipynb
0.06MB
4-2 확률적 경사 하강법.ipynb
0.11MB
5-1 결정 트리.ipynb
0.72MB
5-2 교차검증과 그리드서치.ipynb
0.77MB
5-3 트리의 앙상블.ipynb
1.43MB
6-1 군집 알고리즘.ipynb
1.06MB
6-2 k-평균.ipynb
1.75MB
6-3 주성분 분석.ipynb
3.78MB

 

 

위의 내용은 7월 2주동안 공부한 내용인데

원래 한 달안에 끝내는 마음으로 이 책을 시작했는데, 너무 내용을 쉽게 풀어서 써줘서 생각보다 빨리 끝날 것 같다.  

이 책이 진짜 쉽게 설명한 만큼 내용이 깊지는 않은데

추후에 깊게 공부하기 위해 전반적인 내용을 훑어본다는 느낌으로 공부하면 좋을 것 같다. 

 

딥러닝 파트도 끝나면 정리해서 올려야겠다. 

(내용은 모두 혼자 공부하는 머신러닝 + 딥러닝을 기반으로 작성되었다.)

'프로그래밍 > 머신러닝' 카테고리의 다른 글

LightGBM  (1) 2024.09.04
정규 표현식 (Regular Expression)  (0) 2024.08.23
로지스틱 회귀분석  (0) 2024.08.13
혼자 공부하는 머신러닝 + 딥러닝(딥러닝)  (0) 2024.08.08

매개변수를 포인터로 선언하는 경우

1. 함수 간의 변수 공유를 통한 범용의 함수 정의 

 

> 함수와 함수 간의 지역변수를 공유하기 위하여 매개변수에 포인터를 사용한다. 

> 인수와 매개변수를 통하여 변수를 공유함으로써 코딩의 유연성(flexibilty)을 줄 수 있다.

> 매개변수를 통한 변수의 공유가 없다면 반환값을 사용하여 함수 간의 값을 전달을 해야하므로 자료형마다 값을 입력받는 수많은 함수를 만들어야한다. 또한 다양한 자료형을 한꺼번에 입력받기 위한 함수를 만들기는 매우 까다롭다.

 

2. 대량의 데이터를 함수 간의 공유

 

> 배열을 함수에 전달할 때 값의 복사 방식을 사용하면 복사본을 만들기 위한 주기억장치 할당으로 실행시간 및 주기억장치 낭비가 발생할 수 있다. 따라서 C언어에서는 기본적으로 배열의 시작 주소를 넘기는 방식을 사용한다. 

 

3. 동적 할당

 

> 프로그램에서는 배열 크기를 정할 때 최악의 경우를 대비하여 최대로 잡는다. 그러나 실제로 프로그램을 실행하는 동안 배열에 저장된 배열 원소 수가 예상보다 많이 작으면 이미 할당된 배열의 기억 공간 중 사용되지 않는 부분은 낭비된다. 이를 해결하기 위해 프로그램을 실행하면서 필요할 때마다 배열을 위한 기억 공간을 할당받고 필요 없어진 공간은 해제하는 동적 할당 방법을 사용할 수 있다. 이러한 동적 할당에 포인터가 사용된다. 

 

두 변수의 값을 바꾸는 함수 

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

void exchange_value(int *ref1, int *ref2);

int main()
{
	int v1 = 36, v2 = 98;

	printf("before exchange: v1 = %d, v2 = %d\n", v1, v2);
	exchange_value(&v1, &v2);
	printf("after exchange: v1 = %d, v2 = %d\n", v1, v2);

	return 0;
}

void exchange_value(int *ref1, int *ref2)
{
	int temp;

	temp = *ref1;
	*ref1 = *ref2;
	*ref2 = temp;
}

 

 

 

 

두 변수를 오름차순으로 정렬하기

#include <stdio.h>
#include <math.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

void sort_value(int *ref1, int *ref2);

int main()
{
	int v1 = 36, v2 = 98;

	printf("v1? ");
	scanf("%d", &v1);
	printf("v2? ");
	scanf("%d", &v2);

	sort_value(&v1, &v2);
	printf("after exchange: v1 = %d, v2 = %d\n", v1, v2);

	return 0;
}

void sort_value(int *ref1, int *ref2)
{
	int temp;

	if (*ref1 > *ref2)
	{
		temp = *ref1;
		*ref1 = *ref2;
		*ref2 = temp;
	}
}

 

 

 

두 수의 몫과 나머지

#include <stdio.h>
#include <math.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

void division(int *x, int *y, int a, int b);

int main()
{
	int n1 = 75, n2 = 8, quo, rem;

	division(&quo, &rem, n1, n2);
	printf("%d / %d = %d \n", n1, n2, quo);
	printf("%d %% %d = %d \n", n1, n2, rem);
}

void division(int *x, int *y, int a, int b)
{
	*x = a / b;
	*y = a % b;
}

'프로그래밍 > C,C++' 카테고리의 다른 글

포인터  (0) 2024.06.06
다양한 함수와 변수의 참조 범위  (0) 2024.05.30
문자열 처리  (0) 2024.05.28
인수 전달하는 함수  (0) 2024.05.28
인수 전달하지 않는 함수  (0) 2024.05.27

지금까지의 데이터를 저장하기 위하여 변수를 선언한 것은 선언할 때 명시한 변수의 자료형의 크기에 따라 주기억장치를 할당받는 것이고 변수를 사용한다는 것은 변수에 할당된 영역에 값을 저장하거나 저장된 값을 읽어서 사용한다는 것을 의미한다.

int var;
var = 10;
printf("%d\n", var);

 

포인터주소를 저장하기 위한 변수다. 

포인터를 사용하는 이유는 값을 간접참조(우회적으로 참조)하기 위한 것이다.

예시를 보자. 아래 예시는 위의 예시와는 다르다.(위의 예에서 var을 직접 사용하지 않고 *ptr을 사용하여 간접 참조한다.)

int *ptr;
ptr = &var;
printf("%d\n", *ptr);

 

여기서 포인터 연산자(*)와 주소 연산자(&)의 의미를 배워보자.

 

포인터 

char *p_chr;
int *p_int;
double *p_dbl;

 

char *p_chr; >> p_chr이 가리키는 곳에 char형 값이 있다. 

char *p_int; >> p_int이 가리키는 곳에 int형 값이 있다. 

char *p_dbl; >> p_dbl이 가리키는 곳에 double형 값이 있다. 

 

주의!!

주소의 크기는 같으므로 포인터의 크기는 같다.

printf("문자형과 포인터의 크기: %d, %d\n", sizeof(char), sizeof p_chr);
printf("정수형과 포인터의 크기: %d, %d\n", sizeof(int), sizeof p_int);
printf("실수형과 포인터의 크기: %d, %d\n", sizeof(double), sizeof p_dbl);

// 문자형과 포인터의 크기: 1, 4
// 정수형과 포인터의 크기: 4, 4
// 실수형과 포인터의 크기: 8, 4

 

>> 포인터의 크기는 모두 4바이트(32비트 컴파일러일 경우)로 같다. 이유는 주소를 표현하는 크기는 같기 때문이다. 

>> 예를 들자면 건물의 크기는 다양하지만, 주소의 길이는 비슷한 것과 같은 이치이다. 

 

주소연산자 (&)

int amt, *p_amt;

amt = 100;
p_amt = &amt;

 

일반 변수 amt를 선언하고 포인터 연산자 *와 연산자 &를 사용하여 amt 변수의 시작주소를 p_amt에 저장하는 예시이다.

밑에 그림은 어떻게 흐름이 이어나가는건지 그림으로 나타낸 것이다. 

amt의 시작주소가 1004인데 그거를 p_amt = &amt를 통해서 amt의 시작주소를 p_amt에 저장한다.

 

 

포인터 연산자 (*)

지금 현재 p_amt에는 amt의 시작주소인 1004가 저장되어 있는 것이다. 

그렇다면 p_amt가 아닌 p_amt가 가리키는 곳을 참조하고 싶을 때는 포인터 연산자 (*)를 사용해야한다.

포인터는 자료의 시작주소만을 저장하고 있고 포인터의 자료형 크기에 따라 마지막 주소가 결정된다.

즉 p_amt는 정수형 포인터이고 4바이트를 참조하므로 시작주소는 1004번지이고 끝 주소는 1007번지가 된다.

*p_amt는 1004-1007번지의 값을 읽어오는 수식으로 p_amt로 amt를 간접참조한다. 

따라서 amt == *p_amt 수식은 참이다. 

 

 

'프로그래밍 > C,C++' 카테고리의 다른 글

포인터 예제  (0) 2024.06.11
다양한 함수와 변수의 참조 범위  (0) 2024.05.30
문자열 처리  (0) 2024.05.28
인수 전달하는 함수  (0) 2024.05.28
인수 전달하지 않는 함수  (0) 2024.05.27

값에 의한 호출(call-by-value)

f_sum() 함수를 호출할 때 a값이 전달되어 begin에 저장되며, b 값이 전달되어 end에 저장된다.

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

int f_sum(int begin, int end);

int main()
{
	int a, b, sum;    //지역변수

	printf("a ~ b까지의 합 구하기 \n");
	printf("a는? ");
	scanf("%d", &a);
	printf("b는? ");
	scanf("%d", &b);

	sum = f_sum(a, b); //함수호출

	printf("\n%d ~ %d의 합은 %d", a, b, sum);

	return 0;
}

int f_sum(int begin, int end)
{
	int i, sum;

	sum = 0;
	for (i = begin; i <= end; i++)
	{
		sum += i;
	}

	return sum;
}

 

f_sum()함수에서 main()의 인수 a를 전달받은 begin 변수는 a와 같은 값을 가진다. 

f_sum()함수에서 return sum; 직전에 begin = begin * 2;를 실행하여 begin 값을 변경하면 main()의 a값도 변할까? 

a와 begin은 서로 다른 함수에서 선언하여 다른 기억장소를 사용하기 때문에 a는 변하지 않는다. 

f_sum() 함수를 int f_sum(int a, int b)로 정의하여도 매개변수 a는 동명이인에 해당하므로 다른 기억장소를 사용한다. 

 

이 방식은 호출된 함수에서 자신을 호출한 함수의 인수를 절대로 변경할 수 없으므로 함수를 호출할 때 마음 놓고 자신의 정보를 전달할 수 있다. 이 '함수 간 독립성 보장'이라는 특징 때문에 함수 호출 시 기본적으로 사용하는 인수 전달 방식이 '값에 의한 호출'이다.

 

이해를 돕기 위한 예시

내 노트를 복사 >>> 각자 내용이 같은 노트를 갖게 된다.

 

 

코드 예시

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)
#define N 5

int f_even(int no);

int main()
{
	int i, marble[N] = { 5, 4, 2, 3, 7 };

	for (i = 0; i < N; i++)
	{
		if (f_even(marble[i]) == 1)
		{
			printf("%d: 홀수\n", marble[i]);
		}

		else
		{
			printf("%d: 짝수\n", marble[i]);
		}
	}

	return 0;
}

int f_even(int no)
{
	if (no % 2 != 0)
	{
		return 1;
	}

	else
	{
		return 0;
	}
}

 

주소에 의한 호출(call-by-value)

배열 전체를 함수에 전달할 때는 주소에 의한 호출 방식만 사용한다.

원소가 천 개인 배열 전체를 값에 의한 호출 방식으로 전달한다고 가정해보자.

인수인 배열의 원소 개수만큼 값을 저장할 매개변수의 기억장소가 필요하고, 배열 원소 순서대로 매개변수에 값을 저장하는데 시간이 걸린다. 

배열은 대량의 자료를 저장하는 데 사용하므로 이 배열을 값에 의한 호출로 전달한다면 추가적 기억장소와 값 복사로 인한 자원 낭비가 데이터 양에 비례하여 커진다. 

그래서 배열은 원본을 공유하는 주소에 의한 호출 방식을 사용한다. 

 

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)
#define N 5

int f_tot(int ary[N]);

int main()
{
	int kor[N] = { 45, 34, 43, 33, 48 };

	printf("총계: %d \n", f_tot(kor));

	return 0;
}

int f_tot(int ary[N])
{
	int i, sum;

	sum = 0;
	for (i = 0; i < N; i++)
	{
		sum += ary[i];
	}

	return sum;
}

 

 

배열 인수: 차원과 상관없이 배열명만 사용 

>> 배열명은 배열 시작 주소이므로 결국 주소를 전달하는 방식 

 

배열 매개변수 선언: 전달되는 배열의 차원에 맞게 배열 선언 

 

주의

- 배열은 함수 간에 같은 기억장소를 공유하므로 호출된 함수 f_tot()에서 ary배열 원소를 변경하면 main()의 kor 배열 원소가 변경된다.  >> 함수에서 배열 내용을 변경한 후 배열을 반환할 필요가 없다.

 

- 매개변수 배열 선언 시 

 1차원 배열이라면 int ary[N] 대신 int ary[ ]처럼 원소수 생략 가능

 2차원 배열이라면 int b[5][7]대신 int b[ ][7]처럼 행 개수 생략 가능

 

- f_tot() 함수는 원소 개수가 N개일 때만 사용할 수 있다. 원소 개수와 상관없이 총계를 구하는 함수는 나중에 배울 예정.

 

 

배열을 전달하여 모든 원소를 두 배로 변경하기 

 

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)
#define N 5

void f_double(int ary[N]);

int main()
{
	int i, kor[N] = { 45, 34, 43, 33, 48 };

	f_double(kor);

	for (i = 0; i < N; i++)
	{
		printf("%4d", kor[i]);
	}

	return 0;
}

void f_double(int ary[N])
{
	int i;

	for (i = 0; i < N; i++)
	{
		ary[i] *= 2;
	}
}

 

 

예시

노트 원본을 빌려주므로 노트는 공유하는 한 권만 있게 되는 경우

 

 

 

함수 호출 시 결과를 저장할 빈 배열을 추가로 전달하기

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)
#define N 5

void f_twice(int a[N], int a_twice[N]);
void f_print(int ary[N]);

int main()
{
	int i, kor[N] = {45, 34, 43, 33, 48};
	int kor_twice[N];

	f_twice(kor, kor_twice);
	f_print(kor);
	f_print(kor_twice);
}

void f_twice(int a[N], int a_twice[N])
{
	int i;

	for (i = 0; i < N; i++)
	{
		a_twice[i] = a[i] * 2;
	}
}

void f_print(int ary[N])
{
	int i;

	for (i = 0; i < N; i++)
	{
		printf("%d ", ary[i]);
	}

	printf("\n");
}

 

void형 함수면 return; 생략가능

 

 

#include <stdio.h>
#pragma warning(disable: 4996)
#pragma warning(disable: 6031)

double get_average(int ary[], int n);

int main()
{
	int a[4] = { 92, 80, 75, 93 };
	int b[3] = { 88, 57, 81 };

	printf("a 평균: %5.1f \n", get_average(a, sizeof(a) / sizeof(a[0])));
	printf("b 평균: %5.1f \n", get_average(b, sizeof(b) / sizeof(b[0])));

	return 0;
}

double get_average(int ary[], int n)
{
	int i, sum;

	sum = 0;
	for (i = 0; i < n; i++)
	{
		sum += ary[i];
	}

	return (double) sum / n;
}

'프로그래밍 > C,C++' 카테고리의 다른 글

포인터 예제  (0) 2024.06.11
포인터  (0) 2024.06.06
문자열 처리  (0) 2024.05.28
인수 전달하는 함수  (0) 2024.05.28
인수 전달하지 않는 함수  (0) 2024.05.27

+ Recent posts