IT/Dev

AI 스마트 평가 시스템: ai를 활용한 구현 방법 공개(파이썬)

스토리버킷 2025. 5. 4. 13:28

이전 글에서 우리는 AI와 머신러닝을 활용한 지원서 평가 시스템의 필요성과 기본 개념에 대해 알아봤습니다.

 

머신러닝과 AI(GEMINI)를 이용한 스마트 평가 시스템 구현 : 공정성과 업무 효율성

 

이번 글에서는 실제 구현 방법, 고급 프롬프트 엔지니어링 기법, 시스템 최적화 방법 등 실제로 제가 구축했던 방법을 공유해드립니다.

프로그램작업은 파이썬으로 했습니다. 

 

 

 

참고로 대부분의 작업을 저는 바이브코딩으로 처리했습니다. 파이썬은 기초적인 지식만 있었고, 아이디어를 구현하려면 엄청난 시간이 필요했겠지만 ai의 도움을 받아서 대부분의 코딩을 했습니다. 하지만 꼭 알아두셔야 할 것이 있는데요. 아무리 인공지능으로 코딩한다고 해도 정말 꾸준히 코드를 살펴보고, 테스트를 해보고 아이디어를 보강하고 다시 진행하기를 수없이 반복해야만 합니다.

 

반응형

 

 

해보신 분들은 아시겠지만 정말 운이 좋은 경우가 아니라면 결코 대충 시작하면 코딩을 직접 안한다는 것 뿐 많은 시간과 노력이 필요할 수 있습니다. 제 경우도 수도 없이 인공지능이 코드를 바꿔버리거나 삭제하거나 자기가 코딩했던 과정을 통째로 잊어버리는 등 많은 시행착오가 있었습니다.

 

 

스마트 지원서 평가시스템 구현 방법

실제로 AI 기반 지원서 평가 시스템을 구현하기 위한 단계별 접근법을 살펴보겠습니다.

저는 평가시스템을 만들기 위해서 몇가지 단계를 거쳤는데요.

이전의 지원서 자료 수집 - 머신러닝 데이터 전처리 - 머신러닝을 통한 훈련 모델 제작 - 머신러닝 평가시스템 개발 - ai 평가시스템 보완 - 하이브리드 평가 결과 구현 등입니다.

 

1. 데이터 준비 및 전처리

효과적인 모델 학습을 위해서는 양질의 데이터 준비가 필수적입니다. 실제로 구축을 할 때 양질의 데이터를 만드는데 시간을 많이 써야한다고 생각합니다. 합격과 불합격 샘플을 만들고 왜 합격했는지, 불합격했는지를 다시 검토하고 이를 수치화하는 작업과 적절성을 조정해야합니다.

 

1.1 데이터 수집 훈련 데이터 구성: 데이터 수집 과거 지원서  그에 대한 평가 결과(합격/불합격 또는 점수) 최소 200-300개 이상의 샘플을 확보하는 것이 이상적.

다양한 품질과 결과를 가진 균형 잡힌 데이터셋만들기.

 

# 데이터 구조 예시
training_data = {
    'application_id': [1, 2, 3, ...],
    'personal_statement': ['지원 동기...', '저는...', ...],
    'experience': ['프로젝트 경험...', '인턴십...', ...],
    'education': ['학사...', '석사...', ...],
    'result': ['합격', '불합격', ...],  # 또는 점수(0-100)
    'evaluator_comments': ['논리적 구성이...', '경험이 부족...', ...]
}

 

1.2 고급 텍스트 전처리

기본적인 정규화 외에도 다음 기법을 적용합니다.

import nltk
import re
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer

nltk.download('stopwords')
nltk.download('wordnet')
nltk.download('punkt')

def advanced_preprocess(text):
    # 1. 소문자 변환 및 특수문자 제거
    text = re.sub(r'[^\w\s]', '', text.lower())
    
    # 2. 불용어 제거
    stop_words = set(stopwords.words('english'))
    words = text.split()
    filtered_words = [word for word in words if word not in stop_words]
    
    # 3. 어간 추출(Lemmatization)
    lemmatizer = WordNetLemmatizer()
    lemmatized_words = [lemmatizer.lemmatize(word) for word in filtered_words]
    
    # 4. 다시 텍스트로 조합
    processed_text = ' '.join(lemmatized_words)
    
    return processed_text

