学习Machine Leaning In Action(二):kNN

kNN算法又称为k最近邻算法,是各种分类算法中较简单的一种(有可能是最简单的)。他的思路很好理解,即将待分类向量和所有已知向量求距离,再统计k个最小距离向量所属的类型,最多的类型即为待分类向量的类型。虽然简单,但它的效果却不差,kNN具有较高的准确度,对outliers不敏感,同时对所用的数据类型没有任何要求,使用非常简单。目前kNN常用在文本分类等任务中。

在kNN算法中,最重要的也许就是距离函数了,一般常用欧氏距离作为两个向量的度量:


但这不是一定的,可以用来度量距离的函数有很多,比如在文本分类的应用中,常用的就是余弦距离:



在实际使用kNN的过程中,归一化是必须注意的。由于kNN对数据没有要求,因此不同向量元素间的取值差异可能很大,取值大的元素权重就大,削弱了其他元素的作用,导致分类效果劣化。如下,第一项和第三项的值几乎可以忽略了:


归一化方法也很多,不过最简单也最常用的就是下式所定义的计算方法:


其中min和max分别是所有向量中该元素的最小值和最大值,oldValue是该元素的原始值,newValue是该元素归一化后的值。

另外,用于训练的不同类别的样本数量要尽可能相等,这样才不会在计算距离和投票时,某一类别由于样本数目较多而占优势。


最近由于项目原因,常使用C#,因此习惯性的用C#来写一些东西。kNN对数据类型没有要求,距离函数也可以有很多种,因此要实现一个通用的kNN算法,最好的选择就是将其写为一个C#中的泛型类,该泛型类有两个泛参,TVector和TLabel。TVector是用户自己定义的数据类型,TLabel是分类标签,可以是数字,也可以是字符串等等。在调用分类方法Classify时,需要提供一个用户自己定义的距离函数,最后的分类结果以标签(TLabel)的形式返回。时间紧张,代码拙劣,请谅解。

public class KNN<TVector, TLabel> where TLabel : IComparable
    {
        public TVector[] DataSet
        {
            get;
            private set;
        }

        public TLabel[] LabelSet
        {
            get;
            private set;
        }

        public delegate double DistanceFunction(TVector source, TVector destination);
        
        public void Train(TVector[] samples, TLabel[] labels)
        {
            if (samples.Length != labels.Length)
                throw new ArgumentException("Different length of labels and training_data!");

            DataSet = new TVector[samples.Length];
            LabelSet = new TLabel[labels.Length];

            samples.CopyTo(DataSet, 0);
            labels.CopyTo(LabelSet, 0);
        }

        Dictionary<TLabel, int> m_voting_table = new Dictionary<TLabel, int>(new DicCmp<TLabel>());
        public TLabel Classify(TVector vector, int k, DistanceFunction dist_func)
        {
            if (DataSet == null)
                throw new MemberAccessException("There's no vectors in DataSet");
            if (k > DataSet.Length)
                throw new ArgumentException("The value of k is greater than the vectors' count");

            //Calculate distances for all vectors in the dataset
            KeyValuePair<TLabel, double>[] label_dist_pair = new KeyValuePair<TLabel, double>[DataSet.Length];
            for (int i = 0; i < DataSet.Length; ++i)
            {
                double dist = dist_func(vector, DataSet[i]);
                label_dist_pair[i] = new KeyValuePair<TLabel, double>(LabelSet[i], dist);
            }
            //Sort via distance.
            Array.Sort(label_dist_pair, new SortCmp<TLabel>());

            //Do majority voting from the k-nearest neighbors.
            m_voting_table.Clear();
            for (int i = 0; i < k; ++i)
            {
                if (m_voting_table.ContainsKey(label_dist_pair[i].Key))
                {
                    m_voting_table[label_dist_pair[i].Key]++;
                }
                else
                {
                    m_voting_table.Add(label_dist_pair[i].Key, 1);
                }
            }

            //Find out the maximum-frequency label. And that is our result.
            int max_freq = 0;
            TLabel label_to_return = LabelSet[0];
            foreach (var item in m_voting_table)
            {
                if (item.Value > max_freq)
                {
                    max_freq = item.Value;
                    label_to_return = item.Key;
                }
            }

            return label_to_return;
        }
    }

最后总结一下kNN的优缺点。

优点:

1、准确率高;

2、实现简单;

3、对outliers不敏感,因为使用多数投票方式决定分类结果;

4、对数据类型无要求,使用灵活方便;

缺点:

1、所需存储空间大,需要将所有训练数据保存起来;

2、计算量大,每一次分类都要遍历一遍所有数据,且对每一对向量都要求距离;

3、对k值的选择没有很好的方法,一般通过实验方式得到k值;

猜你喜欢

转载自blog.csdn.net/aichipmunk/article/details/9377013