跳到主要内容

python机器学习-数据降维

· 阅读需 6 分钟
Jason Lee

PCA(主成分分析)

example:

import numpy as np
from sklearn.decomposition import PCA

# 创建一个示例数据集
data = np.array([
[1.5, 2.0, 3.0, 4.5],
[2.2, 2.8, 3.9, 4.2],
[1.9, 2.5, 3.2, 4.7],
[2.7, 3.0, 3.6, 4.0],
[2.3, 2.9, 3.7, 4.3]
])

# 初始化 PCA 模型,指定主成分的数量
pca = PCA(n_components=2)

# 对数据进行拟合
pca.fit(data)

# 获取主成分
principal_components = pca.components_

# 获取主成分的方差贡献比例
explained_variance_ratio = pca.explained_variance_ratio_

# 对数据进行 PCA 转换
transformed_data = pca.transform(data)

print("原始数据:\n", data)
print("主成分:\n", principal_components)
print("主成分的方差贡献比例:", explained_variance_ratio)
print("PCA 转换后的数据:\n", transformed_data)

输出:

原始数据:
[[1.5 2. 3. 4.5]
[2.2 2.8 3.9 4.2]
[1.9 2.5 3.2 4.7]
[2.7 3. 3.6 4. ]
[2.3 2.9 3.7 4.3]]
主成分:
[[-0.61305201 -0.55633419 -0.46392272 0.31533349]
[-0.53989171 -0.03347232 0.83292811 0.11673604]]
主成分的方差贡献比例: [0.87919714 0.0703791 ]
PCA 转换后的数据:
[[ 1.00928239 -0.02497259]
[-0.37705186 0.28493986]
[ 0.45617665 -0.0677326 ]
[-0.71873459 -0.2649261 ]
[-0.36967259 0.07269143]]

在深入之前,有必要解释一下 PCA 为什么在高维数据中找最大方差的方向而不是别的指标(比如最大值),来降维的?找到最大方差的方向意味着在这个方向上数据的变化最为显著,即信息越丰富。以我之前接触到的人脸图像为例,不同位置的像素有差异,才能让分类器有能力识别。

为了将我视频学习到的和这个例子对应起来,将输出中的几个部分进行解释:

首先,这个例子中的原始数据是有五个样本,每个样本有四个维度的特征(一开始我还真没搞明白这个)。然后,pca 算法找出四个主成分(找的过程是通过计算协方差矩阵和特征向量等步骤得来的,视频举的例子是几何平面便于理解,而书上的公式是一种通用的方法,适合任意数量纬度特征,二者本质相同),也就是输出中的主成分。主成分本质上是一个方向向量,且模为 1,所以本例中的主成分有两组,每组四个值,对应四种特征,哪个特征的绝对值最大,说明哪个特征在这个主成分中占主导。方差贡献比例就是,依次计算所有数据在某一主成分上的投影点到距离之和,然后全部求和,最后计算一个主成分所占的比例,比例越高,说明方差越大,数据在这个主成分上差异越明显。最后的转换后数据,则是将所有数据在某一主成分上的投影点到距离,作为以各主成分为新坐标轴的空间内的坐标。

LDA(线性判别分析)

不同于 PCA,LDA 有类别标签参与训练,所以是有监督的。LDA 训练过程中的优化目标也变成了最大化不同类之间的类间差距,而不是 PCA 的找样本内方差最大的方向,可以说是完全不同的方法,但是都能实现降维。

LDA 在一条线上投影数据点,并且让投影点的不同类之间的均值差距最大,而同类的方差最小,这样就让不同类之间的类间差距最大化。

可以说,LDA 的适用范围更窄些,它要求数据有明确的标签,且只适用分类问题;而 PCA 则适用范围更广,因为它只要求提供特征,能适用于不限于分类的问题。

example:

# 导入必要的库
from sklearn import datasets
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
import matplotlib.pyplot as plt

# 加载示例数据集(这里以Iris数据集为例)
iris = datasets.load_iris()
X = iris.data
y = iris.target

# 创建LDA模型
lda = LinearDiscriminantAnalysis(n_components=2) # 我们希望将数据降至二维

# 拟合模型并进行降维
X_lda = lda.fit_transform(X, y)

