Bài 58: K-Means gom cụm Khách hàng theo độ tuổi, thu nhập và ngân sách chi tiêu

Bài học này Tui hướng dẫn các bạn cách lập trình K-Means để gom cụm khách hàng theo thu nhập, độ tuổi và ngân sách chi tiêu. Chúng ta dùng phương pháp Elbow để tìm ra K Cụm tối ưu nhất để gom, bài học này chúng ta trực quan hóa các cụm bằng 2D và 3D. Đồng thời phân loại chi tiết Khách hàng theo từng cụm để dễ dàng đưa ra các chính sách chăm sóc và tiếp thị phù hợp. Lý thuyết K-Means các bạn đọc trong bài 57

Bạn tải cơ sở dữ liệu MySQL Server “SalesDatabase “trong link:

https://tranduythanh.com/datasets/salesdatabase.rar

Giải nén ra ta có thư mục salesdatabase

Trong MySQL Workbench bạn nhớ tạo một Schema tên là “salesdatabase” sau đó dùng chức năng import đã được học để import dữ liệu vào.

Đồng thời trong quá trình lập trình liên quan tới kết nối cơ sở dữ liệu thì nhớ đổi các thông số kết nối theo máy tính của bạn đã cài đặt.

Cấu trúc bảng dữ liệu như hình dưới đây:

Chúng ta sẽ gom cụm khách hàng bằng K-Means theo nhiều chiều khác nhau, và cho nhận xét. Yêu cầu các thuộc tính liên quan tới Customer dùng để gom cụm sẽ được trích suất từ 2 bảng dữ liệu “customer” và “customer_spend_score” theo SQL script như sau:

select distinct customer.CustomerID, Age, Annual_Income, Spending_Score
from customer, customer_spend_score
where customer.CustomerID=customer_spend_score.CustomerID

Kết quả tiền xử lý dữ liệu:

Trong Pycharm, ta tạo một dự án tên “KMeansCustomer“:

(1) File “Connector.py” là mã lệnh để kết nối, truy vấn dữ liệu:

#python -m pip install mysql-connector-python
import mysql.connector
import traceback
import pandas as pd

class Connector:
    def __init__(self,server=None, port=None, database=None, username=None, password=None):
        self.server=server
        self.port=port
        self.database=database
        self.username=username
        self.password=password
    def connect(self):
        try:
            self.conn = mysql.connector.connect(
                host=self.server,
                port=self.port,
                database=self.database,
                user=self.username,
                password=self.password
            )
            return self.conn
        except:
            self.conn=None
            traceback.print_exc()
        return None

    def disConnect(self):
        if self.conn != None:
            self.conn.close()

    def queryDataset(self, sql):
        try:
            cursor = self.conn.cursor()
            cursor.execute(sql)
            df = pd.DataFrame(cursor.fetchall())
            df.columns=cursor.column_names
            return df
        except:
            traceback.print_exc()
        return None

Tiếp theo ta viết mã lệnh cho “CustomerCluster.py” và thử nghiệm các chức năng:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
from sklearn.cluster import KMeans
import numpy as np

from Connector import Connector

conn=Connector('localhost',3306,'salesdatabase','root','@Obama123')
conn.connect()

sql1="select * from customer"
df1=conn.queryDataset(sql1)
print(df1)

Thực thi mã lệnh trên ta có kết quả:

Tiếp tục bổ sung nối đuôi các mã lệnh tiền xử lý dữ liệu để rút trích thông tin khách hàng, tuổi, thu nhập, điểm tiêu dùng và cuối tập tin “CustomerCluster.py”:

sql2="select distinct customer.CustomerId, Age, Annual_Income, Spending_Score " \
     "from customer, customer_spend_score " \
     "where customer.CustomerId=customer_spend_score.CustomerID"

df2=conn.queryDataset(sql2)
df2.columns = ['CustomerId', 'Age', 'Annual Income', 'Spending Score']

print(df2)

print(df2.head())

print(df2.describe())

