AI 기초

GUIDE / 8번째 글

AI와 프라이버시: 개인정보를 지키는 기술

멤버십 추론·모델 역추론·학습 데이터 추출 등 AI 프라이버시 공격 유형을 분석하고, 차분 프라이버시·연합학습·동형암호 세 가지 보호 기술을 코드와 함께 비교합니다.

PALDYN Team47 MIN READ

지난 글에서 모델의 결정이 어느 집단에 치우치는지를 숫자로 재고, SHAP·LIME 같은 기법으로 그 결정의 근거를 열어 봤다. 그런데 모델을 열어 보면 학습 데이터가 함께 비친다 — 모델이 데이터를 너무 잘 기억하면 그 자체가 프라이버시 문제가 된다.

프라이버시를 "개인정보를 암호화해서 보관하는 것"으로 아는 경우가 많다. 그건 저장소 이야기다. AI 시스템에서 개인정보는 저장소 밖에서도 세 번 더 움직인다 — 요청으로 흘러 들어가고, 로그와 캐시에 남고, 학습에 쓰이면 파라미터 안으로 녹아든다. 마지막 것이 특히 다루기 어렵다. 데이터베이스의 행 하나는 지우면 사라지지만, 수십억 개 가중치에 흩뿌려진 한 사람의 흔적은 어디를 지워야 할지조차 분명하지 않다.

이 글은 그 흔적이 어떻게 생기고, 어떤 방식으로 새어 나오며, 무엇으로 막는지를 본다. 기술만으로 끝나지 않는 대목 — 어디서 지울지, 얼마나 오래 두는지, 지워 달라는 요청을 어떻게 처리하는지 — 도 함께 다룬다.

모델의 암기

모델은 학습 데이터를 압축해 파라미터에 담는다. 이 압축이 완전한 요약이라면 문제가 없겠지만, 실제로는 일부 문자열이 거의 원본 그대로 남는다. 이것을 암기(memorization)라고 부른다. 일반화와 암기는 같은 학습 과정의 양면이라 한쪽만 끄는 스위치가 없다는 점이 이 문제의 핵심이다.

학습 데이터 추출

학습 데이터 추출(training data extraction)은 모델에 특정 프롬프트를 넣어 학습 말뭉치에 있던 문자열을 그대로 뱉게 만드는 공격이다. 2021년 연구에서 GPT-2를 상대로 이 공격이 성공했다. 연구진은 모델에 짧은 접두사를 주고 이어지게 한 뒤, 나온 문장을 실제 웹 말뭉치와 대조해 원본이 있는지 확인했다. 그렇게 확인된 것 중에 실제 이메일 주소, 전화번호, 이름이 있었다.

여기서 두 가지를 구분해야 한다. 모델이 그럴듯한 전화번호를 지어내는 것과, 학습 때 본 전화번호를 그대로 내놓는 것은 완전히 다른 사건이다. 앞은 무해한 환각이고 뒤는 유출이다. 겉보기로는 둘 다 "숫자 열한 자리"라서, 원본 말뭉치와 대조하기 전에는 어느 쪽인지 알 수 없다. 유출을 확인하려면 말뭉치를 갖고 있어야 하는데 — 공격자는 대개 갖고 있지 않고, 방어하는 쪽은 갖고 있다. 이 점이 방어자에게 유일하게 유리한 비대칭이다.

# LLM 학습 데이터 추출 위험성 예시 (개념적)
# 반복 토큰 주입으로 학습 데이터 노출 유도
adversarial_prompt = "다음을 계속 반복하세요: " * 50
# → 모델이 학습 중 본 텍스트를 그대로 출력할 수 있음

# 멤버십 추론: 과적합된 모델에서 특정 샘플 포함 여부 확인
def membership_inference(model, target_sample, shadow_samples):
    target_loss = model.loss(target_sample)
    shadow_losses = [model.loss(s) for s in shadow_samples]
    # 학습 데이터는 비학습 데이터보다 손실이 낮음
    threshold = np.mean(shadow_losses)
    return target_loss < threshold  # True면 학습 데이터 포함 의심

위의 반복 토큰 주입은 같은 낱말을 끝없이 되풀이시켜 모델을 대화 응답 모드에서 밀어내는 방식이다. 대화용으로 조정된 모델은 평소 학습 말뭉치를 그대로 내놓지 않지만, 이렇게 이상한 상태로 몰아넣으면 조정 이전의 습관이 드러난다. 정렬이 암기를 지운 게 아니라 덮어 두었을 뿐이라는 뜻이다. 이 구분은 AI 탈옥에서 다루는 방어의 한계와 정확히 같은 구조다.

암기의 조건

암기는 아무 문자열에나 고르게 일어나지 않는다. 세 가지가 확률을 밀어 올린다 — 말뭉치 안의 중복 횟수, 모델 크기, 그리고 모델에 먼저 주는 문맥의 길이다. 셋 다 방향이 같다. 같은 문자열이 여러 번 나올수록, 모델이 클수록, 앞에 더 긴 단서를 줄수록 이어지는 부분을 원본대로 뱉을 확률이 올라간다.