# 绘制降维后的数据
plt.figure(figsize=(8, 6))
colors = ['navy', 'turquoise', 'darkorange']
lw = 2

for color, i, target_name in zip(colors, [0, 1, 2], iris.target_names):
plt.scatter(X_lda[y == i, 0], X_lda[y == i, 1], alpha=0.8, color=color,
label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('LDA of IRIS dataset')
plt.show()

16978904104791697890409581.png

工作中的一些工具使用技巧和方法论

· 阅读需 2 分钟
Jason Lee

我的工作是后端开发,这几天的内容就是做一套增删改查接口,暂时还没涉及复杂的逻辑。之前在外包里面搞前端,工具就两个:vscode 和浏览器。现在包括但不限于:navicat(操纵数据库)、vscode(写代码)、winscp(把本地写好的代码传到测试服务器)、postman(测试接口)、xshell(查看服务报错日志来 debug)、金山文档(写接口文档)。项目是没有办法在本地运行的,都要上传到测试服务器,通过请求接口后才能知道有无 bug。

之前的一个痛点是,每次写完代码,要在 scp 里面改目录,拖进去;如果每次修改 model、service、controller,就要拖动三次,还容易看错目录,搞得很紧张。后来,自己想的办法是,把 winscp 的文本编辑器由默认的内置改为 vscode,这样,直接从远程服务器上下载一个文件到本地,用 vscode 打开,编辑完保存,改动就同步到服务器上了。

《python 机器学习》数据预处理

· 阅读需 3 分钟
Jason Lee

缺失数据值的处理

在 scikit-learn 中处理缺失数据的方法:

SimpleImputer: SimpleImputer 是 scikit-learn 中用于填充缺失数据的类。你可以使用不同的策略来填充缺失值,包括均值、中位数、众数等。例如:

from sklearn.impute import SimpleImputer

imputer = SimpleImputer(strategy='mean') # 使用均值填充缺失值
X_imputed = imputer.fit_transform(X)

KNNImputer: KNNImputer 使用 K-最近邻算法来填充缺失值,根据相邻样本的值进行估计。

from sklearn.impute import KNNImputer

imputer = KNNImputer(n_neighbors=5) # 使用5个最近邻的值来估计缺失值
X_imputed = imputer.fit_transform(X)

在 pandas 中处理缺失数据的方法:

isna() 和 dropna(): isna() 方法用于检测缺失值,dropna() 方法用于删除包含缺失值的行或列。

import pandas as pd

df = pd.DataFrame({'A': [1, 2, None, 4], 'B': [None, 6, 7, 8]})
df.isna() # 检测缺失值
df.dropna() # 删除包含缺失值的行
df.dropna(axis=1) # 删除包含缺失值的列

fillna(): fillna() 方法用于填充缺失值,你可以指定要使用的值或方法。

df.fillna(value=0)  # 用0填充缺失值
df.fillna(method='ffill') # 使用前一个非缺失值进行前向填充
df.fillna(method='bfill') # 使用后一个非缺失值进行后向填充

处理类别数据

这里对类别数据做一个定义:一般以字符串形式出现,表示类别。类别数据分为两种,一种是内在有数值大小关系的,比如衣服尺寸:S,M,L;另一种是标称特征,不具备排序特征。既然是字符串,我们都要处理为数字方便训练。

sklearn

这个函数是处理标签列的,本质上是通过枚举,将所有独立的标签从 0 开始编号。之所以可以这样处理,因为我们不关心类标被赋予哪个整数值。

from sklearn.preprocessing import LabelEncoder
class_le = LabelEncoder()
y = class_le.fir_transform(df['label'].values)

// 如果要将整数类标还原到字符串,可以用:
class_le.inverse_transform(y)

如果是属于特征的类别数据,特别是无序数据,用枚举则是不合适的。这是因为,这会使得模型假定不同类别之间有顺序关系。所以,独热编码更合适。

from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder()
ohe.fit_transform(df['color'].values)

pandas

pandas 有 get_dummies 可以用于实现独热编码

pd.get_dummies(df) // 只对字符串列进行转化

《python机器学习》分类算法

· 阅读需 3 分钟
Jason Lee

前言

我打算开一个全新的系列,记录我在学习《python 机器学习》这本书过程中的要点和感想。

决策边界图

分类任务中的一种图,可以看出模型在整个网格内的预测结果。

16962158450461696215844398.png

代码如下:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.svm import SVC

# 步骤2:加载鸢尾花数据集
iris = datasets.load_iris()
X = iris.data[:, :2] # 使用前两个特征以便进行可视化
y = iris.target

# 步骤3:训练一个SVM分类器
clf = SVC(kernel='linear')
clf.fit(X, y)

# 步骤4:创建一个网格来覆盖特征空间
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.01), np.arange(y_min, y_max, 0.01))

