阅读视图

发现新文章,点击刷新页面。
🔲 ☆

RAG系列-语义分块RAG(Semantic Chunking RAG)

02. 语义分块RAG(Semantic Chunking RAG)

方法简介

语义分块RAG通过计算句子间的语义相似度来智能分块,而不是简单的固定长度分块。它使用百分位数、标准差或四分位距等方法找到语义断点,将文本分割成语义连贯的块,提升检索精度。

核心代码

  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
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import fitz
import os
import numpy as np
import json
from openai import OpenAI

def extract_text_from_pdf(pdf_path):
    """
    Extracts text from a PDF file.

    Args:
    pdf_path (str): Path to the PDF file.

    Returns:
    str: Extracted text from the PDF.
    """
    # Open the PDF file
    mypdf = fitz.open(pdf_path)
    all_text = ""  # Initialize an empty string to store the extracted text

    # Iterate through each page in the PDF
    for page in mypdf:
        # Extract text from the current page and add spacing
        all_text += page.get_text("text") + " "

    # Return the extracted text, stripped of leading/trailing whitespace
    return all_text.strip()

# Initialize the OpenAI client with the base URL and API key
client = OpenAI(
    base_url="https://api.studio.nebius.com/v1/",
    api_key=os.getenv("OPENAI_API_KEY")  # Retrieve the API key from environment variables
)

def get_embedding(text, model="BAAI/bge-en-icl"):
    """
    Creates an embedding for the given text using OpenAI.

    Args:
    text (str): Input text.
    model (str): Embedding model name.

    Returns:
    np.ndarray: The embedding vector.
    """
    response = client.embeddings.create(model=model, input=text)
    return np.array(response.data[0].embedding)

def cosine_similarity(vec1, vec2):
    """
    Computes cosine similarity between two vectors.

    Args:
    vec1 (np.ndarray): First vector.
    vec2 (np.ndarray): Second vector.

    Returns:
    float: Cosine similarity.
    """
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

def compute_breakpoints(similarities, method="percentile", threshold=90):
    """
    Computes chunking breakpoints based on similarity drops.

    Args:
    similarities (List[float]): List of similarity scores between sentences.
    method (str): 'percentile', 'standard_deviation', or 'interquartile'.
    threshold (float): Threshold value (percentile for 'percentile', std devs for 'standard_deviation').

    Returns:
    List[int]: Indices where chunk splits should occur.
    """
    # Determine the threshold value based on the selected method
    if method == "percentile":
        # Calculate the Xth percentile of the similarity scores
        threshold_value = np.percentile(similarities, threshold)
    elif method == "standard_deviation":
        # Calculate the mean and standard deviation of the similarity scores
        mean = np.mean(similarities)
        std_dev = np.std(similarities)
        # Set the threshold value to mean minus X standard deviations
        threshold_value = mean - (threshold * std_dev)
    elif method == "interquartile":
        # Calculate the first and third quartiles (Q1 and Q3)
        q1, q3 = np.percentile(similarities, [25, 75])
        # Set the threshold value using the IQR rule for outliers
        threshold_value = q1 - 1.5 * (q3 - q1)
    else:
        # Raise an error if an invalid method is provided
        raise ValueError("Invalid method. Choose 'percentile', 'standard_deviation', or 'interquartile'.")

    # Identify indices where similarity drops below the threshold value
    return [i for i, sim in enumerate(similarities) if sim < threshold_value]

def split_into_chunks(sentences, breakpoints):
    """
    Splits sentences into semantic chunks.

    Args:
    sentences (List[str]): List of sentences.
    breakpoints (List[int]): Indices where chunking should occur.

    Returns:
    List[str]: List of text chunks.
    """
    chunks = []  # Initialize an empty list to store the chunks
    start = 0  # Initialize the start index

    # Iterate through each breakpoint to create chunks
    for bp in breakpoints:
        # Append the chunk of sentences from start to the current breakpoint
        chunks.append(". ".join(sentences[start:bp + 1]) + ".")
        start = bp + 1  # Update the start index to the next sentence after the breakpoint

    # Append the remaining sentences as the last chunk
    chunks.append(". ".join(sentences[start:]))
    return chunks  # Return the list of chunks

def create_embeddings(text_chunks):
    """
    Creates embeddings for each text chunk.

    Args:
    text_chunks (List[str]): List of text chunks.

    Returns:
    List[np.ndarray]: List of embedding vectors.
    """
    # Generate embeddings for each text chunk using the get_embedding function
    return [get_embedding(chunk) for chunk in text_chunks]

