0%

近期简单学习了一下向量数据库 qdrant 与 sentence-transformers 库,两者结合可以构建一个简单的自然语言搜索引擎。顺着官方的教程实操了一遍之后,稍微调整一番,我在中文数据集上构建了一个自然语言搜索引擎。

数据采集

教程中的数据集是一些美国的初创公司的数据(来自 startups-list.com),我打算自己从互联网上采集一些中文数据集,正好近期一直在使用微信读书,决定采集微信读书平台上计算机分类下的书籍数据。

观察了微信读书网页版的请求之后,我发现可以通过 ‘https://weread.qq.com/web/bookListInCategory/{type}?maxIndex={maxIndex}‘ 获取指定分类下的书籍列表。
在实验过程中,我发现即使分类下有更多的书籍,maxIndex 超过 480 之后就不再返回新的数据了,这应该是微信读书官方的一些限制。不过计算机分类下一共有七个子分类,每个分类都采集四百多本书籍,总共有 3000 多本书籍,也足够使用了。

首先定义一个函数用于获取制定分类指定页数的书籍列表。

1
2
3
4
5
6
url = 'https://weread.qq.com/web/bookListInCategory/'
def fetch_page(param):
type, page = param
_url = f"{url}{type}?maxIndex={page*20}"
resp = requests.get(_url)
return resp.json()["books"]

为了提高采集的效率,我创建了一个线程池用于批量执行采集任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from itertools import product
with ThreadPoolExecutor(max_workers=10) as executor:
pages = list(
executor.map(
fetch_page,
product(
[700001, 700002, 700003, 700004, 700005, 700006, 700007],
range(1, 25),
),
)
)

books = []
for page in pages:
books.extend(page)

最终的执行时间是 11 秒,相当不错的速度。

数据处理

参考教程里的流程,我使用 pandas 进行了初步的数据处理,并用 sentence-transformers 库生成书籍描述的向量表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sentence_transformers import SentenceTransformer
import numpy as np
import pandas as pd

model = SentenceTransformer(
"paraphrase-multilingual-MiniLM-L12-v2", device="cuda"
)

df = pd.DataFrame([book['bookInfo'] for book in books])

vectors = model.encode(
[row.title + ". " + row.author + "." + row.intro for row in df.itertuples()],
show_progress_bar=True,
)

vectors.shape # (3244, 384)

np.save("wx_books.npy", vectors, allow_pickle=False)
Read more »

近期在研究开源的 rust 实现的向量数据库 qdrant。顾名思义,向量数据是用于存储和查询向量的数据库,而向量本质上是一个多维空间中的点。如果要用向量数据库处理文本数据,就需要将文本转换为向量表示,机器学习术语叫做文本嵌入(Text Embedding)。

传统的文本嵌入方法是基于统计的,比如 TF-IDF,Word2Vec 等。随着 transformer 架构的出现和发展,基于 transformer 的文本嵌入方法也越来越流行,并且在很多任务上取得了很好的效果。sentence-transformers 就是一个基于 transformer 的文本嵌入工具包,可以用于生成句子的向量表示。

安装 sentence-transformers

安装 pytorch

可以前往 pytorch 官网 根据自己的环境选择合适的安装方式,我的设备是 RTX 2060s 的 Windows PC,安装了 windows 的 cuda 版本。

1
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

安装好 pytorch 后可以验证一下。

1
2
3
4
5
6
7
8
9
10
11
import torch

# Check if PyTorch is installed
print("PyTorch version:", torch.__version__)

# Check if CUDA is available
print("CUDA available:", torch.cuda.is_available())


x = torch.rand(5, 3)
print(x)

安装 sentence-transformers

sentence-transformers 可以直接使用 pip 安装。

1
pip3 install sentence-transformers

使用 sentence-transformers

sentence-transformers 提供了很多预训练模型,可以直接使用。我们这里使用的是 paraphrase-multilingual-MiniLM-L12-v2 模型,支持多语言,模型尺寸也比较大(480M)。只处理英文文本的话,可以使用 all-MiniLM-L6-v2 模型(80M)。