# 步骤5:对网格上的每个点使用分类器进行预测,获取决策边界
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

# 步骤6:可视化数据点、决策边界和决策区域
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral, alpha=0.8)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
plt.xlabel('Sepal Length')
plt.ylabel('Sepal Width')
plt.title('Decision Boundary Visualization')
plt.show()

主要的函数有 meshgrid,功能是生成网格坐标数据。还有 contourf,绘制决策边界图。

逻辑回归

线性计算方式,激活函数采用 sigmoid,从而得出属于正类别的概率。

决策树/随机森林

决策树就是一种二叉树,通过计算一种特征能否尽可能的将类别区分来决定选择哪一种特征作为节点。随机森林则是决策树的集合,通过随机抽样得到训练样本来引入随机性,并且最后由多数投票来确定预测结果。

基于eggjs, sequelize和pgsql的小需求

· 阅读需 5 分钟
Jason Lee

前言

这是我在部门遇到的第一个小需求。这一次,我写的是后端代码。刚开始,我还陷入在一种思维定式里,似乎一定要在现有项目的工程里添加代码。如果这样做,就需要在本地或者测试服务器启动项目,然而不熟悉服务器环境和配置的我,容易卡在一个环节上无法继续。我后面想明白了,完全可以将其视作一个独立的小项目。

pgsql 启动入口

不同于 mysql 直接通过命令行工具启动,pgsql 的启动方式有两种。一种是自带的 pgadmin4 这款数据库可视化软件,只要第一次连接上,后续打开都会自动连接。另一种是 sql shell, 也是随 pgsql 一起安装的。打开后,前面几个参数都是默认,最后输入口令(密码)。

16959671454581695967145365.png

eggjs+sequelize 的开发流程

初始化 egg 项目和安装 npm 包

$ npm init egg --type=simple
$ npm i

# sequelize 和 nodejs的pgsql驱动
npm install --save egg-sequelize pg pg-hstore

在 config/plugin.js 中引入 egg-sequelize 插件

'use strict';

exports.sequelize = {
enable: true,
package: 'egg-sequelize',
};

在 config/config.default.js 中编写 sequelize 配置

/* eslint valid-jsdoc: "off" */

'use strict';

module.exports = appInfo => {
/**
* built-in config
* @type {Egg.EggAppConfig}
**/
const config = exports = {};

// use for cookie sign key, should change to your own and keep security
config.keys = appInfo.name + '_1696034448361_9424';

// add your middleware config here
config.middleware = [];

// mysql

// config.sequelize = {
// dialect: 'mysql', // support: mysql, mariadb, postgres, mssql
// database: 'egg-sequelize-example-dev',
// host: '127.0.0.1',
// port: 3306,
// username: 'root',
// password: 'root',
// };

// pgsql

config.sequelize = {
database: 'egg-sequelize-example-dev',
username: 'postgres',
password: 'root',
host: 'localhost', // 或者你的数据库主机地址
port: 5432, // 或者你的数据库端口号
dialect: 'postgres', // 指定数据库类型为PostgreSQL
};

// add your user config here
const userConfig = {
// myAppName: 'egg',
};

return {
...config,
...userConfig,
};
};

创建数据库和数据表(在 pgadmin4 等工具中写 sql 语句)

CREATE DATABASE IF NOT EXISTS 'egg-sequelize-doc-default';
create table [name];

编写 model

注意字段要和数据表中定义的完全一致。这时候,model 可以通过 ctx.model 访问到,在编写 controller 或 service 层时可通过 ide 检查能否找到 model,若不能,检查 typings 文件夹下相应的 index.d.ts 文件,看看名称能否对应上。