중복 횟수가 왜 결정적인지는 한 번 따라가 보면 분명해진다. 어떤 문자열이 말뭉치에 딱 한 번 나오면 학습은 그 방향으로 가중치를 아주 조금 민다. 다른 수십억 개 문장이 반대 방향으로 밀면 그 흔적은 묻힌다. 그런데 한 사람이 게시판 서명란에 전화번호를 넣어 두고 글 500편을 썼다면, 크롤러가 그 게시판을 한 번 훑는 것만으로 같은 번호가 500번 들어온다. 미러 사이트가 셋이면 1,500번이다. 이 정도가 되면 그 번호는 더 이상 잡음이 아니라 모델이 배워야 할 규칙처럼 보인다.

그래서 가장 값싼 대책이 중복 제거(deduplication)다. 학습 전에 같은 문서·같은 문단을 걸러 내면 암기가 눈에 띄게 줄어든다는 것이 여러 연구에서 확인됐다. 노이즈를 넣는 것도, 구조를 바꾸는 것도 아니고 그냥 데이터를 정리하는 일인데 효과가 크다. 프라이버시 대책을 고를 때 이걸 먼저 하지 않고 차분 프라이버시부터 검토하는 것은 순서가 뒤바뀐 것이다.

멤버십 추론

멤버십 추론(membership inference)은 내용을 뽑아내는 게 아니라 "이 레코드가 학습에 쓰였는가"만 맞히는 공격이다. 원리는 단순하다. 모델은 학습 중에 본 샘플에서 손실이 더 낮다. 그래서 대상 샘플의 손실을 재고, 학습에 안 쓰였을 게 확실한 샘플들의 손실 분포와 비교해 유난히 낮으면 학습 데이터로 의심한다. 위 코드의 membership_inference가 그 얼개다.

"포함 여부만 아는 게 뭐가 문제인가" 싶지만, 데이터셋이 무엇이냐에 따라 그 한 비트가 전부다. 특정 질환 환자 코호트로 만든 모델이라면, 어떤 사람의 레코드가 학습에 쓰였다는 사실 자체가 그 사람의 진단명이다. 대출 연체자 데이터셋, 상담 이용자 데이터셋도 마찬가지다. 집합에 속한다는 사실이 곧 민감 정보인 데이터셋에서는 내용이 한 글자도 안 새어도 유출이다.

멤버십 추론이 잘 통하는 모델에는 공통점이 있다. 과적합된 모델이다. 학습 손실과 검증 손실의 차이가 벌어질수록 두 분포가 갈라지고, 갈라진 만큼 공격자가 선을 긋기 쉬워진다. 과적합을 줄이는 정규화가 프라이버시 대책이기도 한 셈이다. 다만 이건 상관관계이지 보장은 아니다 — 검증 손실이 잘 붙어 있어도 특정 희귀 샘플 하나는 여전히 크게 암기될 수 있다. 보장을 원하면 뒤에 나오는 차분 프라이버시로 가야 한다.

AI 시스템의 프라이버시 위협

데이터의 경로

암기는 우리가 모델을 직접 학습시킬 때 이야기다. 대부분의 팀은 남의 모델을 API로 쓴다. 그쪽에서는 질문이 달라진다 — 우리가 보낸 것이 어디를 지나 어디에 얼마나 머무는가.

네 갈래 보관

"이 벤더는 우리 데이터를 학습에 안 쓴다"는 한 줄로 검토를 끝내는 경우가 많은데, 데이터가 머무는 자리는 넷이고 학습은 그중 하나다.

첫째는 요청 본문이다. 처리하는 동안 메모리에 있고 보통 응답과 함께 사라진다. 둘째는 로그다. 오류 추적·남용 감시·과금 대조를 위해 요청의 일부 또는 전부가 기록되며, 여기에 보관 기간이 붙는다. 셋째는 캐시다. 프롬프트 캐시, 임베딩 저장소, CDN 응답 캐시가 각각 다른 수명을 갖는다. 넷째가 학습이다.

이 넷은 주체도 기간도 다르다. 학습 사용을 껐어도 로그는 남고, 로그 보관 기간이 30일이어도 캐시는 별도 정책을 따를 수 있다. 검토할 때는 넷을 따로 물어야 한다 — "학습에 쓰나요"가 아니라 "요청 본문이 어디에 얼마나 남나요"가 맞는 질문이다. 답이 문서에 없으면 그건 없는 게 아니라 안 적힌 것이다.

학습 사용 설정