1
2
3
4
5
6
7
8
9
10
from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

#Sentences are encoded by calling model.encode()
emb1 = model.encode("This is a red cat with a hat.")
emb2 = model.encode("Have you seen my red cat?")

cos_sim = util.cos_sim(emb1, emb2)
print("Cosine-Similarity:", cos_sim) # Cosine-Similarity: tensor([[0.7097]])

上述代码中,我们使用 sentence-transformers 加载了 paraphrase-multilingual-MiniLM-L12-v2 模型,并使用该模型将两个句子转换为向量表示,然后计算了两个向量的余弦相似度。

余弦相似度是一个常用的相似度度量方法,其值域为 [-1, 1],值越大表示两个向量越相似。其他的相似度度量方法还有欧氏距离、曼哈顿距离等。

Read more »

shuttle 平台简介

shuttle 是一个 PaaS 平台,用于部署 rust 后端服务,支持多个流行的 rust web 框架,如 axum,actix-web、rocket 等。作为对比的话,类似于 heroku(python) 和 deno deploy 平台。

部署 shuttle 服务

将一个现有的 rust web 服务改造并部署到 shuttle 平台上非常简单。拿 axum 框架距离只需要在 cargo.toml 中添加 shuttle-runtime 与 shuttle-axum 依赖,并简单地修改 main 函数即可。

1
2
3
4
5
6
7
#[shuttle_runtime::main]
async fn axum() -> shuttle_axum::ShuttleAxum {

let router = Router::new().route("/", get(hello_world));

Ok(router.into())
}

然后通过命令行工具即可将服务部署到 shuttle 平台上。

1
2
3
4
# cargo install cargo-shuttle ## 安装cargo shuttle
# cargo shuttle login ## 登录shuttle
# cargp shuttle project start ## 创建项目
cargo shuttle deploy # 部署服务

实践

花了一点时间,我将之前开发的二维码生成服务qr-web-rs部署到了 shuttle 平台上,可以通过以下两个链接访问。

总结

shuttle 平台体验相当不错,并且有不少的免费额度,可以用于个人 rust 服务的部署。

前缀树(Trie 树,也称为字典树、单词查找树)是一种树形数据结构,用于高效地存储和检索字符串集合中的键。前缀树的主要优势在于能够快速地查找具有相同前缀的字符串,并且对于大量的字符串集合,它可以提供较高的检索效率。

前缀树的应用非常广泛,包括:

  • 字符串检索:通过前缀树可以快速查找是否存在某个字符串,或者查找具有相同前缀的所有字- 符串。
  • 自动完成:前缀树可以用于实现自动完成功能,根据用户输入的前缀提供可能的建议。
  • IP 路由:在路由表中,前缀树用于快速匹配最长前缀。

前缀树可以通过多种方式实现,在 Python 中最简单且直观的方式是用嵌套的 dict 实现。

首先定义一下前缀树的接口,应该包括 insert、search 和 startswith 三个方法。

1
2
3
4
5
6
7
8
9
10
trie = Trie()
trie.insert("app")
trie.insert("apple")
trie.insert("banana")
assert trie.startswith("a")
assert trie.startswith("ba")
assert not trie.startswith("c")
assert trie.search("app")
assert not trie.search("appl")
assert trie.search("apple")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Trie:
def __init__(self):
self.root = {-1: False}

def insert(self, word: str):
parent = self.root
for ch in word:
node = parent.get(ch, {-1: False})
parent[ch] = node
parent = node
parent[-1] = True

def __find(self, word:str):
node = self.root
for ch in word:
if ch not in node:
return None
node = node[ch]
return node

def search(self, word: str) -> bool:
node = self.__find(word)
return node is not None and node[-1]

def startswith(self, prefix: str) -> bool:
node = self.__find(prefix)
return node is not None
Read more »