# app/model/station.js

'use strict';

module.exports = (app) => {
const { STRING, INTEGER, TEXT, DATE } = app.Sequelize;
const { DataTypes } = require('sequelize');

const Station = app.model.define('Station', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: { type: STRING(30), allowNull: false },
associated_yard: { type: STRING(30), allowNull: false },
address: { type: STRING(50) },
longitude: { type: DataTypes.NUMERIC(5), allowNull: false},
latitude: { type: DataTypes.NUMERIC(5), allowNull: false },
province: {type: STRING(32), allowNull: false},
city: {type: STRING(32), allowNull: false},
county: { type: STRING(32), allowNull: false },
tag: { type: DataTypes.ARRAY(DataTypes.TEXT) },
property_unit: { type: STRING(30) },
property_contact: { type: STRING(30) },
property_phone: { type: STRING(11) },
status: { type: STRING(10), allowNull: false },
map_induction: { type: DataTypes.BOOLEAN, allowNull: false },
area: { type: STRING(30) },
accounting_policy: { type: STRING(30), allowNull: false },
operating_description: { type: TEXT, allowNull: false },
comment: { type: TEXT },
created_at: DATE,
updated_at: DATE,
});

return Station;
};

上述代码中需要注意 sequelize 的表名推导问题。在const Station = app.model.define('Station', {...})中,第一个 Station 是模型名称,是 eggjs 中调用 app.model.[name]用到的名称。然后 define 函数中的'Station'和数据库中的表明存在对应关系,具体的对应规则是:

  1. 默认的是单数转复数:默认情况下,Sequelize 会将模型的名称转换为复数形式,并将其用作数据库表的名称。比如,station 转成 stations。所以创建表时,应该把表命名为复数形式。
  2. 自定义表名:可以在定义模型时明确指定要映射到的表的名称。
const User = sequelize.define('User', {
// 模型属性定义
}, {
tableName: 'custom_users_table' // 自定义表名
});

编写 controller 和 service 层,在 router.js 中添加路由。

sklearn crash course

· 阅读需 4 分钟
Jason Lee

前言

油管视频教程。想到哪儿写到哪儿。

如何让 jupyter notebook 切换到 venv 环境

首先在虚拟环境下的 cmd 中运行:

pip install ipykernel

然后,在在虚环境中将当前的虚拟环境添加到 Jupyter Notebook 的 kernel 中:

python -m ipykernel install --name 虚环境名称 --display-name 虚环境名称 --user

框架

16955688583221695568857524.png

上图是用 sklearn 完成机器学习任务的一般框架。首先数据类型分为特征(X)和标签(y)。pipeline(管道、流程)包含数据归一化(preprocessing 的一种)和模型(model)。pipeline 有两个重要 api:fit 和 predict,前者训练,后者测试。

如果要确定超参数的值,且训练数据有限的情况下,需要用交叉验证(Cross validation, CV)。用法是:

mod = GridSearchCV(estimator=pipe, param_grid={
'model__n_neighbors': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
},
cv=3)

其中,estimator 是 pipeline 或者 model,param_grid 是需要确定的超参数,是一个字典,键是'model__[name]'的格式,cv 表示要将数据分为几折来进行交叉验证。

完整代码如下:

from sklearn.datasets import load_boston
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
import pandas as pd

mod = GridSearchCV(estimator=pipe, param_grid={
'model__n_neighbors': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
},
cv=3)
mod.fit(X, y);

## 查看交叉验证的结果
pd.DataFrame(mod.cv_results_)

预处理

视频以一个存在离群点的场景为例,说明预处理措施的作用。离群点会导致模型预测的偏差。

16956472633051695647262551.png

最容易想到的预处理方式是正态分布化,但是离群点问题仍然存在。因此,采用一种均匀分布方法:QuantileTransformer。

16956474313051695647430838.png

可以看出,离群点不明显了。

另外,增加特征的维数(PolynomialFeatures)也在预处理范畴内。增加特征维数可以更好地捕获数据中的特征关系。

最后介绍了 One Hot Encoding,一种经典的将文本数据转为数值特征(标签)的预处理措施。

