语谱图(二) Spectrogram 的产生

1. 信号预处理部分

预处理部分中 包括

  1. 预加重
  2. 分帧
  3. 加窗 ;

1.1 读取音频数据

python可以用librosa库来读取音频文件,但是对于MP3文件,它会自动调用audio_read函数,所以如果是MP3文件,务必保证将ffmpeg.exe的路径添加到系统环境变量中,不然audio_read函数会出错。

这里我们首先读取音频文件,并作出0-20秒的波形。

现在的音乐文件采样率通常是44.1kHz

用y和sr分别表示信号和采样率。

代码和图形如下:

import librosa
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
import matplotlib.ticker as ticker#这是一个画图函数,方便后续作图
def personal_plot(x,y):plt.figure(dpi=200,figsize=(12,6))rcParams['font.family']='Comic Sans MS'plt.plot(x,y)plt.xlim(x[0],x[-1])plt.xlabel('time/s',fontsize=20)plt.ylabel('Amplitude',fontsize=20)plt.xticks(fontsize=16)plt.yticks(fontsize=16)plt.grid()#注意如果文件名不加路径,则文件必须存在于python的工作目录中
y,sr = librosa.load('笑颜.mp3',sr=None)#这里只获取0-20秒的部分,这里也可以在上一步的load函数中令duration=20来实现
tmax,tmin = 20,0
t = np.linspace(tmin,tmax,(tmax-tmin)*sr)
personal_plot(t,y[tmin*sr:tmax*sr])

在这里插入图片描述

1.2 预加重

通常来讲语音/音频信号的高频分量强度较小,低频分量强度较大,信号预加重就是让信号通过一个高通滤波器,让信号的高低频分量的强度不至于相差太多。在时域中,对信号x[n]作如下操作:
在这里插入图片描述

α通常取一个很接近1的值,typical value为0.97或0.95.
从时域公式来看,可能有部分人不懂为啥这是一个高通滤波器,我们从z变换的角度看一下滤波器的transfer function:
在这里插入图片描述
可以看出滤波器有一个极点0,和一个零点α。
当频率为0时,z=1, 放大系数为(1-α)。当频率渐渐增大,放大系数不断变大,当频率到pi时,放大系数为(1+α)。

离散域中, [ 0 , π ] [0,\pi] [0,π] 对应连续域中的 [ 0 , f s / 2 ] [0, fs/2] [0,fs/2] (单位Hz)。

其中fs为采样率,在我们这里是44.1kHz。
因此当频率到22000Hz时,放大系数为 ( 1 + α ) (1+α) (1+α)

下面用两段代码和对应的图像给出一个直观感受:

alpha = 0.97
emphasized_y = np.append(y[tmin*sr],y[tmin*sr+1:tmax*sr]-alpha*y[tmin*sr:tmax*sr-1])
n = int((tmax-tmin)*sr) #信号一共的sample数量#未经过预加重的信号频谱
plt.figure(dpi=300,figsize=(7,4))
freq = sr/n*np.linspace(0,n/2,int(n/2)+1)
plt.plot(freq,np.absolute(np.fft.rfft(y[tmin*sr:tmax*sr],n)**2)/n)
plt.xlim(0,5000)
plt.xlabel('Frequency/Hz',fontsize=14)

在这里插入图片描述

#预加重之后的信号频谱
plt.figure(dpi=300,figsize=(7,4))
plt.plot(freq,np.absolute(np.fft.rfft(emphasized_y,n)**2)/n)
plt.xlim(0,5000)
plt.xlabel('Frequency/Hz',fontsize=14)

在这里插入图片描述
这两段代码里用了函数librosa.fft.rfft(y,n)
rfft表示经过fft变换之后只取其中一半(因为另一半对应负频率,没有用处), y对应信号,n对应要做多少点的FFT。

我们这里的信号有44.1k*20=882000 个点,所以对应的FFT 也做882000点的FFT,每一个点所对应的实际频率是该点的索引值*fs/n,这是咋得出来的?

因为第882000个点应该对应(约等于)fs(或者离散域中的 2 π 2\pi 2π),所以前面的点根据线性关系一一对应即可。

这里只展示0-5000Hz,可以看出,经过预加重之后的信号高频分量明显和低频分量的差距没那么大了。