我在最近的前端项目中用到了 vite 与 swc 作为构建工具和 JavaScript 编译器,二者都比较年轻,但提供更优秀的前端开发体验。当下前端生态的新选手层出不穷,本文就简单介绍一下最近几年前端生态涌现的诸多新兴的技术与工具。

Javascript 运行时

Deno

Deno是一个使用 rust 开发,基于 V8 引擎的简单、现代、安全的 Javascript 和 TypeScript 运行时。
我在 2021 年的时候写过一篇文章介绍deno。最近两年 Deno 的发展比较迅速,引入了一些新的 API 与能力,提高了稳定性以及与 Node.js 的兼容性。
顺便提一下Deno Deploy平台,可以方便地部署 Deno 项目为 severless 应用。

Bun

Bun是一个使用 zig 语言开发的基于 JavaScriptCore 引擎的全功能 JavaScript 和 TypeScript 工具集。

JavaScript 编译器与打包器

SWC

SWC是一个可扩展的基于 Rust 的下一代快速 Web 开发工具,同时支持 JavaScript 的编译与打包。Deno 和 Next.js 都使用了 SWC 作为编译器。

esbuild

esbuild是一个极快的 web 打包工具,使用 go 语言编写。

bun bundler

bun 对 JavaScript 模块的打包有原生的支持(官方的 benchmark 中比 esbuild 要快很多)。

Node.js 版本管理工具

volta

volta是一个使用 Rust 开发的新兴的 Node.js 版本管理工具。我目前正在使用 volta 管理 Node.js 版本,可以简单地根据项目切换 Node.js(包括 npm 与 yarn)的版本,安装不同版本的 Node.js 也很快。

fnm

fnm是一个快速、简单的 Node.js 版本管理工具,使用 Rust 开发。

Node.js 包管理工具

yarn v2+

新版本yarn做了很多改进,比如使用 Plug'n'Play 模式进行依赖项解析,提高性能与可靠性;支持零安装模式;对 monorepo 的支持更好等。

pnpm

pnpm是一个快速、节省空间的包管理器。

构建工具

parcel

parcel是一个快速、零配置的 Web 应用程序打包工具,支持 JavaScript、TypeScript、CSS、HTML、JSON 等文件的打包。parcel 2.0 版本使用了 swc 作为编译器,速度快了很多。

vite

vite是一个新兴的前端构建工具,在开发时使用 HMR 技术实现秒级的热更新,编译时使用 esbuild 和 Rollup 进行编译与打包,速度非常快。vite 也是 vue3 的官方构建工具。

tuborpack

tuborpack是一个使用 rust 开发的针对 JavaScript 和 TypeScript 优化的渐进式打包工具,由 Webpack 的原作者开发。

前端框架(React.js 生态)

next.js

next.js是一个 React 框架,提供了 SSR、SSG、CSR 等多种渲染模式,同时提供了很多优秀的功能,比如图片优化、代码分割、预取、预加载、路由、静态资源优化等。

Remix

Remix是一个全栈 web 框架,支持 SSR 与 CSR。

最近因工作需要,需要开发一个后台管理系统。考虑到开发效率和技术成熟度,决定使用 React 和 Ant Design 的技术栈。

之前我一直使用 create-react-app 来创建 React 项目,但是 create-react-app 已经很久没有更新了,新版的react 文档也不再推荐使用 create-react-app,而是推荐使用更成熟的框架,比如 next.js 与 remix。next.js 与 remix 都是比较大的 SSR 框架,对于简单的后台管理系统来说,可能有点大材小用。

作为一个新兴的前端构建工具,vite 的成熟度日益提升,对于 React.js 的支持也已经很完善,所以我决定使用 vite 来开发这个后台管理系统。

Read more »

最近获取到了腾讯云的的 Serveeless Stable Difussion 的内测资格,部署一个无服务器版本的 SD Web UI,总体体验比较良好。

部署流程

获取内测资格

首先需要获取内测资格,可以通过这个链接申请。

创建 Serverless 应用