# 한국어 전처리를 위해서는 KoNLPy 같은 한국어 자연어 처리 라이브러리 활용

 

참고로 한국어 전처리 과정에서 맥, 윈도우에 따라 제대로 안되는 모델도 있습니다. koNLPy 대신에 다른 모델도 있으니 살펴보시고 적용해보고 테스트 해봐야합니다. 

 

1.3 섹션별 분석 준비

지원서의 각 섹션마다 다른 평가 기준을 적용하기 위해 섹션을 분리합니다.

def extract_sections(application_text):
    sections = {}
    
    # 정규표현식이나 키워드 기반으로 섹션 분리
    # 예: "학력 사항", "경력 사항", "자기소개서" 등의 제목 탐지
    
    education_pattern = r'(?i)(학력|education)(.+?)(?=(경력|자기소개|experience|personal)|\Z)'
    experience_pattern = r'(?i)(경력|experience)(.+?)(?=(학력|자기소개|education|personal)|\Z)'
    statement_pattern = r'(?i)(자기소개|personal statement)(.+?)\Z'
    
    education_match = re.search(education_pattern, application_text, re.DOTALL)
    if education_match:
        sections['education'] = education_match.group(2).strip()
        
    # 비슷한 방식으로 다른 섹션들도 추출
    
    return sections

 

 

2. 머신러닝 모델 구축 및 최적화

이제 머신러닝 모델을 구축하고 최적화하는 방법을 살펴보겠습니다.

 

2.1 고급 텍스트 특성 추출

단순 TF-IDF를 넘어 더 정교한 특성 추출 방법을 적용합니다.

from sklearn.feature_extraction.text import TfidfVectorizer
from sentence_transformers import SentenceTransformer

# 1. TF-IDF 특성
def extract_tfidf_features(texts, max_features=1000):
    tfidf = TfidfVectorizer(max_features=max_features, ngram_range=(1, 2))
    features = tfidf.fit_transform(texts)
    return features, tfidf

# 2. BERT 임베딩 특성
def extract_bert_features(texts):
    model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
    embeddings = model.encode(texts)
    return embeddings

# 3. 커스텀 특성
def extract_custom_features(texts):
    features = []
    for text in texts:
        # 텍스트 길이
        length = len(text)
        
        # 평균 문장 길이
        sentences = text.split('.')
        avg_sentence_length = sum(len(s) for s in sentences) / max(len(sentences), 1)
        
        # 고유 단어 비율
        words = text.split()
        unique_ratio = len(set(words)) / max(len(words), 1)
        
        # 기술 관련 키워드 등장 횟수
        tech_keywords = ['개발', '분석', '프로젝트', '설계', '구현']
        tech_count = sum(text.count(word) for word in tech_keywords)
        
        features.append([length, avg_sentence_length, unique_ratio, tech_count])
    
    return np.array(features)

 

2.2 모델 선택 및 훈련

다양한 모델을 실험하고 최적의 모델을 선택.

from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report

def train_and_evaluate_models(X, y):
    # 모델 후보들
    models = {
        'Random Forest': RandomForestClassifier(),
        'Gradient Boosting': GradientBoostingClassifier(),
        'Logistic Regression': LogisticRegression(),
        'SVM': SVC(probability=True)
    }
    
     # 각 모델별 하이퍼파라미터 그리드
    param_grids = {
        'Random Forest': {'n_estimators': [50, 100, 200], 'max_depth': [None, 10, 20]},
        'Gradient Boosting': {'n_estimators': [50, 100], 'learning_rate': [0.01, 0.1]},
        'Logistic Regression': {'C': [0.1, 1, 10], 'penalty': ['l1', 'l2']},
        'SVM': {'C': [0.1, 1, 10], 'kernel': ['linear', 'rbf']}
    }
    
    results = {}
    
    # 각 모델 훈련 및 평가
    for name, model in models.items():
        grid = GridSearchCV(model, param_grids[name], cv=5, scoring='f1')
        grid.fit(X, y)
        best_score = grid.best_score_
        best_params = grid.best_params_
        best_model = grid.best_estimator_
        
        results[name] = {
            'best_score': best_score,
            'best_params': best_params,
            'best_model': best_model
        }
        
        print(f"{name} - Best F1 Score: {best_score:.4f}")
        print(f"Best Parameters: {best_params}")
    
    # 최고 성능 모델 반환
    best_model_name = max(results, key=lambda k: results[k]['best_score'])
    return results[best_model_name]['best_model'], best_model_name


    