这样预加重的好处有什么?
原文提到了三点:
(1)就是我们刚刚提到的平衡一下高频和低频
(2)避免FFT中的数值问题(也就是高频值太小出现在分母的时候可能会出问题)
(3)或许可以提高SNR。

1.3 分帧

预处理完信号之后,要把原信号按时间分成若干个小块,一块就叫一帧(frame)。

为啥要做这一步?因为原信号覆盖的时间太长,用它整个来做FFT,我们只能得到信号频率和强度的关系,而失去了时间信息。

我们想要得到频率随时间变化的关系,所以完成以下步骤

  1. 将原信号分成若干帧,
  2. 对每一帧作FFT(又称为短时FFT,因为我们只取了一小段时间),然后对每一帧频谱进行映射。
  3. 最后将得到的结果按照时间顺序拼接起来。

这就是语谱图(spectrogram)的原理。

在进行语谱图之前, 先对信号定义下面几个变量:

  • frame_size: 每一帧的长度。通常取20-40ms。太长会使时间上的分辨率(time resolution)较小,太小会加重运算成本。 这里取25ms.

  • frame_length: 每一帧对应的sample数量。
    等于fs*frame_size。我们这里是44.1k*0.025=1102.

  • frame_stride: 相邻两帧的间隔。通常间隔必须小于每一帧的长度,即两帧之间要有重叠,否则我们可能会失去两帧边界附近的信息。
    做特征提取的时候,我们是绝不希望丢失有用信息的。 这里取10ms,即有60%的重叠。

  • frame_step: 相邻两帧的sample数量。这里是441.

  • frame_num: 整个信号所需要的帧数。
    一般希望所需要的帧数是个整数值,所以这里要对信号补0 (zero padding)让信号的长度正好能分成整数帧。

具体代码如下:

frame_size, frame_stride = 0.025,0.01
frame_length, frame_step = int(round(sr*frame_size)),int(round(sr*frame_stride))
signal_length = (tmax-tmin)*sr
frame_num = int(np.ceil((signal_length-frame_length)/frame_step))+1 #向上舍入
pad_frame = (frame_num-1)*frame_step+frame_length-signal_length #不足的部分补零
pad_y = np.append(emphasized_y,np.zeros(pad_frame))
signal_len = signal_length+pad_frame

1.4 加窗

分帧完毕之后,对每一帧加一个窗函数,以获得较好的旁瓣下降幅度。通常使用hamming window

为啥要加窗?

要注意,即使我们什么都不加,在分帧的这个过程中也相当于给信号加了矩形窗,离散滤波器设计的内容中指出:

矩形窗的频谱有很大的旁瓣

时域中将窗函数和原函数相乘,相当于频域的卷积,矩形窗函数和原函数卷积之后,由于旁瓣很大,会造成原信号和加窗之后的对应部分的频谱相差很大,这就是频谱泄露

hamming window有较小的旁瓣,造成的spectral leakage也就较小。

代码实现如下:

  1. 首先定义indices变量,这个变量生成每帧所对应的sample的索引。
  2. np.tile函数可以使得array从行或者列扩展。
  3. 然后定义frames,对应信号在每一帧的值。frames共有1999行,1102列,分别对应一共有1999帧和每一帧有1102个sample

将得到的frameshamming window直接相乘即可,注意这里不是矩阵乘法。

indices = np.tile(np.arange(0, frame_length), (frame_num, 1)) + np.tile(np.arange(0, frame_num * frame_step, frame_step), (frame_length, 1)).T
frames = pad_y[indices] #frame的每一行代表每一帧的sample值
frames *= np.hamming(frame_length) #加hamming window 注意这里不是矩阵乘法

2. 语谱图(此处等价为功率谱)

2.1 做FFT,求频谱 ( frequency spectrum)