def semantic_search(query, text_chunks, chunk_embeddings, k=5):
    """
    Finds the most relevant text chunks for a query.

    Args:
    query (str): Search query.
    text_chunks (List[str]): List of text chunks.
    chunk_embeddings (List[np.ndarray]): List of chunk embeddings.
    k (int): Number of top results to return.

    Returns:
    List[str]: Top-k relevant chunks.
    """
    # Generate an embedding for the query
    query_embedding = get_embedding(query)

    # Calculate cosine similarity between the query embedding and each chunk embedding
    similarities = [cosine_similarity(query_embedding, emb) for emb in chunk_embeddings]

    # Get the indices of the top-k most similar chunks
    top_indices = np.argsort(similarities)[-k:][::-1]

    # Return the top-k most relevant text chunks
    return [text_chunks[i] for i in top_indices]

def generate_response(system_prompt, user_message, model="meta-llama/Llama-3.2-3B-Instruct"):
    """
    Generates a response from the AI model based on the system prompt and user message.

    Args:
    system_prompt (str): The system prompt to guide the AI's behavior.
    user_message (str): The user's message or query.
    model (str): The model to be used for generating the response. Default is "meta-llama/Llama-2-7B-chat-hf".

    Returns:
    dict: The response from the AI model.
    """
    response = client.chat.completions.create(
        model=model,
        temperature=0,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_message}
        ]
    )
    return response

# 完整调用流程
def semantic_chunking_rag_pipeline(pdf_path, query):
    # 1. 提取PDF文本
    extracted_text = extract_text_from_pdf(pdf_path)

    # 2. 按句子分割
    sentences = extracted_text.split(". ")

    # 3. 生成句子嵌入
    embeddings = [get_embedding(sentence) for sentence in sentences]

    # 4. 计算句子间相似度
    similarities = [cosine_similarity(embeddings[i], embeddings[i + 1]) for i in range(len(embeddings) - 1)]

    # 5. 计算断点(使用百分位数方法)
    breakpoints = compute_breakpoints(similarities, method="percentile", threshold=90)

    # 6. 分割成语义块
    text_chunks = split_into_chunks(sentences, breakpoints)

    # 7. 创建块嵌入
    chunk_embeddings = create_embeddings(text_chunks)

    # 8. 语义搜索
    top_chunks = semantic_search(query, text_chunks, chunk_embeddings, k=2)

    # 9. 生成回答
    system_prompt = "You are an AI assistant that strictly answers based on the given context. If the answer cannot be derived directly from the provided context, respond with: 'I do not have enough information to answer that.'"
    user_prompt = "\n".join([f"Context {i + 1}:\n{chunk}\n=====================================\n" for i, chunk in enumerate(top_chunks)])
    user_prompt = f"{user_prompt}\nQuestion: {query}"

    ai_response = generate_response(system_prompt, user_prompt)
    return ai_response.choices[0].message.content

代码讲解

  • 句子分割:按句号分割文本成句子
  • 嵌入生成:为每个句子生成向量表示
  • 相似度计算:计算相邻句子的余弦相似度
  • 断点检测:使用百分位数方法找到语义断点
  • 语义分块:根据断点将句子组合成语义块
  • 检索生成:基于语义块进行检索和答案生成

主要特点

  • 基于语义相似度的智能分块
  • 支持多种断点检测方法(百分位数、标准差、四分位距)
  • 保持语义连贯性
  • 比固定长度分块更精准

使用场景

  • 长文档处理
  • 需要保持语义完整性的场景
  • 复杂问答系统
  • 学术论文、技术文档等结构化文本
🔲 ☆

RAG系列-基础RAG(Simple RAG)

01. 基础RAG(Simple RAG)

方法简介

基础RAG(Retrieval-Augmented Generation)是最简单的检索增强生成方法。它通过向量化检索获取与用户查询最相关的文档片段,并将这些片段作为上下文输入给大语言模型进行答案生成。

核心代码

  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
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import fitz
import os
import numpy as np
import json
from openai import OpenAI

def extract_text_from_pdf(pdf_path):
    """
    Extracts text from a PDF file and prints the first `num_chars` characters.

    Args:
    pdf_path (str): Path to the PDF file.

    Returns:
    str: Extracted text from the PDF.
    """
    # Open the PDF file
    mypdf = fitz.open(pdf_path)
    all_text = ""  # Initialize an empty string to store the extracted text

    # Iterate through each page in the PDF
    for page_num in range(mypdf.page_count):
        page = mypdf[page_num]  # Get the page
        text = page.get_text("text")  # Extract text from the page
        all_text += text  # Append the extracted text to the all_text string

    return all_text  # Return the extracted text

