SDG-PGMs 미국 예제(us_person) 읽기

NVIDIA가 공개한 확률적 그래프 모델 프레임워크와 미국 인물 생성 예제의 변수 구조 정리

1무엇을 읽었나

  • 대상: NVIDIA-NeMo/SDG-PGMs 저장소의 examples/us_person/. 라이선스는 Apache 2.0이다.
  • 읽은 시점: 2026-09-21. 커밋 c34fd1a (2026-05-29).
  • 읽은 파일: 저장소 README, 예제 README, us_person_generator.py, 기반 클래스 src/pgms/generators/base/pgm_generator.py.
  • 코드를 실행하지는 않았다. 아래 내용은 모두 코드와 README를 읽고 정리했다.

Nemotron-Personas-Korea 카드는 인구통계 변수를 "proprietary probabilistic graphical model (PGM)"로 뽑았다고만 적었고 한국 모델의 구조는 공개하지 않았다. 이 저장소는 같은 방식으로 PGM을 만드는 틀과 미국 예제를 공개했다. 저장소 README는 SDG-PGMs가 Data Designer와 함께 Nemotron-Personas 컬렉션을 만드는 데 쓰였다고 소개한다. 한국 예제는 들어 있지 않다.

예제에 들어 있는 통계는 가짜다. 예제 README가 직접 밝혔다. 동봉된 집계표는 표의 짜임(컬럼과 범주)만 실제와 같고 수치는 난수이며, 우편번호·지명·인종 라벨도 자리만 채운 값이다. 이 예제로 뽑은 표본은 미국 인구를 반영하지 않는다. 실제 Nemotron-Personas-USA를 만든 집계표와 모델은 공개되지 않았다. 이 예제에서 배울 것은 수치가 아니라 변수 구조다.

2프레임워크가 하는 일

SDG-PGMs는 파이썬 라이브러리 pgmpy의 베이지안 네트워크 위에 몇 가지를 얹은 틀이다. 개인 레코드가 아니라 집계표(count table, 범주 조합별 인원수를 적은 표)를 입력으로 받는다. 집계표를 조건부 확률표(CPD)로 바꾸고 그 확률표에서 부모 변수부터 차례로 값을 뽑는다.

기능내용코드 위치
연쇄 베이지안 네트워크 (cascaded)네트워크를 여러 단계로 나눠 차례로 돌린다. 뒤 단계는 앞 단계에서 뽑힌 값을 그대로 받아 조건으로 쓴다.get_edges()가 단계별 간선 목록을 돌려준다
단계 사이 후처리단계가 끝날 때마다 일반 파이썬 함수로 컬럼을 더하거나 고친다. 확률로 뽑을 필요가 없는 값(우편번호에서 시·주 찾기 등)을 여기서 만든다.get_postprocessing_steps()
잠재 변수 (latent)네트워크 안에서만 쓰고 최종 결과에서는 뺀다.get_latents()
집계표 → CPD집계표에 없는 조합을 0으로 채우고 아주 작은 값(pseudocount, 기본 1e-10)을 더한 뒤 정규화한다. 확률이 0이 되어 샘플링이 멈추는 일을 막는 장치다.fill_na_and_gen_cpd()
조건을 걸어 뽑기 (evidence)"서부 지역만"처럼 조건을 주면, 일단 넉넉히 뽑은 뒤 조건에 맞지 않는 표본을 버리는 기각 샘플링을 한다. 조건이 너무 드물면 오류를 낸다._rejection_sample()
Data Designer 연결만든 생성기를 Data Designer의 컬럼 생성기로 꽂는다. PGM이 인구통계 변수를 만들고 LLM이 서술문을 쓰는 파이프라인이 이렇게 이어진다.data_designer_plugins/pgm_generator_plugin.py

생성기를 새로 만들려면 PGMGenerator를 상속해 네 가지를 채운다. 집계표를 읽는 get_data(), 변수와 범주를 선언하는 get_variables(), 간선을 선언하는 get_edges(), 확률표를 만드는 get_cpds()다.

3미국 예제의 전체 흐름

변수 29개를 선언하고 베이지안 네트워크 3개를 차례로 돌린다. 29개 가운데 12개는 잠재 변수라 결과에서 빠지고 후처리에서 만든 컬럼이 더해져 최종 표본은 23개 컬럼이 된다.

1단계. 기본 인구통계

zipcode, sex, age_group, age

후처리: 우편번호에서 앞 4자리(zip_prefix4)와 지역 유형(zipcode_category)을 만든다. 연령대를 변수별 구간 4종으로 바꿔 붙인다.

2단계. 주소, 혼인·학력, 인종

street_name, unit, marital_status, education_level, bachelors_field, ethnicity, race_broad, race_group, ethnicity_origin