访问Serverless 应用控制台,点击“新建应用”。

create serverless app

选择应用模板

在应用市场分类下选择“AI 绘画(Stable Diffusion)应用”模板,填写应用名称,点击“下一步。”。

choose app template

应用配置

填写应用名称,选择地域,点击“完成”,等待应用创建完成。

cofig app

deploy log

体验 SD Web UI

serverless 应用创建完成后,可以在应用详情页查看应用的访问地址。

app url

sd web ui

图片生成速度还是很快的,不弱于 Google Colab 的体验。以下是使用默认提示词和配置生成的赛博朋克主题的城堡图片。

sample image

总结

在公有云 GPU 机器价格高昂的环境下,使用 serverless 无服务器应用部署 AI 应用或许是一个不错的选择。

Python 是一门动态类型语言,没有编译器对变量类型正确性的检查与保证,这也意味着经常需要在运行时对变量的类型进行校验,尤其是在后端接口开发中,毕竟前端传入的数据往往是不可控的。

Python 3.5 引入了类型注解与 typing 模块,可以对 Python 代码进行静态类型检查,很大程度上提高了代码的可读性与可维护性,尤其是在较大的项目中。

除了静态类型检查,Python 的类型注解也可以在应用在运行时,例如 FastAPI(Pydantic) 就是利用了类型注解来进行请求参数解析、数据校验和 OpenAPI 文档生成的。

实际上 Python 标准库提供了一个简单的运行时类型检查的能力。

使用 isinstance 进行类型检查

isinstance 函数最常见的用法是判断一个对象是否是某个类型(及其子类)的实例,例如:

1
2
3
isinstance(1, int)  # True
isinstance("hello", str) # True
isinstance(None, object) # True

使用isinstance检查抽象类型

到了类型注解的时代,我们可以使用 isinstance 来检查一个对象是否实现了某个抽象接口,例如:

1
2
3
from typing import Callable, Iterable
isinstance(print, Callable) # True
isinstance([1, 2, 3], Iterable) # True

有一些遗憾的是,这里并不能为抽象类型添加范型参数(毕竟对容器的每个元素进行类型检查是一个非常耗时的事情),例如:

1
isinstance([1,2,3], list[int]) # TypeError: isinstance() argument 2 cannot be a parameterized generic

使用 isinstance 检查 typing.Protocol 自定义类型

Python 3.8 引入了 typing.Protocol 类与typing.runtime_checkable装饰器,可以用来定义类型,然后在运行时对对象进行类型检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from typing import Protocol, runtime_checkable
from dataclasses import dataclass

@runtime_checkable
class HasName(Protocol):
name: str

def say_hello(obj: HasName) -> None:
assert isinstance(obj, HasName), "obj must have a name attribute"
print(f"Hello {obj.name}")

@dataclass
class Person:
name: str

@dataclass
class Dog:
nick: str


say_hello(Person("John")) # Hello John
say_hello(Dog("Bobby")) # AssertionError: obj must have a name attribute

总结

虽然会带来一定的性能损耗,但是运行时的类型检查在很多场景下都是必要的,isinstance 函数可以帮助我们实现这一目的。
对于更复杂的类型检查,可以借助 dataclass 或者 pydantic。
值得一提的是 pydantic 的 2.0 版本使用 rust 上线了核心的数据校验逻辑,性能上有了很大的提升。

今天在自己的 PC 上部署和体验了ChatGLM-6B的推理服务,简单记录一下流程。

web ui

ChatGLM-6B 简介

ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型,基于 General Language Model (GLM) 架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级的显卡上进行本地部署(INT4 量化级别下最低只需 6GB 显存)。
ChatGLM-6B 使用了和 ChatGPT 相似的技术,针对中文问答和对话进行了优化。经过约 1T 标识符的中英双语训练,辅以监督微调、反馈自助、人类反馈强化学习等技术的加持,62 亿参数的 ChatGLM-6B 已经能生成相当符合人类偏好的回答,更多信息请参考我们的博客

Read more »