def chunk_text(text, n, overlap):
    """
    Chunks the given text into segments of n characters with overlap.

    Args:
    text (str): The text to be chunked.
    n (int): The number of characters in each chunk.
    overlap (int): The number of overlapping characters between chunks.

    Returns:
    List[str]: A list of text chunks.
    """
    chunks = []  # Initialize an empty list to store the chunks

    # Loop through the text with a step size of (n - overlap)
    for i in range(0, len(text), n - overlap):
        # Append a chunk of text from index i to i + n to the chunks list
        chunks.append(text[i:i + n])

    return chunks  # Return the list of text chunks

# Initialize the OpenAI client with the base URL and API key
client = OpenAI(
    base_url="https://api.studio.nebius.com/v1/",
    api_key=os.getenv("OPENAI_API_KEY")  # Retrieve the API key from environment variables
)

def create_embeddings(text, model="BAAI/bge-en-icl"):
    """
    Creates embeddings for the given text using the specified OpenAI model.

    Args:
    text (str): The input text for which embeddings are to be created.
    model (str): The model to be used for creating embeddings. Default is "BAAI/bge-en-icl".

    Returns:
    dict: The response from the OpenAI API containing the embeddings.
    """
    # Create embeddings for the input text using the specified model
    response = client.embeddings.create(
        model=model,
        input=text
    )

    return response  # Return the response containing the embeddings

def cosine_similarity(vec1, vec2):
    """
    Calculates the cosine similarity between two vectors.

    Args:
    vec1 (np.ndarray): The first vector.
    vec2 (np.ndarray): The second vector.

    Returns:
    float: The cosine similarity between the two vectors.
    """
    # Compute the dot product of the two vectors and divide by the product of their norms
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

def semantic_search(query, text_chunks, embeddings, k=5):
    """
    Performs semantic search on the text chunks using the given query and embeddings.

    Args:
    query (str): The query for the semantic search.
    text_chunks (List[str]): A list of text chunks to search through.
    embeddings (List[dict]): A list of embeddings for the text chunks.
    k (int): The number of top relevant text chunks to return. Default is 5.

    Returns:
    List[str]: A list of the top k most relevant text chunks based on the query.
    """
    # Create an embedding for the query
    query_embedding = create_embeddings(query).data[0].embedding
    similarity_scores = []  # Initialize a list to store similarity scores

    # Calculate similarity scores between the query embedding and each text chunk embedding
    for i, chunk_embedding in enumerate(embeddings):
        similarity_score = cosine_similarity(np.array(query_embedding), np.array(chunk_embedding.embedding))
        similarity_scores.append((i, similarity_score))  # Append the index and similarity score

    # Sort the similarity scores in descending order
    similarity_scores.sort(key=lambda x: x[1], reverse=True)
    # Get the indices of the top k most similar text chunks
    top_indices = [index for index, _ in similarity_scores[:k]]
    # Return the top k most relevant text chunks
    return [text_chunks[index] for index in top_indices]

def generate_response(system_prompt, user_message, model="meta-llama/Llama-3.2-3B-Instruct"):
    """
    Generates a response from the AI model based on the system prompt and user message.

    Args:
    system_prompt (str): The system prompt to guide the AI's behavior.
    user_message (str): The user's message or query.
    model (str): The model to be used for generating the response. Default is "meta-llama/Llama-2-7B-chat-hf".

    Returns:
    dict: The response from the AI model.
    """
    response = client.chat.completions.create(
        model=model,
        temperature=0,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_message}
        ]
    )
    return response

# 完整调用流程
def simple_rag_pipeline(pdf_path, query):
    # 1. 提取PDF文本
    extracted_text = extract_text_from_pdf(pdf_path)

    # 2. 分块处理
    text_chunks = chunk_text(extracted_text, 1000, 200)

    # 3. 创建嵌入
    response = create_embeddings(text_chunks)

    # 4. 语义搜索
    top_chunks = semantic_search(query, text_chunks, response.data, k=2)

    # 5. 生成回答
    system_prompt = "You are an AI assistant that strictly answers based on the given context. If the answer cannot be derived directly from the provided context, respond with: 'I do not have enough information to answer that.'"
    user_prompt = "\n".join([f"Context {i + 1}:\n{chunk}\n=====================================\n" for i, chunk in enumerate(top_chunks)])
    user_prompt = f"{user_prompt}\nQuestion: {query}"

    ai_response = generate_response(system_prompt, user_prompt)
    return ai_response.choices[0].message.content