후처리: 인종·민족 변수를 합쳐 ethnic_background를 만든다. 학사 미만이면 전공을 no_degree로 바꾼다. 학력과 전공을 한 변수(education_level_bachelors)로 합친다.

3단계. 직업과 이름

occupation, detailed_occupation, last_name, first_name, middle_name

후처리: 세부 직업을 occupation 컬럼에 덮어쓴다. 85세 이상은 not_in_workforce로 바꾼다. 시·주·카운티, 생년월일, 전화번호, 이메일, 사회보장번호 형식의 ID, uuid를 만든다.

단계를 나눈 이유는 코드에서 드러난다. 직업의 부모인 education_level_bachelors는 학력과 전공이 둘 다 뽑힌 뒤에야 만들 수 있고, 이름의 부모인 ethnic_background도 인종 변수 4개가 뽑힌 뒤에 규칙으로 정해진다. 확률로 뽑는 일과 규칙으로 계산하는 일이 번갈아 나오기 때문에 네트워크를 끊고 그 사이에 후처리를 끼웠다.

4변수별 부모와 집계표

간선 목록(get_edges())과 확률표(get_cpds())를 합쳐 한 표로 옮겼다. 부모가 없는 변수는 zipcode 하나다. 점선 테두리는 결과에서 빠지는 잠재 변수다.

단계변수 (자식)부모확률표를 만든 집계표비고
1zipcode없음population_by_zipcode우편번호별 인구수가 곧 출발 확률이다
1sexzipcodeage_group_sex_by_zipcode를 성별로 합산
1age_groupzipcode, sexage_group_sex_by_zipcode
1ageage_group, sexage_by_age_group_sex연령대 안에서 한 살 단위 나이를 뽑는다
2marital_statuszipcode, sex, age_group_maritalmarital_status_by_age_sex_zipcode
2education_levelzipcode, sex, age_group_educationeducation_by_age_sex_zipcode
2bachelors_fieldzipcode, sex, age_group_bachelorsbachelors_field_by_age_sex_zipcode학력과 무관하게 일단 뽑고 학사 미만이면 후처리에서 지운다
2ethnicityzip_prefix4ethnicity_by_zip_prefix히스패닉 여부
2race_broadzip_prefix4, ethnicityrace_broad_by_zip_prefix_ethnicity
2race_groupzip_prefix4, race_broadrace_group_by_zip_prefix_race_broad
2ethnicity_originzip_prefix4, ethnicityethnicity_origin_by_zip_prefix_ethnicity
2street_name, unitzipcode_categorystreet_name_by_zipcode_category, unit_by_zipcode_categorypseudocount를 0으로 뒀다
3occupationeducation_level_bachelors, age_group_occupationoccupation_by_education_age성별과 지역은 부모가 아니다
3detailed_occupationoccupation, sexdetailed_occupation_by_occupation_sexpseudocount를 0으로 뒀다
3last_nameethnic_backgroundlast_name_by_ethnic_background
3first_name, middle_nameethnic_background, sexfirst_name_by_ethnic_background_sex, middle_name_by_ethnic_background_sex
확률표 없이 규칙으로만 정해지는 값: city, state(우편번호 대응표), county(우편번호에 걸친 카운티 가운데 무작위 하나), street_number(평균 200인 기하분포), birth_date, phone_number(우편번호의 지역번호), email_address, national_id, uuid.

5코드를 읽으며 눈에 띈 설계

직업은 학력·전공과 연령대만 본다

occupation의 부모는 학력과 전공을 합친 변수, 그리고 직업용 연령대 둘뿐이다. 성별은 그다음 detailed_occupation에서야 들어오고, 지역은 직업에 전혀 영향을 주지 않는다. 직업 대분류가 같으면 어느 지역에 살든 세부 직업 분포가 같다는 뜻이다. 학력과 전공을 따로 부모로 두지 않고 bachelors-공학처럼 한 변수로 합쳐 놓았다. 집계표가 학력·전공 조합별로 직업 인원을 세어 둔 교차표여서 학력과 전공의 교호작용이 확률표에 그대로 들어간다.

연령대 구간을 변수마다 따로 둔다

나이는 한 번만 뽑지만 연령대 변수는 다섯 개다. 기본 age_group 외에 혼인용, 학력용, 전공용, 직업용이 따로 있고, 대응표(age_group_map)로 바꿔 붙인다. 통계표마다 연령 구간을 끊는 방식이 다르기 때문에 생긴 구조로 보인다 (추정). 동봉된 가짜 데이터에서는 전공용만 구간이 다르다. 기본 구간이 under_18, 18_24, 25_44, 45_84, 85_plus인데 전공용은 앞의 둘을 under_25로 합쳤다. 한국 통계로 옮길 때도 표마다 연령 구간이 달라 같은 장치가 필요해진다.

단계는 앞 단계 표본을 넘겨받아 이어진다

