【Datawhale AI 夏令营】第四期 大模型应用开发笔记 02 RAG代码分析
啥苇子 2024-08-21 16:01:05 阅读 82
昨天介绍了RAG的流程,具体请看这篇笔记。
【Datawhale AI 夏令营】第四期 大模型应用开发笔记 01 RAG
今天将代码进行分析。本文章用到了一些AI工具来帮助我们分析代码。
目录
1.环境准备
2.模型下载
编辑
3.RAG实战
3.1 索引(indexing)
#“def __init__
#def get_embeddings
3.2 检索 (retrieval)
1.def __init__
2.def get_similarity
3.def query
3.3 生成(generation)
1.def __init__
2.def generate
1.环境准备
复制代码
<code>git lfs install
git clone https://www.modelscope.cn/datasets/Datawhale/AICamp_yuan_baseline.git
cp AICamp_yuan_baseline/Task\ 3:源大模型RAG实战/* .
git lfs install
:这是安装 Git Large File Storage(Git LFS)的命令。Git LFS 用于处理大型文件,例如二进制文件、图像、音频等,以提高 Git 对大型文件的处理效率。
git clone https://www.modelscope.cn/datasets/Datawhale/AICamp_yuan_baseline.git
:这是使用 Git 克隆一个远程仓库的命令。它会从指定的 URL 下载该仓库的所有内容到本地。
cp AICamp_yuan_baseline/Task 3:源大模型 RAG 实战/*.
:这是一个复制命令。cp
是复制的命令,它会将 AICamp_yuan_baseline
目录下的 Task 3:源大模型 RAG 实战
子目录中的所有文件复制到当前目录(.
表示当前目录)。
# 查看已安装依赖
pip list
<code># 安装 streamlit
pip install streamlit==1.24.0
2.模型下载
# 向量模型下载
from modelscope import snapshot_download
model_dir = snapshot_download("AI-ModelScope/bge-small-zh-v1.5", cache_dir='.')code>
# 源大模型下载
from modelscope import snapshot_download
model_dir = snapshot_download('IEITYuan/Yuan2-2B-Mars-hf', cache_dir='.')code>
#向量模型下载
这里使用的是 modelscope 中的 snapshot_download 函数,第一个参数为模型名称 AI-ModelScope/bge-small-zh-v1.5
,第二个参数 cache_dir
为模型保存路径,这里.
表示当前路径。
#下载源大模型
同理。
3.RAG实战
3.1 索引(indexing)
class EmbeddingModel:
"""
class for EmbeddingModel
"""
def __init__(self, path: str) -> None:
self.tokenizer = AutoTokenizer.from_pretrained(path)
self.model = AutoModel.from_pretrained(path).cuda()
print(f'Loading EmbeddingModel from {path}.')
def get_embeddings(self, texts: List) -> List[float]:
"""
calculate embedding for text list
"""
encoded_input = self.tokenizer(texts, padding=True, truncation=True, return_tensors='pt')code>
encoded_input = {k: v.cuda() for k, v in encoded_input.items()}
with torch.no_grad():
model_output = self.model(**encoded_input)
sentence_embeddings = model_output[0][:, 0]
sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
return sentence_embeddings.tolist()
关键点解析:
#“def __init__
1.关于类的知识。
类的属性(定义类的变量,成员变量),类的行为(定义类的函数,成员方法)`def __init__(self, path: str) -> None:` 是 Python 中类的特殊方法 `__init__` 的定义。 `__init__` 方法是类的初始化方法,当创建类的实例时会自动调用这个方法来进行一些初始化的操作。 在这个定义中: - `self` 是必需的参数,它代表类的实例本身。通过 `self`,可以在方法内部访问和修改实例的属性。 - `path: str` 是另一个参数,它被明确标注为字符串类型。这意味着在调用这个初始化方法时,必须传入一个字符串类型的参数给 `path` 。 - `-> None` 表示这个方法不返回任何值。
2.`AutoTokenizer.from_pretrained` 是 `transformers` 库中的一个方法。
它的作用是从预训练的模型存储路径(或名称)加载相应的分词器(Tokenizer)。 例如,如果有一个预训练的语言模型,比如 `bert-base-uncased` ,通过指定 `AutoTokenizer.from_pretrained('bert-base-uncased')` 就可以获取为这个模型专门配置的分词器,该分词器知道如何将输入的文本按照该模型所要求的方式进行分词、添加特殊标记等预处理操作。
3.`AutoTokenizer.from_pretrained()` 和 `AutoModel.from_pretrained().cuda()` 的区别
1. 功能不同: - `AutoTokenizer.from_pretrained()` 用于加载与预训练模型配套的分词器(Tokenizer),其主要作用是对输入的文本进行分词、添加特殊标记等预处理操作,以便模型能够正确理解和处理输入。 - `AutoModel.from_pretrained().cuda()` 用于加载预训练的模型,并将其放置到 GPU 上。模型负责对经过分词处理的输入进行计算和推理,以得到相应的输出。
2. 处理对象不同: - 前者处理的是文本数据,将文本转换为适合模型输入的格式。 - 后者处理的是经过预处理后的数值型输入数据,并进行模型的计算和推理。
3. 设备不同(在后者使用了 `.cuda()` 的情况下): - `AutoTokenizer.from_pretrained()` 加载的分词器通常在 CPU 上运行。 - `AutoModel.from_pretrained().cuda()` 加载的模型被放置在 GPU 上运行,以利用 GPU 的加速能力。 例如,假设有一个文本分类任务,首先使用 `AutoTokenizer.from_pretrained()` 对输入的文本进行分词和编码,然后将编码后的结果输入到通过 `AutoModel.from_pretrained().cuda()` 加载并放置在 GPU 上的模型中进行分类预测。
4.将 `AutoModel.from_pretrained().cuda()` 加载的模型放置在 GPU 上运行的原因:
1. 并行计算能力 - GPU 具有大量的并行处理核心,可以同时处理多个计算任务。在深度学习中,模型的计算通常可以被高度并行化,例如对不同的数据样本同时进行前向传播和反向传播计算。
2. 高内存带宽 - 深度学习模型通常需要处理大量的数据,包括模型参数和输入数据。GPU 具有更高的内存带宽,能够更快地传输数据,减少数据传输的时间开销。
3. 矩阵运算优化 - 深度学习中的许多计算,如卷积操作、矩阵乘法等,都是以大规模矩阵运算为主。GPU 针对这些矩阵运算进行了硬件优化,能够更高效地完成这些计算。
4. 缩短训练时间 - 对于大型深度学习模型的训练,使用 GPU 可以显著缩短训练时间。例如,在处理图像识别任务时,使用 GPU 可能会将训练时间从几天缩短到几个小时。
5. 实时推理需求 - 在一些实时应用场景,如在线图像分类、语音识别等,需要快速对输入数据进行推理并给出结果。GPU 的加速能力可以满足这些实时性要求。 举个例子,如果要训练一个深度神经网络来识别大量的图像,使用 CPU 可能需要数周甚至数月的时间,但使用 GPU 可能在几天内就能完成训练。在实时的图像分类应用中,使用 GPU 可以在几毫秒内给出分类结果,保证了应用的实时响应性能。
5.CPU(中央处理器)和 GPU(图形处理器)的区别
请根据4推理
#def get_embeddings
1.`get_embeddings(self, texts: List) -> List[float]` 是一个类方法的定义。
以下是对其各部分的解释: - `get_embeddings` :方法的名称,用于获取嵌入向量。 - `self` :表示该方法是类的实例方法,通过它可以访问类的属性和其他方法。 - `texts: List` :这是方法的参数,参数名为 `texts` ,类型为 `List` ,表示输入的是一个文本列表。 - `-> List[float]` :表示方法的返回值类型为一个包含浮点数的列表。 这个方法的作用是接收一个文本列表,并经过一系列处理计算后,返回对应的嵌入向量列表(每个向量由浮点数组成)。
2.padding
在自然语言处理和深度学习中,`padding` 通常指的是在输入数据(如文本序列)中添加额外的元素(通常是零或特定的填充值),以使不同长度的输入序列具有相同的长度。 例如,假设有两个文本序列:`["I", "like", "cats"]` 和 `["Dogs", "are", "great", "pets"]` 。为了能够将它们一起输入到需要固定长度输入的模型中(例如某些神经网络架构),可以对较短的序列进行 `padding` 。 比如,假设我们希望所有序列的长度都为 5 ,那么第一个序列就会被填充为 `["I", "like", "cats", "PAD", "PAD"]` ,其中 `PAD` 表示填充值。 `padding` 的主要目的是为了能够以批量的方式处理数据,提高计算效率,并且使模型能够适应不同长度的输入。
3.truncation
在自然语言处理中,`truncation`(截断)指的是当输入的文本序列长度超过了预设的最大长度时,将其截断为指定的长度。 例如,如果设定最大长度为 10 个单词,而输入的文本序列是 `["This", "is", "a", "very", "long", "sentence", "that", "needs", "truncation"]` ,经过 `truncation` 操作后,可能会变成 `["This", "is", "a", "very", "long", "sentence", "that", "needs", "trunc"]` ,只保留了前面的部分。 这样做的目的通常是为了满足模型对输入长度的限制,避免处理过长的序列导致计算复杂度增加或内存不足等问题。
4.return_tensors
`return_tensors` 是在处理文本数据时的一个参数,用于指定返回的数据格式。 当 `return_tensors='pt'` 时,表示希望返回的是 PyTorch 张量(`torch.Tensor`)格式的数据。 除了 `'pt'` (代表 PyTorch),还可能有其他常见的值,例如 `'tf'` 表示返回 TensorFlow 的张量格式, `'np'` 表示返回 NumPy 的数组格式等。 通过指定 `return_tensors` 的值,可以根据后续使用的框架或计算环境的需求,获取相应格式的数据,以便更方便地进行后续的计算和处理
5. {k: v.cuda() for k, v in encoded_input.items()}
在 {k: v.cuda() for k, v in encoded_input.items()} 中,k: v.cuda() 是字典推导式中的键值对表达式。
k 表示原始字典 encoded_input 中的键,v.cuda() 表示将对应的值 v 移动到 GPU 上。这行代码使用了字典推导式来创建一个新的字典。 它的作用是遍历 `encoded_input` 字典中的每个键值对 `(k, v)` ,将值 `v` 移动到 GPU 上(使用 `.cuda()` 方法),并以键值对的形式 `(k: v.cuda())` 组成新的字典。 例如,如果 `encoded_input = {'input_ids': torch.tensor([1, 2, 3]), 'attention_mask': torch.tensor([0, 1, 1])}` ,那么经过这行代码处理后,得到的新字典中这两个张量都被放置在了 GPU 上。
6.`with torch.no_grad():`
是一个上下文管理器,用于在其内部执行的代码块中禁用梯度计算Gradient calculation。 在深度学习中,计算梯度通常用于模型的训练以进行参数更新。但在某些情况下,比如在模型的推理阶段或者只需要进行前向传播计算而不需要计算梯度时,使用 `torch.no_grad()` 可以节省内存和计算资源,因为不需要跟踪计算图来计算梯度。 例如,当您只想获取模型的输出而不进行反向传播更新参数时,可以将相关的计算放在这个上下文管理器中。
7.model_output = self.model(**encoded_input)
这行代码使用了之前创建的经过处理并移动到 GPU 上的输入 encoded_input ,将其传递给类中的模型 self.model 进行计算,并将计算结果存储在 model_output 变量中。
这里的 **encoded_input 是将字典 encoded_input 中的键值对作为关键字参数传递给模型的方法。模型会根据输入的数据进行相应的前向传播计算,并返回输出结果
8.sentence_embeddings = model_output[0][:, 0]
这行代码从模型的输出 `model_output` 中提取特定的部分来获取句子嵌入向量 (`sentence_embeddings`) 。 具体来说: - `model_output[0]` 通常是指模型输出的某个特定部分,可能是某个特定的层或者某种特定类型的输出。 - `[:, 0]` 是一种索引操作。`:` 表示选择所有的行,`0` 表示选择第 0 列。所以整体上是选择了 `model_output[0]` 中的所有行的第 0 列元素,将其赋值给 `sentence_embeddings` 。
9.sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
这段代码使用了 PyTorch 中的函数来对 sentence_embeddings
进行处理torch.nn.functional.normalize
函数用于对输入的张量进行归一化操作。参数 p=2
表示使用 L2 范数进行归一化。L2 范数的计算方式是对张量元素的平方和进行平方根运算。参数 dim=1
则指定了在哪个维度上进行归一化。在这个例子中,是在第 1 个维度上进行归一化。
10.归一化
归一化是一种数据预处理技术,它将数据映射到一个特定的范围或遵循特定的分布,使得不同的数据具有可比性和一致性。 归一化的主要目的包括:
1. 消除量纲影响:不同特征可能具有不同的量纲(例如长度用米,重量用千克)。归一化可以消除这种量纲差异,使不同特征在数值上处于相似的范围,避免某些特征因为数值过大或过小而在模型中占据主导地位。
2. 提高模型稳定性和收敛速度:对于一些机器学习算法和模型,输入数据的数值范围较小且分布均匀时,模型的训练通常更加稳定,收敛速度也可能更快。
3. 便于比较和分析:归一化后的数据更容易进行比较和分析,能够更直观地看出数据之间的相对大小和关系。
常见的归一化方法包括:
1. 最小-最大归一化(Min-Max Normalization):将数据映射到指定的范围,通常是 [0, 1] 。计算公式为:`X_normalized = (X - X_min) / (X_max - X_min)` ,其中 `X` 是原始数据,`X_min` 是数据的最小值,`X_max` 是数据的最大值。
2. Z-score 标准化(Standardization):也称为均值归一化,使数据的均值为 0 ,标准差为 1 。计算公式为:`X_normalized = (X - X_mean) / X_std` ,其中 `X_mean` 是数据的均值,`X_std` 是数据的标准差。 例如,对于一组身高数据 `[150, 160, 170, 180, 190]` ,如果进行最小-最大归一化到 [0, 1] 范围,可能会变成 `[0, 0.2, 0.4, 0.6, 0.8]` ;如果进行 Z-score 标准化,可能会变成 `[-1.58, -0.79, 0, 0.79, 1.58]` (假设均值为 170 ,标准差为 15.81 )。
11.L2范数以及对L2范数进行归一化
L2 范数(也称为欧几里得范数)是一种用于衡量向量长度或大小的方法。对于一个向量 v = [v1, v2, v3,..., vn] ,其 L2 范数 ||v||₂ 的计算方式为:||v||₂ = √(v1² + v2² + v3² +... + vn²)。例如,对于向量 v = [3, 4] ,其 L2 范数为:||v||₂ = √(3² + 4²) = √(9 + 16) = √25 = 5
在使用 L2 范数进行归一化时,就是将向量的每个元素除以该向量的 L2 范数,使得归一化后的向量的 L2 范数为 1 。例如,对于向量 v = [3, 4] ,其 L2 范数为 5 ,归一化后的向量为 [3/5, 4/5] 。L2 范数归一化在很多领域都有应用,比如在机器学习中,对特征向量进行归一化可以提高模型的性能和稳定性。
12.沿着第 1 个维度进行归一化操作。
假设我们有一个张量 tensor = torch.tensor([[1, 2, 3], [4, 5, 6]]) ,它的形状是 (2, 3),张量的形状可以理解为一个具有 2 行 3 列的矩阵。 。当 dim=1 时,就是对每一行分别进行归一化。对于第一行 [1, 2, 3] ,计算其 L2 范数为 sqrt(1^2 + 2^2 + 3^2) = sqrt(14) ,归一化后变为 [1/sqrt(14), 2/sqrt(14), 3/sqrt(14)] 。对于第二行 [4, 5, 6] ,计算其 L2 范数为 sqrt(4^2 + 5^2 + 6^2) = sqrt(77) ,归一化后变为 [4/sqrt(77), 5/sqrt(77), 6/sqrt(77)] 。最终得到归一化后的张量。
print("> Create embedding model...")
embed_model_path = './AI-ModelScope/bge-small-zh-v1___5'
embed_model = EmbeddingModel(embed_model_path)
新建一个 EmbeddingModel
对象 embed_model
3.2 检索 (retrieval)
# 定义向量库索引类
class VectorStoreIndex:
"""
class for VectorStoreIndex
"""
def __init__(self, doecment_path: str, embed_model: EmbeddingModel) -> None:
self.documents = []
for line in open(doecment_path, 'r', encoding='utf-8'):code>
line = line.strip()
self.documents.append(line)
self.embed_model = embed_model
self.vectors = self.embed_model.get_embeddings(self.documents)
print(f'Loading {len(self.documents)} documents for {doecment_path}.')
def get_similarity(self, vector1: List[float], vector2: List[float]) -> float:
"""
calculate cosine similarity between two vectors
"""
dot_product = np.dot(vector1, vector2)
magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2)
if not magnitude:
return 0
return dot_product / magnitude
def query(self, question: str, k: int = 1) -> List[str]:
question_vector = self.embed_model.get_embeddings([question])[0]
result = np.array([self.get_similarity(question_vector, vector) for vector in self.vectors])
return np.array(self.documents)[result.argsort()[-k:][::-1]].tolist()
1.def __init__
这一段就是定义成员变量,与之前的EmbeddingModel联系起来,比如用到了新建的类对象embed_model,还用到了前面定义的成员方法get_embeddings()
2.
def get_similarity
这一段就是计算两个向量 vector1 和 vector2 的余弦相似度。用到了NumPy的函数。
dot_product = np.dot(vector1, vector2) :计算两个向量的点积。
magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2) :计算两个向量范数的乘积。norm范数。这行代码使用 NumPy 的 linalg.norm 函数计算向量 vec1 的范数(norm)。默认是L2范数,不知道L2范数请往前翻。
3.def query
result = np.array([self.get_similarity(question_vector, vector) for vector in self.vectors])。np.array()
函数:用于创建一个 NumPy 数组。将返回的相似度值都放入数组中。
return np.array(self.documents)[result.argsort()[-k:][::-1]].tolist()。
这行代码执行了以下几个主要操作: 1. `result.argsort()` :对 `result` 数组进行排序,并返回排序后的索引。 2. `[-k:]` :获取排序后的索引的后 `k` 个元素。 3. `[::-1]` :对获取的后 `k` 个索引进行反转,得到最终的前 `k` 个最大相似度的索引。 4. `np.array(self.documents)[...]` :根据上述得到的前 `k` 个索引,从 `self.documents` 转换得到的 NumPy 数组中选取相应的元素。 5. `.tolist()` :将选取的元素转换为列表并返回。 总的来说,这行代码的作用是根据相似度的排序结果,获取前 `k` 个最相似的文档,并将它们转换为列表返回。
print("> Create index...")
doecment_path = './knowledge.txt'
index = VectorStoreIndex(doecment_path, embed_model)
question = '介绍一下广州大学'
print('> Question:', question)
context = index.query(question)
print('> Context:', context)
这里我们传入用户问题 介绍一下广州大学
,可以看到,准确地返回了知识库中的第一条知识。
3.3 生成(generation)
# 定义大语言模型类
class LLM:
"""
class for Yuan2.0 LLM
"""
def __init__(self, model_path: str) -> None:
print("Creat tokenizer...")
self.tokenizer = AutoTokenizer.from_pretrained(model_path, add_eos_token=False, add_bos_token=False, eos_token='<eod>')code>
self.tokenizer.add_tokens(['<sep>', '<pad>', '<mask>', '<predict>', '<FIM_SUFFIX>', '<FIM_PREFIX>', '<FIM_MIDDLE>','<commit_before>','<commit_msg>','<commit_after>','<jupyter_start>','<jupyter_text>','<jupyter_code>','<jupyter_output>','<empty_output>'], special_tokens=True)
print("Creat model...")
self.model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.bfloat16, trust_remote_code=True).cuda()
print(f'Loading Yuan2.0 model from {model_path}.')
def generate(self, question: str, context: List):
if context:
prompt = f'背景:{context}\n问题:{question}\n请基于背景,回答问题。'
else:
prompt = question
prompt += "<sep>"
inputs = self.tokenizer(prompt, return_tensors="pt")["input_ids"].cuda()code>
outputs = self.model.generate(inputs, do_sample=False, max_length=1024)
output = self.tokenizer.decode(outputs[0])
print(output.split("<sep>")[-1])
1.def __init__
这一步就是加载自动分词器与因果语言模型。
AutoModelForCausalLM.from_pretrained
是 Hugging Face
的 transformers
库中的一个方法。
它的作用是从预训练的模型中加载一个因果语言模型(Causal Language Model)。
预训练的模型通常是在大规模的文本数据上进行训练得到的,具有对语言的通用理解能力。
通过这个方法,可以方便地获取已经训练好的、具有一定语言生成能力的模型,并基于此进行进一步的微调(Fine-tuning)或者直接用于生成任务。
2.def generate
1.inputs = self.tokenizer(prompt, return_tensors="pt")["input_ids"].cuda()
<code>self.tokenizer(prompt) 对 prompt
进行分词操作。return_tensors="pt"code> 表示要求返回的结果是
PyTorch
张量格式。["input_ids"]
从返回的结果中获取 input_ids
这一部分,input_ids
通常是对输入文本的数字编码表示。.cuda()
将这个张量移到 GPU
上,以便在 GPU
上进行后续的计算,提高计算效率。例如,如果 prompt
是 "Hello, how are you?" ,经过分词和处理后,得到的 input_ids
张量可能类似于 tensor([101, 7592, 1010, 2054, 102])
,然后这个张量会被移到 GPU
上准备用于后续与模型的交互
2.outputs = self.model.generate(inputs, do_sample=False, max_length=1024)
这行代码使用 self.model
(之前加载的语言模型)根据给定的输入 inputs
生成输出。
inputs
:这是经过预处理和编码的输入数据,为模型提供了起始信息。do_sample=False
:当设置为 False
时,模型在生成输出时不会进行随机采样。这意味着生成的结果将更具确定性和可重复性。如果设置为 True
,模型会根据概率分布进行随机选择,生成的结果可能会有所不同。max_length=1024
:指定了生成输出的最大长度。这可以防止模型生成过长且可能无意义或不相关的输出。
例如,如果输入 inputs
提供了一些初始的文本提示,模型会基于此生成后续的文本,生成的文本长度不会超过 1024 个词元(tokens),并且生成的方式是确定性的,而不是基于随机采样。
3.output = self.tokenizer.decode(outputs[0]
这行代码使用之前加载的分词器 self.tokenizer
对模型生成的输出 outputs[0]
进行解码操作。解码的目的是将模型生成的数字编码(通常是整数形式的 token
索引)转换回人类可读的文本形式。例如,如果模型生成的 outputs[0]
是一个包含一系列整数 token
索引的张量,比如 [101, 7592, 2088, 102]
,通过 self.tokenizer.decode
方法,可能会将其转换为类似 "Hello world"
这样的可读文本。
print("> Create Yuan2.0 LLM...")
model_path = './IEITYuan/Yuan2-2B-Mars-hf'
llm = LLM(model_path)
这里我们传入 Yuan2-2B-Mars
的模型路径,新建一个 LLM
对象 llm
。
LLM
类的入口是生成函数 generate()
,它有两个参数:
question
: 用户提问,是一个str
context
: 检索到的上下文信息,是一个List,默认是[],代表没有使用RAG
print('> Without RAG:')
llm.generate(question, [])
print('> With RAG:')
llm.generate(question, context)
这里我们的question与context是前面写好的。请翻到本篇3.2.3
结果如下:(图片好像放不了只能这样了)
Without RAG:
广州大学成立于1952年,是中华广州大学(Guangzhou University)是广东省内一所综合性大学,位于中国广东省广州市。前身为广州工学院,人民共和国成立后创建的第一所高等工科院校。广州大学坐落在广州市海珠区,占地面积广阔,校园环境优美。学校拥有多个校区,其中主校区位于广州市番禺区,其他校区分布在广州市的其他地区。学校占地面积约4800亩,拥有现代化的教学、实验和生活设施。广州大学以培养人才为宗旨,注重理论与实践相结合的教学模式。学校开设了多个学院和专业,涵盖了工学、理学、文学、法学、经济学、管理学、艺术学等多个领域。学校现有本科专业近388个,研究生专业涵盖科学、工程、管理、文学、法学、艺术等多个领域。广州大学注重国际交流与合作,积极推进国际化办学。学校与许多国际知名大学建立了合作关系,开展学术交流和合作研究。此外,学校还鼓励学生参与国际交流项目,提供海外实习和留学机会,提升学生的国际视野和能力。广州大学一直以来致力于为学生提供优质的教育环境和丰富的学习资源。学校拥有先进的教学设施和实验室,以及图书馆、体育场馆、艺术工作室等丰富的学生课外活动设施。广州大学以其优秀的教学质量、领先的科研水平和培养优秀学生的能力而闻名。学校致力于培养具有创新精神和社会责任感的高素质人才,为地方经济发展和社会进步做出贡献。<eod>>
With RAG:
广州大学是一所位于广东省广州市的全日制普通高等学校,实行省市共建、以市为主的办学体制。学校的办学历史可以追溯到1927年创办的私立广州大学,后来在1951年并入华南联合大学。1984年定名为广州大学。2888年,广州大学经过教育部批准,与广州教育学院、广州师范学院、华南建
设学院西院、广州高等师范专科学校合并组建新的广州大学。<eod>
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。