项目:RAG

一种将信息检索系统与大语言模型的生成能力相结合的技术框架。

基础流程:索引检索生成

索引:目的是把杂乱的非结构化文档,处理成计算机便于快速查找的格式。有基于关键词的索引和基于语义的索引。

检索:不同的索引结构对应不同的检索方式?》《 mmmmmmmmmmm,可以根据关键词和根据语义进行检索。

2. 两种索引和检索的方式:

基于关键词匹配(无法理解语义)和基于语义向量化(可以理解语义)

文本预加载->文本分割(固定长度分割,引入一定的重叠区域)

#建立document对象
for response_ref in response_refs:
    if len(response_ref) > 0:
        documents.append(Document(text=response_ref))
#文本分割
node_parser = SentenceSplitter(chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap)  #SentenceSplitter 的作用:是一个文本分割器,负责把长文本切成适合检索的小块。
nodes = node_parser.get_nodes_from_documents(documents)

2.1基于关键词

最常用的算法:BM25,求每一个文件(D)和关键词(Q)之间的相关性。

利用技术:倒排索引(记录每个词出现在每个文档里的频率),词频饱和机制(防止无意义的词重复导致文件得分高),长度归一化机制(避免文章长度太长导致的得分高)

bm25_retriever = BM25Retriever.from_defaults(
    nodes=nodes,
    similarity_top_k=similarity_top_k
)

2.2基于词块向量的索引和检索(🐖:将文本转换成高维向量,语义相近的向量在向量空间里距离较近,可以找到意思最相关的文本)

步骤:nodes->embedding(存储在字典里,根据HNSW 构建索引结构)->retrieve(利用余弦相似度找到最相近的向量)->选择top-k输出

embedding模型的训练:1.预训练 2. 模型微调

模型微调:指句子提取向量进行对比学习训练,目的是让语义相近的向量挨的更近。

微调的方法:将句子映射到向量,计算不同向量间相似度,用softmax转变成概率分布,构建Infonce损失函数,行损失和列损失,最终损失是两者的平均值。

# 相似度矩阵 S (3x3)
         h1+    h2+    h3+
h1   [  sim11  sim12  sim13 ]
h2   [  sim21  sim22  sim23 ]
h3   [  sim31  sim32  sim33 ]

# 行方向:对每行做 softmax
行1: softmax([sim11, sim12, sim13]) → 希望 sim11 最大
行2: softmax([sim21, sim22, sim23]) → 希望 sim22 最大
行3: softmax([sim31, sim32, sim33]) → 希望 sim33 最大

# 列方向:对每列做 softmax
列1: softmax([sim11, sim21, sim31]) → 希望 sim11 最大
列2: softmax([sim12, sim22, sim32]) → 希望 sim22 最大
列3: softmax([sim13, sim23, sim33]) → 希望 sim33 最大

检索过程:

#构建embedding模型
embedding_model = HuggingFaceEmbedding(
    model_name=embedding_model_path,
    device=device
)
#构建向量索引
index = VectorStoreIndex(nodes, embed_model=self.embedding_model)
#构建索引器
vector_retriever = index.as_retriever(similarity_top_k=self.top_k)
#执行索引
vector_results = vector_retriever.retrieve(query)

2.3.去重合并

将bm25生成的vector生成的结果进行合并和去重。

3.生成

检索阶段完成,已经得到query相关的参考文献,进入生成阶段,即利用大语言模型LLM结合参考文献生成最终答案。包含两个重要部分:prompt和LLM推理

C笔试2024年真题

二:编程题

1.
编写程序,输入一个数字 n(n<10000000),将其中各个位的奇数提取
出来,逆序组成一个新的数字输出。比如:输入 n 1769243,输出为 3971
#include <stdio.h>
#include <math.h>
int main() {
	int  num[8] = { 0 };
	int n, count = 0;
	scanf("%d", &n);
	int i = 0;
	int data = 0;
	while ((n /10) != 0) {
		num[i] =n%10;
		n = n / 10;
		i++;
		count++;
	}
	num[i] = n;
	count++;
	for (int m = 0; m < count; m++) {
		if (num[m] % 2 != 0) {
			printf("%d", num[m]);
		}
	}

	return 0;
}

