首页> 搜索结果页
"可以python代码" 检索
共 32210 条结果
【python进阶】你还在使用for循环新建数组?生成器表达式帮你一行解决
引言‍♂️作者简介:生鱼同学,大数据科学与技术专业硕士在读‍,曾获得华为杯数学建模国家二等奖,MathorCup 数学建模竞赛国家二等奖,亚太数学建模国家二等奖。✍️研究方向:复杂网络科学兴趣方向:利用python进行数据分析与机器学习,数学建模竞赛经验交流,网络爬虫等。在python学习的过程中,我们最先接触到的就是python的数组,元组,字典等基础类型,但很少有人深入讨论python的内置序列类型以及它们的高级使用姿势。深度学习python的内置序列,不仅能让我们编写的API更加的易用简介,也能够更好的理解python中各种序列的特性。在本文中,我们就来一起解锁python内置序列的高级用法,玩转pyhon序列。内置序列类型python中有很多的序列类型,主要可以分为以下两类:容器序列:能存放不同数据类型的数据的序列。(list, tuple, collections.deque)扁平序列:只能容纳一种类型的序列。(str, bytes, bytearray, memoryview, array.array)说明:扁平序列储存的是一段连续的内存空间,而容器序列存放的是它们包含的任意类型对象的引用。另外,序列类型还可以从可修改与不可修改的角度进行分类,主要能被分成以下两类:可变序列:list, bytearray, array.array, collections.deque, memoryview不可变序列:str, tuple, bytes为了深入的讨论可变序列与不可变序列的差异,我们看下面这个UML图:在上图中,继承从子类指向超类,可以看到可变序列(MutableSequence)继承了不可变序列(Sequence)的很多方法。与此同时,通过UML图我们也可以更直观的发现其不同的地方,这有助于我们了解后续的内置序列类型的差异。列表推导与生成器表达式列表推导相信大家已经对基础的序列类型list有了初步的了解与认识,但当我们想要创建一个新的数组时,往往会想到使用for循环遍历生成。其实在python中还存在一种构建列表的方法叫做列表推导(list comprehension),它是构建列表的快捷方式,同时也能够使你的代码更加易读与简洁。假设我们需要创建从0到10的一个列表,我们来看下面的两段代码:# 不使用列表推导 example_list_01 = [] for i in range(10): example_list_01.append(i) print(example_list_01) >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]# 列表推导 example_list_01 = [i for i in range(10)] print(example_list_01) >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]相信大部分人第一时间都会考虑使用第一种方法进行列表的创建,但明显使用了列表推导(生成器表达式推导列表)的例子看起来更加简便且易读。我们再来看一个更复杂的例子,假设我们想要寻找10以内的偶数,我们看下面两段代码:# 不使用列表推导 example_list_02 = [] for i in range(10): if i % 2 == 0: example_list_02.append(i) print(example_list_02)# 列表推导 example_list_02 = [i for i in range(10) if i % 2 == 0] print(example_list_02)显然,下面的代码可读性更强且更为简单。另外,使用filter也能够完成上述的功能,但是可读性并不强。我们使用filter完成上述功能的代码如下:example_list_03 = list(filter(lambda i: i % 2 == 0, range(10))) print(example_list_03)显然,这样的可读性并不强。在列表推导中,我们还可以将自己的函数或者python内置函数直接对生成的数组进行处理,请看下面这个例子:def deal(num): return '处理过的' + str(num) deal_list = [deal(i) for i in range(10)] print(deal_list ) >>> ['处理过的0', '处理过的1', '处理过的2', '处理过的3', '处理过的4', '处理过的5', '处理过的6', '处理过的7', '处理过的8', '处理过的9']最后,我们再用列表推导表达式尝试计算笛卡尔积并与for循环完成的相同的功能做对比,请看下面的代码:colors = ['红色','蓝色','绿色'] clothes = ['上衣','裤子','运动鞋'] clothes_list_01 = [] for color in colors: for clothe in clothes: clothes_list_01.append((color,clothe)) print('未使用列表推导:',clothes_list_01) clothes_list_02 = [(color,clothe) for color in colors for clothe in clothes] print('使用列表推导:',clothes_list_01)结果如下:未使用列表推导: [('红色', '上衣'), ('红色', '裤子'), ('红色', '运动鞋'), ('蓝色', '上衣'), ('蓝色', '裤子'), ('蓝色', '运动鞋'), ('绿色', '上衣'), ('绿色', '裤子'), ('绿色', '运动鞋')] 使用列表推导: [('红色', '上衣'), ('红色', '裤子'), ('红色', '运动鞋'), ('蓝色', '上衣'), ('蓝色', '裤子'), ('蓝色', '运动鞋'), ('绿色', '上衣'), ('绿色', '裤子'), ('绿色', '运动鞋')]可以看到输出的结果是完全相同的,但是利用列表推导的代码更为简洁。生成器表达式虽然使用上述的列表推导语法也可以生成元组等其他类型的序列,但是使用生成器表达式会更好。生成器并不是先建立一个完整的列表再将其传递到某个构造函数内,而是逐个产出元素,这会更加的节省内存。我们看下面几个例子,用来了解生成器表达式是如何生成字典与元组的。# 使用生成器表达式构建字典 dict_transform_list = [('APPLE', '苹果'), ('BNANA', '香蕉'), ('PEAR', '梨子')] dict_01 = {key: value for key,value in dict_transform_list} >>>{'APPLE': '苹果', 'BNANA': '香蕉', 'PEAR': '梨子'}# 使用生成器表达式构建元组 tuple_01 = tuple(i for i in range(10)) >>>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)总结在本文中,介绍了生成器与表达式的用法,帮助我们快速创建数组以及其他序列,解锁了python序列的新姿势。在后续的更新中,我将继续对元组的高级姿势和玩法进行介绍。如果你觉得本文对你有帮助的话,希望你点个收藏或帮我点个赞,如果有其他问题也可以私信我与我交流或在评论区与我讨论,我们下次再见。
文章
机器学习/深度学习  ·  数据采集  ·  数据挖掘  ·  大数据  ·  API  ·  uml  ·  Python  ·  容器
2023-03-17
机器学习算法(一): 基于逻辑回归的分类预测
机器学习算法(一): 基于逻辑回归的分类预测项目链接参考fork一下直接运行:https://www.heywhale.com/home/column/64141d6b1c8c8b518ba97dcc1 逻辑回归的介绍和应用1.1 逻辑回归的介绍逻辑回归(Logistic regression,简称LR)虽然其中带有"回归"两个字,但逻辑回归其实是一个分类模型,并且广泛应用于各个领域之中。虽然现在深度学习相对于这些传统方法更为火热,但实则这些传统方法由于其独特的优势依然广泛应用于各个领域中。而对于逻辑回归而且,最为突出的两点就是其模型简单和模型的可解释性强。逻辑回归模型的优劣势:优点:实现简单,易于理解和实现;计算代价不高,速度很快,存储资源低;缺点:容易欠拟合,分类精度可能不高1.1 逻辑回归的应用逻辑回归模型广泛用于各个领域,包括机器学习,大多数医学领域和社会科学。例如,最初由Boyd 等人开发的创伤和损伤严重度评分(TRISS)被广泛用于预测受伤患者的死亡率,使用逻辑回归 基于观察到的患者特征(年龄,性别,体重指数,各种血液检查的结果等)分析预测发生特定疾病(例如糖尿病,冠心病)的风险。逻辑回归模型也用于预测在给定的过程中,系统或产品的故障的可能性。还用于市场营销应用程序,例如预测客户购买产品或中止订购的倾向等。在经济学中它可以用来预测一个人选择进入劳动力市场的可能性,而商业应用则可以用来预测房主拖欠抵押贷款的可能性。条件随机字段是逻辑回归到顺序数据的扩展,用于自然语言处理。逻辑回归模型现在同样是很多分类算法的基础组件,比如 分类任务中基于GBDT算法+LR逻辑回归实现的信用卡交易反欺诈,CTR(点击通过率)预估等,其好处在于输出值自然地落在0到1之间,并且有概率意义。模型清晰,有对应的概率学理论基础。它拟合出来的参数就代表了每一个特征(feature)对结果的影响。也是一个理解数据的好工具。但同时由于其本质上是一个线性的分类器,所以不能应对较为复杂的数据情况。很多时候我们也会拿逻辑回归模型去做一些任务尝试的基线(基础水平)。说了这些逻辑回归的概念和应用,大家应该已经对其有所期待了吧,那么我们现在开始吧!!!2 学习目标了解 逻辑回归 的理论掌握 逻辑回归 的 sklearn 函数调用使用并将其运用到鸢尾花数据集预测3 代码流程Part1 Demo实践Step1:库函数导入Step2:模型训练Step3:模型参数查看Step4:数据和模型可视化Step5:模型预测Part2 基于鸢尾花(iris)数据集的逻辑回归分类实践Step1:库函数导入Step2:数据读取/载入Step3:数据信息简单查看Step4:可视化描述Step5:利用 逻辑回归模型 在二分类上 进行训练和预测Step5:利用 逻辑回归模型 在三分类(多分类)上 进行训练和预测4 算法实战### 4.1 Demo实践Step1:库函数导入## 基础函数库 import numpy as np ## 导入画图库 import matplotlib.pyplot as plt import seaborn as sns ## 导入逻辑回归模型函数 from sklearn.linear_model import LogisticRegressionStep2:模型训练##Demo演示LogisticRegression分类 ## 构造数据集 x_fearures = np.array([[-1, -2], [-2, -1], [-3, -2], [1, 3], [2, 1], [3, 2]]) y_label = np.array([0, 0, 0, 1, 1, 1]) ## 调用逻辑回归模型 lr_clf = LogisticRegression() ## 用逻辑回归模型拟合构造的数据集 lr_clf = lr_clf.fit(x_fearures, y_label) #其拟合方程为 y=w0+w1*x1+w2*x2Step3:模型参数查看## 查看其对应模型的w print('the weight of Logistic Regression:',lr_clf.coef_) ## 查看其对应模型的w0 print('the intercept(w0) of Logistic Regression:',lr_clf.intercept_) the weight of Logistic Regression: [[0.73455784 0.69539712]] the intercept(w0) of Logistic Regression: [-0.13139986]Step4:数据和模型可视化## 可视化构造的数据样本点 plt.figure() plt.scatter(x_fearures[:,0],x_fearures[:,1], c=y_label, s=50, cmap='viridis') plt.title('Dataset') plt.show()# 可视化决策边界 plt.figure() plt.scatter(x_fearures[:,0],x_fearures[:,1], c=y_label, s=50, cmap='viridis') plt.title('Dataset') nx, ny = 200, 100 x_min, x_max = plt.xlim() y_min, y_max = plt.ylim() x_grid, y_grid = np.meshgrid(np.linspace(x_min, x_max, nx),np.linspace(y_min, y_max, ny)) z_proba = lr_clf.predict_proba(np.c_[x_grid.ravel(), y_grid.ravel()]) z_proba = z_proba[:, 1].reshape(x_grid.shape) plt.contour(x_grid, y_grid, z_proba, [0.5], linewidths=2., colors='blue') plt.show()### 可视化预测新样本 plt.figure() ## new point 1 x_fearures_new1 = np.array([[0, -1]]) plt.scatter(x_fearures_new1[:,0],x_fearures_new1[:,1], s=50, cmap='viridis') plt.annotate(s='New point 1',xy=(0,-1),xytext=(-2,0),color='blue',arrowprops=dict(arrowstyle='-|>',connectionstyle='arc3',color='red')) ## new point 2 x_fearures_new2 = np.array([[1, 2]]) plt.scatter(x_fearures_new2[:,0],x_fearures_new2[:,1], s=50, cmap='viridis') plt.annotate(s='New point 2',xy=(1,2),xytext=(-1.5,2.5),color='red',arrowprops=dict(arrowstyle='-|>',connectionstyle='arc3',color='red')) ## 训练样本 plt.scatter(x_fearures[:,0],x_fearures[:,1], c=y_label, s=50, cmap='viridis') plt.title('Dataset') # 可视化决策边界 plt.contour(x_grid, y_grid, z_proba, [0.5], linewidths=2., colors='blue') plt.show()Step5:模型预测## 在训练集和测试集上分别利用训练好的模型进行预测 y_label_new1_predict = lr_clf.predict(x_fearures_new1) y_label_new2_predict = lr_clf.predict(x_fearures_new2) print('The New point 1 predict class:\n',y_label_new1_predict) print('The New point 2 predict class:\n',y_label_new2_predict) ## 由于逻辑回归模型是概率预测模型(前文介绍的 p = p(y=1|x,\theta)),所以我们可以利用 predict_proba 函数预测其概率 y_label_new1_predict_proba = lr_clf.predict_proba(x_fearures_new1) y_label_new2_predict_proba = lr_clf.predict_proba(x_fearures_new2) print('The New point 1 predict Probability of each class:\n',y_label_new1_predict_proba) print('The New point 2 predict Probability of each class:\n',y_label_new2_predict_proba) The New point 1 predict class: [0] The New point 2 predict class: [1] The New point 1 predict Probability of each class: [[0.69567724 0.30432276]] The New point 2 predict Probability of each class: [[0.11983936 0.88016064]]可以发现训练好的回归模型将X_new1预测为了类别0(判别面左下侧),X_new2预测为了类别1(判别面右上侧)。其训练得到的逻辑回归模型的概率为0.5的判别面为上图中蓝色的线。4.2 基于鸢尾花(iris)数据集的逻辑回归分类实践在实践的最开始,我们首先需要导入一些基础的函数库包括:numpy (Python进行科学计算的基础软件包),pandas(pandas是一种快速,强大,灵活且易于使用的开源数据分析和处理工具),matplotlib和seaborn绘图。Step1:库函数导入## 基础函数库 import numpy as np import pandas as pd ## 绘图函数库 import matplotlib.pyplot as plt import seaborn as sns本次我们选择鸢花数据(iris)进行方法的尝试训练,该数据集一共包含5个变量,其中4个特征变量,1个目标分类变量。共有150个样本,目标变量为 花的类别 其都属于鸢尾属下的三个亚属,分别是山鸢尾 (Iris-setosa),变色鸢尾(Iris-versicolor)和维吉尼亚鸢尾(Iris-virginica)。包含的三种鸢尾花的四个特征,分别是花萼长度(cm)、花萼宽度(cm)、花瓣长度(cm)、花瓣宽度(cm),这些形态特征在过去被用来识别物种。变量描述sepal length花萼长度(cm)sepal width花萼宽度(cm)petal length花瓣长度(cm)petal width花瓣宽度(cm)target鸢尾的三个亚属类别,'setosa'(0), 'versicolor'(1), 'virginica'(2)Step2:数据读取/载入## 我们利用 sklearn 中自带的 iris 数据作为数据载入,并利用Pandas转化为DataFrame格式 from sklearn.datasets import load_iris data = load_iris() #得到数据特征 iris_target = data.target #得到数据对应的标签 iris_features = pd.DataFrame(data=data.data, columns=data.feature_names) #利用Pandas转化为DataFrame格式Step3:数据信息简单查看## 利用.info()查看数据的整体信息 iris_features.info() # Column Non-Null Count Dtype --- ------ -------------- ----- 0 sepal length (cm) 150 non-null float64 1 sepal width (cm) 150 non-null float64 2 petal length (cm) 150 non-null float64 3 petal width (cm) 150 non-null float64 dtypes: float64(4) memory usage: 4.8 KB ## 对于特征进行一些统计描述 iris_features.describe() sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) count 150.000000 150.000000 150.000000 150.000000 mean 5.843333 3.057333 3.758000 1.199333 std 0.828066 0.435866 1.765298 0.762238 min 4.300000 2.000000 1.000000 0.100000 25% 5.100000 2.800000 1.600000 0.300000 50% 5.800000 3.000000 4.350000 1.300000 75% 6.400000 3.300000 5.100000 1.800000 max 7.900000 4.400000 6.900000 2.500000Step4:可视化描述## 合并标签和特征信息 iris_all = iris_features.copy() ##进行浅拷贝,防止对于原始数据的修改 iris_all['target'] = iris_target ## 特征与标签组合的散点可视化 sns.pairplot(data=iris_all,diag_kind='hist', hue= 'target') plt.show() 从上图可以发现,在2D情况下不同的特征组合对于不同类别的花的散点分布,以及大概的区分能力。for col in iris_features.columns: sns.boxplot(x='target', y=col, saturation=0.5,palette='pastel', data=iris_all) plt.title(col) plt.show()利用箱型图我们也可以得到不同类别在不同特征上的分布差异情况。# 选取其前三个特征绘制三维散点图 from mpl_toolkits.mplot3d import Axes3D fig = plt.figure(figsize=(10,8)) ax = fig.add_subplot(111, projection='3d') iris_all_class0 = iris_all[iris_all['target']==0].values iris_all_class1 = iris_all[iris_all['target']==1].values iris_all_class2 = iris_all[iris_all['target']==2].values # 'setosa'(0), 'versicolor'(1), 'virginica'(2) ax.scatter(iris_all_class0[:,0], iris_all_class0[:,1], iris_all_class0[:,2],label='setosa') ax.scatter(iris_all_class1[:,0], iris_all_class1[:,1], iris_all_class1[:,2],label='versicolor') ax.scatter(iris_all_class2[:,0], iris_all_class2[:,1], iris_all_class2[:,2],label='virginica') plt.legend() plt.show()Step5:利用 逻辑回归模型 在二分类上 进行训练和预测## 为了正确评估模型性能,将数据划分为训练集和测试集,并在训练集上训练模型,在测试集上验证模型性能。 from sklearn.model_selection import train_test_split ## 选择其类别为0和1的样本 (不包括类别为2的样本) iris_features_part = iris_features.iloc[:100] iris_target_part = iris_target[:100] ## 测试集大小为20%, 80%/20%分 x_train, x_test, y_train, y_test = train_test_split(iris_features_part, iris_target_part, test_size = 0.2, random_state = 2020) ## 从sklearn中导入逻辑回归模型 from sklearn.linear_model import LogisticRegression ## 定义 逻辑回归模型 clf = LogisticRegression(random_state=0, solver='lbfgs') # 在训练集上训练逻辑回归模型 clf.fit(x_train, y_train) ## 查看其对应的w print('the weight of Logistic Regression:',clf.coef_) ## 查看其对应的w0 print('the intercept(w0) of Logistic Regression:',clf.intercept_) ## 在训练集和测试集上分布利用训练好的模型进行预测 train_predict = clf.predict(x_train) test_predict = clf.predict(x_test) from sklearn import metrics ## 利用accuracy(准确度)【预测正确的样本数目占总预测样本数目的比例】评估模型效果 print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_train,train_predict)) print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_test,test_predict)) ## 查看混淆矩阵 (预测值和真实值的各类情况统计矩阵) confusion_matrix_result = metrics.confusion_matrix(test_predict,y_test) print('The confusion matrix result:\n',confusion_matrix_result) # 利用热力图对于结果进行可视化 plt.figure(figsize=(8, 6)) sns.heatmap(confusion_matrix_result, annot=True, cmap='Blues') plt.xlabel('Predicted labels') plt.ylabel('True labels') plt.show()The accuracy of the Logistic Regression is: 1.0The accuracy of the Logistic Regression is: 1.0The confusion matrix result: [[ 9 0] [ 0 11]]我们可以发现其准确度为1,代表所有的样本都预测正确了。Step6:利用 逻辑回归模型 在三分类(多分类)上 进行训练和预测## 测试集大小为20%, 80%/20%分 x_train, x_test, y_train, y_test = train_test_split(iris_features, iris_target, test_size = 0.2, random_state = 2020) ## 定义 逻辑回归模型 clf = LogisticRegression(random_state=0, solver='lbfgs') # 在训练集上训练逻辑回归模型 clf.fit(x_train, y_train) ## 查看其对应的w print('the weight of Logistic Regression:\n',clf.coef_) ## 查看其对应的w0 print('the intercept(w0) of Logistic Regression:\n',clf.intercept_) ## 由于这个是3分类,所有我们这里得到了三个逻辑回归模型的参数,其三个逻辑回归组合起来即可实现三分类。 ## 在训练集和测试集上分布利用训练好的模型进行预测 train_predict = clf.predict(x_train) test_predict = clf.predict(x_test) ## 由于逻辑回归模型是概率预测模型(前文介绍的 p = p(y=1|x,\theta)),所有我们可以利用 predict_proba 函数预测其概率 train_predict_proba = clf.predict_proba(x_train) test_predict_proba = clf.predict_proba(x_test) print('The test predict Probability of each class:\n',test_predict_proba) ## 其中第一列代表预测为0类的概率,第二列代表预测为1类的概率,第三列代表预测为2类的概率。 ## 利用accuracy(准确度)【预测正确的样本数目占总预测样本数目的比例】评估模型效果 print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_train,train_predict)) print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_test,test_predict)) [9.35695863e-01 6.43039513e-02 1.85301359e-07] [9.80621190e-01 1.93787400e-02 7.00125246e-08] [1.68478815e-04 3.30167226e-01 6.69664295e-01] [3.54046163e-03 4.02267805e-01 5.94191734e-01] [9.70617284e-01 2.93824740e-02 2.42443967e-07] ... [9.64848137e-01 3.51516748e-02 1.87917880e-07] [9.70436779e-01 2.95624025e-02 8.18591606e-07]] The accuracy of the Logistic Regression is: 0.9833333333333333 The accuracy of the Logistic Regression is: 0.8666666666666667## 查看混淆矩阵 confusion_matrix_result = metrics.confusion_matrix(test_predict,y_test) print('The confusion matrix result:\n',confusion_matrix_result) # 利用热力图对于结果进行可视化 plt.figure(figsize=(8, 6)) sns.heatmap(confusion_matrix_result, annot=True, cmap='Blues') plt.xlabel('Predicted labels') plt.ylabel('True labels') plt.show()通过结果我们可以发现,其在三分类的结果的预测准确度上有所下降,其在测试集上的准确度为:$86.67\%$,这是由于'versicolor'(1)和 'virginica'(2)这两个类别的特征,我们从可视化的时候也可以发现,其特征的边界具有一定的模糊性(边界类别混杂,没有明显区分边界),所有在这两类的预测上出现了一定的错误。5 重要知识点逻辑回归 原理简介:Logistic回归虽然名字里带“回归”,但是它实际上是一种分类方法,主要用于两分类问题(即输出只有两种,分别代表两个类别),所以利用了Logistic函数(或称为Sigmoid函数),函数形式为:$$ logi(z)=\frac{1}{1+e^{-z}} $$其对应的函数图像可以表示如下:import numpy as np import matplotlib.pyplot as plt x = np.arange(-5,5,0.01) y = 1/(1+np.exp(-x)) plt.plot(x,y) plt.xlabel('z') plt.ylabel('y') plt.grid() plt.show()通过上图我们可以发现 Logistic 函数是单调递增函数,并且在z=0的时候取值为0.5,并且$logi(\cdot)$函数的取值范围为$(0,1)$。而回归的基本方程为$z=w_0+\sum_i^N w_ix_i$,将回归方程写入其中为:$$ p = p(y=1|x,\theta) = h_\theta(x,\theta)=\frac{1}{1+e^{-(w_0+\sum_i^N w_ix_i)}} $$所以, $p(y=1|x,\theta) = h_\theta(x,\theta)$,$p(y=0|x,\theta) = 1-h_\theta(x,\theta)$逻辑回归从其原理上来说,逻辑回归其实是实现了一个决策边界:对于函数 $y=\frac{1}{1+e^{-z}}$,当 $z=>0$时,$y=>0.5$,分类为1,当 $z<0$时,$y<0.5$,分类为0,其对应的$y$值我们可以视为类别1的概率预测值.对于模型的训练而言:实质上来说就是利用数据求解出对应的模型的特定的$w$。从而得到一个针对于当前数据的特征逻辑回归模型。而对于多分类而言,将多个二分类的逻辑回归组合,即可实现多分类。
文章
机器学习/深度学习  ·  存储  ·  自然语言处理  ·  算法  ·  数据可视化  ·  搜索推荐  ·  数据挖掘  ·  Python
2023-03-22
Linux进程控制
一.进程创建fork()函数:在进程概念这篇文章中,我们浅浅地了解了一下fork函数,它的功能是让父进程去创建一个子进程,并且有两个返回值,对应着父进程的返回值和子进程的返回值。那么,为什么会这样?接下来我们好好地讨论一下fork函数。在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。#include <unistd.h> pid_t fork(void); 返回值:子进程中返回0,父进程返回子进程id,出错返回-1先来看三个问题:1.如何理解fork函数有两个返回值的问题?2.如何理解fork函数返回后,子进程中返回0,父进程返回子进程id?3.如何理解同一个id值,为什么会保存两个不同的值,让if  else  if同时执行?现象看下面代码:#include <stdio.h> #include <unistd.h> int global_value = 100; int main() { pid_t id = fork(); if(id < 0) { printf("fork error\n"); return 1; } else if(id == 0) { int cnt = 0; while(1) { printf("我是子进程, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value); sleep(1); cnt++; if(cnt == 10) { global_value = 300; printf("子进程已经更改了全局的变量啦..........\n"); } } } else { while(1) { printf("我是父进程, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value); sleep(2); } } sleep(1); }结果: 先来解决第二个问题:2.如何理解fork函数返回后,子进程中返回0,父进程返回子进程id?对于这个问题,我们可以结合现实:一个父亲可以有多个孩子,而每个孩子只能也必定有一个父亲。而对于孩子而言,父亲对于孩子来说是具有唯一性的,而孩子对于父亲来说,父亲需要给孩子取名,才能准确地从几个孩子中找到某个孩子,比如son1,son2,son3.所以,父子进程也一样,子进程返回0,是因为父亲只有一位。而父进程返回的是子进程的id,即是孩子的名字。然后来看第一个问题:1.如何理解fork函数有两个返回值的问题?fork()函数,是操作系统提供的函数,在用户空间调用fork函数的时候,实际上就是在调用内核空间中的fork函数。在fork函数的函数主体中,就有创建子进程的相关指令,最后是返回 子进程的pid。那么在返回的时候,是分流了。因为在到达return指令之前,子进程就已经被创建好了,并且有可能已经在OS的运行队列当中,准备被调度,因此,此时对于fork函数的这个return指令,不仅仅是被父进程使用,还会给子进程拿去使用。所以,fork函数就有两个返回值,一个是返回子进程的,一个是返回父进程的。第三个问题:3.如何理解同一个id值,为什么会保存两个不同的值,让if  else  if同时执行?返回的本质就是写入。所以,对于pid_t id = fork();为什么会保存两个不同的值,就先看谁先返回,那就谁先写入id。比如父进程先返回,先写入id,此时id的值是子进程的pid,此时的子进程中的id,它的地址和内容,跟父进程的是一样的,就是指向了同一个地址。但是当子进程返回的时候,此时为了保证进程的独立性,OS就会进行写时拷贝,额外给子进程一个id的空间,此时的现象是:父子进程的id的地址是一样的,但是!内容不一样,这里就用到了上一篇的文章:进程概念   所了解到的进程地址空间的知识。这就是id为什么会保存两个不同的值。然后,父子进程会共用上面那段代码,就是if else if的代码,当id的值对应着不同的判断条件,代码就指向那种指令。也就看到了if  else if会同时执行的现象了。写时拷贝通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图: fork的常规用法1、一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。2、一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。fork也有调用失败的时候,其原因很大可能是系统中有太多的进程或者实际用户的进程数超过了限制。二、进程终止:在谈进程退出情况之前,我们先来聊聊退出码的问题。相信我们在写代码的时候,特别是使用C/C++写代码时,我们都会写main函数,然后最后写一个return 0。那么问题来了,return 0的含义是什么?0又是什么意思?为什么是0,而不是1,不是2等等。其实return 0中的0,就是退出码的意思,而return 0,标定进程退出的结果是否正确。来看下代码和对应的测试结果:#include<stdio.h> int add(int begin, int end) { int i = 0; int sum = 0; for (i = begin; i < end; i++) { sum += i; } return sum; } int main() { //进程退出码的作用就是:让我们得知我们写的代码所完成的任务是否完成 int num = add(1, 100); if (num == 5050) { return 0; } else { return 1; } //进程对出的时候,对应的退出码 //标定进程执行的结果是否正确 //return 0; }运行后,我们通过echo $?来查看退出码的结果:$?是环境变量的一种,$?的作用是永远记录最近的一个进程在命令行中执行完毕时对应的退出码(main---->return ?;) 可以看到,当执行了上面的那个程序之和,退出码的结果是1,但当我们再次执行echo $?的指令后,发现变为0了,是因为$?会对最近的一个程序进行判断。echo $?本身也是一个程序指令,所以后面的退出码为0.接下来我们来看看不同数字的退出码代表着什么意思: 从图中可以得知,0代表着成功的意思,而非0的数字,代表着各种失败的提升。可以举的例子有:当我们在命令行写入:ls asdasdas,打开这样的一个文件,但是我们没有这样的文件,那么可以看到结果如下: 好了,在了解了退出码之和,我们可以谈谈进程退出的情况了。进程退出情况1.代码运行完毕,结果正确  ----return 0;2.代码运行完毕,结果不正确 -------return !0   退出码在这个时候起效3.代码异常终止-----这情况下退出码无意义进程如何结束?有两种办法:1. 从main返回2. 调用exit第一种很好理解,我们的程序都是从main函数开始,最后由main函数的return 0来返回,终止程序。对于第二种,我们需要认识exit()函数。exit函数其实在平时写代码的时候,就用过几次。选择来了解一下它。exit()函数的作用是终止进程,不管在哪调用它:不管是在main函数里面调用exit,还是在main函数调用的函数的内部使用它,只要执行了exit函数,整个进程都会终止。还有一个功能与exit函数类型的,叫做_exit()。它们的区别就在于,exit函数是库函数,而_exit属于系统调用,并且,exit()函数会刷新缓冲区,_exit并不会刷新缓冲区。看下图: 温馨提示:库函数和系统调用的不同之处在于,库函数的调用,本质上就是建立在了系统调用之上,是操作系统提供给用户写代码时使用的函数。库函数——系统调用——OS三者的层次关系大概如下图:当然啦,如果存在父子进程同时使用一段代码的时候,而且exit函数是在当fork函数返回值为0,也就是子进程执行的代码段的时候,终止的子进程。即谁调用谁终止。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。 进程终止就到这。接下来我们来谈谈进程等待。三、进程等待进程等待可以解决僵尸进程问题。所以,进程等待是很有比较性的:1.子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。2.另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。3.最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。4.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息进程等待方法1.wait方法。wait()是一个函数。通过man我们可以搜出其基本信息:pid_t wait(int*status);返回值:成功返回被等待进程pid,失败返回-1。参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL  它的功能是让进程等待,从而时父进程回收子进程资源。看下面的代码:#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t id = fork(); if (id == 0) { //子进程 int cnt = 10; while (cnt) { printf("我是子进程: %d, 父进程: %d,cnt: %d\n", getpid(), getppid(), cnt--); sleep(1); } exit(0);//进程终止 } //父进程 sleep(15); pid_t ret = wait(NULL); if (id > 0) { printf("wait success: %d\n", ret); } return 0; }上面程序的功能:我们期望,子进程返回0,即进入while循环后,10秒的时间内,子进程在运行着,然后子进程终止,此时,父进程中的sleep的时间也过了10秒,还有5秒,在这5秒的时间内,子进程就是一个僵尸进程(Z)。我们期望,通过父进程中的wait,可以回收子进程的资源,从而解决僵尸进程。看下面结果: 可以看到,有在一段时间内,子进程的状态为Z,即僵尸状态,然后变成了STAT。2.waitpid方法pid_ t waitpid(pid_t pid, int *status, int options);返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;参数:pid:Pid=-1,等待任一个子进程。与wait等效。Pid>0.等待其进程ID与pid相等的子进程。status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)options:WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。 ①如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。②如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。③如果不存在该子进程,则立即出错返回获取子进程status:wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图: 当子进程变成僵尸状态的时候,子进程的PCB内部就保存有子进程的退出码和退出信号,父进程通过status,将子进程的这些资源拿到手。阻塞与非阻塞阻塞:当父进程通过系统调用wait/waitpid去获取子进程的资源时,但子进程还没有退出,等待的这个状态,就叫做阻塞。非阻塞:子进程还没退出,父进程就不等了,直接返回下面的代码可以测试一下非阻塞#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t id = fork(); (id == 0) { //子进程 int cnt = 10; while (cnt) { printf("我是子进程: %d, 父进程: %d,cnt: %d\n", getpid(), getppid(), cnt--); sleep(1); } exit(0);//进程终止 } //父进程 //sleep(15); // pid_t ret = wait(NULL); int status = 0; while (1) { pid_t ret = waitpid(id, &status, WNOHANG); if (ret == 0) { //waitpid调用成功,子进程没有退出 //子进程没有退出,waitpid没有等待失败,仅仅只是检查状态 printf("wait done, but child is running......\n"); } else if (ret > 0) { //waitpid调用成功,子进程退出了 printf("wait success: %d, sig number: %d,child eixt code: %d\n", ret, (status & 0x7F), (status >> 8) & 0XFF); break; } else { //waitpid调用失败 printf("waitpid call failed\n"); break; } } return 0; }我们可以看到,父进程在进行轮询检测,直到子进程退出。 非阻塞的好处是不会占用父进程的资源,父进程在轮询的期间可以去做别的事。四、进程替换首先需要知道的是创建子进程的目的:a. 让子进程执行父进程代码的一部分:执行父进程在磁盘上对应的一部分代码。b、让进程执行一个全新的程序:让子进程加载磁盘上指定的程序,执行新程序的代码和数据——>这动作就叫做进程的替换接下来将以四步来对进程的替换进行学习:①先见见猪跑,看看什么是进程替换;②理解原理(是什么,为什么,怎么办);③对应的方法;④应用的场景4.1 先见见猪跑,看看什么是进程替换需要用到替换函数(execl)其实有六种以exec开头的函数,统称exec函数:#include <unistd.h>`int execl(const char *path, const char *arg, ...);int execlp(const char *file, const char *arg, ...);int execle(const char *path, const char *arg, ...,char *const envp[]);int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);函数解释:这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1所以exec函数只有出错的返回值而没有成功的返回值。因为成功的返回值没有必要,都已经替换了进程了,即使返回了,这个值也用不了。命名理解:l(list) : 表示参数采用列表。意思是将参数一个一个地传入exec*v(vector) : 参数用数组。意思是可以将我们需要传入的参数放在数组里面,然后统一传入。p(path) : 有p自动搜索环境变量PATHe(env) : 表示自己维护环境变量 温馨提示:int execl(const char *path, const char *arg, ...);第一个参数的意思是找到这个程序的路径,第二个参数的意思是如何执行这个程序,第三个参数  ...  是c语言中的可变参数列表,像scanf,printf等都有...)。替换函数的功能就是将指定的程序 (注意是程序,不是进程) 加载到内存当中,让指定的程序执行。请看下面代码:#include<stdio.h> #include<unistd.h> int main() { printf("process is running..\n"); execl("/usr/bin/ls", "ls", "--color=auto", "-a", "-l", NULL); printf("process running done...\n"); return 0; }通过替换函数execl,我们可以执行别人的代码程序,比如ls,-a,-l。 可以看到,在代码里面的第二个printf没有将我们需要打印的内容打印出来,因此我们需要了解清除进程替换的原理。4.2 进程程序替换原理 进程程序替换本质上就是将指定的程序的代码和数据,从磁盘上加载到物理内存的指定的位置上,并且把原来位置上的的数据和代码给覆盖掉,因此,在进程程序替换的时候,并没有创建新的进程。 所以我们回到上面的那个问题,为什么第二个printf没有执行?答案就是:因为第二个printf是在execl之后的,在执行了execl后,第二个printf被覆盖掉了,所以也就没办法执行了。我们再举一个例子,那就是再父子进程中,子进程进行程序替换:看下面代码:#include<stdio.h> #include<unistd.h> #include<assert.h> #include<stdlib.h> #include<sys/types.h> #include<sys/wait.h> int main() { printf("process is running..\n"); pid_t id = fork(); assert(id != -1); if (id == 0) { sleep(1); execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL); exit(1); } int status = 0; pid_t ret = waitpid(id, &status, 0); if (ret > 0) { printf("wait success: exit code:%d,sig: %d\n", (status >> 8) & 0xFF, status & 0x7F); } return 0; }结果如下:因为进程具有独立性,所以当子进程进行程序替换的时候,OS就会在物理内存中进行写时拷贝,页表的映射关系重新安排。由此,子进程的程序替换也不会影响到了父进程。替换自己写的程序:①C程序替换C程序:那么接下来,我们试着去写一段程序, 然后用另外一段代码程序来执行,也就是说,上面程序替换是替换系统命令的, 现在是替换自己写的代码程序。创建一个my_exec.c的C程序。#include<stdio.h> int main() { printf("这是一段C程序\n"); printf("这是一段C程序\n"); printf("这是一段C程序\n"); return 0; }然后在my_test.c的C程序总,使用execl函数即可:execl("./my_exec", "my_exec",NULL); ②C程序替换C++程序:没错,在替换函数中,我们可以在C程序的代码中去替换CPP的程序,因为是系统调用,系统就是老大,系统想替换谁就是谁,而且程序替换,就是叫程序替换,不叫语言替换,C++、Java。shell和python都是没问题的。这里不演示了,演示的例子无非就是将后缀改为cpp,并且使用C++的语法,操作过程几乎差不多。4.3 对应的方法前面我们已经将了第一个方法:int execl(const char *path, const char *arg, ...);那么接下来我们谈谈第二个方法:int execlp(const char *file, const char *arg, ...);execlp的使用方法,就是不需要带路径:execlp("ls", "ls", "--color=auto", "-a", "-l", NULL);这里面的两个"ls",并不是重复,因为第一个"ls"的意思是要执行的对象,第二个"ls"的意思是如何执行。第三个方法:int execv(const char *path, char *const argv[]);带v,需要传的是数组的形式,即将需要传入的参数放入一个数组中,然后传入数组即可。char* const avg[] = {"ls", "-a", "-l", "--color=auto", NULL}; execlv("/usr/bin/ls", avg);第四个方法:int execvp(const char *file, char *const argv[]);execvp可以看作是execv和execp的结合使用execlvp("ls", avg);第五个方法:int execle(const char *path, const char *arg, ...,char *const envp[]);第四个参数,我们传的是环境变量。在my_exec.c的程序中,加入环境变量:#include<stdio.h> #include<stdlib.h> int main() { //系统就有 printf("PATH:%s\n", getenv("PATH")); printf("PWD:%s\n", getenv("PWD")); //自定义环境 printf("MYENV:%s\n", getenv("MYENV")); printf("这是一段C程序\n"); printf("这是一段C程序\n"); printf("这是一段C程序\n"); return 0; }①自定义变量:然后拿到my_test.c中:char* const envp[] = {(char*)"MYENV=1122334455",NULL}; execle("./my_exec","my_exec",NULL,envp);结果发现,没有将系统自带的环境变量的内容输出。 ②系统自带的环境变量:extern char **environ; execle("./my_exec","my_exec",NULL,environ);结果发现,没有了自定义的环境变量 那么,我想把自定义的和系统自带的环境变量都输出:使用putenv函数,将自定义环境变量导入系统的环境变量表中。putenv((char*)"MYENV=44332211");//将指定环境变量导入到系统中,即environ指向的环境变量表 execle("./my_exec","my_exec",NULL,environ);可以看到,即有系统自带的,也有自定义的。 其实对于execle,我们可以与 int main(int argc,char *argv[],char *env[]){}结合起来谈谈。代码和数据加载到内存的操作,其实就是操作系统调用了exec*函数完成的,所以在Linux的系统中,exec*是加载器。exec*函数的功能就是将程序加载到内存嘛,这是谈的第一点。第二点就是,对于main函数而言,是先进行程序的加载,才会开始调用main函数,那么main函数的参数,就是由execle传参传过去的!第六个方法:真正的系统调用的接口:int execve(const char *filename, char *const argv[], char* const envp[]);这个方法是真正的系统调用的接口,上面五个,都是基于系统调用的接口封装起来的,是为了有更多的选择性。总结一下exec*家族的成员:4.4 应用场景 综合前面的知识,实现一个简单的shell。#include<stdio.h> #include<unistd.h> #include<assert.h> #include<string.h> #include<sys/types.h> #include<sys/wait.h> #include<stdlib.h> #define NUM 1024 #define OPT_NUM 64 char lineCommand[NUM]; char* myargv[OPT_NUM]; int mian() { while (1) { //输出提示符 printf("用户名@主机名 当前路径# "); fflush(stdout); //获取用户输入,输入的时候,回输入\n char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin); assert(s != NULL); (void)s; //清除最后一个\n lineCommand[strlen(lineCommand) - 1] = 0; //字符串切割 myargv[0] = strtok(lineCommand, " "); int i = 1; while (myargv[i++] = strtok(NULL, " ")); //条件编译 #ifdef DEBUG for (int i = 0; myargv[i]; i++) { printf("myargv[%d]: %s\n", i, myargv[i]); } #endif //指向命令 pid_t id = fork(); assert(if != -1); if (id == 0) { execvp(myargv[0], myargv); exit(1); } waidpid(id, NULL, 0); } } 最后,我们指向myshell的程序后,输入一些命令行指令,那么就可以通过execvp去将对应的程序加载到内存,就可以执行这些程序了! 本文结束~喜欢的话可以点波关注。
文章
Java  ·  Linux  ·  Shell  ·  调度  ·  C语言  ·  C++  ·  Python
2023-03-21
Python3,好看的外(shen)表(cai)千篇一律,炫彩的日志万里挑一。
1、引言小屌丝:鱼哥, 我今天被炫到了。小鱼:怎么了,你还能被旋到了?小屌丝对啊, 被炫到了,很不是滋味。小鱼:不应该啊, 你这酒量,不都是 “青岛不倒我不倒,雪花不飘我不飘,半斤不是酒,一斤扶墙走…”,小屌丝:打住,合着你说的是喝酒啊?小鱼:嗯?? 那你说的不是喝酒?小屌丝:我说的是被技能给炫到了小鱼:哦,那不足为奇~小屌丝:… 至少我也是新生代农民工…小鱼:哦,那你自己搞一个更炫的, 闪亮他们的眼…小屌丝:我要是能搞得出啦, 就不在这跟你唠嗑了。小鱼:那是啥嘞?小屌丝:就是调试的控制台,输出的日志是彩色的。小鱼:就这???小屌丝:对啊,就这。小鱼:唉,现在是不是午饭时间了。小屌丝:行, 整完了,咱俩去旋一个。小鱼:你看看, 这刚喝完,又的去旋,总这样,身体也吃不消啊。小屌丝: 好,整…小鱼:好嘞。 那咱十分钟后,老地方见。2、代码实战2.1 库介绍关于coloredlogs,这里,我直接引用官网的内容:The coloredlogs package enables colored terminal output for Python’s logging module. The ColoredFormatter class inherits from logging. Formatter and uses ANSI escape sequences to render your logging messages in color. It uses only standard colors so it should work on any UNIX terminal. It’s currently tested on Python 2.7, 3.5+ and PyPy (2 and 3).这里,我用汉语简单概括的唠叨一下(是时候展示我的英文水平了):coloredlogs 包为 Python 的日志记录模块启用彩色终端输出。 类继承自日志记录。格式化程序并使用 ANSI 转义序列以彩色呈现日志记录消息。 它仅使用 标准颜色,因此它应该适用于任何UNIX终端。 可以在python2.7 和3.5及更高版本使用。所以, 是不是觉得,很easy呢。如果掌握了此方法, 以后在调试日志的时候, 根据颜色值,来判断错误的等级,是不是更高大上呢。话不多说,直接安装,咱来实战。2.2 库安装涉及到第三方库,肯定就需要安装老规矩,pip 安装pip install coloredlogs然后就是等待着安装。其它安装方式,直接看这两篇:《Python3,选择Python自动安装第三方库,从此跟pip说拜拜!!》《Python3:我低调的只用一行代码,就导入Python所有库!》安装完成,就是这个样子:2.3 代码示例2.3.1 demo我先整一个简单的demo,来看看效果# -*- coding:utf-8 -*- # @Time : 2023-02-22 # @Author : Carl_DJ import coloredlogs, logging # 创建logger. logger = logging.getLogger(__name__) #设置等级为 DEBUG coloredlogs.install(level='DEBUG') coloredlogs.install(level='DEBUG', logger=logger) # 输出 logger.debug("this is a debugging message") logger.info("this is an informational message") logger.warning("this is a warning message") logger.error("this is an error message") logger.critical("this is a critical message") 结果展示2.3.2 实战有了上面的简单demo, 我们给封装一下,便于后期在框架中直接被调用。代码示例# -*- coding:utf-8 -*- # @Time : 2023-02-22 # @Author : Carl_DJ ''' 实现功能: 输出的日志,为彩色 ''' import logging import coloredlogs import sys #logger 配置 logging.basicConfig() logger = logging.getLogger(name='logger') #logger安装到coloredlogs coloredlogs.install(logger=logger) #保证coloredlogs 不会将日志事件传递给跟logger,防止重复记录每个事件 logger.propagate = False #输入logger字体颜色 配置 coloredFormatter = coloredlogs.ColoredFormatter( fmt='[%(name)s] %(asctime)s %(funcName)s %(lineno)-3d %(message)s', #定义logger不同级别,输出的字体颜色 level_styles=dict( debug = dict(color = 'yellow'), info = dict(color = 'green'), warning = dict(color = 'blue',bright = True), error = dict(color = 'red',bold = True,bright = True), critical = dict(color = 'black',bold=True,background = 'cyan'), ), field_styles=dict( name = dict(color = 'white'), asctime = dict(color = 'white'), funcName = dict(color = 'white'), lineno = dict(color = 'white'), ) ) #配置控制台输出,这是日志常规写法, 不做过多讲解 ch = logging.StreamHandler(stream=sys.stdout) ch.setFormatter(fmt = coloredFormatter) logger.addHandler(hdlr=ch) #设置输出日志的等级 logger.setLevel(level=logging.INFO) #设置输出文案内容 logger.debug(msg = "logger.debug test!") logger.info(msg = "logger.info test!") logger.warning(msg = "logger.warning test!") logger.error(msg = "logger.error test!") logger.critical(msg = "logger.critical test!") 结果展示3、总结看到这里, 今天的分享,就差不多该结束了。关于coloredlogs的日常使用,掌握这篇就差不多了。其实,在平时的工作中,并没有太多的时间去搞这么花里胡哨的,但是,既然有这个库,那我们就多掌握一些。好了,也不啰嗦了, 再唠叨,小屌丝也该着急了。我是奕然:CSDN 博客专家;阿里云 专家博主;51CTO 博客专家;51认证讲师;金牌面试官&面试培训师;关注我,带你学习更多更有趣的Python知识。
文章
Python
2023-03-06
Python3,为了无损压缩gif动图,我不得不写了一个压缩神器,真香。
1、引言小屌丝:鱼哥, 求助~ 求助~ 求助~小鱼:你这是告诉我,重要的事情 说三遍吗?小屌丝:你可以这么理解。小鱼:好吧… 什么事情,这么慌慌张张。小屌丝:我的动图太大了, 无法上传。小鱼:呦呵… 你也开始写博文了?小屌丝:向鱼哥看齐。小鱼:没毛病。话说回来, 单张5MB的图片,这都满足不了你?小屌丝:别提了, 为了能上传这个图片,我可没少想办法。小鱼:你直接在某网站,进行压缩就可以啊。小屌丝:不行,需要注册会员,而且,还需要收费的。小鱼:你还差钱?小屌丝:哎呦,你不都说白嫖最香吗?小鱼:… 我… 我可 没说。小屌丝:你说了… 明明就说了。小鱼:你还想压缩gif动图吗?小屌丝:… 想啊小鱼:那我说过这句话吗?小屌丝::… 没… 没…有…吧~小鱼:这还差不多, 那你把图片给我, 我来整。小屌丝:可行。我们来看下,小屌丝提供的原图片的大小,10MB,想象一下,如果阿里云社区不限制图片上传的大小,那是不是… 。主要是担心运维同学,天天在公司加班,身心疲惫啊~ ~。所以,为了照顾运维同学的身体,我们就来压缩自己的图片大小吧。2、代码实战2.1 模块介绍因为是要生成gif动图,所以,必不可少的模块:ImageIo。如果你不了解ImageIo,那你可以借着这个机会来了解它;如果你了解ImageIo,那你可以借着这个机会来加深印象;小屌丝:这合着, 不管会不会,都要重新学习一次呗。小鱼:知识就是在复习与学习之间,才能完全掌握的。小屌丝:服。。1、ImageIo定义:引言官网的解释:ImageIo提供了一系列示例图像,可以使用类似于URI的方式来使用。换句话说,即:ImageIo是一个处理图像的接口。2、ImageIo组成部分ImageIo分为三部分,如下:Plugin:面向后端的适配器用于响应来自 它可以将来自 iio.core 的请求转换为 满足请求的后端指令(例如,读/写/迭代);Backend Library:可以读取和/或写入的库 图像或类似图像的对象(/视频);它可以据需要进行安装;ImageResource:包含图像数据的数据 blob;通常是由ImageIo读取驱动器。看到这, 是不是对ImageIo有了初步的了解。接下来, 我们就去体会ImageIo的强大功能喽。2.2 安装由于ImageIo是第三方库,所以,在使用前,要安装。老规矩,我们直接pip方式安装,即:pip install imageio然后就是等待着安装。其它安装方式,直接看这两篇:《Python3,选择Python自动安装第三方库,从此跟pip说拜拜!!》《Python3:我低调的只用一行代码,就导入Python所有库!》2.3 代码示例代码示例# -*- coding:utf-8 -*- # @Time : 2023-02-15 # @Author : Carl_DJ ''' 实现功能 使用ImageIo 和PIL库,对gif动图进行压缩处理 ''' import imageio from PIL import Image, ImageSequence # 设置压缩尺寸,这里设置压缩尺寸为500 rp = 500 img_list = [] # 读取原gif动图 img = Image.open("./data/param-demo.gif") # 对原动图进行压缩,并存入img_list for i in ImageSequence.Iterator(img): i = i.convert('RGB') if max(i.size[0], i.size[1]) > rp: i.thumbnail((rp, rp)) img_list.append(i) # 计算帧的频率 durt = (img.info)['duration'] / 1000 # 读取img_list合成新的gif imageio.mimsave('param-out.gif', img_list, duration=durt ) 运行结果:你看, 这压缩完成后, 就是2.7MB,这缩小的不是一点点。接着,我展示下压缩后的动图,看看是不是无损展示。小鱼:你看, 是不是非常完美。小屌丝:唉, 鱼哥,你这动图,怎么似曾相似啊小鱼:这说明,你又仔细的看我的博文了,这就《Python3,2分钟掌握Doscoart库,你也能成为艺术大师。》 这篇啊。小屌丝:我去~~~ 我说的嘛。3、总结看到这里, 今天的分享就差不多结束了。同样,回顾下今天的内容,其实很简单的。介绍了什么是ImageIo,已经ImageIo的组成部分,和代码实战。所以, 是不是觉得, 跟着小鱼学习新知识,根本就没有那么费劲呢。我是奕然:CSDN 博客专家;阿里云 专家博主;51CTO 博客专家;51认证讲师;金牌面试官&面试培训师;关注我,带你学习更多更有趣的Python知识。
文章
运维  ·  Python
2023-02-28
Python3,我只用了10行代码,就写了一个词云生成器。
1、引言小鱼:小屌丝,你在干啥呢?小屌丝:鱼哥,你看, 我的PPT写的 高大尚不。小鱼:这有啥高大尚的啊,小屌丝:你仔细看, 往下翻一页小鱼:额。你这那是PPT,就是浴皇大帝、昂科旗等车系的测评吗。小屌丝:别管内容了, 鱼哥,你就说,这个样式怎么样, 帅不帅气吧。小鱼:嗯,样式嘛, 还可以的。小屌丝:鱼哥,你这么淡定, 你的意思, 你也会?小鱼:额… 我可没说, 休想套路我。小屌丝:鱼哥,我这就要说到公道话了, 独乐乐不如众乐乐。小鱼:有的时候,需要独乐乐。小屌丝:鱼哥,别整没用的, 你就说分不分享吧?小鱼:额… 这个… 你说啥?小屌丝:我说,你把这个代码分享出来呗。小鱼:你说分享什么啊?小屌丝:分享词云生成器的代码小鱼:分享什么代码啊?小屌丝:去泡温泉…小鱼:好嘞,早说不就完事了嘛。2、代码实战2.1 库介绍说到词云的制作, 不得不提的第三方库, stylecloud:简洁易用的词云库当然仅仅有stylecloud 还是不够的, 还需要一个库,即 jieba:中文分词库所以, 今天我们就用stylecloud 和jieba来完成本次的代码实战。接下来, 我们先了解 这两个库。2.1.1 jiebajieba: 中文分词库1、运行原理初始化:加载词典文件,获取每个词语和它出现的词数切分短语:利用正则,将文本切分为一个个语句,之后对语句进行分词构建DAG:通过字符串匹配,构建所有可能的分词情况的有向无环图,也就是DAG构建节点最大路径概率,以及结束位置:计算每个汉字节点到语句结尾的所有路径中的最大概率,并记下最大概率时在DAG中对应的该汉字成词的结束位置。构建切分组合:根据节点路径,得到词语切分的结果,也就是分词结果。HMM新词处理:对于新词,也就是jieba词典中没有的词语,我们通过统计方法来处理,jieba中采用了HMM(隐马尔科夫模型)来处理。返回分词结果:通过yield将上面步骤中切分好的词语逐个返回。yield相对于list,可以节约存储空间。2、主要模式精确模式:把文本精确的切分开,不存在冗余单词全模式:把文本中所有可能的词语都扫描出来,有冗余搜索引擎模式:在精确模式基础上,对长词再次切分3、主要功能jieba.cut 方法接受四个输入参数:.需要分词的字符串;.cut_all 参数用来控制是否采用全模式;.HMM 参数用来控制是否使用 HMM 模型;.use_paddle 参数用来控制是否使用paddle模式下的分词模式,..paddle模式采用延迟加载方式,通过enable_paddle接口安装paddlepaddle-tiny,并且import相关代码;jieba.cut_for_search 方法接受两个参数:需要分词的字符串;是否使用 HMM 模型。该方法适合用于搜索引擎构建倒排索引的分词,粒度比较细。jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode),或者用 jieba.lcut 以及 jieba.lcut_for_search 直接返回 listjieba.Tokenizer(dictionary=DEFAULT_DICT) 新建自定义分词器,可用于同时使用不同词典。jieba.dt 为默认分词器,所有全局分词相关函数都是该分词器的映射。2.1.2 stylecloud关于词云生成工具, 常用的无非这3种:pyecharts简单易用;上手快;不够美观;wordcloud使用频次最高;使用最广泛;stylecloud简单易用;最接近完美;接下来,我们就使用 sytlecloud第三方库,看看它完美到什么程度。2.2 库安装涉及到第三方库,肯定就需要安装老规矩,pip 安装pip install jieba pip install stylecloud然后就是等待着安装。其它安装方式,直接看这两篇:《Python3,选择Python自动安装第三方库,从此跟pip说拜拜!!》《Python3:我低调的只用一行代码,就导入Python所有库!》2.2 代码实战代码示例# -*- coding:utf-8 -*- # @Time : 2023-02-02 # @Author : Carl_DJ ''' 实现功能: 词云生成器 ''' import json import stylecloud import codecs import jieba from collections import Counter #过滤掉高频出现的词汇 passwords = set() #读取词汇文档 content = [line.strip() for line in open('./data/passwords.txt', 'r',encoding='utf8').readlines()] passwords.update(content) #获取文档词汇, 并截取长度为3个 def make_words(txt): make_list = jieba.cut(txt) c = Counter() words_list = [] #获取词汇文本 for x in make_list: #长度为3,超过截取 if len(x) == 3 and x !='\r\n': c[x] += 1 words_list.append(x) for k,v in c.most_common(50): if k not in passwords: # print(f'{k,v}') #组合词云内容 return " ".join(words_list) #读取中大型suv测评.txt内容 with codecs.open('./data/中大型suv测评.txt','r','utf8') as f: #格式需要utf8 否则会报错 txt = f.read() # words_txt = make_words(txt) #设置词云展示的样式,字体,生成文件名称等, stylecloud.gen_stylecloud(text=words_txt,custom_stopwords=content, background_color='#1A1A1A', colors=['#dd4444', '#fec42c', '#fac858'], max_font_size=100, output_name='xt6测评.jpg', font_path="C:/Windows/Fonts/FZSTK.TTF" )结果展示注:这里需要准备两个文件password.txt : 过滤文本中出现太多次数的词汇;suv测评.txt:词云的主要显示的文本内容;文本内容示例:这里强调一点:文本的内容,你可以一行写很多字,但是,为了词云展示的内容更丰富, 列数,一定要多。当然,文档内容, 也可以是下载的小说,或者你自己写的任何内容。3、总结看到这里, 今天的分享就要结束了。回头看一下,其实词云生成器,并不难。主要就是对 jieba、stylecloud 这两个库的使用。这里我仅仅列举了 stylecloud 第三方库,当然,如果你有兴趣,也可以使用 pyecharts、 wordcloud ,看看生成的词云如何。也就当是你自己的练手了。好了,就唠叨这里了。我是奕然:CSDN 博客专家;阿里云 专家博主;51CTO博客专家;51认证讲师;企业金牌面试官;关注小鱼,带你学习更多更有趣的python知识。当然,如果你想晋升自己的技能,;如果你想提升自己的面试成功率;如果你现在处在职业迷茫期,想重新规划职业生涯;都可以找小鱼聊聊的。
文章
自然语言处理  ·  搜索推荐  ·  索引  ·  Python
2023-02-08
Python中的模块系统
模块随着程序功能越来越复杂,代码量也急剧增加。可以通过将逻辑相近的代码分割到不同的文件中,减少单个文件中代码的行数,提高可维护性。在Python中一个py文件就是一个模块,一个大型的代码项目,一般是由多个文件(模块)组成。一个模块可以被另一个模块导入使用。包在Python中,包含__init__.py文件的目录被称为包,包被用来声明模块之间的查找关系。比如下面的app就是一个包,test也是一个包。app可以被看做是test的子包。test ├── app │ ├── config.py │ ├── __init__.py │ └── lib.py ├── __init__.py └── main.py__init__.py文件可以为空。__init__.py文件中一般用来导出所在目录下的其他模块。当目录中不存在__init__.py文件时,该目录也可以被称为一个包,只是习惯上喜欢在每个目录中放置一个__init__.py文件。至于两者区别,含有__init__.py的目录,可以讲__init__.py看做一扇可以通往该目录下所有模块的大门,导入方法在Python中导入一个包或模块时,有两种主要的方法,一种是使用import关键字进行导入,另一种是使用官方库importlib来进行导入,后者常被用于一些需要自定义导入的,或者是自动导入某些模块的场景下。导入一个模块app ├── config.py ├── __init__.py └── lib.py当lib.py导入config.py时,可以直接通过import进行导入# lib.py import config导入一个包test ├── app │ ├── config.py │ ├── __init__.py │ └── lib.py ├── __init__.py └── main.py当main.py中导入app包时,可以直接使用import进行导入# main.py import app与导入模块不同,导入一个包时,包目录下的__init__.py文件会被自动执行。导入包中的某一个具体模块时import app.lib那么导入一个包可以认为是通过下面这种方式进行导入的,只是在Python中可以将这种方式简写为imoprt app。import app.__init__ as appas在导入时,Python会将当前导入的包或模块自动添加到全局变量空间中,有时可能会出现下面这种情况import lib1.app import lib2.app在不同的包中,存在名字相同的模块,这是后者就会将前者覆盖,通过as可以将导入的变量重命名。import lib1.app as app1 import lib2.app as app2使用相对路径进行导入当代码项目过大时,文件关系复杂或者模块的嵌套层级过深时,可以通过相对导入的方式进行导入某一个模块test ├── app │ ├── config.py │ ├── __init__.py │ ├── lib.py │ └── __pycache__ │ └── __init__.cpython-39.pyc ├── auth │ ├── config.py │ └── __init__.py ├── __init__.py └── main.py当app/lib.py文件需要使用auth/config.py模块的功能时,可以这样进行导入# app/lib.py from ..auth import config as auth_config这种导入方法必须是使用from进行导入,包前面一个点表示同一目录,两个点表示上一级目录,三个点表示上一级目录的上一级目录,以此类推。在使用这种方法时,对于新手经常会出现下面这种报错ImportError: attempted relative import with no known parent package出现这种原因是因为,在使用相对导入时,必须存在一个最上层位置,在Python中,使用python main.py运行main.py文件,则main.py所在的目录即为最上层位置,其他的则为下层位置。当python test/app/lib.py时,最上层位置变成了test/app,所以此时..auth自然无法找到。解决方法是,在使用相对路径导入时,要明确最后运行文件的所在目录,来确定最上层位置,如果你确定不了,那就最好不要使用。循环导入当一个项目的模块数量很多时,可能模块之间的依赖关系错综复杂,会出现A导入B,而B也导入了A,从而构成了循环导入的情况。# A.py import B print(B.b) a = 1 # B.py import A print(A.a) b = 1python A.py AttributeError: partially initialized module 'B' has no attribute 'b' (most likely due to a circular import)出现循环引用问题,绝大多数情况都是因为人为设计失误造成的,你可以通过引入新模块,减少A和B相互依赖的变量。importlib在使用import关键字导入模块时,会调用内置的__import__方法,通过importlib可以绕过__import__方法,实现自己的导入模块的逻辑,比如在导入前做一些初始化工作,或者是进行自动导入的逻辑。test ├── app │ ├── config.py │ ├── __init__.py │ ├── lib.py │ └── __pycache__ │ └── __init__.cpython-39.pyc ├── auth │ ├── config.py │ └── __init__.py ├── __init__.py └── main.pyfrom importlib import import_module app_config = import_module('app.config')需要注意的是,在使用这种方式在导入一个包下面的模块时,__init__.py也会自动被调用。一些常见的import写法from app import config as app_config import app.config as app_config # import * 表示导入所有的模块 from app import * as app_allPython是如何进行查找路径的在Python中,import通过sys.path下面的路径进行查找要导入的包或模块。Python首先会先从当前目录中查找模块,找不到会继续从sys.path下面进行查找,最后找不到会抛出错误。可以通过修改sys.path来修改查找的顺序,比如下面这样test ├── app │ ├── config.py │ ├── __init__.py │ ├── lib.py │ └── __pycache__ │ └── __init__.cpython-39.pyc ├── auth │ ├── config.py │ └── __init__.py ├── __init__.py └── main.py# test/app/config.py apple = 'apple' # test/main.py import sys sys.path.append('./app') import config print(config.apple) # apple资料引用https://docs.python.org/zh-cn/3/reference/import.html#https://docs.python.org/zh-cn/3/library/importlib.html#https://docs.python.org/zh-cn/3/tutorial/modules.html#
文章
Python
2023-02-09
Python3,自从我掌握了Doscoart库, 我对艺术的成就越来越高。
1、引言小屌丝:鱼哥,最近在忙啥?小鱼:咱俩陌生了?小屌丝:何出此言?小鱼:你说的话又嘛意思呢?小屌丝:我的意思, 最近看你这整理各种资料,貌似很忙的样子?小鱼:我平时不也这么忙嘛小屌丝:鱼哥, 还能正常唠嗑嘛?小鱼:我又没说不能唠嗑。小屌丝:鱼哥,行… 非常行…小鱼:男人,怎么能不行!小屌丝:…小鱼:~ ~小屌丝:discoart模块知道吗?小鱼:貌似, 大概,可能,或许,知道。小屌丝:太好了, 那能不能给我讲一讲呢?小鱼:然后呢?小屌丝:老地方~小鱼:又是老地方,整的我都不好意思了。小屌丝:这都是小事,我主要就想让你多放松放松…小鱼:停,停, 打住~ 别说多了, 我们来聊discoart。小屌丝:别着急啊,小鱼:能不着急吗,你看,这都几点了, 再晚一会,就…小屌丝:昂…2、 代码实战2.1 模块介绍说起 discoart 可能大部分都不太了解。但是,说到艺术库,可能你就有些印象了。这里,我也引用官网对discoart的解析,让你对它有个初步的了解,如下:DiscoArt is an elegant way of creating compelling Disco Diffusion[*] artworks for generative artists, AI enthusiasts and hard-core developers. DiscoArt has a modern & professional API with a beautiful codebase, ensuring high usability and maintainability. It introduces handy features such as result recovery and persistence, gRPC/HTTP serving w/o TLS, post-analysis, easing the integration to larger cross-modal or multi-modal applications. 这里,我也简答的用汉语描述一下,即:DiscoArt是一种优雅的方式,可以为生成艺术家,AI爱好者和铁杆开发人员创建引人注目的Disco Diffusion艺术品。 DiscoArt拥有现代和专业的API,具有漂亮的代码库,确保了高可用性和可维护性。它引入了方便的功能,例如结果恢复和持久性,没有TLS的gRPC / HTTP服务,后期分析,简化与更大的跨模态或多模态应用程序的集成。简答一句话概括:DiscoArt就是为了艺术而生的。2.2 模块安装涉及到第三方库,肯定就需要安装老规矩,pip 安装pip install discoart然后就是等待着安装。其它安装方式,直接看这两篇:《Python3,选择Python自动安装第三方库,从此跟pip说拜拜!!》《Python3:我低调的只用一行代码,就导入Python所有库!》安装的样子,如下:这里提示一下:按照官网的要求, discoart的使用,必须依托于:Python 3.7+ 和 CUDA 的 PyTorch 。2.3 代码示例2.3.1 创建默认图片这里直接使用discoart的 create方法即可代码示例:# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 使用默认参数创建图片 ''' from discoart import create ca = create()效果展示2.3.2 设置参数创建图片代码示例# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 设置参数创建图片 ''' from discoart import create #设置参数 ca = create( text_prompts='A painting of sea cliffs in a tumultuous storm, Trending on ArtStation.', init_image='https://d2vyhzeko0lke5.cloudfront.net/xxxx7e77b72f0.png', skip_steps=100, )效果展示2.3.3 查看设置参数如果你忘记参数,也没关系,直接用cheatsheet 查询即可代码展示# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 查看设置参数 ''' from discoart import cheatsheet #设置参数 sha = cheatsheet()2.3.4 查看配置如果要查看文档配置, 可以使用show_config:代码示例# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 查看文档配置 ''' from discorat import show_config # 展示第一个项目运行的配置 show_config(da) # 参考第四个项目的运行配置 show_config(da[3]) #查看discoartID show_config('discoart-xxxxfbf288')2.3.5 保存配置如果要保存文档配置, 可以使用save_config:代码展示# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 保存文档配置 ''' from discoart import save_config #保存第一次运行的配置 save_config(da, 'my.yml') #保存第四次运行的配置 save_config(da[3], 'my.yml')2.3.6 加载配置有了查看和保存,当然也可以直接加载配置文件了, 这里,使用load_config即可代码示例# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 加载文档配置 ''' from discoart import create, load_config #加载配置文件 config = load_config('my.yml') create(**config)2.3.7 导出配置文件为了便于后期的管理使用,同样可以直接导出配置文件为SVG映像,使用 save_config_svg方法:代码示例# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 导出配置文件为SVG映像 ''' from discoart.config import save_config_svg #直接保存为svg映像 save_config_svg(da)这里也展示一下, 保存的svg映像2.3.8 生成Python代码更神奇的功能,就是可以直接从配置中生成可运行的Python代码,使用export_python方法:代码示例# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 生成可运行的Python代码 ''' from discoart.config import export_python export_python(da)2.3.9 调用文档如果你觉得自己配置太繁琐, 那可以直接使用DocumentArray作为初始状态运行。代码示例# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 调用DocumentArray作为初始状态,运行 ''' from discoart import create from docarray import DocumentArray da = DocumentArray.pull('discoart-32xxx') create( init_document=da[0], cut_ic_pow=0.5, tv_scale=600, cut_overview='[12]*1000', cut_innercut='[12]*1000', use_secondary_model=False, )当然, 如果你只想从已有的 DocArray ID 初始化, 那也不是不可能,# -*- coding:utf-8 -*- # @Time : 2023-02-12 # @Author : Carl_DJ ''' 实现功能: 从已有的 DocArray ID 初始化 ''' from discoart import create create(init_document='discoart-320xxxx')3、总结看到这里, Doscart库的介绍就完成了。按照流程, 我们来回顾一下今天都分享了啥内容:创建默认图片;设置参数创建图片;查看设置参数;查看文档配置;报错文档配置;加载文档配置;导出配置文件生成Python代码;调用文档;你看, Doscoart也没有想想的那么难嘛, 常用的功能,也就差不多这么多。所以, 只要我们把一个库从头到尾的捋一遍, 其实是很容易掌握的。最后,唠叨一句:我是奕然:CSDN 博客专家;阿里云 专家博主;51CTO 博客专家;51认证讲师;金牌面试官&面试培训师;关注我,带你学习更多更有趣的Python知识。
文章
并行计算  ·  PyTorch  ·  算法框架/工具  ·  Python
2023-02-20
EasyNLP集成K-Global Pointer算法,支持中文信息抽取
作者:周纪咏、汪诚愚、严俊冰、黄俊导读信息抽取的三大任务是命名实体识别、关系抽取、事件抽取。命名实体识别是指识别文本中具有特定意义的实体,包括人名、地名、机构名、专有名词等;关系抽取是指识别文本中实体之间的关系;事件抽取是指识别文本中的事件信息并以结构化的形式呈现出来。信息抽取技术被广泛应用于知识图谱的构建、机器阅读理解、智能问答和信息检索系统中。信息抽取的三大任务不是相互独立的关系,而是相互依存、彼此依赖的关系。命名实体识别是关系抽取、事件抽取的基础,关系抽取是事件抽取的基础。同时,关系抽取、事件抽取对命名实体识别任务有帮助,事件抽取对关系抽取任务有帮助。但目前关于仅使用一个模型完成中文信息抽取三大任务的研究相对较少,因此,我们提出 K-Global Pointer 算法并集成进 EasyNLP 算法框架中,使用户可以使用自定义数据集训练中文信息抽取模型并使用。EasyNLP(https://github.com/alibaba/EasyNLP)是阿⾥云机器学习 PAI 团队基于 PyTorch 开发的简单易⽤且功能丰富的中⽂NLP 算法框架,⽀持常⽤的中⽂预训练模型和⼤模型落地技术,并且提供了从训练到部署的⼀站式 NLP 开发体验。EasyNLP 提供了简洁的接⼝供⽤户开发 NLP 模型,包括 NLP 应⽤AppZoo 和预训练 ModelZoo,同时提供技术帮助⽤户⾼效的落地超⼤预训练模型到业务。由于跨模态理解需求的不断增加,EasyNLP 也⽀持各种跨模态模型,特别是中⽂领域的跨模态模型,推向开源社区,希望能够服务更多的 NLP 和多模态算法开发者和研究者,也希望和社区⼀起推动 NLP/多模态技术的发展和模型落地。本⽂简要介绍 K-Global Pointer 的技术解读,以及如何在 EasyNLP 框架中使⽤K-Global Pointer 模型。K-Global Pointer 模型详解Global Pointer 模型是由苏剑林提出的解决命名实体识别任务的模型,n∗n 的矩阵 A(n为序列长度),A[i,j]代表的是序列i到序列j组成的连续子串为对应实体类型的概率,通过设计门槛值B即可将文本中具有特定意义的实体识别出来。K-Global Pointer 模型是在 Global Pointer 模型的基础之上改进的。首先我们将仅支持命名实体识别的模型拓展成支持中文信息抽取三大任务的模型。然后,我们使用了 MacBERT 预训练语言模型来将文本序列转换成向量序列。最后我们针对不同的任务设计了一套 prompt 模板,其能帮助预训练语言模型“回忆”起自己在预训练时“学习”到的内容。接下来,我们将根据中文信息抽取三大任务分别进行阐述。针对命名实体识别任务,我们有文本w1,w2,w3,...,wn以及需要提取的实体类型 entity_type,对应的 prompt 为“找到文章中所有【entity_type】类型的实体?”,对应的输入模型的文本H为“找到文章中所有【entity_type】类型的实体?文章:【w1,w2,w3,...,wn】”,模型经过相应的处理即可输出文本中实体类型为 entity_type 的实体。针对关系抽取任务,我们有文本w1,w2,w3,...,wn以及需要提取的关系类型 relation_type(subject_type-predicate-object_type),分为两步。第一步,对应的 prompt 为“找到文章中所有【subject_type】类型的实体?”,对应的输入模型的文本H为“找到文章中所有【subject_type】类型的实体?文章:【w1,w2,w3,...,wn】”,模型经过相应的处理即可输出文本中实体类型为 subject_type 的实体e1。第二步,对应的 prompt 为“找到文章中所有【e1】的【predicate】?”,对应的输入模型的文本H为“找到文章中所有【e1】的【predicate】?文章:【w1,w2,w3,...,wn】”,模型经过相应的处理即可输出实体e2。即可构成关系三元组(e1、predicate、e2)。针对事件抽取任务,我们有文本w1,w2,w3,...,wn以及需要提取的事件类型class,每个class包含 event_type 以及 role_list(r_{1},r_{2},...),分为两步。第一步,对应的 prompt 为“找到文章中所有【event_type】类型的实体?”,对应的输入模型的文本H为“找到文章中所有【event_type】类型的实体?文章:【w1,w2,w3,...,wn】”,模型经过相应的处理即可输出的实体e。第二步,针对 role_list 中不同的rx,对应的 prompt 为“找到文章中所有【e】的【rx】?”,对应的输入模型的文本H为“找到文章中所有【e】的【rx】?文章:【w1,w2,w3,...,wn】”,模型经过相应的处理即可输出实体ex。即可构成事件{event_type:e,role_list:{r_{1}:e_{1},r_{2}:e_{2},...}}。K-Global Pointer 模型的实现与效果在 EasyNLP 框架中,我们在模型层构建了 K-Global Pointer 模型的 Backbone,其核⼼代码如下所示:self.config = AutoConfig.from_pretrained(pretrained_model_name_or_path)self.backbone = AutoModel.from_pretrained(pretrained_model_name_or_path)self.dense_1 = nn.Linear(self.hidden_size, self.inner_dim * 2)self.dense_2 = nn.Linear(self.hidden_size, self.ent_type_size * 2) context_outputs = self.backbone(input_ids, attention_mask, token_type_ids)outputs = self.dense_1(context_outputs.last_hidden_state)qw, kw = outputs[..., ::2], outputs[..., 1::2] pos = SinusoidalPositionEmbedding(self.inner_dim, 'zero')(outputs)cos_pos = pos[..., 1::2].repeat_interleave(2, dim=-1)sin_pos = pos[..., ::2].repeat_interleave(2, dim=-1)qw2 = torch.stack([-qw[..., 1::2], qw[..., ::2]], 3)qw2 = torch.reshape(qw2, qw.shape)qw = qw * cos_pos + qw2 * sin_poskw2 = torch.stack([-kw[..., 1::2], kw[..., ::2]], 3)kw2 = torch.reshape(kw2, kw.shape)kw = kw * cos_pos + kw2 * sin_pos logits = torch.einsum('bmd,bnd->bmn', qw, kw) / self.inner_dim ** 0.5bias = torch.einsum('bnh->bhn', self.dense_2(last_hidden_state)) / 2logits = logits[:, None] + bias[:, ::2, None] + bias[:, 1::2, :, None] mask = torch.triu(attention_mask.unsqueeze(2) * attention_mask.unsqueeze(1))y_pred = logits - (1-mask.unsqueeze(1))*1e12y_true = label_ids.view(input_ids.shape[0] * self.ent_type_size, -1)y_pred = y_pred.view(input_ids.shape[0] * self.ent_type_size, -1)loss = multilabel_categorical_crossentropy(y_pred, y_true)为了验证 EasyNLP 框架中 K-Global Pointer 模型的有效性,我们使用 DuEE1.0、DuIE2.0、CMeEE-V2、CLUENER2020、CMeIE、MSRA、People's_Daily 7 个数据集联合进行训练,并在各个数据集上分别进行验证。其中 CMeEE-V2、CLUENER2020、MSRA、People's_Daily 数据集适用于命名实体识别任务,DuIE2.0、CMeIE 数据集适用于关系抽取任务,DuEE1.0 数据集适用于事件抽取任务。结果如下所示:数据集DuEE1.0DuIE2.0CMeEE-V2CLUENER2020CMeIEMSRAPeople's_Daily参数设置B=0.60.86570.87250.82660.8890.81550.98560.9933可以通过上述结果,验证 EasyNLP 框架中 K-Global Pointer 算法实现的正确性、有效性。K-Global Pointer 模型使用教程以下我们简要介绍如何在 EasyNLP 框架使⽤K-Global Pointer 模型。分为三种情况,分别是①用户使用数据训练模型②用户验证训练好的模型③用户使用训练好的模型完成中文信息抽取任务。我们提供了联合 DuEE1.0、DuIE2.0、CMeEE-V2、CLUENER2020、CMeIE、MSRA、People's_Daily 7 个数据集的数据,可以通过 sh run_train_eval_predict_user_defined_local.sh 来下载获取 train.tsv、dev.tsv、predict_input_EE.tsv、predict_input_NER.tsv 文件,其中 train.tsv 文件可用于训练、dev.tsv 文件可用于验证、predict_input_EE.tsv、predict_input_NER.tsv 文件可用于测试。用户也可以使用自定义数据。⽤户可以直接参考GitHub(https://github.com/alibaba/EasyNLP)上的说明安装 EasyNLP 算法框架。然后 cd EasyNLP/examples/information_extraction。①用户使用数据训练模型数据准备训练模型需要使用训练数据和验证数据。用户可以使用我们提供的数据,也可以使用自定义数据。数据表示为 train.tsv 文件以及 dev.tsv 文件,这两个⽂件都包含以制表符\t 分隔的五列,第一列是标签,第二列是上文 K-Global Pointer 模型详解中提到的H,第三列是答案的开始,第四列是答案的的结束,第五列是答案。样例如下:People's_Daily-train-0 ['找到文章中所有【LOC】类型的实体?文章:【海钓比赛地点在厦门与金门之间的海域。】'] [29, 32] [31, 34] 厦门|金门DuIE2.0-train-0 ['找到文章中所有【图书作品】类型的实体?文章:【《邪少兵王》是冰火未央写的网络小说连载于旗峰天下】'] [24] [28] 邪少兵王DuIE2.0-train-1 ['找到文章中【邪少兵王】的【作者】?文章:【《邪少兵王》是冰火未央写的网络小说连载于旗峰天下】'] [28] [32] 冰火未央DuEE1.0-train-25900 ['找到文章中所有【竞赛行为-夺冠】类型的实体?文章:【盖斯利在英国大奖赛首场练习赛中夺冠】'] [41] [43] 夺冠DuEE1.0-train-25901 ['找到文章中【夺冠】的【冠军】?文章:【盖斯利在英国大奖赛首场练习赛中夺冠】'] [19] [22] 盖斯利DuEE1.0-train-25902 ['找到文章中【夺冠】的【夺冠赛事】?文章:【盖斯利在英国大奖赛首场练习赛中夺冠】'] [25] [35] 英国大奖赛首场练习赛训练模型代码如下:python main.py \--mode train \--tables=train.tsv,dev.tsv \--input_schema=id:str:1,instruction:str:1,start:str:1,end:str:1,target:str:1 \--worker_gpu=4 \--app_name=information_extraction \--sequence_length=512 \--weight_decay=0.0 \--micro_batch_size=2 \--checkpoint_dir=./information_extraction_model/ \--data_threads=5 \--user_defined_parameters='pretrain_model_name_or_path=hfl/macbert-large-zh' \--save_checkpoint_steps=500 \--gradient_accumulation_steps=8 \--epoch_num=3 \--learning_rate=2e-05 \--random_seed=42训练好的模型保存在 information_extraction_model 文件夹中。②用户验证训练好的模型数据准备验证模型需要使用验证数据。用户可以使用我们提供的数据,也可以使用自定义数据。数据表示为 dev.tsv 文件,这个⽂件包含以制表符\t 分隔的五列,第一列是标签,第二列是上文 K-Global Pointer 模型详解中提到的H,第三列是答案的开始,第四列是答案的的结束,第五列是答案。样例如下:People's_Daily-train-0 ['找到文章中所有【LOC】类型的实体?文章:【海钓比赛地点在厦门与金门之间的海域。】'] [29, 32] [31, 34] 厦门|金门DuIE2.0-train-0 ['找到文章中所有【图书作品】类型的实体?文章:【《邪少兵王》是冰火未央写的网络小说连载于旗峰天下】'] [24] [28] 邪少兵王DuIE2.0-train-1 ['找到文章中【邪少兵王】的【作者】?文章:【《邪少兵王》是冰火未央写的网络小说连载于旗峰天下】'] [28] [32] 冰火未央DuEE1.0-train-25900 ['找到文章中所有【竞赛行为-夺冠】类型的实体?文章:【盖斯利在英国大奖赛首场练习赛中夺冠】'] [41] [43] 夺冠DuEE1.0-train-25901 ['找到文章中【夺冠】的【冠军】?文章:【盖斯利在英国大奖赛首场练习赛中夺冠】'] [19] [22] 盖斯利DuEE1.0-train-25902 ['找到文章中【夺冠】的【夺冠赛事】?文章:【盖斯利在英国大奖赛首场练习赛中夺冠】'] [25] [35] 英国大奖赛首场练习赛验证模型代码如下:python main.py \--mode evaluate \--tables=dev.tsv \--input_schema=id:str:1,instruction:str:1,start:str:1,end:str:1,target:str:1 \--worker_gpu=4 \--app_name=information_extraction \--sequence_length=512 \--weight_decay=0.0 \--micro_batch_size=2 \--checkpoint_dir=./information_extraction_model/ \--data_threads=5③用户使用训练好的模型完成中文信息抽取任务数据准备测试模型需要使用测试数据。用户可以使用我们提供的数据,也可以使用自定义数据。对于命名实体识别任务,数据表示为 predict_input_NER.tsv 文件,这个⽂件包含以制表符\t 分隔的三列,第一列是标签,第二列是实体类型,第三列是文本。我们支持对同一个文本识别多种实体类型,仅需要在第二列中将不同的实体类型用;分隔开。样例如下:1 LOC;ORG 海钓比赛地点在厦门与金门之间的海域。对于关系抽取任务,数据表示为 predict_input_RE.tsv 文件,这个⽂件包含以制表符\t 分隔的三列,第一列是标签,第二列是关系类型,第三列是文本。我们支持对同一个文本识别多种关系类型,仅需要在第二列中将不同的关系类型用;分隔开。对于一个关系类型 relation_type(subject_type-predicate-object_type)表示为 subject_type:predicate,样例如下:1 图书作品:作者 《邪少兵王》是冰火未央写的网络小说连载于旗峰天下对于事件抽取任务,数据表示为 predict_input_EE.tsv 文件,这个⽂件包含以制表符\t 分隔的三列,第一列是标签,第二列是事件类型 class,第三列是文本。我们支持对同一个文本识别多种事件类型,仅需要在第二列中将不同的事件类型用;分隔开。对于一个事件类型 class 包含 event_type 以及 role_list(r1,r2,……)表示为 event_type:r1,r2,……,样例如下:1 竞赛行为-夺冠:夺冠赛事,裁员人数 盖斯利在英国大奖赛首场练习赛中夺冠复制代码测试模型对于命名实体识别任务,代码如下:python main.py \--tables=predict_input_NER.tsv \--outputs=predict_output_NER.tsv \--input_schema=id:str:1,scheme:str:1,content:str:1 \--output_schema=id,content,q_and_a \--worker_gpu=4 \--app_name=information_extraction \--sequence_length=512 \--weight_decay=0.0 \--micro_batch_size=4 \--checkpoint_dir=./information_extraction_model/ \--data_threads=5 \--user_defined_parameters='task=NER'模型输出结果见 predict_output_NER.tsv 文件对于关系抽取任务,代码如下:python main.py \--tables=predict_input_RE.tsv \--outputs=predict_output_RE.tsv \--input_schema=id:str:1,scheme:str:1,content:str:1 \--output_schema=id,content,q_and_a \--worker_gpu=4 \--app_name=information_extraction \--sequence_length=512 \--weight_decay=0.0 \--micro_batch_size=4 \--checkpoint_dir=./information_extraction_model/ \--data_threads=5 \--user_defined_parameters='task=RE'模型输出结果见 predict_output_RE.tsv 文件对于事件抽取任务,代码如下:python main.py \--tables=predict_input_EE.tsv \--outputs=predict_output_EE.tsv \--input_schema=id:str:1,scheme:str:1,content:str:1 \--output_schema=id,content,q_and_a \--worker_gpu=4 \--app_name=information_extraction \--sequence_length=512 \--weight_decay=0.0 \--micro_batch_size=4 \--checkpoint_dir=./information_extraction_model/ \--data_threads=5 \--user_defined_parameters='task=EE'模型输出结果见 predict_output_EE.tsv 文件在阿里云机器学习 PAI-DSW 上进行中文信息抽取PAI-DSW(Data Science Workshop)是阿里云机器学习平台 PAI 开发的云上 IDE,面向不同水平的开发者,提供了交互式的编程环境(文档)。在 DSW Gallery 中,提供了各种 Notebook 示例,方便用户轻松上手 DSW,搭建各种机器学习应用。我们也在 DSW Gallery 中上架了使用 PAI-Diffusion 模型进行中文信息抽取的 Sample Notebook,欢迎大家体验!未来展望在未来,我们计划进一步改进 K-Global Pointer 模型,敬请期待。我们将在 EasyNLP 框架中集成更多中⽂模型,覆盖各个常⻅中⽂领域,敬请期待。我们也将在 EasyNLP 框架中集成更多 SOTA 模型,来⽀持各种 NLP 和多模态任务。此外,阿⾥云机器学习 PAI 团队也在持续推进中⽂NLP 和多模态模型的⾃研⼯作,欢迎⽤户持续关注我们,也欢迎加⼊我们的开源社区,共建中⽂NLP 和多模态算法库!Github 地址:https://github.com/alibaba/EasyNLPReferenceChengyu Wang, Minghui Qiu, Taolin Zhang, Tingting Liu, Lei Li, Jianing Wang, Ming Wang, Jun Huang, Wei Lin. EasyNLP: A Comprehensive and Easy-to-use Toolkit for Natural Language Processing. EMNLP 2022GlobalPointer:https://kexue.fm/archives/8373阿里灵杰回顾阿里灵杰:阿里云机器学习PAI开源中文NLP算法框架EasyNLP,助力NLP大模型落地阿里灵杰:预训练知识度量比赛夺冠!阿里云PAI发布知识预训练工具阿里灵杰:EasyNLP带你玩转CLIP图文检索阿里灵杰:EasyNLP中文文图生成模型带你秒变艺术家阿里灵杰:EasyNLP集成K-BERT算法,借助知识图谱实现更优Finetune阿里灵杰:中文稀疏GPT大模型落地 — 通往低成本&高性能多任务通用自然语言理解的关键里程碑阿里灵杰:EasyNLP玩转文本摘要(新闻标题)生成阿里灵杰:跨模态学习能力再升级,EasyNLP电商文图检索效果刷新SOTA阿里灵杰:EasyNLP带你实现中英文机器阅读理解阿里灵杰:EasyNLP发布融合语言学和事实知识的中文预训练模型CKBERT阿里灵杰:当大火的文图生成模型遇见知识图谱,AI画像趋近于真实世界阿里灵杰:PAI-Diffusion模型来了!阿里云机器学习团队带您徜徉中文艺术海洋阿里灵杰:阿里云PAI-Diffusion功能再升级,全链路支持模型调优,平均推理速度提升75%以上
文章
机器学习/深度学习  ·  人工智能  ·  自然语言处理  ·  算法  ·  IDE  ·  测试技术  ·  Shell  ·  开发工具  ·  知识图谱  ·  开发者
2023-03-01
应用场景系列之(1):流量管理下的熔断场景
在采用Istio 服务网格技术的很多客户案例中, 熔断是其中一个非常普遍的流量管理场景。在启用网格技术之前, 在一些使用Resilience4j 的Java 服务中客户已经使用了熔断功能, 但相比之下, Istio能够在网络级别支持启用熔断能力,无需集成到每个服务的应用程序代码中。深入理解熔断器在不同场景下的行为, 是将其应用到线上环境之前的关键前提。介绍启用熔断功能,需要创建一个目标规则来为目标服务配置熔断。其中, connectionPool下定义了与熔断功能相关的参数, 相关配置参数为:tcp.maxConnections: 到目标主机的最大 HTTP1 /TCP 连接数。默认值为2³²-1。http.http1MaxPendingRequests:等待就绪的连接池连接时,最多可以排队的请求数量。默认值为1024。http.http2MaxRequests:对后端服务的最大活跃请求数。默认值为1024。这些参数在一个简单的场景中, 如一个客户端和一个目标服务实例(在 Kubernetes 环境中,一个实例相当于一个 pod)的情况下是清晰的。然而, 在生产环境中,比较可能出现的场景是:一个客户端实例和多个目标服务实例多个客户端实例和单个目标服务实例客户端和目标服务的多个实例我们创建了两个 python 脚本——一个用于表示目标服务,另一个用于调用服务的客户端。服务器脚本是一个简单的 Flask 应用程序,它公开一个休眠 5 秒的API服务端点,然后返回一个“hello world!”字符串。示例代码如下所示:#! /usr/bin/env python3 from flask import Flask import time app = Flask(__name__) @app.route('/hello') def get(): time.sleep(5) return 'hello world!' if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port='9080', threaded = True)客户端脚本以 10 个为一组调用服务器端点,即 10 个并行请求,然后在发送下一批 10 个请求之前休眠一段时间。它在无限循环中执行此操作。为了确保当我们运行客户端的多个pod 时,它们都同时发送批处理,我们使用系统时间(每分钟的第 0、20 和40秒)发送批处理。#! /usr/bin/env python3 import requests import time import sys from datetime import datetime import _thread def timedisplay(t): return t.strftime("%H:%M:%S") def get(url): try: stime = datetime.now() start = time.time() response = requests.get(url) etime = datetime.now() end = time.time() elapsed = end-start sys.stderr.write("Status: " + str(response.status_code) + ", Start: " + timedisplay(stime) + ", End: " + timedisplay(etime) + ", Elapsed Time: " + str(elapsed)+"\n") sys.stdout.flush() except Exception as myexception: sys.stderr.write("Exception: " + str(myexception)+"\n") sys.stdout.flush() time.sleep(30) while True: sc = int(datetime.now().strftime('%S')) time_range = [0, 20, 40] if sc not in time_range: time.sleep(1) continue sys.stderr.write("\n----------Info----------\n") sys.stdout.flush() # Send 10 requests in parallel for i in range(10): _thread.start_new_thread(get, ("http://circuit-breaker-sample-server:9080/hello", )) time.sleep(2)部署示例应用使用如下YAML部署示例应用,################################################################################################## # circuit-breaker-sample-server services ################################################################################################## apiVersion: v1 kind: Service metadata: name: circuit-breaker-sample-server labels: app: circuit-breaker-sample-server service: circuit-breaker-sample-server spec: ports: - port: 9080 name: http selector: app: circuit-breaker-sample-server --- apiVersion: apps/v1 kind: Deployment metadata: name: circuit-breaker-sample-server labels: app: circuit-breaker-sample-server version: v1 spec: replicas: 1 selector: matchLabels: app: circuit-breaker-sample-server version: v1 template: metadata: labels: app: circuit-breaker-sample-server version: v1 spec: containers: - name: circuit-breaker-sample-server image: registry.cn-hangzhou.aliyuncs.com/acs/istio-samples:circuit-breaker-sample-server.v1 imagePullPolicy: Always ports: - containerPort: 9080 --- ################################################################################################## # circuit-breaker-sample-client services ################################################################################################## apiVersion: apps/v1 kind: Deployment metadata: name: circuit-breaker-sample-client labels: app: circuit-breaker-sample-client version: v1 spec: replicas: 1 selector: matchLabels: app: circuit-breaker-sample-client version: v1 template: metadata: labels: app: circuit-breaker-sample-client version: v1 spec: containers: - name: circuit-breaker-sample-client image: registry.cn-hangzhou.aliyuncs.com/acs/istio-samples:circuit-breaker-sample-client.v1 imagePullPolicy: Always 启动之后, 可以看到为客户端和服务器端分别启动了对应的pod, 类似如下:> kubectl get po |grep circuit circuit-breaker-sample-client-d4f64d66d-fwrh4 2/2 Running 0 1m22s circuit-breaker-sample-server-6d6ddb4b-gcthv 2/2 Running 0 1m22s在未定义目标规则限制的情况下, 服务器端可以满足处理并发的10个客户端请求, 因此在服务器端的响应结果始终是200。客户端侧的日志应当类似如下:----------Info---------- Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.016539812088013 Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.012614488601685 Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.015984535217285 Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.015599012374878 Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.012874364852905 Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.018714904785156 Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.010422468185425 Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.012431621551514 Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.011001348495483 Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.01432466506958启用熔断规则通过服务网格技术启用熔断规则, 只需要针对目标服务定义对应的目标规则DestinationRule即可。创建并应用以下目标规则:apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: circuit-breaker-sample-server spec: host: circuit-breaker-sample-server trafficPolicy: connectionPool: tcp: maxConnections: 5它将与目标服务的 TCP 连接数限制为 5。让我们看看它在不同场景中的工作方式。场景#1:一个客户端实例和一个目标服务实例在这种情况下,客户端和目标服务都只有一个 pod。当我们启动客户端 pod 并监控日志时(建议重启下客户端以获得更直观的统计结果),我们会看到类似以下内容:----------Info---------- Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.0167787075042725 Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.011920690536499 Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.017078161239624 Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.018405437469482 Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.018689393997192 Status: 200, Start: 02:49:40, End: 02:49:50, Elapsed Time: 10.018936395645142 Status: 200, Start: 02:49:40, End: 02:49:50, Elapsed Time: 10.016417503356934 Status: 200, Start: 02:49:40, End: 02:49:50, Elapsed Time: 10.019930601119995 Status: 200, Start: 02:49:40, End: 02:49:50, Elapsed Time: 10.022735834121704 Status: 200, Start: 02:49:40, End: 02:49:55, Elapsed Time: 15.02303147315979可以看到所有请求都成功。但是,每批中只有 5 个请求的响应时间约为 5 秒,其余的要慢得多(大部分为10秒之多)。这意味着仅使用tcp.maxConnections会导致过多的请求排队,等待连接释放。如前面所述,默认情况下,可以排队的请求数为2³²-1。要真正具有熔断(即快速失败)行为,我们还需要设置http.http1MaxPendingRequests限制可以排队的请求数量。它的默认值为1024。有趣的是,如果我们将它的值设置为0,它就会回落到默认值。所以我们必须至少将它设置为1。让我们更新目标规则以仅允许1 个待处理请求:apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: circuit-breaker-sample-server spec: host: circuit-breaker-sample-server trafficPolicy: connectionPool: tcp: maxConnections: 5 http: http1MaxPendingRequests: 1并重启客户端 pod(记得一定需要重启客户端, 否则统计结果会出现偏差), 并继续观察日志, 类似如下:----------Info---------- Status: 503, Start: 02:56:40, End: 02:56:40, Elapsed Time: 0.005339622497558594 Status: 503, Start: 02:56:40, End: 02:56:40, Elapsed Time: 0.007254838943481445 Status: 503, Start: 02:56:40, End: 02:56:40, Elapsed Time: 0.0044133663177490234 Status: 503, Start: 02:56:40, End: 02:56:40, Elapsed Time: 0.008964776992797852 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.018309116363525 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.017424821853638 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.019804954528809 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.01643180847168 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.025975227355957 Status: 200, Start: 02:56:40, End: 02:56:50, Elapsed Time: 10.01716136932373可以看到, 4 个请求立即被限制,5 个请求发送到目标服务,1 个请求排队。这是预期的行为。可以看到客户端 Istio 代理与目标服务中的pod 建立的活跃连接数为5:kubectl exec $(kubectl get pod --selector app=circuit-breaker-sample-client --output jsonpath='{.items[0].metadata.name}') -c istio-proxy -- curl -X POST http://localhost:15000/clusters | grep circuit-breaker-sample-server | grep cx_active outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local::172.20.192.124:9080::cx_active::5场景#2:一个客户端实例和多个目标服务实例现在让我们在有一个客户端实例和多个目标服务实例 pod 的场景中运行测试。首先,我们需要将目标服务部署扩展到多个副本(比如3 个):kubectl scale deployment/circuit-breaker-sample-server --replicas=3在这里要验证的两个场景分别是:连接限制应用于 pod 级别:目标服务的每个 pod 最多 5 个连接;或者它是否应用于服务级别:无论目标服务中的 pod 数量如何,总共最多 5 个连接;在(1)中,我们应该看不到限制或排队,因为允许的最大连接数为 15(3 个 pod,每个 pod 5 个连接)。由于我们一次只发送 10 个请求,所有请求都应该成功并在大约 5 秒内返回。在(2)中,我们应该看到与之前场景 #1大致相同的行为。让我们再次启动客户端 pod 并监控日志:----------Info---------- Status: 503, Start: 03:06:20, End: 03:06:20, Elapsed Time: 0.011791706085205078 Status: 503, Start: 03:06:20, End: 03:06:20, Elapsed Time: 0.0032286643981933594 Status: 503, Start: 03:06:20, End: 03:06:20, Elapsed Time: 0.012153387069702148 Status: 503, Start: 03:06:20, End: 03:06:20, Elapsed Time: 0.011871814727783203 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.012892484664917 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.013102769851685 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.016939163208008 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.014261484146118 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.01246190071106 Status: 200, Start: 03:06:20, End: 03:06:30, Elapsed Time: 10.021712064743042我们仍然看到类似的限制和排队,这意味着增加目标服务的实例数量不会增加客户端的限制。因此, 我们推导出该限制适用于服务级别。运行一段时间之后, 可以看到客户端 Istio 代理与目标服务中的每个 pod 建立的连接数:outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local::172.20.192.124:9080::cx_active::2 outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local::172.20.192.158:9080::cx_active::2 outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local::172.20.192.26:9080::cx_active::2客户端代理与目标服务中的每个 pod 有 2 个活动连接, 不是 5,而是 6。正如 Envoy 和 Istio 文档中所提到的,代理在连接数量方面允许一些回旋余地。场景#3:多个客户端实例和一个目标服务实例在这种情况下,我们有多个客户端实例 pod,而目标服务只有一个 pod。相应地缩放副本:kubectl scale deployment/circuit-breaker-sample-server --replicas=1 kubectl scale deployment/circuit-breaker-sample-client --replicas=3由于所有 Istio 代理都基于本地信息独立运行,无需相互协调,因此对这个测试的期望是每个客户端 pod 都会表现出场景 #1 的行为,即每个 pod 将有 5 个请求被立即发送到目标服务,1 个请求正在排队并受到限制。让我们看一下日志,看看实际发生了什么:Client 1 ----------Info---------- Status: 503, Start: 03:10:40, End: 03:10:40, Elapsed Time: 0.008828878402709961 Status: 503, Start: 03:10:40, End: 03:10:40, Elapsed Time: 0.010806798934936523 Status: 503, Start: 03:10:40, End: 03:10:40, Elapsed Time: 0.012855291366577148 Status: 503, Start: 03:10:40, End: 03:10:40, Elapsed Time: 0.004465818405151367 Status: 503, Start: 03:10:40, End: 03:10:40, Elapsed Time: 0.007823944091796875 Status: 503, Start: 03:10:40, End: 03:10:40, Elapsed Time: 0.06221342086791992 Status: 503, Start: 03:10:40, End: 03:10:40, Elapsed Time: 0.06922149658203125 Status: 503, Start: 03:10:40, End: 03:10:40, Elapsed Time: 0.06859922409057617 Status: 200, Start: 03:10:40, End: 03:10:45, Elapsed Time: 5.015282392501831 Status: 200, Start: 03:10:40, End: 03:10:50, Elapsed Time: 9.378434181213379 Client 2 ----------Info---------- Status: 503, Start: 03:11:00, End: 03:11:00, Elapsed Time: 0.007795810699462891 Status: 503, Start: 03:11:00, End: 03:11:00, Elapsed Time: 0.00595545768737793 Status: 503, Start: 03:11:00, End: 03:11:00, Elapsed Time: 0.013380765914916992 Status: 503, Start: 03:11:00, End: 03:11:00, Elapsed Time: 0.004278898239135742 Status: 503, Start: 03:11:00, End: 03:11:00, Elapsed Time: 0.010999202728271484 Status: 200, Start: 03:11:00, End: 03:11:05, Elapsed Time: 5.015426874160767 Status: 200, Start: 03:11:00, End: 03:11:05, Elapsed Time: 5.0184690952301025 Status: 200, Start: 03:11:00, End: 03:11:05, Elapsed Time: 5.019806146621704 Status: 200, Start: 03:11:00, End: 03:11:05, Elapsed Time: 5.0175628662109375 Status: 200, Start: 03:11:00, End: 03:11:05, Elapsed Time: 5.031521558761597 Client 3 ----------Info---------- Status: 503, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.012019157409667969 Status: 503, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.012546539306640625 Status: 503, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.013760805130004883 Status: 503, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.014089822769165039 Status: 503, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.014792442321777344 Status: 503, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.015463829040527344 Status: 503, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.01661539077758789 Status: 200, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.02904224395751953 Status: 200, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.03912043571472168 Status: 200, Start: 03:13:20, End: 03:13:20, Elapsed Time: 0.06436014175415039结果显示, 每个客户端上的 503 数量增加了。系统仅允许来自所有三个客户端实例pod 的 5 个并发请求。检查客户端代理日志,希望能得到一些线索,并观察到两种不同类型的日志,用于被限制的请求 ( 503 )。其中, 注意到RESPONSE_FLAGS包括了两个值:UO和URX。UO:上游溢出(断路)URX: 请求被拒绝,因为已达到上游重试限制 (HTTP)或最大连接尝试次数 (TCP) 。{"authority":"circuit-breaker-sample-server:9080","bytes_received":"0","bytes_sent":"81","downstream_local_address":"192.168.142.207:9080","downstream_remote_address":"172.20.192.31:44610","duration":"0","istio_policy_status":"-","method":"GET","path":"/hello","protocol":"HTTP/1.1","request_id":"d9d87600-cd01-421f-8a6f-dc0ee0ac8ccd","requested_server_name":"-","response_code":"503","response_flags":"UO","route_name":"default","start_time":"2023-02-28T03:14:00.095Z","trace_id":"-","upstream_cluster":"outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local","upstream_host":"-","upstream_local_address":"-","upstream_service_time":"-","upstream_transport_failure_reason":"-","user_agent":"python-requests/2.21.0","x_forwarded_for":"-"} {"authority":"circuit-breaker-sample-server:9080","bytes_received":"0","bytes_sent":"81","downstream_local_address":"192.168.142.207:9080","downstream_remote_address":"172.20.192.31:43294","duration":"58","istio_policy_status":"-","method":"GET","path":"/hello","protocol":"HTTP/1.1","request_id":"931d080a-3413-4e35-91f4-0c906e7ee565","requested_server_name":"-","response_code":"503","response_flags":"URX","route_name":"default","start_time":"2023-02-28T03:12:20.995Z","trace_id":"-","upstream_cluster":"outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local","upstream_host":"172.20.192.84:9080","upstream_local_address":"172.20.192.31:58742","upstream_service_time":"57","upstream_transport_failure_reason":"-","user_agent":"python-requests/2.21.0","x_forwarded_for":"-"} 带有UO标志的请求由客户端代理在本地进行限制。带有URX标志的请求被目标服务代理拒绝。日志中其他字段的值(例如DURATION、UPSTREAM_HOST 和 UPSTREAM_CLUSTER)也证实了这一点。为了进一步验证,我们还要检查目标服务端的代理日志:{"authority":"circuit-breaker-sample-server:9080","bytes_received":"0","bytes_sent":"81","downstream_local_address":"172.20.192.84:9080","downstream_remote_address":"172.20.192.31:59510","duration":"0","istio_policy_status":"-","method":"GET","path":"/hello","protocol":"HTTP/1.1","request_id":"7684cbb0-8f1c-44bf-b591-40c3deff6b0b","requested_server_name":"outbound_.9080_._.circuit-breaker-sample-server.default.svc.cluster.local","response_code":"503","response_flags":"UO","route_name":"default","start_time":"2023-02-28T03:14:00.095Z","trace_id":"-","upstream_cluster":"inbound|9080||","upstream_host":"-","upstream_local_address":"-","upstream_service_time":"-","upstream_transport_failure_reason":"-","user_agent":"python-requests/2.21.0","x_forwarded_for":"-"} {"authority":"circuit-breaker-sample-server:9080","bytes_received":"0","bytes_sent":"81","downstream_local_address":"172.20.192.84:9080","downstream_remote_address":"172.20.192.31:58218","duration":"0","istio_policy_status":"-","method":"GET","path":"/hello","protocol":"HTTP/1.1","request_id":"2aa351fa-349d-4283-a5ea-dc74ecbdff8c","requested_server_name":"outbound_.9080_._.circuit-breaker-sample-server.default.svc.cluster.local","response_code":"503","response_flags":"UO","route_name":"default","start_time":"2023-02-28T03:12:20.996Z","trace_id":"-","upstream_cluster":"inbound|9080||","upstream_host":"-","upstream_local_address":"-","upstream_service_time":"-","upstream_transport_failure_reason":"-","user_agent":"python-requests/2.21.0","x_forwarded_for":"-"}正如预期的那样,这里有 503 响应码,这也是导致客户端代理上有 "response_code":"503"以及"response_flags":"URX"。总而言之, 客户端代理根据它们的连接限制(每个 pod最多 5 个连接)发送请求——排队或限制(使用UO 响应标志)多余的请求。所有三个客户端代理在批处理开始时最多可以发送 15 个并发请求。但是,其中只有 5 个成功,因为目标服务代理也在使用相同的配置(最多 5 个连接)限制。目标服务代理将仅接受 5 个请求并限制其余请求,这些请求在客户端代理日志中带有URX响应标志。上述所发生情况的直观描述类似如下:场景#4:多个客户端实例和多个目标服务实例最后一个也可能是最常见的场景,我们有多个客户端实例pod和多个目标服务实例 pod 。当我们增加目标服务副本时,我们应该会看到请求的成功率整体增加,因为每个目标代理可以允许 5 个并发请求。如果我们将副本数增加到 2,我们应该会看到所有 3 个客户端代理在一个批次中生成的 30 个请求中有 10 个请求成功。我们仍然会观察到客户端和目标服务代理上的限制。如果我们将副本增加到 3,我们应该看到 15 个成功的请求。如果我们将数量增加到 4,我们应该仍然只能看到 15 个成功的请求。为什么? 这是因为无论目标服务有多少个副本,客户端代理上的限制都适用于整个目标服务。因此,无论有多少个副本,每个客户端代理最多可以向目标服务发出 5 个并发请求。总结在客户端:每个客户端代理独立应用该限制。如果限制为 100,则每个客户端代理在应用本地限制之前可以有 100 个未完成的请求。如果有 N 个客户端调用目标服务,则总共最多可以有 100*N 个未完成的请求。客户端代理的限制是针对整个目标服务,而不是针对目标服务的单个副本。即使目标服务有 200 个活动 pod, 限流仍然会是100。在目标服务端:每个目标服务代理也适用该限制。如果该服务有 50 个活动的 pod,则在应用限流并返回 503 之前,每个 pod 最多可以有 100 个来自客户端代理的未完成请求。如果对服务网格 ASM 感兴趣或者对上述内容有任何疑问,欢迎钉钉扫描下方二维码或搜索群号(30421250)加入服务网格ASM 用户交流群,一起探索服务网格技术。
文章
监控  ·  Kubernetes  ·  网络协议  ·  Java  ·  API  ·  Python  ·  Perl  ·  容器
2023-02-28
...
跳转至:
阿里云机器学习平台PAI
2317 人关注 | 165 讨论 | 343 内容
+ 订阅
  • 阿里云PAI-DeepRec CTR 模型性能优化天池大赛——获奖队伍技术分享
  • 天池 DeepRec CTR 模型性能优化大赛 - 夺冠技术分享
  • 喜马拉雅基于阿里云机器学习平台PAI-HybridBackend的深度学习模型训练优化实践
查看更多 >
阿里云容器服务 ACK
234764 人关注 | 146 讨论 | 881 内容
+ 订阅
  • 阿里云容器服务 ACK 产品技术动态(202302)
  • 丽迅物流通过 ACR EE 管理大规模容器镜像,快速响应业务需求
  • 将集群成本分析接入ACK注册集群
查看更多 >
开发与运维
5772 人关注 | 133335 讨论 | 318987 内容
+ 订阅
  • 使用代理http时告诉切换IP代理有什么作用?如何切换?
  • Java 最常见的面试题:spring 支持几种 bean 的作用域?
  • Java 最常见的面试题:spring 中的 bean 是线程安全的吗?
查看更多 >
人工智能
2869 人关注 | 12320 讨论 | 102508 内容
+ 订阅
  • 【电力系统】考虑柔性负荷的综合能源系统低碳经济优化调度附matlab代码
  • 【微电网优化】基于改进粒子群算法的微网多目标优化调度附matlab代码
  • 集群固定翼无人机飞行仿真平台附matlab代码
查看更多 >
云原生
234321 人关注 | 11601 讨论 | 47335 内容
+ 订阅
  • Java 最常见的面试题:spring 支持几种 bean 的作用域?
  • Java 最常见的面试题:spring 中的 bean 是线程安全的吗?
  • Java 最常见的面试题:spring 有哪些主要模块?
查看更多 >