【大模型实战篇】大模型分词算法Unigram及代码示例
1. 算法原理介绍
与 BPE 分词(参考《BPE原理及代码示例》)和 WordPiece 分词(参考《WordPiece原理及代码示例》)不同,Unigram 分词方法【1】是从一个包含足够多字符串或词元的初始集合开始,迭代地删除其中的词元,直到达到预期的词表大小。该方法假设通过删除某个词元能够增加训练语料的似然性,并以此作为选择标准。这个过程依赖于一个训练好的单一语言模型。
为了估计单一语言模型,在每次迭代中,首先根据旧的语言模型找到当前最优的分词方式,然后重新估计单一概率以更新语言模型。通常,使用动态规划算法(如维特比算法,Viterbi Algorithm)来高效找到语言模型对词汇的最优分词方案。
Unigram算法通常用于SentencePiece,这是AlBERT、T5、mBART、Big Bird和XLNet等模型使用的分词算法。如前述,Unigram训练算法与BPE和WordPiece相比,Unigram的工作方式相反:它从一个大词汇开始,逐步删除其中的标记,直到达到所需的词汇大小。可以使用多种方法构建基础词汇,例如提取预分词单词中最常见的子串,或在具有较大词汇量的初始语料库上应用BPE。
在训练的每一步,Unigram算法根据当前词汇计算语料库的损失。然后,对于词汇中的每个符号,算法计算如果移除该符号整体损失将增加多少,并寻找对整体损失影响最小的符号。这些符号对语料库的整体损失影响较小,因此在某种意义上,它们是“需求较低”的,最适合被删除。可以预见,这个过程非常耗时。需要注意基本字符绝不删除,以确保任何单词都可以被分词。
2. 案例说明
重用在BPE中的语料库:
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
在这个例子中,将所有严格子串作为初始词汇:
["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"]
分词算法 Unigram模型是一种语言模型,认为每个标记与之前的标记是独立的。这是最简单的语言模型,因为给定之前的上下文,标记X的概率仅为标记X的概率。因此,如果使用Unigram语言模型生成文本,总是会预测最常见的标记。给定标记的概率是其在原始语料库中的频率(出现次数),除以词汇中所有标记频率的总和(以确保概率总和为1)。例如,“ug”出现在“hug”、“pug”和“hugs”中,因此在当前语料库中它的频率为20。
以下是词汇中所有可能子词的频率:
("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16) ("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5)
因此,所有频率的总和为210,而子词“ug”的概率为20/210。
现在,为了对给定单词进行分词,查看所有可能的分词方式,并根据Unigram模型计算每种方式的概率。由于所有标记被视为独立,因此该概率只是每个标记概率的乘积。例如,单词“pug”的分词["p", "u", "g"]的概率为:
P ( [ "p" , "u" , "g" ] ) = P ( "p" ) × P ( "u" ) × P ( "g" ) = 5/210 × 36/210 × 20/210= 0.000389
相比之下,分词["pu", "g"]的概率为:
P ( [ "pu" , "g" ] ) = P ( "pu" ) × P ( "g" ) = 5/210 × 20/210 = 0.0022676
因此,这种分词的概率要高得多。一般来说,标记数量最少的分词将具有最高的概率(因为对于每个标记都有210的除法),这对应于直观上的目的:将单词分成尽可能少的标记。
使用Unigram模型对单词进行分词的结果是概率最高的分词。在“pug”的例子中,为每个可能的分词计算的概率如下:
["p", "u", "g"] : 0.000389
["p", "ug"] : 0.0022676
["pu", "g"] : 0.0022676
因此,“pug”的分词将为["p", "ug"]或["pu", "g"],具体取决于首先遇到的分词。
在这个情况下,很容易找到所有可能的分词并计算它们的概率,但通常这会更困难。采用维特比算法,可以构建一个图来检测给定单词的可能分词,通过说如果从字符a到字符b有一个子词,则从a到b有一个分支,并将子词的概率赋予该分支。
为了找到在该图中具有最佳分数的路径,维特比算法确定每个单词位置的最佳分词,它结束于该位置。由于是从开始到结束,因此通过遍历所有以当前字符结束的子词来找到最佳分数,然后使用该子词起始位置的最佳分词分数。接下来,只需解开到达结尾的路径。
看一个使用我们的词汇和单词“unhug”的例子。对于每个位置,最佳分数的子词如下:
字符0 (u): "u" (分数0.171429)
字符1 (n): "un" (分数0.076191)
字符2 (h): "un" "h" (分数0.005442)
字符3 (u): "un" "hu" (分数0.005442)
字符4 (g): "un" "hug" (分数0.005442)
因此,“unhug”的分词将为["un", "hug"]。
基于上述例子,已经了解了分词是如何工作的,进一步探讨训练期间使用的损失函数。在任何给定阶段,这个损失是通过对语料库中的每个单词进行分词计算的,使用当前的词汇表和根据每个标记在语料库中的频率确定的单语模型。
语料库中的每个单词都有一个分数,损失则是这些分数的负对数似然——即语料库中所有单词的 -log(P(单词)) 的总和。
回到以下示例语料库:
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
每个单词的分词及其相应的分数为:
"hug": ["hug"] (分数 0.071428)
"pug": ["pu", "g"] (分数 0.007710)
"pun": ["pu", "n"] (分数 0.006168)
"bun": ["bu", "n"] (分数 0.001451)
"hugs": ["hug", "s"] (分数 0.001701)
因此,损失为:
10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8
现在需要计算每个标记的移除如何影响损失。为了简化,只为两个标记进行计算,并将整个过程留到有代码帮助时再进行。在这个特殊的情况下,有两个等价的标记化:例如,"pug" 可以标记为 ["p", "ug"],并具有相同的分数。因此,从词汇表中移除 "pu" 将产生相同的损失。
另一方面,移除 "hug" 将导致损失增加,因为 "hug" 和 "hugs" 的标记化将变为:
"hug": ["hu", "g"] (分数 0.006802)
"hugs": ["hu", "gs"] (分数 0.001701)
这些变化将导致损失上升:
10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5
因此,标记 "pu" 可能会从词汇表中移除,但 "hug" 则不会。
3. 代码实现
依然沿用在BPE中的语料,在jupyter环境进行测试。本来我们验证的预训练模型为xlnet-base-cased,但model scope没有对应的,所以我们更换成T5,效果是一样的,因为使用的分词器是一样的。
corpus = ["This is the Hugging Face Course.","This chapter is about tokenization.","This section shows several tokenizer algorithms.","Hopefully, you will be able to understand how they are trained and generate tokens.",
]
同样,下载和加载T5预训练模型:
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
from transformers import AutoTokenizermodel_dir = snapshot_download('AI-ModelScope/t5-base', cache_dir='/root/autodl-tmp', revision='master')
mode_name_or_path = '/root/autodl-tmp/AI-ModelScope/t5-base'
tokenizer = AutoTokenizer.from_pretrained(mode_name_or_path, trust_remote_code=True)
统计预分词的词频:
from collections import defaultdictword_freqs = defaultdict(int)
for text in corpus:words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)new_words = [word for word, offset in words_with_offsets]for word in new_words:word_freqs[word] += 1word_freqs
接下来,需要将词汇初始化为一个比我们最终希望的词汇量更大的值。必须包含所有基本字符(否则将无法标记每个单词),但对于较大的子字符串,将仅保留最常见的,因此按频率对它们进行排序:
char_freqs = defaultdict(int)
subwords_freqs = defaultdict(int)
for word, freq in word_freqs.items():for i in range(len(word)):char_freqs[word[i]] += freq# 循环遍历至少长度为2的子单词for j in range(i + 2, len(word) + 1):subwords_freqs[word[i:j]] += freq# 按频率对子单词进行排序
sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True)
sorted_subwords[:10]
将字符与最佳子单词分组,以达到初始词汇大小 300:
token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)]
token_freqs = {token: freq for token, freq in token_freqs}
接下来,计算所有频率的总和,将频率转换为概率。对于我们的模型,将存储概率的对数,因为将对数相加比乘以小数字更数值稳定,并且这将简化模型损失的计算:
from math import logtotal_sum = sum([freq for token, freq in token_freqs.items()])
model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()}
现在,主要函数是使用维特比算法进行单词标记化。如前所述,该算法计算每个子字符串的最佳分段,将其存储在名为 best_segmentations 的变量中。为单词中的每个位置(从 0 到单词的总长度)存储一个字典,包含两个键:最佳分段中最后一个标记的起始索引,以及最佳分段的分数。通过最后一个标记的起始索引,将能够在列表完全填充后检索完整的分段。
填充列表只需两个循环:主循环遍历每个起始位置,第二个循环尝试从该起始位置开始的所有子字符串。如果子字符串在词汇中,我们就得到了到该结束位置的单词的新分段,将其与 best_segmentations 中的内容进行比较。一旦主循环完成,只需从末尾开始,跳过一个起始位置,记录标记,直到到达单词的起始位置:
def encode_word(word, model):best_segmentations = [{"start": 0, "score": 1}] + [{"start": None, "score": None} for _ in range(len(word))]for start_idx in range(len(word)):# This should be properly filled by the previous steps of the loopbest_score_at_start = best_segmentations[start_idx]["score"]for end_idx in range(start_idx + 1, len(word) + 1):token = word[start_idx:end_idx]if token in model and best_score_at_start is not None:score = model[token] + best_score_at_start# If we have found a better segmentation ending at end_idx, we updateif (best_segmentations[end_idx]["score"] is Noneor best_segmentations[end_idx]["score"] > score):best_segmentations[end_idx] = {"start": start_idx, "score": score}segmentation = best_segmentations[-1]if segmentation["score"] is None:# We did not find a tokenization of the word -> unknownreturn ["<unk>"], Nonescore = segmentation["score"]start = segmentation["start"]end = len(word)tokens = []while start != 0:tokens.insert(0, word[start:end])next_start = best_segmentations[start]["start"]end = startstart = next_starttokens.insert(0, word[start:end])return tokens, score
在一些单词上尝试下初始模型:
计算模型在语料库上的损失:
def compute_loss(model):loss = 0for word, freq in word_freqs.items():_, word_loss = encode_word(word, model)loss += freq * word_lossreturn loss
计算删除每个标记后模型的损失:
import copydef compute_scores(model):scores = {}model_loss = compute_loss(model)for token, score in model.items():# 始终保留长度为1的标记if len(token) == 1:continuemodel_without_token = copy.deepcopy(model)_ = model_without_token.pop(token)scores[token] = compute_loss(model_without_token) - model_lossreturn scores
由于 "ll" 用于 "Hopefully" 的标记化,移除它可能会导致使用标记 "l" 两次,因此预期它会产生正损失。
这种方法效率非常低,因此 SentencePiece 使用了一个近似的模型损失计算:它并不是从头开始,而是用剩余词汇中标记 X 的分割替换标记 X。这样,所有分数可以在计算模型损失的同时一次性计算出来。在此基础上,需要做的最后一件事是将模型使用的特殊标记添加到词汇表中,然后循环直到我们从词汇表中修剪出足够的标记以达到所需大小:
percent_to_remove = 0.1
while len(model) > 100:scores = compute_scores(model)sorted_scores = sorted(scores.items(), key=lambda x: x[1])# 移除具有最低分数的 percent_to_remove 标记。for i in range(int(len(model) * percent_to_remove)):_ = token_freqs.pop(sorted_scores[i][0])total_sum = sum([freq for token, freq in token_freqs.items()])model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()}
然后,为了对一些文本进行标记化,只需应用预标记化,然后使用 encode_word()
函数:
def tokenize(text, model):words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)pre_tokenized_text = [word for word, offset in words_with_offsets]encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text]return sum(encoded_words, [])tokenize("This is the amazing course.", model)
4. 参考材料
【1】Unigram tokenization
相关文章:
【大模型实战篇】大模型分词算法Unigram及代码示例
1. 算法原理介绍 与 BPE 分词(参考《BPE原理及代码示例》)和 WordPiece 分词(参考《WordPiece原理及代码示例》)不同,Unigram 分词方法【1】是从一个包含足够多字符串或词元的初始集合开始,迭代地删除其中的…...
Dockerfile搭建ELK
使用 Dockerfile 安装 ELK 一、引言 ELK Stack(Elasticsearch, Logstash, Kibana)是一种流行的日志管理和分析解决方案。它允许用户实时搜索、分析和可视化日志数据。通过 Docker,可以方便地部署 ELK ,快速获取一个功能齐全的日…...
在合规的地方怎么用EACO地球链兑换交换价值?
地球链EACO(EarthChain,简称$E)是一种虚拟数字资产。 目前在中国大陆,虚拟资产相关业务活动属于金融活动,包括虚拟资产的交易、兑换等操作,因此应该谨慎去寻求如何用它来交换价值。 虚拟资产交易炒作活动&…...
VS无法安装Win10SDK_10.0.2200,快捷方法
Visual Studio无法安装Win10SDK_10.0.2200,我在安装VS2019、2022提示,软件就不能编译。 因为之前安装过VS软件,重新安装软件提示“无法安装”。 原因 之前安装在D盘,现在没有D盘了 说明 因为电脑第一次安装VS,会自动安…...
github多个账号配置多个SSH秘钥
背景 对于有多个github账号的同学,需要配置多个ssh秘钥分别管理多个账号。 方法 1、生成多个SSH秘钥 # 为第一个 GitHub 账号生成密钥 ssh-keygen -t ed25519 -C "your_email_1example.com" -f ~/.ssh/id_ed25519_github_work# 为第二个 GitHub 账号生…...
静态/动态代理详解,一次性看完再也不会搞不清!
代理官方原文翻译: 给其他对象提供一个代理或者占位符,来控制对这个对象的访问。 代理最核心的思想: 在客户端和目标对象之间创建一个“中介”,用于保护目标对象和增强目标对象 静态代理: 该代理对象需要我们手动…...
Webserver(2)GCC
目录 安装GCCVScode远程连接到虚拟机编写代码gcc编译过程gcc与g的区别Xftp连接虚拟机上传文件 安装GCC sudo apt install gcc g查看版本是7.5 touch test.c创建代码 但是在虚拟机中写代码很不方便 VScode远程连接到虚拟机编写代码 gcc test.c -o app在虚拟机中用gcc编译的…...
mac电脑设置chrome浏览器语言切换为日语英语等不生效问题
在chrome中设置了语言,并且已经置顶了,但是不生效,在windows上直接有设置当前语言为chrome显示语言,但是mac上没有。 解决办法 在系统里面有一个单独给chrome设置语言的: 单独给它设定成指定的语言,然后重…...
Python中的人工智能框架与实例
在人工智能(AI)领域,Python因其简洁的语法、丰富的库和强大的社区支持,成为了最受欢迎的编程语言之一。本文将详细介绍Python中的人工智能框架,并通过具体实例展示如何使用这些框架来实现不同的人工智能应用。 一、Python中的人工智能框架 …...
论文阅读(二十六):Dual Attention Network for Scene Segmentation
文章目录 1.Introduction3.DANet3.1Position Attention Module3.2Channel Attention Module 论文:Dual Attention Network for Scene Segmentation 论文链接:Dual Attention Network for Scene Segmentation 代码链接:Github 1.Intr…...
Stack和Queue(3)
Stack和Queue(3) priority_queue的模拟实现 priority_queue.h #include <vector>namespace soobin {template<class T, class Container vector<T>>class priority_queue{public://强制生成默认构造priority_queue() default;temp…...
怎样把学生的成绩单独告知家长?
期中考试季的到来让校园里的气氛似乎也变得紧张起来。家长们开始频繁地联系老师,希望了解孩子的表现;孩子们则在考试后,绞尽脑汁地想出各种理由,以期在成绩不理想时能减轻家长的失望。老师们更是忙得不可开交,不仅要批…...
vue3父组件控制子组件表单验证及获取子组件数值方法
1、关键部分的代码如下,我努力交代清楚了,希望能让大家看懂。 <template><KeepAlive><component ref"comp" :is"compNames[steps[compIndex].comp]" /></KeepAlive><el-button click"prevBtn"…...
【JavaEE】【多线程】单例模式
目录 一、设计模式1.1 单例模式1.1.1 饿汉模式1.1.2 懒汉模式 1.2 线程安全问题1.3 懒汉模式线程安全问题的解决方法1.3.1 原子性问题解决1.3.2 解决效率问题1.3.3 解决内存可见性问题和指令重排序问题 一、设计模式 在讲解案例前,先介绍一个概念设计模式ÿ…...
Java.6--多态-设计模式-抽象父类-抽象方法
一、多态 1.定义--什么是多态? a.同一个父类的不同子类对象,在做同一行为的时候,有不同的表现形式,这就是多态。(总结为:一个父类下的不同子类,同一行为,不同表现形式。࿰…...
JAVA Maven 的安装与配置
一、下载地址 官方网站:Maven – Download Apache Maven 我这里是3.8.6版本 二、安装步骤 maven安装之前要先安装jdk,请确保你的系统已经安装了jdk环境。 1.将下载好的 Maven 进行解压 apache-maven-3.6.8-bin.zip 2.配置本地仓库:修改 conf/settin…...
【程序分享】PCB元件坐标对齐工具 V1.3
↑↑↑点击上方蓝字,关注我们! “PCB元件坐标对齐工具 V1.3”脚本程序在PCB文档中将元件的坐标自动移动到参考圆弧的中心,参考圆弧支持机械层1层和禁止布线层,参考图元的位置任意,不局限于栅格位置。 程序会自动…...
[bug] vllm 0.6.1 RuntimeError: operator torchvision::nms does not exist
[bug] vllm 0.6.1 RuntimeError: operator torchvision::nms does not exist 环境 python 3.10 torch 2.4.0cu118 torchvision 0.19.0cu118 vllm 0.6.1.post2cu118问题详情 if torch._C._d…...
处理Hutool的Http工具上传大文件报OOM
程序环境 JDK版本: 1.8Hutool版本: 5.8.25 问题描述 客服端文件上传主要代码: HttpRequest httpRequest HttpUtil.createPost(FILE_UPLOAD_URL); Resource urlResource new UrlResource(url, fileName); httpRequest.form("file&q…...
transforms的使用
示例代码 from PIL import Image from torch.utils.tensorboard import SummaryWriter from torchvision import transforms#打开该图片 img_path"hymenoptera_data/val/bees/10870992_eebeeb3a12.jpg" imgImage.open(img_path) writerSummaryWriter("logs&quo…...
python-PyQt项目实战案例:制作一个视频播放器
文章目录 1. 关键问题描述2. 通过OpenCV读取视频/打开摄像头抓取视频3. 通过PyQt 中的 QTimer定时器实现视频播放4. PyQt 视频播放器实现代码参考文献 1. 关键问题描述 在前面的文章中已经分享了pyqt制作图像处理工具的文章,也知道pyqt通过使用label控件显示图像的…...
反向传播的微积分原理 | Chapter 4 | Deep Learning | 3Blue1Brown
目录 前言1. 简介2. 神经网络中的链式法则3. 微积分的计算4. 公式含义5. 代价函数对权重偏置的敏感度6. 多个神经元的情形7. 回顾相关资料结语 前言 3Blue1Brown 视频笔记,仅供自己参考 这个章节主要来深度讲解反向传播中的一些微积分理论 官网:https://…...
matlab读取excel表格
使用matlab读取excel表格中的数据 使用推荐代码读取excel表格中的数据 path "C:\Users\24975\Desktop\503\GUI展示案例\Tx_20_0_Rx_40_90_0.1_95_L.xlsx";%文件路径 data readtable(path,Sheet,Sheet1,ReadRowNames,false,ReadVariableNames,false,Ra…...
基于springboot+vue实现的助学兼职系统(源码+L文+ppt)4-092
基于springbootvue实现的助学兼职系统(源码L文ppt)4-092 第4章 系统设计 4.1 总体功能设计 一般学生、招聘公司和管理者都需要登录才能进入助学兼职系统,使用者登录时会在后台判断使用的权限类型,包括一般使用者和管理者,一般使…...
⌈ 传知代码 ⌋ 农作物病害分类(Web端实现)
💛前情提要💛 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间,对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…...
CMU生成式人工智能大模型:从入门到放弃(九)
引言 在前面的系列博客中,我们深入探讨了生成式对抗网络(GANs)和变分自编码器(VAEs)等生成式模型。今天,我们将探索扩散模型(Diffusion Models)的进一步应用,并讨论在上…...
HTML基础总结
一、简介 HTML(HyperText Markup Language)即超文本标记语言,是用于创建网页的标准标记语言。它通过使用各种标签来定义网页的结构和内容,告诉浏览器如何显示网页。HTML 文档由标签和文本组成,标签用于描述文本的性质…...
EXCELL中如何两条线画入一张图中,标记坐标轴标题?
1,打开excel,左击选中两列, 2,菜单栏>“插入”>”二维折线图”选中一个 3,选中出现的两条线中的一条右击>最下一行,“设置数据系列格式” 4,右测“系列选项中”>点击“次坐标轴” 5…...
Zabbix企业级分布式监控环境部署
“运筹帷幄之中,决胜千里之外”。在IT运维中,监控占据着重要的地位,按比例来算,说占30%一点也不为过。对IT运维工程师来说,构建一个真正可用的监控告警系统是一项艰巨的任务。在监控系统的开源软件中,可供选…...
水轮发电机油压自动化控制系统解决方案介绍
在现代水电工程中,水轮机组油压自动化控制系统,不仅直接关系到水轮发电机组的安全稳定运行,还影响着整个水电站的生产效率和经济效益。 一、系统概述 国科JSF油压自动控制系统,适用于水轮发电机组调速器油压及主阀(蝶…...
东至县住房和城乡建设网站/seo工具下载
前言 继荣凯同学写的tomcat间Session共享时,tomcat的配置: http://blog.csdn.net/srk950606/article/details/72867537 我来总结一下nginx如何来配置; 文档概述 本文档适用于开始了解Tomcat Session共享技术、需要配置nginx的人使用&…...
企业网站制作心得/外链信息
分层好处: 1,复杂问题简单化:将复杂的任务分解成多层来完成,每一层只处理简单的任务,方便定位问题 2,减少重复开发:规范数据分层,通过中间层数据,能够减少极大的重复计算…...
太原免费网站建设/seo关键词推广话术
好久没写东西了,去年用了angular2的RC版本和ionic2写了一个项目,因为开发周期和有些版本不稳定,所以一直没有升级,ng2新版本引用Aot打包,听说优化还不错,现在尝试升级ionic2、angular2到最新版本࿰…...
wordpress图片轮播插件/网站维护的内容有哪些
1、使用 gitlab 创建合并请求 master 合并到 branch_test --> compare branches and continue --> submit merge request 提示:合并冲突。 2、使用 idea 打开项目。 (1)执行:git fetch origin (2)切换到 master 分支,更新到最新版本。 (3)切换到 branch_test 分支,更…...
做推广网站费用/bt蚂蚁磁力搜索天堂
文章目录前言前言 private int partition(ProducerRecord<K, V> record, byte[] serializedKey, byte[] serializedValue, Cluster cluster) {//如果你的这个消息已经分配了分区号,那直接就用这个分区号就可以了//但是正常情况下,消息是没有分区号…...
兰州微网站建设/湖南网站营销推广
安装postfixpostfix是一个快速、易于管理、安全性高的邮件发送服务,可以配合dovecot实现一个完美的邮箱服务器。1、安装postfix [rootlocalhost ~]# rpm -qa | grep postfix[rootlocalhost ]# yum install -y postfixLoaded plugins: fastestmirror, refresh-packag…...