Thực hiện mã lệnh trên ta có kết quả:

Tiếp tục bổ sung nối đuôi mã lệnh dưới đây vào “CustomerCluster.py” để thực hiển Histogram:

def showHistogram(df,columns):
    plt.figure(1, figsize=(7,8))
    n = 0
    for column in columns:
        n += 1
        plt.subplot(3, 1, n)
        plt.subplots_adjust(hspace=0.5, wspace=0.5)
        sns.distplot(df[column], bins=32)
        plt.title(f'Histogram of {column}')
    plt.show()

showHistogram(df2,df2.columns[1:])

Thực thi mã lệnh trên ta có kết quả:

Nhìn vào biểu đồ Histogram, ta thấy được sự phân phối có tần suất của các tập dữ liệu liên quan tới Age, Annual Income và Spending Score.

Trong thuật toán K-Means thì chúng ta cần phải xác định trước số cụm. Và ta thường phân vân đâu là số lượng cụm cần phân chia tốt nhất đối với một bộ dữ liệu cụ thể, thì phương pháp Elbow là một cách giúp ta lựa chọn được số lượng các cụm phù hợp dựa vào đồ thị trực quan hoá bằng cách nhìn vào sự suy giảm của hàm biến dạng và lựa chọn ra điểm khuỷ tay (elbow point)

Tiếp tục bổ sung nối đuôi mã lệnh vào “CustomerCluster.py” để tính phương pháp Elbow nhằm tìm ra K cụm tối ưu cho K-Means:

def elbowMethod(df,columnsForElbow):
    X = df.loc[:, columnsForElbow].values
    inertia = []
    for n in range(1 , 11):
        model = KMeans(n_clusters = n,
                   init='k-means++',
                   max_iter=500,
                   random_state=42)
        model.fit(X)
        inertia.append(model.inertia_)

    plt.figure(1 , figsize = (15 ,6))
    plt.plot(np.arange(1 , 11) , inertia , 'o')
    plt.plot(np.arange(1 , 11) , inertia , '-' , alpha = 0.5)
    plt.xlabel('Number of Clusters') , plt.ylabel('Cluster sum of squared distances')
    plt.show()

-init: dùng k-means++

-max_iter: 500 lần

-random_state=42 để dùng cùng 1 phân bố dữ liệu chỗ mỗi lần chạy đảm bảo nhất quán

Ta thử nghiệm tìm K cụm theo phương pháp Elbow với 2 đặc trưng: Age và Spending Score. Ta bổ sung nối đuôi mã lệnh dưới đây vào “CustomerCluster.py”

columns=['Age','Spending Score']
elbowMethod(df2,columns)

Chạy phương thức elbow và có kết quả k=4:

Viết hàm runKMeans() với một đối số truyền vào là 1 ma trận X bất kỳ và số cluster muốn gom, cluster được lấy theo phương thức elbow

Tiếp tục bổ sung nối đuôi mã lệnh runKMeans() vào cuối “CustomerCluster.py”:

def runKMeans(X,cluster):
    model = KMeans(n_clusters=cluster,
                   init='k-means++',
                   max_iter=500,
                   random_state=42)
    model.fit(X)
    labels = model.labels_
    centroids = model.cluster_centers_
    y_kmeans = model.fit_predict(X)
    return y_kmeans,centroids,labels

Thử nghiệm triệu gọi hàm runKMeans(), với columns[Age, Spending Score], cluster=4. Tiếp tục bổ sung mã lệnh nối đuôi vào “CustomerCluster.py” để xem kết quả:

X = df2.loc[:, columns].values
cluster=4
colors=["red","green","blue","purple","black","pink","orange"]

y_kmeans,centroids,labels=runKMeans(X,cluster)
print("y_kmeans:")
print(y_kmeans)
print("centroids:")
print(centroids)
print("labels:")
print(labels)
df2["cluster"]=labels
print("df2:")
print(df2)

