哈喽,我是Johngo~
过去一周,拿到了一位同学,面试腾讯的一个具体内容。岗位是机器学习算法岗。
在上周和大家分享了在腾讯面试的第一部分内容。
对其中的核心内容进行了整理,大家看再看一眼,今儿和大家分享的是第二部分内容的讲解~
内容比较多,咱们今天对第二部分进行一个总结~
K-means算法其实是一种常用的聚类算法,其工作原理相对简单而且直观。
首先,来谈一下它的工作原理。
工作原理:
在实践中,K-means算法有一些需要注意的地方:
下面,给到大家一个案例,可以根据具体的代码流程和注释进行学习~
import torch
def kmeans(data, k, max_iters=100):
# 随机初始化聚类中心
centroids = data[torch.randperm(data.size(0))[:k]]
for _ in range(max_iters):
# 分配步骤
distances = torch.cdist(data, centroids)
cluster_assignments = torch.argmin(distances, dim=1)
# 更新聚类中心
for i in range(k):
cluster_points = data[cluster_assignments == i]
if len(cluster_points) > 0:
centroids[i] = cluster_points.mean(dim=0)
return cluster_assignments, centroids
# 生成示例数据
data = torch.randn(100, 2)
# 设置聚类数量K
k = 3
# 运行K-means算法
cluster_assignments, centroids = kmeans(data, k)
# 打印聚类中心
print("聚类中心:", centroids)
SVM(支持向量机)常常用于分类和回归任务。它的基本原理是通过找到一个最优的超平面来将不同类别的数据分开。这个超平面被选为最大化边界,这样可以使得新的数据点在分类时更加准确。
在SVM中,我们希望找到一个超平面,它可以将数据分成两个类别,并且使得距离最近的数据点到超平面的距离最大化。这些最靠近超平面的数据点被称为支持向量。这个距离被称为间隔(margin),而支持向量机的目标就是最大化这个间隔。
如果我们有一个线性可分的数据集,那么我们可以通过以下几个步骤来构建一个SVM模型:
数学上,可以用以下形式的优化问题来描述SVM:
其中,
是超平面的法向量,
是偏置项,
是训练样本,
是其对应的类别标签(+1 或 -1)。
在实际应用中,我们可以使用软间隔SVM来处理线性不可分的情况,并使用核技巧来处理非线性数据。
下面,给到大家一个案例,可以根据具体的代码流程和注释进行学习~
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np
# 生成一些随机的线性可分数据
np.random.seed(0)
X = np.random.randn(300, 2)
Y = np.random.choice([-1, 1], size=(300,))
X[Y == 1] += 1
# 将数据转换为PyTorch张量
X = torch.tensor(X, dtype=torch.float32)
Y = torch.tensor(Y, dtype=torch.float32)
# 定义线性SVM模型
class LinearSVM(nn.Module):
def __init__(self):
super(LinearSVM, self).__init__()
self.fc = nn.Linear(2, 1)
def forward(self, x):
return self.fc(x)
# 定义训练函数
def train_model(model, criterion, optimizer, num_epochs=1000):
for epoch in range(num_epochs):
optimizer.zero_grad()
outputs = model(X).squeeze()
loss = criterion(outputs, Y)
loss.backward()
optimizer.step()
if (epoch+1) % 100 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
# 初始化模型、损失函数和优化器
model = LinearSVM()
criterion = nn.HingeEmbeddingLoss(margin=1.0)
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 训练模型
train_model(model, criterion, optimizer)
# 绘制决策边界
w = model.fc.weight.detach().numpy().flatten()
b = model.fc.bias.detach().numpy().flatten()
plt.scatter(X[:, 0], X[:, 1], c=Y.numpy())
plt.xlabel('X1')
plt.ylabel('X2')
x1 = np.linspace(-3, 3, 100)
x2 = (-w[0] * x1 - b) / w[1]
plt.plot(x1, x2, '-r', label='Decision Boundary')
plt.legend()
plt.show()
袋装法 和提升法 都是集成学习中常用的技术,用来提高模型的性能。这两种方法都是通过结合多个弱学习器(例如决策树)来构建一个更强大的模型,但它们的工作原理和策略略有不同。
首先,袋装法通过在原始数据集上多次随机采样,构建多个独立的学习器,然后将它们的预测结果进行平均或投票来得到最终的预测结果。袋装法的代表性算法就是随机森林(Random Forest),它在构建每棵树时都采用了随机特征选择和随机样本采样,以保证各个基学习器之间的独立性。
提升法则是通过训练一系列的弱学习器,每一个都在前一个学习器的基础上进行改进,最终将所有学习器进行加权组合得到最终的预测结果。提升法的代表性算法包括AdaBoost、Gradient Boosting和XGBoost等。其中,AdaBoost通过调整样本的权重来重点关注被前一个基学习器分类错误的样本,从而使得后续的学习器更关注于难以分类的样本;而Gradient Boosting则是通过拟合前一个学习器的残差来逐步减少误差,从而达到不断改进的目的。
这两种方法的差异在于样本的处理方式和学习器的组合方式。袋装法更注重通过随机采样来获得多样性,然后对多个学习器的结果进行集成;而提升法则更注重在误差的基础上进行改进,通过串行地训练多个学习器来逐步提高模型的性能。
当谈到核技巧(Kernel Trick)时,我们谈论的是一种在机器学习中广泛使用的技术,特别是在支持向量机(Support Vector Machine, SVM)和核岭回归(Kernel Ridge Regression)中。
这个技巧的核心思想是将输入数据从原始的特征空间映射到更高维的特征空间,以便于在新的特征空间中更容易地解决问题。
一般来说,核技巧有三个主要的应用场景:
举个例子来说,我们可以考虑一个简单的非线性分类问题,比如在二维平面上的一个环形数据集。在这种情况下,如果我们直接在原始特征空间中使用线性分类器,可能无法很好地分割两个类别。但是,如果我们使用径向基函数(Radial Basis Function, RBF)作为核函数,将数据映射到一个高维空间,那么在新的空间中就可能找到一个线性可分的超平面,从而成功解决这个问题。
下面,给到大家一个案例,可以根据具体的代码流程和注释进行学习~
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
# 生成环形数据集
def generate_data(num_samples):
theta = np.random.uniform(0, 2*np.pi, num_samples)
r = 2 + np.random.randn(num_samples)
x1 = r * np.cos(theta)
x2 = r * np.sin(theta)
X = np.vstack((x1, x2)).T
y = np.where(r < 2, 0, 1)
return X, y
# 定义RBF核函数
def rbf_kernel(X1, X2, sigma=1):
dist_sq = np.sum(X1**2, axis=1, keepdims=True) + np.sum(X2**2, axis=1) - 2 * np.dot(X1, X2.T)
return np.exp(-dist_sq / (2 * sigma**2))
# SVM模型
class SVM(nn.Module):
def __init__(self, input_dim):
super(SVM, self).__init__()
self.input_dim = input_dim
self.fc = nn.Linear(input_dim, 1)
def forward(self, x):
return self.fc(x)
# 训练SVM模型
def train_svm(X_train, y_train, kernel_func, lr=0.01, num_epochs=1000):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
X_train = torch.tensor(X_train, dtype=torch.float32, device=device)
y_train = torch.tensor(y_train, dtype=torch.float32, device=device).view(-1, 1)
model = SVM(X_train.shape[1]).to(device)
criterion = nn.HingeEmbeddingLoss()
optimizer = optim.SGD(model.parameters(), lr=lr)
for epoch in range(num_epochs):
optimizer.zero_grad()
outputs = model(X_train).squeeze()
loss = criterion(outputs, y_train)
loss.backward()
optimizer.step()
if (epoch+1) % 100 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
return model
# 画出决策边界
def plot_decision_boundary(X, y, model):
plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.RdBu, marker='o', edgecolors='k', s=100)
plt.xlabel('X1')
plt.ylabel('X2')
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.1), np.arange(x2_min, x2_max, 0.1))
Z = model(torch.tensor(np.c_[xx1.ravel(), xx2.ravel()], dtype=torch.float32)).detach().numpy().reshape(xx1.shape)
plt.contour(xx1, xx2, Z, levels=[-1, 0, 1], colors='k', linestyles=['--', '-', '--'])
plt.title('Decision Boundary')
plt.show()
# 生成数据
X_train, y_train = generate_data(200)
# 使用RBF核函数的SVM模型进行训练
model = train_svm(X_train, y_train, rbf_kernel)
# 画出决策边界
plot_decision_boundary(X_train, y_train, model)
当我们想要构建决策树时,首先需要了解决策树的构建过程以及如何对其进行剪枝。
决策树是一种非常直观且易于理解的机器学习模型,它将数据集按照特征的不同属性进行分割,以最小化数据的不纯度,从而达到对数据进行分类或回归的目的。
在构建决策树时,为了避免过拟合,我们可以采用剪枝技术来减少决策树的复杂度,提高泛化能力。
下面,给到大家一个案例,可以根据具体的代码流程和注释进行学习~
import torch
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
# 加载数据集
iris = load_iris()
X = torch.tensor(iris.data)
y = torch.tensor(iris.target)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 定义决策树分类器
class DecisionTree:
def __init__(self, max_depth=None):
self.model = DecisionTreeClassifier(max_depth=max_depth)
def fit(self, X, y):
self.model.fit(X, y)
def predict(self, X):
return self.model.predict(X)
# 实例化决策树分类器并进行训练
dt = DecisionTree(max_depth=3) # 设置最大深度为3,进行预剪枝
dt.fit(X_train, y_train)
# 进行预测
y_pred = dt.predict(X_test)
# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print("预剪枝决策树的准确率:", accuracy)
层次聚类是一种将数据集分层次进行划分的聚类方法,其基本概念是通过计算数据点之间的相似度或距离来构建一个层次结构。在这个层次结构中,数据点首先被合并成小的聚类,然后逐渐合并成更大的聚类,直到所有的数据点都被合并到一个大的聚类中为止。
优点:
缺点:
在PyTorch中,我们可以使用torch.cluster.hierarchical
模块来实现层次聚类。以下是一个简单的示例代码,其中我们使用层次聚类来对一个合成的数据集进行聚类,并使用matplotlib
来可视化聚类结果。
下面,给到大家一个案例,可以根据具体的代码流程和注释进行学习~
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.cluster import AgglomerativeClustering
# 创建一个合成的数据集
X, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=0)
# 使用层次聚类算法
agglomerative = AgglomerativeClustering(n_clusters=4)
clusters = agglomerative.fit_predict(X)
# 可视化聚类结果
plt.scatter(X[:, 0], X[:, 1], c=clusters, cmap='viridis', alpha=0.5)
plt.title('Hierarchical Clustering')
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
我们来聊一聊线性回归和逻辑回归的原理及区别。
线性回归是一种用于建立变量之间线性关系的统计模型。它假设因变量(Y)与自变量(X)之间存在着线性关系。其数学表达式通常为:
其中,
是因变量,
是自变量,
是模型的系数,
是误差项。
线性回归的目标是找到最佳拟合的系数,使得模型预测的值与真实观测值之间的残差平方和最小化。这通常通过最小化残差平方和的方法来实现,即最小二乘法(Least Squares Method)。
逻辑回归是一种用于建立因变量与自变量之间的概率关系的统计模型,尤其适用于分类问题。其数学表达式为:
其中,
表示给定自变量
条件下因变量
取值为1的概率,
是自然对数的底数。
逻辑回归通过将线性模型的输出结果映射到[0, 1]的范围内,进而进行概率预测。常用的映射函数是sigmoid函数。
我们来看一下如何用Python实现线性回归和逻辑回归。
线性回归
import numpy as np
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
# 生成一些随机数据作为示例
np.random.seed(0)
X = 2 * np.random.rand(1000, 1)
y = 4 + 3 * X + np.random.randn(1000, 1)
# 使用sklearn中的线性回归模型
lin_reg = LinearRegression()
lin_reg.fit(X, y)
# 打印模型系数
print("Intercept:", lin_reg.intercept_)
print("Coefficient:", lin_reg.coef_)
# 绘制数据和拟合直线
plt.scatter(X, y)
plt.plot(X, lin_reg.predict(X), color='red')
plt.xlabel('X')
plt.ylabel('y')
plt.title('Linear Regression')
plt.show()
逻辑回归
import numpy as np
from sklearn.linear_model import LogisticRegression
import matplotlib.pyplot as plt
# 生成一些随机数据作为示例
np.random.seed(0)
X = 2 * np.random.rand(100, 1)
y = (X > 1).astype(int).reshape(-1)
# 使用sklearn中的逻辑回归模型
log_reg = LogisticRegression()
log_reg.fit(X, y)
# 打印模型系数
print("Intercept:", log_reg.intercept_)
print("Coefficient:", log_reg.coef_)
# 绘制数据和决策边界
plt.scatter(X, y)
plt.plot(np.sort(X, axis=0), log_reg.predict_proba(np.sort(X, axis=0))[:, 1], color='red')
plt.xlabel('X')
plt.ylabel('Probability of y=1')
plt.title('Logistic Regression')
plt.show()
我们来看看它们的原理及区别~
随机森林是一种基于决策树的集成学习方法。它的原理如下:
关键点和注意事项:
梯度提升树是一种通过迭代训练弱学习器来构建强大模型的技术。其原理如下:
关键点和注意事项:
下面,给到大家一个案例,可以根据具体的代码流程和注释进行学习~
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 生成一些示例数据
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 使用随机森林进行训练和预测
rf_clf = RandomForestClassifier(n_estimators=100, random_state=42)
rf_clf.fit(X_train, y_train)
rf_preds = rf_clf.predict(X_test)
rf_accuracy = accuracy_score(y_test, rf_preds)
print("Random Forest Accuracy:", rf_accuracy)
# 使用梯度提升树进行训练和预测
gb_clf = GradientBoostingClassifier(n_estimators=100, random_state=42)
gb_clf.fit(X_train, y_train)
gb_preds = gb_clf.predict(X_test)
gb_accuracy = accuracy_score(y_test, gb_preds)
print("Gradient Boosting Accuracy:", gb_accuracy)
当我们谈论线性回归时,损失函数及其优化方法是至关重要的概念。损失函数是衡量模型预测结果与实际观测值之间差异的函数,而优化方法则是通过最小化损失函数来调整模型参数,以使其预测结果更接近实际观测值。
首先,让我们来谈谈线性回归的基本原理。线性回归是一种用于建立自变量(特征)与因变量(目标)之间线性关系的模型。其基本形式可以表示为:
其中,
是因变量,
是自变量,
是模型参数,
是误差项。
损失函数通常用于衡量模型预测值与实际观测值之间的差异。在线性回归中,最常用的损失函数是平方损失函数(也称为均方误差):
其中,
是样本数量,
是第
个观测值的实际值,
是模型预测的值。
接下来,让我们讨论一下优化方法。优化方法的目标是通过调整模型参数来最小化损失函数。线性回归中最常用的优化方法是梯度下降法。梯度下降法的基本思想是通过沿着损失函数梯度的反方向逐步调整参数,直至达到损失函数的最小值。
梯度下降法的更新规则如下:
其中,
是学习率,控制参数更新的步长。
下面,给到大家一个案例,可以根据具体的代码流程和注释进行学习~
import numpy as np
class LinearRegression:
def __init__(self, learning_rate=0.01, n_iterations=1000):
self.learning_rate = learning_rate
self.n_iterations = n_iterations
self.weights = None
self.bias = None
def fit(self, X, y):
n_samples, n_features = X.shape
self.weights = np.zeros(n_features)
self.bias = 0
for _ in range(self.n_iterations):
y_predicted = np.dot(X, self.weights) + self.bias
# 计算损失函数的梯度
dw = (1 / n_samples) * np.dot(X.T, (y_predicted - y))
db = (1 / n_samples) * np.sum(y_predicted - y)
# 更新参数
self.weights -= self.learning_rate * dw
self.bias -= self.learning_rate * db
def predict(self, X):
return np.dot(X, self.weights) + self.bias
这段代码实现了一个简单的线性回归模型,其中 fit
方法用于训练模型,predict
方法用于预测新样本的值。
通过损失函数及其优化方法,能够更好地理解线性回归模型是如何从数据中学习并进行预测的。