基于KNN的简单分类实验

目录

1.K邻近算法简单介绍

2.knn算法原理

3.基于knn的邮件分类实例

1.K邻近算法简单介绍

K近邻算法(K-Nearest Neighbors,简称KNN)是一种用于分类和回归的统计方法。KNN 可以说是最简单的分类算法之一,同时,它也是最常用的分类算法之一。

注意:KNN 算法是有监督学习中的分类算法,它看起来和另一个机器学习算法 K-means 有点像(K-means 是无监督学习算法),但却是有本质区别的。

2.knn算法的原理

k-近邻算法(k-Nearest Neighbour algorithm)的工作原理:给定一个已知标签类别的训练数据集,输入没有标签的新数据后,在训练数据集中找到与新数据最邻近的 k 个实例,如果这 k 个实例的多数属于某个类别,那么新数据就属于这个类别。即由那些离新数据最近的 k 个实例来投票决定新数据归为哪一类

最邻近分类算法是数据挖掘分类(classification)技术中最简单的算法之一,其指导思想是”近朱者赤,近墨者黑“,即由你的邻居来推断出你的类别。

该算法一般流程为:

  1. 对未知类别属性的数据集中的每个点依次执行一下操作:

  2. (1)计算已知类别数据集中的点与当前点之间的距离
  3. (2)按照距离递增次序排序
  4. (3)选取与当前点距离最小的k个点

KNN三要素

1、K值的选择

  • 对于K值的选择,一般根据样本分布选择一个较小的值,然后通过交叉验证来选择一个比较合适的最终值;
  • 当选择比较小的K值的时候,表示使用较小领域中的样本进行预测,训练误差会减小,但是会导致模型变得复杂,容易导致过拟合;
  • 当选择较大的K值的时候,表示使用较大领域中的样本进行预测,训练误差会增大,同时会使模型变得简单,容易导致欠拟合;

2、距离度量

  • 一般使用欧几里德距离
  • 本篇博客用到的是曼哈顿距离公式

  •  曼哈顿距离中的距离计算:(本篇博客实例用到的距离计算方法)



    曼哈顿距离中的距离计算公式比欧氏距离的计算公式看起来简洁很多,只需要把两个点坐标的 x 坐标相减取绝对值,y 坐标相减取绝对值,再加和。

    从公式定义上看,曼哈顿距离一定是一个非负数,距离最小的情况就是两个点重合,距离为 0,这一点和欧氏距离一样。曼哈顿距离和欧氏距离的意义相近,也是为了描述两个点之间的距离,不同的是曼哈顿距离只需要做加减法,这使得计算机在大量的计算过程中代价更低,而且会消除在开平方过程中取近似值而带来的误差。

    欧氏距离公式:

    d=∑mi=1(xi−yi)2−−−−−−−−−−−−√d = \sqrt{\sum_{i=1}^m(x_i - y_i)^2}d=i=1∑m​(xi​−yi​)2​
    例如:求点(1,0,0,1)(1,0,0,1)(1,0,0,1)和(7,6,9,4)(7,6,9,4)(7,6,9,4)之间的距离:
    (7−1)2+(6−0)2+(9−0)2+(4−1)2−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−√\sqrt{(7-1)^2+(6-0)^2+(9-0)^2+(4-1)^2}(7−1)2+(6−0)2+(9−0)2+(4−1)2​

3、决策规则

KNNKNNKNN在做回归和分类的主要区别在于最后做预测时的决策方式不同:

(1)分类预测规则:一般采用多数表决法或者加权多数表决法

1. 多数表决法:

  • 每个邻近样本的权重是一样的,也就是说最终预测的结果为出现类别最多的那个类;
  • 如图,待预测样本被预测为红色圆

2. 加权多数表决法:

  • 每个邻近样本的权重是不一样的,一般情况下采用权重和距离成反比的方式来计算,也就是说最终预测结果是出现权重最大的那个类别;
  • 如图,红色圆到待预测样本的距离为3,蓝色方块到待预测样本的距离为2,权重与距离成反比,所以蓝色的权重比较大,待预测样本被预测为蓝色方块。