Mảng X được lấy theo các columns (các thuộc tính mà customer ta muốn clustering). Đồng thời tạo cột cluster cho df2, nhằm phục vụ cho truy vấn danh sách Customer chi tiết theo từng cụm

Thực thi mã lệnh ở trên ta có kết quả:

Tiếp tục Viết hàm visualizeKMeans nhận vào 6 đối số, hàm này trực quan hóa kết quả gom cụm. Hàm này viết nối đuôi vào “CustomerCluster.py“:

•X: ma trận đã gom cụm

•y_kmeans: nhãn các phần tử được gom cụm

•cluster: số cụm

•title: tiêu đề

•xlabel: nhãn trục x

•ylabel: nhãn trục y

•colors: mảng màu

def visualizeKMeans(X,y_kmeans,cluster,title,xlabel,ylabel,colors):
    plt.figure(figsize=(10, 10))
    for i in range(cluster):
        plt.scatter(X[y_kmeans == i, 0],
                    X[y_kmeans == i, 1],
                    s=100,
                    c=colors[i],
                    label='Cluster %i'%(i+1))
    plt.title(title)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    plt.legend()
    plt.show()

Triệu gọi hàm visualizeKMeans, bằng cách tiếp tục viết nối đuôi mã lệnh vào “CustomerCluster.py“:

visualizeKMeans(X,
                y_kmeans,
                cluster,
                "Clusters of Customers - Age X Spending Score",
                "Age",
                "Spending Score",
                colors)

Kết quả sau khi triệu gọi hàm visualizeKMeans.

Gom cụm customer theo cột Age Spending Score

Thực nghiệm gom cụm theo 2 cột khác: Cột Annual Income Spending Score

Tiếp tục bổ sung nối đuôi mã lệnh sau vào “CustomerCluster.py”:

columns=['Annual Income','Spending Score']
elbowMethod(df2,columns)

Thực thi lệnh trên ta có kết quả:

Như vậy, theo phương thức elbow và có kết quả k=5

Tiến hành thực nghiệm gom cụm với cluster=5 và Cột Annual Income Spending Score

Tiếp tục bổ sung nối đuôi mã lệnh sau vào “CustomerCluster.py”:

X = df2.loc[:, columns].values
cluster=5

y_kmeans,centroids,labels=runKMeans(X,cluster)
print("y_kmeans:")
print(y_kmeans)
print("centroids:")
print(centroids)
print("labels:")
print(labels)
df2["cluster"]=labels
print("df2:")
print(df2)

Mảng X được lấy theo các columns (các thuộc tính mà customer ta muốn clustering). Đồng thời tạo cột cluster cho df2, nhằm phục vụ cho truy vấn danh sách Customer chi tiết theo từng cụm

Thực thi mã lệnh trên ta có kết quả:

Triệu gọi hàm visualizeKMeans để trực quan hóa gom cụm với K=5. Tiếp tục bổ sung nối đuôi mã lệnh vào “CustomerCluster.py”:

visualizeKMeans(X,
                y_kmeans,
                cluster,
                "Clusters of Customers - Annual Income X Spending Score",
                "Annual Income",
                "Spending Score",
                colors)

Kết quả sau khi triệu gọi hàm visualizeKMeans. Gom cụm customer theo cột Annual IncomeSpending Score với K=5

Tiếp tục thực nghiệm gom cụm theo 3 cột khác: Cột Age, Annual IncomeSpending Score

Ta tiếp tục viết nối đuôi mã lệnh vào “CustomerCluster.py”:

columns=['Age','Annual Income','Spending Score']
elbowMethod(df2,columns)

Chạy phương thức elbow và có kết quả k=6

Tiến hành thực nghiệm gom cụm với cluster=6 và Cột Age, Annual IncomeSpending Score

Ta viết nối đuôi mã lệnh vào “CustomerCluster.py”:

X = df2.loc[:, columns].values
cluster=6

y_kmeans,centroids,labels=runKMeans(X,cluster)
print("y_kmeans:")
print(y_kmeans)
print("centroids:")
print(centroids)
print("labels:")
print(labels)
df2["cluster"]=labels
print("df2:")
print(df2)