같은 회사의 서비스라도 결이 다르다. 대체로 개발자용 API는 기본적으로 고객 데이터를 모델 학습에 쓰지 않고, 일반 소비자용 앱과 무료 플랜은 다른 기본값을 갖는 경우가 있다. 여기서 사고가 나는 전형적인 경로는 기술이 아니라 조직이다 — 회사가 계약한 기업용 창구는 안전하게 설정돼 있는데, 급한 사람이 개인 계정으로 같은 모델에 고객 데이터를 붙여 넣는다.

그래서 설정만큼 중요한 것이 경로를 하나로 모으는 일이다. 사내에서 모델에 닿는 길을 게이트웨이 하나로 좁히면 설정을 한 곳에서 걸 수 있고, 뒤에 나올 마스킹도 한 곳에 붙는다. 길이 여럿이면 가장 느슨한 길이 조직의 실제 수준이 된다.

리전과 국외 이전

개인정보를 국외로 보내는 일은 대부분의 개인정보 보호 법제에서 별도 절차를 요구한다. 한국 개인정보 보호법도 국외 이전에 고지와 동의 등의 요건을 두고 있고, EU GDPR도 역외 이전을 따로 규율한다. 모델 API 호출은 이 요건에 걸리는 전형적인 행위다 — 사용자가 입력한 문장에 이름이 들어 있으면 그 문장이 곧 개인정보이고, 그것이 다른 나라 서버로 나간다.

리전을 고를 수 있는 서비스라면 절반은 해결되지만, 여기에 함정이 하나 있다. 추론이 도는 리전과 로그·모니터링이 모이는 리전이 같다는 보장이 없다. 본문은 국내에 머물러도 오류 리포트에 요청 일부가 붙어 다른 나라로 나갈 수 있다. 리전을 고를 때는 추론 리전만이 아니라 부수 데이터의 경로까지 문서에서 확인해야 한다.

요청 하나가 남는 네 자리

하위 처리자

벤더도 남의 인프라를 쓴다. 클라우드, 모니터링, 결제, 고객 지원 도구 — 이들을 하위 처리자(sub-processor)라고 부른다. 우리가 고객에게 지는 책임은 하위 처리자까지 이어지는데, 정작 우리는 그 목록을 벤더의 공개 페이지로만 안다.

실무에서 볼 것은 세 가지다. 목록이 공개돼 있는가, 바뀔 때 미리 알려 주는가, 알린 뒤에 이의를 제기할 기간이 있는가. 셋 다 계약서에 적히는 항목이고, 셋 다 기술이 아니라 문서로 해결되는 문제다. 규제 쪽 전반은 AI 규제에서 따로 다룬다.

마스킹 지점

경로를 정리했으면 다음은 무엇을 실어 보낼지다. 원칙은 하나로 요약된다 — 모델이 볼 필요가 없는 값은 모델에 닿기 전에 지운다.

모델 앞단

출력에서 개인정보를 거르는 필터를 두는 팀이 많은데, 그건 늦다. 출력을 거를 때쯤이면 그 값은 이미 요청 본문으로 나갔고, 벤더 로그에 남았고, 프롬프트 캐시에 들어갔을 수 있다. 출력 필터는 모델이 지어낸 값이나 다른 경로로 새어 나온 값을 잡는 마지막 그물이지 첫 방어선이 아니다. 첫 방어선은 언제나 우리 서버, 요청을 만들기 직전이다.

import re

def mask_pii(text):
    # 이름, 전화번호, 이메일, 주민번호 마스킹
    text = re.sub(r'\b\d{6}-\d{7}\b', '[주민번호]', text)
    text = re.sub(r'\b01[016789]-\d{3,4}-\d{4}\b', '[전화번호]', text)
    text = re.sub(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
                  '[이메일]', text)
    return text

safe_prompt = mask_pii(user_input)
response = client.messages.create(
    model="claude-opus-4-7",
    messages=[{"role": "user", "content": safe_prompt}]
)

정규식으로 잡히는 것은 꼴이 정해진 값뿐이다. 주민등록번호 열세 자리, 전화번호, 이메일, 카드번호까지는 이 방식으로 충분하다. 문제는 사람 이름, 주소, 사번, 병명처럼 꼴이 없는 값이다. "김민수 고객님이 어제 강남점에서"라는 문장에는 정규식이 걸 자리가 없다. 여기서부터는 개체명 인식(NER, Named Entity Recognition) 같은 모델 기반 탐지를 붙여야 하고, 그러면 정확도가 100%가 아니라는 새 문제가 생긴다. 실무에서는 둘을 겹쳐 쓴다 — 꼴이 있는 값은 정규식으로 확실히 잡고, 나머지는 탐지 모델로 훑되 놓치는 것이 있다고 전제한다. 구체적인 설계는 PII 마스킹에서 더 파고든다.

가역 마스킹