代码讲解

  • 文档处理:使用PyMuPDF提取PDF文本,按字符数分块
  • 嵌入生成:使用BAAI/bge-en-icl模型生成文本嵌入
  • 语义搜索:计算查询与文档块的余弦相似度,返回最相关的k个片段
  • 答案生成:将检索到的上下文与用户问题输入LLM生成答案

主要特点

  • 实现简单,易于理解和扩展
  • 使用余弦相似度进行语义检索
  • 支持PDF文档处理
  • 可配置的检索数量k

使用场景

  • FAQ自动问答
  • 小型企业知识库
  • 结构化文档检索增强
  • 基础文档问答系统
☑️ ☆

读付鹏和高善文对当前经济评论

今天读了付鹏先生在HSBC内部演讲的文稿,后面相继听了高善文先生的演讲。在阅读了高善文和付鹏关于中国经济形势的深刻分析后,我获得了对当前和未来经济趋势的更全面理解。

通过深入阅读高善文的分析,我深刻认识到经济转型和周期性压力是塑造中国经济未来的关键力量。经济转型引发的结构性变化是深远而持久的,它要求我们不断适应新的发展模式,比如从劳动密集型向技术密集型转变,从投资驱动向消费驱动转变。与此同时,周期性压力则在短期内对经济产生显著影响,如需求波动、市场信心变化等,这些都可能对我们的职业和财务状况产生直接或间接的影响。

这种理解使我意识到在不同的经济周期阶段,需要采取不同的应对策略。目前,我们的职业生涯将长期处于这个经济周期的尾声阶段,这意味着整体经济环境、就业环境以及收入增长潜力都不能与经济高速发展时期同日而语。为了适应这些变化,我们需要认真评估自己所在行业在经济转型中的位置,以及未来可能的发展趋势。如果行业前景黯淡,可能需要考虑转行或提升技能以适应新兴行业的需求。在经济增速放缓的背景下,我们也需要更加谨慎地管理个人财务,包括减少不必要的债务、增加储蓄和投资于相对稳定的资产。然后调整消费习惯,避免过度消费,尤其是在经济不确定性较高时期,理性消费变得更加重要。也是时候考虑要开启副业,增加职业以外的收入,以应对可能的经济波动。希望能够更好地应对经济转型和周期性压力带来的挑战,同时也为未来可能出现的新机遇做好准备。

作为普通人,我们需要建立更为全面和深入的经济理解,以便在不断变化的经济环境中做出明智的决策。这两篇文章为我未来的财务规划和职业发展提供了宝贵的指导。

🔲 ☆

认证订阅

This message is used to verify that this feed (feedId:71916462721158158) belongs to me (userId:45764741539537920). Join me in enjoying the next generation information browser https://follow.is.

☑️ ☆

写在28岁的中点

Dear Ethan:

又到了充满期待的新的一年,过去的一年你过得还好吗?在往年的年终总结中,你会对即将到来的一年许下满满的期待。很明显,去年的你又是欠下满满债务的你。选择在你28岁的中点写一封信给自己,这更私人,但也更贴近你的内心。

命途多舛,何以不甘

又一年的时间,你经历了不同的事情,遇到了不同的人,了解了不同的故事,现在轮到你说一说自己的故事了。也许都听过关于西西弗斯的故事,他的一生就是不断将巨石推到山顶,又不得不经受巨石滚落,再将石头推向山顶,这样一个荒诞的周而复始的故事。这也许,也是我们每一个人所需要经历的人生。

三月到五月的你,在小论文、毕业论文修改和实验中度过,那时的你,年轻气盛,因为一点小小的观点和老师争吵的面红耳赤。六月份经历了毕业的狂欢,阔别了昔日一起学习的良师益友,与好友约定毕业旅行,因囊中羞涩与疫情的封控而取消。急忙奔赴职场,结实新的朋友,重新投入到自我的升华之中。总的来说,去年的你,经历了人生中的两件大事:毕业和工作,再次完成学生到职场人身份的转变,其它的都是一地鸡毛。

这一年中失去的东西太多太多,任何一点细小的死亡与崩坏都会变得不可承受,这大概就是去年的一个缩影吧,巨石一次次的滚动,我们一次次的再上路。真的很想努力,但满满的无力感。这种无力感,年复一年,细细沉思,最早可追溯到2015年,那是我第一次深刻体会这种无力感。如今七年已过,你仍旧在与这种无力感继续搏斗着。

