DataSet_

本文介绍了Apache Spark中DataSet的创建及其与RDD和DataFrame之间的转换。DataSet是一个强类型的数据集合,可以通过样例类序列或基本类型序列创建。RDD可以通过反射机制转换为DataSet,而DataSet可以直接获取其内部的RDD。DataFrame作为DataSet的特例,两者之间可以方便地互相转换。这些转换在Spark数据处理中扮演着重要角色。

DataSet

DataSet 是具有强类型的数据集合,需要提供对应的类型信息。

创建 DataSet

// 1) 使用样例类序列创建 DataSet
scala> case class Person(name: String, age: Long)
defined class Person
scala> val caseClassDS = Seq(Person("zhangsan",2)).toDS()
caseClassDS: org.apache.spark.sql.Dataset[Person] = [name: string, age: Long]
scala> caseClassDS.show
+---------+---+
| name|age|
+---------+---+
| zhangsan| 2|
+---------+---+
// 2) 使用基本类型的序列创建 DataSet
scala> val ds = Seq(1,2,3,4,5).toDS
ds: org.apache.spark.sql.Dataset[Int] = [value: int]
scala> ds.show
+-----+
|value|
+-----+
| 1|
| 2|
| 3|
| 4|
| 5|
+-----+

注意:在实际使用的时候,很少用到把序列转换成DataSet,更多的是通过RDD来得到 DataSet

RDD 转换为 DataSet

SparkSQL 能够自动将包含有 case 类的 RDD 转换成 DataSet,case 类定义了 table 的结构,case 类属性通过反射变成了表的列名。Case 类可以包含诸如 Seq 或者 Array 等复杂的结构。

scala> case class User(name:String, age:Int)
defined class User
scala> sc.makeRDD(List(("zhangsan",30), ("lisi",49))).map(t=>User(t._1,
t._2)).toDS
res11: org.apache.spark.sql.Dataset[User] = [name: string, age: int]

DataSet 转换为 RDD

// DataSet 其实也是对 RDD 的封装,所以可以直接获取内部的 RDD
scala> case class User(name:String, age:Int)
defined class User
scala> sc.makeRDD(List(("zhangsan",30), ("lisi",49))).map(t=>User(t._1,
t._2)).toDS
res11: org.apache.spark.sql.Dataset[User] = [name: string, age: int]
scala> val rdd = res11.rdd
rdd: org.apache.spark.rdd.RDD[User] = MapPartitionsRDD[51] at rdd at
<console>:25
scala> rdd.collect
res12: Array[User] = Array(User(zhangsan,30), User(lisi,49))

DataFrame 和 DataSet 转换

DataFrame 其实是 DataSet 的特例,所以它们之间是可以互相转换的。

// ➢ DataFrame 转换为 DataSet
scala> case class User(name:String, age:Int)
defined class User
scala> val df = sc.makeRDD(List(("zhangsan",30),
("lisi",49))).toDF("name","age")
df: org.apache.spark.sql.DataFrame = [name: string, age: int]
scala> val ds = df.as[User]
ds: org.apache.spark.sql.Dataset[User] = [name: string, age: int]
// ➢ DataSet 转换为 DataFrame
scala> val ds = df.as[User]
ds: org.apache.spark.sql.Dataset[User] = [name: string, age: int]
scala> val df = ds.toDF
df: org.apache.spark.sql.DataFrame = [name: string, age: int]