Mảng X được lấy theo các columns (các thuộc tính mà customer ta muốn clustering). Đồng thời tạo cột cluster cho df2, nhằm phục vụ cho truy vấn danh sách Customer chi tiết theo từng cụm

Thực thi mã lệnh ta có kết quả:

Vì trường hợp này dạng 3 thuộc tính (cột) nên ta bổ sung thêm 1 hàm trực quan hóa 3D kết quả gom cụm:

df: tập dữ liệu đã gom cụm.

columns: dữ liệu các trục. Là dữ liệu có các cột mà ta gom cụm

hover_data: Khi di chuyển chuột vào node sẽ hiển thị chi tiết.

cluser: số cụm

Ta tiếp tục bổ sung mã lệnh vào cuối “CustomerCluster.py”:

def visualize3DKmeans(df,columns,hover_data,cluster):
    fig = px.scatter_3d(df,
                        x=columns[0],
                        y=columns[1],
                        z=columns[2],
                        color='cluster',
                        hover_data=hover_data,
                        category_orders={"cluster": range(0, cluster)},
                        )
    fig.update_layout(margin=dict(l=0, r=0, b=0, t=0))
    fig.show()

Triệu gọi hàm trực quan hóa gom cụm 3D. Ở đây hover_data lấy theo cột dữ liệu để chương trình tự động mapping chính xác dữ liệu hiển thị

Tiêp tục bổ sung mã lệnh vào cuối “CustomerCluster.py”:

hover_data=df2.columns
visualize3DKmeans(df2,columns,hover_data,cluster)

Thực thi mã lệnh ta có kết quả:

Kết quả trực quan hóa gom cụm 3D.

Ta có thể tương tác trên chart 3D này, di chuyển tới từng cụm, vào từng node để xem dữ liệu.

Có  thể tương tác trên các node góc phải màn hình, xuất chart ra tập tin ảnh

(Máy tính cần có cấu hình mạnh để render 3D)

minh họa thêm tương các node và cụm:

Dưới đây là coding đầy đủ của “CustomerCluster.py”:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
from sklearn.cluster import KMeans
import numpy as np

from Connector import Connector

conn=Connector('localhost',3306,'salesdatabase','root','@Obama123')
conn.connect()

sql1="select * from customer"
df1=conn.queryDataset(sql1)
print(df1)

sql2="select distinct customer.CustomerId, Age, Annual_Income, Spending_Score " \
     "from customer, customer_spend_score " \
     "where customer.CustomerId=customer_spend_score.CustomerID"

df2=conn.queryDataset(sql2)
df2.columns = ['CustomerId', 'Age', 'Annual Income', 'Spending Score']

print(df2)

print(df2.head())

print(df2.describe())

def showHistogram(df,columns):
    plt.figure(1, figsize=(7,8))
    n = 0
    for column in columns:
        n += 1
        plt.subplot(3, 1, n)
        plt.subplots_adjust(hspace=0.5, wspace=0.5)
        sns.distplot(df[column], bins=32)
        plt.title(f'Histogram of {column}')
    plt.show()

showHistogram(df2,df2.columns[1:])

def elbowMethod(df,columnsForElbow):
    X = df.loc[:, columnsForElbow].values
    inertia = []
    for n in range(1 , 11):
        model = KMeans(n_clusters = n,
                   init='k-means++',
                   max_iter=500,
                   random_state=42)
        model.fit(X)
        inertia.append(model.inertia_)

    plt.figure(1 , figsize = (15 ,6))
    plt.plot(np.arange(1 , 11) , inertia , 'o')
    plt.plot(np.arange(1 , 11) , inertia , '-' , alpha = 0.5)
    plt.xlabel('Number of Clusters') , plt.ylabel('Cluster sum of squared distances')
    plt.show()

columns=['Age','Spending Score']
elbowMethod(df2,columns)

