电影题材分类引入
众所周知,电影可以按照题材分类,然而题材本身是如何定义的?由谁来判定某部电影属于哪个题材?也就是说同一题材的电影具有哪些公共特征?这些都是在进行电影分类时必须要考虑的问题。
没有哪个电影人会说自己制作的电影和以前的某部电影类似,但我们确实知道每部电影在风格上的确有可能会和同题材的电影相近。那么动作片具有哪些共有特征,使得动作片之间非常类似,而与爱情片存在着明显的差别呢?
动作片中也会存在接吻镜头,爱情片中也会存在打斗场景,我们不能单纯依靠是否存在打斗或者亲吻来判断影片的类型。但是爱情片中的亲吻镜头更多,动作片中的打斗场景也更频繁,基于此类场景在某部电影中出现的次数可以用来进行电影分类。
k-近邻算法概述
简单地说,K 近邻算法采用测量不同特征值之间的距离方法进行分类。它具有的优缺点如下:
- 优点:精度高、对异常值不敏感、无数据输入假定。
- 缺点:计算复杂度高、空间复杂度高。
K 近邻算法适用数据范围为:数值型和标称型。
KNN 是一种基于实例的学习(Instance-based Learning),它没有显式的训练过程,而是直接利用训练数据对测试数据进行预测。
分类任务:找到 k 个最近的邻居,根据这些邻居的类别投票决定待预测样本的类别。
回归任务:找到 k 个最近的邻居,根据这些邻居的值取平均值作为待预测样本的值。
KNN 的算法步骤
KNN 的实现步骤如下:
-
加载数据:准备好训练集(已知标签的数据)和测试集(待预测的数据)。
-
选择距离度量方法:常用的距离度量方法有欧氏距离、曼哈顿距离等。
-
计算距离:对于测试集中的每个样本,计算它与训练集中所有样本的距离。
-
选择 k 值:确定 k 的值(即选择多少个邻居)。
-
找到 k 个最近邻居:根据距离排序,选择距离最近的 k 个样本。
-
投票或平均:
- 分类任务:对 k 个邻居的类别进行投票,票数最多的类别即为预测结果。
- 回归任务:对 k 个邻居的值取平均值作为预测结果。
-
输出结果:返回预测的类别或值。
距离度量方法
KNN 的核心是计算样本之间的距离,常用的距离度量方法有:
(1)欧氏距离(Euclidean Distance)
欧氏距离是最常用的距离度量方法,适用于连续型数据。
d ( x , y ) = ∑ i = 1 n ( x i − y i ) 2 d(x, y) = \sqrt{\sum_{i=1}^n (x_i - y_i)^2} d(x,y)=i=1∑n(xi−yi)2
(2) 曼哈顿距离(Manhattan Distance)
曼哈顿距离是两点在坐标轴上的绝对距离之和。
d ( x , y ) = ∑ i = 1 n ∣ x i − y i ∣ d(x, y) = \sum_{i=1}^n |x_i - y_i| d(x,y)=i=1∑n∣xi−yi∣
(3) 闵可夫斯基距离(Minkowski Distance)
闵可夫斯基距离是欧氏距离和曼哈顿距离的推广形式。
d ( x , y ) = ( ∑ i = 1 n ∣ x i − y i ∣ p ) 1 / p d(x, y) = \left( \sum_{i=1}^n |x_i - y_i|^p \right)^{1/p} d(x,y)=(i=1∑n∣xi−yi∣p)1/p
- 当 ( p = 1 ) 时,就是曼哈顿距离。
- 当 ( p = 2 ) 时,就是欧氏距离。
(4) 余弦相似度(Cosine Similarity)
余弦相似度衡量的是两个向量的夹角余弦值,适用于文本数据或高维稀疏数据。
cosine ( x , y ) = x ⋅ y ∥ x ∥ ∥ y ∥ \text{cosine}(x, y) = \frac{x \cdot y}{\|x\| \|y\|} cosine(x,y)=∥x∥∥y∥x⋅y
用k-近邻算法实现电影分类
现在我们回到前面电影分类的例子,使用 K 近邻算法分类爱情片和动作片。有人曾经统计过很多电影的打斗镜头和接吻镜头,图 显示了 6 部电影 (这6部电影也就是训练集…)的打斗和接吻镜头数。假如有一部未看过的电影,如何确定它是爱情片还是动作片呢?我们可以使用 K 近邻 来解决这个问题。
首先我们需要知道这个未知电影存在多少个打斗镜头和接吻镜头,图 中问号位置是该未知电影出现的镜头数图形化展示,具体数字参见表。
即使不知道未知电影属于哪种类型,我们也可以通过某种方法计算出来。首先计算未知电影与样本集中其他电影的距离,如表所示。(此处暂时不要关心如何计算得到这些距离值,使用 Python 实现电影分类应用时,会提供具体的计算方法。)
现在我们得到了样本集中所有电影与未知电影的距离,按照距离递增排序,可以找到
k
k
k 个距离最近的电影。假定
k
=
3
k=3
k=3,则三个最靠近的电影依次是 He’s Not Really into Dudes
、Beautiful Woman
和 California Man
。K 近邻算法按照距离最近的三部电影的类型,决定未知电影的类型,而这三部电影全是爱情片,因此我们判定未知电影是爱情片。
手搓KNN实现手写数字识别
导入相应的库
import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
加载数据集
digits = load_digits()
X = digits.data # 特征矩阵 (1797, 64)
y = digits.target # 标签向量 (1797,)
-
使用 load_digits() 加载手写数字数据集。
-
X 是特征矩阵,形状为 (1797, 64),表示 1797 个样本,每个样本有 64 个特征。
-
y 是标签向量,形状为 (1797,),表示每个样本的真实类别(0 到 9)。
划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
-
使用 train_test_split 将数据集划分为训练集和测试集。
-
test_size=0.3 表示测试集占 30%,训练集占 70%。
-
random_state=42 确保每次运行代码时划分结果一致。
定义距离函数(欧氏距离)
欧氏距离是 KNN 中最常用的距离度量方法。
def euclidean_distance(x1, x2):
return np.sqrt(np.sum((x1 - x2) ** 2))
定义 KNN 分类器
class KNN:
def __init__(self, k=3):
self.k = k
def fit(self, X_train, y_train):
self.X_train = X_train
self.y_train = y_train
def predict(self, X_test):
y_pred = []
for x in X_test:
# 计算当前测试样本与所有训练样本的距离
distances = [euclidean_distance(x, x_train) for x_train in self.X_train]
# 找到距离最近的 k 个邻居的索引
k_indices = np.argsort(distances)[:self.k]
# 获取这 k 个邻居的标签
k_nearest_labels = [self.y_train[i] for i in k_indices]
# 投票决定预测标签(取众数)
most_common = np.bincount(k_nearest_labels).argmax()
y_pred.append(most_common)
return np.array(y_pred)
-
init(self, k=3):初始化 KNN 分类器,设置 k 值。
-
fit(self, X_train, y_train):训练模型,存储训练数据。
-
predict(self, X_test):对测试集进行预测。
-
计算测试样本与所有训练样本的距离。
-
找到距离最近的 k 个邻居。
-
通过投票(取众数)决定预测标签。
创建 KNN 实例并训练模型
knn = KNN(k=3)
knn.fit(X_train, y_train)
-
创建 KNN 实例,设置 k=3。
-
调用 fit 方法训练模型(存储训练数据)。
预测测试集
调用 predict 方法对测试集进行预测。
y_pred = knn.predict(X_test)
计算准确率
使用 accuracy_score 计算模型在测试集上的准确率。
accuracy = accuracy_score(y_test, y_pred)
print(f"模型准确率: {accuracy:.4f}")
可视化部分测试样本的预测结果
plt.figure(figsize=(10, 8))
for i in range(20):
plt.subplot(4, 5, i + 1)
plt.imshow(X_test[i].reshape(8, 8), cmap='gray')
plt.title(f"True: {y_test[i]}\nPred: {y_pred[i]}")
plt.axis('off')
plt.tight_layout()
plt.show()
导入库KNN分类器实现手写数字识别
数据集:我们使用 Scikit-learn 提供的 load_digits 数据集。
导入库
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import matplotlib.pyplot as plt
import numpy as np
-
sklearn.datasets.load_digits:加载手写数字数据集。
-
sklearn.model_selection.train_test_split:用于将数据集划分为训练集和测试集。
-
sklearn.neighbors.KNeighborsClassifier:KNN 分类器。
-
sklearn.metrics:提供模型评估工具,如准确率、分类报告和混淆矩阵。
-
matplotlib.pyplot:用于数据可视化。
-
numpy:用于数值计算。
加载数据集
# 加载数据集
digits = load_digits()
# 查看数据集信息
print("数据集特征形状:", digits.data.shape) # 输出: (1797, 64)
print("数据集标签形状:", digits.target.shape) # 输出: (1797,)
print("类别:", np.unique(digits.target)) # 输出: [0 1 2 3 4 5 6 7 8 9]
-
load_digits():加载手写数字数据集。数据集包含 1797 个样本,每个样本是一个 8x8 的图像(64 个特征)。
-
digits.data:特征矩阵,形状为 (1797, 64),表示 1797 个样本,每个样本有 64 个特征。
-
digits.target:标签向量,形状为 (1797,),表示每个样本的真实类别(0 到 9)。
-
np.unique(digits.target):查看数据集中所有唯一的类别标签。
数据可视化
# 可视化部分手写数字
plt.figure(figsize=(10, 4))
for i in range(10):
plt.subplot(2, 5, i + 1)
plt.imshow(digits.images[i], cmap='gray')
plt.title(f"Label: {digits.target[i]}")
plt.axis('off')
plt.show()
-
digits.images:数据集中的图像数据,形状为 (1797, 8, 8),表示 1797 个 8x8 的图像。
-
plt.imshow():显示图像,cmap=‘gray’ 表示使用灰度图。
-
plt.title():为每张图像添加标题,显示其真实标签。
-
plt.axis(‘off’):关闭坐标轴显示。
-
plt.show():显示图像。
划分训练集和测试集
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, test_size=0.3, random_state=42)
print("训练集形状:", X_train.shape) # 输出: (1257, 64)
print("测试集形状:", X_test.shape) # 输出: (540, 64)
-
train_test_split():将数据集划分为训练集和测试集。
-
test_size=0.3:测试集占 30%,训练集占 70%。
-
random_state=42:设置随机种子,确保每次运行代码时划分结果一致。
-
-
X_train 和 X_test:训练集和测试集的特征矩阵。
-
y_train 和 y_test:训练集和测试集的标签向量。
训练 KNN 模型
# 创建 KNN 分类器
knn = KNeighborsClassifier(n_neighbors=3) # 设置 k=3
# 训练模型
knn.fit(X_train, y_train)
-
KNeighborsClassifier(n_neighbors=3):创建一个 KNN 分类器,设置 k=3,即选择 3 个最近邻居。
-
knn.fit(X_train, y_train):训练模型。KNN 的训练过程只是将数据存储起来,没有显式的模型参数学习。
模型评估
# 预测
y_pred = knn.predict(X_test)
# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"模型准确率: {accuracy:.4f}")
# 打印分类报告
print("\n分类报告:")
print(classification_report(y_test, y_pred))
# 打印混淆矩阵
print("\n混淆矩阵:")
print(confusion_matrix(y_test, y_pred))
-
knn.predict(X_test):对测试集进行预测,返回预测的标签。
-
accuracy_score(y_test, y_pred):计算模型在测试集上的准确率。
-
classification_report(y_test, y_pred):生成分类报告,包括精确率(precision)、召回率(recall)和 F1 分数。
-
confusion_matrix(y_test, y_pred):生成混淆矩阵,显示每个类别的预测情况。
可视化预测结果
# 可视化部分测试样本的预测结果
plt.figure(figsize=(10, 8))
for i in range(20):
plt.subplot(4, 5, i + 1)
plt.imshow(X_test[i].reshape(8, 8), cmap='gray')
plt.title(f"True: {y_test[i]}\nPred: {y_pred[i]}")
plt.axis('off')
plt.tight_layout()
plt.show()
-
X_test[i].reshape(8, 8):将测试集的第 i 个样本(64 维向量)转换为 8x8 的图像。
-
plt.title():显示图像的真实标签和预测标签。
-
plt.tight_layout():自动调整子图间距,避免重叠。
-
plt.show():显示图像。