总结一下,sklearn 中,调用预处理措施的格式是:Transformer().fit_transform(data).

指标

precision_score:分母是模型预测为正类的个数,分子是模型预测正类正确的个数。

recall_score: 分母是样本中所有正类的个数,分子是分母中模型预测正类正确的个数。

GridSearchCV (或者 model 和 pipeline)可以使用 scoring 来自定义目标函数。而自定义的损失函数需要 make_scorer 生成。

from sklearn.model_selection import GridSearchCV
from sklearn.metrics import precision_score, recall_score, make_scorer

def min_recall_precision(est, X, y_true, sample_weight=None):
y_pred = est.predict(X)
recall = recall_score(y_true, y_pred)
precision = precision_score(y_true, y_pred)
return min(recall, precision)

grid = GridSearchCV(
estimator=LogisticRegression(max_iter=1000),
param_grid={'class_weight': [{0: 1, 1: v} for v in np.linspace(1, 20, 30)]},
scoring={'precision': make_scorer(precision_score),
'recall': make_scorer(recall_score),
'min_both': min_recall_precision},
refit='min_both',
return_train_score=True,
cv=10,
n_jobs=-1
)

kafka初体验

· 阅读需 4 分钟
Jason Lee

启动 zookeeper 和 kafka

安装参考教程。本机版本是 2.12-3.5.1。

在启动 kafka 之前先启动 zookeeper:

.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties

然后

.\bin\windows\kafka-server-start.bat .\config\server.properties

创建 topic

.\bin\windows\kafka-topics.bat --create --bootstrap-server localhost:9092 --topic my-topic --partitions 3 --replication-factor 1

解释一下参数的含义:

  • kafka-topics.bat:用于创建、列出或删除 Kafka 主题的命令。
  • create:指示要创建主题。
  • bootstrap-server localhost:9092:指定 Kafka 服务器的地址和端口。确保这个地址和端口与你的 Kafka 服务器配置一致。
  • topic my-topic:指定要创建的主题名称,你可以将 my-topic 替换为你想要的主题名称。
  • partitions 3:指定主题的分区数。你可以根据需要调整分区数。
  • replication-factor 1:指定主题的复制因子。在本例中,复制因子为 1,这意味着每个分区只有一个副本。

列出 kafka 所有 topics,查看是否创建成功:

.\bin\windows\kafka-topics.bat --list --bootstrap-server localhost:9092

生产者发送消息

.\bin\windows\kafka-console-producer.bat --bootstrap-server localhost:9092 --topic my-topic
  • kafka-console-producer.bat:这是 Kafka 的命令行生产者工具。
  • bootstrap-server localhost:9092:指定 Kafka 服务器的地址和端口。确保这个地址和端口与你的 Kafka 服务器配置一致。
  • topic your-topic-name:指定你要发送消息的主题名称,请将 your-topic-name 替换为你的实际主题名称。

运行上述命令后,命令行将进入消息输入模式。你可以在命令行中输入消息,然后按 Enter 键发送它们。当你完成发送消息后,你可以通过键入 Ctrl+C 来退出 Kafka 生产者。

消费者接受消息

.\bin\windows\kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic my-topic --from-beginning
  • kafka-console-consumer.bat:这是 Kafka 的命令行消费者工具。
  • bootstrap-server localhost:9092:指定 Kafka 服务器的地址和端口。确保这个地址和端口与你的 Kafka 服务器配置一致。
  • topic your-topic-name:指定你要消费消息的主题名称,请将 your-topic-name 替换为你的实际主题名称。
  • from-beginning:这个选项表示你要从主题的起始位置开始消费消息。如果你希望从当前的消息位置开始消费,可以省略这个选项。

运行上述命令后,Kafka 消费者将开始消费指定主题中的消息。如果你之前已经在生产者中发送了消息到该主题,那么这些消息将会显示在命令行中。

你可以在命令行中看到消费的消息,直到你手动停止消费者(通过按 Ctrl+C)或者关闭命令行窗口。

文本分类比赛学习记录

· 阅读需 6 分钟
Jason Lee

前言