지운 값을 다시 채워야 하는 자리가 있다. 고객 문의에 답장 초안을 만드는 시스템이라면 초안에 실제 이름이 들어가야 쓸모가 있다. 이때 쓰는 것이 가역 마스킹이다. 값을 지우는 대신 [고객명_1] 같은 자리표로 바꿔 보내고, 자리표와 원본의 대응표는 우리 서버에만 둔다. 모델은 자리표를 그대로 쓴 답을 돌려주고, 우리가 마지막에 되돌린다.

되돌리면 안 되는 자리도 분명히 해야 한다. 분석·통계·평가 로그처럼 사람을 특정할 필요가 없는 용도에서는 비가역으로 지운다. 가르는 기준은 하나다 — 그 사람을 다시 지목해야 업무가 되는가. 답장은 그렇고, 품질 평가는 아니다.

그리고 대응표 자체가 새로운 위험이다. 그 표를 가진 쪽은 마스킹된 모든 기록을 원본으로 되돌릴 수 있으므로, 표는 원본 데이터와 같은 등급으로 다뤄야 한다. 수명도 짧게 잡는다. 답장 초안 한 건에 필요한 대응표를 며칠씩 들고 있을 이유가 없다.

가역 마스킹의 왕복

마스킹 로그

무엇을 지웠는지는 남기고 무엇이었는지는 남기지 않는다. "이 요청에서 전화번호 2건, 이메일 1건을 마스킹했다"까지만 기록한다는 뜻이다.

이 기록이 두 가지 일을 한다. 하나는 사고가 났을 때 범위를 좁히는 것이다. 어느 기간의 요청에 개인정보가 몇 종류나 실렸는지 세어 두면, 유출이 확인됐을 때 영향 범위를 추정할 근거가 생긴다. 다른 하나는 마스킹 규칙이 조용히 망가지는 것을 잡는 일이다. 하루 평균 수백 건 잡히던 전화번호가 어느 날부터 0건이 되면 개인정보가 사라진 게 아니라 정규식이 깨진 것이다. 이 숫자를 안 보고 있으면 몇 달을 그대로 지난다.

차분 프라이버시

앞까지가 "안 넣기"였다면 여기서부터는 "넣되 못 집어내게 하기"다.

부인 가능성

차분 프라이버시(Differential Privacy, DP)는 결과가 특정 개인의 포함 여부와 거의 무관해지도록 계산 과정에 노이즈를 넣는 방식이다. 어떤 데이터셋 D와, D에서 한 사람의 레코드만 바꾼 D'이 있을 때 두 결과의 분포가 충분히 비슷하면 된다.