(2)回归预测规则:一般采用平均值法或者加权平均值法
 

  1. 平均值法
  • 每个邻近样本的权重是一样的,也就是说最终预测的结果为所有邻近样本的目标属性值的均值;
  1. 加权平均值法
  • 每个邻近样本的权重是不一样的,一般情况下采用权重和距离成反比的方式来计算,也就是说在计算均值的时候进行加权操作;

3.基于KNN的实例:邮件分类

读取数据

实验数据集共11572封邮件,包括9648封非垃圾邮件,1924封垃圾邮件。首先对每封邮件进行文本预处理,便于数据分析。

邮件文本预处理


去除非字母符号
使用正则表达式将非文字类的符号,如标点符号、数字或特殊字符等去除。

data=re.sub(r'[^A-Za-z]', ' ', data)


将字母转换为小写
将字母转换为小写,同时由于英文单词之间以空格作为自然分界符,因此以空格为间隔直接进行分词。

str_list = data.lower().split()


去除停用词
将介词等对垃圾邮件判断无关的词移除,并在停词表中增加人名等使词典改进。

stop_words = set(stopwords.words('english'))
filtered_sentence = [w for w in str_list if not w in stop_words]

根据特征分词
对清理后的邮件文本单词列表进行词频统计。

np.unique(list(map(lambda t: len(str(t).strip()), df['date'])))#根据长度去特征
print(np.unique(list(map(lambda t: len(str(t).strip()), df['date']))))
np.unique(list(filter(lambda t: len(str(t).strip())==30, df['date'])))
print(np.unique(list(filter(lambda t: len(str(t).strip())==31, df['date']))))
def extract_email_date(str1):
    if not isinstance(str1, str):
        str1 = str(str1)
         
    str_len = len(str1)
    week = ""
    hour = ""
    time_quantum = ""      
    if str_len < 10:
        week = "unknown"
        hour = "unknown"
        time_quantum = "unknown"
        pass
    elif str_len == 16:
        rex = r"(\d{2}):\d{2}"#只取冒号前面的
        it = re.findall(rex, str1)
        if len(it) == 1:
            hour = it[0]
        else:
            hour = "unknown"
        week = "Fri"
        time_quantum = "0"
        pass
    elif str_len == 19: #['Sep 23 2005 1:04 AM']
        week = "Sep"
        hour = "01"
        time_quantum = "3"
        pass
    elif str_len == 21: #['August 24 2005 5:00pm'
        week ="Wed"
        hour = "17"
        time_quantum = "1"
        pass
    else:               #'Fri 2 Sep 2005 08:17:50'  Wed 31 Aug 2005 15:06:36 
        rex = r"([A-Za-z]+\d?[A-Za-z]*) .*?(\d{2}):\d{2}:\d{2}.*"# 加问号保险些# 'Fri 23 Sep 2005 09:39:39 +0800 X-Priority: 3 X-Mailer: FoxMail'
        it = re.findall(rex, str1)
        if len(it) == 1 and len(it[0]) ==2:
            week = it[0][0][-3:]
            hour = it[0][1]
            int_hour = int(hour)
            if int_hour <8:
                time_quantum = "3"
            elif int_hour <13:
                time_quantum = "0"
            elif int_hour <19:
                time_quantum = "1"
            else:
                time_quantum = "2"
            pass
        else:
            week = "unknown"
            hour = "unknown"
            time_quantum = 'unknown'
     
    week = week.lower()
    hour = hour.lower()
    time_quantum = time_quantum.lower()
    return(week, hour, time_quantum)
#数据转换
date_time_extract_result = list(map(lambda st: extract_email_date(st), df['date']))
df['date_week'] = pd.Series(map(lambda t: t[0], date_time_extract_result))
df['date_hour'] = pd.Series(map(lambda t: t[1], date_time_extract_result))
df['date_time_quantum'] = pd.Series(map(lambda t: t[2], date_time_extract_result))
print(df.head(4))
 
print("======星期属性字段的描述==========")
print(df.date_week.value_counts().head(3))
print(df[['date_week', 'label']].groupby(['date_week', 'label'])['label'].count())
 