2.3 앙상블 방법 활용

여러 모델의 강점을 결합한 앙상블 방법을 구현합니다.

 

from sklearn.ensemble import VotingClassifier

def create_ensemble(X, y):
    # 개별 모델들
    rf = RandomForestClassifier(n_estimators=100)
    gb = GradientBoostingClassifier(n_estimators=100)
    lr = LogisticRegression()
    
    # 앙상블 모델
    ensemble = VotingClassifier(
        estimators=[('rf', rf), ('gb', gb), ('lr', lr)],
        voting='soft'  # 확률 기반 투표
    )
    
    # 앙상블 모델 훈련
    ensemble.fit(X, y)
    
    # 교차 검증으로 성능 평가
    scores = cross_val_score(ensemble, X, y, cv=5, scoring='f1')
    print(f"Ensemble Model - Average F1 Score: {scores.mean():.4f}")
    
    return ensemble

 

3. 생성형 AI 최적화

고급 프롬프트 엔지니어링 생성형 AI의 성능은 프롬프트 설계에 크게 좌우됩니다.

효과적인 프롬프트 엔지니어링 기법을 살펴보겠습니다.

 

* 여기서도 중요한 부분이 있는데요. 프롬프트를 잘 넣는다고 해도 결과물은 예상과 다른 것이 나올 수 있습니다. 그 이유는 결론에서 다시 말씀드릴게요.

 

3.1 구조화된 프롬프트 템플릿

평가 기준을 명확히 하고 구조화된 응답을 유도하는 프롬프트 템플릿

def create_structured_prompt(text, position_requirements=None):
    prompt = f"""
    역할: 당신은 경험 많은 인사 전문가로, 지원서를 객관적이고 일관되게 평가합니다.
    
    임무: 다음 자기소개서를 평가하고 다음 기준에 따라 1-10점 사이의 점수를 매겨주세요.
    
    평가 기준:
    1. 관련성(Relevance): 지원자의 경험과 기술이 직무 요구사항과 얼마나 관련이 있는가?
    2. 구체성(Specificity): 지원자가 자신의 경험과 성과를 얼마나 구체적으로 설명하는가?
    3. 논리성(Logic): 문장과 단락이 논리적으로 연결되어 있는가?
    4. 진정성(Authenticity): 지원자의 경험이 진실되고 일관되게 느껴지는가?
    5. 적합성(Fit): 지원자의 가치관과 목표가 회사/조직의 문화와 일치하는가?
    
    지원 직무 요구사항: {position_requirements if position_requirements else "구체적인 요구사항이 제공되지 않았습니다."}
    
    자기소개서:
    "{text}"
    
    응답 형식:
    JSON 형식으로 다음과 같이 응답해주세요:
    ```json
    {
      "scores": {
        "relevance": [1-10 점수],
        "specificity": [1-10 점수],
        "logic": [1-10 점수],
        "authenticity": [1-10 점수],
        "fit": [1-10 점수]
      },
      "overall_score": [평균 점수, 소수점 한 자리까지],
      "strengths": ["장점1", "장점2", "장점3"],
      "areas_for_improvement": ["개선점1", "개선점2"],
      "summary": "간략한 평가 요약 (100자 이내)"
    }
    ```
    
    주의사항:
    - 평가는 객관적이고 직무 관련성에 초점을 맞추세요.
    - 인종, 성별, 연령, 교육 기관 이름과 관련된 편향이 없어야 합니다.
    - 점수는 항목별로 독립적으로 평가하세요.
    - 모든 필드를 작성하고 정확한 JSON 형식을 유지하세요.
    """
    return prompt

 

3.2 다단계 평가 프롬프트

복잡한 평가를 여러 단계로 나누어 진행하는 방법.