此前的每一个人生阶段—-初中,高中,大学,似乎总是被安排着走的,大的方向永远是一年比一年好。那份不甘于现实的热情,还能继续保持,也许正是因为不曾经历大的挫折。仔细回忆过往的人生,之前的你确实保持着点自我。那会儿呢,只需要考虑自己就已经足够了,家人永远是不断给予付出的那一方,所以那会儿做什么事都是那么天真吧!这份自我得益于你的少年意气,得益于家庭给你的支撑,也得益于时代的滚滚向前。但人生或命运从来就没有承诺过谁,总会往更好的方向发展。巨石总会滚落,而明天一早睁眼,我们依旧需要推着巨石往上。

肩负起自己的责任

去年的你,每一天都在慌慌张张中度过,连家人都没能好好陪伴,也没有很好的意识到,父母的年纪已经到了颐养天年的时刻,我们需要无时不刻的关注着,陪伴着他们。而你每一天都在焦虑中挣扎,却无法鼓起勇气,让现在的你有所改观,因为你此刻内心是害怕的,害怕试错的代价太大,害怕失败,害怕被人嘲笑。可是,正如上面所说,人生或命运从来就没有承诺过谁,总会往更好的方向发展,所以今年的你,一定要鼓起勇气做出一点改变啦!

我知道在过去的一年,你无数次打开B站,似乎想要寻找什么答案,可是刷了很久,焦虑一点没减少。事实一次次的告诉你,既然别人无法明确的告诉你,那你就要学会戴着镣铐和生活共舞,不是吗?毛姆在写《月亮与六便士》的时候,大概忘了在理想和现实中间还有责任。他没有告诉你站在路口,抬头是月亮,低头是捡硬币,责任在肩膀上压着,那你该往哪儿走。你唯一确定的是,你想负起这个责任。因为曾经家人的支持是你的底气,你今天,同样想成为家人的底气。

所谓成长,接受自我

直到现在我才真正的意识到,所谓的成长就是认知的不断升级。只有当你明白这个道理,这个世界才开始真正的展开在你的眼前,原来以前认为错误的事情,原来也可以是对的「之前和老师争论的面红耳赤」。你不再为某一个你不认同的点去争论,慢慢的学会去理解别人,尊重别人,倾听大家的声音,不再自我,这已经是你最大的成长了。到了这个年纪才谈成长,这也许是一件过于奢侈的事情了。有很多很多的人,已经过早的品尝了世间的滋味。但对于刚入社会的我来说,考验才刚刚开始。成长不是随着年龄的增长,被社会打磨成一样的世故和圆滑,而是在生命的成熟中,仍有一颗纯真的童心和一颗善良的爱心。你想得到月亮,即使如此的平凡,不能起飞,也要努力的走着,跑着,伸手去够,去摘。即使经历过种种不顺,还是会有好事发生,会有新的缘分,新的身份,新的挑战,我不认输,你也不要,好吗?

寄语未来

2023年,愿你在不平和焦虑的时候,能记起你的初心和梦想,然后大踏步的坚持走向明天!!!

🔲 ☆

读《程序员修炼之道》

务实的哲学

  • 团队信任对于创造力和协作至关重要,关键时刻信任的破坏几乎无法修复

  • 提供选择,别找借口– 小黄鸭编程

  • 破窗理论– 不要因为一些危急的事情,造成附加伤害,尽可能控制软件的熵

  • 人们都觉得,加入一个推进中的成功项目更容易一些(煮石头汤的故事)

  • 永远审视项目,不要做温水青蛙,先养成仔细观察周围环境的习惯,然后再项目中这样做

  • 知识和经验是你最重要的资产,但是它们是时效资产,学习新事物的能力是你最重要的战略资产。 知识组合:

    1. 定期投资–安排一个固定的时间和地点学习

      • 每年学习一门新语言
      • 每月读一本技术书
      • 读非技术书
      • 上课– 了解公司之外的人都在做什么
      • 尝试不同的环境
      • 与时俱进–关心最新技术的进展

      想法的交叉是很重要的 批判性思维–批判性思考独到的和听到的东西

    2. 多样化– 熟悉的技能越多越好

    3. 风险管理–不同技术在高风险高回报到低风险低回报区间均匀分布,不要把技术鸡蛋放在一个篮子里

    4. 低买高卖–在一项新兴技术流行之前就开始学习,不过这是押宝

    5. 重新评估调整–不断刷新自己的知识库

  • 批判性思维

    1. 五次为什么
    2. 谁从中收益
    3. 有什么背景
    4. 什么时候在哪里工作可以工作起来
    5. 为什么是这个问题
  • 写一个大纲, 问自己:这是否用正确的方式表达了我想表达的东西,以及现在是表达这个东西的好时机吗?