本文是我对在公司参加的“ChatGPT 生成文本检测器”比赛。数据集为中文作文样本,其中从互联网上采集得到了真实作文,并且 ChatGLM-6B 生成了部分作文。参赛选手的任务是根据文本内容,区分作文的来源。但是,文本不是以内容呈现,而是一堆数字字符串,形如:[0 43 2 66]。可以推测出,每个数字代表一个汉字在语料库中的索引。

文本分类任务的四步:

  • 准备数据集:包括加载数据集和执行基本预处理,然后把数据集分为训练集和验证集。
  • 特征工程:将原始数据集被转换为用于训练机器学习模型的平坦特征(flat features)。
  • 模型训练
  • 进一步提高分类器性能

下面按照前面三步(省略第四步)介绍我的做法。

准备数据集

这一步的主要工作是读取原始文件,并划分训练集和验证集,用到的库 pandas 和 sklearn。

import pandas as pd
from sklearn import model_selection, preprocessing, naive_bayes, metrics, linear_model
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer

df = pd.read_csv('train.csv')

# print(df['content'])

#将数据集分为训练集和验证集
train_x, valid_x, train_y, valid_y = model_selection.train_test_split(df['content'], df['label'])

# label编码为目标变量
encoder = preprocessing.LabelEncoder()
train_y = encoder.fit_transform(train_y)
valid_y = encoder.fit_transform(valid_y)

特征工程

计数向量是数据集的矩阵表示,其中每行代表来自语料库的文档,每列表示来自语料库的术语,并且每个单元格表示特定文档中特定术语的频率计数:

#创建一个向量计数器对象
count_vect = CountVectorizer(analyzer='word', token_pattern=r'\w{1,}')
count_vect.fit(df['content'])

#使用向量计数器对象转换训练集和验证集
xtrain_count = count_vect.transform(train_x)
xvalid_count = count_vect.transform(valid_x)

模型训练

线性分类器用于训练。

## 训练函数
def train_model(classifier, feature_vector_train, label, feature_vector_valid, is_neural_net=False):
# fit the training dataset on the classifier
classifier.fit(feature_vector_train, label)


# predict the labels on validation dataset
predictions = classifier.predict(feature_vector_valid)


if is_neural_net:
predictions = predictions.argmax(axis=-1)


return classifier, metrics.accuracy_score(predictions, valid_y)

if __name__ == "__main__":
# classifier, accuracy = train_model(naive_bayes.MultinomialNB(), xtrain_count, train_y, xvalid_count)
# print("NB, Count Vectors: ", accuracy)

classifier, accuracy = train_model(linear_model.LogisticRegression(), xtrain_count, train_y, xvalid_count)
print("LR, Count Vectors: ", accuracy)

# csv out

testDF = pd.read_csv('test.csv')
test_x = testDF['content']

#使用向量计数器对象转换测试集
xtest_count = count_vect.transform(test_x)

predictions = classifier.predict(xtest_count)

submissionDF = pd.DataFrame()
submissionDF['name'] = testDF['name']
submissionDF['label'] = predictions

submissionDF.to_csv('LR_submission.csv', index=False)

总结

这次是我在 NLP 领域的一次小试牛刀,主要用到了 sklearn 框架,使得代码的编写变得简单。在不需要数据清洗等步骤的情况下,通过简单的计数向量和逻辑回归就使得最终的测试集结果达到了 0.99 以上,说明本次比赛的数据还是比较简单的。

另外,这次比赛也改变了我对于机器学习的认知。一开始我认为这是一个有难度的任务,想去 huggingface 上找一些文本分类的模型,践行“拿来主义”。但是怎么也找不到合适的。后来,在网上搜索文本分类的解决方案,看到一个相似的任务,且用了 sklearn,很短的代码就能达到不错的效果。也许,传统机器学习的能力比我想象的强很多。

更新

过了一段时间后,在接触更多 sklearn 知识后,我用 pipeline 来以更简单的方式实现:

import pandas as pd
from sklearn import model_selection, preprocessing, naive_bayes, metrics, linear_model
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.decomposition import PCA, TruncatedSVD
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.ensemble import VotingClassifier
from sklearn.neighbors import KNeighborsClassifier # 用于分类任务
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score
from sklearn.metrics import roc_curve, roc_auc_score
import matplotlib.pyplot as plt

