Data Science/머신러닝

[PySpark] 신용카드 사기거래 탐지 모델링(3) - 기본모델링

히또아빠 2023. 6. 27. 16:37

item

우선, 회사에서 데이터 모델링 작업을 하다가 라벨이 심각하게 불균등하고, 게다가 데이터 정보량도 거의 없어 모델링이 필요한가? rule 기반으로 분류 작업을 하면 안될까??? 라는 고민을 하다가 현재 데이터에서 주성분 몇개 빼고 정보량이 없는 상태에서 모델간의 성능 비교를 해보자는 취지로 진행하였다.

그냥 주성분 전부 다써서 기본 로지스틱 모델만 돌려도 성능이 꽤나오는 깔끔한 데이터니 해보실분들은 따로 해보길 권한다.


1. ML용 Input 데이터 구성

우선, Pyspark ML을 이용하기 위해서는 vectorassembler을 이용해 머신러닝 알고리즘용 featurevector를 구성한다. feature가 dense vector 또는 sparse vector로 구성되는데 리소스에 따라 효율이 좋은 벡터 형태로 자동 구성해준다.

- 참고

https://sikmulation.tistory.com/45

 

[PySpark] dense 벡터와 sparse 벡터

희소 벡터를 생성하려면 벡터 길이(엄격하게 증가해야 하는 0이 아닌 값과 0이 아닌 값의 인덱스)를 제공해야 합니다. pyspark.mllib.linag.Vecotors 라이브러리는 dense(고밀도), sparse(희소) 두 유형의 로

sikmulation.tistory.com

 

아래와 같이 벡터

from pyspark.ml import Pipeline
from pyspark.ml.feature import VectorAssembler

# 분류모델
from pyspark.ml.classification import LogisticRegression

# 튜닝
from pyspark.ml.tuning import ParamGridBuilder, TrainValidationSplit
from pyspark.ml.tuning import CrossValidator

# metric
from pyspark.ml.evaluation import MulticlassClassificationEvaluator, BinaryClassificationEvaluator

un_cols = ["Class", "V1", "V2","V3", "V4", "V5", "V6", "V7", "V8", "V9","V10", "V11" ,"V12", "V13", "V14", "V15", "V16", "V17", "V18", "V19"]
input_col = data.drop(*un_cols).columns

assembler = VectorAssembler(inputCols = input_col, outputCol = "features", handleInvalid = "keep")

Input_df = assembler.transform(data).select("features", "Class")

Input_df.printSchema()

root
 |-- features: vector (nullable = true)
 |-- Class: integer (nullable = true)

 

2. train/test 데이터

데이터를 train, test 용으로 임의로 7:3로 나눴다. 오버샘플링이나 언더샘플링 등 데이터 불균형에 대한 처리는 아무것도 안했다.  일단은 그냥 한번 진행 해보자.

train, test = Input_df.randomSplit([0.7, 0.3], seed = 1234)
train.groupBy("Class").count().show()

+-----+------+
|Class| count|
+-----+------+
|    1|   356|
|    0|199138|
+-----+------+

test.groupBy("Class").count().show()

+-----+-----+
|Class|count|
+-----+-----+
|    1|  136|
|    0|85177|
+-----+-----+

3.모델(Test, CV)

로지스틱 모형에서 하이퍼 파라미터를 5차 교차검증으로 튜닝해 테스트하는 코드이다.  여기서 https://sikmulation.tistory.com/51

 

[PySpark] 5폴드 교차검증(5-fold crossvalidation) 과 모델선택

1.교차검증과 모델선택 ML 알고리즘 마다 조절해야할 하이퍼 파라미터 옵션이 존재하는데 이를 조정하면서 학습데이터에서 과소적합, 과대적합을 방지 할 수 있다. 그 중 교차검증 방법론은 연

sikmulation.tistory.com

# 모델 선언
lr = LogisticRegression(labelCol = "Class", featuresCol = "features")

# 파라미터 그리드 설정
paramGrid = ParamGridBuilder().addGrid(lr.regParam, [0.0, 1.0])\
                                .addGrid(lr.maxIter, [100])\
                                .addGrid(lr.elasticNetParam, [0.1, 0.25, 0.5, 0.75, 0.9])\
                                .build()