def runKMeans(X,cluster):
    model = KMeans(n_clusters=cluster,
                   init='k-means++',
                   max_iter=500,
                   random_state=42)
    model.fit(X)
    labels = model.labels_
    centroids = model.cluster_centers_
    y_kmeans = model.fit_predict(X)
    return y_kmeans,centroids,labels

X = df2.loc[:, columns].values
cluster=4
colors=["red","green","blue","purple","black","pink","orange"]

y_kmeans,centroids,labels=runKMeans(X,cluster)
print("y_kmeans:")
print(y_kmeans)
print("centroids:")
print(centroids)
print("labels:")
print(labels)
df2["cluster"]=labels
print("df2:")
print(df2)

def visualizeKMeans(X,y_kmeans,cluster,title,xlabel,ylabel,colors):
    plt.figure(figsize=(10, 10))
    for i in range(cluster):
        plt.scatter(X[y_kmeans == i, 0],
                    X[y_kmeans == i, 1],
                    s=100,
                    c=colors[i],
                    label='Cluster %i'%(i+1))
    plt.title(title)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    plt.legend()
    plt.show()

visualizeKMeans(X,
                y_kmeans,
                cluster,
                "Clusters of Customers - Age X Spending Score",
                "Age",
                "Spending Score",
                colors)

columns=['Annual Income','Spending Score']
elbowMethod(df2,columns)

X = df2.loc[:, columns].values
cluster=5

y_kmeans,centroids,labels=runKMeans(X,cluster)
print("y_kmeans:")
print(y_kmeans)
print("centroids:")
print(centroids)
print("labels:")
print(labels)
df2["cluster"]=labels
print("df2:")
print(df2)

visualizeKMeans(X,
                y_kmeans,
                cluster,
                "Clusters of Customers - Annual Income X Spending Score",
                "Annual Income",
                "Spending Score",
                colors)

columns=['Age','Annual Income','Spending Score']
elbowMethod(df2,columns)

X = df2.loc[:, columns].values
cluster=6

y_kmeans,centroids,labels=runKMeans(X,cluster)
print("y_kmeans:")
print(y_kmeans)
print("centroids:")
print(centroids)
print("labels:")
print(labels)
df2["cluster"]=labels
print("df2:")
print(df2)

def visualize3DKmeans(df,columns,hover_data,cluster):
    fig = px.scatter_3d(df,
                        x=columns[0],
                        y=columns[1],
                        z=columns[2],
                        color='cluster',
                        hover_data=hover_data,
                        category_orders={"cluster": range(0, cluster)},
                        )
    fig.update_layout(margin=dict(l=0, r=0, b=0, t=0))
    fig.show()

hover_data=df2.columns
visualize3DKmeans(df2,columns,hover_data,cluster)

Như vậy tới đây Tui đã hoàn tất hướng dẫn các bạn cách lập trình K-Means để gom cụm khách hàng theo thu nhập, độ tuổi và ngân sách chi tiêu. Chúng ta dùng phương pháp Elbow để tìm ra K Cụm tối ưu nhất để gom, bài học này chúng ta trực quan hóa các cụm bằng 2D và 3D. Đồng thời phân loại chi tiết Khách hàng theo từng cụm để dễ dàng đưa ra các chính sách chăm sóc và tiếp thị phù hợp.

Bài học này rất thiết thực, ứng dụng vào thực tiễn rất lớn, các bạn cố gắng thực hành nhiều lần để hiểu rõ về nó, cũng như có thể áp dụng vào dự án thực tế của doanh nghiệp

Source code đầy đủ của dự án các bạn tải ở đây:

https://www.mediafire.com/file/hgvx5xusot3enhs/KMeansCustomer.rar/file

Bắt đầu từ bài học sau Tui sẽ viết các bài Blog liên quan tới Hợp đồng thông minh và Công nghệ Blockchain, một trong những kiến thức và công nghệ nổi đình nổi đám hiện nay, các bạn chú ý theo dõi.

Chúc các bạn thành công

Leave a Reply