务实的方法

ETC(easy to change)

核心知道思想

  • DRY(Don’t repeat yourself)
  • 正交性 良好设计中,数据库相关代码应该和用户界面保持正交, 当系统的组件相互之间高度依赖时,就没有局部修理这回事。
🔲 ☆

Django源码系列:文件变化后server自动重启机制

初试 - 文件变化后 server 自动重启

本源码系列是基于 Django4.0 的源码,可以自行到django 官方下载。

在此之前,不妨先了解下 django 是如何做到自动重启的

开始

django 使用 runserver 命令的时候,会启动俩个进程。

runserver 主要调用了 django/utils/autoreload.pymain 方法。
至于为何到这里的,我们这里不作详细的赘述,后面篇章会进行说明。

主线程通过 os.stat 方法获取文件最后的修改时间进行比较,继而重新启动 django 服务(也就是子进程)。

大概每秒监控一次。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# django/utils/autoreload.py 的 reloader_thread 方法

def reloader_thread():
    ...
    # 监听文件变化
    # -- Start
    # 这里主要使用了 `pyinotify` 模块,因为目前可能暂时导入不成功,使用 else 块代码
    # USE_INOTIFY 该值为 False
    if USE_INOTIFY:
        fn = inotify_code_changed
    else:
        fn = code_changed
    # -- End
    while RUN_RELOADER:
        change = fn()
        if change == FILE_MODIFIED:
            sys.exit(3)  # force reload
        elif change == I18N_MODIFIED:
            reset_translations()
        time.sleep(1)

code_changed 根据每个文件的最后修改时间是否发生变更,则返回 True 达到重启的目的。

父子进程&多线程

关于重启的代码在 python_reloader 函数内

 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
28
29

# django/utils/autoreload.py

def restart_with_reloader():
    # 在这里开始设置环境变量为true
    new_environ = {**os.environ, DJANGO_AUTORELOAD_ENV: "true"}
    args = get_child_arguments() #获取执行的命令参数
    # 重启命令在这里开始生效
    while True:
        p = subprocess.run(args, env=new_environ, close_fds=False)
        if p.returncode != 3:
            return p.returncode


def run_with_reloader(main_func, *args, **kwargs):
    signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
    # 刚开始 DJANGO_AUTORELOAD_ENV是没有被设置为true的所以这里会进入到else里。
    try:
        if os.environ.get(DJANGO_AUTORELOAD_ENV) == "true":
            reloader = get_reloader()
            logger.info(
                "Watching for file changes with %s", reloader.__class__.__name__
            )
            start_django(reloader, main_func, *args, **kwargs) # 开启django服务线程
        else:
            exit_code = restart_with_reloader()
            sys.exit(exit_code) # 0为正常退出,其他的会抛出相关的错误
    except KeyboardInterrupt:
        pass

程序启动,因为没有 RUN_MAIN 变量,所以走的 else 语句块。

颇为有趣的是,restart_with_reloader 函数中使用 subprocess.run 方法执行了启动程序的命令( e.g:python3 manage.py runserver ),此刻 RUN_MAIN 的值为 True ,接着执行 _thread.start_new_thread(main_func, args, kwargs) 开启新线程,意味着启动了 django 服务。

如果子进程不退出,则停留在 run 方法这里(进行请求处理),如果子进程退出,退出码不是 3,while 则被终结。反之就继续循环,重新创建子进程。

总结

以上就是 django 检测文件修改而达到重启服务的实现流程。

结合 subprocess.run 和 环境变量 创建俩个进程。主进程负责监控子进程和重启子进程。 子进程下通过开启一个新线程(也就是 django 服务)。主线程监控文件变化,如果变化则通过 sys.exit(3) 来退出子进程,父进程获取到退出码不是 3 则继续循环创建子进程,反之则退出整个程序。

好,到这里。我们勇敢的迈出了第一步,我们继续下一个环节!!! ヾ(◍°∇°◍)ノ゙

🔲 ☆

关于周报这种小事