df = pd.read_csv('train.csv')
X = df['content'].values
y = df['label'].values
le = preprocessing.LabelEncoder()
y = le.fit_transform(y)


#将数据集分为训练集和验证集
train_x, valid_x, train_y, valid_y = model_selection.train_test_split(X, y, random_state=43)

pipe_lr = Pipeline([('cv', CountVectorizer(analyzer='word', token_pattern=r'\w{1,}')),
('scl', StandardScaler(with_mean=False)),
('clf', linear_model.LogisticRegression(max_iter=50000))])

pipe_lr.fit(train_x, train_y)
print('Test Accuracy: %.4f' %pipe_lr.score(valid_x, valid_y))

## more metrics
valid_y_pred = pipe_lr.predict(valid_x)
confmat = confusion_matrix(y_true=valid_y, y_pred=valid_y_pred)
print(confmat)

print('Precision: %.3f' % precision_score(y_true=valid_y, y_pred=valid_y_pred))
print('Recall: %.3f' % recall_score(y_true=valid_y, y_pred=valid_y_pred))
print('F1: %.3f' % f1_score(y_true=valid_y, y_pred=valid_y_pred))

## draw roc
y_probs = pipe_lr.predict_proba(valid_x)
fpr, tpr, thresholds = roc_curve(valid_y, y_probs[:, 1], pos_label=1)
roc_auc = roc_auc_score(valid_y, y_probs[:, 1])
print('auc: %.4f' % roc_auc)
# Plot the ROC curve
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC curve (area = %.4f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc='lower right')
plt.show()


## testset result

testDF = pd.read_csv('test.csv')
test_x = testDF['content'].values

pipe_lr.fit(X, y)
predictions = pipe_lr.predict(test_x)

submissionDF = pd.DataFrame()
submissionDF['name'] = testDF['name']
submissionDF['label'] = predictions

submissionDF.to_csv('LR_submission.csv', index=False)

在新版本中,加入了不少指标用于衡量模型性能。更重要的是,之前在输出测试结果时,用的是训练集上训练的模型,而不是整个数据集上重新训练一次。在重新训练后,发现指标从 0.9901 来到了 0.9912,提示非常显著。这提示了我以后一定要注意这个问题。

typescript文件造成win文件夹卡顿原因及解决方案

· 阅读需 2 分钟
Jason Lee

写了好几天的 ts 代码,发现打开包含 ts 代码的文件夹时,文件夹会缓慢刷新,而且不是列表形式展示。非常痛苦:(

一开始以为是 windows11 的 bug,后来一想,最开始新建 ts 代码时,win11 直接把它当成视频文件,会不会和这个有关系。所以就用 google 搜了这个问题,果然,只有第一个搜索结果给出了问题原因和解决方案。正如所预料的,win11 把 ts 文件默认成视频文件,每次展示都要刷新。办法就是修改注册表,使系统默认 ts 为文本文档。新建.reg文件:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\.ts]
@="txtfile"
"Content Type"="text/plain"
"PerceivedType"="text"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\KindMap]
".ts"="document"

双击即修改完成。

参考: https://www.cnblogs.com/himeka/p/16306947.html

另外,我发现只有 google 能搜到上述解决方案,bing 和 baidu 都不行。现在知道用谁了吧...

人人都能听懂的 javascript 异步机制

· 阅读需 7 分钟
Jason Lee

前言

同步异步一直是一个绕不过去的小问题,要么遇不上,要么遇上了似懂非懂。这篇文章力争从异步任务的底层需求出发,对 promise、async/await 进行一个详细的了解。

同步 or 异步?

16913918214981691391820745.png

上面这张图表示了两种模式的执行顺序区别,也是最根本的区别。

chatGPT 给出的定义如下:

异步任务是指在程序执行过程中,不会阻塞后续代码执行的任务。它们允许在后台执行,以便在等待长时间操作(例如网络请求、文件读写或计算密集型任务)完成时,程序能够继续执行其他任务,提高了程序的响应性和效率。 一个异步函数可以拆分为两个部分,一个是耗时部分,这部分时间内执行后面的代码来防止阻塞;另一个是执行回调任务部分,当耗时部分产生结果后,执行回调逻辑。