2단계 네트워크에도 zipcode와 sex 노드가 있지만 확률표는 주지 않았다. 기반 클래스는 확률표가 없는 노드에 균등분포를 넣어 두는데, 실제로는 1단계에서 뽑힌 값이 partial_samples로 고정돼 들어가므로 균등분포는 쓰이지 않는다. ethnic_background에 균등분포를 준 것도 같은 이유다. 값은 2단계 후처리에서 이미 정해져 있다.

pseudocount를 0으로 둔 곳

기본값 1e-10은 집계표에 없는 조합에도 아주 작은 확률을 준다. 세부 직업, 도로명, 호수에는 0을 줬다. 세부 직업은 상위 직업 안에서만 의미가 있으므로 작은 값이라도 더하면 "관리직인데 세부 직업은 간호사" 같은 불가능한 조합이 나올 수 있다는 주석이 코드에 달려 있다.

재현성의 한계

seed는 NumPy에만 걸린다. PGM이 뽑는 컬럼은 같은 시드에서 같은 값이 나오지만 후처리에서 파이썬 random으로 만드는 카운티·전화번호·ID·이메일·생일·uuid는 돌릴 때마다 달라진다. 예제 README에 그렇게 적혀 있다.

프라이버시에 대한 입장

README는 집계표에서 표본을 뽑으므로 원자료의 행을 복사하지 않는다고 설명한다. 다만 차분 프라이버시 같은 형식적 보장은 이 틀이 제공하지 않는다고 적었다. 집계표의 출처로는 공공 영역이거나 허용적 라이선스인 정부 통계를 권한다.

6한국판과 견주어 보기

한국 모델의 구조는 공개되지 않았으므로 아래 비교의 한국 쪽은 공개된 컬럼과 카드의 서술에 근거한다. 구조에 관한 부분은 (추정)이다.

항목미국 예제 (코드로 확인)한국판 (카드와 컬럼으로 확인)
지리우편번호에서 출발한다. 시·주·카운티는 대응표로 붙인다.district(시군구), province(시도). 출발 변수가 무엇인지는 공개되지 않았다.
공통 변수성별, 나이, 혼인상태, 학력, 전공, 직업, 이름
한쪽에만 있는 변수인종·민족 계열 5개, 주소, 전화번호, 이메일, 사회보장번호 형식 ID병역, 가구 유형, 주거 형태
직업의 조건학력×전공×연령대의 교차표 하나로 직업을 뽑고, 직업×성별로 세부 직업을 뽑는다.카드는 세부 직업 배정에서 성별·소득·학력·전공이 각각 독립적으로 영향을 준다고 가정했고 교호작용은 넣지 않았다고 밝혔다.
이름인종·민족 배경과 성별에 조건부대법원 이름 자료를 썼다고 카드에 나온다. 조건 변수는 공개되지 않았다.

직업 항목의 차이는 집계표 사정에서 나온 것으로 보인다 (추정). 미국 예제는 학력·전공·연령대를 한꺼번에 교차한 직업 집계표가 있다는 전제로 짜여 있다. 한국 카드는 독립 가정의 이유로 "공개 데이터의 한계"와 "확률적 그래프 모델의 실무적 제약"을 들었다. 교차표를 구하지 못하면 요인별 주변 분포를 따로 구해 곱하는 수밖에 없고 그러면 교호작용이 빠진다.

이 프로젝트의 축소 모형과 견주면

scripts/10_toy_pgm.py는 성별 → 연령대 → 혼인상태 → 가구유형의 사슬 하나를 만들어 조상 샘플링을 했다. 원리는 같다. 미국 예제에서 더 볼 것은 세 가지다. 한 변수에 부모가 둘이나 셋 붙는다. 네트워크를 끊고 그 사이에 규칙 기반 후처리를 넣는다. 결과에 내보내지 않는 잠재 변수로 통계표 사이의 구간 차이를 맞춘다.

7수준별 직무 페르소나에 옮긴다면

여기부터는 코드에서 확인한 내용이 아니라 이 프로젝트에 적용할 때의 생각이다.

미국 예제의 틀에서 SQF/NCS 수준은 3단계의 직업 뒤에 오는 변수로 넣는 것이 자연스럽다. 부모 후보는 직업, 학력, 직업용 연령대다. 연령대는 경력 연수를 대신하는 변수가 된다. 걸림돌은 집계표다. 직업×학력×연령대 조합별로 수준 분포를 센 공식 통계가 있어야 확률표를 만들 수 있다. 그런 표가 없으면 한국판 카드처럼 요인별로 독립을 가정하거나, 수준 정의 문서의 학력·경력 요건을 규칙으로 옮겨 후처리에서 정해야 한다. 규칙으로 정하면 확률 모형이 아니라 결정 규칙이 되므로 같은 직업·학력·연령대인 사람은 모두 같은 수준을 받는다.

8출처