2024-12-16 22:30:42
我在 Obsidian 中管理 Newsletter 特别是文章发布之前经常需要花很长的时间在网络上搜索与文章主题匹配的图片作为文章的 featured image,同时还需要考虑版权、匹配度等等问题,如果 AI 能自动帮我完成这件事情那就太好了,顺着这个思路我发现 wordpress 已经支持类似的功能了,唯一的缺点就是得花钱🤪。于是我就想着在 personal assistant 插件中支持这个功能,这篇文章就介绍一下我是怎么在 Obsidian 插件中设计和构建这个能力的。
基于众所周知的原因,ChatGPT、Claude、Stable Diffusion、DALL-E 这些最优秀的 AI 服务,在国内基本上是没发正常访问的,所以目标对象肯定是国内可用的大模型服务,同一个大模型服务既要支持文本生成,又要支持图片生成,通义千问服务可能唯一能满足我要求的了。我想这在公有云领域,这就是所谓的「Vendor Lock-in」。
personal assistant 基于通义千问大模型的 featured image 生成的工作流程如下:
使用 AI 生成 featured image 的步骤:
效果如下所示:
2024-12-08 19:39:43
作为人类,我们可以阅读并理解文本(至少可以理解一部分),然而计算机“用数字思考”,因此它们无法自动理解单词和句子的含义。如果我们要让计算机理解自然语言,就需要将这些信息转换成计算机可以处理的格式——数字向量。这篇文章就研究一下科学家是如何一步一步让计算机理解和认识人类的语言的。
人们早在多年前就学会了如何将文本转换为机器可理解的格式(最早的版本之一是ASCII)。这种方法有助于渲染和传输文本,但并不编码单词的意义。当时,标准的搜索技术是关键词搜索,即仅查找包含特定单词或N元语法的所有文档。
然后,在几十年后,嵌入式表示(embeddings)出现了。我们可以计算单词、句子甚至图像的嵌入式表示。嵌入式表示也是数字向量,但它们能够捕捉意义。因此,你可以使用它们进行语义搜索,甚至处理不同语言的文档。
将文本转换为向量的最基本方法是词袋模型。让我们以理查德·P·费曼的一句名言为例:“我们很幸运生活在一个我们仍在不断发现的时代”。我们将使用这句话来说明词袋模型的方法。
获得词袋向量的第一步是将文本拆分为单词(标记),然后将单词还原为其基本形式。例如,“running”将转换为“run”。这个过程称为词干提取(stemming,即将文本拆分为单词并进行词干提取)。
如下代码所示,用 NLTK 实现词袋模型:
from nltk.stem import SnowballStemmer
from nltk.tokenize import word_tokenize
import nltk
import os
# Resource punkt_tab not found. Try using the NLTK Downloader
if not os.path.exists("./tokenizers/punkt_tab.zip"):
nltk.download("punkt_tab", download_dir="./")
text = "We are lucky to live in an age in which we are still making discoveries"
# tokenization - splitting text into words
words = word_tokenize(text)
print(words)
# ['We', 'are', 'lucky', 'to', 'live', 'in', 'an', 'age', 'in', 'which',
# 'we', 'are', 'still', 'making', 'discoveries']
stemmer = SnowballStemmer(language="english")
stemmed_words = list(map(lambda x: stemmer.stem(x), words))
print(stemmed_words)
# ['we', 'are', 'lucki', 'to', 'live', 'in', 'an', 'age', 'in', 'which',
# 'we', 'are', 'still', 'make', 'discoveri']
import collections
bag_of_words = collections.Counter(stemmed_words)
print(bag_of_words)
# {'we': 2, 'are': 2, 'in': 2, 'lucki': 1, 'to': 1, 'live': 1,
# 'an': 1, 'age': 1, 'which': 1, 'still': 1, 'make': 1, 'discoveri': 1}
实际上,如果我们想将文本转换为向量,不仅要考虑文本中的单词,还要考虑整个词汇表。假设我们的词汇表中还包括“i”、“you”和“study”,我们就可以从费曼的这句名言创建一个向量。
# bag of words vector
{'we': 2, 'are': 2, 'in': 2, 'lucki': 1, 'to': 1, 'live': 1,'an': 1, 'age': 1, 'which': 1, 'still': 1, 'make': 1, 'discoveri': 1, 'i': 0, 'you': 0, 'study': 0}
这种方法相当基础,它不考虑单词的语义意义,因此句子“the girl is studying data science”和“the young woman is learning AI and ML”在向量空间中不会接近彼此。
词袋模型的一个稍作改进的版本是TF-IDF(Term Frequency — Inverse Document Frequency,词频-逆文档频率)。它是两个度量值的乘积。
$$TF-IDF(t,d,D) = TF(t,d) \times IDF(t, D) $$
最终,我们会得到一些向量,其中常见的词(如“I”或“you”)具有较低的权重,而多次出现在文档中的罕见词则具有较高的权重。这种策略会带来稍微更好的结果,但它仍然无法捕捉语义意义。
这种方法的另一个挑战是它会产生非常稀疏的向量。向量的长度等于语料库的大小。英语中大约有47万独特的单词(来源),因此我们将得到巨大的向量。由于一个句子通常不会超过50个不同的词,因此向量中99.99%的值将是0,不携带任何信息。鉴于这一点,科学家们开始思考密集向量表示。
词语潜在信息(Latent Information)与神经网络方法(Neural Network Method)
英文语境下,当我们谈论狗(dog)时,可能会使用除了“dog”之外的其他词语。我们是否应该在我们的分类方案中考虑像“canine”(犬类)或“feline”(猫科)这样的术语?我们需要为这些词添加一个新的项,例如 (dogx, caty, caninez, felinei)。
在英语中,一个大约30,000个词的词汇表对于这种词袋模型来说效果很好。在计算机世界中,我们可以比实体图书馆更平滑地扩展这些维度,但原则上问题类似。在高维度下,事情变得难以管理。随着组合爆炸,算法运行缓慢,稀疏性(大多数文档对大多数术语的计数为0)对统计和机器学习构成了问题。
因此,要将一本书投影到潜在空间(latent space)中,我们需要一个大矩阵,定义词汇表中每个观察到的术语对每个潜在术语的贡献程度。
有几个不同的算法可以从足够大的文档集合中推断出这一点:潜在语义分析(Latent Semantic Analysis, LSA),它使用术语-文档矩阵的奇异值分解(基本上是高级线性代数),以及潜在狄利克雷分配(Latent Dirichlet Allocation, LDA),它使用一种称为狄利克雷过程的统计方法。
我们使用词频作为某种更为模糊的主题性的代理。通过将这些词频投影到嵌入空间中,我们不仅可以降低维度,还可以推断出比原始词频更好地指示主题性的潜在变量。为此,我们需要一个定义良好的算法,如LSA,它可以处理文档语料库,找到从词袋输入到嵌入空间向量的良好映射。基于神经网络的方法使我们能够推广这一过程,并突破LSA的限制。
最著名的密集表示方法之一是word2vec,这是谷歌在2013年提出的,论文标题为“Efficient Estimation of Word Representations in Vector Space”,作者是Mikolov等人。
在这篇论文中提到了两种不同的word2vec方法:
这两种方法都能生成词的密集向量表示,这些向量不仅包含了词的频率信息,还捕捉了词之间的语义关系。例如,使用word2vec生成的向量可以反映出“king”和“queen”之间的关系类似于“man”和“woman”之间的关系。
word2vec的成功在于它能够有效地将高维的词袋模型转化为低维的密集向量,同时保留了词的语义信息。这使得在自然语言处理任务中,如语义搜索、情感分析和机器翻译等,可以更高效地使用这些向量。
高维词向量的密集表示的核心思想是训练两个模型:编码器(encoder)和解码器(decoder)。以skip-gram模型为例,我们可以将单词“christmas”传递给编码器。编码器会生成一个向量,然后将这个向量传递给解码器,期望解码器能够输出“merry”、“to”和“you”等上下文单词。
具体步骤如下:
训练过程:
通过这种方式,skip-gram模型能够生成高质量的词嵌入,这些嵌入不仅保留了词的频率信息,还捕捉了词之间的语义关系,为各种自然语言处理任务提供了强大的支持。这个模型开始考虑单词的意义,因为它是在单词的上下文中进行训练的。然而,它忽略了形态学(我们从单词部分可以获得的信息,例如,“-less”意味着缺乏某物)。这一缺点后来通过查看子词skip-grams在GloVe中得到了解决。
此外,word2vec只能处理单词,但我们希望对整个句子进行编码。因此,让我们进入下一个演进步骤,即transformers。
下一次演变与 Vaswani 等人在论文“Attention is All You Need”中引入的变压器方法有关。变压器能够生成信息丰富的密集向量,并成为现代语言模型的主要技术。
变压器允许使用相同的“核心”模型,并针对不同的用例进行微调,而无需重新训练核心模型(这需要大量时间和成本)。这导致了预训练模型的兴起。其中一个早期流行的模型是Google AI的BERT(基于变压器的双向编码器表示)。
内部而言,BERT仍然像word2vec一样在词元级别上操作,但我们仍然希望获得句子嵌入。因此,一个简单的方法可能是取所有词元向量的平均值。不幸的是,这种方法的表现不佳。
这个问题在2019年随着Sentence-BERT的发布得到了解决。Sentence-BERT在语义文本相似性任务上超越了所有先前的方法,并允许计算句子嵌入。
我使用阿里云大模型服务的text-embedding-v2来生成文本嵌入向量。结果,我们得到了一个1536维的浮点数向量。现在我们可以对所有数据重复这一过程,并开始分析这些值。
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv("./env/.env"))
import dashscope
from http import HTTPStatus
from pprint import pprint
resp = dashscope.TextEmbedding.call(
model=dashscope.TextEmbedding.Models.text_embedding_v2,
input="We are lucky to live in an age in which we are still making discoveries.",
dimension=1536,
)
pprint(resp['output']) if resp.status_code == HTTPStatus.OK else print(resp)
# output
# {'embeddings': [{'embedding': [0.022378576171554372,
# -0.027432455162420308,
# -0.00355793080956962,
# -0.030121118785560987,
# ...
# ],
# 'text_index': 0}]}
嵌入实际上是向量。因此,如果我们想了解两个句子之间的相似程度,可以计算它们之间向量的距离。距离越小,表示它们的语义意义越接近。
可以使用不同的度量来测量两个向量之间的距离:
定义两点(或向量)之间距离的最标准方法是欧几里得距离或L2范数。这种度量在日常生活中最常用,例如,当我们谈论两个城镇之间的距离时。
以下是L2距离的视觉表示和公式:
$$\text{L2距离} = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2 + \cdots + (z_2 - z_1)^2}$$
另一种常用的距离是L1范数或曼哈顿距离。这种距离是以纽约的曼哈顿岛命名的。该岛的街道布局呈网格状,两个点之间的最短路径将是L1距离,因为需要沿着网格行走。
$$\text{L1距离} = \sum_{i=1}^{n} |x_i - y_i|$$
另一种查看向量之间距离的方法是计算点积或标量积。以下是公式,我们可以轻松实现它:
$$\text{点积} = \vec{a} \cdot \vec{b} = |\vec{a}||\vec{b}|cos\theta= \sum_{i=1}^{n} a_i b_i$$
这种度量的解释有点棘手。一方面,它显示了向量是否指向同一方向。另一方面,结果高度依赖于向量的大小。例如,让我们计算两对向量之间的点积:
在这两种情况下,向量都是共线的,但在第二种情况下,点积大十倍:2 对 20。
余弦相似度经常被使用。余弦相似度是点积除以向量的模长(或范数)的归一化结果。
让我们谈谈这种度量的物理意义。余弦相似度等于两个向量之间的夹角的余弦值。向量越接近,度量值越高。
$$\text{余弦相似度} = \frac{\vec{a} \cdot \vec{b}}{|\vec{a}| |\vec{b}|}$$
你可以使用任何距离来比较你得到的嵌入。例如,我计算了不同聚类之间的平均距离。无论是L2距离还是余弦相似度,都展示了类似的结果:
然而,对于NLP任务,最佳实践通常是使用余弦相似度。背后的原因包括:
理解数据的最佳方式是将其可视化。不幸的是,嵌入有1536个维度,因此直接查看数据非常困难。然而,有一种方法:我们可以使用降维技术将向量投影到二维空间中。
注意: 以下可视化数据来自 Stack Exchange Data Dump,由于数据量是92.3G,我就没有自己本地跑可视化结果,本文可视化视图来自 Mariya Mansurova 的Text Embeddings: Comprehensive Guide。
最基本的降维技术是主成分分析(PCA)。
PCA是一种线性算法,而在现实生活中大多数关系是非线性的。因此,由于非线性问题,我们可能无法很好地分离聚类。让我们尝试使用一种非线性算法——t-SNE,看看它是否能显示出更好的结果。
来源内容:
Is it safe to drink the water from the fountains found all over
the older parts of Rome?
When I visited Rome and walked around the older sections, I saw many
different types of fountains that were constantly running with water.
Some went into the ground, some collected in basins, etc.
Is the water coming out of these fountains potable? Safe for visitors
to drink from? Any etiquette regarding their use that a visitor
should know about?
t-SNE嵌入:
我们可以在t-SNE可视化中找到这条内容,并发现它实际上靠近咖啡聚类。
这句话的意思是,在t-SNE可视化中,这条关于罗马喷泉水的问题被映射到了一个与咖啡相关的话题附近。这可能表明,尽管这两者看似不相关,但它们在某些方面存在一定的语义相似性,或者在数据集中它们经常一起出现。
2024-11-30 23:16:35
Vector embeddings are a method to convert non-structured data, such as text, images, and videos, into numerical representations that capture their meanings and relationships. This allows computers, which only understand numbers, to process and interpret these data more effectively. Embeddings are crucial for tasks like semantic similarity and are used in various AI models, including LLMs, RAG, and multimodal processing.
vector embeddings 的出现最本质的原因是科学家为了解决一个问题 —— 让只认识0、1数字,只会做逻辑运算和浮点运算的计算机,能够理解人类语言、文字、图像、视频等数据包含的语义以及它们之间的关系。例如,「泰迪」、「狗」、「犬」、「哺乳动物」这几个单词在不同的上下文中其实是同一个意思,即一种四足、有尾巴、有尖牙的哺乳动物,这就是文字、图像、视频中包括的语义信息,科学家希望计算机可以理解这些。
既然计算机只认识数字,只会做运算,科学家的想法很直接,那就我们就将人类使用的文字、句子、文章、书本、图像、视频等等非结构化的数据转换成数字来描述,帮助计算机高效的理解和处理它们,于是就有了 vector embeddings。
vector embeddings(向量嵌入)是一种将单词、句子和其他数据转换为数字的方法,这些数字捕获了它们的含义和关系。它们将不同类型的数据表示为高维空间(多维空间)中的点,其中相似的数据点聚集在一起。这些数值表示有助于机器更有效地理解和处理这些数据。
向量嵌入在处理语义相似度时至关重要。vector(向量)仅仅是一系列数字;vector embedding(向量嵌入)是一系列代表输入数据的数字。通过使用 vector embeddings,我们可以结构化非结构化数据或通过将其转换为一系列数字来处理任何类型的数据。这种方法使我们能够对输入数据执行数学运算,而不是依赖定性比较。
在 vector embeddings (向量嵌入)的上下文中,embedding(嵌入)和 vector(向量)是同一回事。两者都指数据的数值表示,其中每个数据点都由高维空间中的向量表示。
“vector 向量”仅指具有特定维数的数字数组。在 vector embedding(向量嵌入)的情况下,这些向量表示上述任何数据点在一个连续的空间中。
“embedding 嵌入”专门指将数据表示为 vector(向量)的技术,以捕获有意义的信息、语义关系或上下文特征。embedding 嵌入旨在捕获数据的底层结构或属性,通常通过训练算法或模型来学习。
虽然嵌入和向量可以在向量嵌入的上下文中互换使用,“嵌入”强调以有意义和结构化的方式表示数据的概念,而“向量”则指数值表示本身。
vector embeddings 是深度学习模型中输入数据的内部表示,也称为嵌入模型或深度神经网络。那么,我们如何提取这些信息呢?
我们通过移除最后一层并从倒数第二层获取输出,来获得 vector 向量。神经网络的最后一层通常输出模型的预测,因此我们取倒数第二层的输出。vector embedding(向量嵌入)是馈送到神经网络预测层的数据。
vector embedding(向量嵌入)的维数等于模型中倒数第二层的尺寸,因此与向量的尺寸或长度可互换。常见的向量维数包括384(例如 Sentence Transformers Mini-LM 生成的 vector)、768(例如 Sentence Transformers MPNet 生成的 vector)、1,536(例如 OpenAI 生成的 vector)和2,048(例如 ResNet-50 生成的 vector)。
最早的 GPT-2 设置中,头的数量是 12(dimension heads),它可以整除 768。
这个数字来自于超参数优化(hyperparameter optimization)。使用 4096 大小的嵌入和 1 层的神经网络,或者使用 16 大小的嵌入和 2B 参数的神经网络是没有意义的,这些值之间需要一个良好的平衡。
那么为什么是 768 而不是 769 呢?我们通常使用 2 的幂(或接近的值)来尝试超参数,因为它们在计算上更快,并且更适合 GPU 内存分配(就像你的屏幕分辨率一样,GPU 只是一个计算矩阵的大型机器)。768 = 512 + 256 = 2**9 + 2**8
。
我曾经查找资料并试图弄懂 vector embeddings(向量嵌入)中每个维度的含义。最终的答案是,单个维度没有任何意义。vector embeddings 中的单个维度过于抽象,无法确定其含义。然而,当我们将所有维度放在一起时,它们提供了输入数据的语义含义。
向量的维度是不同属性的高级抽象表示。表示的属性取决于训练数据和模型本身。文本和图像模型生成不同的嵌入,因为它们针对根本不同的数据类型进行训练。即使是不同的文本模型也会生成不同的嵌入。有时它们在大小上不同;其他时候,它们在表示的属性上不同。例如,在法律数据上训练的模型将学习与在医疗保健数据上训练的模型不同的事物。
2012 年 AlexNet 的出现标志着图像识别技术的飞跃。自那时以来,计算机视觉领域取得了无数进展。最新的知名图像识别模型是 ResNet-50,它是一个基于前代 ResNet-34 架构的 50 层深度残差网络。
尝试使用 microsoft/resnet-50 · Hugging Face生成图像的 vector embeddings:
# Load model directly
from transformers import AutoFeatureExtractor, AutoModelForImageClassification
from PIL import Image
extractor = AutoFeatureExtractor.from_pretrained("microsoft/resnet-50")
model = AutoModelForImageClassification.from_pretrained("microsoft/resnet-50")
image = Image.open("<image path>")
# image = Resize(size=(256, 256))(image)
inputs = extractor(images=image, return_tensors="pt")
# print(inputs)
outputs = model(**inputs)
vector_embeddings = outputs[1][-1].squeeze()
人工智能对自然语言的处理已经从基于规则的嵌入发展到了一个新的高度。从最初的神经网络开始,我们通过 RNN 添加了递归关系来跟踪时间步长。从那时起,我们使用 Transformer 来解决序列转导问题。
Transformer 由编码器、注意力矩阵和解码器组成。编码器将输入编码为表示状态的矩阵,注意力矩阵和解码器对状态和注意力矩阵进行解码,以预测正确的下一个标记来完成输出序列。GPT 是迄今为止最流行的语言模型,它由严格的解码器组成。它们对输入进行编码并预测正确的下一个标记。
尝试使用 sentence-transformers生成文本的 vector embeddings:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("<model-name>")
vector_embeddings = model.encode(“<input>”)
以图生文为例,使用开源模型 CLIP VIT 来生成 embeddings:
# Load model directly
from transformers import AutoProcessor, AutoModelForZeroShotImageClassification
processor = AutoProcessor.from_pretrained("openai/clip-vit-large-patch14")
model = AutoModelForZeroShotImageClassification.from_pretrained("openai/clip-vit-large-patch14")
from PIL import Image
image = Image.open("<image path>")
# image = Resize(size=(256, 256))(image)
inputs = extractor(images=image, return_tensors="pt")
# print(inputs)
outputs = model(**inputs)
vector_embeddings = outputs[1][-1].squeeze()
以语音生成文字为例,使用开源模型 Whisper 模型来获取 embedding:
import torch
from transformers import AutoFeatureExtractor, WhisperModel
from datasets import load_dataset
model = WhisperModel.from_pretrained("openai/whisper-base")
feature_extractor = AutoFeatureExtractor.from_pretrained("openai/whisper-base")
ds = load_dataset("hf-internal-testing/librispeech_asr_dummy", "clean", split="validation")
inputs = feature_extractor(ds[0]["audio"]["array"], return_tensors="pt")
input_features = inputs.input_features
decoder_input_ids = torch.tensor([[1, 1]]) * model.config.decoder_start_token_id
vector_embedding = model(input_features, decoder_input_ids=decoder_input_ids).last_hidden_state
视频的 embeddings 比语音、图片的 embeddings 更加复杂,它需要多模态的处理来保证语音与图片的同步,以 DeepMind 开源模型 multimodal perceiver 为例生成视频的 vector embeddings(注意代码 outputs[1][-1].squeeze()
):
def autoencode_video(images, audio):
# only create entire video once as inputs
inputs = {'image': torch.from_numpy(np.moveaxis(images, -1, 2)).float().to(device),
'audio': torch.from_numpy(audio).to(device),
'label': torch.zeros((images.shape[0], 700)).to(device)}
nchunks = 128
reconstruction = {}
for chunk_idx in tqdm(range(nchunks)):
image_chunk_size = np.prod(images.shape[1:-1]) // nchunks
audio_chunk_size = audio.shape[1] // SAMPLES_PER_PATCH // nchunks
subsampling = {
'image': torch.arange(
image_chunk_size * chunk_idx, image_chunk_size * (chunk_idx + 1)),
'audio': torch.arange(
audio_chunk_size * chunk_idx, audio_chunk_size * (chunk_idx + 1)),
'label': None,
}
# forward pass
with torch.no_grad():
outputs = model(inputs=inputs, subsampled_output_points=subsampling)
output = {k:v.cpu() for k,v in outputs.logits.items()}
reconstruction['label'] = output['label']
if 'image' not in reconstruction:
reconstruction['image'] = output['image']
reconstruction['audio'] = output['audio']
else:
reconstruction['image'] = torch.cat(
[reconstruction['image'], output['image']], dim=1)
reconstruction['audio'] = torch.cat(
[reconstruction['audio'], output['audio']], dim=1)
vector_embeddings = outputs[1][-1].squeeze()
# finally, reshape image and audio modalities back to original shape
reconstruction['image'] = torch.reshape(reconstruction['image'], images.shape)
reconstruction['audio'] = torch.reshape(reconstruction['audio'], audio.shape)
return reconstruction
return None
2024-11-23 22:12:01
随着 LLM 技术的兴起,日常工作会频繁的碰到 LLM/AI 相关的技术和项目,我所在的 OS Security Team 会研究 AI for System & System for AI 这两个领域,这就被迫需要补充学习 LLM/AI 相关的知识。吴恩达的书《How to Build Your Career in AI》,解决了很多学习 LLM/AI 的疑惑,例如:
这篇文章想分享了吴恩达给予我在学习 AI 这条路上的一些启发和思考。
现如今已经发表的关于 AI 研究的论文,任何人一生能不可能读完。因此在学习时,选择主题至关重要。
理解线性回归、逻辑回归、神经网络、决策树、聚类和异常检测等模型很重要。除了特定的模型之外,理解机器学习工作原理的核心概念更为重要,如偏差/方差、成本函数、正则化、优化算法和误差分析。
深度学习已经是机器学习中非常重要的一部分,以至于没有一定的理解就很难在这个领域取得优异成绩!了解神经网络的基础知识、使它们发挥作用的实际技能(如超参数调整)、卷积网络、序列模型和 transformers 是非常有价值的。
线性代数(向量、矩阵及其各种操作)以及概率和统计(包括离散和连续概率、标准概率分布、基本规则如独立性和贝叶斯法则,以及假设检验)。此外,探索性数据分析(EDA)——使用可视化和其他方法系统地探索数据集——是一项被低估的技能,它在以数据为中心的 AI 开发中特别有用,其中分析错误和获得见解可以真正帮助推动进展!最后,对微积分的基本直观理解也会有所帮助。
虽然仅凭机器学习建模技能就能找到工作并做出巨大贡献,但如果你还能编写良好的软件来实现复杂的 AI 系统,你的就业机会将会增加。这些技能包括编程基础、数据结构(尤其是与机器学习相关的,如数据框)、算法(包括与数据库和数据操作相关的算法)、软件设计、熟悉 Python 以及熟悉关键库如 TensorFlow 或 PyTorch 和 scikit-learn。
BJ Fogg 在他的《Tiny Habits: The Small Changes That Change Everything》中解释说,建立新习惯的最佳方式是从小事做起并取得成功,而不是一开始就定下过高的目标导致失败。例如,与其每天试图锻炼 30 分钟,他建议先设定一个做一次俯卧撑的目标,并坚持去做。
这种方法可能对那些想要花更多时间学习的你们有所帮助。如果你从每天坚持观看,比如说,10 秒的教育视频开始——并且你能够持续这样做——每天学习的习惯就会自然地养成。即使在这 10 秒内你什么都没学到,你也正在建立每天学习一点的习惯。有些日子,也许你会最终学习一个小时或更长时间。
数学是人工智能的基础技能吗?多了解一些数学总是好的!但是要学习的东西太多了,实际上,有必要进行优先排序。以下是如何加强你的数学背景的方法。
为了弄清楚什么是重要的知识,有必要问一问,为了完成你想要做的工作所需的决策,你需要知道什么 —— “为了实现目标,人们需要知道什么?”。
理解你所使用的算法背后的数学通常是有帮助的,因为它使你能够调试它们。但是,有用的知识深度会随着时间而变化。随着机器学习技术的成熟和变得更加可靠和即用,它们需要较少的调试,对涉及的数学有较浅的理解可能就足以使它们工作。
例如,在机器学习的早期,用于求解线性方程组(用于线性回归)的线性代数库还不成熟。我必须了解这些库的工作原理,以便在不同的库之间做出选择并避免数值舍入误差。但随着数值线性代数库的成熟,这一点变得不那么重要了。
深度学习仍然是一项新兴技术,因此当你训练神经网络时,如果优化算法难以收敛,理解梯度下降、动量和 Adam 优化算法背后的数学原理将帮助你做出更好的决策。同样地,如果你的神经网络表现异常——比如,在某些分辨率的图像上预测效果差,而在其他图像上则不然——理解神经网络架构背后的数学原理会使你处于更有利的位置来解决问题。
当然,我也鼓励由好奇心驱动的学习。如果某件事让你感兴趣,不妨去学习它,不管它可能有多么实用!也许这会激发你的创造力或技术突破。
找到一个领域专家并询问:“你希望哪些事情能够变得更好?为什么这些事情还没有得到解决?”。例如,如果你想将 AI 应用于气候变化,你可能会发现电网运营商无法准确预测未来风能和太阳能等间歇性能源可能产生的电力。
不要因为一个想法令人兴奋就立马去执行,有时可能存在一个不需要更多努力就能实现的更好想法。一旦理解了问题,就可以更有效地头脑风暴潜在的解决方案,有时没有好的 AI 解决方案那也没关系。
可以通过查看已发表的论文、竞争对手的做法,或者也许构建一个快速的概念验证实现来确定一种方法在技术上是否可行,也可以通过咨询领域专家来确定其价值。
一旦你认为一个项目足够有价值,下一步就是确定要追求的指标。这包括机器学习指标(如准确率)和业务指标(如收入)。机器学习团队通常最擅长优化学习算法的指标。但我们可能需要走出舒适区,提出与用户参与度、收入等相关的业务指标。不幸的是,并不是每个业务问题都能归结为优化测试集的准确率!如果你无法确定合理的里程碑,这可能是一个信号,表明你需要更多地了解这个问题。一个快速的概念验证可以帮助提供缺失的视角。
考虑项目所需的一切,包括数据、人员、时间以及可能需要的其他团队的任何集成或支持。
2024-11-17 00:16:46
近期阿里云双十一活动 RDS 99/年,非常有性价比,于是我就买了一个香港区的 MySQL RDS 用做我的 Memos 数据服务。这就不可避免的碰到了数据库迁移的问题,即需要将 SQLite 数据库迁移到 MySQL,这种生产环境的数据库迁移一般都非常谨慎,需要严格 SQL 开发和验证,需要花费不少的时间。由于 SQLite 文件有备份,刚刚购买的 MySQL 资源充足,心血来潮之下想玩一波大的 —— 试试 AI 来帮我完成生产数据库的迁移工作。
我完成这些事情是 prompt tune 的思路,主要包括:
这里主要是确认当前生产数据创建了哪些库表,有两种方法:
show create table ${table name}
CREATE DATABASE IF NOT EXISTS memos-migrate-test;
version: '3'
services:
db:
image: mysql:latest
ports:
- 3306:3306
restart: always
environment:
MYSQL_ROOT_PASSWORD: your_root_password
volumes:
- ./data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
docker pull mysql
docker compose up -d
I have a notes service and the data is stored on sqlite database, now i want to migrate all the data to mysql, please help me to write a script in python. Here is the schema definition of the sqlite database
<<<LATEST.sql>>>
注意:替换 LATEST.sql 的内容。
当前具备 code 分析能力的 LLM 均可以用来做迁移工具的 prompt tune,由于 Claude 在代码分析和生成等领域有非常不错的口碑,所以我这次使用了 Claude 3.5 Sonnet 作为调优模型。
Here's a Python script that can help you migrate data from SQLite to MySQL based on the schema you provided:
\`\`\`python
import sqlite3
import mysql.connector
import json
# SQLite connection
sqlite_conn = sqlite3.connect('path_to_your_sqlite_database.db')
sqlite_cursor = sqlite_conn.cursor()
# MySQL connection
mysql_conn = mysql.connector.connect(
host="your_mysql_host",
user="your_mysql_user",
password="your_mysql_password",
database="your_mysql_database"
)
mysql_cursor = mysql_conn.cursor()
# List of tables to migrate
tables = [
"migration_history", "system_setting", "user", "user_setting", "memo",
"memo_organizer", "memo_relation", "resource", "activity", "idp",
"inbox", "webhook", "reaction"
]
for table in tables:
print(f"Migrating table: {table}")
# Fetch data from SQLite
sqlite_cursor.execute(f"SELECT * FROM {table}")
rows = sqlite_cursor.fetchall()
if not rows:
print(f"No data in table {table}")
continue
# Get column names
columns = [description[0] for description in sqlite_cursor.description]
# Prepare MySQL insert query
placeholders = ', '.join(['%s'] * len(columns))
columns_string = ', '.join(columns)
insert_query = f"INSERT INTO {table} ({columns_string}) VALUES ({placeholders})"
# Insert data into MySQL
for row in rows:
# Convert any JSON fields
processed_row = []
for value, column in zip(row, columns):
if column in ['tags', 'payload']:
try:
processed_value = json.dumps(json.loads(value))
except:
processed_value = value
else:
processed_value = value
processed_row.append(processed_value)
try:
mysql_cursor.execute(insert_query, processed_row)
except mysql.connector.Error as err:
print(f"Error inserting into {table}: {err}")
continue
mysql_conn.commit()
print(f"Migrated {len(rows)} rows for table {table}")
# Close connections
sqlite_conn.close()
mysql_conn.close()
print("Migration completed")
\`\`\`
Before running this script, make sure to:
1. Install required packages: `pip install mysql-connector-python`
2. Replace `'path_to_your_sqlite_database.db'` with the actual path to your SQLite database file.
3. Update the MySQL connection details (host, user, password, database) with your actual MySQL server information.
This script does the following:
1. Connects to both SQLite and MySQL databases.
2. Iterates through each table in the given list.
3. Fetches all data from the SQLite table.
4. Prepares an insert query for MySQL.
5. Inserts the data into the corresponding MySQL table, handling JSON fields if necessary.
6. Commits the changes and moves to the next table.
Note that this script assumes that you have already created the tables in MySQL with the same schema as in SQLite. If you haven't, you'll need to create the tables in MySQL first using the CREATE TABLE statements you provided.
Also, be aware that this script does not handle auto-increment fields specially. If you have auto-increment fields and want to preserve the exact IDs from SQLite, you may need to modify the script to handle this (e.g., by temporarily disabling auto-increment in MySQL during the migration).
prompt tuning 就是在测试环境中执行 Claude 生成的工具,并将测试的报错内容反馈给 Claude 让它继续进行优化调整,并重新生成迁移工具。
测试初始化 prompt 步骤中的迁移工具,发现有如下报错:
LATEST.sql
)进行测试发现有时间戳相关报错:
migrate error: Incorrect datetime value: '1698320364' for column 'created_ts'
I apologize for the oversight. The error you're encountering is due to a difference in how SQLite and MySQL handle datetime values. SQLite often stores timestamps as Unix timestamps (seconds since epoch), while MySQL expects a formatted datetime string.
To resolve this issue, we need to convert the Unix timestamp to a datetime format that MySQL can understand. Here's an updated version of the script that handles this conversion:
\`\`\`python
import sqlite3
import mysql.connector
import json
from datetime import datetime
# SQLite connection
sqlite_conn = sqlite3.connect('path_to_your_sqlite_database.db')
sqlite_cursor = sqlite_conn.cursor()
# MySQL connection
mysql_conn = mysql.connector.connect(
host="your_mysql_host",
user="your_mysql_user",
password="your_mysql_password",
database="your_mysql_database"
)
mysql_cursor = mysql_conn.cursor()
# List of tables to migrate
tables = [
"migration_history", "system_setting", "user", "user_setting", "memo",
"memo_organizer", "memo_relation", "resource", "activity", "idp",
"inbox", "webhook", "reaction"
]
# List of timestamp columns
timestamp_columns = ['created_ts', 'updated_ts']
def convert_timestamp(value):
try:
return datetime.fromtimestamp(int(value)).strftime('%Y-%m-%d %H:%M:%S')
except ValueError:
return value
for table in tables:
print(f"Migrating table: {table}")
# Fetch data from SQLite
sqlite_cursor.execute(f"SELECT * FROM {table}")
rows = sqlite_cursor.fetchall()
if not rows:
print(f"No data in table {table}")
continue
# Get column names
columns = [description[0] for description in sqlite_cursor.description]
# Prepare MySQL insert query
placeholders = ', '.join(['%s'] * len(columns))
columns_string = ', '.join(columns)
insert_query = f"INSERT INTO {table} ({columns_string}) VALUES ({placeholders})"
# Insert data into MySQL
for row in rows:
# Convert any JSON fields and timestamps
processed_row = []
for value, column in zip(row, columns):
if column in ['tags', 'payload']:
try:
processed_value = json.dumps(json.loads(value))
except:
processed_value = value
elif column in timestamp_columns:
processed_value = convert_timestamp(value)
else:
processed_value = value
processed_row.append(processed_value)
try:
mysql_cursor.execute(insert_query, processed_row)
except mysql.connector.Error as err:
print(f"Error inserting into {table}: {err}")
continue
mysql_conn.commit()
print(f"Migrated {len(rows)} rows for table {table}")
# Close connections
sqlite_conn.close()
mysql_conn.close()
print("Migration completed")
\`\`\`
The main changes in this updated script are:
1. We've added a list of `timestamp_columns` that need to be converted.
2. We've added a `convert_timestamp` function that converts Unix timestamps to MySQL datetime format.
3. In the data processing loop, we now check if a column is in the `timestamp_columns` list and convert it if necessary.
This script should handle the timestamp conversion correctly. If you encounter any other issues or if some tables have different datetime formats, you may need to adjust the `convert_timestamp` function or add more specific handling for certain tables or columns.
Also, make sure that your MySQL tables are set up to accept NULL values for timestamp columns if applicable, as some of your SQLite data might contain NULL values for these fields.
再次进行测试发现有保留关键字导致语法相关的报错:
Error inserting into user_setting: 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'key, value) VALUES (1, 'USER_SETTING_ACCESS_TOKENS', '{\"accessTokens\":[{\"acce' at line 1
I apologize for the oversight. The error is occurring because 'blob' and 'type' are also reserved keywords in MySQL. We need to escape these column names as well. Here's an updated version of the script that handles this issue:
\`\`\`python
import sqlite3
import mysql.connector
import json
from datetime import datetime
# SQLite connection
sqlite_conn = sqlite3.connect('path_to_your_sqlite_database.db')
sqlite_cursor = sqlite_conn.cursor()
# MySQL connection
mysql_conn = mysql.connector.connect(
host="your_mysql_host",
user="your_mysql_user",
password="your_mysql_password",
database="your_mysql_database"
)
mysql_cursor = mysql_conn.cursor()
# List of tables to migrate
tables = [
"migration_history", "system_setting", "user", "user_setting", "memo",
"memo_organizer", "memo_relation", "resource", "activity", "idp",
"inbox", "webhook", "reaction"
]
# List of timestamp columns
timestamp_columns = ['created_ts', 'updated_ts']
# List of reserved keywords that need to be escaped
reserved_keywords = ['key', 'value', 'blob', 'type']
def convert_timestamp(value):
try:
return datetime.fromtimestamp(int(value)).strftime('%Y-%m-%d %H:%M:%S')
except ValueError:
return value
def escape_column_names(columns):
return [f"`{column}`" if column.lower() in reserved_keywords else column for column in columns]
for table in tables:
print(f"Migrating table: {table}")
# Fetch data from SQLite
sqlite_cursor.execute(f"SELECT * FROM {table}")
rows = sqlite_cursor.fetchall()
if not rows:
print(f"No data in table {table}")
continue
# Get column names
columns = [description[0] for description in sqlite_cursor.description]
# Escape column names if necessary
escaped_columns = escape_column_names(columns)
# Prepare MySQL insert query
placeholders = ', '.join(['%s'] * len(columns))
columns_string = ', '.join(escaped_columns)
insert_query = f"INSERT INTO {table} ({columns_string}) VALUES ({placeholders})"
# Insert data into MySQL
for row in rows:
# Convert any JSON fields and timestamps
processed_row = []
for value, column in zip(row, columns):
if column in ['tags', 'payload']:
try:
processed_value = json.dumps(json.loads(value))
except:
processed_value = value
elif column in timestamp_columns:
processed_value = convert_timestamp(value)
else:
processed_value = value
processed_row.append(processed_value)
try:
mysql_cursor.execute(insert_query, processed_row)
except mysql.connector.Error as err:
print(f"Error inserting into {table}: {err}")
print(f"Problematic row: {processed_row}")
continue
mysql_conn.commit()
print(f"Migrated {len(rows)} rows for table {table}")
# Close connections
sqlite_conn.close()
mysql_conn.close()
print("Migration completed")
\`\`\`
The main changes in this updated script are:
1. We've added 'blob' and 'type' to the list of reserved keywords that need to be escaped.
2. The `escape_column_names` function now checks against this expanded list of reserved keywords.
3. I've added a line to print the problematic row when an error occurs, which can help with debugging if you encounter any other issues.
This change should resolve the SQL syntax error you encountered with the `resource` table. The script now properly escapes the 'blob' and 'type' column names, which should allow the insertion into the `resource` table to proceed without errors.
If you encounter any other issues, please let me know, and I'll be happy to help further. Also, remember that if your `blob` column contains large binary data, you might need to
调优完成之后,就可以向正式的 MySQL 数据库实例进行数据迁移了,主要注意点的:
LATEST.sql
进行初始化库表import sqlite3
import mysql.connector
import json
from datetime import datetime
# SQLite connection
sqlite_conn = sqlite3.connect('~/.memos/memos_prod.db')
sqlite_cursor = sqlite_conn.cursor()
# MySQL connection
mysql_conn = mysql.connector.connect(
host="xxxxxx.rds.aliyuncs.com",
user="xxxxxxxxxxxxxxxxx",
password="xxxxxxxxxxxxx",
database="xxxxxxxxxxxxx"
)
mysql_cursor = mysql_conn.cursor()
# List of tables to migrate
tables = [
"migration_history", "system_setting", "user", "user_setting", "memo",
"memo_organizer", "memo_relation", "resource", "activity", "idp",
"inbox", "webhook", "reaction"
]
# List of timestamp columns
timestamp_columns = ['created_ts', 'updated_ts']
# List of reserved keywords that need to be escaped
reserved_keywords = ['key', 'value', 'blob', 'type']
def convert_timestamp(value):
try:
return datetime.fromtimestamp(int(value)).strftime('%Y-%m-%d %H:%M:%S')
except ValueError:
return value
def escape_column_names(columns):
return [f"`{column}`" if column.lower() in reserved_keywords else column for column in columns]
for table in tables:
print(f"Migrating table: {table}")
# Fetch data from SQLite
sqlite_cursor.execute(f"SELECT * FROM {table}")
rows = sqlite_cursor.fetchall()
if not rows:
print(f"No data in table {table}")
continue
# Get column names
columns = [description[0] for description in sqlite_cursor.description]
# Escape column names if necessary
escaped_columns = escape_column_names(columns)
# Prepare MySQL insert query
placeholders = ', '.join(['%s'] * len(columns))
columns_string = ', '.join(escaped_columns)
insert_query = f"INSERT INTO {table} ({columns_string}) VALUES ({placeholders})"
# Insert data into MySQL
for row in rows:
# Convert any JSON fields and timestamps
processed_row = []
for value, column in zip(row, columns):
if column in ['tags', 'payload']:
try:
processed_value = json.dumps(json.loads(value))
except:
processed_value = value
elif column in timestamp_columns:
processed_value = convert_timestamp(value)
else:
processed_value = value
processed_row.append(processed_value)
try:
mysql_cursor.execute(insert_query, processed_row)
except mysql.connector.Error as err:
print(f"Error inserting into {table}: {err}")
print(f"Problematic row: {processed_row}")
continue
mysql_conn.commit()
print(f"Migrated {len(rows)} rows for table {table}")
# Close connections
sqlite_conn.close()
mysql_conn.close()
print("Migration completed")
2024-11-10 00:17:32
双十一了,好物清单、什么值得买之类的内容铺天盖地,到底我们在消费的时候受到哪些东西影响,我觉得需要研究一下消费决策模型。根据恩格尔(Engel)等提出的 EBK 模型可以看出消费时原来是这么样复杂,哪怕买瓶水、买个包子都无形中被这么多因素影响着。为了自己的钱包,我觉得很有必要建立属于自己的消费决策模型。
根据 EKB 模型,消费决策是由五步构成的:
当人们认为理想与实际之间有差距时,便产生了问题,问题产生后则整个系统(内外)便开始运作,目标也化成了具体的行动。
例如:工作了半天感觉肚子饿了,继续饿着就没力气干活了,看来需要找点吃的了。
当人们发现了问题的存在,便会去搜寻相关的信息,信息搜寻又可分为内部搜寻与外部搜寻两种,所谓内部搜寻是指在其现有资料或过去的购买经验中去寻找。当内部搜寻无法满足其需要时,便会转由外部寻找,所谓的外部搜寻诸如大众传播媒体、行销人员及亲友等。
例如:
收集到信息之后就需要判断一下哪个更好,哪个跟适合。
例如:
当评估了各种可能的方案后,便会选择一个最能解决原始问题的方案并采取消费行为。
例如:
当消费并使用了某产品后,可能发生满意或购买认知失调两种结果,并存储于记忆当中。
例如:
商家为了刺激消费,常常会巧妙地运用心理学知识。这些心理技巧潜移默化地影响着我们的消费决策。下面就来详细介绍一些常见的商家常用的心理学知识:
理解消费决策的心理过程并据此做出明智的消费选择是至关重要的。下面提供了一个优化后、更具可操作性的消费决策分析和思考方法,整合了心理学原理和实践指导:
最后分享一个我自己正在尝试使用的简单消费决策逻辑:
使用说明: