四月二十三日
今天主要写了一些东西,然后对于昨天的人生意义的思考,有了一些新的理解。如果最后有空的话,把昨天没写完的对话再写一点。
无私的互联网分享精神,刚入门人工智能的可以跟着这个学,绝对不会误导你们。
人工智能入门
数据预处理
数据(数值、文本、图像等)清洗
numpy pandas opencv(cv2)
异常值检测与处理
# pandas
df.drop(labels=['delete'], axis=1) # 简单删除
df.drop_duplicates() # 数据去重,删去重复行,如果要删除重复项并保留最后一次出现,使用keep='last'
df.dropna() # 删除数据集中空缺的数据列或行
df.isna() # 确定数据集中的缺失值
df.fillna() # 用括号内的东西代替文件中的缺失值
df.fillna(method='pad') # 前面的值填充缺失值
df.fillna(method='bfill') # 后面的
df.interpolate() # 插值填充(线性)
# numpy
cleaned_data = np.where(outliers, np.median(data), data) # 条件替换 median返回中位数
cleaned_data = data[~outliers] # 掩码过滤
# opencv
repaired_img = cv2.inpaint(img, mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA) # 恢复异常区域(噪声、遮挡)
cleaned_img = cv2.bitwise_and(img, img, mask=valid_mask) # 通过二值化掩码去除异常区域,类似np.where
数据转换
# pandas
df['col'] = df['col'].astype('float32')
df['date'] = pd.to_datetime(df['date_str'])
# numpy
arr = arr.astype('int32')
# opencv
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.resize()
cv2.warpAffine() # 平移、旋转、缩放
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 固定阈值图像分割
数据标准化/归一化
(data - mean)/std
SVD 分解算法
用于PCA等类似的降维操作
PCA
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, roc_auc_score
pca = decomposition.PCA(n_components=2)
X_centered = X - X.mean(axis=0)
pca.fit(X_centered)
X_pca = pca.transform(X_centered)
X_train, X_test, y_train, y_test = train_test_split(X_pca, y, test_size=.3,
stratify=y,
random_state=42)
clf = DecisionTreeClassifier(max_depth=2, random_state=42)
clf.fit(X_train, y_train)
preds = clf.predict_proba(X_test)
t-SNE
from sklearn.manifold import TSNE
tsne = TSNE(random_state=17)
X_tsne = tsne.fit_transform(X)
# 下同上
scipy库实现
import numpy as np
from scipy.linalg import svd
# 生成一个随机矩阵
A = np.random.rand(4, 3)
# 进行SVD分解
U, S, VT = svd(A)
# 验证分解结果
Sigma = np.zeros_like(A)
Sigma[:len(S), :len(S)] = np.diag(S)
reconstructed_A = U @ Sigma @ VT
print("原始矩阵与重构矩阵的误差:", np.linalg.norm(A - reconstructed_A))
numpy从零实现
def manual_svd(A, tol=1e-10):
""" 手动实现 SVD 分解(仅用 NumPy)
:param A: 输入矩阵 (m x n)
:param tol: 奇异值截断阈值(小于该值的视为 0)
:return: U, Sigma, VT """
# 计算 A^T A 和 AA^T
ATA = A.T @ A
AAT = A @ A.T
# 计算 A^T A 的特征值和特征向量(得到 V)
eigenvalues_V, V = np.linalg.eigh(ATA) # eigh 适用于对称矩阵
eigenvalues_V[eigenvalues_V < tol] = 0 # 去除数值误差导致的极小值
idx_V = np.argsort(eigenvalues_V)[::-1] # 降序排序 eigenvalues_V =
eigenvalues_V[idx_V] V = V[:, idx_V]
# 计算 AA^T 的特征值和特征向量(得到 U)
eigenvalues_U, U = np.linalg.eigh(AAT)
eigenvalues_U[eigenvalues_U < tol] = 0
idx_U = np.argsort(eigenvalues_U)[::-1]
eigenvalues_U = eigenvalues_U[idx_U]
U = U[:, idx_U]
# 计算奇异值 Sigma(取非零特征值的平方根)
Sigma = np.zeros_like(A, dtype=float)
singular_values = np.sqrt(eigenvalues_V[eigenvalues_V > tol])
np.fill_diagonal(Sigma, singular_values)
# 确保 U 和 V 的符号一致性(避免 SVD 符号歧义)
for i in range(min(A.shape)):
if Sigma[i, i] > tol:
# 计算 U 的第 i 列是否与 A @ V[:, i] 方向一致
u_i = U[:, i]
v_i = V[:, i]
if np.dot(A @ v_i, u_i) < 0:
U[:, i] = -u_i # 调整 U 的符号
`return U, Sigma, V.T # 返回 U, Sigma, VT
特征提取等数据处理技术
随机森林计算特征重要性:
forest = RandomForestRegressor(n_estimators=1000, max_features=10,
random_state=0)
forest.fit(hostel_data.drop(['hostel', 'rating'], axis=1),
hostel_data['rating'])
importances = forest.feature_importances_
indices = np.argsort(importances)[::-1]
超参数优化:
from sklearn.model_selection import GridSearchCV
parameters = {'max_depth': [5, 10], 'min_samples_leaf': [1, 3], 'max_features': [10, 13]}
rf = RandomForestClassifier(n_estimators=100, random_state=42)
gcv = GridSearchCV(rf, parameters, cv=5)
gcv.fit(X, y)
print("最优参数:", gcv.best_params_)
print("最优准确率:", gcv.best_score_)
数据不均衡处理
1. 全局直方图均衡化
通过调整全局数据分布,增强整体对比度或平衡类别分布。
适用场景:全局数据分布集中且需增强整体区分度。
import cv2 import numpy as np
# 读取数据(假设为图像灰度矩阵)
data = np.random.randint(0, 256, (100, 100), dtype=np.uint8)
# 全局直方图均衡化
equalized_data = cv2.equalizeHist(data)
2. 局部直方图均衡化(CLAHE)
对局部区域进行均衡化,避免全局调整导致的噪声放大,适用于细粒度数据不均衡问题。
适用场景:数据中存在局部特征差异较大的区域。
# 创建CLAHE对象
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
# 应用局部均衡化
local_equalized = clahe.apply(data)
3. 自适应阈值均衡化
结合动态阈值调整数据分布,例如通过限制对比度自适应增强局部区域。
适用场景:数据中存在极端值或需抑制噪声的场景。
% 读取数据并转换为灰度图像
I = imread('input.jpg');
I_eq = adapthisteq(I, 'ClipLimit', 0.02, 'Distribution', 'uniform');
4. 自动色彩均衡化(ACE)
通过模拟人眼视觉特性调整局部对比度和色彩平衡,可用于多维度数据增强。
def auto_color_equalization(data, alpha=5.0):
mean = np.mean(data)
adjusted = alpha * (data - mean) + mean
return np.clip(adjusted, 0, 255).astype(np.uint8)
adjusted_data = auto_color_equalization(data)
5. 重采样技术(过采样/欠采样)
通过调整样本数量平衡类别分布,如随机过采样少数类或欠采样多数类。
from imblearn.over_sampling import RandomOverSampler
# 假设X为特征,y为标签
ros = RandomOverSampler(random_state=42)
X_resampled, y_resampled = ros.fit_resample(X, y)
模型训练与评估
XGBoost 梯度提升算法的应用
有以下注意事项:
1.配合之前学过的np、json等对原始数据进行处理;
2.需要运用到sklearn的相关算法,如
from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error
要做到能写、会用;
3.如果是自己写pram,使用的是model.train,data、label数据需要转换成DMatrix格式,而如果是直接调用模型便不需考虑,如:
model = xgb.XGBRegressor(random_state=42) model.fit(X_train, y_train)
相关例题:
提升 XGB 回归模型训练效果(大学组)、训练 XGB 回归模型(职业院校组)
线性回归
从零实现
对于函数$y(x,w)=w_0+w_1x$ 进行线性拟合,推导的最终最优结果是$w_1=\displaystyle\frac{n\sum_{}^{}x_iy_i - \sum_{}{}x_i\sum_{}y_i}{n\sum_{}{}x_i2 - (\sum_{}{}x_i)2}$
$w_0=\displaystyle\frac{\sum_{}{}x_i2\sum_{}^{}y_i - \sum_{}{}x_i\sum_{}x_iy_i}{n\sum_{}{}x_i2 - (\sum_{}{}x_i)2}$
基于这两个公式可以从零实现线性回归
def w_calculator(x, y):
n = len(x)
w1 = (n*sum(x*y) - sum(x)*sum(y))/(n*sum(x*x) - sum(x)*sum(x))
w0 = (sum(x*x)*sum(y) - sum(x)*sum(x*y))/(n*sum(x*x)-sum(x)*sum(x))
return w0, w1
通过sklearn包实现
from sklearn.linear_model import LinearRegression
model = LinearRegression() # 定义模型
model.fit(x.reshape(len(x),1),y) # 拟合模型
model.intercept_, model.coef_ # 得到模型拟合参数,截距和斜率
model.predict([[150]]) # 预测150
矩阵实现
对于一元线性函数,有表达式$y(x,w)=XW$
最终推导有$W={(XTX)}X^Ty$
def w_matrix(x,y):
w=(x.T*x).I*x.T*y
return w
# 用例
x = np.matrix([[1, 56], [1, 72], [1, 69], [1, 88], [1, 102],
[1, 86], [1, 76], [1, 79], [1, 94], [1, 74]])
y = np.matrix([92, 102, 86, 110, 130, 99, 96, 102, 105, 92])
w_matrix(x, y.reshape(10, 1))
逻辑回归
对数损失函数:
$J(w) = -\frac{1}{m} \sum_{i=1}^{m} \left[ y^{(i)} \log h_w \left( x^{(i)} \right) + \left( 1 - y^{(i)} \right) \log \left( 1 - h_w \left( x^{(i)} \right) \right) \right]$
对其进行推导可得梯度方向
$\frac{\partial J}{\partial w} = \frac{1}{m} x^T (h_w(x) - y)$
sigmoid函数
$f(z)=\displaystyle\frac{1}{1+ e^{-z}}$
从零实现
def sigmoid(z):
sigmoid = 1 / (1 + np.exp(-z))
return sigmoid
def loss(h,y):
loss = (-y * np.log(h) - (1 - y) * np.log(1-h)).mean()
return loss
def gradient(X,h,y):
gradient = np.dot(X.T, (h - y)) / y.shape[0]
return gradient
def Logistic_Regression(x, y, lr, num_iter):
intercept = np.ones((x.shape[0], 1)) # 截距项
x = np.concatenate((intercept, x), axis=1) # 将截距项与原始特征矩阵拼接,构造新的特征矩阵,偏置参数纳入参数向量w,简化后续的矩阵计算
w = np.zeros(x.shape[1]) # 权重参数
for i in range(num_iter):
z = np.dot(x,w)
h = sigmoid(z)
g = gradient(x,h,y)
w -= lr * g
l = loss(h,y)
return l, w
# 使用
x = df[['X0', 'X1']].values
y = df['Y'].values
lr = 0.01
num_iter = 30000
L = Logistic_Regression(x, y, lr, num_iter)
sklearn实现
LogisticRegression(penalty='l2', dual=False, tol=0.0001, C=1.0, fit_intercept=True, intercept_scaling=1, class_weight=None, random_state=None, solver='liblinear', max_iter=100, multi_class='ovr', verbose=0, warm_start=False, n_jobs=1)
参数介绍:
- $penalty$:惩罚项,默认为L2正则,可修改为L1正则
- $dual$:布尔型,默认为False。如果为True,则求解对偶问题,只在penalty=‘l2'且solver=’liblinear‘时有对偶形式。通常样本数大于特征数时,默认为False,求解原始问题。
- $tol$:收敛阈值,当模型参数的更新量小于 tol 时,认为模型已经收敛,停止迭代(即使没有达到
max_iter),默认值为 0.0001。 - $C$:正则化系数λ的倒数,必须是大于0的浮点数。值越小,正则化越强。
- $fit_intercept$: 布尔值,默认为True。表示是否计算截距项。
- $intercept_scaling$: 浮点数,默认为1.0。仅在使用
solver='liblinear'且fit_intercept=True时有用。在这种情况下,特征向量x变为[x, intercept_scaling],即附加了一个常数值等于intercept_scaling的合成特征。 - $class_weight$: 字典或'balanced',默认为None。用于指定分类模型中各类别的权重。可以不输入,即不考虑权重;或者选择'balanced'让库自动计算权重;也可以自定义权重字典。
- $random_state$: 整数或RandomState实例,默认为None。控制随机数生成器的种子。
- $solver$: 字符串,默认为'liblinear'。用于指定求解器类型。
- $max_iter$: 整数,默认为100。求解器的最大迭代次数。
- $multi_class$: 字符串,默认为'ovr'。用于指定多分类问题的策略。
- $verbose$: 整数,默认为0。控制日志显示的详细程度。
- $warm_start$: 布尔值,默认为False。如果为True,则利用上一次拟合的结果作为初始值。
- $n_jobs$: 整数,默认为1。用于指定并行任务的数量。
from sklearn.linear_model import LogisticRegression
# 以下是逻辑回归部分
model = LogisticRegression(tol=0.001, max_iter=10000, solver='liblinear')
model.fit(x,y)
model.coef_, model.intercept_
# 以下是可视化部分
plt.figure(figsize=(10, 6))
plt.scatter(df['X0'], df['X1'], c=df['Y'])
x1_min, x1_max = df['X0'].min(), df['X0'].max()
x2_min, x2_max = df['X1'].min(), df['X1'].max()
xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max),
np.linspace(x2_min, x2_max))
grid = np.c_[xx1.ravel(), xx2.ravel()] # 将网格数据展平并组合成一个二维数组,每一行代表一个网格点的坐标
probs = (np.dot(grid, model.coef_.T) + model.intercept_).reshape(xx1.shape)
plt.contour(xx1, xx2, probs, levels=[0], linewidths=1, colors='red')
函数np.meshgrid 用于创建两个特征的网格数据
函数np.linspace 用于生成在指定范围内均匀分布的数值
决策树
属于监督学习范畴,适用于解决离散标签的分类问题
DecisionTreeClassifier(
criterion='',
max_depth=,
random_state=
)
主要内容:
1.$criterion$:
$entropy$(信息增益):
适合类别分布均衡的数据。
对噪声敏感度较低,生成树结构较平衡
计算复杂度略高
$gini$(基尼不纯度):
计算从数据集中随机抽取两个样本,其类别标签不一致的概率。基尼值越小,数据纯度越高
快但树易深、细分不如entropy
2.max_depth:
限制树的生长复杂度,直接影响模型的泛化能力、训练效率和可解释性、
3.另:
可以尝试复杂写法相互比较得出最优选择
# 定义参数网格(覆盖关键超参数)
param_grid = {
'criterion': ['gini', 'entropy'], # 分割质量指标
'max_depth': [3, 5, 7, 10, None], # 树的最大深度(None表示无限制)
'min_samples_split': [2, 5, 10], # 节点分裂所需最小样本数
'min_samples_leaf': [1, 2, 4] # 叶节点最小样本数(防过拟合)
}
# 执行网格搜索(5折交叉验证,按准确率评估)
grid_search = GridSearchCV(
estimator=dt,
param_grid=param_grid,
cv=5,
scoring='accuracy',
n_jobs=-1 # 使用全部CPU核心加速计算
)
grid_search.fit(X_train, y_train)
# 输出最优参数组合及性能
print(f"最佳参数:{grid_search.best_params_}")
print(f"最佳交叉验证准确率: {grid_search.best_score_:.4f}")
# 用最优模型在测试集上评估
best_model = grid_search.best_estimator_
本节较为简单,但3.中内容对于后面XGBoost、sklearn的其他内容有借鉴意义
朴素贝叶斯
从零实现
class NaiveBayes:
def __init__(self):
self.class_probs = {} # 类先验概率
self.feature_stats = {} # 存储(均值, 标准差) per特征
def fit(self, X, y):
# 计算类别概率(拉普拉斯平滑)
class_counts = Counter(y)
total = len(y)
self.class_probs = {cls: (count+1)/(total+len(class_counts)) for cls, count in class_counts.items()} # [11]()
# 计算特征统计量(高斯假设)
for cls in class_counts:
cls_data = [X[i] for i in range(len(X)) if y[i]==cls]
self.feature_stats[cls] = [
(np.mean(feature), np.std(feature))
for feature in zip(*cls_data)
]
def predict(self, X_test):
predictions = []
for x in X_test:
max_prob = -1
best_cls = None
for cls in self.class_probs:
prob = np.log(self.class_probs[cls]) # 对数概率防下溢
for i in range(len(x)):
mean, std = self.feature_stats[cls][i]
exponent = np.exp(-((x[i]-mean)**2)/(2*std**2))
prob += np.log(exponent/(std*np.sqrt(2*np.pi))) # [5]()
if prob > max_prob:
max_prob, best_cls = prob, cls
predictions.append(best_cls)
return predictions
sklearn实现
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# 数据加载与划分
X, y = load_dataset()
# 自定义数据加载函数
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# 模型训练与评估
model = GaussianNB()
model.fit(X_train, y_train)
preds = model.predict(X_test)
print(classification_report(y_test, preds)) # [10]()
随机森林等
Bagging
from sklearn.ensemble import BaggingRegressor
bdt = BaggingRegressor(DecisionTreeRegressor()).fit(X_train, y_train)
随机森林
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42)
rf.fit(X_train, y_train)
常见的传统机器学习算法应用
卷积神经网络(Convolutional Neural Networks, CNN)
class CNN(nn.Module):
def __init__(self):
super().__init__()
self.conv_layers = nn.Sequential(
nn.Conv2d(3, 16, 3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, 3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.fc_layers = nn.Sequential(
nn.Linear(32 * 8 * 8, 256),
nn.ReLU(),
nn.Linear(256, 10)
)
def forward(self, x):
x = self.conv_layers(x)
x = x.view(x.size(0), -1)
return self.fc_layers(x)
循环神经网络(Recurrent Neural Networks, RNN)
class SimpleRNN(nn.Module):
def __init__(self, input_size=10, hidden_size=20):
super().__init__()
self.rnn = nn.RNN(
input_size=input_size,
hidden_size=hidden_size,
batch_first=True
)
self.fc = nn.Linear(hidden_size, 1)
def forward(self, x):
# x形状: (batch_size, 序列长度, 输入特征维度)
out, _ = self.rnn(x)
return self.fc(out[:, -1, :])
长短期记忆网络(Long Short-Term Memory, LSTM)
class SimpleLSTM(nn.Module):
def __init__(self, input_size=10, hidden_size=20):
super().__init__()
self.lstm = nn.LSTM(
input_size=input_size,
hidden_size=hidden_size,
batch_first=True
)
self.fc = nn.Linear(hidden_size, 1)
def forward(self, x):
out, (h_n, c_n) = self.lstm(x)
return self.fc(out[:, -1, :])
自编码器(AutoEncoders)
class Autoencoder(nn.Module):
def __init__(self):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 64)
)
self.decoder = nn.Sequential(
nn.Linear(64, 256),
nn.ReLU(),
nn.Linear(256, 784),
nn.Sigmoid()
)
def forward(self, x):
x = x.view(x.size(0), -1)
latent = self.encoder(x)
reconstructed = self.decoder(latent)
return reconstructed
生成对抗网络(GenerativeAdversarial Networks, GAN)等深度学习模型应用
class Generator(nn.Module):
def __init__(self, latent_dim=100):
super().__init__()
self.model = nn.Sequential(
nn.Linear(latent_dim, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 512),
nn.LeakyReLU(0.2),
nn.Linear(512, 784),
nn.Tanh()
)
def forward(self, z):
return self.model(z).view(-1, 1, 28, 28)
class Discriminator(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
nn.Linear(784, 512),
nn.LeakyReLU(0.2),
nn.Linear(512, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 1),
nn.Sigmoid()
)
def forward(self, img):
img_flat = img.view(img.size(0), -1)
return self.model(img_flat)
模型效果评估(包括:准确率、精确率、召回率、F1 分数、R-squared、均方误差等模型性能评估指标计算方法)
模型应用部署
ONNX
import onnx
model = onnx.load('model.onnx') # 使用load方法加载模型到model
type(model) # 使用type方法打印模型格式,这会输出:onnx.onnx_ml_pb2.ModelProto
print(model) # 输出一个ModelProto对象的字符串表示形式,这将会展示很多细节
model_graph = model.graph # 获得模型计算图
print(model_graph.input) # 查看模型输入
print(model_graph.output) # 查看模型输出
# 可视化模型所有节点
for node in model_graph.node:
print(f"Node name: {node.name}") # 节点名称
print(f"Node operation: {node.op_type}") # 节点操作类型
print(f"Node inputs: {node.input}") # 节点输入
print(f"Node outputs: {node.output}") # 节点输出
print("\n")
ONNX常见算子
- Batch Normalization (BatchNorm)
Sub: 从输入中减去均值Div: 用标准差归一化Mul: 乘以缩放因子(gamma)Add: 加上偏置(beta)
- Softmax
Exp: 对输入进行指数运算ReduceSum: 对指数值求和Div: 将指数值除以和,以得到概率分布
- Dropout
RandomUniform: 生成随机掩码Mul: 将掩码应用于输入Div: 对激活值进行缩放(通常按保持率的倒数缩放)
# 在建立一个torch神经网络之后可以
import torch.onnx
# 创建模型实例
model = Model()
dummy_input = torch.randn(1, 1, 28, 28)
# 基础转换
torch.onnx.export(model, dummy_input, "model.onnx")
model:需要转换的 Pytorch 模型实例。dummy_input:用于模型的输入张量,用来追踪模型的计算图。"model.onnx":导出的 ONNX 模型的保存路径。
import onnx
# 加载 ONNX 模型
onnx_model = onnx.load("model.onnx")
# 检查模型,若模型存在错误或者不一致性,函数会抛出`onnx.checker.ValidationError` 异常,详细描述模型中的问题
onnx.checker.check_model(onnx_model)
# 若模型存在动态操作
# 通过 torch.jit.trace 进行追踪
traced_model = torch.jit.script(branch_model)
dummy_input = torch.randn(1,10)
# 导出为 ONNX
torch.onnx.export(traced_model, dummy_input, "traced_model.onnx")
ONNXRuntime
# 加载ONNX模型
import onnxruntime as ort
session = ort.InferenceSession(model_path)
ort.InferenceSession用于创建一个推理会话,该会话将载入指定路径下的 ONNX 模型。model_path是模型文件的路径。
# 查看输入输出格式,输出同理
input_details = [input_ for input_ in session.get_inputs()]
for idx, input_detail in enumerate(input_details):x
print(f"第{idx+1}个输入形状:{input_detail.shape}, 数据类型:{input_detail.type}, 输入名称:{input_detail.name}")
# 模型推理
input_data1 = np.random.rand(3, 10).astype(np.float32)
input_data2 = np.random.rand(3, 20).astype(np.float32)
outputs = session.run(None, {"input1": input_data1, "input2": input_data2})
# None表示获取所有输出
print(outputs)
# 获取中间结果
import copy
import onnx
# 加载 ONNX 模型文件
onnx_model = onnx.load("multi_io_model.onnx")
# 深拷贝模型的输出信息,ori_output 保存原始模型输出的结构,防止后续修改影响原有模型
ori_output = copy.deepcopy(onnx_model.graph.output)
# 获取模型中每一层的输出
for node in onnx_model.graph.node:
for output in node.output:
# 将每个节点的输出添加到模型的输出列表中
onnx_model.graph.output.extend([onnx.ValueInfoProto(name=output)])
# 使用 ONNX Runtime 创建推理会话,SerializeToString 将模型序列化为字节流
ort_session = ort.InferenceSession(onnx_model.SerializeToString())
# 运行推理,使用 input_data1 和 input_data2 作为模型的输入
ort_outs = ort_session.run(None, {"input1": input_data1, "input2": input_data2})
# 获取模型中所有输出节点的名称
outputs = [x.name for x in ort_session.get_outputs()]
# 将输出的名称和对应的输出值以有序字典的形式组合,便于查找
ort_outs = OrderedDict(zip(outputs, ort_outs))
print(ort_outs)
# 模型量化
from onnxruntime.quantization import quantize_dynamic, QuantType
# 动态量化模型并保存
quantize_dynamic(model_path, 'model_dynamic_quant.onnx', weight_type=QuantType.QInt8)
model_path:我们前面已经加载的模型名称。'model_dynamic_quant.onnx':量化后保存文件的路径。weight_type:量化的数据类型,这里选择 int8 类型。
Flask 框架应用
入门程序
from flask import Flask
app = Flask(__name__)
@app.route('/')
def test():
return 'test'
app.run()
直接部署
from flask import Flask, request, jsonify
import onnxruntime as ort
app = Flask(__name__)
# 加载预先训练好的 ONNX 模型
session = ort.InferenceSession('deploy.onnx')
@app.route("/")
def welcome_lanqiao():
return "欢迎参加蓝桥杯"
# 定义路由'/predict',并允许 POST 请求
@app.route("/predict", methods=['POST'])
def predict():
# 从请求中获取 JSON 格式的数据,并从中提取出输入数据
inputs = request.get_json()['inputs']
# 使用 ONNX 会话对象执行模型推理,并获取输出结果
output = session.run(None, {"input": inputs})[0]
# 将输出结果转换为列表,并封装成 JSON 格式返回给客户端
return jsonify({'output': output.tolist()})
app.run()
线程锁与热更新
from flask import Flask, request, jsonify
import onnxruntime as ort
import threading # 导入 threading 模块,用于实现线程锁
app = Flask(__name__)
# 在应用启动时加载模型
session = ort.InferenceSession('deploy.onnx')
# 线程锁,用于在更新模型时确保线程安全
model_lock = threading.Lock()
@app.route("/")
def welcome_lanqiao():
return "欢迎参加蓝桥杯"
@app.route("/predict", methods=["POST"])
def predict():
# 在一个函数内部修改一个全局变量,需要使用 global 关键字来声明这个变量是在全局作用域中定义的
global session
inputs = request.json['inputs']
# 加锁,确保在推理过程中模型不会被更新
with model_lock:
output = session.run(None, {"input": inputs})[0]
return jsonify({'output': output.tolist(), 'model_path': session._model_path})
@app.route("/update_model", methods=["POST"])
def update_model():
global session
# 从请求中获取新的模型路径
new_model_path = request.json['model_path']
# 加锁,在更新模型时确保线程安全
with model_lock:
# 加载新的 ONNX 模型
session = ort.InferenceSession(new_model_path)
# 返回模型更新成功的消息
return "模型已更新成功"
app.run()
单例模式
import threading
import onnxruntime as ort
from flask import Flask, request, jsonify
import threading
import onnxruntime as ort
class ModelSingleton:
_instance = None # 类变量,用于保存单例对象实例
_lock = threading.Lock() # 创建一个锁对象用于线程安全地初始化单例
# 重写__new__方法来控制类实例的创建
def __new__(cls, model_path): # 类方法的第一个参数是类 cls 本身
with cls._lock: # 使用锁确保线程安全
if cls._instance is None: # 检查是否已经创建了单例
cls._instance = super().__new__(cls) # 如果没有,则调用父类的__new__方法创建实例
# 创建 ONNX 推理会话,并将模型路径与会话关联
cls._instance.session = ort.InferenceSession(model_path)
cls._instance.model_path = model_path # 记录当前加载的模型路径
# 返回单例实例
return cls._instance
# 更新模型的方法
def update_model(self, new_model_path): # 方法接受新的模型路径作为参数
with self._lock: # 使用锁确保线程安全
self.session = ort.InferenceSession(new_model_path) # 更新 ONNX 推理会话
self.model_path = new_model_path # 更新记录的模型路径
# 使用示例
model = ModelSingleton('deploy.onnx')
model.update_model('deploy_new.onnx')
app = Flask(__name__)
class ModelSingleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, model_path):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.session = ort.InferenceSession(model_path)
cls._instance.model_path = model_path
return cls._instance
def update_model(self, new_model_path):
with self._lock:
self.session = ort.InferenceSession(new_model_path)
self.model_path = new_model_path
# 初始化单例模型实例
model_instance = ModelSingleton('deploy.onnx')
@app.route("/")
def welcome_lanqiao():
return "欢迎参加蓝桥杯"
@app.route("/predict", methods=["POST"])
def predict():
inputs = request.json['inputs']
with model_instance._lock:
output = model_instance.session.run(None, {"input": inputs})[0]
return jsonify({'output': output.tolist(), 'model_path': model_instance.model_path})
@app.route("/update_model", methods=["POST"])
def update_model():
new_model_path = request.json['model_path']
model_instance.update_model(new_model_path)
return "模型已更新成功"
app.run()
高吞吐下的批处理
from flask import Flask, request, jsonify
import numpy as np
import onnxruntime as ort
app = Flask(__name__)
session = ort.InferenceSession('mobilenetv2.onnx')
# 定义批次大小
BATCH_SIZE = 32
def batch_predict(inputs):
# 初始化一个空列表来收集所有的预测结果
output = []
# 遍历输入数据的索引,步长为 BATCH_SIZE
for i in range(0, len(inputs), BATCH_SIZE):
# 使用切片来获取当前批次的输入数据
batch_inputs = inputs[i:i+BATCH_SIZE]
batch_outputs = session.run(None, {"input": batch_inputs})[0]
# 将当前批次的预测结果添加到总结果列表中
output.extend(batch_outputs)
# 返回所有批次的预测结果
return output
@app.route("/predict", methods=['POST'])
def predict():
inputs = request.get_json()['inputs']
output = batch_predict(inputs)
label = np.argmax(output, axis=1).tolist()
return jsonify({'label': label})
app.run()
简单的自适应调节
from flask import Flask, request, jsonify
import numpy as np
import onnxruntime as ort
import time
app = Flask(__name__)
session = ort.InferenceSession('mobilenetv2.onnx')
BATCH_SIZE = 32 # 初始批处理大小
MAX_TIME = 0.5 # 最大允许单次请求处理时间(秒)
def batch_predict(inputs):
output = []
global BATCH_SIZE
start_time = time.time() # 开始时间
for i in range(0, len(inputs), BATCH_SIZE):
batch_inputs = inputs[i:i+BATCH_SIZE]
batch_outputs = session.run(None, {"input": batch_inputs})[0]
output.extend(batch_outputs)
duration = time.time() - start_time # 计算总耗时
if duration > MAX_TIME and BATCH_SIZE > 1:
BATCH_SIZE -= 1 # 动态减小批处理大小
elif duration < MAX_TIME / 2 and BATCH_SIZE < 64: # 防止批处理大小过大
BATCH_SIZE += 1 # 动态增加批处理大小
return output
@app.route("/predict", methods=['POST'])
def predict():
inputs = request.get_json()['inputs']
output = batch_predict(inputs)
label = np.argmax(output, axis=1).tolist()
return jsonify({'label': label})
if __name__ == '__main__':
app.run()
缓存机制
from flask import Flask, request, jsonify
import numpy as np
import onnxruntime as ort
import hashlib
import pickle
app = Flask(__name__)
session = ort.InferenceSession('mobilenetv2.onnx')
# 使用字典来作为缓存
cache = {}
def get_cache_key(input_data):
# 使用单个输入数据的哈希值作为缓存键
return hashlib.md5(pickle.dumps(input_data)).hexdigest()
# ^MD5哈希值 ^序列化为字节流 ^转换为16进制字符串
@app.route("/predict", methods=['POST'])
def predict():
inputs = request.get_json()['inputs']
cached_labels = []
cache_miss_indices = []
cache_miss_data = []
# 检查缓存并记录没有缓存的数据
for index, single_input in enumerate(inputs):
cache_key = get_cache_key(single_input)
if cache_key in cache:
cached_labels.append((index, cache[cache_key]))
else:
cache_miss_indices.append(index)
cache_miss_data.append(single_input)
# 对缓存未命中的数据进行模型推理
if cache_miss_data:
batch_outputs = session.run(None, {"input": cache_miss_data})[0]
batch_labels = np.argmax(batch_outputs, axis=1).tolist()
# 更新缓存并记录这部分数据的结果
for miss_index, data, label in zip(cache_miss_indices, cache_miss_data, batch_labels):
cache_key = get_cache_key(data)
cache[cache_key] = label
cached_labels.append((miss_index, label))
# 将结果按照原始请求顺序组装
cached_labels.sort() # 由于是元组列表,并且索引在前,这将按原始顺序排序
final_labels = [label for _, label in cached_labels]
return jsonify({'label': final_labels})
app.run()
人生意义思考
意义是一种询问不来的东西,它甚至思考不来。《自卑与超越》中写到,人对于他生活意义的思考,其实最多地体现在他的行为上。行为就是人三观的反应(其实很多人没有形成明确具体的三观,所以这句话是不严谨的,但我姑且这么说),同时行为也反应着他认为的人生意义。人们能从一个人做了什么,模糊地感知,他认为的生活意义。
但是,对于“人生的意义是什么”这个问题本身,它是有源头的。人们只会在无法从自己的行为与经历感受充实时问出这个问题。从来不会有人问“我现在是为什么要来吃饭呀”,但经常有人问“我为什么要来处理这件琐事”。而空虚在长期积累后,人一定会问出这个哲学的终极问题:“我要干什么?我为什么要这么干?人生的意义是什么?”。有的人对空虚的感知比较敏感,一两天便足以让他抛出这个问题。而有的人对空虚的感受很弱,需要及其长期的积累,也许是四五十年,他会抛出这个问题。抛出这个问题的根本原因是,空虚使人迫切的想知道解决方案。人们总是觉得,只要能获得这个问题的答案,他能够马上从宇宙中汲取力量,摆脱空虚,获得更加美好的人生。
但这是不科学的。
一个问题怎么能够回答人生呢?人生意义应该从每一天,每一刻的事情中得到。如果未来的你会对现在做的事感到充实,那么这件事就是有意义的。如果生命中有意义的事情逐渐积累,一个有意义的人生就成功搭建了。而如何对所做的事感到充实。恭喜,你开始思考下一个问题了,人生意义的问题已经得到了暂时的解决。
思而不学则殆,以后的几天我应该会继续学一些哲学内容,等到有想法了再来写一些思考。
昨天对话的继续
现在网络上男女对立的话题中,有一个问题:男性擅长理性思考和解决问题,女性擅长表达情绪与理解情绪。这次对话冲击了这个说法。母亲一直在感受我的空虚与失落,与此同时在帮助我探索原因和寻找方法。而我则持续地输出情绪,要不是母亲,可能问题会发展到纯情绪方向。
突然想写一些我目前对于网络上男女对立问题的思考(别把我帖子删了呀球球)
我觉得现在男女对立的趋势不会因为国家的调控而缓和,除开一些境外反动势力想借此瓦解中国民族凝聚力的因素,因为这之中一些问题是根本性的,无法调和的。当今中国社会的最大错位是:人们守着农业时代的传统,怀揣工业时代的知识,生活在信息时代。这个巨大错位导致,生产力的大发展导致女性生产力对男性进行了大幅度的追赶,与此同时,人越来越可以独自生存而不依赖任何人(注意我说的是依赖而不是依靠),可是传统的家庭结构是从农业时代早期建立的,它正在遭受严重冲击。这是生产力和生产关系之间的冲突,是根本性的,这个问题随着生产力的进一步发展必然需要进行变化。而家庭这样东西收到的冲击,连带着引发了很多的男女对立问题。
不想写了,睡觉去了。
浙公网安备 33010602011771号