四月二十三日

今天主要写了一些东西,然后对于昨天的人生意义的思考,有了一些新的理解。如果最后有空的话,把昨天没写完的对话再写一点。
无私的互联网分享精神,刚入门人工智能的可以跟着这个学,绝对不会误导你们。

人工智能入门

数据预处理

数据(数值、文本、图像等)清洗

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_1​x$ 进行线性拟合,推导的最终最优结果是$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常见算子

  1. Batch Normalization (BatchNorm)
    • Sub : 从输入中减去均值
    • Div : 用标准差归一化
    • Mul : 乘以缩放因子(gamma)
    • Add : 加上偏置(beta)
  2. Softmax
    • Exp : 对输入进行指数运算
    • ReduceSum : 对指数值求和
    • Div : 将指数值除以和,以得到概率分布
  3. 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()

人生意义思考

意义是一种询问不来的东西,它甚至思考不来。《自卑与超越》中写到,人对于他生活意义的思考,其实最多地体现在他的行为上。行为就是人三观的反应(其实很多人没有形成明确具体的三观,所以这句话是不严谨的,但我姑且这么说),同时行为也反应着他认为的人生意义。人们能从一个人做了什么,模糊地感知,他认为的生活意义。
但是,对于“人生的意义是什么”这个问题本身,它是有源头的。人们只会在无法从自己的行为与经历感受充实时问出这个问题。从来不会有人问“我现在是为什么要来吃饭呀”,但经常有人问“我为什么要来处理这件琐事”。而空虚在长期积累后,人一定会问出这个哲学的终极问题:“我要干什么?我为什么要这么干?人生的意义是什么?”。有的人对空虚的感知比较敏感,一两天便足以让他抛出这个问题。而有的人对空虚的感受很弱,需要及其长期的积累,也许是四五十年,他会抛出这个问题。抛出这个问题的根本原因是,空虚使人迫切的想知道解决方案。人们总是觉得,只要能获得这个问题的答案,他能够马上从宇宙中汲取力量,摆脱空虚,获得更加美好的人生。
但这是不科学的。
一个问题怎么能够回答人生呢?人生意义应该从每一天,每一刻的事情中得到。如果未来的你会对现在做的事感到充实,那么这件事就是有意义的。如果生命中有意义的事情逐渐积累,一个有意义的人生就成功搭建了。而如何对所做的事感到充实。恭喜,你开始思考下一个问题了,人生意义的问题已经得到了暂时的解决。
思而不学则殆,以后的几天我应该会继续学一些哲学内容,等到有想法了再来写一些思考。

昨天对话的继续

现在网络上男女对立的话题中,有一个问题:男性擅长理性思考和解决问题,女性擅长表达情绪与理解情绪。这次对话冲击了这个说法。母亲一直在感受我的空虚与失落,与此同时在帮助我探索原因和寻找方法。而我则持续地输出情绪,要不是母亲,可能问题会发展到纯情绪方向。

突然想写一些我目前对于网络上男女对立问题的思考(别把我帖子删了呀球球)

我觉得现在男女对立的趋势不会因为国家的调控而缓和,除开一些境外反动势力想借此瓦解中国民族凝聚力的因素,因为这之中一些问题是根本性的,无法调和的。当今中国社会的最大错位是:人们守着农业时代的传统,怀揣工业时代的知识,生活在信息时代。这个巨大错位导致,生产力的大发展导致女性生产力对男性进行了大幅度的追赶,与此同时,人越来越可以独自生存而不依赖任何人(注意我说的是依赖而不是依靠),可是传统的家庭结构是从农业时代早期建立的,它正在遭受严重冲击。这是生产力和生产关系之间的冲突,是根本性的,这个问题随着生产力的进一步发展必然需要进行变化。而家庭这样东西收到的冲击,连带着引发了很多的男女对立问题。

不想写了,睡觉去了。

posted @ 2025-04-23 00:06  xzclviuhasddlkjbre  阅读(77)  评论(0)    收藏  举报