从认识到双向链接开始我先使用了 obsidian,然而对于我这种懒癌晚期的人来说,需要结构化的记录,真的不太适合我「取一个好听的名字真的太难了」。当然有一说一,双链这个观点真的是太妙了。

现在的我已经全面转向了 logseq 来进行笔记记录,不得不说这种支持自定义代码的笔记真的不错「虽然我到目前为止使用最多的是高级查询TODO这两个功能,记录笔记的话只是零星记录了几句话,并没有详细的记录或者输出一些东西。」。在 logseq 中是以日期为主线的,这免去了我对要写的内容的抽象主题的负担。

工作日报周报

首先对任务进行分类,在学习了 Logseq 的高级查询语法并了解其能力之后,我决定使用#tag这种形式来组织个人任务和工作任务,属于工作任务的会标记上#work标签,个人任务就不进行标签。

首先就是工作,这个是大块内容,我目前使用 logseq 生成日报&周报,用于之后提交到绩效评估系统中。

日报/周报代码

日报和周报代码上只有 inputs[:yesterday]更改成 inputs[:7d]即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#+BEGIN_QUERY
{
    :title "查询昨天完成的任务"
    :query [:find (pull ?b [*])
       :in $ ?start ?end
       :where
       [?b :block/marker ?m]
       [?b :block/page ?p]
       [?p :page/journal? true]
       [?p :page/journal-day ?d]
       [(>= ?d ?start)]
       [(<= ?d ?end)]
       [?b :block/path-refs [:block/name "work"]]
       [(= "DONE" ?m)]
   ]
   :inputs [:yesterday :today]
}
#+END_QUERY

效果图如下:

🔲 ☆

Randy Pausch 教授的最后一课

在重听李沐老师的《Resnet 论文精读》这一课的时候,ps:「之前没有好好读,:>)羞耻」。在讲到双栏论文中第一页的第二栏最上面,这个位置在学术界是非常重要的。提到了 Randy Pausch 教授在图形学开创了这一风格的写法,然后提到了他的最后一课「深刻印象」。

于是我从 cmu 网站中找到了当年演讲的材料,并完整的看了视频,这份笔记是 Randy 的人生经验和建议的抄录。

人生经验

  1. Have something to bring to the table, because that will make you more welcom. 你必须要有一些真本领,这样可以让你更受欢迎。
  1. You’ve got to get the fundamentals down because otherwise the fancy stuff isn’t going to work. 你必须练好基本功,否则后面的事情都不会发生。
  1. That was a bit of a setback. But remember, the brick walls are there for a reason. The brick walls are not there to keep us out. The brick walls are there to give us a chance to show how badly we want something. Becuase the brick walls are there to stop the people who don’t want it badly enough. They’re there to stop the other people. Remember brick walls let us show our dedication. They are there to separate us from the people who don’t really want to achieve their childhood dreams. 你总会遇到挫折。但是记住,它们的出现不是没有原因的。砖墙并不是为了挡住我们。它在那里,只是为了测试,我们的决心到底有多迫切。它在那里挡住那些没有强烈决心的人。它不让那些人通过。记住,砖墙的存在是为了显示我们自己付出的决心。它使得我们,同那些并不真的想实现梦想的人得以区分。

为人处世的建议

  1. helping others. 帮助他人。
  1. never lose the childlike wonder. It’s what drives us. 永远不要失去好奇心,它是人类前进的动力。
  1. Loyalty is a two way street. 诚以待人,这样别人也会忠实的对待你。
  1. Never give up. 永远不要放弃
  1. You can’t get there alone. People have to help you. You get people to help you by telling the truth. 你不能单打独斗,必须有人来帮你。只要你讲真话,就会有人来帮你。
  1. Apologize when you screw up and focus on other people, not on yourself. 当你把事情搞砸,首先要向别人道歉,首先关心他们的损失,而不是你自己的损失。
  1. When you do the right thing, good stuff has a way of happening. 如果你做了正确的事,好的结果自然会发生。
  1. Get a feedback loop and listen to it. 注意倾听反馈。
  1. Show gratitude. 感恩
  1. Don’t complain. Just work harder. 不要抱怨,而要加倍努力。
  1. Be good at something, it makes you valuable. 要有一技之长,它使你有价值。
  1. Work hard. 努力再努力。
  1. Find the best in everybody. 注意发现他人的优点。
  1. Be prepared. Luck is truly where preparation meets opportunity. 做好准备。所谓幸运,真的是机会和准备的结合。
🔲 ☆

对个人发展的思考