<think>我们遇到了错误:expected sequence of length 1 at dim 2 (got 3) 这个错误意味着:在张量的第2维度(dim=2,即从0开始计的第2个维度,也就是第三个维度)上,期望的长度是1,但实际传入的长度是3。 结合用户提供的代码: def build_dataset(sample_length,vocab): dataset_x = []; dataset_y = []; for i in range(sample_length): x,y = build_sample(vocab); dataset_x.append([x]); # 注意这里将x放入一个列表中,然后再添加到dataset_x dataset_y.append([y]); # 同理 print(dataset_y, dataset_y); # 这里打印两次dataset_y,可能是笔误,应该是打印dataset_x和dataset_y return torch.FloatTensor(dataset_x), torch.FloatTensor(dataset_y) 我们来分析: 首先,这个函数返回两个张量:dataset_x 和 dataset_y,它们都是由列表转换而来的FloatTensor。 我们注意构建过程: dataset_x 的每个元素是 [x] -> 这意味着每个x被放入一个列表,然后整个dataset_x是一个列表,里面每个元素是包含一个x的列表。 dataset_y 同理。 那么,假设x是一个数值(标量),那么[x]就是一个只有一个元素的列表。 因此,整个dataset_x将是一个二维列表,例如:[[x1], [x2], [x3], ...] 转换为张量后,会得到一个形状为 (sample_length, 1) 的张量。 但是,如果x本身就是一个向量(例如长度为3的向量),那么[x]就变成了一个列表,里面有一个元素是向量,即:[[x1, x2, x3]](注意这里x1,x2,x3是向量中的元素,但整体看,这个列表只有一个元素,这个元素是一个向量)。 那么整个dataset_x就会是:[[向量1], [向量2], ...] -> 这是一个二维列表,第一维是样本个数,第二维是1(因为每个样本用一个长度为1的列表包裹,但这个列表里的元素是向量)。 但是,当我们用torch.FloatTensor()转换时,它会将这个二维列表转换成二维张量吗?不会,因为每个元素是一个向量,所以实际上会得到一个三维张量。 例如: 假设我们有一个样本,x是一个长度为3的向量:x=[0.1, 0.2, 0.3] 那么 [x] = [[0.1, 0.2, 0.3]] -> 这是一个1x3的二维列表 当我们有多个样本,比如2个样本: dataset_x = [ [[0.1,0.2,0.3]], [[0.4,0.5,0.6]] ] 这个列表有两个元素,每个元素是一个1x3的列表(即每个元素是一个包含3个元素的列表,而这个包含3个元素的列表外面又有一个列表,所以是两层列表)。 当用torch.FloatTensor(dataset_x)转换时,会得到一个形状为(2,1,3)的张量。 因此,我们得到: 第0维:样本数量(2) 第1维:1(因为每个样本是用一个列表包裹的,这个列表长度为1) 第2维:3(每个样本向量的长度) 现在,错误信息说:期望第2维(dim=2)的长度为1,但实际为3。也就是说,某个下游操作期望这个张量的第三维(即dim=2)是1,但现在是3。 所以问题可能出在: 1. 我们构建的每个样本的特征向量x应该是标量(单个数值)?但实际返回了向量(长度为3)。 2. 或者,构建的样本特征向量x确实应该是向量,但模型期望的输入特征维度应该是1,而现在是3。这可能是模型定义的问题。 但是,从代码中可以看到,我们调用的是build_sample(vocab)来生成一个样本。因此,我们需要检查build_sample函数返回的x是什么? 假设build_sample函数返回的x是一个长度为3的向量,那么: dataset_x的形状为 (sample_length, 1, 3) dataset_y同理? 而模型可能期望的输入是 (batch_size, sequence_length, feature_dim) 且要求feature_dim=1,但现在feature_dim=3。 如何解决? 方案1:改变build_sample函数,让它返回一个标量?但这样可能不合理,因为通常特征不止一个。 方案2:改变数据集的构建方式,去掉多余的维度。 观察代码:我们用了两层列表包裹: 外层列表:dataset_x.append([x]) 内层:[x] 这个操作可能是不必要的,如果我们希望dataset_x是一个二维张量,形状为(sample_length, feature_dim),那么我们应该直接append(x),而不是[x]。但是注意,x必须是一个一维的向量(即一个样本的所有特征组成一个向量)。 但是,如果x已经是一个标量,那么直接append(x)会使得dataset_x是一个一维列表,然后转成形状为(sample_length,)的张量,这不符合二维输入(通常要求样本数×特征数)。 所以,正确的做法应该是: 如果x是标量,那么我们应该用append([x]),这样得到二维张量(sample_length,1) 如果x是向量(一维),那么我们应该直接append(x),这样得到二维张量(sample_length, len(x)) 但是,在我们的代码中,无论x是什么,我们都用了append([x]),这会导致: 当x是标量时:二维张量(sample_length,1) -> 符合要求 当x是向量(长度为n)时:三维张量(sample_length, 1, n) -> 而我们通常希望的是二维(sample_length, n) 因此,我们需要去掉内层的列表包裹,改为: dataset_x.append(x) 但是,要注意x必须是数值(标量)或者一维数组(向量)。如果是多维,则需要展平。 所以,修改后的代码: def build_dataset(sample_length, vocab): dataset_x = [] dataset_y = [] for i in range(sample_length): x, y = build_sample(vocab) # 假设x和y都是数值或一维向量 dataset_x.append(x) # 直接添加,不再包裹一层列表 dataset_y.append(y) print(dataset_x, dataset_y) # 打印检查 return torch.FloatTensor(dataset_x), torch.FloatTensor(dataset_y) 这样,如果x是长度为n的一维向量,那么得到的dataset_x张量的形状就是(sample_length, n),而dataset_y同理。 但是,注意模型可能要求输入是三维的(比如RNN需要序列数据,形状为(batch, seq_len, features)),而我们这里构建的是二维(batch, features)。所以我们需要考虑: 如果模型要求输入是三维的,那么我们就需要将每个样本x构建成一个序列(即每个样本是一个时间步序列,每个时间步有一个特征向量)。那么在我们的build_sample中,x应该是一个二维数组(矩阵),表示一个序列(时间步数×每个时间步的特征数)。然后我们直接append(x)(x作为矩阵),这样整个dataset_x就会是一个三维张量(样本数×时间步数×特征数)。 总结错误原因:在构建数据集时,对每个样本x额外包裹了一层列表,导致张量多出一个维度(大小为1),破坏了预期的维度结构。 解决方案: 1. 去掉包裹层:将`dataset_x.append([x])`改为`dataset_x.append(x)` 2. 确保build_sample返回的x的形状符合预期(如果是单个特征,则返回标量,这样构建出来就是(sample_length,1))或者返回一维向量(特征数)或二维向量(时间步,特征)等,根据模型需要。 注意:同时检查build_sample返回的y,因为同样的操作可能会影响y。根据错误信息,问题出在输入数据(x)还是标签(y)?错误信息没有明确,但根据错误描述,可能是输入也可能是标签。所以我们需要同时检查。 修改代码后,我们打印返回的张量形状,确保其符合模型输入要求。 如果修改后,我们发现维度仍然不对,那么我们需要检查build_sample函数,确认其返回的x和y到底是什么形状。 举例: 假设build_sample返回的x是一个长度为3的向量,那么修改前: dataset_x: 形状为 (sample_length, 1, 3) 修改后: dataset_x: 形状为 (sample_length, 3) 但是,如果模型期望的是(sample_length, 1, 1)?那么显然(sample_length, 3)也不对。所以我们需要根据模型的要求来调整。 因此,我们还需要知道模型期望的输入形状。如果模型期望输入是三维的,那么我们需要将每个样本构建成三维中的单个样本(即形状为(1, n)的序列,其中n是特征数)?这样整个数据集就是(sample_length, 1, n)。那么我们可以这样构建: dataset_x.append(x.reshape(1, -1)) # 将x变成1行多列(即一个时间步,多个特征) 这样,每个样本就是一个形状为(1,n)的矩阵,然后整个dataset_x就是(sample_length, 1, n)的三维张量。 但是,如果模型期望每个样本是一个时间步序列,并且序列长度为L,每个时间步特征数为F,那么build_sample应该返回一个L×F的矩阵,然后我们直接append这个矩阵,这样整个dataset_x的形状就是(sample_length, L, F)。 综上所述,我们需要: 1. 确定模型输入的期望形状。 2. 调整build_sample函数,使其返回的形状符合要求(每个样本是一个二维矩阵,或者是一维向量,具体根据模型输入要求)。 3. 调整build_dataset中的append操作,不再额外增加维度。 最后,我们还需要注意:标签y的维度要求。例如,如果是分类问题,标签可能是整数(标量),那么我们就直接append(y)即可,这样得到一维张量(长度为sample_length)。但如果是多标签,则可能需要处理。 因此,我们修改代码为: def build_dataset(sample_length, vocab): dataset_x = [] dataset_y = [] for i in range(sample_length): x, y = build_sample(vocab) # 根据模型期望的输入形状处理x # 假设我们希望x的形状为 (seq_len, features) 的二维数组 # 如果x是一维向量,我们可以将其reshape为(1, -1)表示一个时间步 # 或者根据实际需要调整,这里需要根据模型来定 # 同样,对y也可能需要调整,如果是分类问题,可能需要将类别索引转换为整数,或者one-hot等 dataset_x.append(x) # 这里x应该是一个二维数组(序列)或一维数组(单个向量)? dataset_y.append(y) # 转换为张量 tensor_x = torch.FloatTensor(dataset_x) # 注意:这里要求dataset_x中每个元素的形状相同 tensor_y = torch.FloatTensor(dataset_y) # 同理 return tensor_x, tensor_y 注意:如果每个样本x的形状不一致(比如序列长度不同),则不能直接转换为张量,需要使用padding等。但这里假设每个样本的序列长度相同。 因此,关键还是在于build_sample返回的x和y的形状。 建议:在build_dataset函数中打印第一个样本x和y的形状,以便检查。 示例:在循环内,第一次获取样本后打印: if i == 0: print(f"样本0: x的形状: {np.array(x).shape}, y的形状: {np.array(y).shape}") 然后根据打印结果调整。 结论:当前错误是因为在append时多包裹了一层列表,导致维度增加。去除多余的包裹层,并根据模型期望的形状调整build_sample的返回值。 注意:如果build_sample返回的x是标量,那么修改后dataset_x的形状就是(sample_length, ),这不符合二维输入要求(缺少特征维度)。此时,我们可以使用: dataset_x.append([x]) # 这样得到 (sample_length, 1) 的二维张量 但是不要用两层列表包裹(即不要用append([[x]]))。 因此,具体处理要根据x的类型: 我们希望x总是以向量的形式返回(即使只有一个特征,也返回一个列表,如[0.5]),这样我们就可以直接append(x)(x是列表),得到二维张量(sample_length, 特征数)。 所以,修改build_sample函数,让它返回的x和y都是列表(或一维数组)可能更合适。 最后,我们修改build_dataset函数如下: def build_dataset(sample_length, vocab): dataset_x = [] dataset_y = [] for i in range(sample_length): x, y = build_sample(vocab) # 确保x和y都是列表(一维) if isinstance(x, (int, float)): x = [x] # 将标量转为列表 if isinstance(y, (int, float)): y = [y] dataset_x.append(x) dataset_y.append(y) return torch.FloatTensor(dataset_x), torch.FloatTensor(dataset_y) 这样,无论build_sample返回标量还是向量,我们都能保证dataset_x和dataset_y是二维张量(样本数×特征数)。 但是,如果模型要求三维输入(比如RNN),那么我们就需要在build_sample中构建序列,返回一个二维列表(每个样本是一个序列,每个时间步是一个特征向量)。此时,我们直接append这个二维列表,就会得到三维张量(样本数×时间步长×特征数)。 所以,如果模型要求三维输入,那么在build_sample中我们就需要生成一个序列(列表的列表),例如: x = [[0.1, 0.2], [0.3, 0.4]] # 两个时间步,每个时间步两个特征 这样,append(x)后,整个dataset_x就是一个三维张量。 因此,我们不需要在build_dataset中做额外的包裹,而是让build_sample返回正确维度的数据。 最终建议: 1. 去掉build_dataset中的多余包裹:将`append([x])`改为`append(x)` 2. 在build_sample中控制返回值的维度,使其符合模型输入要求(二维数组表示序列,三维数组?通常不需要,因为每个样本就是一个序列,序列由向量组成,所以二维数组就够了) 修改后的代码: def build_dataset(sample_length, vocab): dataset_x = [] dataset_y = [] for i in range(sample_length): x, y = build_sample(vocab) dataset_x.append(x) # x可以是标量、一维数组、二维数组等 dataset_y.append(y) # 转换为张量 tensor_x = torch.tensor(dataset_x, dtype=torch.float) tensor_y = torch.tensor(dataset_y, dtype=torch.float) # 注意:如果y是整数标签,可能需要用torch.long return tensor_x, tensor_y 注意:这里使用了torch.tensor而不是torch.FloatTensor,因为这样我们可以指定dtype。对于标签y,如果是分类的整数标签,应该用torch.long。 同时,我们需要确保每个样本x的形状是一致的。 这样修改后,张量的维度就会符合build_sample返回的x的维度结构,从而避免错误。 最后,因为错误信息提到dim2上期望1得到3,所以如果我们的模型要求输入是三维的(batch, seq, features),那么每个样本x应该是一个二维数组(seq, features),这样整个数据集就是(batch, seq, features)。如果模型要求features为1,而我们返回的features为3,那么就需要调整build_sample返回的特征数量。 因此,我们可能需要检查模型第一层的输入特征维度,并相应地调整build_sample生成的特征数。 综上所述,具体步骤: 1. 修改build_dataset,去除多余包裹。 2. 检查build_sample返回的x的维度(特别是特征维度)是否符合模型要求。 3. 如果模型要求特征维度为1,而build_sample返回的特征维度为3,则需要在build_sample中减少特征数量,或者修改模型第一层的输入维度。 请根据上述步骤修改代码,并检查维度是否匹配。 </think>### 问题分析:维度不匹配错误 `expected sequence of length 1 at dim 2 (got 3)` 结合错误信息和代码 `build_dataset` 函数,核心问题在于**构建张量时维度结构不符合模型预期**。具体分析如下: #### 错误根源 ```python def build_dataset(sample_length,vocab): dataset_x = []; dataset_y = [] for i in range(sample_length): x, y = build_sample(vocab) # 假设返回x为标量,y为向量 dataset_x.append([x]) # 额外添加列表包裹 dataset_y.append([y]) # 额外添加列表包裹 return torch.FloatTensor(dataset_x), torch.FloatTensor(dataset_y) ``` 1. **维度膨胀**: - 当 `build_sample` 返回的 `y` 是长度为3的向量(如 `[0.2, 0.5, 0.3]`) - `[y]` 操作使其变成嵌套列表:`[[0.2, 0.5, 0.3]]` - 最终 `dataset_y` 的形状为 `(sample_length, 1, 3)`,而非预期的 `(sample_length, 3)` 2. **模型期望**: - 下游模型要求输入维度为 `(batch_size, seq_length, 1)` - 实际传入张量第三维(`dim=2`)长度为3,引发维度不匹配错误 --- ### 解决方案:重构数据维度 #### 方法1:直接展平嵌套结构(推荐) ```python def build_dataset(sample_length, vocab): dataset_x, dataset_y = [], [] for _ in range(sample_length): x, y = build_sample(vocab) dataset_x.append(x) # 直接添加标量或向量 dataset_y.append(y) # 直接添加向量,避免嵌套 # 转换为张量并确保正确维度 tensor_x = torch.tensor(dataset_x, dtype=torch.float32).unsqueeze(1) # 添加序列维度 tensor_y = torch.tensor(dataset_y, dtype=torch.float32) return tensor_x, tensor_y ``` **关键修改**: - 移除 `[x]` 和 `[y]` 的多余列表包裹 - 通过 `unsqueeze(1)` 为输入添加序列维度(若需 `(batch, seq_len=1, features)`) - 输出维度: - `tensor_x`: `(sample_length, 1)` → 模型通常要求 `(batch, seq_len, features)` - `tensor_y`: `(sample_length, 3)` #### 方法2:维度校正(兼容旧代码) ```python def build_dataset(sample_length, vocab): dataset_x, dataset_y = [], [] for _ in range(sample_length): x, y = build_sample(vocab) dataset_x.append([x]) dataset_y.append(y) # 仅对y取消嵌套 # 转换后校正维度 tensor_x = torch.FloatTensor(dataset_x) tensor_y = torch.FloatTensor(dataset_y) # 压缩多余维度 [N,1,3] -> [N,3] tensor_y = tensor_y.squeeze(1) return tensor_x, tensor_y ``` #### 方法3:修改数据生成源 在 `build_sample` 中确保输出维度一致性: ```python def build_sample(vocab): # 原始返回 y = [0.1, 0.4, 0.5] (3维) # 改为返回标量(如取平均值) y_value = sum(y_vector) / len(y_vector) # 标量输出 return x_scalar, y_value ``` --- ### 预防措施 1. **维度断言**: ```python tensor_x, tensor_y = build_dataset(100, vocab) assert tensor_y.dim() == 2, f"应为二维张量,实际维度:{tensor_y.dim()}" assert tensor_y.size(1) == 1, f"dim1应为长度1,实际:{tensor_y.size(1)}" ``` 2. **统一维度接口**: ```python class NormalizedDataset(Dataset): def __init__(self, raw_data): self.data = [self._normalize(item) for item in raw_data] def _normalize(self, item): x, y = item return torch.tensor(x).view(1, -1), torch.tensor(y).view(-1, 1) # 显式定义维度 ``` 3. **模型输入层适配**: ```python # 若实际需保留3维特征 self.model = nn.Sequential( nn.Linear(3, 1), # 将特征从3维映射到1维 nn.ReLU() ) ``` ### 根本原因总结 | 问题环节 | 错误表现 | 修复方案 | |----------|----------|----------| | 数据嵌套 | `append([y])` → 添加多余维度 | 改用 `append(y)` | | 特征维度 | `build_sample` 返回高维向量 | 源头降维或模型适配 | | 维度检查缺失 | 未验证张量形状 | 添加维度断言 | 通过移除多余的列表嵌套操作,可确保张量维度与模型预期严格一致。建议优先采用**方案1**进行重构,这是最彻底的解决方案[^1][^2]。 --- ### 相关问题 1. 如何在PyTorch中快速检查张量的维度结构? 2. 有哪些常用的张量维度变换方法(如`squeeze`/`unsqueeze`)? 3. 当模型输入维度与数据维度不匹配时,如何设计兼容层? 4. 如何避免PyTorch数据加载过程中的维度膨胀问题? 5. 在多模态数据处理中如何统一不同来源的维度格式?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值