初识神经网络01——认识PyTorch


文章目录

  • 一、认识PyTorch
    • 1.1 PyTorch是什么
    • 1.2 安装PyTorch
  • 二、认识Tensor
    • 2.1 创建Tensor
      • 2.1.1 基本方式
      • 2.2.2 创建线性和随机张量
    • 2.2 Tensor属性
      • 2.2.1 切换设备
      • 2.2.2 类型转换
    • 2.3 Tensor与Numpy的数据转换
      • 2.3.1 张量转ndarray
      • 2.3.2 Numpy转张量
    • 2.4 Tensor常见操作
      • 2.4.1 取值
      • 2.4.2 基础运算
      • 2.4.3 点积与叉积
      • 2.4.4 形状操作
      • 2.4.5 升维和降维
  • 总结


一、认识PyTorch

1.1 PyTorch是什么

PyTorch是一个基于Python的深度学习框架,它提供了一种灵活、高效、易于学习的方式来实现深度学习模型。PyTorch最初由Facebook开发,被广泛应用于计算机视觉、自然语言处理、语音识别等领域。

​ PyTorch使用张量(tensor)来表示数据,可以轻松地处理大规模数据集,且可以在GPU上加速。

​ PyTorch提供了许多高级功能,如**自动微分(automatic differentiation)、自动求导(automatic gradients)**等,这些功能可以帮助我们更好地理解模型的训练过程,并提高模型训练效率。

除了PyTorch,还有很多其它常见的深度学习框架:

  1. TensorFlow: Google开发,广泛应用于学术界和工业界。TensorFlow提供了灵活的构建、训练和部署功能,并支持分布式计算。
  2. Keras: Keras是一个高级神经网络API,已整合到TensorFlow中。
  3. PaddlePaddle: PaddlePaddle(飞桨)是百度推出的开源深度学习平台,旨在为开发者提供一个易用、高效的深度学习开发框架。
  4. MXNet:由亚马逊开发,具有高效的分布式训练支持和灵活的混合编程模型。
  5. Caffe:具有速度快、易用性高的特点,主要用于图像分类和卷积神经网络的相关任务。
  6. CNTK :由微软开发的深度学习框架,提供了高效的训练和推理性能。CNTK支持多种语言的接口,包括Python、C++和C#等。
  7. Chainer:由Preferred Networks开发的开源深度学习框架,采用动态计算图的方式。

PyTorch中有3种数据类型:浮点数、整数、布尔。其中,浮点数和整数又分为8位、16位、32位、64位,加起来共9种。

之所以分为8位、16位、32位、64位,是因为在不同场景中,对数据的精度和速度要求不同。通常,移动或嵌入式设备追求速度,对精度要求相对低一些。精度越高,往往效果也越好,自然硬件开销就比较高。

1.2 安装PyTorch

首先需要使用Anaconda创建一个虚拟环境,建议所使用的python版本不要低于3.8。

conda create -n 你的环境名 python=3.9

如果你是使用的电脑没有独立显卡,或者使用的不是NVIDA的显卡的话,那么只需要安装CPU版本的PyTorch即可。在官方文档里面找到适合你设备的PyTorch的CPU版本及对应的安装指令执行即可。

具体安装步骤可以参考这篇文档:PyTorch安装教程

二、认识Tensor

Tensor,也叫张量,是一个多维数组,通俗来说可以看作是扩展了标量、向量、矩阵的更高维度的数组。张量的维度决定了它的形状(Shape),PyTorch会将数据封装成张量(Tensor)进行计算,所谓张量就是元素为相同类型的多维矩阵。张量可以在 GPU 上加速运行。

张量有device(所属设备)、dtype(数据类型)、shape(形状)等常见属性,知道这些属性对我们认识Tensor很有帮助。

2.1 创建Tensor

2.1.1 基本方式

张量可以通过标量、numpy数组以及list进行创建。

  • torch.tensor()
    注意这里的tensor是小写,该API是根据指定的数据创建张量。
import torch
import numpy as np# 通过标量创建
t = torch.tensor(1)
print(t)# 通过ndarray创建
t = torch.tensor(np.random.randn(3, 5))
print(t)# 使用Tensor创建
t = torch.Tensor([1,2,3])
print(t)