多帧信号做FFT, 在上一节中获得了frames变量,其每一行对应每一帧,所以我们分别对每一行做FFT。( 这里需要注意, 不同的分帧算法实现,定义不一样, 有的是将每一列作为一帧, 自己看代码时, 需要进行区分

由于每一行是1102个点的信号,所以可以选择1024点FFT(FFT点数比原信号点数少会降低频率分辨率frequency resolution,但这里相差很小,所以可以忽略)。

2.2 取频谱模平方, 求功率谱 (power spectrum )

将得到的FFT变换取其magnitude,并进行平方再除以对应的FFT点数,即可得到功率谱。

  1. 功率谱,代表的是信号本身在不同的频率成分上各自所包含的功率(或者能量),通过检查一个信号的功率谱,可以确定信号中存在哪些频率以及它们的相对强度。

其横坐标代表是频率,通常使用Hz 单位,频率轴可以对数的,这样任意两个频率成分之间是对数关系。
纵坐标,代表了每个频率成分所对应的能量, 通常使用dB 单位。
功率谱图中的高度代表了,在某一个频段区间内, 该频段内的能量(或者说功率)。

2.3 语谱图spectrogram

原始信号所对应的,无论频谱还是功率谱,反应的都是信号在各个频率分量上的信息。 但是,这两者都丢失了信号在时间维度上的信息。

从而,为了将一个信号同时表达出,信号在频率分量上的信息,又要显示出信号随着时间变化的信息。 这便是语谱图的本意。

  • 语谱图的横坐标表示时间, 纵坐标是频率,每个点上数值强度或者颜色深度表示该点在时频上的特性。

注意: 如何从功率谱得到语谱图呢?
使用短时傅里叶变换。即在

  1. 对信号进行分帧之后;
  2. 对每一帧信号计算其对应的功率谱。
  3. 再将该功率谱垂直翻转90度, 从而便得到当前帧的语谱图形式,此时纵轴便是频率,横轴是当前帧时间上的信息;
  4. 重复第2, 3 步骤,便会将多个帧的功率谱在时间维度上,按照时间的先后顺序拼接起来,从而构成了语谱图。

所以,语谱图便是将每一帧信号的功率谱做成一个合集(每一帧信号都有一个功率谱)。将多个帧的功率谱,翻转之后,按照时间顺序拼接,就构成信号的spectrogram)。

此时, 使用每一帧信号的功率谱, 构成一个功率谱的合集, 该功率谱合集便是等价于语谱图;

NFFT = 1024 #frame_length=1102,所以用1024足够了
mag_frames = np.absolute(np.fft.rfft(frames,NFFT))
pow_frames = mag_frames**2/NFFT #Pow_frames就是功率谱矩阵的变量名.plt.figure(dpi=300,figsize=(12,6))
plt.imshow(20*np.log10(pow_frames[40:].T),cmap=plt.cm.jet,aspect='auto')
plt.yticks([0,128,256,384,512],np.array([0,128,256,384,512])*sr/NFFT)

在这里插入图片描述

2.4 语谱图和Mel语谱图的比较

mel-spectrogram就是将梅尔滤波器组应用到语谱图中。

此时,语谱图的纵坐标从Hz 刻度便转换成Mel刻度;

Mel尺度和普通频率之间使用如下转换公式;

Mel(f) = 2595 * log10(1 + f/700)

注意,在实际的代码中,会有另外的公式;
即在1000Hz, 内使用线性变换, 超过1000Hz 后再使用如上的变换。

------------------ 以下内容 扩展到 Mel 语谱图部分---------------

3. Mel 滤波器组

所谓梅尔滤波器组是一个等高的三角滤波器组,每个滤波器的起始点在上一个滤波器的中点处。其对应的频率在梅尔尺度上是线性的,因此称之为梅尔滤波器组。

  1. 每个滤波器对应的频率可以将最大频率(下图中是4000,我们这里是22.05k)用上文中提到的公式转换成梅尔频率,在梅尔尺度上线性分成若干个频段,再转换回实际频率尺度即可。

实际操作时,将每个滤波器分别和功率谱pow_frames进行点乘,获得的结果即为该频带上的能量(energy)。

这里我们的pow_frames是一个(1999,513)的矩阵(这里可能有人疑问513咋来的?我们刚刚做的不是1024点FFT吗?

这里注意因为我们用了rfft,只用了非负的那一半频率,所以是1024/2+1个点).

3.1 Mel 滤波器的个数与 N f f t N_{fft} Nfft 点数之间的关系

梅尔滤波器fbank是一个(mel_N, 513)的矩阵,其中mel_N代表对应的梅尔滤波器个数,这个值不能太大,因为这里我们一共只有513个点,如果mel_N取得太大,会导致前面几个滤波器的长度都是0 (因为低频的梅尔滤波器特别窄)