注:回调函数的定义是执行回调任务的一种形式,javascript 另外两种回调形式为 Promise,async/await。

setTimeout 函数

几乎所有讲解 js 异步机制的都会用 setTimeout 函数来举例子。它是一个最简单的执行异步任务的函数,意思是延迟一定时间后,执行一个回调函数。注意它有三个参数。

setTimeout(function, delay, [param1, param2, ...]);
  • function: 要执行的函数。
  • delay: 延迟的时间,单位为毫秒。

回调函数为什么被鄙视

解释完了同步和异步的差异以及异步的优势,下面说一下这篇文章要讨论的核心:假如目的是多个异步任务按顺序执行,应该如何书写易懂的代码?

上面说了异步任务的耗时部分结束后,要执行回调部分;如果用了回调函数的形式,且回调函数的执行结果要作为下一个异步任务的输入,那么就会有多个缩进,使得代码难以读懂,比如:

getUserInfo(function (userInfo) {
getUserOrders(userInfo.id, function (orders) {
orders.forEach(function (order) {
getOrderDetails(order.id, function (details) {
console.log("Order ID: " + order.id);
console.log("Order Details: " + details);
});
});
});
});

假设我们要依次执行三个异步操作:获取用户信息、获取用户订单列表,然后获取每个订单的详细信息。在上面的代码中,每个异步操作都依赖前一个操作的结果,因此它们被嵌套在一起。这会使代码变得难以理解。面对这样的嵌套回调,处理错误也会变得非常困难:你必须在“金字塔”的每一级处理错误,而不是在最高一级一次完成错误处理。

所以,现在的目标是:

  1. 减少嵌套,使得代码方便阅读和调试
  2. 在最高一级一次性处理错误

一句话总结:异步任务是让代码不会阻塞,而 promise 和 async/await 都是为了让多个异步任务按顺序执行的代码更易懂。

接下来看看 promise 和 async/await 是怎么让异步代码更易懂的?

promise(期约)

必须要说明的是,promise 的内容比较多,而且绕。不过,我们至少要记住它的表达形式:

function asyncTask(shouldResolve) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldResolve) {
resolve("Task completed successfully.");
} else {
reject(new Error("Task failed."));
}
}, 1000);
});
}

asyncTask()
.then((result) => {
console.log(result); // 输出: Task result
})
.catch((error) => {
console.error(error);
});

一个 Promise 实例被创建,并传入一个函数作为初始化参数。这个函数有两个参数,分别是 resolve 和 reject。resolve 含义是异步任务顺利执行,并返回一个值,这个值作为 resolve 的参数而调用。然后,then 方法里面传入的函数,其中的参数正是上面 resolve 种传入的参数。在这个例子里,"Task completed successfully."就是下面 then 里面的 result。另外,reject 指的是执行异步任务的过程中,发生了错误,这个错误经由 reject 函数,被下面的 catch 捕获。reject 抛出的错误只能被.catch 捕获,而不会被 try/catch 捕获。

我的理解是,resolve/reject 都是一种占位符。作用是当函数执行到这里,遇到他们俩,知道该往哪个方向走,并携带上参数。现在我们看看那两个目标是不是实现了。

  1. 减少嵌套:如果有进一步的的异步任务,可以放在 then 里面执行,有几层,就写几个 then,避免了嵌套。
  2. 一次性处理错误:最下面的.catch 会捕获.then 链中任意一步发生的错误(reject)。

async/await

同样的,先给出形式。

function asyncTask() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Async task completed");
resolve("Task result");
}, 1000);
});
}

async function runTask() {
try {
const result = await asyncTask();
console.log(result); // 输出: Task result
} catch (error) {
console.error(error);
}
}

runTask();

异步函数是基于 Promise 的语法糖,使得异步任务的写法向同步任务看齐。这个例子中,asyncTask 函数显式返回了一个 Promise 对象,实际上,这个函数可以返回非 Promise 对象,只是会被 await 隐式地转化为 Promise 对象。await 字面意思包含暂停,也就是说,异步函数执行到 await 会暂停,等待事件循环队列下一次轮到自己执行。