# metricName: areaUnderROC, areaUnderPR
evaluator = BinaryClassificationEvaluator(labelCol = "Class", metricName = "areaUnderPR")
# 5fold - cv
cv = CrossValidator(
    estimator = lr,
    estimatorParamMaps = paramGrid,
    evaluator = evaluator,
    numFolds = 5 
)

# cv
model2 = cv.fit(train)

model_prediction2 = model2.transform(test)

model_prediction2.groupBy("Class", "prediction").count().show()

+-----+----------+-----+
|Class|prediction|count|
+-----+----------+-----+
|    1|       0.0|  150|
|    0|       0.0|85148|
|    0|       1.0|    1|
+-----+----------+-----+

4.모델성능 평가

모델의 성능평가 하는 코드이다. 어차피 언더피팅으로 모델이 제대로 학습될 일이 없다. 왜??? 데이터의 정보량이 굉장히 부족하니까. 이런 상황이 feature를 직접 구성해서 모델을 돌려야 하는 경우에는 다반사다. 그래서 확률값 기준으로 어떻게 모델을 평가할 지 고민을 헀고, 아래 코드는 상위 10% 확률값에서 성능 지표의 각각 값에 대해 뽑는 함수이다.

def get_perform(data):
    
    global f1score, lift
    print("quantile","|","cutoff", "|","lift" ,"|", "Recall", "|", "Precision", "|", "f1score")
    n_percent = [i / 100 for i in range(99, 89, -1)]
    
    data = data.withColumn("prob", vector_to_array("probability").getItem(1))
    pop = data.filter(col("Class") == '1').count()/data.count()
    
    for i in range(0,len(n_percent)):
        model_prob = data.approxQuantile("prob", [n_percent[i]], 0.0001)
        model_result = data.filter(col("prob") >= model_prob[0])
    
        model_numerator = model_result.filter(col("Class") =='1').count()/model_result.count()
        lift = model_numerator/pop
        
        #calculate accuracy, sensitivity, specificity and precision
        
        cm_dt_result = data.withColumn("nw_prod", when(col("prob") > model_prob[0], 1).otherwise(0))
        cm_dt_result = cm_dt_result.crosstab("nw_prod", "Class")
        cm_dt_result = cm_dt_result.toPandas()
        cm_dt_result.sort_values(by = ['nw_prod_Class'])
            
        TP = cm_dt_result["1"][0]
        FP = cm_dt_result["0"][0]
        TN = cm_dt_result["0"][1]
        FN = cm_dt_result["1"][1]

        Accuracy = (TP+TN)/(TP+FP+TN+FN) # 정확도
        Recall = TP/(TP+FN) # 정밀도(Sensitivity), 민감도
        Specificity = TN/(TN+FP) # 특이도
        Precision = TP/(TP+FP) # 정밀도 
        f1score = 2 * Recall * Precision / (Recall+Precision) #f1score
        
        print(round((1- n_percent[i]) * 100, 4), "%", "|", "%0.6f" %model_prob[0], "|", "%0.6f" %lift, "|", "%0.6f" %Recall, "|", "%0.6f" %Precision, "|", "%0.6f" %f1score)
    
    return f1score, lift
quantile | cutoff | lift | Recall | Precision | f1score
1.0 % | 0.007434 | 18.666448 | 0.186667 | 0.032864 | 0.055888
2.0 % | 0.005657 | 13.309772 | 0.266667 | 0.023419 | 0.043057
3.0 % | 0.004877 | 9.992073 | 0.300000 | 0.017578 | 0.033210
4.0 % | 0.004384 | 8.323478 | 0.333333 | 0.014641 | 0.028050
5.0 % | 0.004030 | 7.058309 | 0.353333 | 0.012415 | 0.023987
6.0 % | 0.003754 | 6.214863 | 0.373333 | 0.010931 | 0.021240
7.0 % | 0.003544 | 5.613341 | 0.393333 | 0.009873 | 0.019262
8.0 % | 0.003347 | 5.164336 | 0.413333 | 0.009083 | 0.017775
9.0 % | 0.003191 | 4.736366 | 0.426667 | 0.008330 | 0.016341
10.0 % | 0.003051 | 4.333283 | 0.433333 | 0.007621 | 0.014979
(0.014978684180205095, 4.3332825322391555)

 

300x250
반응형