在这里插入图片描述

梅尔滤波器的数量越多,低频滤波器分到的频率点数越少,相应的就需要 N F F T N_{FFT} NFFT 提高,否则低频滤波器对应的点数太少甚至为0.

我们只要将这两个矩阵相乘pow_frames*fbank.
T即可得到mel-spectrogram,结果是一个(1999, 40)的矩阵,每一行是一帧,每一列代表对应的梅尔频带的能量。

3.2 Mel 滤波器计算公式

具体梅尔滤波器的图例和计算公式以及对应代码如下:
在这里插入图片描述

其中m代表滤波器的序号,f(m-1)和f(m)、f(m+1)分别对应第m个滤波器的起始点、中间点和结束点。

大家一定要注意的一点是,这里的f(m)对应的值不是频率值,而是对应的sample的索引!

比如,我们这里最大频率是22050 Hz, 所以22050Hz对应的是第513个sample,即频率f所对应的值是f/fs*NFFT。

代码中有一段np.where(condition,a,b),这个函数的功能是检索b中的元素,当condition满足的时候,输出a;

否则,输出b中的原元素。

这一步的操作是为了将其中的全部0值以一个很小的非负值代替,否则在计算dB的时候,log中出现0会出错。

#下面定义mel filter
mel_N = 40 #滤波器数量,这个数字若要提高,则NFFT也要相应提高
mel_low, mel_high = 0, (2595*np.log10(1+(sr/2)/700))
mel_freq = np.linspace(mel_low,mel_high,mel_N+2)
hz_freq = (700 * (10**(mel_freq / 2595) - 1))
bins = np.floor((NFFT)*hz_freq/sr) #将频率转换成对应的sample位置
fbank = np.zeros((mel_N,int(NFFT/2+1))) #每一行储存一个梅尔滤波器的数据
for m in range(1, mel_N + 1):f_m_minus = int(bins[m - 1])   # leftf_m = int(bins[m])             # centerf_m_plus = int(bins[m + 1])    # rightfor k in range(f_m_minus, f_m):fbank[m - 1, k] = (k - bins[m - 1]) / (bins[m] - bins[m - 1])for k in range(f_m, f_m_plus):fbank[m - 1, k] = (bins[m + 1] - k) / (bins[m + 1] - bins[m])
filter_banks = np.matmul(pow_frames, fbank.T)
filter_banks = np.where(filter_banks == 0, np.finfo(float).eps, filter_banks)  # np.finfo(float)是最小正值
filter_banks = 20 * np.log10(filter_banks)  # dB
#filter_banks -= np.mean(filter_banks,axis=1).reshape(-1,1)
plt.figure(dpi=300,figsize=(12,6))
plt.imshow(filter_banks[40:].T, cmap=plt.cm.jet,aspect='auto')
plt.yticks([0,10,20,30,39],[0,1200,3800,9900,22000])

4. Mel 语谱图

最后一步是将梅尔滤波器作用到上一步得到的pow_frames上,得到的mel-spectrogram如下:

在这里插入图片描述
机器学习的时候,每一个音频段即可用对应的mel-spectogram表示,每一帧对应的某个频段即为一个feature。

因此我们一共获得了1999*40个feature和对应的值。

实际操作中,每个音频要采用同样的长度,这样我们的feature数量才是相同的。

通常还要进行归一化,即每一帧的每个元素要减去该帧的平均值,以保证每一帧的均值均为0.

5. MFCCs 原理

5.1 为什么需要MFCC系数

因为2中得到的梅尔谱系数是互相关的,在一些机器学习算法中可能会出问题,因为有些算法假设数据不存在互相关性。

因此,可以用DCT变换来压缩梅尔谱,得到一组不相关的系数。DCT在图像压缩领域很常见,大家可以自己查阅相关资料其原理。

5.2 MFCC 的前13 个系数

在语音识别中,得到的梅尔倒频系数只保存前2-13个,剩下的不用,因为研究表明其他系数代表了系数中高阶的变化,在ASR中没啥用。

当然,更深层次的原因是MFCC是倒谱系数,所谓倒谱系数,就是对log之后的梅尔谱系数进行DCT变换;

其实相当于将实际上是频域的信号当成时域信号强行进行频域变换,得到的是频域信号在伪频域的幅频相应;

