23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
랭턴의 개미(Langton's Ant) 는 매우 단순한 규칙을 따르는 2차원 격자 위의 개미입니다.
해당 개미는 흰색 칸에서는 시계방향으로 90도 회전하고, 칸 색을 반전(흰색->검은색, 검은색->흰색)한 뒤 한 칸 전진하며,
검은색 칸에서는 반시계방향으로 90도 회전하고, 칸 색을 반전하고 한 칸을 전진하는데요,
이 단순한 규칙만으로도 격자 위에 신기한 패턴을 그리게 됩니다.
아래는 초기 - 중기 - 후기로 나누어 랭턴의 개미의 행동 과정을 설명한 내용입니다.
처음에는 모든 격자가 흰색이고, 개미가 가운데에 위치합니다.
개미는 규칙에 따라 움직이지만 초기에는 격자 위에 남기는 패턴이 매우 단순하거나 대칭적입니다.
몇 백 번 움직인 후에는 격자 위에 무질서한 흑백 무늬가 형성되기 시작합니다.
수천 번의 이동 후, 개미는 격자 위를 마치 무작위처럼 움직이며 불규칙하고 복잡한 패턴을 만듭니다.
이 단계에서는 격자 위에 남겨지는 흑백 무늬가 매우 혼란스럽고, 어떤 규칙성도 보이지 않습니다.
약 만 번의 이동 이후, 개미는 갑자기 반복적인 고속도로 패턴을 만들기 시작합니다.
이 패턴은 104번 이동마다 반복되며, 개미는 이 패턴을 계속해서 따라가면서 격자 한쪽으로 빠져나갑니다.
이처럼 단순한 규칙이 반복될 때 예측할 수 없는 복잡한 패턴이 나타나고, 결국에는 안정적인 반복 구조로 수렴하는 것이 랭턴의 개미의 특징입니다.
마케팅 시뮬레이션도 랭턴의 개미와 유사하게, 단순한 규칙 (ex - 고객 이동, 채널 전환 등)이 반복되면 예측 불가능한 변화와 복잡한 결과가 나타날 수 있습니다.
단기적으로는 변화가 불규칙하지만, 장기적으로는 랭턴의 개미처럼 안정적인 정상 상태로 수렴하는 경향이 있습니다.
새로운 사업이나 서비스를 시작할 때, 고객이 어떤 채널을 주로 이용하는지, 그리고 시간이 지남에 따라 그 점유율이 어떻게 변화하는지 예측하는 것은 매우 중요합니다.
마르코프 체인을 활용하면 랭턴의 개미처럼 각 채널의 점유율이 '정상 상태'로 어떻게 수렴하는지 시뮬레이션할 수 있습니다.
기본적으로 고객들이 아래와 같은 확률로 채널 간 전이를 한다고 가정합니다.
상담센터 50%, 오프라인 매장 30%, 온라인 앱 15%, 웹/이메일 등 기타 5%
그리고 기본적으로 랭턴의 개미처럼, 고객들은 현재 채널에 대해 20%정도의 추가적인 매력도를 느낀다고 가정합니다.
states = ["상담센터", "오프라인 매장", "온라인 앱", "웹/이메일"]
P = np.array([
[0.70, 0.20, 0.08, 0.02],
[0.30, 0.50, 0.15, 0.05],
[0.40, 0.10, 0.45, 0.05],
[0.60, 0.20, 0.15, 0.05]
])
init = np.array([0.50, 0.30, 0.15, 0.05])각 채널 별 점유율은 아래와 같은 루트를 통해 수렴하게 됩니다.
초기 상태와는 달리, 50%였던 상담센터는 54% 가량으로, 30%의 오프라인 매장은 26%로, 15%였던 앱은 16%로, 5%였던 웹/이메일은 약 3% 가량으로 변경되게 됩니다.
만약 고객들이 경험한 채널에 대해 좀 더 애착을 가지고 자주 사용한다고 가정하고, 해당 채널에 1.4배의 가중치를 추가적으로 더 주게 되면 어떻게 바뀔까요?
states = ["상담센터", "오프라인 매장", "앱", "웹/이메일"]
P = np.array([
[0.70, 0.20, 0.08, 0.02],
[0.30, 0.50, 0.15, 0.05],
[0.40, 0.10, 0.45, 0.05],
[0.60, 0.20, 0.15, 0.05]
])
init = np.array([0.50, 0.30, 0.15, 0.05])
# 현재 경험한 채널에 40%의 가중치 추가
def adjust_transition_matrix(P, alpha=1.2, beta=0.8):
P_new = np.zeros_like(P)
for i in range(P.shape[0]):
for j in range(P.shape[1]):
if i == j:
P_new[i, j] = P[i, j] * alpha
else:
P_new[i, j] = P[i, j] * beta
P_new[i, :] /= P_new[i, :].sum()
return P_new
P_adjusted = adjust_transition_matrix(P)이 때는 초기에 큰 비중을 가지고 있던 상담센터의 비율이 매우 높아지고, 앱은 잠시 비중이 높아지다가 다시 수렴하며, 나머지 채널들은 각각 감소하는 방향으로 바뀌게 됩니다.
디지털 전환과 리소스 효율화로 인해, 온라인 채널을 활성화하려고 합니다.
온라인 앱이나 웹에 추가적인 쿠폰을 주거나, 무료 배송 등의 프로모션을 진행해, 상담센터와 오프라인 매장 방문 비중을 줄이고 (-10%), 앱과 웹/이메일의 비중을 높이려고 합니다. (+20%)
프로모션을 통해 고객 분들이 현재 경험한 채널에서 벗어나 좀 더 다양한 채널을 경험할 수 있게 유도하였습니다. (+40% -> -40%)
# 채널 의존도 조정 (+40% -> -40%)
def adjust_transition_matrix(P, alpha=0.8, beta=1.2):
P_new = np.zeros_like(P)
for i in range(P.shape[0]):
for j in range(P.shape[1]):
if i == j:
P_new[i, j] = P[i, j] * alpha
else:
P_new[i, j] = P[i, j] * beta
# 정규화
P_new[i, :] /= P_new[i, :].sum()
return P_new
P_adjusted = adjust_transition_matrix(P)
...
P_promo = P_adjusted.copy()
# 앱, 웹/이메일로의 전이 확률 증가 (+20%)
P_promo[:, 2] *= 1.2; P_promo[:, 3] *= 1.2
# 오프라인, 상담센터로의 전이 확률 감소 (-10%)
P_promo[:, 0] *= 0.9; P_promo[:, 1] *= 0.9
# 정규화
for i in range(P_promo.shape[0]):
P_promo[i, :] /= P_promo[i, :].sum()하지만 오프라인 매장의 경우에는 유의미한 수치로 줄어들었지만, 상담센터의 비중은 여전히 높고, 웹/이메일의 경우 거의 비중이 변하지 않았습니다.
그렇다면 좀 더 강한 프로모션을 진행해본다면 어떨까요? 기존 채널에서 벗어나진 않고는 못 버틸 정도로 매력적인 프로모션을 제공하겠습니다.
더 강력한 프로모션을 진행해, 상담센터와 오프라인 매장 방문 비중을 줄이고 (-30%), 앱과 웹/이메일의 비중을 높이려고 합니다. (+50%)
강력한 프로모션을 통해 고객 분들이 현재 경험한 채널에서 벗어나 좀 더 다양한 채널을 경험할 수 있게 유도하였습니다. (-40% -> -80%)
# 채널 의존도 조정 (-40% -> -80%)
def adjust_transition_matrix(P, alpha=0.6, beta=1.4):
P_new = np.zeros_like(P)
for i in range(P.shape[0]):
for j in range(P.shape[1]):
if i == j:
P_new[i, j] = P[i, j] * alpha
else:
P_new[i, j] = P[i, j] * beta
# 정규화
P_new[i, :] /= P_new[i, :].sum()
return P_new
P_adjusted = adjust_transition_matrix(P)
P_promo = P_adjusted.copy()
# 온라인앱, 웹/기타로의 전이 확률 증가 (+50%)
P_promo[:, 2] *= 1.5; P_promo[:, 3] *= 1.5
# 오프라인, 상담센터로의 전이 확률 감소 (-30%)
P_promo[:, 0] *= 0.7; P_promo[:, 1] *= 0.7
# 정규화
for i in range(P_promo.shape[0]):
P_promo[i, :] /= P_promo[i, :].sum()상담센터와 오프라인 매장 방문이 유의미하게 줄어들고, 앱 방문률은 큰 폭으로 늘어나 상담센터를 이은 2위로 올라갔습니다.
하지만 웹/이메일의 경우 파격적인 프로모션에도 불구하고, 조금 늘어나긴 했지만 엇비슷한 값으로 수렴하게 되었습니다.
실질적으로 웹/이메일의 점유율을 올리려면 전이행렬의 기본값 자체를 높이는 구조적 변화가 필요합니다.
랭턴의 개미와 마르코프 체인을 활용한 시뮬레이션을 통해, 단순한 규칙이 반복될 때 초기에는 예측할 수 없는 복잡한 변화가 나타나지만,
시간이 지나면 결국 안정적인 상태로 수렴한다는 사실을 확인할 수 있었습니다.
채널 점유율 역시 단기적으로는 다양한 변동 요인에 따라 불규칙하게 움직이지만, 장기적으로는 각 채널의 고유한 전이 확률에 따라 정상 상태로 수렴합니다.
특히, 단순한 채널 이동 확률 상승이나 강화만으로는 웹/이메일의 점유율을 크게 높이기 어렵다는 점이 시뮬레이션 결과에서 드러났습니다.
웹/이메일 채널의 점유율을 높이려면 기본값 자체를 구조적으로 변경하는 마케팅 전략이 필요하다는 사실을 알 수 있었습니다.
이번 시뮬레이션은 단순한 초기 상태와 확률을 통한 간단한 시뮬레이션이지만, 고객의 채널 경험을 유도하거나 점유율을 예측하는 작업이 얼마나 복잡하고 어려운지,
그리고 데이터 기반의 구조적 접근이 얼마나 중요한지 실감할 수 있었습니다.
아래는 랭턴의 개미 시뮬레이션을 파이썬으로 구현한 예시입니다. 이 코드를 통해 랭턴의 개미를 각 단계별로 시뮬레이션 해보실 수 있습니다.
관련 코드 :
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
class LangtonsAnt:
def __init__(self, grid_size=100):
self.N = grid_size
self.grid = np.zeros((self.N, self.N), dtype=int)
self.x = self.y = self.N // 2
self.dir = 0
def step(self):
if self.grid[self.y, self.x] == 0:
self.dir = (self.dir + 1) % 4
self.grid[self.y, self.x] = 1
else:
self.dir = (self.dir - 1) % 4
self.grid[self.y, self.x] = 0
if self.dir == 0: self.y = (self.y - 1) % self.N
elif self.dir == 1: self.x = (self.x + 1) % self.N
elif self.dir == 2: self.y = (self.y + 1) % self.N
else: self.x = (self.x - 1) % self.N
def run_and_capture(self, target_steps):
captured = {}
for step in range(1, max(target_steps) + 1):
self.step()
if step in target_steps:
captured[step] = (self.grid.copy(), self.get_position())
return captured
def get_position(self):
return self.x, self.y, self.dir
target_steps = [1, 100, 1000,
3000, 5000, 10000,
15000, 20000, 25000]
ant = LangtonsAnt(100)
results = ant.run_and_capture(target_steps)
fig = make_subplots(rows=3, cols=3,
subplot_titles=[f"Step {step}" for step in target_steps],
vertical_spacing=0.08, horizontal_spacing=0.05)
for i, step in enumerate(target_steps):
row, col = divmod(i, 3)
grid, (x, y, dir) = results[step]
fig.add_trace(
go.Heatmap(
z=grid,
colorscale=[[0, '#FFFFFF'], [1, '#000000']],
showscale=False,
hoverinfo='none'
),
row=row+1, col=col+1
)
fig.add_trace(
go.Scatter(
x=[x], y=[y],
mode='markers',
marker=dict(
symbol='triangle-up' if dir == 0 else
'triangle-right' if dir == 1 else
'triangle-down' if dir == 2 else 'triangle-left',
size=14,
color='#FF0000',
line=dict(width=2, color='#000000')
),
showlegend=False
),
row=row+1, col=col+1
)
fig.update_layout(
title="랭턴의 개미 진화 단계 (초기 → 불규칙 → 고속도로)",
width=1200,
height=1600,
margin=dict(t=100),
plot_bgcolor='#F0F0F0'
)
fig.update_xaxes(showticklabels=False, zeroline=False)
fig.update_yaxes(showticklabels=False, zeroline=False, scaleanchor="x")
fig.show()
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.