这一段时间我也一直在思考,毕业之后的发展。读了许多大佬写的博客,想着能够从中汲取一些经验和启发。发现大家都有一个共性就是善于总结和对自己的职业生涯进行了一定的规划,他们清楚的了解自己的定位和未来想要得到什么。我一直坚信花时间来整理状态,是一件很值得投资的事情。这篇文章也相当于研究生生涯总结和职业生涯的开篇吧!

毕业前的时光算得上是踏上职场前最后的悠闲。尽管对未来充满了希望,与此同时,我的内心也充斥着彷徨。害怕在未来几年一无所成,害怕自己达不到自己所期待的那种境界。每年设定目标,然而能够完成的却寥寥无几。才有了现在对未来的迷茫,心中一腔热血却像苍蝇一样,漫无目的。这可能也是普通人和大佬之间的区别吧!阅读过大佬的文章后,有下面的启示。

别多想,多实践

  1. 你思考的 90%的问题,至少 1000 年前,先哲们都给了答案。
  2. 剩下的 10%,300 年前的哲学家,思想家一定给出了答案。
  3. 绝对不存在你思考的问题,历史上的思想家没有给出答案。所以大多数的时候,没看书之前,你的思考,是徒劳的。

大多数时候,我们所思考的问题,前人都已经思考过了。我们需要做的是,踏踏实实的沉下心来,进行学习即可。我们总在思考未来应该如何做才能快速的成长,追求完成一件事情的形式。殊不知未来正是由无数个当下的事情组成的。与其担心未来,还不如沉下心来把当下的事情做到极致,未来一定会出现在你的眼前。

尽早规划(长线思维)

  1. 怎么过一天,就怎么过一生,如果认为明天或一年之后会有所改变,那么今天的自己是一年前希望看到的自己么。
  2. 随着时间的推移,资产(你认为有价值的一切)变得更有价值还是廉价
  3. 把每一个场景都看成投资场景,每一个行为当作投资行为,重视它对现在及将来的影响

咱们中国有一个不变的传统就是“五年规划”,国家尚且如此,何况人?所以我们也要及早的对自己进行一个五年规划,这个规划应该是方向性的,不是细节的。细节应当是每年每季度每月具体去完成以达到五年规划中的目标。

注重输出

  1. 只是看书或者视频,容易造成已经理解了某个知识点的错觉,短期记忆未经加固,很快就会「挥发」
  2. 无输出不输入,输出的方式可以是文章或者视频或者闲聊,经过强化后的内容更容易进入长期记忆
  3. 输出的过程会联结之前的积累,让知识更扎实,输出过程也会更流畅,输入和输出的比例可以控制在 3:7

在知识库系统的过程中,我们往往过于完美主义,想把知识系统工具一次性构建完整,然后把大量的时间都放在了工具的搭建中,折腾了很多中工具:notion, obsidian, logseq 等。我个人认为,工具是在自己不断的使用的过程中完善的,知识库也是一样,首先我们要有输出,之后在慢慢的进行调整,最后会实现一个理想中适合自己的知识系统的。我在这里建议放弃完美主义,在实践中不断的调整自己调整工具。还有一个就是逃离算法推荐吧,拥抱内容的多元化。

不要只停留在理论上,去实践,会发现更多的问题和挑战,也更有趣味,“what i cannot create, i do not understand”。

参考资料

  1. 工程师的成长
  2. 写在 28 岁边上
  3. 技术同学在业务中的成长
  4. 用未来的视角来看今天
☑️ ☆

ssh自动断开修复方法

工作中常需要连接着服务器,比如在深度学习训练模型的过程中,需要长时间连接着服务器,在一段时间没有操作后,ssh 会自动断开。 为了解决这个问题,在网上找到一种配置方法,亲测很久都不会再断开了,在此记录:

众所周知,ssh 是用于与远程服务器建立加密通信通道的,因此配置涉及到服务器和客户端:

  • 服务端 /etc/ssh/sshd_config
1
2
3
4
5
+ClientAliveInterval 60 # 每60秒发送一个KeepAlive请求
+ClientAliveCountMax 15 # 总时间为:15*60, 15分钟没有操作,终端断开。
# 以下命令重启 sshd 服务
service sshd reload
service sshd restart
  • 客户端 ~/.ssh/config
1
2
3
4
# 修改~/.ssh/config配置,对当前用户生效
# 这样配置通配所有服务端
Host *
	ServerAliveInterval 60

参考文献

  1. SSH 长时间不使用自动断开解决方案
❌