前2-13个系数代表的是包络,因为他们在伪频域上是低频信号,所以在前面,后面的系数是伪频域的高频信号,代表的是spectral details,在语音识别的时候,对我们帮助更大的是包络,因为包含了formants等信息。

5.3 小结

总的来说,过去在HMM、GMM等模型用的比较火的时候,多将MFCC用于特征提取,因为当时的机器学习算法有相应的不足。

如今最热门的是以神经网络为代表的深度学习算法,神经网络内部复杂,在训练的过程中可以在网络内部将互相关的问题弱化,也因此DCT变换显得有些多余,何况还会提高计算量,而且DCT作为一种线性变换,有可能会导致损失信号中一些非线性信息。

因此,如今Mel-spectogram用的更多。

参考资料

Mel-frequency cepstrum
Mel Frequency Cepstral Coefficient (MFCC) tutorial
Notes on Music Information Retrieval
机器学习中距离和相似性度量方法
https://robinchao.github.io/2018/07/24/speech-recognation-mfcc.html
https://blog.csdn.net/weixin_50547200/article/details/117294164

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/12590.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一网打尽,音乐高手都在使用的打谱软件不藏私推荐

一网打尽,音乐高手都在使用的打谱软件不藏私推荐 关键词:打谱软件,Guitar Pro,Overture,Sibelius,Finale Guitar Pro:sourl.cn/KsuXZz Overture:sourl.cn/VsYZ3y Sibelius:sourl.cn/2fyfZt 学音乐的大家都知道&am…

WaveTone 2.67原创汉化版扒谱辅助教程

深度解析音频结构 精准扒谱,扒和弦分析! WaveTone 是音频后期制作,深度学习编曲的好助手! 汉化版支持中英文自由切换,重启应用程序生效! 支持导出主流音频Wav格式和MIDI键盘记录文件!可以在…

【Musescore 】开源打谱软件 快速入门笔记

前两天做了个西贝柳斯的打谱软件学习笔记,反正都是初学,今天再来学习一款同类软件,比较之后确定一款深入学习。 西贝柳斯的学习笔记在此:https://blog.csdn.net/yuetaope/article/details/120020342 西贝柳斯是商业收费软件&#…

Guitar Pro8.1专业版新功能简谱介绍

Guitarpro 8.1版本中,已成功推出全新的简谱功能!Guitar Pro是一款非常流行的音乐制谱软件,它支持各种乐器的制谱。在思杰马克丁引入这款软件之后,为它专门定制了中文版,并针对中国用户重新定价。GuitarPro经过5年研发&…

吉他谱软件guitar pro2023吉他和弦、六线谱、BASS四线谱绘制

Guitar Pro由法国Arobas Music出品,主要用于管弦乐器的学习,通过建立不同的音轨,可完成不同乐器乐谱的编排制作。Guitar Pro发布23余年来,其强大的功能被广泛应用于专业乐队的创作和排练,其独创的gtp文档格式在专业领域…

谷歌推出全能扒谱AI:只要听一遍歌曲,钢琴小提琴的乐谱全有了

晓查 发自 凹非寺量子位 报道 | 公众号 QbitAI 听一遍曲子,就能知道乐谱,还能马上演奏,而且还掌握“十八般乐器”,钢琴、小提琴、吉他等都不在话下。 这就不是人类音乐大师,而是谷歌推出的“多任务多音轨”音乐转音符模…

Google Bard vs ChatGPT:哪一个更适合创造富有创造性的文学作品?

1、Google Bard,ChatGPT特点、用途、性能和应用场景等方面的讨论 首先,我们来看看Google Bard和ChatGPT的特点。 Google Bard是一种基于AI的诗歌生成器,使用了深度学习技术和自然语言处理技术,旨在创造富有想象力和具有感情的文学…

阿里正式加入ChatGPT战局,“通义千问”上线后表现如何?

ChatGPT发布后,数月间全世界都对AI的能力有了新的认知。 ChatGPT掀起的战局,现在又多了一位选手了! 阿里版类ChatGPT突然官宣正式对外开放企业邀测,由达摩院开发,名为“通义千问” 顾名思义,阿里正式加入Ch…

阿里版ChatGPT——通义千问,开箱初体验