print("======小时属性字段的描述==========")
print(df.date_hour.value_counts().head(3))
print(df[['date_hour', 'label']].groupby(['date_hour', 'label'])['label'].count())
 
print("======时间段属性字段的描述==========")
print(df.date_hour.value_counts().head(3))
print(df[['date_time_quantum', 'label']].groupby(['date_time_quantum', 'label'])['label'].count())            
                 
df['has_date'] = df.apply(lambda c: 0 if c['date_week'] == 'unknown' else 1, axis=1)
print(df.head(4))
#===========================开始分词==============================================
print('='*30 + '现在开始分词,请耐心等待5分钟。。。' + '='*20)
df['content'] = df['content'].astype('str')
df['jieba_cut_content'] = list(map(lambda st: "  ".join(jieba.cut(st)), df['content']))
df.head(4)

长度特征提取

#特征工程之四 长度提取  
def precess_content_length(lg):
    if lg <= 10:
        return 0
    elif lg <= 100:
        return 1
    elif lg <= 500:
        return 2
    elif lg <= 1000:
        return 3
    elif lg <= 1500:
        return 4
    elif lg <= 2000:
        return 5
    elif lg <= 2500:
        return 6
    elif lg <=  3000:
        return 7
    elif lg <= 4000:
        return 8
    elif lg <= 5000:
        return 9
    elif lg <= 10000:
        return 10
    elif lg <= 20000:
        return 11
    elif lg <= 30000:
        return 12
    elif lg <= 50000:
        return 13
    else:
        return 14
 
df['content_length'] = pd.Series(map(lambda st:len(st), df['content']))
df['content_length_type'] = pd.Series(map(lambda st: precess_content_length(st), df['content_length']))
# print(df.head(10))  #如果不count就按照自然顺序排      
df2 = df.groupby(['content_length_type', 'label'])['label'].agg(['count']).reset_index()#agg 计算并且添加count用于后续计算
df3 = df2[df2.label == 1][['content_length_type', 'count']].rename(columns = {'count' : 'c1'})
df4 = df2[df2.label == 0][['content_length_type', 'count']].rename(columns = {'count' : 'c2'})
df5 = pd.merge(df3, df4)#注意pandas中merge与concat的区别
df5['c1_rage'] = df5.apply(lambda r: r['c1'] / (r['c1'] + r['c2']), axis = 1)
df5['c2_rage'] = df5.apply(lambda r: r['c2'] / (r['c1'] + r['c2']), axis = 1)
print(df5) 


 

模型训练预测
使用scikit-learn机器学习库训练分类器,输入样本数据和结构化的输出结果,运行k-近邻算法(KNN)判定输入数据属于哪一个分类。由于3000个单词属性的数值对于计算结果的影响是相等的,数据已经在同一尺度,因此可以不用进行数值归一化,而是直接将向量放入KNN分类器中,在试验中准确率无变化也说明其对结果无明显影响。
首先,提供数据集的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。
之后,在运行算法前,调整并指定超参数:使用交叉验证法,得到最优K值,此时K=4,即计算距离后,选取距离最小的前4个点,选择这4个最相似数据中出现次数最多的分类,作为新数据的分类。在多次试验后,选取weights = ‘uniform’,即不考虑距离权重这个超参数,所有的邻近点的权重都是相等的;选取p=1,使用曼哈顿距离计算两点距离,而非欧式距离。
最后算法结论得出,错误率在可接受范围内,则可以运行k-近邻算法进行分类。

部分代码

knn的模型预测代码

#-*- coding:utf-8 -*-
import pandas as pd
import numpy as np
import jieba
import time
 
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.decomposition import TruncatedSVD
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score, precision_score, recall_score
from sklearn.ensemble.tests.test_forest import check_min_samples_leaf

df = pd.read_csv('./data/result_process02', sep =',')
# print(df.head(5))
df.dropna(axis = 0, how ='any', inplace = True)
# print(df.head(5))
# print(df.info())

x_train, x_test, y_train, y_test = train_test_split(df[['has_date','jieba_cut_content',\
                                                        'content_length_sema']],df['label'],\
                                                    test_size = 0.2, random_state = 0)
