Trongchƣơng3,luậnvănđãphântíchchitiếtbàitoánphâncụmphân loại khách hàng sử dụng dịch vụ Viễn thông tại VNPT Hải Phòng.
- Khảosátnguồndữliệulịchsửcuộc gọi.
- Quátrìnhtiềnxửlýdữliệulịchsử,tạoranguồndữliệuđầu vào
phùhợpchothuậttoánPAM
- TriểnkhaithuậttoánphâncụmPAM
- Đánhgiákếtquảthuđƣợcsauquátrìnhphâncụm.
62
KẾT LUẬN
Các vấn đề đã đƣợc tìm hiểu trong luận văn
Luận văn tập trung nghiên cứu tổng quan về KPDL nói chung và PCDL nói riêng và áp dụng giải thuật PAM để phân loại khách hàng sử dụng dịch vụ Viễn thông. Đây là bƣớc khởi đầu trong quá trình tìm hiểu những vấn đề cần quan tâm khi giải quyết các bài toán khai phá dữ liệu trong thực tế. Những kết quả mà luận văn đã thực hiện:
+ Về lý thuyết, luận văn tập trung tìm hiểu một số kỹ thuật phân cụm. + Về thực tiễn, luận văn đã đƣa ra các kết quả cài đặt thử nghiệm của bài toán phân cụm dịch vụ khách hàng sử dụng Viễn thông bằng thuật toán PAM. Đây là một bài toán ứng dụng phân cụm dựa trên lƣu lƣợng cuộc gọi và dịch vụ giá trị gia tăng. Tùy vào từng bài toán thực tế mà có thể phát triển thành sản phẩm hoàn chỉnh có thể ứng dụng rộng rãi.
Qua quá trình thực nghiệm và nghiên cứu lý thuyết có thể đƣa ra một số kết luận nhƣ sau:
• Mỗi một giải thuật phân cụm áp dụng cho một số mục tiêu và kiểu dữ liệu nhất định.
• Mỗi giải thuật có một mức độ chính xác riêng và khả năng thực hiện trên từng kích thƣớc dữ liệu là khác nhau. Điều này còn tuỳ thuộc vào cách thức tổ chức dữ liệu ở bộ nhớ chính, bộ nhớ ngoài... của các giải thuật.
• Khai phá dữ liệu sẽ hiệu quả hơn khi bƣớc tiền xử lý, lựa chọn thuộc tính, mô hình đƣợc giải quyết tốt.
Hƣớng nghiên cứu tiếp theo
Hƣớng phát triển tiếp theo của đề tài là đi sâu vào nghiên cứu các kỹ thuật phân cụm áp dụng với cơ sở dữ liệu phức tạp hơnsẽphát triển thành bài toán vớidữliệu lớn hơn, bao quát hơn, nhiều chọn lựa hơn nhƣ phân cụm dựa trên các loại hình doanh nghiệp khác nhau, với dữ liệu khác nhau...
63
TÀILIỆUTHAMKHẢO Tài liệu tiếng Việt
[1].Lê Thu Trang,“Phương pháp phân cụm dữ liệu và ứng dụng”,Luậnvănthạcsĩ,TrƣờngĐạihọcCông nghệ Thông tin và Truyền thông – Đại học Thái Nguyên,2008.
[2].Nguyễn Văn Sự,“Khai phá dữ liệu bằng cây quyết định và ứng dụng trong hệ hỗ trợ quyết định”,Luậnvănthạcsĩ, TrƣờngĐạihọcCông nghệ Thông tin và Truyền thông – Đại học Thái Nguyên,2010.
Tài liệutiếng Anh
[3].JiaweiHan,JianPei.DataMining:ConceptsandTechniquesSecondEditio n.DianeCerra(2006). [4].ZhexueHuang.Extensionstothek- MeansAlgorithmforClusteringLargeDataSetswithCategoricalValues.KluwerAca demic(1998). [5].N.Hussein.AFastGreedyk-meansAlgorithm(2002). [6]HoTuBao,KnowledgeDiscoveryandDataMining,2000.
[7].Aloise, D.; Deshpande, A.; Hansen, P.; Popat, P. (2009). "NP- hardness of Euclidean sum-of-squares clustering".Machine Learning 75: 245– 249.
[8]. B. Mirkin, Clustering for data mining: A data recovery approach. London: Chapman and Hall, 2005.
[9]. R. Maitra, “Initializing partition-optimization algorithms,” IEEE/ACM Transactions on Computational Biology and Bioinformatics, vol. 6, pp. 144–157, 2009.
[10]. Elkan, C. (2003). "Using the triangle inequality to accelerate k- means". Proceedings of the Twentieth International Conference on Machine Learning (ICML).
[11]. Kanungo, T.; Mount, D. M.; Netanyahu, N. S.; Piatko, C. D.; Silverman, R.; Wu, A. Y. (2002). "An efficient k-means clustering algorithm:
64
Analysis and implementation". IEEE Trans. Pattern Analysis and Machine Intelligence 24: 881–892.
[12]OrenZamirandOrenEtzioni,WebdocumentClustering:AFeasibilityDe monstration, Universityof Washington,USA, ACM,1998.
[13] Raymond T. Ng and Jiawei Han. CLARANS: A Method for Clustering Objects for Spatial Data Mining. IEEE Transactions on Knowledge and Data Engineering,14(5):1003-016, 2002.
[14] Anomaly Detection in Temperature Data Using DBSCAN
Algorithm: Erciyes Univeristy,
http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=5946052&tag=1Mete
65
PHỤ LỤC
Mã nguồn chƣơng trình phân cụm bằng thuật toán PAM
using System;
using System.Collections.Generic; namespace AlgorithmPAM
{
public class PAM {
protected BaseMatrix data;
protected DistanceMatrix distances; protected int nClusters;
protected Clusters clusters;
// ordered index of element subset
// all immediate data and output should have the same size as idx
// (only the input BaseMatrix data has the full original set of data elements) // (indexing of DistanceMatrix is handled by the class itself)
int[] idx;
// clustering cost, to be minimized double cost;
// distance between element and closest medoid protected double[] nearestDistances;
// distance between element and second closest medoid double[] nextNearestDistances;
// nearest medoid of each element int[] nearestMedoids;
66 // next-nearest medoid of each element int[] nextNearestMedoids;
// set of medoids
HashSet<int> medoids;
// set of non-meoids (maintain for finding swap candidates) HashSet<int> nonmedoids;
// set of all indexed elements
// required since Java's HashSet cannot use native types int[] elements;
int maxSwaps = 1000;
public PAM(BaseMatrix data) : this(data, null, null)
{ }
public PAM(BaseMatrix data, DistanceMatrix distances, int[] idx) {
this.data = data;
if (data == null || data.NRows() == 0) {
throw new Exception("Data matrix is empty."); }
if (idx == null) {
// initially, use index all data elements in original order int m = data.NRows();
idx = new int[m];
67 { idx[i] = i; } } this.idx = idx; if (distances == null) {
this.distances = new DistanceMatrix(data, idx); } else { this.distances = distances.subset(idx); } this.clusters = null; }
public Clusters cluster(int k) {
int n = size(); if (n == 0) {
throw new Exception("No data elements are indexed."); }
if (k > n) {
throw new Exception("Number of clusters must be less than the number of data elements.");
68 }
else if (k == n) {
// build trivial single clusters return new Clusters(k); }
this.nClusters = k; initialize();
buildPhase(); swapPhase();
clusters = new Clusters(nearestMedoids, getCost()); clusters.center = medoids;
return clusters; }
/**
* Size. Number of data elements. */
public int size() {
return idx.Length; }
/**
* Calculate the clustering cost: sum of distances to cluster medoids. * @return cost
*/
private double getCost() {
69 double c = 0;
for (int i = 0; i < nearestDistances.Length; ++i) {
c += nearestDistances[i]; }
return c; }
private void initialize() {
int m = size();
nearestDistances = new double[m]; nextNearestDistances = new double[m]; nearestMedoids = new int[m];
nextNearestMedoids = new int[m]; elements = new int[m];
medoids = new HashSet<int>(); nonmedoids = new HashSet<int>(); for (int ii = 0; ii < m; ++ii)
{
// initialize distances to infinity
nearestDistances[ii] = nextNearestDistances[ii] = Double.PositiveInfinity;
// initialize medoids to non-valid indices, s.t. unexpected bugs trigger indexing error
nearestMedoids[ii] = nextNearestMedoids[ii] = -1; elements[ii] = ii;
70 nonmedoids.Add(elements[ii]); }
} /**
* BUILD phase. Select a initial set of k medoids. */
private void buildPhase() {
int m = size(); // select first medoid
// find element with minimum total distance to all other elements double[] totalDistances = new double[m];
for (int ii = 0; ii < m; ++ii) {
// sum distances to all other elements // assume distance to itself is 0
double d = 0; for (int jj = 0; jj < m; ++jj) { d += distances.getValue(ii, jj); } totalDistances[ii] = d; }
double minDistance = totalDistances[0]; int minIndex = 0;
for (int ii = 0; ii < m; ++ii) {
71 if (totalDistances[ii] < minDistance) { minDistance = totalDistances[ii]; minIndex = ii; } }
// add element to medoid set addMedoid(minIndex);
// select remaining k - 1 medoids double[] gains = new double[m];
for (int kk = 1; kk < nClusters; ++kk) {
// consider each i as medoid candidate for (int ii = 0; ii < m; ++ii)
{
// if ii is already a medoid, it has negative gain to prevent it from being selected again
if (medoids.Contains(elements[ii])) { gains[ii] = -1.0; } else { double gain = 0;
// for each non-medoid j != i, calculate the gain for (int jj = 0; jj < m; ++jj)
72
if (jj == ii || medoids.Contains(elements[jj])) continue; if (nearestDistances[jj] > distances.getValue(ii, jj)) {
// add i will improve j's nearest distances
// (if selected, i will be the new nearest neighbour of j) gain += nearestDistances[jj] - distances.getValue(ii, jj); }
}
gains[ii] = gain; }
}
// select candidate with maximum gain double maxGain = Double.NegativeInfinity; int maxIndex = -1;
for (int ii = 0; ii < m; ++ii) { if (gains[ii] > maxGain) { maxGain = gains[ii]; maxIndex = ii; } }
// add element to medoid set addMedoid(maxIndex);
}
// check that the number of medoids match the expected if (nClusters != medoids.Count)
73 {
throw new Exception("Expected error in BUILD phase: Number of medoids does not match parameter k.");
} } /**
* SWAP phase. Attempt to improve clustering quality by exchanging medoids with non-medoids.
*/
private void swapPhase() {
bool notConverged = true; bool continueLoop = true; int nSwaps = 0;
while (notConverged && continueLoop) {
notConverged = false; continueLoop = false;
IEnumerator<int> medIt = medoids.GetEnumerator(); while (medIt.MoveNext() && continueLoop)
{
int ii = medIt.Current;
IEnumerator<int> nonmedIt = nonmedoids.GetEnumerator(); while (nonmedIt.MoveNext())
{
int hh = nonmedIt.Current;
74
// by calculating gains by all other elements
// Calculate cumulative change to distance to nearest medoid for all nonmedoids j != h
double change = 0;
IEnumerator<int> nonmedIt2 = nonmedoids.GetEnumerator(); while (nonmedIt2.MoveNext()) { int jj = nonmedIt2.Current; if (jj == hh) continue; double d = nearestDistances[jj]; if (distances.getValue(ii, jj) > d) {
// if removed, i will have no impact if (distances.getValue(jj, hh) < d) {
// if selected, h will improve nearest distance for j change += distances.getValue(jj, hh) - d;
} } else {
// i cannot be closer than the nearest neighbour for j; // therefore, distances[i][j] == d
// and i is currently the nearest neighbour for j double e = nextNearestDistances[jj];
if (distances.getValue(jj, hh) < e) {
75
// if i and h are swapped, h will become the nearest neighbour
// nearest distance for j may improve or worsen change += distances.getValue(jj, hh) - d;
} else {
// if i is removed, the current next-nearest of j will be promoted to nearest change += e - d; } } } if (change < 0) {
// distance to nearest medoid summed over all nonmedoids is improved: swap
swap(hh, ii);
//System.out.print("Swap " + hh + " and " + ii + " for change = " + change + "\n");
// non-convergence if any swap occurs, up to a maximum number of swaps (to guard against swap cycles)
if (nSwaps++ < maxSwaps) { notConverged = true; } else {
76 continueLoop = false;
}
// reset iterator medIt = medoids.GetEnumerator(); // break out of inner loop to consider next medoid break; } } } } }
private void addMedoid(int add) {
medoids.Add(elements[add]); nonmedoids.Remove(elements[add]); updateNearest(add, -1); }
private void swap(int add, int remove) {
medoids.Add(elements[add]);
nonmedoids.Remove(elements[add]); medoids.Remove(elements[remove]); nonmedoids.Add(elements[remove]);
77 updateNearest(add, remove); }
/**
* Update nearest and next-nearest distances.
* Does not check whether {@code added} or {@ removed} have been added to or removed from the medoid set.
* FIXME optimize
* @param added Index of element added to medoid set (-1 for none)
* @param removed Index of element removed from medoid set (-1 for none)
*/
private void updateNearest(int added, int removed) {
int m = size(); if (added >= 0) {
// added index is valid
// check if any nearest distance improves for (int ii = 0; ii < m; ++ii)
{
double d = distances.getValue(ii, added); if (d < nearestDistances[ii])
{
// element i is nearer to added medoid than previous nearest: update
78
int oldMedoid = nearestMedoids[ii]; nearestMedoids[ii] = added;
nearestDistances[ii] = d;
// pump nearest distance to next-nearest distance nextNearestMedoids[ii] = oldMedoid;
nextNearestDistances[ii] = oldDistance; }
else if (d < nextNearestDistances[ii]) {
// element i is nearer to added medoid than previous next-nearest: update nextNearestMedoids[ii] = added; nextNearestDistances[ii] = d; } } } if (removed >= 0) {
// removed index is valid
// check if the removed medoid is the nearest or next-nearest of any element
for (int ii = 0; ii < m; ++ii) {
if (nearestMedoids[ii] == removed) {
79
// promote next-nearest to nearest
nearestMedoids[ii] = nextNearestMedoids[ii]; nearestDistances[ii] = nextNearestDistances[ii]; // find new next-nearest
updateNextNearest(ii); }
else if (nextNearestMedoids[ii] == removed) {
// find new next-nearest updateNextNearest(ii); } } } } /**
* Update next nearest for element i. * Assume nearest medoid is already set. * @param ii element index to be updated */
private void updateNextNearest(int ii) {
int nearestMedoid = nearestMedoids[ii]; // find the next-nearest
IEnumerator<int> it = medoids.GetEnumerator(); double minDistance = Double.PositiveInfinity;
80 int nextNearestMedoid = -1; while (it.MoveNext())
{
int jj = it.Current;
// ignore if j is the nearestMedoid, since we are interested in the next- nearest if (jj == nearestMedoid) continue; if (distances.getValue(ii, jj) < minDistance) { minDistance = distances.getValue(ii, jj); nextNearestMedoid = jj; } } // update nextNearestDistances[ii] = minDistance; nextNearestMedoids[ii] = nextNearestMedoid; } } }