如果出现如下错误:

UserWarning: Failed to initialize NumPy: _ARRAY_API not found

一般是因为numpy和pytorch版本不兼容,可以降低numpy版本。

  • torch.Tensor
    注意这里的Tensor是大写,该API根据形状创建张量,其也可用来创建指定数据的张量。
# 1. 根据形状创建张量
tensor1 = torch.Tensor(2, 3)
print(tensor1)
# 2. 也可以是具体的值
tensor2 = torch.Tensor([[1, 2, 3], [4, 5, 6]])
print(tensor2, tensor2.shape, tensor2.dtype)tensor3 = torch.Tensor([10])
print(tensor3, tensor3.shape, tensor3.dtype)# 指定tensor数据类型
tensor1 = torch.Tensor([1,2,3]).short()
print(tensor1)tensor1 = torch.Tensor([1,2,3]).int()
print(tensor1)tensor1 = torch.Tensor([1,2,3]).float()
print(tensor1)tensor1 = torch.Tensor([1,2,3]).double()
print(tensor1)

torch.Tensor与torch.tensor区别

特性torch.Tensor()torch.tensor()
数据类型推断强制转为 torch.float32根据输入数据自动推断(如整数→int64
显式指定 dtype不支持支持(如 dtype=torch.float64
设备指定不支持支持(如 device='cuda'
输入为张量时的行为创建新副本(不继承原属性)默认共享数据(除非 copy=True
推荐使用场景需要快速创建浮点张量需要精确控制数据类型或设备

还有诸如torch.IntTensor()、torch.FloatTensor()、 torch.DoubleTensor()、
torch.LongTensor()…等用于创建指定类型的张量。如果数据类型不匹配,那么在创建的过程中会进行类型转换,要尽可能避免,防止数据丢失。

2.2.2 创建线性和随机张量

  • 线性张量
    使用torch.arange 和 torch.linspace 创建线性张量:
# 不用科学计数法打印
torch.set_printoptions(sci_mode=False)# 1. 创建线性张量
r1 = torch.arange(0, 10, 2)
print(r1)
# 2. 在指定空间按照元素个数生成张量:等差
r2 = torch.linspace(3, 10, 10)
print(r2)r2 = torch.linspace(3, 10000000, 10)
print(r2)
  • 随机张量
    在 PyTorch 中,使用torch.randn 创建随机张量。种子影响所有与随机性相关的操作,包括张量的随机初始化、数据的随机打乱、模型的参数初始化等。通过设置随机数种子,可以做到模型训练和实验结果在不同的运行中进行复现。也即是说,不设置随机种子时,每次打印的结果不一样。
 # 设置随机数种子
torch.manual_seed(123)# 获取随机数种子
print(torch.initial_seed())# 生成随机张量,均匀分布(范围 [0, 1))
# 创建2个样本,每个样本3个特征
print(torch.rand(2, 3))# 4. 生成随机张量:标准正态分布(均值 0,标准差 1)
print(torch.randn(2, 3))# 5. 原生服从正态分布:均值为2, 方差为3,形状为1*4的正态分布
print(torch.normal(mean=2, std=3, size=(1, 4)))

2.2 Tensor属性

2.2.1 切换设备

默认在cpu上运行,可以显式的切换到GPU:

# 获取属性
data = torch.tensor([1, 2, 3])
print(data.dtype, data.device, data.shape)# 把数据切换到GPU进行运算
device = "cuda" if torch.cuda.is_available() else "cpu"
data = data.to(device)
print(data.device)

不同设备上的数据是不能相互运算的。

或者使用cuda进行切换:

data = data.cuda()

当然也可以直接创建在GPU上:

# 直接在GPU上创建张量
data = torch.tensor([1, 2, 3], device='cuda')
print(data.device)

2.2.2 类型转换

训练模型或推理时,类型转换也是张量的基本操作,是需要掌握的。

data = torch.tensor([1, 2, 3])
print(data.dtype)  # torch.int64# 1. 使用type进行类型转换
data = data.type(torch.float32)
print(data.dtype)  # float32
data = data.type(torch.float16)
print(data.dtype)  # float16# 2. 使用类型方法
data = data.float()
print(data.dtype)  # float32
# 16 位浮点数,torch.float16,即半精度
data = data.half()
print(data.dtype)  # float16
data = data.double()
print(data.dtype)  # float64
data = data.long()
print(data.dtype)  # int64
data = data.int()
print(data.dtype)  # int32#  使用dtype属性
data = torch.tensor([1, 2, 3], dtype=torch.half)
print(data.dtype)

2.3 Tensor与Numpy的数据转换

2.3.1 张量转ndarray

用于需要计算的情景,转换后即脱离了计算图。此时分浅拷贝(内存共享)和深拷贝(内存不共享)。

  • 浅拷贝
    调用numpy()方法可以把Tensor转换为Numpy,此时内存是共享的。
# 1. 张量转numpy
data_tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
data_numpy = data_tensor.numpy()
print(type(data_tensor), type(data_numpy))
# 2. 他们内存是共享的
data_numpy[0, 0] = 100
print(data_tensor, data_numpy)
  • 深拷贝
    使用copy()方法可以避免内存共享:
# 1. 张量转numpy
data_tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])# 2. 使用copy()避免内存共享
data_numpy = data_tensor.numpy().copy()
print(type(data_tensor), type(data_numpy))# 3. 此时他们内存是不共享的
data_numpy[0, 0] = 100
print(data_tensor, data_numpy)

2.3.2 Numpy转张量

也同样分为浅拷贝和深拷贝。

  • 浅拷贝
    from_numpy方法转Tensor默认是内存共享的
# 1. numpy转张量
data_numpy = np.array([[1, 2, 3], [4, 5, 6]])
data_tensor = torch.from_numpy(data_numpy)
print(type(data_tensor), type(data_numpy))# 2. 他们内存是共享的
data_tensor[0, 0] = 100
print(data_tensor, data_numpy)
  • 深拷贝
    使用传统的torch.tensor()则内存是不共享的
# 1. numpy转张量
data_numpy = np.array([[1, 2, 3], [4, 5, 6]])
data_tensor = torch.tensor(data_numpy)
print(type(data_tensor), type(data_numpy))# 2. 内存是不共享的
data_tensor[0, 0] = 100
print(data_tensor, data_numpy)

2.4 Tensor常见操作

2.4.1 取值

我们可以把单个元素tensor转换为Python数值,这是非常常用的操作。

data = torch.tensor([[18]])
print(data.item())

注意:

  • 和Tensor的维度没有关系,都可以取出来!
  • 如果有多个元素则报错
  • 仅适用于CPU张量,如果张量在GPU上,需先移动到CPU

2.4.2 基础运算

常见的加减乘除次方取反开方等各种操作,带有_的方法为原地操作。

# 生成范围 [0, 10) 的 2x3 随机整数张量
data = torch.randint(0, 10, (2, 3))
print(data)
# 元素级别的加减乘除:不修改原始值
print(data.add(1))
print(data.sub(1))
print(data.mul(2))
print(data.div(3))
print(data.pow(2))# 元素级别的加减乘除:修改原始值
data = data.float()
data.add_(1)
data.sub_(1)
data.mul_(2)
data.div_(3.0)
data.pow_(2)
print(data)

2.4.3 点积与叉积

  • 点积

也叫矩阵乘法,是线性代数中的一种基本运算,用于将两个矩阵相乘,生成一个新的矩阵。

假设有两个矩阵:

  • 矩阵 A的形状为 m×n(m行 n列)。
  • 矩阵 B的形状为 n×p(n行 p列)。

矩阵 A和 B的乘积 C=A×B是一个形状为 m×p的矩阵,其中 C的每个元素 Cij,计算 A的第 i行与 B的第 j列的点积。计算公式为:
Cij=∑k=1nAik×BkjC_{ij}=∑_{k=1}^nA_{ik}×B_{kj} Cij=k=1nAik×Bkj

矩阵乘法运算要求如果第一个矩阵的shape是 (N, M),那么第二个矩阵 shape必须是 (M, P),即第一个矩阵的列数必须与第二个矩阵的行数相同,最后两个矩阵点积运算的shape为 (N, P)。

在 PyTorch 中,使用@或者matmul完成Tensor的乘法。

data1 = torch.tensor([[1, 2, 3], [4, 5, 6]
])
data2 = torch.tensor([[3, 2], [2, 3], [5, 3]
])
print(data1 @ data2)
print(data1.matmul(data2))
  • 叉积

也叫阿达玛积,是指两个形状相同的矩阵或张量对应位置的元素相乘。它与矩阵乘法不同,矩阵乘法是线性代数中的标准乘法,而阿达玛积是逐元素操作。假设有两个形状相同的矩阵 A和 B,它们的阿达玛积 C=A∘B定义为:
Cij=Aij×BijC_{ij}=A_{ij}×B_{ij} Cij=Aij×Bij
其中:

  • Cij 是结果矩阵 C的第 i行第 j列的元素。
  • Aij和 Bij分别是矩阵 A和 B的第 i行第 j 列的元素。

在 PyTorch 中,可以使用mul函数或者*来实现;

data1 = torch.tensor([[1, 2, 3], [4, 5, 6]])
data2 = torch.tensor([[2, 3, 4], [2, 2, 3]])
print(data1 * data2)
print(data1.mul(data2))

2.4.4 形状操作

  • reshape
    可以用于将张量转换为不同的形状,但要确保转换后的形状与原始形状具有相同的元素数量。
data = torch.randint(0, 10, (4, 3))
print(data)
# 1. 使用reshape改变形状
data = data.reshape(2, 2, 3)
print(data)# 2. 使用-1表示自动计算
data = data.reshape(2, -1)
print(data)
  • view

view进行形状变换的特征:

  • 张量在内存中是连续的;
  • 返回的是原始张量视图,不重新分配内存,效率更高;
  • 如果张量在内存中不连续,view 将无法执行,并抛出错误。

张量的内存布局决定了其元素在内存中的存储顺序。对于多维张量,内存布局通常按照最后一个维度优先的顺序存储,即先存列,后存行。例如,对于一个二维张量
A,其形状为 (m, n),其内存布局是先存储第 0 行的所有列元素,然后是第 1 行的所有列元素,依此类推。 如果张量的内存布局与形状完全匹配,并且没有被某些操作(如转置、索引等)打乱,那么这个张量就是连续的

PyTorch 的大多数操作都是基于 C 顺序的,我们在进行变形或转置操作时,很容易造成内存的不连续性。

tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
print("正常情况下的张量:", tensor.is_contiguous())# 对张量进行转置操作
tensor = tensor.t()
print("转置操作的张量:", tensor.is_contiguous())
print(tensor)
# 此时使用view进行变形操作,取值为-1表示自动推断该维度
tensor = tensor.view(2, -1)
print(tensor)

运行结果如下:
在这里插入图片描述

  • transpose
    transpose 用于交换张量的两个维度,注意,是2个维度,它返回的是新张量,原张量不变。
torch.transpose(input, dim0, dim1)

参数:

  • input: 输入的张量。
  • dim0: 要交换的第一个维度。
  • dim1: 要交换的第二个维度。
data = torch.randint(0, 10, (3, 4, 5))
print(data, data.shape)
# 使用transpose进行形状变换
transpose_data = torch.transpose(data,0,1)
# transpose_data = data.transpose(0, 1)
print(transpose_data, transpose_data.shape)

转置后的张量可能是非连续的(is_contiguous() 返回 False),如果需要连续内存(如某些操作要求),可调用 .contiguous():

y = x.transpose(0, 1).contiguous()
  • permute
    它通过重新排列张量的维度来返回一个新的张量,不改变张量的数据,只改变维度的顺序。
torch.permute(input, dims)

参数

  • input: 输入的张量。
  • dims: 一个整数元组,表示新的维度顺序。
data = torch.randint(0, 10, (3, 4, 5))
print(data, data.shape)
# 使用permute进行多维度形状变换
permute_data = data.permute(1, 2, 0)
print(permute_data, permute_data.shape)

和 transpose 一样,permute 返回新张量,原张量不变。重排后的张量可能是非连续的(is_contiguous() 返回 False),必要时需调用 .contiguous()。

维度顺序必须合法:dims 中的维度顺序必须包含所有原始维度,且不能重复或遗漏。例如,对于一个形状为 (2, 3, 4) 的张量,dims=(2, 0, 1) 是合法的,但 dims=(0, 1) 或 dims=(0, 1, 2, 3) 是非法的。

与 transpose() 的对比

特性permute()transpose()
功能可以同时调整多个维度的顺序只能交换两个维度的顺序
灵活性更灵活较简单
使用场景适用于多维张量适用于简单的维度交换

2.4.5 升维和降维

  • squeeze降维
    用于移除所有大小为 1 的维度,或者移除指定维度的大小为 1 的维度。
torch.squeeze(input, dim=None)

参数

  • input: 输入的张量。
  • dim (可选): 指定要移除的维度。如果指定了 dim,则只移除该维度(前提是该维度大小为 1);如果不指定,则移除所有大小为 1 的维度。
data = torch.randint(0, 10, (1, 4, 5, 1))
print(data, data.shape)# 进行降维操作
data1 = data.squeeze(0).squeeze(-1)
print(data.shape)# 移除所有大小为 1 的维度
data2 = torch.squeeze(data)# 尝试移除第 1 维(大小为 3,不为 1,不会报错,张量保持不变。)
data3 = torch.squeeze(data, dim=1)
print("尝试移除第 1 维后的形状:", data3.shape)
  • unsqueeze升维
torch.unsqueeze(input, dim)

参数

  • input: 输入的张量。
  • dim: 指定要增加维度的位置(从 0 开始索引)。
data = torch.randint(0, 10, (32, 32, 3))
print(data.shape)
# 升维操作
data = data.unsqueeze(0)
print(data.shape)

Tensor也有广播机制,且与numpy数组基本一致,这里不再赘述。


总结

本文简要介绍了PyTorch,以及Tensor的一部分常用基本操作。

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

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

相关文章

Android UI 组件系列(十一):RecyclerView 多类型布局与数据刷新实战

博客专栏:Android初级入门UI组件与布局 源码:通过网盘分享的文件:Android入门布局及UI相关案例 链接: https://pan.baidu.com/s/1EOuDUKJndMISolieFSvXXg?pwd4k9n 提取码: 4k9n 引言 在 Android 应用中,RecyclerView 是最常用…

如何学习跨模态对齐(尤其是 CLIP 思想)

学习跨模态对齐(尤其是CLIP思想)需要结合理论基础、经典模型原理、实践复现和前沿扩展,以下是一套系统的学习路径,从入门到深入逐步展开: 一、先补基础:跨模态对齐的“前置知识” 跨模态对齐的核心是让图…

日记研究:一种深入了解用户真实体验的UX研究方法

在用户体验(UX)研究中,我们常常需要了解用户在真实世界中如何与产品互动。然而,由于时间和空间的限制,我们很难像“特工”一样全天候跟踪用户。这时,“日记研究”(Diary Studies)就成…

鸿蒙app 开发中 加载图片的时候闪一下 如何解决

1.解决 在图片上 加载这个属性 .syncLoad(true) 参考的官方链接

【OS】进程与线程

进程进程实体代码段相关数据PCB进程标识符外部标识符:为方便用户对进程的访问,为每个进程设置一个外部标识符,通常由字母和数字组成内部标识符:为方便系统对进程的使用,在OS中又为进程设置了内部标识符,赋予…

Django 序列化详解:从 Model 到 JSON,全面掌握数据转换机制

一、引言:什么是 Django 序列化?在 Web 开发中,序列化(Serialization) 是指将复杂的数据结构(如数据库模型对象)转换为可传输的格式(如 JSON、XML、YAML 等),…

茶叶蛋大冒险小游戏流量主微信抖音小程序开源

游戏特点 响应式设计:完美适配各种移动设备屏幕尺寸 直观的触摸控制:左右滑动屏幕控制茶叶蛋移动 中式风格元素: 茶叶蛋角色带有裂纹纹理和可爱表情 筷子、蒸笼等中式厨房元素作为障碍物 八角、茶叶等香料作为收集物 锅底火焰动画效果 游戏机…

区分邮科工业交换机与路由器

在这个数字化的时代,我们每天都在享受着互联网带来的便利。无论是工作还是娱乐,网络已经成为我们生活中不可或缺的一部分。然而,在这个看似简单的背后,隐藏着两个至关重要的设备——邮科工业交换机和路由器。它们就像网络世界的双…

【数据结构入门】数组和链表的OJ题(2)

目录 1.回文链表 分析: 代码: 2.相交链表 分析: 代码: 3.环形链表 分析: 代码: 面试提问: 4.环形链表II 分析1: 分析2: 代码: 5.随机链表的复…

文件包含篇

web78 第一题filter伪协议直接读源码即可 ?filephp://filter/convert.base64-encode/resourceflag.php web79 flag.php的php无法用大小写绕过,所以用Php://input只读流 import requests url "http://fadb524a-f22d-4747-a35c-82f71e84bba7.challenge.ctf.sho…

互作蛋白组学技术对比:邻近标记与传统IP-MS、Pull down-MS优势对比

在生命科学领域,蛋白质间的相互作用构成了生命活动的核心网络,驱动着信号传导、基因调控、代谢途径等关键过程。为了绘制这幅复杂的“分子互作地图”,科学家们开发了多种技术,其中免疫共沉淀结合质谱(IP-MS&#xff09…

(ZipList入门笔记一)ZipList的节点介绍

ZipList是 Redis 中一种非常紧凑、节省内存的数据结构 Ziplist(压缩列表) 的内部内存布局。它被用于存储元素较少的 List、Hash 和 Zset。 下面我们来详细介绍每一个节点的含义: 1. zlbytes (ziplist bytes) 含义: 整个压缩列…

Unix 发展史概览

这里是一个简明清晰的 Unix 发展史概览,涵盖从起源到现代的重要节点和演变过程。Unix 发展史概览 1. Unix 起源(1969年) 贝尔实验室:Ken Thompson 和 Dennis Ritchie 开发出 Unix 操作系统。最初设计目标:简洁、可移植…

基于coze studio开源框架二次定制开发教程

目录 一、 项目介绍 1.1 什么是Coze Studio 1.2 功能清单 1.3对比商业版本 二、 功能定开说明 2.1 技术栈简介 2.2 项目架构

RHCE认证题解

考前说明请勿更改 IP 地址。DNS 解析完整主机名,同时也解析短名称。• 所有系统的 root 密码都是 redhat• Ansible 控制节点上已创建用户账户 devops。可以使用 ssh 访问• 所需的所有镜像保存在镜像仓库 utility.lab.example.compodman 可使用下述账号登录使用 用…

调用com对象的坑

1、谏言 最近我在弄64位调用32位dll的问题,在几种IPC之间,最后考虑了调用COM 毕竟我们只在windows平台 2、第一坑–修改编译后都需要重新注册,注册表 一直以为只需要编译就好了,结果调用没反应、报错什么的,需要先撤销…

【Python】PyQt 实现 TreeWidget 多级联动选择逻辑,打造素材搜索自定义树形控件!

在开发自己的写作素材管理工具时,我遇到了一个非常典型但又略显棘手的 UI 问题: 💡 如何实现一个“可自由勾选分类标签”的树形结构界面,支持父子节点自动联动勾选,提升用户体验? 虽然 PyQt 的 QTreeWidget 是构建多层分类结构的好帮手,但默认却不具备父子节点的自动级…

27-数据仓库与Apache Hive-2

1.数仓开发语言概述 理论上来说,任何一款编程语言只要具备读写数据、处理数据的能力,都可以用于数仓的开发。比如大家耳熟能详的C、java、Python等; 关键在于编程语言是否易学、好用、功能是否强大。遗憾的是上面所列出的C、Python等编程语言…

软件测试——接口自动化

测试中的自动化分为两类: 1.ui自动化(web、移动端)2.接口自动化 前面的博客中,我们已经讲解了web端的ui自动化,感兴趣的同学可以去看看:软件测试——自动化测试常见函数_自动化测试代码编写-CSDN博客 今…

Flask一个用户同时只能在一处登录实现

场景:web页面如果多人用同一账号同时登录操作,可能会导致数据等的混乱甚至出现故障。并且可能损害开发者的利益。为此,本篇文章就讲下如何实现同一账户同时仅能一个地方登录操作。 思路:1. 用户登陆时生成token(uuid.u…