def multi_stage_evaluation(text, ai_model):
    # 1단계: 초기 개요 분석
    stage1_prompt = f"""
    다음 자기소개서의 주요 주제와 핵심 내용을 분석해주세요:
    "{text[:1000]}..."
    """
    stage1_result = ai_model.generate_content(stage1_prompt).text
    
    # 2단계: 세부 항목별 평가
    stage2_prompt = f"""
    앞서 분석한 자기소개서의 주요 내용을 바탕으로, 다음 항목별로 1-10점 사이의 점수를 매겨주세요:
    1. 관련 경험
    2. 기술적 역량
    3. 의사소통 능력
    4. 문제 해결 능력
    5. 팀워크 및 협업
    
    참고할 초기 분석 내용:
    {stage1_result}
    
    자기소개서:
    "{text[:1500]}..."
    """
    stage2_result = ai_model.generate_content(stage2_prompt).text
    
    # 3단계: 종합 평가 및 피드백
    stage3_prompt = f"""
    앞서 진행한 항목별 평가를 바탕으로, 최종 종합 평가와 구체적인 피드백을 제공해주세요.
    
    항목별 평가 결과:
    {stage2_result}
    
    JSON 형식으로 다음 정보를 포함하세요:
    1. 종합 점수 (100점 만점)
    2. 가장 인상적인 부분 3가지
    3. 개선이 필요한 부분 2가지
    4. 채용 추천 여부 (추천/보통/비추천)
    """
    final_result = ai_model.generate_content(stage3_prompt).text
    
    # 결과 파싱 (실제 구현에서는 JSON 파싱 필요)
    return final_result

 

3.3 역할 기반 프롬프트

다양한 관점에서 평가하기 위한 역할 기반 프롬프트.

def role_based_evaluation(text, role, ai_model):
    roles = {
        "HR 전문가": "인재 발굴과 조직 문화 적합성 측면에서 평가해주세요.",
        "기술 전문가": "지원자의 기술적 역량과 전문성을 중점적으로 평가해주세요.",
        "팀 리더": "리더십 잠재력과 팀워크 측면에서 평가해주세요.",
        "고객 서비스 담당자": "의사소통 능력과 공감 능력을 중점적으로 평가해주세요."
    }
    
    prompt = f"""
    당신은 {role}입니다. {roles.get(role, "")}
    
    다음 자기소개서를 읽고 당신의 전문 분야 관점에서 평가해주세요:
    
    "{text}"
    
    다음 형식으로 응답해주세요:
    1. 강점 분석 (3가지)
    2. 개선점 분석 (2가지)
    3. 총평 (100자 이내)
    4. 추천 점수 (1-10)
    """
    
    return ai_model.generate_content(prompt).text

 

4. 결과 통합 및 의사결정 시스템

ML과 AI 결과를 효과적으로 통합하고 최종 결정을 내리는 방법을 살펴보겠습니다.

 

4.1 가중치 최적화

ML과 AI 결과의 최적 가중치를 찾기

def optimize_weights(ml_scores, ai_scores, true_labels):
    from sklearn.metrics import f1_score
    import numpy as np
    
    best_f1 = 0
    best_weights = (0.5, 0.5)  # 기본값
    
    # 다양한 가중치 조합 시도
    for ml_weight in np.arange(0.0, 1.1, 0.1):
        ai_weight = 1 - ml_weight
        
        # 가중 평균 계산
        combined_scores = ml_weight * np.array(ml_scores) + ai_weight * np.array(ai_scores)
        
        # 임계값 기반 예측 (예: 7점 이상이면 합격)
        predictions = (combined_scores >= 7).astype(int)
        
        # F1 점수 계산
        f1 = f1_score(true_labels, predictions)
        
        if f1 > best_f1:
            best_f1 = f1
            best_weights = (ml_weight, ai_weight)
    
    print(f"최적 가중치: ML={best_weights[0]:.1f}, AI={best_weights[1]:.1f}, F1 점수: {best_f1:.4f}")
    return best_weights

 

4.2 등급 및 추천 시스템

최종 점수를 등급으로 변환하고 추천 결정을 내리는 부분입니다.

def assign_grade_and_recommendation(score):
    # 등급 할당
    if score >= 9.0:
        grade = "A+"
        recommendation = "강력 추천"
        description = "탁월한 지원자. 즉시 면접 진행 권장."
    elif score >= 8.0:
        grade = "A"
        recommendation = "추천"
        description = "우수한 지원자. 면접 진행 권장."
    elif score >= 7.0:
        grade = "B+"
        recommendation = "면접 고려"
        description = "유망한 지원자. 추가 평가 후 면접 고려."
    elif score >= 6.0:
        grade = "B"
        recommendation = "보류"
        description = "평균적인 지원자. 다른 지원자와 비교 후 결정."
    elif score >= 5.0:
        grade = "C+"
        recommendation = "재검토"
        description = "기본 요건은 충족하나 경쟁력이 부족함."
    else:
        grade = "C 이하"
        recommendation = "부적합"
        description = "직무 요건과 맞지 않음."
    
    return {
        "grade": grade,
        "recommendation": recommendation,
        "description": description
    }

 