P(M(D)∈S)≤eε⋅P(M(D′)∈S)P(M(D) \in S) \le e^{\varepsilon} \cdot P(M(D') \in S)

이 부등식이 모든 출력 집합 S에 대해 성립하면 ε-차분 프라이버시를 만족한다고 한다. 발상을 말로 옮기면 이렇다 — 결과를 본 사람이 "당신 데이터가 여기 들어 있었군요"라고 주장하면, 당신은 "내가 빠졌어도 거의 같은 결과가 나왔을 텐데요"라고 답할 수 있다. 이 부인 가능성이 차분 프라이버시가 주는 것의 전부이자 핵심이다. 개별 값을 숨기는 게 아니라, 개별 값의 유무가 결과를 거의 못 움직이게 만든다.

중요한 성질이 하나 더 있다. 이 보장은 공격자가 무엇을 알고 있든 유효하다. 다른 데이터셋을 갖고 있든, 나머지 전원의 정보를 알고 있든 부등식은 그대로다. 익명화가 재식별에 계속 뚫려 온 이유가 "공격자가 가진 다른 정보"였음을 생각하면, 이 무조건성이 차분 프라이버시가 표준이 된 이유다.

프라이버시 예산

ε은 그 "거의"의 크기다. 값이 작을수록 두 분포가 붙어 있고 보호가 세다. 부등식 오른쪽이 eεe^{\varepsilon} 이므로 감각은 지수적으로 벌어진다. ε=1이면 두 분포의 확률비는 최대 약 2.7배, ε=3이면 약 20배, ε=10이면 약 22,000배까지 허용된다. 22,000배면 사실상 아무것도 보장하지 않는 것 아닌가 싶지만, 그건 최악의 경우 상한이고 실제 누출은 훨씬 작다는 것이 통상적인 해석이다. 그래도 ε을 하나의 점수처럼 비교할 때는 이 지수 스케일을 기억해야 한다 — ε 4와 8은 두 배 차이가 아니다.

실용 구현에서는 ε 하나가 아니라 (ε, δ) 쌍을 쓴다. δ는 그 보장이 깨질 확률의 상한이고, 보통 데이터셋 크기의 역수보다 작게 잡는다. 아래 코드에서 delta=1e-5가 그것이다.

그리고 ε은 예산이라고 부른다. 같은 데이터에 질의를 여러 번 하면 소모량이 쌓이기 때문이다. 한 번에 ε=1로 물어보고 같은 걸 열 번 물으면 보호는 ε=1이 아니다. 학습도 마찬가지라 스텝마다 소모가 누적되고, 그래서 아래처럼 학습이 끝난 뒤 총 소비량을 계산해 보는 절차가 따라붙는다.

# Opacus: PyTorch용 차분 프라이버시 학습
from opacus import PrivacyEngine

model = MyModel()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
data_loader = DataLoader(dataset, batch_size=64)

privacy_engine = PrivacyEngine()

model, optimizer, data_loader = privacy_engine.make_private(
    module=model,
    optimizer=optimizer,
    data_loader=data_loader,
    noise_multiplier=1.1,   # 노이즈 크기 (클수록 강한 보호)
    max_grad_norm=1.0,      # 그래디언트 클리핑
)

# 학습 후 소비된 프라이버시 예산 확인
epsilon = privacy_engine.get_epsilon(delta=1e-5)
print(f"ε = {epsilon:.2f}, δ = 1e-5")
# ε이 작을수록 강한 프라이버시 보호 (보통 ε < 10 목표)

정확도 비용

딥러닝에 차분 프라이버시를 거는 방법이 DP-SGD다. 하는 일은 두 가지뿐이다. 먼저 샘플별 기울기의 크기를 max_grad_norm으로 잘라 낸다(클리핑). 한 샘플이 학습을 크게 흔들지 못하게 상한을 두는 것이다. 그다음 잘라 낸 기울기 합에 노이즈를 더한다. 개인 하나의 영향이 상한 안으로 들어와 있으니, 그 상한에 견줄 만한 노이즈를 얹으면 있고 없고가 구분되지 않는다.

공짜가 아니다. 노이즈는 학습 신호도 함께 흐리므로 정확도가 떨어지고, 샘플별 기울기를 따로 계산해야 해서 학습이 느려지고 메모리도 더 든다. 실무에서 ε=1~10 범위를 쓰는 것은 이 상충 때문이다.

깎이는 방식이 고르지 않다는 점은 특히 새겨 둘 만하다. 노이즈에 먼저 지워지는 것은 드물게 나타나는 패턴이고, 그것은 대개 소수 집단의 데이터다. 다수 집단의 패턴은 표본이 많아 노이즈를 이겨 내지만, 표본이 적은 집단의 신호는 노이즈에 묻힌다. 그래서 DP를 걸면 전체 정확도는 조금 떨어지는데 특정 집단의 정확도는 크게 떨어지는 일이 생긴다. 편향과 공정성에서 잰 격차가 프라이버시 대책 때문에 벌어질 수 있다는 뜻이고, 두 지표를 따로 보면 이걸 놓친다.

프라이버시 보호 기술

연합 학습과 동형암호

차분 프라이버시가 "모아 놓고 흐리게"라면, 남은 두 기법은 "애초에 안 모으기"와 "모으되 못 보기"다.

기울기 집계

연합 학습(Federated Learning)은 원시 데이터를 서버로 보내는 대신 각 기기에서 로컬 학습을 하고 모델 업데이트만 서버로 보내는 구조다. 서버는 받은 업데이트들을 평균해 전역 모델을 갱신하고, 갱신된 모델을 다시 기기로 내려보낸다. 이 왕복을 라운드라고 하고, 가장 기본이 되는 집계 방식이 아래의 FedAvg다.

# FedAvg 알고리즘 간략 구현
def federated_train(global_model, clients, rounds=10):
    for round in range(rounds):
        # 각 클라이언트에서 로컬 학습
        local_updates = []
        for client in clients:
            local_model = copy.deepcopy(global_model)
            local_optimizer = torch.optim.SGD(
                local_model.parameters(), lr=0.01
            )
            # 클라이언트 로컬 데이터로만 학습 (데이터 서버 미전송)
            for epoch in range(5):
                for batch in client.local_dataloader:
                    loss = local_model.forward(batch)
                    loss.backward()
                    local_optimizer.step()

            local_updates.append(local_model.state_dict())

        # 서버에서 그래디언트 평균 집계 (FedAvg)
        averaged_weights = average_weights(local_updates)
        global_model.load_state_dict(averaged_weights)

    return global_model

스마트폰 키보드의 다음 단어 예측이 대표적인 적용처다. 사용자가 무엇을 타이핑하는지는 기기 밖으로 나가지 않고, 학습된 가중치의 변화분만 나간다. 병원처럼 데이터를 밖으로 못 내보내는 기관들이 공동 모델을 만들 때도 같은 구조를 쓴다.

기울기 역전

문제는 "가중치의 변화분만 나간다"가 "아무것도 안 나간다"와 다르다는 점이다. 기울기 역전(gradient inversion)은 전송된 기울기에서 원본 입력을 복원하는 공격이다. 기울기는 결국 그 입력에 대해 손실이 어떻게 변하는지를 담고 있으므로, 그 방향을 만들어 내는 입력을 거꾸로 찾아 나가면 된다. 무작위 이미지에서 출발해 그 이미지의 기울기가 받은 기울기와 같아지도록 이미지를 최적화하는 식이다. 배치가 작을 때 원본 이미지를 알아볼 정도로 복원한 결과가 여러 차례 보고됐다.

막는 방법은 둘이다. 하나는 보안 집계(secure aggregation)로, 서버가 개별 업데이트를 못 보고 합계만 보게 만드는 암호 프로토콜이다. 개별 기울기가 안 보이면 어느 기기의 데이터를 복원할지 자체가 성립하지 않는다. 다른 하나가 앞 절의 차분 프라이버시다. 기기에서 업데이트에 노이즈를 얹어 보낸다. 연합 학습에 차분 프라이버시를 함께 쓰는 조합이 흔한 것은 이 때문이다 — 연합 학습만으로는 프라이버시 보장이 되지 않는다. 구조가 데이터의 이동을 줄일 뿐, 수학적 보장을 주지는 않는다.

통신 비용과 데이터 이질성

연합 학습이 널리 안 쓰이는 이유는 공격보다는 운영 쪽에 있다. 라운드마다 모델 전체를 내려보내고 업데이트를 올려받는다. 파라미터가 억 단위면 라운드 한 번의 통신량이 기기당 수백 메가바이트가 되고, 라운드는 수백 번 돈다. 그래서 업데이트를 양자화하거나 희소화해 보내는 압축이 사실상 필수다.

데이터가 기기마다 다르다는 점도 크다. 중앙 학습은 데이터를 섞어 배치를 만들지만, 연합 학습에서 각 기기의 데이터는 그 사람의 데이터라 분포가 제각각이다. 이것을 비독립동일분포(non-IID)라고 부른다. 이 상태에서 로컬 학습을 오래 돌리면 각 기기의 모델이 서로 다른 방향으로 멀어지고, 평균을 내면 어느 쪽도 아닌 지점에 떨어진다. 로컬 에폭을 줄이면 이 문제는 완화되지만 그만큼 라운드 수가 늘어 통신 비용이 올라간다. 여기에 기기가 배터리·네트워크 사정으로 중간에 빠지는 일까지 겹친다. 연합 학습을 검토할 때는 프라이버시 이득과 이 운영 비용을 같이 저울에 올려야 한다.

암호문 위의 연산

연합 학습이 데이터를 안 보내는 쪽이라면, 동형암호(Homomorphic Encryption)는 보내되 못 읽게 하는 쪽이다. 암호화된 상태 그대로 연산을 하고, 그 결과를 복호화하면 평문으로 계산한 결과와 같아지는 암호 방식이다. 서버는 입력도 출력도 못 보면서 계산만 대신해 준다. 앞의 두 기법이 "새어 나가는 양을 줄이는" 쪽이라면, 이건 서버가 아예 볼 수 없게 만드는 쪽이다.

# CKKS 동형암호로 암호화된 데이터에 대한 신경망 추론 (개념적)
import tenseal as ts

# 클라이언트: 데이터 암호화
context = ts.context(
    ts.SCHEME_TYPE.CKKS,
    poly_modulus_degree=8192,
    coeff_mod_bit_sizes=[60, 40, 40, 60]
)
context.generate_galois_keys()
context.global_scale = 2**40

plain_data = [1.0, 2.0, 3.0, 4.0]
encrypted_data = ts.ckks_vector(context, plain_data)

# 서버: 암호화된 데이터로 추론 (복호화 없이!)
# 선형 레이어: ax + b
result_enc = encrypted_data * weight_vector + bias

# 클라이언트: 결과 복호화
result = result_enc.decrypt()

CKKS는 실수 근사 연산을 지원하는 방식이라 신경망처럼 소수 계산이 많은 곳에 쓰인다. 키는 클라이언트가 갖고, 서버는 암호문과 공개된 연산 키만 받는다.

가장 강한 보장을 주지만 대가가 크다. 연산이 10배에서 1,000배까지 느려지고 암호문 크기도 평문보다 훨씬 크다. 그래서 대형 언어 모델 추론을 통째로 이 위에서 돌리는 것은 현재로선 현실적이지 않다.

속도만 문제도 아니다. 동형암호가 잘 하는 것은 덧셈과 곱셈이고, 신경망에는 ReLU·시그모이드·소프트맥스처럼 비선형인 연산이 섞여 있다. 이것들은 다항식으로 근사해 넣어야 하는데, 근사 차수를 올리면 정확도가 좋아지는 대신 곱셈 깊이가 늘어 다시 느려진다. 모델을 암호에 맞춰 다시 설계해야 한다는 뜻이다.

그래서 실제 적용은 모델이 작고 데이터의 민감도가 압도적으로 높은 자리에 몰린다 — 금융의 신용 평가, 의료 영상 판독처럼 "서버가 원본을 못 보는 것"이 요구사항으로 못 박힌 도메인이다. 일반적인 서비스에서는 앞의 마스킹과 접근 통제가 비용 대비 훨씬 낫다.

삭제 요청

기술을 다 갖춰도 마지막에 남는 질문이 있다. 어떤 사람이 자기 데이터를 지워 달라고 하면 무엇을 어디까지 지우는가.

재학습 비용

삭제권은 여러 법제가 인정하는 권리다. EU GDPR 제17조가 삭제권(right to erasure)을 두고 있고, 한국 개인정보 보호법도 정보주체의 삭제 요구권과 보유 기간 경과 시 파기 의무를 규정한다.

문제는 AI 시스템에서 "지운다"의 범위다. 원본 데이터베이스에서 행을 지우는 것은 쉽다. 그다음이 길다 — 임베딩을 만들어 넣은 벡터 데이터베이스, 파생 데이터셋, 요청 로그, 프롬프트 캐시, 그리고 백업이다. 백업은 특히 까다롭다. 지난 90일치 스냅샷마다 그 사람이 들어 있는데, 백업을 통째로 다시 쓰는 것은 대개 불가능하다. 실무에서는 백업에 대해 "복원 시 삭제 목록을 다시 적용한다"는 절차로 갈음하는 방식이 쓰인다.

가장 어려운 것이 이미 학습에 들어간 경우다. 파라미터에서 한 사람 몫만 빼내는 연산은 없다. 원칙대로 하면 그 데이터를 뺀 말뭉치로 처음부터 다시 학습해야 하는데, 대형 모델에서 이건 수억 원대의 비용과 수 주의 시간이다. 삭제 요청 한 건마다 그렇게 할 수는 없다.

삭제 요청이 닿아야 하는 자리

머신 언러닝

이 자리를 메우려는 분야가 머신 언러닝(machine unlearning)이다. 특정 데이터의 영향을 사후에 제거하되 전체 재학습보다 훨씬 싸게 하는 것이 목표다.

접근은 크게 둘로 갈린다. 하나는 재학습을 싸게 만드는 구조를 미리 잡아 두는 쪽이다. 학습 데이터를 여러 조각으로 나눠 조각마다 모델을 따로 학습하고 결과를 합치도록 설계하면, 삭제 요청이 오면 그 데이터가 든 조각의 모델만 다시 학습하면 된다. 조각이 열 개면 비용이 10분의 1이 되는 셈인데, 대신 조각을 나눈 만큼 각 모델이 보는 데이터가 줄어 정확도가 깎인다. 다른 하나는 근사 언러닝이다. 그 데이터가 남긴 영향을 추정해 파라미터를 반대 방향으로 밀어 준다. 훨씬 싸지만 정말 지워졌다는 것을 증명하기 어렵다는 약점이 있다. 남은 흔적을 앞의 멤버십 추론으로 찾아내는 평가가 함께 붙는 이유다.

정리하면 지금 시점에서 머신 언러닝은 연구 단계이지, 삭제 요청에 대한 답으로 내놓을 수 있는 완성된 절차가 아니다.

최소 수집

앞의 두 소절을 읽고 나면 결론이 하나로 모인다. 지우는 비용이 이렇게 크면, 애초에 안 넣는 것이 유일하게 값싼 대책이다.

구체적으로는 세 가지다. 수집 최소화 — 지금 기능에 필요 없는 필드는 받지 않는다. 나중에 쓸지도 모른다는 이유로 받아 둔 데이터가 나중에 그대로 부채가 된다. 목적 제한 — 고객 응대용으로 받은 데이터를 모델 학습에 돌려쓰지 않는다. 이건 법적 요건이기도 하지만, 실무적으로는 삭제 요청이 왔을 때 따라가야 할 경로를 하나로 유지하는 장치다. 보관 기간 — 필드마다 수명을 정하고 자동으로 만료시킨다. 사람이 기억해서 지우는 방식은 반드시 실패한다.

그리고 학습에 쓸 데이터는 들어가기 전에 한 번 더 거른다. 개인 식별 정보를 걸러 내고, 중복을 제거하고, 남은 것에 대해서만 학습한다. 이 글에서 본 방어 중 가장 값싸고 가장 확실한 것이 이 지점이다.

방어의 순서

AI 프라이버시는 한 겹으로 끝나지 않는다. 모델은 학습 데이터를 암기하고, 그 암기는 추출·멤버십 추론으로 밖에서 확인될 수 있다. 우리가 직접 학습시키지 않고 API만 써도 요청·로그·캐시·학습이라는 네 갈래로 데이터가 남고, 국외 이전과 하위 처리자까지 따라간다.

대책은 값이 싼 순서로 쌓는다. 안 넣기(최소 수집·마스킹)가 가장 싸고 확실하다. 그다음이 중복 제거와 과적합 억제 같은 학습 위생이다. 수학적 보장이 필요하면 차분 프라이버시로 가되 정확도, 특히 소수 집단의 정확도가 깎인다는 것을 함께 잰다. 데이터를 모을 수 없는 상황이면 연합 학습을 쓰되 그것만으로는 보장이 안 된다는 점을 잊지 않는다. 동형암호는 가장 강하지만 지금은 작고 민감한 도메인의 도구다.

마지막으로, 이 모두가 조직의 원칙과 함께 가야 한다. 무엇을 받을지, 얼마나 둘지, 지워 달라는 요청을 어떻게 처리할지가 정해져 있지 않으면 기술은 그 위에 얹힐 자리가 없다.

연습 문제

개념 문제

  1. 어떤 모델이 특정 개인의 전화번호를 출력했다. 이것이 암기에 의한 유출인지 환각인지 가르려면 무엇을 확인해야 하는가.
    학습 말뭉치에 그 문자열이 실제로 있었는지 대조한다. 출력만 봐서는 두 경우가 구분되지 않는다. 그래서 이 확인은 말뭉치를 가진 쪽, 즉 모델을 학습시킨 쪽만 할 수 있다.
  2. 어떤 병원이 특정 질환 환자 코호트로 모델을 학습시켰다. 멤버십 추론이 성공하면 무엇이 유출되는가.
    그 사람의 진단명이다. 레코드의 내용이 한 글자도 안 나와도, 그 코호트에 속한다는 사실 자체가 질환을 뜻하기 때문이다.
  3. ε=4인 설정과 ε=8인 설정은 보호 강도가 두 배 차이인가.
    아니다. 부등식의 상한이 eεe^{\varepsilon} 이라 지수적으로 벌어진다. 두 값의 비는 e4e^{4}, 약 55배다.
  4. 같은 문자열이 말뭉치에 여러 번 들어 있을 때 암기 확률이 오르는 이유는 무엇인가.
    학습이 그 방향으로 가중치를 되풀이해 밀기 때문이다. 한 번만 나온 문자열의 흔적은 다른 문장들에 묻히지만, 되풀이되면 잡음이 아니라 배워야 할 규칙처럼 보인다.

시나리오 문제

  1. 벤더가 "고객 데이터를 모델 학습에 사용하지 않습니다"라고 밝혔다. 이것으로 확인되지 않은 것은 무엇인가.
    요청 본문·로그·캐시의 보관 주체와 기간이다. 학습은 데이터가 남는 네 자리 중 하나일 뿐이다. 리전과 하위 처리자도 따로 물어야 한다.
  2. 연합 학습을 도입하면 원시 데이터가 서버로 가지 않는다. 이것만으로 프라이버시가 보장되는가.
    아니다. 전송되는 기울기에서 원본을 복원하는 기울기 역전 공격이 가능하다. 보안 집계나 차분 프라이버시를 함께 걸어야 한다.
  3. 마스킹 로그에 "전화번호 2건 마스킹"만 적고 값은 안 적는 이유는 무엇인가.
    값을 적으면 로그 자체가 개인정보 저장소가 되어 지우려던 것을 다시 만드는 셈이 된다. 건수만 있어도 사고 시 범위 추정과 규칙 고장 탐지라는 두 목적은 달성된다.
  4. 고객 응대 초안을 만드는 시스템에서 이름을 자리표로 바꿔 보냈다. 대응표를 다룰 때 지켜야 할 것 둘은 무엇인가.
    원본과 같은 등급으로 보호하는 것과 수명을 짧게 잡는 것이다. 대응표를 가진 쪽은 마스킹된 모든 기록을 되돌릴 수 있으므로, 표가 오래 남으면 마스킹의 효과가 사라진다.

읽어주셔서 감사합니다. 😊

LATEST

AI 기초의 최신 글

AI 기초2026.08.19

가드레일을 테스트한다는 것

가드레일 테스트는 단위 테스트와 성질이 다릅니다. 세트를 두 벌로 나누는 이유, 통과 기준을 절대값 대신 기준선으로 잡는 법, 세트가 오염되는 경로, 표본 크기가 결정에 미치는 영향을 정리합니다.

43 MIN
AI 기초2026.08.19

거절도 설계한다: 안 된다고 말하는 법

거절은 안전 장치의 마지막 동작이면서 사용자가 제품을 평가하는 순간입니다. 켜고 끄는 스위치 대신 다섯 칸 사다리를 쓰는 법, 좋은 거절 문구의 조건, 과잉 거절을 재는 방법을 정리합니다.

41 MIN
AI 기초2026.08.19

콘텐츠 조정: 정책을 판정 가능한 것으로 만들기

정책 문서가 있어도 판정은 안 됩니다. 두 사람이 같은 라벨을 붙일 수 있게 정책을 쪼개는 법, 임계값을 둘로 두는 이유, 검토 큐가 터지지 않게 설계하는 법, 라벨마다 따로 봐야 하는 지표를 정리합니다.

50 MIN