print("训练集大小%d" % x_train.shape[0])
print("测试集大小%d" % x_test.shape[0])
# print(x_train.head(1000))
# print(x_test.head(10))
# #================================================================================================
print('开始训练集的特征工程:')
start_time_train = time.time()
transformer = TfidfVectorizer(norm = 'l2', use_idf = True)
svd = TruncatedSVD(n_components=20)
jieba_cut_content = list(x_train['jieba_cut_content'].astype('str'))
transformer_model = transformer.fit(jieba_cut_content)#Tf-idf
df1 = transformer_model.transform(jieba_cut_content)
svd_model = svd.fit(df1)
df2 = svd_model.transform(df1)
data = pd.DataFrame(df2)
end_time_train = time.time()
print('Running train_svd time: %.2s second(s)'%(end_time_train-start_time_train))
# print(data.head(10))
# print(data.info())
#
data['has_date'] = list(x_train['has_date'])
data['content_length_sema'] = list(x_train['content_length_sema'])
# print(data.head(10))
# print(data.info())
#
print('开始KNN模型训练:')
knn = KNeighborsClassifier(n_neighbors=5)
model = knn.fit(data, y_train)
#测试集处理
jieba_cut_content_test = list(x_test['jieba_cut_content'].astype('str'))
data_test = pd.DataFrame(svd_model.transform(transformer_model.transform(jieba_cut_content_test)))
data_test['has_date'] = list(x_test['has_date'])
data_test['content_length_sema'] = list(x_test['content_length_sema'])
# print(data_test.head(10))
# print(data_test.info())
start_time_KNN = time.time()
y_predict = model.predict(data_test)
#
precision = precision_score(y_test, y_predict)
recall = recall_score(y_test, y_predict)
f1mean = f1_score(y_test, y_predict)

print('精确率为:%0.5f' % precision)
print('召回率为:%0.5f' % recall)

end_time_KNN = time.time()
print('Running KNN_model time: %.2s second(s)'%(end_time_KNN-start_time_KNN))

结果

总结

1.垃圾邮件过滤系统中一般采用算法过滤+其它过滤统计结合的方式来进行垃圾邮件过滤。
2.在垃圾邮件过滤中主要是需要进行分词操作。
3.在垃圾邮件过滤中一般注意召回率,也就是说一般情况下,需要尽可能的提高垃圾邮件过滤的成功率。
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
KNN (K-Nearest Neighbors) 是一种基本的分类算法,它的实现步骤如下: 1. 收集数据:收集鸢尾花数据集,包括花萼长度、花萼宽度、花瓣长度、花瓣宽度和鸢尾花的种类。 2. 准备数据:将数据集划分为训练集和测试集。为了公平地评估模型的性能,通常将数据集按照一定比例划分成训练集和测试集,比如 70% 训练集和 30% 测试集。 3. 分析数据:使用可视化工具对数据进行分析,可以使用散点图来表示不同种类鸢尾花的花萼长度和花萼宽度之间的关系。 4. 训练算法:KNN 算法没有显式的训练过程,因为它是一种基于实例的学习方法。在这个步骤中,KNN 算法只是简单地将训练集存储在内存中。 5. 测试算法:使用测试集对算法进行测试。对于每一个测试数据点,KNN 算法会找到离它最近的 k 个训练数据点,然后根据这 k 个数据点的标签进行分类。可以使用准确率来评估算法的性能,准确率等于分类正确的样本数除以总样本数。 6. 使用算法:当算法的性能达到要求之后,就可以使用它对新的数据进行分类了。 具体实现步骤可以参考下面的代码: ```python import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier # 加载数据集 iris = load_iris() X = iris.data y = iris.target # 划分数据集为训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1) # 创建 KNN 分类器 knn = KNeighborsClassifier(n_neighbors=3) # 训练模型 knn.fit(X_train, y_train) # 预测测试集 y_pred = knn.predict(X_test) # 计算准确率 accuracy = np.mean(y_pred == y_test) print('Accuracy:', accuracy) ``` 输出结果为: ``` Accuracy: 0.9777777777777777 ``` 这表示我们的模型在测试集上的准确率为 97.78%。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值