4.3 종합 보고서 생성

평가 결과를 종합하여 상세 보고서 생성하기

def generate_comprehensive_report(applicant_data, ml_result, ai_result, combined_score):
    import json
    
    # AI 결과 파싱 (가정: JSON 형식으로 반환됨)
    try:
        ai_analysis = json.loads(ai_result)
    except:
        ai_analysis = {
            "scores": {"relevance": 0, "specificity": 0, "logic": 0, "authenticity": 0, "fit": 0},
            "strengths": ["분석 실패"],
            "areas_for_improvement": ["분석 실패"],
            "summary": "AI 분석 결과를 파싱할 수 없습니다."
        }
    
    # 등급 및 추천사항
    grade_info = assign_grade_and_recommendation(combined_score)
    
    # 보고서 생성
    report = {
        "applicant": {
            "name": applicant_data.get("name", "미상"),
            "position": applicant_data.get("position", "미상"),
            "application_date": applicant_data.get("date", "미상")
        },
        "scores": {
            "machine_learning": round(ml_result, 2),
            "ai_evaluation": {
                "overall": ai_analysis.get("overall_score", 0),
                "breakdown": ai_analysis.get("scores", {})
            },
            "combined_score": round(combined_score, 2)
        },
        "grade": grade_info["grade"],
        "recommendation": grade_info["recommendation"],
        "assessment": {
            "summary": grade_info["description"],
            "strengths": ai_analysis.get("strengths", []),
            "areas_for_improvement": ai_analysis.get("areas_for_improvement", []),
            "detailed_comments": ai_analysis.get("summary", "")
        }
    }
    
    return report

 

 

마치며, 

AI와 머신러닝의 융합으로 진화하는 평가 시스템

AI와 머신러닝을 결합한 스마트 지원서 평가 시스템은 단순한 자동화 수준을 넘어서, 객관성, 정밀성, 확장성을 동시에 확보할 수 있는 강력한 도구입니다. 특히 생성형 AI(Gemini 등)의 활용은 지원자의 텍스트 표현에 내재된 의미와 맥락을 이해하고 평가하는 데 있어 기존 룰 기반 시스템보다 훨씬 더 정교한 분석을 가능하게 합니다.

 

이번 시리즈에서는 다음과 같은 실전적인 요소들을 다뤘습니다

 

• 데이터 수집부터 전처리, 특성 추출까지의 고도화된 준비 과정

• 머신러닝 모델의 실전 적용 및 앙상블 기법을 통한 성능 향상

• 생성형 AI를 위한 구조화된 프롬프트 엔지니어링 및 다단계 평가 방식

• ML과 AI 평가 결과를 통합하는 의사결정 전략과 종합 보고서 생성 방법

 

전체 시스템을 모듈화하고 시각화하는 부분을 추가하면 대시보드를 통해 운영까지 연결하는 아키텍처 구성을 할 수 있습니다.

이렇게 해서 보다 더 공정하고 투명하며 일관된 평가를 할 수 있게 되며, 반복적이고 주관에 의존하던 기존 인사 평가의 한계를 넘는 것에 도움을 줄 수 있을 것이라 생각합니다. 

 

반면 앞서 말씀드린 것처럼 프롬프트를 정교하게 넣어도 결과는 원하는 것과 다르게 나올 수 있는데요. 프롬프트를 정교하게 넣었을 때도 지원서에 대해서 평가는 사람과 다를 수 있습니다. 오히려 프롬프트를 단순화해서 머신러닝이 처리한 것을 보조적으로 보완할 때 오히려 결과가 나을 수도 있고 반대로 지표로 넣은 것에 따라 너무 엄격해져서 합격자를 하나도 못내는 경우가 발생할 수도 있습니다. 

적절한 수준을 만들기 위해서 프롬프트를 비롯한 평가 지표들과 머신러닝에서 사용하는 지표들을 튜닝해야할 수 있음을 감안하시면 좋겠습니다.

 

감사합니다. 

 

 

반응형