所有行业、所有应用、所有服务都值得基于新型人工智能技术重做一遍,在带来创造性客户体验的同时,生产范式、工作范式、生活范式也将发生变化。——阿里集团董事会主席兼CEO 张勇 2023阿里云峰会上,通义千问大语言模型对外发布,宣称…

活动报名 | 生命科学中的生成式人工智能:如何搭建生命科学的“ChatGPT”

活动议程 日期:3月10日(周五) 时间 主题19:30-19:35开场简介兰艳艳 清华大学教授,青源会会员19:35-20:20Generative Biology: Towards Building the “ChatGPT” in Biology唐建 Mila - Quebec AI Institute助理教授,青…

刚刚,ChatGPT王炸更新!解封了...

推荐阅读: 《太夸张了。。。》 《反击 ChatGPT,谷歌正式推出 Bard!结果..》 1 插件上线 大家知道虽然ChatGPT很厉害,但是你向它了解最近两年的内容,它都会回复不支持。 什么原因呢? 因为ChatGPT模型训练&am…

北京筑龙吴英礼:ChatGPT对采购与招标数字化的影响

2月25日下午,平台经济学沙龙(第八期)在清华大学互联网产业研究院成功举办。本期沙龙以“ChatGPT对招标采购的影响”为主题,由清华大学互联网产业研究院平台经济课题组组长、中国招标投标公共服务平台原总经理、首席经济学家平庆忠…

真吓人 chatGPT-4 几分钟搞定我3天工作量

大家好,我是北妈。 一、 前些天只是在凑热闹,调戏chat和他对话,问他一些问题,看看它到底是不是弱智。 但自从发布了GPT-4.0智能,直接全起飞了。我花20美金/月 买入PLUS账号,主要是申请开发者权限&#xff0…

【使用心得】ChatGPT化身私人助理

使用ChatGPT真的为我的工作和日常生活带来了极大的便利。作为一位私人助理,它可以提供各种实用信息和建议,例如语言翻译、日程管理、邮件发送等等。尤其对于我这样经常需要处理海外事务的职场人士来说,ChatGPT更是成为了必需品。 这种全方位…

CharGPT解封申诉模板

前言 文章目录 前言一、申诉模板图示二、具体操作1、发送邮件2、邮件模板一、申诉模板图示 二、具体操作 1、发送邮件 告诉官方这是一场误会;将自己注册 ChatGPT 的邮箱账号和姓名复制到下面的模板中;登录其它的邮

ChatGPT Plus已重新开放升级!

前天(4月5号)OpenAI声称因为算力不足暂时关闭了升级Plus账号的通道,恢复时间未知。很多用户感慨GPT-4还没体验到就没了。没想到时隔一天,OpenAI就重新开放了升级通道,不知道是真的算力不足还是营销策略。 不过考虑到不…

创始人专访 | Kimi:因为自己淋过雨,所以总想给别人撑把伞

流光溢彩十九载, 初心筑梦再出发。 沃尔得教育成立十九周年系列 沃恩智慧创始人专访——【Kimi】 -- Kimi 沃恩智慧联合创始人 哈工大计算机博士 一作发表数十篇顶会、顶刊论文 多个SCI期刊特邀审稿人 典型的实战派、崇尚用代码解释理论 指导多名本硕博学…

这篇文章,是chatGPT写给网工你的

晚上好,我是chatGPT。 老杨邀请我来和网工们来聊会儿,我感到很荣幸。 今天,我会根据老杨的提问,发表一些我对于网络工程师这行的一些看法和见解。 也希望你看完我的回答之后,可以在留言区告诉我,你的看法…

ChatGPT 可替代?以下7 种 AI 工具更专注于编码

【CSDN 编者按】ChatGPT并不是唯一能帮助你编码的人工智能工具,使用下面这些 7 款工具也能帮助开发者大大的提升编码效率 作者 | Mary Gathoni 译者|陈静琳 责编 | 屠敏 出品 | CSDN(ID:CSDNnews) ChatGPT 是…

ChatGPT:向未来迈进的智能对话伴侣

hello,大家好,我是张张,「架构精进之路」公号作者。 相信最近许多朋友的生活中,可能或多或少都被 ChatGPT 刷屏了。 记得 2023 年初时候,正值 ChatGPT 火热的时候,我当时整理过一篇 一文看懂:近…