2. 编写程序,输入的字符串中只包含字母和*,将字符串中除了首位部

分的*号去掉。例如:输入字符串为****AB*C*DE**F***,输出的字符串应
****ABCDEF***
int earase_L(char str[]) {
	int left = 0;
	int i = 0;
	while (str[i] != '\0') {
		if (str[i] <= 'Z' && str[i]>='A') left++;
		else {
			if (left > 0) {
				str[i] = 0;
			}
		}
		i++;
	}
	return 0;
}
int earase_R(char str[]) {
	int right = 0;
	int i = strlen(str);
	for(i;i>0;i--){
		if (str[i] < 'Z' && str[i]>'A') right++;
		else {
			if (right > 0) {
				str[i] = 0;
			}
		}
	}
	return 0;
}
int main() {
	char str[20];
	char strl[20];
	char strr[20];
	gets(str);
	int len = strlen(str);
	strcpy(strl, str);
	strcpy(strr, str);
	earase_L(strl);
	earase_R(strr);
	int i;
	for ( i = 0; i < len; i++) {
		if (strl[i] != '0') printf("%c",strl[i]);
	}
	int j = len-1;
	while (strr[j] == '*') {
		printf("%c",strr[j]);
		j--;
	}
	return 0;
}

3.编写程序,将一个单链表(不带头结点)进行冒泡排序

typedef struct node {
	int data;
	struct node* next;

}Linklist;
int Len(Linklist *list) {
	Linklist* p=list;
	int count = 0;
	while (p != NULL) {
		p = p->next;
		count++;
	}
	return count;
}
int Swap(Linklist* p, Linklist* q) {
	int tempt = 0;
	tempt = p->data;
	p->data = q->data;
	q->data = tempt;
	return 0;
}
void Sort(Linklist* list) {
	Linklist* p=list;
	Linklist* q = p->next;
	int len = Len(list);
	for (int i = 0; i < len-1; i++) {
		for (int j = 0; j < len - i - 1; j++) {
			if ((p->data) >(q->data)) {
				Swap(p, q);
			}
			p = p->next;
			q = q->next;
		}
		p = list;
		q = p->next;
	}
	
}

一:简答题:

1. 请简述数组和结构体的区别及联系

联系:两者都可以用来存储信息;
结构体内元素可以包含数组,数组内也可以是结构体
区别:一个结构体可以有不同类型的数据,而数组所有数据类型必须相同;
结构体可以用结构体名+.取结构体内元素,数组可以用[ ]索引的方式查找内部元素;
如果用指针构建结构体,指针通常指向结构体内第一个元素位置,而用指针构建数组,指针指向数组首地址;

2.解释下面四条语句考察运算符优先级:

()【】. -> 后置++,后置--(优先级最高)

从内到外,从右到左,优先级高的先运算。

int *p[4]; #大小为4的指针型数组,指针指向int型元素
int (*p)[4]; #一个指针指向大小为4的Int型数组
int *p();   #函数返回类型是Int型指针,可以是一个数组或者一个链表
int (*p)();  #p是一个指针,指向返回值是Int型的函数,即函数指针

3.static作用

全局变量和普通的全局变量有什么区别?static 局部变量和普通的局部变量有什么区别?

4. 下面程序的功能是将字符串 src 逆序输出,请找出下面的错误。

#include<string.h> #缺少include<stdlib.h>
void main(){
    char *src = "hello,world";
    char *dest, *d, *s;
    int len,i;
    len = strlen(src);
    dest = (char *)malloc(len); #改:dest=(char*)malloc((len+1)*sizeof(char));
    s=&src[len];   #改:s=&src[len-1]
    d=dest;
    while(len--!=0) {
        d++=s--;  #改:*d++=*s--
    }    
    printf("%s",dest);
    
}

各种头文件的作用:

<stdio.h>  <string.h>  <math.h>  <stdlib.h.> 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